Also, try out a way to have per-module LESS variables defined in PHP.
This might come in handy in the future… Maybe for skin theme support?
(I recommend reviewing the file changes in the order below. :D)
includes/resourceloader/ResourceLoaderFileModule.php
* Pass the context (ResourceLoaderContext) deeper down via
readStyleFiles() and readStyleFile(). We need it to compile the
.less files for the right language.
* Extract LESS compiler creation to getLessCompiler().
* Allow passing a LESS compiler instance to compileLessFile(), rather
than getting one after the method is called.
All of the changes are backwards-compatible.
includes/resourceloader/ResourceLoaderEditToolbarModule.php
* New module to support getting the language data and passing it to
LESS variables.
It might be a good idea to factor out a reusable class for a LESS
module with additional variables, but that would require more
attention to design than I gave it.
resources/src/mediawiki.action/mediawiki.action.edit.toolbar/mediawiki.action.edit.toolbar.less
* Glue code to use the language data defined by the module above and
put it in final CSS.
includes/EditPage.php
* Do not hardcode image URLs in output HTML, as they are provided in
CSS now. This gets rid of some usage of globals.
In fact, we should be able to finally move the inline JavaScript
calls out of getEditToolbar(), but I'm already introducing too many
changes for one patch. That can be done later.
languages/Language.php
* Add getImageFiles() to complement existing getImageFile() method.
Misleadingly named, it returns paths for images for the toolbar
only (and no other ones at all).
skins/common/ → resources/src/mediawiki.action/mediawiki.action.edit.toolbar/
* Moved all of the button images to new location.
Also, boring cleanup that was harder before because we treated the
paths as public API:
* Placed default ones in en/ subdirectory.
* Renamed cyrl/ to ru/.
* Renamed ksh/button_S_italic.png → ksh/button_italic.png.
languages/messages/
* Adjusting paths and filenames for the changes above.
resources/src/mediawiki.action/mediawiki.action.edit.css
resources/src/mediawiki.action/mediawiki.action.edit.js
* Added styles and updated the script to make it possible to have
non-<img> elements as toolbar buttons.
* Consolidated styles that were already required, but defined
somewhere else:
* `cursor: pointer;` (from shared.css)
* `vertical-align: middle;` (from commonElements.css)
Bug: 69277
Change-Id: I39d8ed4258c7da0fe4fe4c665cdb26c86420769c
'includes/resourceloader/DerivativeResourceLoaderContext.php',
'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php',
'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php',
+ 'ResourceLoaderEditToolbarModule' => 'includes/resourceloader/ResourceLoaderEditToolbarModule.php',
'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php',
'ResourceLoaderFilePageModule' => 'includes/resourceloader/ResourceLoaderFilePageModule.php',
'ResourceLoaderFilePath' => 'includes/resourceloader/ResourceLoaderFilePath.php',
* @return string
*/
static function getEditToolbar() {
- global $wgStylePath, $wgContLang, $wgLang, $wgOut;
+ global $wgContLang, $wgOut;
global $wgEnableUploads, $wgForeignFileRepos;
$imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
/**
* $toolarray is an array of arrays each of which includes the
- * filename of the button image (without path), the opening
- * tag, the closing tag, optionally a sample text that is
+ * opening tag, the closing tag, optionally a sample text that is
* inserted between the two when no selection is highlighted
* and. The tip text is shown when the user moves the mouse
* over the button.
+ *
+ * Images are defined in ResourceLoaderEditToolbarModule.
*/
$toolarray = array(
array(
- 'image' => $wgLang->getImageFile( 'button-bold' ),
'id' => 'mw-editbutton-bold',
'open' => '\'\'\'',
'close' => '\'\'\'',
'tip' => wfMessage( 'bold_tip' )->text(),
),
array(
- 'image' => $wgLang->getImageFile( 'button-italic' ),
'id' => 'mw-editbutton-italic',
'open' => '\'\'',
'close' => '\'\'',
'tip' => wfMessage( 'italic_tip' )->text(),
),
array(
- 'image' => $wgLang->getImageFile( 'button-link' ),
'id' => 'mw-editbutton-link',
'open' => '[[',
'close' => ']]',
'tip' => wfMessage( 'link_tip' )->text(),
),
array(
- 'image' => $wgLang->getImageFile( 'button-extlink' ),
'id' => 'mw-editbutton-extlink',
'open' => '[',
'close' => ']',
'tip' => wfMessage( 'extlink_tip' )->text(),
),
array(
- 'image' => $wgLang->getImageFile( 'button-headline' ),
'id' => 'mw-editbutton-headline',
'open' => "\n== ",
'close' => " ==\n",
'tip' => wfMessage( 'headline_tip' )->text(),
),
$imagesAvailable ? array(
- 'image' => $wgLang->getImageFile( 'button-image' ),
'id' => 'mw-editbutton-image',
'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
'close' => ']]',
'tip' => wfMessage( 'image_tip' )->text(),
) : false,
$imagesAvailable ? array(
- 'image' => $wgLang->getImageFile( 'button-media' ),
'id' => 'mw-editbutton-media',
'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
'close' => ']]',
'tip' => wfMessage( 'media_tip' )->text(),
) : false,
array(
- 'image' => $wgLang->getImageFile( 'button-nowiki' ),
'id' => 'mw-editbutton-nowiki',
'open' => "<nowiki>",
'close' => "</nowiki>",
'tip' => wfMessage( 'nowiki_tip' )->text(),
),
array(
- 'image' => $wgLang->getImageFile( 'button-sig' ),
'id' => 'mw-editbutton-signature',
'open' => '--~~~~',
'close' => '',
'tip' => wfMessage( 'sig_tip' )->text(),
),
array(
- 'image' => $wgLang->getImageFile( 'button-hr' ),
'id' => 'mw-editbutton-hr',
'open' => "\n----\n",
'close' => '',
}
$params = array(
- $wgStylePath . '/common/images/' . $tool['image'],
+ // Images are defined in ResourceLoaderEditToolbarModule
+ false,
// Note that we use the tip both for the ALT tag and the TITLE tag of the image.
// Older browsers show a "speedtip" type message only for ALT.
// Ideally these should be different, realistically they
--- /dev/null
+<?php
+/**
+ * Resource loader module for the edit toolbar.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * ResourceLoader module for the edit toolbar.
+ *
+ * @since 1.24
+ */
+class ResourceLoaderEditToolbarModule extends ResourceLoaderFileModule {
+ /**
+ * Serialize a string (escape and quote) for use as a CSS string value.
+ * http://www.w3.org/TR/2013/WD-cssom-20131205/#serialize-a-string
+ *
+ * @param string $value
+ * @return string
+ */
+ private static function cssSerializeString( $value ) {
+ if ( strstr( $value, "\0" ) ) {
+ throw new Exception( "Invalid character in CSS string" );
+ }
+ $value = strtr( $value, array( '\\' => '\\\\', '"' => '\\"' ) );
+ $value = preg_replace_callback( '/[\x01-\x1f\x7f-\x9f]/', function ( $match ) {
+ return '\\' . base_convert( ord( $match[0] ), 10, 16 ) . ' ';
+ }, $value );
+ return '"' . $value . '"';
+ }
+
+ /**
+ * Get language-specific LESS variables for this module.
+ *
+ * @return array
+ */
+ private function getLessVars( ResourceLoaderContext $context ) {
+ $language = Language::factory( $context->getLanguage() );
+
+ // This is very conveniently formatted and we can pass it right through
+ $vars = $language->getImageFiles();
+
+ // lessc tries to be helpful and parse our variables as LESS source code
+ foreach ( $vars as $key => &$value ) {
+ $value = self::cssSerializeString( $value );
+ }
+
+ return $vars;
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return int UNIX timestamp
+ */
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ return max(
+ parent::getModifiedTime( $context ),
+ $this->getHashMtime( $context )
+ );
+ }
+
+ /**
+ * @param ResourceLoaderContext $context
+ * @return string Hash
+ */
+ public function getModifiedHash( ResourceLoaderContext $context ) {
+ return md5(
+ parent::getModifiedHash( $context ) .
+ serialize( $this->getLessVars( $context ) )
+ );
+ }
+
+ /**
+ * Get a LESS compiler instance for this module.
+ *
+ * Set our variables in it.
+ *
+ * @throws MWException
+ * @param ResourceLoaderContext $context
+ * @return lessc
+ */
+ protected function getLessCompiler( ResourceLoaderContext $context ) {
+ $compiler = parent::getLessCompiler();
+ $compiler->setVariables( $this->getLessVars( $context ) );
+ return $compiler;
+ }
+}
public function getStyles( ResourceLoaderContext $context ) {
$styles = $this->readStyleFiles(
$this->getStyleFiles( $context ),
- $this->getFlip( $context )
+ $this->getFlip( $context ),
+ $context
);
// Collect referenced files
$this->localFileRefs = array_unique( $this->localFileRefs );
*
* @param array $styles List of media type/list of file paths pairs, to read, remap and
* concetenate
- *
* @param bool $flip
+ * @param ResourceLoaderContext $context (optional)
*
* @throws MWException
* @return array List of concatenated and remapped CSS data from $styles,
* keyed by media type
*/
- public function readStyleFiles( array $styles, $flip ) {
+ public function readStyleFiles( array $styles, $flip, $context = null ) {
if ( empty( $styles ) ) {
return array();
}
$uniqueFiles = array_unique( $files, SORT_REGULAR );
$styleFiles = array();
foreach ( $uniqueFiles as $file ) {
- $styleFiles[] = $this->readStyleFile( $file, $flip );
+ $styleFiles[] = $this->readStyleFile( $file, $flip, $context );
}
$styles[$media] = implode( "\n", $styleFiles );
}
*
* @param string $path File path of style file to read
* @param bool $flip
+ * @param ResourceLoaderContext $context (optional)
*
* @return string CSS data in script file
* @throws MWException If the file doesn't exist
*/
- protected function readStyleFile( $path, $flip ) {
+ protected function readStyleFile( $path, $flip, $context = null ) {
$localPath = $this->getLocalPath( $path );
$remotePath = $this->getRemotePath( $path );
if ( !file_exists( $localPath ) ) {
}
if ( $this->getStyleSheetLang( $localPath ) === 'less' ) {
- $style = $this->compileLessFile( $localPath );
+ $compiler = $this->getLessCompiler( $context );
+ $style = $this->compileLessFile( $localPath, $compiler );
$this->hasGeneratedStyles = true;
} else {
$style = file_get_contents( $localPath );
* @since 1.22
* @throws Exception If lessc encounters a parse error
* @param string $fileName File path of LESS source
+ * @param lessc $compiler Compiler to use, if not default
* @return string CSS source
*/
- protected function compileLessFile( $fileName ) {
- $compiler = ResourceLoader::getLessCompiler( $this->getConfig() );
+ protected function compileLessFile( $fileName, $compiler = null ) {
+ if ( !$compiler ) {
+ $compiler = $this->getLessCompiler();
+ }
$result = $compiler->compileFile( $fileName );
$this->localFileRefs += array_keys( $compiler->allParsedFiles() );
return $result;
}
+
+ /**
+ * Get a LESS compiler instance for this module in given context.
+ *
+ * Just calls ResourceLoader::getLessCompiler() by default to get a global compiler.
+ *
+ * @param ResourceLoaderContext $context
+ * @throws MWException
+ * @since 1.24
+ * @return lessc
+ */
+ protected function getLessCompiler( ResourceLoaderContext $context = null ) {
+ return ResourceLoader::getLessCompiler( $this->getConfig() );
+ }
}
return self::$dataCache->getSubitem( $this->mCode, 'imageFiles', $image );
}
+ /**
+ * @return array
+ * @since 1.24
+ */
+ function getImageFiles() {
+ return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
+ }
+
/**
* @return array
*/
* basis if needed.
*/
$imageFiles = array(
- 'button-bold' => 'button_bold.png',
- 'button-italic' => 'button_italic.png',
- 'button-link' => 'button_link.png',
- 'button-extlink' => 'button_extlink.png',
- 'button-headline' => 'button_headline.png',
- 'button-image' => 'button_image.png',
- 'button-media' => 'button_media.png',
- 'button-nowiki' => 'button_nowiki.png',
- 'button-sig' => 'button_sig.png',
- 'button-hr' => 'button_hr.png',
+ 'button-bold' => 'en/button_bold.png',
+ 'button-italic' => 'en/button_italic.png',
+ 'button-link' => 'en/button_link.png',
+ 'button-extlink' => 'en/button_extlink.png',
+ 'button-headline' => 'en/button_headline.png',
+ 'button-image' => 'en/button_image.png',
+ 'button-media' => 'en/button_media.png',
+ 'button-nowiki' => 'en/button_nowiki.png',
+ 'button-sig' => 'en/button_sig.png',
+ 'button-hr' => 'en/button_hr.png',
);
/**
);
$imageFiles = array(
- 'button-italic' => 'ksh/button_S_italic.png',
+ 'button-italic' => 'ksh/button_italic.png',
);
$linkPrefixExtension = false;
$imageFiles = array(
- 'button-bold' => 'cyrl/button_bold.png',
- 'button-italic' => 'cyrl/button_italic.png',
- 'button-link' => 'cyrl/button_link.png',
+ 'button-bold' => 'ru/button_bold.png',
+ 'button-italic' => 'ru/button_italic.png',
+ 'button-link' => 'ru/button_link.png',
);
$linkTrail = '/^([a-zабвгдеёжзийклмнопрстуфхцчшщъыьэюя]+)(.*)$/sDu';
'mediawiki.action.edit' => array(
'scripts' => 'resources/src/mediawiki.action/mediawiki.action.edit.js',
+ 'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.css',
'dependencies' => array(
'mediawiki.action.edit.styles',
+ 'mediawiki.action.edit.toolbar',
'jquery.textSelection',
'jquery.byteLimit',
),
'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.styles.css',
'position' => 'top',
),
+ 'mediawiki.action.edit.toolbar' => array(
+ 'class' => 'ResourceLoaderEditToolbarModule',
+ 'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.toolbar/mediawiki.action.edit.toolbar.less',
+ ),
'mediawiki.action.edit.collapsibleFooter' => array(
'scripts' => 'resources/src/mediawiki.action/mediawiki.action.edit.collapsibleFooter.js',
'styles' => 'resources/src/mediawiki.action/mediawiki.action.edit.collapsibleFooter.css',
--- /dev/null
+/*!
+ * Styles for elements of the editing form, loaded only when JavaScript is enabled.
+ */
+
+.mw-toolbar-editbutton {
+ width: 23px;
+ height: 22px;
+ cursor: pointer;
+ vertical-align: middle;
+ /* Cross-browser inline-block */
+ /* Firefox 2 */
+ display: -moz-inline-block;
+ /* Modern browsers */
+ display: inline-block;
+ /* IE7 */
+ zoom: 1;
+ *display: inline;
+}
* @private
*/
function insertButton( b, speedTip, tagOpen, tagClose, sampleText, imageId ) {
+ var $button;
+
// Backwards compatibility
if ( typeof b !== 'object' ) {
b = {
imageId: imageId
};
}
- var $image = $( '<img>' ).attr( {
- width: 23,
- height: 22,
+
+ if ( b.imageFile ) {
+ $button = $( '<img>' ).attr( {
src: b.imageFile,
alt: b.speedTip,
title: b.speedTip,
id: b.imageId || undefined,
'class': 'mw-toolbar-editbutton'
- } ).click( function ( e ) {
+ } );
+ } else {
+ $button = $( '<div>' ).attr( {
+ title: b.speedTip,
+ id: b.imageId || undefined,
+ 'class': 'mw-toolbar-editbutton'
+ } );
+ }
+
+ $button.click( function ( e ) {
if ( b.onClick !== undefined ) {
b.onClick( e );
} else {
return false;
} );
- $toolbar.append( $image );
+ $toolbar.append( $button );
}
isReady = false;
* @param {Object} button Object with the following properties.
* You are required to provide *either* the `onClick` parameter, or the three parameters
* `tagOpen`, `tagClose` and `sampleText`, but not both (they're mutually exclusive).
- * @param {string} button.imageFile Image to use for the button.
+ * @param {string} [button.imageFile] Image to use for the button.
* @param {string} button.speedTip Tooltip displayed when user mouses over the button.
* @param {Function} [button.onClick] Function to be executed when the button is clicked.
* @param {string} [button.tagOpen]
* @param {string} [button.sampleText] Alternative to `onClick`. `tagOpen`, `tagClose` and
* `sampleText` together provide the markup that should be inserted into page text at
* current cursor position.
- * @param {string} [button.imageId] `id` attribute of the button HTML element.
+ * @param {string} [button.imageId] `id` attribute of the button HTML element. Can be
+ * used to define the image with CSS if it's not provided as `imageFile`.
*/
addButton: function () {
if ( isReady ) {
--- /dev/null
+
+button_italic.png
+-------------------
+Source : http://commons.wikimedia.org/wiki/Image:Button_S_italic.png
+License: Public domain
+Author : Purodha Blissenbach, http://ksh.wikipedia.org/wiki/User:Purodha
+
--- /dev/null
+button_bold.png
+---------------
+Source : http://commons.wikimedia.org/wiki/Image:Button_bold_ukr.png
+License: Public domain
+Author : Alexey Belomoev
+
+button_italic.png
+------------------------
+Source : http://commons.wikimedia.org/wiki/Image:Button_italic_ukr.png
+License: Public domain
+Author : Alexey Belomoev
+
+button_link.png
+-----------------
+Source : http://commons.wikimedia.org/wiki/Image:Button_internal_link_ukr.png
+License: GPL
+Author : Saproj, Erik Möller
--- /dev/null
+@import "mediawiki.mixins";
+
+#mw-editbutton-bold {
+ .background-image("images/@{button-bold}");
+}
+
+#mw-editbutton-italic {
+ .background-image("images/@{button-italic}");
+}
+
+#mw-editbutton-link {
+ .background-image("images/@{button-link}");
+}
+
+#mw-editbutton-extlink {
+ .background-image("images/@{button-extlink}");
+}
+
+#mw-editbutton-headline {
+ .background-image("images/@{button-headline}");
+}
+
+#mw-editbutton-image {
+ .background-image("images/@{button-image}");
+}
+
+#mw-editbutton-media {
+ .background-image("images/@{button-media}");
+}
+
+#mw-editbutton-nowiki {
+ .background-image("images/@{button-nowiki}");
+}
+
+// Who decided to make only this single one different than the name of the data item?
+#mw-editbutton-signature {
+ .background-image("images/@{button-sig}");
+}
+
+#mw-editbutton-hr {
+ .background-image("images/@{button-hr}");
+}
clear: both;
}
-#toolbar img {
- cursor: pointer;
-}
-
/**
* File description page
*/
+++ /dev/null
-button_bold.png
----------------
-Source : http://commons.wikimedia.org/wiki/Image:Button_bold_ukr.png
-License: Public domain
-Author : Alexey Belomoev
-
-button_italic.png
-------------------------
-Source : http://commons.wikimedia.org/wiki/Image:Button_italic_ukr.png
-License: Public domain
-Author : Alexey Belomoev
-
-button_link.png
------------------
-Source : http://commons.wikimedia.org/wiki/Image:Button_internal_link_ukr.png
-License: GPL
-Author : Saproj, Erik Möller
+++ /dev/null
-
-button_S_italic.png
--------------------
-Source : http://commons.wikimedia.org/wiki/Image:Button_S_italic.png
-License: Public domain
-Author : Purodha Blissenbach, http://ksh.wikipedia.org/wiki/User:Purodha
-
* @see https://github.com/sebastianbergmann/phpunit/blob/master/src/Extensions/PhptTestCase.php
* @author Sam Smith <samsmith@wikimedia.org>
*/
-class LessFileCompilationTest extends MediaWikiTestCase {
+class LessFileCompilationTest extends ResourceLoaderTestCase {
/**
* @var string $file
"$thisString must refer to a readable file"
);
- $compiler = ResourceLoader::getLessCompiler( RequestContext::getMain()->getConfig() );
+ $rlContext = static::getResourceLoaderContext();
+
+ // Bleh
+ $method = new ReflectionMethod( $this->module, 'getLessCompiler' );
+ $method->setAccessible( true );
+ $compiler = $method->invoke( $this->module, $rlContext );
+
$this->assertNotNull( $compiler->compileFile( $this->file ) );
}