'LanguageBe_tarask' => __DIR__ . '/languages/classes/LanguageBe_tarask.php',
'LanguageBg' => __DIR__ . '/languages/classes/LanguageBg.php',
'LanguageBs' => __DIR__ . '/languages/classes/LanguageBs.php',
+ 'LanguageCode' => __DIR__ . '/languages/LanguageCode.php',
'LanguageConverter' => __DIR__ . '/languages/LanguageConverter.php',
'LanguageCu' => __DIR__ . '/languages/classes/LanguageCu.php',
'LanguageDsb' => __DIR__ . '/languages/classes/LanguageDsb.php',
'MediaWiki\\Tidy\\RaggettInternalHHVM' => __DIR__ . '/includes/tidy/RaggettInternalHHVM.php',
'MediaWiki\\Tidy\\RaggettInternalPHP' => __DIR__ . '/includes/tidy/RaggettInternalPHP.php',
'MediaWiki\\Tidy\\RaggettWrapper' => __DIR__ . '/includes/tidy/RaggettWrapper.php',
+ 'MediaWiki\\Tidy\\RemexCompatFormatter' => __DIR__ . '/includes/tidy/RemexCompatFormatter.php',
+ 'MediaWiki\\Tidy\\RemexCompatMunger' => __DIR__ . '/includes/tidy/RemexCompatMunger.php',
+ 'MediaWiki\\Tidy\\RemexDriver' => __DIR__ . '/includes/tidy/RemexDriver.php',
+ 'MediaWiki\\Tidy\\RemexMungerData' => __DIR__ . '/includes/tidy/RemexMungerData.php',
'MediaWiki\\Tidy\\TidyDriverBase' => __DIR__ . '/includes/tidy/TidyDriverBase.php',
'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php',
'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php',
"ext-xml": "*",
"liuggio/statsd-php-client": "1.0.18",
"mediawiki/at-ease": "1.1.0",
- "oojs/oojs-ui": "0.19.4",
+ "oojs/oojs-ui": "0.19.5",
"oyejorge/less.php": "1.7.0.13",
"php": ">=5.5.9",
"psr/log": "1.0.2",
"wikimedia/ip-set": "1.1.0",
"wikimedia/php-session-serializer": "1.0.4",
"wikimedia/relpath": "1.0.3",
+ "wikimedia/remex-html": "1.0.0",
"wikimedia/running-stat": "1.1.0",
"wikimedia/scoped-callback": "1.0.0",
"wikimedia/utfnormal": "1.1.0",
* MediaWiki out of the box. Not all languages listed there have translations,
* see languages/messages/ for the list of languages with some localisation.
*
- * Warning: Don't use language codes listed in $wgDummyLanguageCodes like "no"
- * for Norwegian (use "nb" instead), or things will break unexpectedly.
+ * Warning: Don't use any of MediaWiki's deprecated language codes listed in
+ * LanguageCode::getDeprecatedCodeMapping or $wgDummyLanguageCodes, like "no"
+ * for Norwegian (use "nb" instead). If you do, things will break unexpectedly.
*
* This defines the default interface language for all users, but users can
* change it in their preferences.
$wgExtraLanguageNames = [];
/**
- * List of language codes that don't correspond to an actual language.
- * These codes are mostly left-offs from renames, or other legacy things.
- * This array makes them not appear as a selectable language on the installer,
- * and excludes them when running the transstat.php script.
- */
-$wgDummyLanguageCodes = [
- 'als' => 'gsw',
- 'bat-smg' => 'sgs',
- 'be-x-old' => 'be-tarask',
- 'bh' => 'bho',
- 'fiu-vro' => 'vro',
- 'no' => 'nb',
- 'qqq' => 'qqq', # Used for message documentation.
- 'qqx' => 'qqx', # Used for viewing message keys.
- 'roa-rup' => 'rup',
- 'simple' => 'en',
- 'zh-classical' => 'lzh',
- 'zh-min-nan' => 'nan',
- 'zh-yue' => 'yue',
+ * List of mappings from one language code to another.
+ * This array makes the codes not appear as a selectable language on the
+ * installer, and excludes them when running the transstat.php script.
+ *
+ * In Setup.php, the variable $wgDummyLanguageCodes is created by combining
+ * these codes with a list of "deprecated" codes, which are mostly leftovers
+ * from renames or other legacy things, and the internal codes 'qqq' and 'qqx'.
+ * If a mapping in $wgExtraLanguageCodes collide with a built-in mapping, the
+ * value in $wgExtraLanguageCodes will be used.
+ *
+ * @since 1.29
+ */
+$wgExtraLanguageCodes = [
+ 'bh' => 'bho', // Bihari language family
+ 'no' => 'nb', // Norwegian language family
+ 'simple' => 'en', // Simple English
];
/**
* - RaggettInternalPHP: Use the PECL extension
* - RaggettExternal: Shell out to an external binary (tidyBin)
* - Html5Depurate: Use external Depurate service
- * - Html5Internal: Use the built-in HTML5 balancer
+ * - Html5Internal: Use the Balancer library in PHP
+ * - RemexHtml: Use the RemexHtml library in PHP
*
* - tidyConfigFile: Path to configuration file for any of the Raggett drivers
* - debugComment: True to add a comment to the output with warning messages
$wgCanonicalNamespaceNames = $wgCanonicalNamespaceNames + $wgExtraNamespaces;
}
+// Merge in the legacy language codes, unless overridden in the config
+if ( !isset( $wgDummyLanguageCodes ) ) {
+ $wgDummyLanguageCodes = [
+ 'qqq' => 'qqq', // Used for message documentation
+ 'qqx' => 'qqx', // Used for viewing message keys
+ ] + $wgExtraLanguageCodes + LanguageCode::getDeprecatedCodeMapping();
+}
+
// These are now the same, always
// To determine the user language, use $wgLang->getCode()
$wgContLanguageCode = $wgLanguageCode;
global $wgContLang;
$prefix = $wgContLang->getFormattedNsText( $title->getNamespace() );
+ // @todo Emit some kind of warning to the user if $title->getNamespace() !==
+ // NS_MAIN and $prefix === '' (viz. pages in an unregistered namespace)
+
if ( $prefix !== '' ) {
$prefix .= ':';
}
$row = $res ? $this->fetchRow( $res ) : false;
if ( !$row ) {
- throw new DBExpectedError( $this, "Failed to query MASTER_POS_WAIT()" );
+ throw new DBExpectedError( $this,
+ "MASTER_POS_WAIT() or MASTER_GTID_WAIT() failed: {$this->lastError()}" );
}
// Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
case 'Html5Internal':
$instance = new MediaWiki\Tidy\Html5Internal( $config );
break;
+ case 'RemexHtml':
+ $instance = new MediaWiki\Tidy\RemexDriver( $config );
+ break;
case 'disabled':
return false;
default:
$options = isset( $query['options'] ) ? (array)$query['options'] : [];
$join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : [];
- if ( count( $order ) ) {
+ if ( $order ) {
$options['ORDER BY'] = $order;
}
if ( $limit !== false ) {
$options['LIMIT'] = intval( $limit );
}
+
if ( $offset !== false ) {
$options['OFFSET'] = intval( $offset );
}
- if ( $this->sortDescending() ) {
- $options['ORDER BY'] = 'qc_value DESC';
- } else {
- $options['ORDER BY'] = 'qc_value ASC';
+
+ $orderFields = $this->getOrderFields();
+ $order = [];
+ $DESC = $this->sortDescending() ? ' DESC' : '';
+ foreach ( $orderFields as $field ) {
+ $order[] = "qc_${field}${DESC}";
+ }
+ if ( $order ) {
+ $options['ORDER BY'] = $order;
}
+
return $dbr->select( 'querycache', [ 'qc_type',
'namespace' => 'qc_namespace',
'title' => 'qc_title',
'value' => 'qc_value' ],
[ 'qc_type' => $this->getName() ],
- __METHOD__, $options
+ __METHOD__,
+ $options
);
}
$label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped();
return Linker::link( $wlh, $label );
}
+
+ /**
+ * Order by title, overwrites QueryPage::getOrderFields
+ *
+ * @return array
+ */
+ function getOrderFields() {
+ return [ 'value DESC', 'namespace', 'title' ];
+ }
+
+ /**
+ * Do not order descending for all order fields. We will use DESC only on one field, see
+ * getOrderFields above. This overwrites sortDescending from QueryPage::getOrderFields().
+ * Do NOT change this to true unless you remove the phrase DESC in getOrderFiels above.
+ * If you do a database error will be thrown due to double adding DESC to query!
+ *
+ * @return bool
+ */
+ function sortDescending() {
+ return false;
+ }
+
}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tidy;
+
+use RemexHtml\HTMLData;
+use RemexHtml\Serializer\HtmlFormatter;
+use RemexHtml\Serializer\SerializerNode;
+use RemexHtml\Tokenizer\PlainAttributes;
+
+/**
+ * @internal
+ */
+class RemexCompatFormatter extends HtmlFormatter {
+ private static $markedEmptyElements = [
+ 'li' => true,
+ 'p' => true,
+ 'tr' => true,
+ ];
+
+ public function __construct( $options = [] ) {
+ parent::__construct( $options );
+ $this->attributeEscapes["\xc2\xa0"] = ' ';
+ unset( $this->attributeEscapes["&"] );
+ $this->textEscapes["\xc2\xa0"] = ' ';
+ unset( $this->textEscapes["&"] );
+ }
+
+ public function startDocument( $fragmentNamespace, $fragmentName ) {
+ return '';
+ }
+
+ public function element( SerializerNode $parent, SerializerNode $node, $contents ) {
+ $data = $node->snData;
+ if ( $data && $data->isPWrapper ) {
+ if ( $data->nonblankNodeCount ) {
+ return "<p>$contents</p>";
+ } else {
+ return $contents;
+ }
+ }
+
+ $name = $node->name;
+ $attrs = $node->attrs;
+ if ( isset( self::$markedEmptyElements[$name] ) && $attrs->count() === 0 ) {
+ if ( strspn( $contents, "\t\n\f\r " ) === strlen( $contents ) ) {
+ return "<{$name} class=\"mw-empty-elt\">$contents</{$name}>";
+ }
+ }
+
+ $s = "<$name";
+ foreach ( $attrs->getValues() as $attrName => $attrValue ) {
+ $encValue = strtr( $attrValue, $this->attributeEscapes );
+ $s .= " $attrName=\"$encValue\"";
+ }
+ if ( $node->namespace === HTMLData::NS_HTML && isset( $this->voidElements[$name] ) ) {
+ $s .= ' />';
+ return $s;
+ }
+
+ $s .= '>';
+ if ( $node->namespace === HTMLData::NS_HTML
+ && isset( $contents[0] ) && $contents[0] === "\n"
+ && isset( $this->prefixLfElements[$name] )
+ ) {
+ $s .= "\n$contents</$name>";
+ } else {
+ $s .= "$contents</$name>";
+ }
+ return $s;
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tidy;
+
+use RemexHtml\HTMLData;
+use RemexHtml\Serializer\Serializer;
+use RemexHtml\Serializer\SerializerNode;
+use RemexHtml\Tokenizer\Attributes;
+use RemexHtml\Tokenizer\PlainAttributes;
+use RemexHtml\TreeBuilder\TreeBuilder;
+use RemexHtml\TreeBuilder\TreeHandler;
+use RemexHtml\TreeBuilder\Element;
+
+/**
+ * @internal
+ */
+class RemexCompatMunger implements TreeHandler {
+ private static $onlyInlineElements = [
+ "a" => true,
+ "abbr" => true,
+ "acronym" => true,
+ "applet" => true,
+ "b" => true,
+ "basefont" => true,
+ "bdo" => true,
+ "big" => true,
+ "br" => true,
+ "button" => true,
+ "cite" => true,
+ "code" => true,
+ "dfn" => true,
+ "em" => true,
+ "font" => true,
+ "i" => true,
+ "iframe" => true,
+ "img" => true,
+ "input" => true,
+ "kbd" => true,
+ "label" => true,
+ "legend" => true,
+ "map" => true,
+ "object" => true,
+ "param" => true,
+ "q" => true,
+ "rb" => true,
+ "rbc" => true,
+ "rp" => true,
+ "rt" => true,
+ "rtc" => true,
+ "ruby" => true,
+ "s" => true,
+ "samp" => true,
+ "select" => true,
+ "small" => true,
+ "span" => true,
+ "strike" => true,
+ "strong" => true,
+ "sub" => true,
+ "sup" => true,
+ "textarea" => true,
+ "tt" => true,
+ "u" => true,
+ "var" => true,
+ ];
+
+ private static $formattingElements = [
+ 'a' => true,
+ 'b' => true,
+ 'big' => true,
+ 'code' => true,
+ 'em' => true,
+ 'font' => true,
+ 'i' => true,
+ 'nobr' => true,
+ 's' => true,
+ 'small' => true,
+ 'strike' => true,
+ 'strong' => true,
+ 'tt' => true,
+ 'u' => true,
+ ];
+
+ /**
+ * Constructor
+ *
+ * @param Serializer $serializer
+ */
+ public function __construct( Serializer $serializer ) {
+ $this->serializer = $serializer;
+ }
+
+ public function startDocument( $fragmentNamespace, $fragmentName ) {
+ $this->serializer->startDocument( $fragmentNamespace, $fragmentName );
+ $root = $this->serializer->getRootNode();
+ $root->snData = new RemexMungerData;
+ $root->snData->needsPWrapping = true;
+ }
+
+ public function endDocument( $pos ) {
+ $this->serializer->endDocument( $pos );
+ }
+
+ private function getParentForInsert( $preposition, $refElement ) {
+ if ( $preposition === TreeBuilder::ROOT ) {
+ return [ $this->serializer->getRootNode(), null ];
+ } elseif ( $preposition === TreeBuilder::BEFORE ) {
+ $refNode = $refElement->userData;
+ return [ $this->serializer->getParentNode( $refNode ), $refNode ];
+ } else {
+ $refNode = $refElement->userData;
+ $refData = $refNode->snData;
+ if ( $refData->currentCloneElement ) {
+ // Follow a chain of clone links if necessary
+ $origRefData = $refData;
+ while ( $refData->currentCloneElement ) {
+ $refElement = $refData->currentCloneElement;
+ $refNode = $refElement->userData;
+ $refData = $refNode->snData;
+ }
+ // Cache the end of the chain in the requested element
+ $origRefData->currentCloneElement = $refElement;
+ } elseif ( $refData->childPElement ) {
+ $refElement = $refData->childPElement;
+ $refNode = $refElement->userData;
+ }
+ return [ $refNode, $refNode ];
+ }
+ }
+
+ /**
+ * Insert a p-wrapper
+ *
+ * @param SerializerNode $parent
+ * @param integer $sourceStart
+ * @return SerializerNode
+ */
+ private function insertPWrapper( SerializerNode $parent, $sourceStart ) {
+ $pWrap = new Element( HTMLData::NS_HTML, 'mw:p-wrap', new PlainAttributes );
+ $this->serializer->insertElement( TreeBuilder::UNDER, $parent, $pWrap, false,
+ $sourceStart, 0 );
+ $data = new RemexMungerData;
+ $data->isPWrapper = true;
+ $data->wrapBaseNode = $parent;
+ $pWrap->userData->snData = $data;
+ $parent->snData->childPElement = $pWrap;
+ return $pWrap->userData;
+ }
+
+ public function characters( $preposition, $refElement, $text, $start, $length,
+ $sourceStart, $sourceLength
+ ) {
+ $isBlank = strspn( $text, "\t\n\f\r ", $start, $length ) === $length;
+
+ list( $parent, $refNode ) = $this->getParentForInsert( $preposition, $refElement );
+ $parentData = $parent->snData;
+
+ if ( $preposition === TreeBuilder::UNDER ) {
+ if ( $parentData->needsPWrapping && !$isBlank ) {
+ // Add a p-wrapper for bare text under body/blockquote
+ $refNode = $this->insertPWrapper( $refNode, $sourceStart );
+ $parent = $refNode;
+ $parentData = $parent->snData;
+ } elseif ( $parentData->isSplittable && !$parentData->ancestorPNode ) {
+ // The parent is splittable and in block mode, so split the tag stack
+ $refNode = $this->splitTagStack( $refNode, true, $sourceStart );
+ $parent = $refNode;
+ $parentData = $parent->snData;
+ }
+ }
+
+ if ( !$isBlank ) {
+ // Non-whitespace characters detected
+ $parentData->nonblankNodeCount++;
+ }
+ $this->serializer->characters( $preposition, $refNode, $text, $start,
+ $length, $sourceStart, $sourceLength );
+ }
+
+ /**
+ * Insert or reparent an element. Create p-wrappers or split the tag stack
+ * as necessary.
+ *
+ * Consider the following insertion locations. The parent may be:
+ *
+ * - A: A body or blockquote (!!needsPWrapping)
+ * - B: A p-wrapper (!!isPWrapper)
+ * - C: A descendant of a p-wrapper (!!ancestorPNode)
+ * - CS: With splittable formatting elements in the stack region up to
+ * the p-wrapper
+ * - CU: With one or more unsplittable elements in the stack region up
+ * to the p-wrapper
+ * - D: Not a descendant of a p-wrapper (!ancestorNode)
+ * - DS: With splittable formatting elements in the stack region up to
+ * the body or blockquote
+ * - DU: With one or more unsplittable elements in the stack region up
+ * to the body or blockquote
+ *
+ * And consider that we may insert two types of element:
+ * - b: block
+ * - i: inline
+ *
+ * We handle the insertion as follows:
+ *
+ * - A/i: Create a p-wrapper, insert under it
+ * - A/b: Insert as normal
+ * - B/i: Insert as normal
+ * - B/b: Close the p-wrapper, insert under the body/blockquote (wrap
+ * base) instead)
+ * - C/i: Insert as normal
+ * - CS/b: Split the tag stack, insert the block under cloned formatting
+ * elements which have the wrap base (the parent of the p-wrap) as
+ * their ultimate parent.
+ * - CU/b: Disable the p-wrap, by reparenting the currently open child
+ * of the p-wrap under the p-wrap's parent. Then insert the block as
+ * normal.
+ * - D/b: Insert as normal
+ * - DS/i: Split the tag stack, creating a new p-wrapper as the ultimate
+ * parent of the formatting elements thus cloned. The parent of the
+ * p-wrapper is the body or blockquote.
+ * - DU/i: Insert as normal
+ *
+ * FIXME: fostering ($preposition == BEFORE) is mostly done by inserting as
+ * normal, the full algorithm is not followed.
+ *
+ * @param integer $preposition
+ * @param Element|SerializerNode|null $refElement
+ * @param Element $element
+ * @param bool $void
+ * @param integer $sourceStart
+ * @param integer $sourceLength
+ */
+
+ public function insertElement( $preposition, $refElement, Element $element, $void,
+ $sourceStart, $sourceLength
+ ) {
+ list( $parent, $newRef ) = $this->getParentForInsert( $preposition, $refElement );
+ $parentData = $parent->snData;
+ $parentNs = $parent->namespace;
+ $parentName = $parent->name;
+ $elementName = $element->htmlName;
+
+ $inline = isset( self::$onlyInlineElements[$elementName] );
+ $under = $preposition === TreeBuilder::UNDER;
+
+ if ( $under && $parentData->isPWrapper && !$inline ) {
+ // [B/b] The element is non-inline and the parent is a p-wrapper,
+ // close the parent and insert into its parent instead
+ $newParent = $this->serializer->getParentNode( $parent );
+ $parent = $newParent;
+ $parentData = $parent->snData;
+ $parentData->childPElement = null;
+ $newRef = $refElement->userData;
+ // FIXME cannot call endTag() since we don't have an Element
+ } elseif ( $under && $parentData->isSplittable
+ && (bool)$parentData->ancestorPNode !== $inline
+ ) {
+ // [CS/b, DS/i] The parent is splittable and the current element is
+ // inline in block context, or if the current element is a block
+ // under a p-wrapper, split the tag stack.
+ $newRef = $this->splitTagStack( $newRef, $inline, $sourceStart );
+ $parent = $newRef;
+ $parentData = $parent->snData;
+ } elseif ( $under && $parentData->needsPWrapping && $inline ) {
+ // [A/i] If the element is inline and we are in body/blockquote,
+ // we need to create a p-wrapper
+ $newRef = $this->insertPWrapper( $newRef, $sourceStart );
+ $parent = $newRef;
+ $parentData = $parent->snData;
+ } elseif ( $parentData->ancestorPNode && !$inline ) {
+ // [CU/b] If the element is non-inline and (despite attempting to
+ // split above) there is still an ancestor p-wrap, disable that
+ // p-wrap
+ $this->disablePWrapper( $parent, $sourceStart );
+ }
+ // else [A/b, B/i, C/i, D/b, DU/i] insert as normal
+
+ // An element with element children is a non-blank element
+ $parentData->nonblankNodeCount++;
+
+ // Insert the element downstream and so initialise its userData
+ $this->serializer->insertElement( $preposition, $newRef,
+ $element, $void, $sourceStart, $sourceLength );
+
+ // Initialise snData
+ if ( !$element->userData->snData ) {
+ $elementData = $element->userData->snData = new RemexMungerData;
+ } else {
+ $elementData = $element->userData->snData;
+ }
+ if ( ( $parentData->isPWrapper || $parentData->isSplittable )
+ && isset( self::$formattingElements[$elementName] )
+ ) {
+ $elementData->isSplittable = true;
+ }
+ if ( $parentData->isPWrapper ) {
+ $elementData->ancestorPNode = $parent;
+ } elseif ( $parentData->ancestorPNode ) {
+ $elementData->ancestorPNode = $parentData->ancestorPNode;
+ }
+ if ( $parentData->wrapBaseNode ) {
+ $elementData->wrapBaseNode = $parentData->wrapBaseNode;
+ } elseif ( $parentData->needsPWrapping ) {
+ $elementData->wrapBaseNode = $parent;
+ }
+ if ( $elementName === 'body'
+ || $elementName === 'blockquote'
+ || $elementName === 'html'
+ ) {
+ $elementData->needsPWrapping = true;
+ }
+ }
+
+ /**
+ * Clone nodes in a stack range and return the new parent
+ *
+ * @param SerializerNode $parentNode
+ * @param bool $inline
+ * @param integer $pos The source position
+ * @return SerializerNode
+ */
+ private function splitTagStack( SerializerNode $parentNode, $inline, $pos ) {
+ $parentData = $parentNode->snData;
+ $wrapBase = $parentData->wrapBaseNode;
+ $pWrap = $parentData->ancestorPNode;
+ if ( !$pWrap ) {
+ $cloneEnd = $wrapBase;
+ } else {
+ $cloneEnd = $parentData->ancestorPNode;
+ }
+
+ $serializer = $this->serializer;
+ $node = $parentNode;
+ $root = $serializer->getRootNode();
+ $nodes = [];
+ $removableNodes = [];
+ $haveContent = false;
+ while ( $node !== $cloneEnd ) {
+ $nextParent = $serializer->getParentNode( $node );
+ if ( $nextParent === $root ) {
+ throw new \Exception( 'Did not find end of clone range' );
+ }
+ $nodes[] = $node;
+ if ( $node->snData->nonblankNodeCount === 0 ) {
+ $removableNodes[] = $node;
+ $nextParent->snData->nonblankNodeCount--;
+ }
+ $node = $nextParent;
+ }
+
+ if ( $inline ) {
+ $pWrap = $this->insertPWrapper( $wrapBase, $pos );
+ $node = $pWrap;
+ } else {
+ if ( $pWrap ) {
+ // End the p-wrap which was open, cancel the diversion
+ $wrapBase->snData->childPElement = null;
+ }
+ $pWrap = null;
+ $node = $wrapBase;
+ }
+
+ for ( $i = count( $nodes ) - 1; $i >= 0; $i-- ) {
+ $oldNode = $nodes[$i];
+ $oldData = $oldNode->snData;
+ $nodeParent = $node;
+ $element = new Element( $oldNode->namespace, $oldNode->name, $oldNode->attrs );
+ $this->serializer->insertElement( TreeBuilder::UNDER, $nodeParent,
+ $element, false, $pos, 0 );
+ $oldData->currentCloneElement = $element;
+
+ $newNode = $element->userData;
+ $newData = $newNode->snData = new RemexMungerData;
+ if ( $pWrap ) {
+ $newData->ancestorPNode = $pWrap;
+ }
+ $newData->isSplittable = true;
+ $newData->wrapBaseNode = $wrapBase;
+ $newData->isPWrapper = $oldData->isPWrapper;
+
+ $nodeParent->snData->nonblankNodeCount++;
+
+ $node = $newNode;
+ }
+ foreach ( $removableNodes as $rNode ) {
+ $fakeElement = new Element( $rNode->namespace, $rNode->name, $rNode->attrs );
+ $fakeElement->userData = $rNode;
+ $this->serializer->removeNode( $fakeElement, $pos );
+ }
+ return $node;
+ }
+
+ /**
+ * Find the ancestor of $node which is a child of a p-wrapper, and
+ * reparent that node so that it is placed after the end of the p-wrapper
+ */
+ private function disablePWrapper( SerializerNode $node, $sourceStart ) {
+ $nodeData = $node->snData;
+ $pWrapNode = $nodeData->ancestorPNode;
+ $newParent = $this->serializer->getParentNode( $pWrapNode );
+ if ( $pWrapNode !== $this->serializer->getLastChild( $newParent ) ) {
+ // Fostering or something? Abort!
+ return;
+ }
+
+ $nextParent = $node;
+ do {
+ $victim = $nextParent;
+ $victim->snData->ancestorPNode = null;
+ $nextParent = $this->serializer->getParentNode( $victim );
+ } while ( $nextParent !== $pWrapNode );
+
+ // Make a fake Element to use in a reparenting operation
+ $victimElement = new Element( $victim->namespace, $victim->name, $victim->attrs );
+ $victimElement->userData = $victim;
+
+ // Reparent
+ $this->serializer->insertElement( TreeBuilder::UNDER, $newParent, $victimElement,
+ false, $sourceStart, 0 );
+
+ // Decrement nonblank node count
+ $pWrapNode->snData->nonblankNodeCount--;
+
+ // Cancel the diversion so that no more elements are inserted under this p-wrap
+ $newParent->snData->childPElement = null;
+ }
+
+ public function endTag( Element $element, $sourceStart, $sourceLength ) {
+ $this->serializer->endTag( $element, $sourceStart, $sourceLength );
+ }
+
+ public function doctype( $name, $public, $system, $quirks, $sourceStart, $sourceLength ) {
+ $this->serializer->doctype( $name, $public, $system, $quirks,
+ $sourceStart, $sourceLength );
+ }
+
+ public function comment( $preposition, $refElement, $text, $sourceStart, $sourceLength ) {
+ list( $parent, $refNode ) = $this->getParentForInsert( $preposition, $refElement );
+ $this->serializer->comment( $preposition, $refNode, $text,
+ $sourceStart, $sourceLength );
+ }
+
+ public function error( $text, $pos ) {
+ $this->serializer->error( $text, $pos );
+ }
+
+ public function mergeAttributes( Element $element, Attributes $attrs, $sourceStart ) {
+ $this->serializer->mergeAttributes( $element, $attrs, $sourceStart );
+ }
+
+ public function removeNode( Element $element, $sourceStart ) {
+ $this->serializer->removeNode( $element, $sourceStart );
+ }
+
+ public function reparentChildren( Element $element, Element $newParent, $sourceStart ) {
+ $self = $element->userData;
+ $children = $self->children;
+ $self->children = [];
+ $this->insertElement( TreeBuilder::UNDER, $element, $newParent, false, $sourceStart, 0 );
+ $newParentNode = $newParent->userData;
+ $newParentId = $newParentNode->id;
+ foreach ( $children as $child ) {
+ if ( is_object( $child ) ) {
+ $child->parentId = $newParentId;
+ }
+ }
+ $newParentNode->children = $children;
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tidy;
+
+use RemexHtml\Serializer\Serializer;
+use RemexHtml\Tokenizer\Tokenizer;
+use RemexHtml\TreeBuilder\Dispatcher;
+use RemexHtml\TreeBuilder\TreeBuilder;
+use RemexHtml\TreeBuilder\TreeMutationTracer;
+
+class RemexDriver extends TidyDriverBase {
+ private $trace;
+ private $pwrap;
+
+ public function __construct( array $config ) {
+ $config += [
+ 'treeMutationTrace' => false,
+ 'pwrap' => true
+ ];
+ $this->trace = $config['treeMutationTrace'];
+ $this->pwrap = $config['pwrap'];
+ parent::__construct( $config );
+ }
+
+ public function tidy( $text ) {
+ $formatter = new RemexCompatFormatter;
+ $serializer = new Serializer( $formatter );
+ if ( $this->pwrap ) {
+ $munger = new RemexCompatMunger( $serializer );
+ } else {
+ $munger = $serializer;
+ }
+ if ( $this->trace ) {
+ $tracer = new TreeMutationTracer( $munger, function ( $msg ) {
+ wfDebug( "RemexHtml: $msg" );
+ } );
+ } else {
+ $tracer = $munger;
+ }
+ $treeBuilder = new TreeBuilder( $tracer, [
+ 'ignoreErrors' => true,
+ 'ignoreNulls' => true,
+ ] );
+ $dispatcher = new Dispatcher( $treeBuilder );
+ $tokenizer = new Tokenizer( $dispatcher, $text, [
+ 'ignoreErrors' => true,
+ 'ignoreCharRefs' => true,
+ 'ignoreNulls' => true,
+ 'skipPreprocess' => true,
+ ] );
+ $tokenizer->execute( [
+ 'fragmentNamespace' => \RemexHtml\HTMLData::NS_HTML,
+ 'fragmentName' => 'body'
+ ] );
+ return $serializer->getResult();
+ }
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tidy;
+
+/**
+ * @internal
+ */
+class RemexMungerData {
+ /**
+ * The Element for the mw:p-wrap which is a child of the current node. If
+ * this is set, inline insertions into this node will be diverted so that
+ * they insert into the p-wrap.
+ *
+ * @var \RemexHtml\TreeBuilder\Element|null
+ */
+ public $childPElement;
+
+ /**
+ * This tracks the mw:p-wrap node in the Serializer stack which is an
+ * ancestor of this node. If there is no mw:p-wrap ancestor, it is null.
+ *
+ * @var \RemexHtml\Serializer\SerializerNode|null
+ */
+ public $ancestorPNode;
+
+ /**
+ * The wrap base node is the body or blockquote node which is the parent
+ * of active p-wrappers. This is set if there is an ancestor p-wrapper,
+ * or if a p-wrapper was closed due to a block element being encountered
+ * inside it.
+ *
+ * @var \RemexHtml\Serializer\SerializerNode|null
+ */
+ public $wrapBaseNode;
+
+ /**
+ * Stack splitting (essentially our idea of AFE reconstruction) can clone
+ * formatting elements which are split over multiple paragraphs.
+ * TreeBuilder is not aware of the cloning, and continues to insert into
+ * the original element. This is set to the newer clone if this node was
+ * cloned, i.e. if there is an active diversion of the insertion location.
+ *
+ * @var \RemexHtml\TreeBuilder\Element|null
+ */
+ public $currentCloneElement;
+
+ /**
+ * Is the node a p-wrapper, with name mw:p-wrap?
+ *
+ * @var bool
+ */
+ public $isPWrapper = false;
+
+ /**
+ * Is the node splittable, i.e. a formatting element or a node with a
+ * formatting element ancestor which is under an active or deactivated
+ * p-wrapper.
+ *
+ * @var bool
+ */
+ public $isSplittable = false;
+
+ /**
+ * This is true if the node is a body or blockquote, which activates
+ * p-wrapping of child nodes.
+ */
+ public $needsPWrapping = false;
+
+ /**
+ * The number of child nodes, not counting whitespace-only text nodes or
+ * comments.
+ */
+ public $nonblankNodeCount = 0;
+
+ public function __set( $name, $value ) {
+ throw new \Exception( "Cannot set property \"$name\"" );
+ }
+}
protected function parseTitleWithNs( $title, $ns ) {
$pieces = explode( ':', $title, 2 );
+ // Is $title of the form Namespace:Title (true), or just Title (false)?
+ $titleIncludesNamespace = ( $ns != '0' && count( $pieces ) === 2 );
+
if ( isset( $this->foreignNamespaces[$ns] ) ) {
$namespaceName = $this->foreignNamespaces[$ns];
} else {
- $namespaceName = $ns == '0' ? '' : $pieces[0];
+ // If the foreign wiki is misconfigured, XML dumps can contain a page with
+ // a non-zero namespace ID, but whose title doesn't contain a colon
+ // (T114115). In those cases, output a made-up namespace name to avoid
+ // collisions. The ImportTitleFactory might replace this with something
+ // more appropriate.
+ $namespaceName = $titleIncludesNamespace ? $pieces[0] : "Ns$ns";
}
// We assume that the portion of the page title before the colon is the
- // namespace name, except in the case of namespace 0
- if ( $ns != '0' ) {
+ // namespace name, except in the case of namespace 0.
+ if ( $titleIncludesNamespace ) {
$pageName = $pieces[1];
} else {
$pageName = $title;
--- /dev/null
+<?php
+/**
+ * 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
+ * @ingroup Language
+ */
+
+/**
+ * Methods for dealing with language codes.
+ * @todo Move some of the code-related static methods out of Language into this class
+ *
+ * @since 1.29
+ * @ingroup Language
+ */
+class LanguageCode {
+ /**
+ * Returns a mapping of deprecated language codes that were used in previous
+ * versions of MediaWiki to up-to-date, current language codes.
+ *
+ * This array is merged into $wgDummyLanguageCodes in Setup.php, along with
+ * the fake language codes 'qqq' and 'qqx', which are used internally by
+ * MediaWiki's localisation system.
+ *
+ * @return string[]
+ */
+ public static function getDeprecatedCodeMapping() {
+ return [
+ // Note that als is actually a valid ISO 639 code (Tosk Albanian), but it
+ // was previously used in MediaWiki for Alsatian, which comes under gsw
+ 'als' => 'gsw',
+ 'bat-smg' => 'sgs',
+ 'be-x-old' => 'be-tarask',
+ 'fiu-vro' => 'vro',
+ 'roa-rup' => 'rup',
+ 'zh-classical' => 'lzh',
+ 'zh-min-nan' => 'nan',
+ 'zh-yue' => 'yue',
+ ];
+ }
+}
"authmanager-link-not-in-progress": "Злучэньне рахункаў не выконваецца або страчаныя зьвесткі сэсіі. Калі ласка, пачніце ізноў спачатку.",
"authmanager-authplugin-setpass-failed-title": "Памылка зьмены паролю",
"authmanager-authplugin-setpass-failed-message": "Дадатак аўтэнтыфікацыі адмовіў зьмену паролю.",
+ "authmanager-authplugin-create-fail": "Дадатак аўтэнтыфікацыі адмовіў у стварэньні рахунку.",
"authmanager-realname-label": "Сапраўднае імя",
"authmanager-provider-temporarypassword": "Часовы пароль",
"changecredentials": "Зьмена ўліковых зьвестак",
"tog-watchlisthidepatrolled": "Скриване на патрулираните редакции от списъка за наблюдение",
"tog-watchlisthidecategorization": "Скриване на категоризацията на статии",
"tog-ccmeonemails": "Получаване на копия на писмата, които пращам на другите потребители",
- "tog-diffonly": "Ð\91ез показване на съдържанието на страницата при преглед на разлики",
+ "tog-diffonly": "Ð\94а не Ñ\81е показва съдържанието на страницата при преглед на разлики",
"tog-showhiddencats": "Показване на скритите категории",
- "tog-norollbackdiff": "Ð\9dе показвай разликата между редакциите след отмяна на редакции",
+ "tog-norollbackdiff": "Ð\94а не Ñ\81е показва разликата между редакциите след отмяна на редакции",
"tog-useeditwarning": "Предупреждаване при опит за напускане на страница, отворена в режим на редактиране, без да са запазени промените",
"tog-prefershttps": "Да се използва винаги защитена връзка при влизане",
"underline-always": "Винаги",
"throttled-mailpassword": "Функцията за напомняне на паролата е използвана през {{PLURAL:$1|последния един час|последните $1 часа}}.\nЗа предотвратяване на злоупотреби е разрешено да се изпраща не повече от едно напомняне в рамките на {{PLURAL:$1|един час|$1 часа}}.",
"mailerror": "Грешка при изпращане на писмо: $1",
"acct_creation_throttle_hit": "През последните $2, през този IP-адрес посетители на това уики са създали {{PLURAL:$1|1 сметка |$1 сметки}}, което е максималният допустим брой за този период.\nВ резултат, към момента не могат да създават повече потребителски сметки през този IP-адрес.",
- "emailauthenticated": "Ð\90дÑ\80еÑ\81Ñ\8aÑ\82 на елекÑ\82Ñ\80оннаÑ\82а ви поÑ\89а беÑ\88е потвърден на $2 в $3.",
+ "emailauthenticated": "Ð\90дÑ\80еÑ\81Ñ\8aÑ\82 на елекÑ\82Ñ\80оннаÑ\82а поÑ\89а e потвърден на $2 в $3.",
"emailnotauthenticated": "Адресът на електронната ви поща все още не е потвърден.\nНяма да получавате писма за никоя от следните възможности.",
"noemailprefs": "За да работят тези функционалности, трябва да посочите адрес на електронна поща в своите настройки.",
"emailconfirmlink": "Потвърждаване на адреса за електронна поща",
"rcfilters-filterlist-feedbacklink": "Reiñ ho soñj diwar-benn ar siloù (beta) nevez",
"rcfilters-highlightbutton-title": "Lakaat an disoc'hoù war wel",
"rcfilters-highlightmenu-title": "Dibabit ul liv",
+ "rcfilters-highlightmenu-help": "Diuzañ ul liv evit lakaat ar perzh-mañ war wel",
"rcfilters-filterlist-noresults": "N'eus bet kavet sil ebet",
"rcfilters-filtergroup-registration": "Enskrivadur an implijer",
"rcfilters-filter-registered-label": "Marilhet",
"listgrouprights-namespaceprotection-namespace": "Esaouenn anv",
"listgrouprights-namespaceprotection-restrictedto": "Gwir(ioù) hag a aotre an implijer da aozañ",
"listgrants": "Aotreoù",
+ "listgrants-summary": "Setu aze ur roll eus an aotreoù gant ar gwirioù moned stag ouzh ar gwirioù implijer. Gallout a ra an implijerien aotren an arloadoù da implijout o c'hont gant aotreoù strishaet diazezet war ar gwirioù bet grataet d'an arload gant an implijer. Un arload a ra en anv un implijer n'hall ket ober gant gwirioù n'int ket bet roet d'an implijer da gentañ. Gallout a ra bezañ tammoù [[{{MediaWiki:Listgrouprights-helppage}}|titouroù ouzhpenn]] diwar-benn ar gwirioù hiniennel.",
"listgrants-grant": "Aotren",
"listgrants-rights": "Gwirioù",
"trackingcategories": "Rummadoù evezhiañ",
+ "trackingcategories-summary": "Rollañ a ra ar bajenn-mañ ar rummadoù heuliañ leuniet ent emgefre gant MediaWiki. Gallout a ra o anvioù bezañ cheñchet en ur gemmañ ar c'hemennadennoù reizhiad a glot ganto en esaouenn anv {{ns:8}}.",
"trackingcategories-msg": "Rummad evezhiañ",
"trackingcategories-name": "Anv ar gemennadenn",
"trackingcategories-desc": "Dezverkoù evit degemer rummadoù",
"restricted-displaytitle-ignored": "Pajennoù gant titloù diskwel lezet a-gostez",
"restricted-displaytitle-ignored-desc": "Ar bajenn-mañ zo dezhi un <code><nowiki>{{DISPLAYTITLE}}</nowiki></code> zo bet laosket a-gostez peogwir n'eo ket kevatal d'an titl zo d'ar bajenn bremañ.",
"noindex-category-desc": "Ar bajenn-mañ n'eo ket menegeret gant ar robotoù rak ar ger hud <code><nowiki>__NOINDEX__</nowiki></code> zo enni hag emañ en un esaouenn anv m'eo aotreet ar merkañ.",
+ "index-category-desc": "Un <code><nowiki>__INDEX__</nowiki></code> zo er bajenn (hag emañ en un esaouenn anv m'eo aotreet ober gant ar merk-se). Abalamour da se e vo menegeret gant robotoù pa ne vije ket bet a-hend-all.",
"broken-file-category-desc": "Er bajenn-mañ ez eus ul liamm restr torr (ul liamm da enframmañ ur restr pa n'eus ket eus ar restr-se).",
"trackingcategories-nodesc": "N'eus deskrivadur ebet.",
"trackingcategories-disabled": "Diweredekaet eo ar rummad",
"logentry-upload-upload": "$1 {{GENDER:$2|положишє}} $3",
"revdelete-summary": "мѣнꙑ опьсаниѥ",
"feedback-cancel": "отъмѣтаниѥ",
- "searchsuggest-search": "исканиѥ",
+ "searchsuggest-search": "{{SITENAME}} : исканиѥ",
"searchsuggest-containing": "сѥ дрьжащи···",
"api-error-unknownerror": "нєвѣдома блаꙁна : ⁖ $1 ⁖",
"duration-seconds": "$1 {{PLURAL:$1|дєѵтєролєпто|дєѵтєролєпта|дєѵтєролєптъ}}",
"cant-move-user-page": "desturê şıma çino, şıma pelanê karberani bıkırışi (bê pelê cerıni).",
"cant-move-to-user-page": "desturê şıma çino, şıma yew peli bıkırışi pelê yew karberi.",
"newtitle": "Sernameyo newe:",
- "move-watch": "Pela çıme u meqsedi seyr ke",
+ "move-watch": "Perra çımey u kırıştışi pıro bıdê",
"movepagebtn": "Pele bere",
"pagemovedsub": "Berdışi kerd temam",
"movepage-moved": "'''\"$1\" berd \"$2\"'''",
"rcfilters-filterlist-title": "Filtros",
"rcfilters-highlightbutton-title": "Resaltar los resultados",
"rcfilters-highlightmenu-title": "Selecciona un color",
+ "rcfilters-highlightmenu-help": "Selecciona un color para resaltar esta propiedad",
"rcfilters-filterlist-noresults": "No se encontraron filtros",
"rcfilters-filtergroup-registration": "Registro de usuario",
"rcfilters-filter-registered-label": "Registrados",
"reblock-logentry": "cambió el bloqueo para [[$1]] con una caducidad de $2 $3",
"blocklogtext": "Esto es un registro de acciones de bloqueo y desbloqueo de usuarios.\nLas direcciones IP bloqueadas automáticamente no aparecen aquí.\nConsulta la [[Special:BlockList|lista de bloqueos]] para ver la lista de bloqueos y prohibiciones de operar en vigor.",
"unblocklogentry": "desbloqueó a $1",
- "block-log-flags-anononly": "solo anónimos",
+ "block-log-flags-anononly": "solo usuarios anónimos",
"block-log-flags-nocreate": "desactivada la creación de cuentas",
"block-log-flags-noautoblock": "bloqueo automático desactivado",
"block-log-flags-noemail": "correo electrónico desactivado",
"talk": "განხილვა",
"views": "გადახედვა",
"toolbox": "ხელსაწყოები",
+ "tool-link-userrights": "{{GENDER:$1|მომხმარებლის}} ჯგუფების შეცვლა",
+ "tool-link-userrights-readonly": "{{GENDER:$1|მომხმარებლის}} ჯგუფების ხილვა",
+ "tool-link-emailuser": "{{GENDER:$1|მომხმარებლისთვის}} ი-მეილის მიწერა",
"userpage": "გადახედე მომხმარებლის გვერდს",
"projectpage": "დაათვალიერე პროექტის გვერდი",
"imagepage": "ფაილის გვერდის ნახვა",
"contentmodelediterror": "არ შეგიძლიათ ამ ვერსიის რედაქტირება, რადგან მისი კონტენტის მოდელი არის <code>$1</code>, რაც განსხვავდება გვერდის მიმდინარე კონტენტის მოედლისაგან <code>$2</code>.",
"recreate-moveddeleted-warn": "'''გაფრთხილება: თქვენ ხელახლა ქმნით გვერდს, რომელიც ადრე წაიშალა.'''\n\nგთხოვთ დაფიქრდეთ, მისაღები არის თუ არა ამ გვერდის რედაქტირების გაგრძელება.\nინფორმაციისთვის ქვემოთ მოყვანილია ამ გვერდის წაშლის ისტორია:",
"moveddeleted-notice": "ეს გვერდი წაიშალა. ინფორმაციის მისაღებად ქვემოთ წარმოდგენილია შესაბამისი ჩანაწერები წაშლისა და გადარქმევის ჟურნალებიდან.",
- "moveddeleted-notice-recent": "á\83\91á\83\9dá\83\93á\83\98á\83¨á\83\98, á\83\94á\83¡ á\83\92á\83\95á\83\94á\83 á\83\93á\83\98 á\83¬á\83\90á\83¨á\83\9aá\83\98á\83\9aá\83\98á\83\90 (á\83\91á\83\9dá\83\9aá\83\9d 24 á\83¡á\83\90á\83\90á\83\97á\83\98á\83¡ á\83\92á\83\90á\83\9cá\83\9bá\83\90á\83\95á\83\9aá\83\9dá\83\91á\83\90á\83¨á\83\98).\ná\83¬á\83\90á\83¨á\83\9aá\83\98á\83¡á\83\90 á\83\93á\83\90 á\83\92á\83\90á\83\93á\83\90á\83¢á\83\90á\83\9cá\83\98á\83¡ á\83\9fá\83£á\83 á\83\9cá\83\90á\83\9aá\83\98 á\83¥á\83\9bá\83\94á\83\9cოთ არის მოცემული.",
+ "moveddeleted-notice-recent": "á\83¡á\83\90á\83\9bá\83¬á\83£á\83®á\83\90á\83 á\83\9dá\83\93, á\83\94á\83¡ á\83\92á\83\95á\83\94á\83 á\83\93á\83\98 á\83¬á\83\90á\83¨á\83\9aá\83\98á\83\9aá\83\98á\83\90 (á\83\91á\83\9dá\83\9aá\83\9d 24 á\83¡á\83\90á\83\90á\83\97á\83\98á\83¡ á\83\92á\83\90á\83\9cá\83\9bá\83\90á\83\95á\83\9aá\83\9dá\83\91á\83\90á\83¨á\83\98).\ná\83¬á\83\90á\83¨á\83\9aá\83\98á\83¡á\83\90 á\83\93á\83\90 á\83\92á\83\90á\83\93á\83\90á\83¢á\83\90á\83\9cá\83\98á\83¡ á\83\9fá\83£á\83 á\83\9cá\83\90á\83\9aá\83\98 á\83¥á\83\95á\83\94á\83\9bოთ არის მოცემული.",
"log-fulllog": "ყველა ჟურნალის ხილვა",
"edit-hook-aborted": "შესწორება გაუქმებულია გადამჭერით.\nდამატებითი ახსნა არ ჩაწერილა.",
"edit-gone-missing": "გვერდის განახლეა შეუძლებელია.\nშესაძლოა, იგი წაიშალა.",
"userrights-user-editname": "შეიტანეთ მომხმარებლის სახელი:",
"editusergroup": "{{GENDER:$1|მომხმარებელთა}} ჯგუფების რედაქტირება",
"editinguser": "უფლებების შეცვლა {{GENDER:$1|მომხმარებლისთვის}} <strong>[[User:$1|$1]]</strong> $2",
+ "viewinguserrights": "{{GENDER:$1|მომხმარებლის}} უფლებების ხილვა <strong>[[User:$1|$1]]</strong> $2",
"userrights-editusergroup": "დაარედაქტირეთ მომხმარებელთა ჯგუფები",
+ "userrights-viewusergroup": "{{GENDER:$1|მომხმარებლის}} ჯგუფების ხილვა",
"saveusergroups": "{{GENDER:$1|მომხმარებელთა}} ჯგუფების შენახვა",
"userrights-groupsmember": "ჯგუფის წევრი:",
"userrights-groupsmember-auto": "ნაგულისხმევი წევრი:",
"trackingcategories-disabled": "კატეგორია გამორთულია",
"mailnologin": "გამგზავნი მისამართი არ არის მითითებული.",
"mailnologintext": "თქვენ უნდა [[Special:UserLogin|წარადგენილი იყოთ სისტემისადმი]] და გქონდეთ წესიერი ელექტრონული ფოსტის მისამართი თქვენს [[Special:Preferences|კონფიგურაციაში]] იმისთვის, რომ გაუგზავნოთ წერილების სხვა მომხმარებლებს.",
- "emailuser": "á\83\92á\83\90á\83£á\83\92á\83\96á\83\90á\83\95á\83\9cá\83\94á\83\97 á\83\98á\83\9bá\83\94á\83\98á\83\9aá\83\98 á\83\90á\83\9b á\83\9bá\83\9dá\83\9bá\83®á\83\9bá\83\90á\83 á\83\94á\83\91á\83\94á\83\9aá\83¡",
- "emailuser-title-target": "ელ. ფოსტის მიწერა {{GENDER:$1|მომხმარებელთან}}",
+ "emailuser": "á\83\9bá\83\9dá\83\9bá\83®á\83\9bá\83\90á\83 á\83\94á\83\91á\83\9aá\83\98á\83¡á\83\97á\83\95á\83\98á\83¡ á\83\98-á\83\9bá\83\94á\83\98á\83\9aá\83\98á\83¡ á\83\9bá\83\98á\83¬á\83\94á\83 á\83\90",
+ "emailuser-title-target": "{{GENDER:$1|მომხმარებლისთვის}} ი-მეილის მიწერა",
"emailuser-title-notarget": "ელ. ფოსტის გაგზავნა მომხმარებელთან",
"emailpagetext": "თუ ამ მომხმარებელმა თავის პარამეტრებში ელ. ფოსტის მოქმედი მისამართი მიუთითა, ქვემოთ მოყვანილი ფორმის შევსებისას შეგიძლიათ {{GENDER:$1|მისთვის}} შეტყობინების გაგზავნა. ელ. ფოსტის მისამართი, რომელიც [[Special:Preferences|თქვენს პარამეტრებში]] მიუთითეთ, დაფიქსირდება „გამომგზავნის“ ველში, რათა ადრესატმა პასუხის გაცემა შეძლოს.",
"defemailsubject": "ელ-ფოსტა საიტის {{SITENAME}} მომხმარებლისგან „$1“",
"botpasswords-no-provider": "Tsy afaka antsoina ny BotPasswordsSessionProvider.",
"botpasswords-restriction-failed": "Manakana ity fidirana ity ny fepetra mifehy ny tenimiafina rôbô.",
"resetpass_forbidden": "Tsy afaka ovaina ny tenimiafina",
+ "resetpass_forbidden-reason": "Tsy afaka ovaina ny tenimiafina: $1",
"resetpass-no-info": "Tsy maintsy tafiditra ao amin'ny kaontinao ianao vao afaka mijery ity pejy ity.",
"resetpass-submit-loggedin": "Ovay ny tenimiafina",
"resetpass-submit-cancel": "Aoka ihany",
"changeemail-password": "Tenimiafinao eo amin'i {{SITENAME}}:",
"changeemail-submit": "Hanova ny adiresy imailaka",
"changeemail-throttled": "Betsaka loatra ny andrana nidiranao.\nMiandrasa $1 alohan'ny manandrana indray.",
+ "changeemail-nochange": "Mampidira adiresy mailaka hafa vaovao.",
"resettokens": "Hamerina ny token",
"resettokens-text": "Azonao averina eto ny token izay hahafahana mitsidika ny fampahalalana tsy sarababem-bahoaka ao amin'ny kaontinao. Tokony ataonao izany raha voazara tsy fanahy iniana na raha nisy nangalatra ny kaontinao.",
"resettokens-no-tokens": "Tsy misy token ho averina",
"grant-highvolume": "Manova pejy ambongadiny",
"grant-oversight": "Manafina mpikambana ary mamafa versiona",
"grant-patrol": "Manamarika ireo fiovam-pejy ho voatsidika",
+ "grant-privateinfo": "Mijery fampahalalana tsy sarababembahoaka",
"grant-protect": "Miaro ary manala ny fiavoram-pejy",
"grant-rollback": "Mamafa fiovana amin'ny pejy",
"grant-sendemail": "Mandefa mailaka amin'ny mpikambana hafa",
"grant-basic": "Zo fototra",
"grant-viewdeleted": "Mitsidika ireo rakitra ary pejy voafafa",
"grant-viewmywatchlist": "Hijery ny lisitry ny pejy arahanao",
+ "grant-viewrestrictedlogs": "Mijery iditry ny laogy tsy sarababembahoaka",
"newuserlogpage": "Laogim-panokafana kaontim-pikambana",
"newuserlogpagetext": "Ity pejy ity dia maneho ny tantaran'asan'ny fampidirana mpikambana vaovao.",
"rightslog": "Laogim-piovan'ny zom-pikambana",
"rcfilters-invalid-filter": "Sivana tsy azo raisina",
"rcfilters-empty-filter": "Tsy misy sivana miasa. Aseho avokoa ireo fandraisan'anjara.",
"rcfilters-filterlist-title": "Sivana",
+ "rcfilters-filterlist-feedbacklink": "Mamoaka hevitra mikasika ny sivana vaovao (beta)",
"rcfilters-highlightbutton-title": "Hampibaribary ny valiny",
"rcfilters-highlightmenu-title": "Hisafidy loko",
+ "rcfilters-highlightmenu-help": "Misafidy loko hampanabaribariana ilay tondro",
"rcfilters-filterlist-noresults": "Tsy nahitana sivana",
"rcfilters-filtergroup-registration": "Fisoratan'ny mpikambana",
"rcfilters-filter-registered-label": "Nisoratra anarana",
"rcfilters-filter-registered-description": "Mpanova tafiditra.",
"rcfilters-filter-unregistered-label": "Tsy nisoratra anarana",
"rcfilters-filter-unregistered-description": "Mpikambana tsy niditra.",
+ "rcfilters-filtergroup-authorship": "Manova ny tompon-tsoratra",
"rcfilters-filter-editsbyself-label": "Ny fiovanao",
"rcfilters-filter-editsbyself-description": "Fiovana nataonao",
"rcfilters-filter-editsbyother-label": "Fiovana nataon'ny hafa",
"rcfilters-filter-major-description": "Fiovana tsy natao ho madinika.",
"rcfilters-filtergroup-changetype": "Karazam-piovana",
"rcfilters-filter-pageedits-label": "Fiovam-pejy",
+ "rcfilters-filter-pageedits-description": "Manova votoatin'ny wiki, resaka, visavisan-tsokajy...",
"rcfilters-filter-newpages-label": "Famoronam-pejy",
"rcfilters-filter-newpages-description": "Fiovana nahatonga famoronam-pejy.",
"rcfilters-filter-categorization-label": "Fiovana amin'ny sokajy",
"pageinfo-length": "Halavam-pejy (oktety)",
"pageinfo-article-id": "Laharam-pejy",
"pageinfo-language": "Tenin'ny votoatiny",
+ "pageinfo-language-change": "ovaina",
"pageinfo-content-model": "Môdelim-botoatim-pejy",
+ "pageinfo-content-model-change": "ovaina",
"pageinfo-robot-policy": "Fanondroana ataon'ny rôbô",
"pageinfo-robot-index": "Azo atao",
"pageinfo-robot-noindex": "Tsy azo tondroina",
"pageinfo-watchers": "Isan'ny mpandray anjara manaraka",
+ "pageinfo-visiting-watchers": "Isan'ny mpijery pejy nijery ny fiovana vao haingan'ity pejy ity",
"pageinfo-few-watchers": "Mpanaraka latsaky ny $1{{PLURAL:}}",
"pageinfo-redirects-name": "Fihodinana manketo amin'ity pejy ity",
"pageinfo-subpages-name": "Zana-pejin'ity pejy ity",
"pageinfo-category-pages": "Isam-pejy",
"pageinfo-category-subcats": "Isan'ny zana-tsokajy",
"pageinfo-category-files": "Isan'ny rakitra",
+ "pageinfo-user-id": "ID mpikambana",
"markaspatrolleddiff": "Marihana ho voamarina",
"markaspatrolledtext": "Marihana ho hita sy voatsara",
"markedaspatrolled": "Voamarina",
"patrol-log-page": "Laogin'ny fanovana voamarina",
"patrol-log-header": "Ity dia laogy mikasikan'ny fanovana voamarina.",
"log-show-hide-patrol": "$1 ny laogy mikasikan'ny versiona voamarina",
+ "confirm-markpatrolled-button": "OK",
"deletedrevision": "Fanovana an'i $1 taloha voafafa.",
"filedeleteerror-short": "Tsi-fetezana teo am-pamafàna ilay rakitra : $1",
"filedeleteerror-long": "Nisy tsi-fetezana nitranga teo am-pamafàna ilay rakitra :\n\n$1",
"rcfilters-filterlist-feedbacklink": "Дајте мислење за новите (бета) филтри",
"rcfilters-highlightbutton-title": "Истакнување на исход",
"rcfilters-highlightmenu-title": "Изберете боја",
+ "rcfilters-highlightmenu-help": "Изберете боја за да го истакнете ова својство",
"rcfilters-filterlist-noresults": "Не пронајдов ниеден филтер",
"rcfilters-filtergroup-registration": "Регистрација на корисници",
"rcfilters-filter-registered-label": "Регистрирани",
"rcfilters-filterlist-feedbacklink": "Geef feedback op de nieuwe (beta) filters",
"rcfilters-highlightbutton-title": "Resultaten markeren",
"rcfilters-highlightmenu-title": "Kies een kleur",
+ "rcfilters-highlightmenu-help": "Selecteer een kleur om deze eigenschap uit te lichten",
"rcfilters-filterlist-noresults": "Geen filters gevonden",
"rcfilters-filtergroup-registration": "Gebruikers-registratie",
"rcfilters-filter-registered-label": "Geregistreerd",
"noarticletext": "Obecnie ta strona nie ma zawartości.\nMożesz [[Special:Search/{{PAGENAME}}|wyszukać ten tytuł na innych stronach]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} przeszukać rejestr] \nlub [{{fullurl:{{FULLPAGENAME}}|action=edit}} utworzyć tę stronę]</span>.",
"noarticletext-nopermission": "Ta strona nie posiada jeszcze zawartości.\nMożesz [[Special:Search/{{PAGENAME}}|wyszukać ten tytuł]] w treści innych stron\nlub <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} przeszukać powiązane rejestry]</span>, ale nie masz uprawnień do utworzenia tej strony",
"missing-revision": "Wersja #$1 strony \"{{FULLPAGENAME}}\" nie istnieje.\n\nZazwyczaj jest to spowodowane przestarzałym linkiem do usuniętej strony. Powód usunięcia znajduje się w [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} rejestrze].",
- "userpage-userdoesnotexist": "Użytkownik „<nowiki>$1</nowiki>” nie jest zarejestrowany.\nUpewnij się, czy na pewno zamierza{{GENDER:|łeś|łaś|sz}} utworzyć lub zmodyfikować właśnie tę stronę.",
+ "userpage-userdoesnotexist": "Użytkownik „$1” nie jest zarejestrowany.\nUpewnij się, czy na pewno zamierzał{{GENDER:|eś|aś|eś/aś}} utworzyć lub zmodyfikować właśnie tę stronę.",
"userpage-userdoesnotexist-view": "Konto użytkownika „$1” nie jest zarejestrowane.",
"blocked-notice-logextract": "{{GENDER:$1|Ten użytkownik|Ta użytkowniczka}} jest obecnie {{GENDER:$1|zablokowany|zablokowana}}.\nOstatni wpis rejestru blokad jest pokazany poniżej.",
"clearyourcache": "<strong>Uwaga:</strong> aby zobaczyć zmiany po zapisaniu, może zajść potrzeba wyczyszczenia pamięci podręcznej przeglądarki.\n* <strong>Firefox / Safari:</strong> Przytrzymaj <em>Shift</em> podczas klikania <em>Odśwież bieżącą stronę</em>, lub naciśnij klawisze <em>Ctrl+F5</em> lub <em>Ctrl+R</em> (<em>⌘-R</em> na komputerze Mac)\n* <strong>Google Chrome:</strong> Naciśnij <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> na komputerze Mac)\n* <strong>Internet Explorer:</strong> Przytrzymaj <em>Ctrl</em>, jednocześnie klikając <em>Odśwież</em>, lub naciśnij klawisze <em>Ctrl+F5</em>\n* <strong>Opera:</strong> Przejdź do <em>Menu → Ustawienia</em> (<em>Opera → Preferencje</em> w Mac), a następnie <em>Prywatność i bezpieczeństwo → Wyczyść dane przeglądania → Opróżnij pamięć podręczną</em>.",
"rcfilters-filterlist-feedbacklink": "Podziel się swoją opinią na temat tych nowych (beta) filtrów",
"rcfilters-highlightbutton-title": "Podświetl wyniki",
"rcfilters-highlightmenu-title": "Wybierz kolor",
+ "rcfilters-highlightmenu-help": "Wybierz kolor, aby podświetlić tę właściwość",
"rcfilters-filterlist-noresults": "Nie znaleziono filtrów",
"rcfilters-filtergroup-registration": "Rejestracja użytkownika",
"rcfilters-filter-registered-label": "Zarejestrowani",
"userlogin-yourpassword": "In login & create account forms, label for password field.\n\nSee examples: [[Special:UserLogin]] and [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n{{Identical|Password}}",
"userlogin-yourpassword-ph": "Placeholder text in login form for password field.\n\nSee examples: [[Special:UserLogin]] and [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n{{Identical|Enter password}}",
"createacct-yourpassword-ph": "Placeholder text in create account form for password field.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n{{Identical|Enter password}}",
- "yourpasswordagain": "Since 1.22 no longer used in core, but may be used by some extensions. DEPRECATED",
+ "yourpasswordagain": "Label for field to re-enter password.",
"createacct-yourpasswordagain": "In create account form, label for field to re-enter password\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]\n{{Identical|Confirm password}}",
"createacct-yourpasswordagain-ph": "Placeholder text in create account form for re-enter password field.\n\nSee example: [{{canonicalurl:Special:UserLogin|type=signup}} Special:UserLogin?type=signup]",
"userlogin-remembermypassword": "The text for a check box in [[Special:UserLogin]].",
"createacct-benefit-body1": "{{PLURAL:$1|редагування|редагування|редагувань}}",
"createacct-benefit-body2": "{{PLURAL:$1|сторінка|сторінки|сторінок}}",
"createacct-benefit-body3": "{{PLURAL:$1|дописувач|дописувачі|дописувачів}} цього місяця",
- "badretype": "Введені паролі не співпадають.",
+ "badretype": "Введені паролі не збігаються.",
"usernameinprogress": "Створення облікового запису для цього імені користувача уже виконується.\nБудь ласка, зачекайте.",
"userexists": "Введене ім'я користувача вже існує.\nБудь ласка оберіть інше ім'я.",
"loginerror": "Помилка при вході до системи",
"delete_and_move_text": "Сторінка з назвою [[:$1|«$1»]] вже існує.\nБажаєте вилучити її для можливості перейменування?",
"delete_and_move_confirm": "Так, вилучити для перейменування",
"delete_and_move_reason": "Вилучена для можливості перейменування сторінки «[[$1]]»",
- "selfmove": "Неможливо перейменувати сторінку: поточна й нова назви сторінки співпадають.",
+ "selfmove": "Неможливо перейменувати сторінку: поточна й нова назви сторінки збігаються.",
"immobile-source-namespace": "Не можна перейменовувати сторінки з простору назв «$1»",
"immobile-target-namespace": "Не можна перейменовувати сторінки до простору назв «$1»",
"immobile-target-namespace-iw": "Інтервікі-посилання не підходить для перейменування сторінки.",
NS_CATEGORY => 'گٹھ',
NS_CATEGORY_TALK => 'گٹھ_گل_بات',
];
+
+$namespaceAliases = [
+ 'تصویر' => NS_FILE,
+];
+
+$magicWords = [
+ 'redirect' => [ '0', '#مڑجوڑ', '#REDIRECT' ],
+];
'mediawiki.rcfilters.filters.dm',
'oojs-ui.styles.icons-moderation',
'oojs-ui.styles.icons-editing-core',
+ 'oojs-ui.styles.icons-editing-styling',
'oojs-ui.styles.icons-interactions',
],
],
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:40Z
+ * Date: 2017-03-07T22:57:01Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:44Z
+ * Date: 2017-03-07T22:57:06Z
*/
-/* stylelint-disable selector-no-vendor-prefix, at-rule-no-unknown */
-/* stylelint-enable selector-no-vendor-prefix, at-rule-no-unknown */
.oo-ui-element-hidden {
display: none !important;
- /* stylelint-disable-line declaration-no-important */
}
.oo-ui-buttonElement {
display: inline-block;
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor {
display: block;
position: absolute;
- /* `top` property is to be set in theme's selector due to specific `@size-anchor` values */
background-repeat: no-repeat;
}
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
+ content: '';
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-color: transparent;
+}
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor {
+ left: 0;
+ /* `top` property is to be set in theme's selector due to specific `@size-anchor` values */
+}
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:after {
+ border-top: 0;
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
+ left: 0;
+ /* `bottom` property is to be set in theme's selector due to specific `@size-anchor` values */
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:after {
+ border-bottom: 0;
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor {
+ top: 0;
+ /* `left` property is to be set in theme's selector due to specific `@size-anchor` values */
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:after {
+ border-left: 0;
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor {
+ top: 0;
+ /* `right` property is to be set in theme's selector due to specific `@size-anchor` values */
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:after {
+ border-right: 0;
+}
.oo-ui-popupWidget-head {
-webkit-touch-callout: none;
-webkit-user-select: none;
border-radius: 0.25em;
box-shadow: 0 0.15em 0.5em 0 rgba(0, 0, 0, 0.2);
}
-.oo-ui-popupWidget-anchored {
+.oo-ui-popupWidget-anchored-top {
margin-top: 6px;
}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor {
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor {
top: -6px;
}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
- content: '';
- position: absolute;
- width: 0;
- height: 0;
- border-style: solid;
- border-color: transparent;
- border-top: 0;
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:before {
bottom: -7px;
left: -6px;
border-bottom-color: #aaa;
border-width: 7px;
}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:after {
bottom: -7px;
left: -5px;
border-bottom-color: #fff;
border-width: 6px;
}
+.oo-ui-popupWidget-anchored-bottom {
+ margin-bottom: 6px;
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
+ bottom: -6px;
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:before {
+ top: -7px;
+ left: -6px;
+ border-top-color: #aaa;
+ border-width: 7px;
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:after {
+ top: -7px;
+ left: -5px;
+ border-top-color: #fff;
+ border-width: 6px;
+}
+.oo-ui-popupWidget-anchored-start {
+ margin-left: 6px;
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor {
+ left: -6px;
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:before {
+ right: -7px;
+ top: -6px;
+ border-right-color: #aaa;
+ border-width: 7px;
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:after {
+ right: -7px;
+ top: -5px;
+ border-right-color: #fff;
+ border-width: 6px;
+}
+.oo-ui-popupWidget-anchored-end {
+ margin-right: 6px;
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor {
+ right: -6px;
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:before {
+ left: -7px;
+ top: -6px;
+ border-left-color: #aaa;
+ border-width: 7px;
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:after {
+ left: -7px;
+ top: -5px;
+ border-left-color: #fff;
+ border-width: 6px;
+}
.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
-webkit-transition: width 100ms ease, height 100ms ease, left 100ms ease;
-moz-transition: width 100ms ease, height 100ms ease, left 100ms ease;
.oo-ui-popupButtonWidget .oo-ui-popupWidget {
cursor: auto;
}
-.oo-ui-popupWidget.oo-ui-popupButtonWidget-frameless-popup {
+.oo-ui-popupButtonWidget-frameless-popup.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor,
+.oo-ui-popupButtonWidget-frameless-popup.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
margin-left: 0.9375em;
}
-.oo-ui-popupWidget.oo-ui-popupButtonWidget-framed-popup {
+.oo-ui-popupButtonWidget-framed-popup.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor,
+.oo-ui-popupButtonWidget-framed-popup.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
margin-left: 1.2375em;
}
.oo-ui-inputWidget {
-webkit-transition: border-color 250ms ease, box-shadow 250ms ease;
-moz-transition: border-color 250ms ease, box-shadow 250ms ease;
transition: border-color 250ms ease, box-shadow 250ms ease;
- /* stylelint-disable indentation */
- /* stylelint-enable indentation */
}
.oo-ui-textInputWidget input.oo-ui-pendingElement-pending,
.oo-ui-textInputWidget textarea.oo-ui-pendingElement-pending {
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:44Z
+ * Date: 2017-03-07T22:57:06Z
*/
-/* stylelint-disable selector-no-vendor-prefix, at-rule-no-unknown */
-/* stylelint-enable selector-no-vendor-prefix, at-rule-no-unknown */
.oo-ui-element-hidden {
display: none !important;
- /* stylelint-disable-line declaration-no-important */
}
.oo-ui-buttonElement {
display: inline-block;
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor {
display: block;
position: absolute;
- /* `top` property is to be set in theme's selector due to specific `@size-anchor` values */
background-repeat: no-repeat;
}
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
+ content: '';
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-color: transparent;
+}
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor {
+ left: 0;
+ /* `top` property is to be set in theme's selector due to specific `@size-anchor` values */
+}
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:after {
+ border-top: 0;
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
+ left: 0;
+ /* `bottom` property is to be set in theme's selector due to specific `@size-anchor` values */
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:after {
+ border-bottom: 0;
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor {
+ top: 0;
+ /* `left` property is to be set in theme's selector due to specific `@size-anchor` values */
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:after {
+ border-left: 0;
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor {
+ top: 0;
+ /* `right` property is to be set in theme's selector due to specific `@size-anchor` values */
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:before,
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:after {
+ border-right: 0;
+}
.oo-ui-popupWidget-head {
-webkit-touch-callout: none;
-webkit-user-select: none;
border-radius: 2px;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.25);
}
-.oo-ui-popupWidget-anchored {
+.oo-ui-popupWidget-anchored-top {
margin-top: 9px;
}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor {
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor {
top: -9px;
}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before,
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
- content: '';
- position: absolute;
- width: 0;
- height: 0;
- border-style: solid;
- border-color: transparent;
- border-top: 0;
-}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:before {
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:before {
bottom: -10px;
left: -9px;
border-bottom-color: #a2a9b1;
border-width: 10px;
}
-.oo-ui-popupWidget-anchored .oo-ui-popupWidget-anchor:after {
+.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor:after {
bottom: -10px;
left: -8px;
border-bottom-color: #fff;
border-width: 9px;
}
+.oo-ui-popupWidget-anchored-bottom {
+ margin-bottom: 9px;
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
+ bottom: -9px;
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:before {
+ top: -10px;
+ left: -9px;
+ border-top-color: #a2a9b1;
+ border-width: 10px;
+}
+.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor:after {
+ top: -10px;
+ left: -8px;
+ border-top-color: #fff;
+ border-width: 9px;
+}
+.oo-ui-popupWidget-anchored-start {
+ margin-left: 9px;
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor {
+ left: -9px;
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:before {
+ right: -10px;
+ top: -9px;
+ border-right-color: #a2a9b1;
+ border-width: 10px;
+}
+.oo-ui-popupWidget-anchored-start .oo-ui-popupWidget-anchor:after {
+ right: -10px;
+ top: -8px;
+ border-right-color: #fff;
+ border-width: 9px;
+}
+.oo-ui-popupWidget-anchored-end {
+ margin-right: 9px;
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor {
+ right: -9px;
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:before {
+ left: -10px;
+ top: -9px;
+ border-left-color: #a2a9b1;
+ border-width: 10px;
+}
+.oo-ui-popupWidget-anchored-end .oo-ui-popupWidget-anchor:after {
+ left: -10px;
+ top: -8px;
+ border-left-color: #fff;
+ border-width: 9px;
+}
.oo-ui-popupWidget-transitioning .oo-ui-popupWidget-popup {
-webkit-transition: width 100ms, height 100ms, left 100ms;
-moz-transition: width 100ms, height 100ms, left 100ms;
.oo-ui-popupButtonWidget .oo-ui-popupWidget {
cursor: auto;
}
-.oo-ui-popupWidget.oo-ui-popupButtonWidget-frameless-popup {
+.oo-ui-popupButtonWidget-frameless-popup.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor,
+.oo-ui-popupButtonWidget-frameless-popup.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
margin-left: 0.9375em;
}
-.oo-ui-popupWidget.oo-ui-popupButtonWidget-framed-popup {
+.oo-ui-popupButtonWidget-framed-popup.oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor,
+.oo-ui-popupButtonWidget-framed-popup.oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
margin-left: 1.5em;
}
.oo-ui-inputWidget {
-webkit-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);
-moz-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);
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);
- /* stylelint-disable indentation */
- /* stylelint-enable indentation */
}
.oo-ui-textInputWidget.oo-ui-widget-enabled input:hover,
.oo-ui-textInputWidget.oo-ui-widget-enabled textarea:hover {
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:40Z
+ * Date: 2017-03-07T22:57:01Z
*/
( function ( OO ) {
* 'start': Align the start (left in LTR, right in RTL) edge with $floatableContainer's start edge
* 'end': Align the end (right in LTR, left in RTL) edge with $floatableContainer's end edge
* 'center': Horizontally align the center with $floatableContainer's center
+ * @cfg {boolean} [hideWhenOutOfView=true] Whether to hide the floatable element if the container
+ * is out of view
*/
OO.ui.mixin.FloatableElement = function OoUiMixinFloatableElement( config ) {
// Configuration initialization
this.setFloatableElement( config.$floatable || this.$element );
this.setVerticalPosition( config.verticalPosition || 'below' );
this.setHorizontalPosition( config.horizontalPosition || 'start' );
+ this.hideWhenOutOfView = config.hideWhenOutOfView === undefined ? true : !!config.hideWhenOutOfView;
};
/* Methods */
if ( [ 'below', 'above', 'top', 'bottom', 'center' ].indexOf( position ) === -1 ) {
throw new Error( 'Invalid value for vertical position: ' + position );
}
- this.verticalPosition = position;
- if ( this.$floatable ) {
- this.position();
+ if ( this.verticalPosition !== position ) {
+ this.verticalPosition = position;
+ if ( this.$floatable ) {
+ this.position();
+ }
}
};
if ( [ 'before', 'after', 'start', 'end', 'center' ].indexOf( position ) === -1 ) {
throw new Error( 'Invalid value for horizontal position: ' + position );
}
- this.horizontalPosition = position;
- if ( this.$floatable ) {
- this.position();
+ if ( this.horizontalPosition !== position ) {
+ this.horizontalPosition = position;
+ if ( this.$floatable ) {
+ this.position();
+ }
}
};
* @chainable
*/
OO.ui.mixin.FloatableElement.prototype.position = function () {
- var containerPos, direction, $offsetParent, isBody, scrollableX, scrollableY,
- horizScrollbarHeight, vertScrollbarWidth, scrollTop, scrollLeft,
- newPos = { top: '', left: '', bottom: '', right: '' };
-
if ( !this.positioning ) {
return this;
}
- if ( !this.isElementInViewport( this.$floatableContainer, this.$floatableClosestScrollable ) ) {
+ if ( this.hideWhenOutOfView && !this.isElementInViewport( this.$floatableContainer, this.$floatableClosestScrollable ) ) {
this.$floatable.addClass( 'oo-ui-element-hidden' );
- return;
+ return this;
} else {
this.$floatable.removeClass( 'oo-ui-element-hidden' );
}
if ( !this.needsCustomPosition ) {
- return;
+ return this;
}
- direction = this.$floatableContainer.css( 'direction' );
- $offsetParent = this.$floatable.offsetParent();
+ this.$floatable.css( this.computePosition() );
+
+ // We updated the position, so re-evaluate the clipping state.
+ // (ClippableElement does not listen to 'scroll' events on $floatableContainer's parent, and so
+ // will not notice the need to update itself.)
+ // TODO: This is terrible, we shouldn't need to know about ClippableElement at all here. Why does
+ // it not listen to the right events in the right places?
+ if ( this.clip ) {
+ this.clip();
+ }
+
+ return this;
+};
+
+/**
+ * Compute how #$floatable should be positioned based on the position of #$floatableContainer
+ * and the positioning settings. This is a helper for #position that shouldn't be called directly,
+ * but may be overridden by subclasses if they want to change or add to the positioning logic.
+ *
+ * @return {Object} New position to apply with .css(). Keys are 'top', 'left', 'bottom' and 'right'.
+ */
+OO.ui.mixin.FloatableElement.prototype.computePosition = function () {
+ var isBody, scrollableX, scrollableY, containerPos,
+ horizScrollbarHeight, vertScrollbarWidth, scrollTop, scrollLeft,
+ newPos = { top: '', left: '', bottom: '', right: '' },
+ direction = this.$floatableContainer.css( 'direction' ),
+ $offsetParent = this.$floatable.offsetParent();
+
if ( $offsetParent.is( 'html' ) ) {
// The innerHeight/Width and clientHeight/Width calculations don't work well on the
// <html> element, but they do work on the <body>
}
}
- this.$floatable.css( newPos );
-
- // We updated the position, so re-evaluate the clipping state.
- // (ClippableElement does not listen to 'scroll' events on $floatableContainer's parent, and so
- // will not notice the need to update itself.)
- // TODO: This is terrible, we shouldn't need to know about ClippableElement at all here. Why does
- // it not listen to the right events in the right places?
- if ( this.clip ) {
- this.clip();
- }
-
- return this;
+ return newPos;
};
/**
* @cfg {number} [width=320] Width of popup in pixels
* @cfg {number} [height] Height of popup in pixels. Omit to use the automatic height.
* @cfg {boolean} [anchor=true] Show anchor pointing to origin of popup
- * @cfg {string} [align='center'] Alignment of the popup: `center`, `force-left`, `force-right`, `backwards` or `forwards`.
- * If the popup is forced-left the popup body is leaning towards the left. For force-right alignment, the body of the
- * popup is leaning towards the right of the screen.
- * Using 'backwards' is a logical direction which will result in the popup leaning towards the beginning of the sentence
- * in the given language, which means it will flip to the correct positioning in right-to-left languages.
- * Using 'forward' will also result in a logical alignment where the body of the popup leans towards the end of the
- * sentence in the given language.
+ * @cfg {string} [position='below'] Where to position the popup relative to $floatableContainer
+ * 'above': Put popup above $floatableContainer; anchor points down to the start edge of $floatableContainer
+ * 'below': Put popup below $floatableContainer; anchor points up to the start edge of $floatableContainer
+ * 'before': Put popup to the left (LTR) / right (RTL) of $floatableContainer; anchor points
+ * endwards (right/left) to the vertical center of $floatableContainer
+ * 'after': Put popup to the right (LTR) / left (RTL) of $floatableContainer; anchor points
+ * startwards (left/right) to the vertical center of $floatableContainer
+ * @cfg {string} [align='center'] How to align the popup to $floatableContainer
+ * 'forwards': If position is above/below, move the popup as far endwards (right in LTR, left in RTL)
+ * as possible while still keeping the anchor within the popup;
+ * if position is before/after, move the popup as far downwards as possible.
+ * 'backwards': If position is above/below, move the popup as far startwards (left in LTR, right in RTL)
+ * as possible while still keeping the anchor within the popup;
+ * if position in before/after, move the popup as far upwards as possible.
+ * 'center': Horizontally (if position is above/below) or vertically (before/after) align the center
+ * of the popup with the center of $floatableContainer.
+ * 'force-left': Alias for 'forwards' in LTR and 'backwards' in RTL
+ * 'force-right': Alias for 'backwards' in RTL and 'forwards' in LTR
* @cfg {jQuery} [$container] Constrain the popup to the boundaries of the specified container.
* See the [OOjs UI docs on MediaWiki][3] for an example.
* [3]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups#containerExample
this.autoClose = !!config.autoClose;
this.$autoCloseIgnore = config.$autoCloseIgnore;
this.transitionTimeout = null;
- this.anchor = null;
+ this.anchored = false;
this.width = config.width !== undefined ? config.width : 320;
this.height = config.height !== undefined ? config.height : null;
- this.setAlignment( config.align );
this.onMouseDownHandler = this.onMouseDown.bind( this );
this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this );
// Initialization
this.toggleAnchor( config.anchor === undefined || config.anchor );
+ this.setAlignment( config.align || 'center' );
+ this.setPosition( config.position || 'below' );
this.$body.addClass( 'oo-ui-popupWidget-body' );
this.$anchor.addClass( 'oo-ui-popupWidget-anchor' );
this.$popup
this.anchored = show;
}
};
+/**
+ * Change which edge the anchor appears on.
+ *
+ * @param {string} edge 'top', 'bottom', 'start' or 'end'
+ */
+OO.ui.PopupWidget.prototype.setAnchorEdge = function ( edge ) {
+ if ( [ 'top', 'bottom', 'start', 'end' ].indexOf( edge ) === -1 ) {
+ throw new Error( 'Invalid value for edge: ' + edge );
+ }
+ if ( this.anchorEdge !== null ) {
+ this.$element.removeClass( 'oo-ui-popupWidget-anchored-' + this.anchorEdge );
+ }
+ this.anchorEdge = edge;
+ this.$element.addClass( 'oo-ui-popupWidget-anchored-' + edge );
+};
/**
* Check if the anchor is visible.
* @return {boolean} Anchor is visible
*/
OO.ui.PopupWidget.prototype.hasAnchor = function () {
- return this.anchor;
+ return this.anchored;
};
/**
OO.ui.warnDeprecation( 'PopupWidget#toggle: Before calling this method, the popup must be attached to the DOM.' );
this.warnedUnattached = true;
}
+ if ( show && !this.$floatableContainer && this.isElementAttached() ) {
+ // Fall back to the parent node if the floatableContainer is not set
+ this.setFloatableContainer( this.$element.parent() );
+ }
// Parent method
OO.ui.PopupWidget.parent.prototype.toggle.call( this, show );
* @chainable
*/
OO.ui.PopupWidget.prototype.updateDimensions = function ( transition ) {
- var popupOffset, originOffset, containerLeft, containerWidth, containerRight,
- popupLeft, popupRight, overlapLeft, overlapRight, anchorWidth, direction,
- dirFactor, align,
+ var widget = this;
+
+ // Prevent transition from being interrupted
+ clearTimeout( this.transitionTimeout );
+ if ( transition ) {
+ // Enable transition
+ this.$element.addClass( 'oo-ui-popupWidget-transitioning' );
+ }
+
+ this.position();
+
+ if ( transition ) {
+ // Prevent transitioning after transition is complete
+ this.transitionTimeout = setTimeout( function () {
+ widget.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
+ }, 200 );
+ } else {
+ // Prevent transitioning immediately
+ this.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
+ }
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.PopupWidget.prototype.computePosition = function () {
+ var direction, align, vertical, start, end, near, far, sizeProp, popupSize, anchorSize, anchorPos,
+ anchorOffset, anchorMargin, parentPosition, positionProp, positionAdjustment, floatablePos,
+ offsetParentPos, containerPos,
+ popupPos = {},
+ anchorCss = { left: '', right: '', top: '', bottom: '' },
alignMap = {
ltr: {
'force-left': 'backwards',
'force-right': 'backwards'
}
},
- widget = this;
+ anchorEdgeMap = {
+ above: 'bottom',
+ below: 'top',
+ before: 'end',
+ after: 'start'
+ },
+ hPosMap = {
+ forwards: 'start',
+ center: 'center',
+ backwards: 'before'
+ },
+ vPosMap = {
+ forwards: 'top',
+ center: 'center',
+ backwards: 'bottom'
+ };
if ( !this.$container ) {
// Lazy-initialize $container if not specified in constructor
this.$container = $( this.getClosestScrollableElementContainer() );
}
direction = this.$container.css( 'direction' );
- dirFactor = direction === 'rtl' ? -1 : 1;
- align = alignMap[ direction ][ this.align ] || this.align;
- // Set height and width before measuring things, since it might cause our measurements
- // to change (e.g. due to scrollbars appearing or disappearing)
+ // Set height and width before we do anything else, since it might cause our measurements
+ // to change (e.g. due to scrollbars appearing or disappearing), and it also affects centering
this.$popup.css( {
width: this.width,
height: this.height !== null ? this.height : 'auto'
} );
- // Compute initial popupOffset based on alignment
- popupOffset = this.width * ( { backwards: -1, center: -0.5, forwards: 0 } )[ align ];
-
- // Figure out if this will cause the popup to go beyond the edge of the container
- originOffset = this.$element.offset().left;
- containerLeft = this.$container.offset().left;
- containerWidth = this.$container.innerWidth();
- containerRight = containerLeft + containerWidth;
- popupLeft = dirFactor * popupOffset - this.containerPadding;
- popupRight = dirFactor * popupOffset + this.containerPadding + this.width + this.containerPadding;
- overlapLeft = ( originOffset + popupLeft ) - containerLeft;
- overlapRight = containerRight - ( originOffset + popupRight );
-
- // Adjust offset to make the popup not go beyond the edge, if needed
- if ( overlapRight < 0 ) {
- popupOffset += dirFactor * overlapRight;
- } else if ( overlapLeft < 0 ) {
- popupOffset -= dirFactor * overlapLeft;
- }
+ align = alignMap[ direction ][ this.align ] || this.align;
+ // If the popup is positioned before or after, then the anchor positioning is vertical, otherwise horizontal
+ vertical = this.popupPosition === 'before' || this.popupPosition === 'after';
+ start = vertical ? 'top' : ( direction === 'rtl' ? 'right' : 'left' );
+ end = vertical ? 'bottom' : ( direction === 'rtl' ? 'left' : 'right' );
+ near = vertical ? 'top' : 'left';
+ far = vertical ? 'bottom' : 'right';
+ sizeProp = vertical ? 'Height' : 'Width';
+ popupSize = vertical ? ( this.height || this.$popup.height() ) : this.width;
+
+ this.setAnchorEdge( anchorEdgeMap[ this.popupPosition ] );
+ this.horizontalPosition = vertical ? this.popupPosition : hPosMap[ align ];
+ this.verticalPosition = vertical ? vPosMap[ align ] : this.popupPosition;
- // Adjust offset to avoid anchor being rendered too close to the edge
- // $anchor.width() doesn't work with the pure CSS anchor (returns 0)
- // TODO: Find a measurement that works for CSS anchors and image anchors
- anchorWidth = this.$anchor[ 0 ].scrollWidth * 2;
- if ( popupOffset + this.width < anchorWidth ) {
- popupOffset = anchorWidth - this.width;
- } else if ( -popupOffset < anchorWidth ) {
- popupOffset = -anchorWidth;
+ // Parent method
+ parentPosition = OO.ui.mixin.FloatableElement.prototype.computePosition.call( this );
+ // Find out which property FloatableElement used for positioning, and adjust that value
+ positionProp = vertical ?
+ ( parentPosition.top !== '' ? 'top' : 'bottom' ) :
+ ( parentPosition.left !== '' ? 'left' : 'right' );
+
+ // Figure out where the near and far edges of the popup and $floatableContainer are
+ floatablePos = this.$floatableContainer.offset();
+ floatablePos[ far ] = floatablePos[ near ] + this.$floatableContainer[ 'outer' + sizeProp ]();
+ // Measure where the offsetParent is and compute our position based on that and parentPosition
+ offsetParentPos = this.$element.offsetParent().offset();
+
+ if ( positionProp === near ) {
+ popupPos[ near ] = offsetParentPos[ near ] + parentPosition[ near ];
+ popupPos[ far ] = popupPos[ near ] + popupSize;
+ } else {
+ popupPos[ far ] = offsetParentPos[ near ] +
+ this.$element.offsetParent()[ 'inner' + sizeProp ]() - parentPosition[ far ];
+ popupPos[ near ] = popupPos[ far ] - popupSize;
+ }
+
+ // Position the anchor (which is positioned relative to the popup) to point to $floatableContainer
+ // For popups above/below, we point to the start edge; for popups before/after, we point to the center
+ anchorPos = vertical ? ( floatablePos[ start ] + floatablePos[ end ] ) / 2 : floatablePos[ start ];
+ anchorOffset = ( start === far ? -1 : 1 ) * ( anchorPos - popupPos[ start ] );
+
+ // If the anchor is less than 2*anchorSize from either edge, move the popup to make more space
+ // this.$anchor.width()/height() returns 0 because of the CSS trickery we use, so use scrollWidth/Height
+ anchorSize = this.$anchor[ 0 ][ 'scroll' + sizeProp ];
+ anchorMargin = parseFloat( this.$anchor.css( 'margin-' + start ) );
+ if ( anchorOffset + anchorMargin < 2 * anchorSize ) {
+ // Not enough space for the anchor on the start side; pull the popup startwards
+ positionAdjustment = ( positionProp === start ? -1 : 1 ) *
+ ( 2 * anchorSize - ( anchorOffset + anchorMargin ) );
+ } else if ( anchorOffset + anchorMargin > popupSize - 2 * anchorSize ) {
+ // Not enough space for the anchor on the end side; pull the popup endwards
+ positionAdjustment = ( positionProp === end ? -1 : 1 ) *
+ ( anchorOffset + anchorMargin - ( popupSize - 2 * anchorSize ) );
+ } else {
+ positionAdjustment = 0;
}
- // Prevent transition from being interrupted
- clearTimeout( this.transitionTimeout );
- if ( transition ) {
- // Enable transition
- this.$element.addClass( 'oo-ui-popupWidget-transitioning' );
+ // Check if the popup will go beyond the edge of this.$container
+ containerPos = this.$container.offset();
+ containerPos[ far ] = containerPos[ near ] + this.$container[ 'inner' + sizeProp ]();
+ // Take into account how much the popup will move because of the adjustments we're going to make
+ popupPos[ near ] += ( positionProp === near ? 1 : -1 ) * positionAdjustment;
+ popupPos[ far ] += ( positionProp === near ? 1 : -1 ) * positionAdjustment;
+ if ( containerPos[ near ] + this.containerPadding > popupPos[ near ] ) {
+ // Popup goes beyond the near (left/top) edge, move it to the right/bottom
+ positionAdjustment += ( positionProp === near ? 1 : -1 ) *
+ ( containerPos[ near ] + this.containerPadding - popupPos[ near ] );
+ } else if ( containerPos[ far ] - this.containerPadding < popupPos[ far ] ) {
+ // Popup goes beyond the far (right/bottom) edge, move it to the left/top
+ positionAdjustment += ( positionProp === far ? 1 : -1 ) *
+ ( popupPos[ far ] - ( containerPos[ far ] - this.containerPadding ) );
}
- // Position body relative to anchor
- this.$popup.css( direction === 'rtl' ? 'margin-right' : 'margin-left', popupOffset );
-
- if ( transition ) {
- // Prevent transitioning after transition is complete
- this.transitionTimeout = setTimeout( function () {
- widget.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
- }, 200 );
- } else {
- // Prevent transitioning immediately
- this.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
- }
+ // Adjust anchorOffset for positionAdjustment
+ anchorOffset += ( positionProp === start ? -1 : 1 ) * positionAdjustment;
- // Reevaluate clipping state since we've relocated and resized the popup
- this.clip();
+ // Position the anchor
+ anchorCss[ start ] = anchorOffset;
+ this.$anchor.css( anchorCss );
+ // Move the popup if needed
+ parentPosition[ positionProp ] += positionAdjustment;
- return this;
+ return parentPosition;
};
/**
} else {
this.align = 'center';
}
+ this.position();
};
/**
* Get popup alignment
*
- * @return {string} align Alignment of the popup, `center`, `force-left`, `force-right`,
+ * @return {string} Alignment of the popup, `center`, `force-left`, `force-right`,
* `backwards` or `forwards`.
*/
OO.ui.PopupWidget.prototype.getAlignment = function () {
return this.align;
};
+/**
+ * Change the positioning of the popup.
+ *
+ * @param {string} position 'above', 'below', 'before' or 'after'
+ */
+OO.ui.PopupWidget.prototype.setPosition = function ( position ) {
+ if ( [ 'above', 'below', 'before', 'after' ].indexOf( position ) === -1 ) {
+ position = 'below';
+ }
+ this.popupPosition = position;
+ this.position();
+};
+
+/**
+ * Get popup positioning.
+ *
+ * @return {string} 'above', 'below', 'before' or 'after'
+ */
+OO.ui.PopupWidget.prototype.getPosition = function () {
+ return this.popupPosition;
+};
+
/**
* PopupElement is mixed into other classes to generate a {@link OO.ui.PopupWidget popup widget}.
* A popup is a container for content. It is overlaid and positioned absolutely. By default, each
// Properties
this.popup = new OO.ui.PopupWidget( $.extend(
- { autoClose: true },
+ {
+ autoClose: true,
+ $floatableContainer: this.$element
+ },
config.popup,
- { $autoCloseIgnore: this.$element.add( config.popup && config.popup.$autoCloseIgnore ) }
+ {
+ $autoCloseIgnore: this.$element.add( config.popup && config.popup.$autoCloseIgnore )
+ }
) );
};
OO.ui.PopupButtonWidget.parent.call( this, config );
// Mixin constructors
- OO.ui.mixin.PopupElement.call( this, $.extend( true, {}, config, {
- popup: {
- $floatableContainer: this.$element
- }
- } ) );
+ OO.ui.mixin.PopupElement.call( this, config );
// Properties
this.$overlay = config.$overlay || this.$element;
* @protected
*/
OO.ui.MenuSelectWidget.prototype.updateItemVisibility = function () {
- var i, item, visible,
+ var i, item, visible, section, sectionEmpty,
anyVisible = false,
len = this.items.length,
showAll = !this.isVisible(),
filter = showAll ? null : this.getItemMatcher( this.$input.val() );
+ // Hide non-matching options, and also hide section headers if all options
+ // in their section are hidden.
for ( i = 0; i < len; i++ ) {
item = this.items[ i ];
- if ( item instanceof OO.ui.OptionWidget ) {
+ if ( item instanceof OO.ui.MenuSectionOptionWidget ) {
+ if ( section ) {
+ // If the previous section was empty, hide its header
+ section.toggle( showAll || !sectionEmpty );
+ }
+ section = item;
+ sectionEmpty = true;
+ } else if ( item instanceof OO.ui.OptionWidget ) {
visible = showAll || filter( item );
anyVisible = anyVisible || visible;
+ sectionEmpty = sectionEmpty && !visible;
item.toggle( visible );
}
}
+ // Process the final section
+ if ( section ) {
+ section.toggle( showAll || !sectionEmpty );
+ }
this.$element.toggleClass( 'oo-ui-menuSelectWidget-invisible', !anyVisible );
* specifies minimum number of rows to display.
* @cfg {boolean} [autosize=false] Automatically resize the text input to fit its content.
* Use the #maxRows config to specify a maximum number of displayed rows.
- * @cfg {boolean} [maxRows] Maximum number of rows to display when #autosize is set to true.
+ * @cfg {number} [maxRows] Maximum number of rows to display when #autosize is set to true.
* Defaults to the maximum of `10` and `2 * rows`, or `10` if `rows` isn't provided.
* @cfg {string} [labelPosition='after'] The position of the inline label relative to that of
* the value or placeholder text: `'before'` or `'after'`
* - by choosing a value from the menu. The value of the chosen option will then appear in the text
* input field.
*
+ * After the user chooses an option, its `data` will be used as a new value for the widget.
+ * A `label` also can be specified for each option: if given, it will be shown instead of the
+ * `data` in the dropdown menu.
+ *
* This widget can be used inside an HTML form, such as a OO.ui.FormLayout.
*
* For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1].
* @example
* // Example: A ComboBoxInputWidget.
* var comboBox = new OO.ui.ComboBoxInputWidget( {
- * label: 'ComboBoxInputWidget',
* value: 'Option 1',
- * menu: {
- * items: [
- * new OO.ui.MenuOptionWidget( {
- * data: 'Option 1',
- * label: 'Option One'
- * } ),
- * new OO.ui.MenuOptionWidget( {
- * data: 'Option 2',
- * label: 'Option Two'
- * } ),
- * new OO.ui.MenuOptionWidget( {
- * data: 'Option 3',
- * label: 'Option Three'
- * } ),
- * new OO.ui.MenuOptionWidget( {
- * data: 'Option 4',
- * label: 'Option Four'
- * } ),
- * new OO.ui.MenuOptionWidget( {
- * data: 'Option 5',
- * label: 'Option Five'
- * } )
- * ]
- * }
+ * options: [
+ * { data: 'Option 1' },
+ * { data: 'Option 2' },
+ * { data: 'Option 3' }
+ * ]
+ * } );
+ * $( 'body' ).append( comboBox.$element );
+ *
+ * @example
+ * // Example: A ComboBoxInputWidget with additional option labels.
+ * var comboBox = new OO.ui.ComboBoxInputWidget( {
+ * value: 'Option 1',
+ * options: [
+ * {
+ * data: 'Option 1',
+ * label: 'Option One'
+ * },
+ * {
+ * data: 'Option 2',
+ * label: 'Option Two'
+ * },
+ * {
+ * data: 'Option 3',
+ * label: 'Option Three'
+ * }
+ * ]
* } );
* $( 'body' ).append( comboBox.$element );
*
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:40Z
+ * Date: 2017-03-07T22:57:01Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:44Z
+ * Date: 2017-03-07T22:57:06Z
*/
-/* stylelint-disable selector-no-vendor-prefix, at-rule-no-unknown */
-/* stylelint-enable selector-no-vendor-prefix, at-rule-no-unknown */
.oo-ui-popupTool .oo-ui-popupWidget-popup,
.oo-ui-popupTool .oo-ui-popupWidget-anchor {
z-index: 4;
}
-.oo-ui-popupTool .oo-ui-popupWidget {
- /* @noflip */
+.oo-ui-popupTool .oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor,
+.oo-ui-popupTool .oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
margin-left: 1.25em;
}
.oo-ui-toolGroupTool > .oo-ui-popupToolGroup {
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:44Z
+ * Date: 2017-03-07T22:57:06Z
*/
-/* stylelint-disable selector-no-vendor-prefix, at-rule-no-unknown */
-/* stylelint-enable selector-no-vendor-prefix, at-rule-no-unknown */
.oo-ui-tool.oo-ui-widget-enabled {
-webkit-transition: background-color 100ms;
-moz-transition: background-color 100ms;
.oo-ui-popupTool .oo-ui-popupWidget-anchor {
z-index: 4;
}
-.oo-ui-popupTool .oo-ui-popupWidget {
- /* @noflip */
+.oo-ui-popupTool .oo-ui-popupWidget-anchored-top .oo-ui-popupWidget-anchor,
+.oo-ui-popupTool .oo-ui-popupWidget-anchored-bottom .oo-ui-popupWidget-anchor {
margin-left: 1.25em;
}
.oo-ui-toolGroupTool > .oo-ui-toolGroup {
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:40Z
+ * Date: 2017-03-07T22:57:01Z
*/
( function ( OO ) {
OO.ui.mixin.PopupElement.call( this, config );
// Initialization
+ this.popup.setPosition( toolGroup.getToolbar().position === 'bottom' ? 'above' : 'below' );
this.$element
.addClass( 'oo-ui-popupTool' )
.append( this.popup.$element );
for ( i = 0, len = this.collapsibleTools.length; i < len; i++ ) {
this.collapsibleTools[ i ].toggle( this.expanded );
}
+
+ // Re-evaluate clipping, because our height has changed
+ this.clip();
};
/**
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:44Z
+ * Date: 2017-03-07T22:57:06Z
*/
-/* stylelint-disable selector-no-vendor-prefix, at-rule-no-unknown */
-/* stylelint-enable selector-no-vendor-prefix, at-rule-no-unknown */
.oo-ui-draggableElement-handle,
.oo-ui-draggableElement-handle.oo-ui-widget {
cursor: move;
left: 0;
right: 0;
bottom: 0;
- /* stylelint-disable declaration-no-important */
- /* stylelint-enable declaration-no-important */
}
.oo-ui-menuLayout-menu,
.oo-ui-menuLayout-content {
.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
opacity: 0.5;
}
-.oo-ui-outlineOptionWidget-level-0 {
- padding-left: 3.5em;
-}
-.oo-ui-outlineOptionWidget-level-0 .oo-ui-iconElement-icon {
- left: 1em;
+.oo-ui-outlineOptionWidget-level-0.oo-ui-iconElement {
+ padding-left: 2.5em;
}
.oo-ui-outlineOptionWidget-level-1 {
- padding-left: 5em;
+ padding-left: 2.5em;
+}
+.oo-ui-outlineOptionWidget-level-1.oo-ui-iconElement {
+ padding-left: 4.5em;
}
-.oo-ui-outlineOptionWidget-level-1 .oo-ui-iconElement-icon {
+.oo-ui-outlineOptionWidget-level-1.oo-ui-iconElement .oo-ui-iconElement-icon {
left: 2.5em;
}
.oo-ui-outlineOptionWidget-level-2 {
- padding-left: 6.5em;
+ padding-left: 5em;
+}
+.oo-ui-outlineOptionWidget-level-2.oo-ui-iconElement {
+ padding-left: 7em;
}
-.oo-ui-outlineOptionWidget-level-2 .oo-ui-iconElement-icon {
- left: 4em;
+.oo-ui-outlineOptionWidget-level-2.oo-ui-iconElement .oo-ui-iconElement-icon {
+ left: 5em;
}
.oo-ui-selectWidget-depressed .oo-ui-outlineOptionWidget.oo-ui-optionWidget-selected {
background-color: #a7dcff;
background-color: transparent;
color: #000;
vertical-align: middle;
- /* stylelint-disable indentation */
- /* stylelint-enable indentation */
}
.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content > input::-webkit-input-placeholder {
color: #72777d;
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:44Z
+ * Date: 2017-03-07T22:57:06Z
*/
-/* stylelint-disable selector-no-vendor-prefix, at-rule-no-unknown */
-/* stylelint-enable selector-no-vendor-prefix, at-rule-no-unknown */
.oo-ui-draggableElement-handle,
.oo-ui-draggableElement-handle.oo-ui-widget {
cursor: move;
left: 0;
right: 0;
bottom: 0;
- /* stylelint-disable declaration-no-important */
- /* stylelint-enable declaration-no-important */
}
.oo-ui-menuLayout-menu,
.oo-ui-menuLayout-content {
.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropLabel {
display: none;
}
+.oo-ui-outlineSelectWidget {
+ height: 100%;
+}
+.oo-ui-outlineSelectWidget:focus {
+ box-shadow: inset 0 0 0 2px #36c;
+}
.oo-ui-outlineOptionWidget {
-webkit-touch-callout: none;
-webkit-user-select: none;
.oo-ui-outlineOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
opacity: 0.5;
}
-.oo-ui-outlineOptionWidget-level-0 {
- padding-left: 3.5em;
-}
-.oo-ui-outlineOptionWidget-level-0 .oo-ui-iconElement-icon {
- left: 1em;
+.oo-ui-outlineOptionWidget-level-0.oo-ui-iconElement {
+ padding-left: 2.571em;
}
.oo-ui-outlineOptionWidget-level-1 {
- padding-left: 5em;
+ padding-left: 2.571em;
+}
+.oo-ui-outlineOptionWidget-level-1.oo-ui-iconElement {
+ padding-left: 4.429em;
}
-.oo-ui-outlineOptionWidget-level-1 .oo-ui-iconElement-icon {
- left: 2.5em;
+.oo-ui-outlineOptionWidget-level-1.oo-ui-iconElement .oo-ui-iconElement-icon {
+ left: 2.571em;
}
.oo-ui-outlineOptionWidget-level-2 {
- padding-left: 6.5em;
+ padding-left: 5.142em;
+}
+.oo-ui-outlineOptionWidget-level-2.oo-ui-iconElement {
+ padding-left: 6.857em;
}
-.oo-ui-outlineOptionWidget-level-2 .oo-ui-iconElement-icon {
- left: 4em;
+.oo-ui-outlineOptionWidget-level-2.oo-ui-iconElement .oo-ui-iconElement-icon {
+ left: 4.429em;
}
.oo-ui-outlineControlsWidget {
height: 3em;
background-color: transparent;
color: #000;
vertical-align: middle;
- /* stylelint-disable indentation */
- /* stylelint-enable indentation */
}
.oo-ui-capsuleMultiselectWidget-handle > .oo-ui-capsuleMultiselectWidget-content > input::-webkit-input-placeholder {
color: #72777d;
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:40Z
+ * Date: 2017-03-07T22:57:01Z
*/
( function ( OO ) {
align: 'forwards',
anchor: false
} );
- OO.ui.mixin.PopupElement.call( this, $.extend( true, {}, config, {
- popup: {
- $floatableContainer: this.$element
- }
- } ) );
+ OO.ui.mixin.PopupElement.call( this, config );
$tabFocus = $( '<span>' );
OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: $tabFocus } ) );
} else {
if ( height !== this.height ) {
this.height = height;
this.menu.position();
+ if ( this.popup ) {
+ this.popup.updateDimensions();
+ }
this.emit( 'resize' );
}
};
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:44Z
+ * Date: 2017-03-07T22:57:06Z
*/
-/* stylelint-disable selector-no-vendor-prefix, at-rule-no-unknown */
-/* stylelint-enable selector-no-vendor-prefix, at-rule-no-unknown */
.oo-ui-actionWidget.oo-ui-pendingElement-pending {
background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
}
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:44Z
+ * Date: 2017-03-07T22:57:06Z
*/
-/* stylelint-disable selector-no-vendor-prefix, at-rule-no-unknown */
-/* stylelint-enable selector-no-vendor-prefix, at-rule-no-unknown */
.oo-ui-window {
background: transparent;
}
/*!
- * OOjs UI v0.19.4
+ * OOjs UI v0.19.5
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-02-28T23:19:40Z
+ * Date: 2017-03-07T22:57:01Z
*/
( function ( OO ) {
"ltr": "images/icons/articleRedirect-ltr.svg",
"rtl": "images/icons/articleRedirect-rtl.svg"
} },
+ "journal": { "file": {
+ "ltr": "images/icons/journal-ltr.svg",
+ "rtl": "images/icons/journal-rtl.svg"
+ } },
"upload": { "file": {
"ltr": "images/icons/upload-ltr.svg",
"rtl": "images/icons/upload-rtl.svg"
"os": "images/icons/bold-cyrl-be.svg"
}
} },
+ "highlight": { "file": {
+ "ltr": "images/icons/highlight-ltr.svg",
+ "rtl": "images/icons/highlight-rtl.svg"
+ } },
"italic": { "file": {
"default": "images/icons/italic-a.svg",
"lang": {
}
},
"images": {
- "beta": { "file": "images/icons/beta.svg" },
- "betaLaunch": { "file": "images/icons/logo-wikimediaDiscovery.svg" },
+ "add": { "file": "images/icons/add.svg" },
+ "beta": { "file": "images/icons/beta.svg", "deprecated": "Deprecated since v0.18.3, don't use." },
+ "betaLaunch": { "file": "images/icons/logo-wikimediaDiscovery.svg", "deprecated": "Moved since v0.18.3, use 'logoWikimediaDiscovery' from the 'Wikimedia' pack instead." },
"bookmark": { "file": {
"ltr": "images/icons/bookmark-ltr.svg",
"rtl": "images/icons/bookmark-rtl.svg"
"ltr": "images/icons/printer-ltr.svg",
"rtl": "images/icons/printer-rtl.svg"
} },
- "ribbonPrize": { "file": "images/icons/ribbonPrize.svg" },
+ "ribbonPrize": { "file": "images/icons/ribbonPrize.svg", "deprecated": "Deprecated since v0.18.3, don't use." },
+ "subtract": { "file": "images/icons/subtract.svg" },
"sun": { "file": {
"ltr": "images/icons/sun-ltr.svg",
"rtl": "images/icons/sun-rtl.svg"
"blockUndo": { "file": {
"ltr": "images/icons/unBlock-ltr.svg",
"rtl": "images/icons/unBlock-rtl.svg"
- } },
+ }, "deprecated": "Renamed since v0.18.3, use 'unBlock' instead." },
"unBlock": { "file": {
"ltr": "images/icons/unBlock-ltr.svg",
"rtl": "images/icons/unBlock-rtl.svg"
"flagUndo": { "file": {
"ltr": "images/icons/unFlag-ltr.svg",
"rtl": "images/icons/unFlag-rtl.svg"
- } },
+ }, "deprecated": "Renamed since v0.18.3, use 'unFlag' instead." },
"unFlag": { "file": {
"ltr": "images/icons/unFlag-ltr.svg",
"rtl": "images/icons/unFlag-rtl.svg"
"trashUndo": { "file": {
"ltr": "images/icons/unTrash-ltr.svg",
"rtl": "images/icons/unTrash-rtl.svg"
- } },
+ }, "deprecated": "Renamed since v0.18.3, use 'unTrash' instead." },
"ongoingConversation": {
"file": {
"ltr": "images/icons/ongoingConversation-ltr.svg",
"prefix": "oo-ui-icon",
"intro": "@import '../../../../src/styles/common';",
"images": {
- "add": { "file": "images/icons/add.svg" },
+ "add": { "file": "images/icons/add.svg", "deprecated": "Moved since v0.19.5, use from the 'interactive' pack instead." },
"advanced": { "file": "images/icons/advanced.svg" },
"alert": { "file": "images/icons/alert.svg" },
"cancel": { "file": "images/icons/cancel.svg" },
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#FFFFFF">
+ <g id="add">
+ <path id="plus" d="M13 6h-2v5H6v2h5v5h2v-5h5v-2h-5z"/>
+ </g>
+</g></svg>
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <g id="close">
- <path id="cross" d="M17.717 7.697l-1.414-1.414L12 10.586 7.697 6.283 6.283 7.697 10.586 12l-4.303 4.303 1.414 1.414L12 13.414l4.303 4.303 1.414-1.414L13.414 12z"/>
- </g>
+ <path d="M17.717 7.697l-1.414-1.414L12 10.586 7.697 6.283 6.283 7.697 10.586 12l-4.303 4.303 1.414 1.414L12 13.414l4.303 4.303 1.414-1.414L13.414 12z"/>
</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24">
+ <path d="M5.066 18.236l.14-.244c.976-1.69 1.341-4.587.815-6.469l-.14-.507.2-.365L11.074 2l9.011 5.203-4.994 8.65-.204.354-.522.134c-1.893.485-4.22 2.252-5.195 3.94l-.14.244-.721-.416-1.041 1.89H3.914l1.893-3.336z" fill-rule="evenodd"/>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24">
+ <path d="M18.934 18.236l-.14-.244c-.976-1.69-1.341-4.587-.815-6.469l.14-.507-.2-.365L12.926 2 3.914 7.203l4.994 8.65.204.354.522.134c1.893.485 4.22 2.252 5.195 3.94l.14.244.721-.416 1.041 1.89h3.355l-1.893-3.336z" fill-rule="evenodd"/>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M16 8V7h-6v1h6zm-2 2V9h-4v1h4zM6 4h1v16H6V4zm2 0h10v13c0 1.7-1.3 3-3 3H8V4z"/>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M8 8V7h6v1H8zm2 2V9h4v1h-4zm8-6h-1v16h1V4zm-2 0H6v13c0 1.7 1.3 3 3 3h7V4z"/>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#FFFFFF">
+ <path d="M18 13H6v-2h12"/>
+</g></svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M18 13H6v-2h12"/>
+</svg>
"os": "images/icons/bold-cyrl-be.svg"
}
} },
+ "highlight": { "file": {
+ "ltr": "images/icons/highlight-ltr.svg",
+ "rtl": "images/icons/highlight-rtl.svg"
+ } },
"italic": { "file": {
"default": "images/icons/italic-a.svg",
"lang": {
}
},
"images": {
- "beta": { "file": "images/icons/beta.svg" },
- "betaLaunch": { "file": "images/icons/logo-wikimediaDiscovery.svg" },
+ "add": { "file": "images/icons/add.svg", "variants": [ "constructive", "progressive" ] },
+ "beta": { "file": "images/icons/beta.svg", "deprecated": "Deprecated since v0.18.3, don't use." },
+ "betaLaunch": { "file": "images/icons/logo-wikimediaDiscovery.svg", "deprecated": "Moved since v0.18.3, use 'logoWikimediaDiscovery' from the 'Wikimedia' pack instead." },
"bookmark": { "file": {
"ltr": "images/icons/bookmark-ltr.svg",
"rtl": "images/icons/bookmark-rtl.svg"
"ltr": "images/icons/printer-ltr.svg",
"rtl": "images/icons/printer-rtl.svg"
} },
- "ribbonPrize": { "file": "images/icons/ribbonPrize.svg" },
+ "ribbonPrize": { "file": "images/icons/ribbonPrize.svg", "deprecated": "Deprecated since v0.18.3, don't use." },
+ "subtract": { "file": "images/icons/subtract.svg" },
"sun": { "file": {
"ltr": "images/icons/sun-ltr.svg",
"rtl": "images/icons/sun-rtl.svg"
"blockUndo": { "file": {
"ltr": "images/icons/unBlock-ltr.svg",
"rtl": "images/icons/unBlock-rtl.svg"
- } },
+ }, "deprecated": "Renamed since v0.18.3, use 'unBlock' instead." },
"unBlock": { "file": {
"ltr": "images/icons/unBlock-ltr.svg",
"rtl": "images/icons/unBlock-rtl.svg"
"flagUndo": { "file": {
"ltr": "images/icons/unFlag-ltr.svg",
"rtl": "images/icons/unFlag-rtl.svg"
- } },
+ }, "deprecated": "Renamed since v0.18.3, use 'unFlag' instead." },
"lock": { "file": {
"ltr": "images/icons/lock-ltr.svg",
"rtl": "images/icons/lock-rtl.svg"
"trashUndo": { "file": {
"ltr": "images/icons/unTrash-ltr.svg",
"rtl": "images/icons/unTrash-rtl.svg"
- } },
+ }, "deprecated": "Renamed since v0.18.3, use 'unTrash' instead." },
"ongoingConversation": {
"file": {
"ltr": "images/icons/ongoingConversation-ltr.svg",
}
},
"images": {
- "add": { "file": "images/icons/add.svg", "variants": [ "constructive", "progressive" ] },
+ "add": { "file": "images/icons/add.svg", "variants": [ "constructive", "progressive" ], "deprecated": "Moved since v0.19.5, use from the 'interactive' pack instead." },
"advanced": { "file": "images/icons/advanced.svg" },
"alert": { "file": "images/icons/alert.svg", "variants": [ "warning" ] },
"cancel": { "file": "images/icons/cancel.svg", "variants": [ "destructive" ] },
"check": { "file": "images/icons/check.svg", "variants": [ "constructive", "progressive", "destructive" ] },
"circle": { "file": "images/icons/circle.svg", "variants": [ "constructive", "progressive" ] },
- "close": { "file": {
- "ltr": "images/icons/close-ltr.svg",
- "rtl": "images/icons/close-rtl.svg"
- } },
+ "close": { "file": "images/icons/close.svg" },
"code": { "file": "images/icons/code.svg" },
"collapse": { "file": "images/icons/collapse.svg" },
"comment": { "file": "images/icons/comment.svg" },
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#fff">
+ <path d="M18 7.6l-1.4-1.4-4.6 4.6-4.6-4.6L6 7.6l4.6 4.6L6 16.8l1.4 1.4 4.6-4.6 4.6 4.6 1.4-1.4-4.6-4.6z"/>
+</g></svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#fff">
- <g id="close">
- <path id="cross" d="M17.4 8.1c.8-.8.8-2 0-2.8L12 10.8 7.4 6.2 6 7.6l4.6 4.6-4 4c-.8.8-.8 2 0 2.8l5.4-5.4 4.6 4.6 1.4-1.4-4.6-4.6z"/>
- </g>
-</g></svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#36c">
- <g id="close">
- <path id="cross" d="M17.4 8.1c.8-.8.8-2 0-2.8L12 10.8 7.4 6.2 6 7.6l4.6 4.6-4 4c-.8.8-.8 2 0 2.8l5.4-5.4 4.6 4.6 1.4-1.4-4.6-4.6z"/>
- </g>
-</g></svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <g id="close">
- <path id="cross" d="M17.4 8.1c.8-.8.8-2 0-2.8L12 10.8 7.4 6.2 6 7.6l4.6 4.6-4 4c-.8.8-.8 2 0 2.8l5.4-5.4 4.6 4.6 1.4-1.4-4.6-4.6z"/>
- </g>
-</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#36c">
+ <path d="M18 7.6l-1.4-1.4-4.6 4.6-4.6-4.6L6 7.6l4.6 4.6L6 16.8l1.4 1.4 4.6-4.6 4.6 4.6 1.4-1.4-4.6-4.6z"/>
+</g></svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#fff">
- <g id="close">
- <path id="cross" d="M6.6 8.1c-.8-.8-.8-2 0-2.8l5.4 5.5 4.6-4.6L18 7.6l-4.6 4.6 4 4c.8.8.8 2 0 2.8L12 13.6l-4.6 4.6L6 16.8l4.6-4.6z"/>
- </g>
-</g></svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#36c">
- <g id="close">
- <path id="cross" d="M6.6 8.1c-.8-.8-.8-2 0-2.8l5.4 5.5 4.6-4.6L18 7.6l-4.6 4.6 4 4c.8.8.8 2 0 2.8L12 13.6l-4.6 4.6L6 16.8l4.6-4.6z"/>
- </g>
-</g></svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <g id="close">
- <path id="cross" d="M6.6 8.1c-.8-.8-.8-2 0-2.8l5.4 5.5 4.6-4.6L18 7.6l-4.6 4.6 4 4c.8.8.8 2 0 2.8L12 13.6l-4.6 4.6L6 16.8l4.6-4.6z"/>
- </g>
-</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M18 7.6l-1.4-1.4-4.6 4.6-4.6-4.6L6 7.6l4.6 4.6L6 16.8l1.4 1.4 4.6-4.6 4.6 4.6 1.4-1.4-4.6-4.6z"/>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><g fill="#fff">
+ <path d="M5.066 18.236l.14-.244c.976-1.69 1.341-4.587.815-6.469l-.14-.507.2-.365L11.074 2l9.011 5.203-4.994 8.65-.204.354-.522.134c-1.893.485-4.22 2.252-5.195 3.94l-.14.244-.721-.416-1.041 1.89H3.914l1.893-3.336z" fill-rule="evenodd"/>
+</g></svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><g fill="#36c">
+ <path d="M5.066 18.236l.14-.244c.976-1.69 1.341-4.587.815-6.469l-.14-.507.2-.365L11.074 2l9.011 5.203-4.994 8.65-.204.354-.522.134c-1.893.485-4.22 2.252-5.195 3.94l-.14.244-.721-.416-1.041 1.89H3.914l1.893-3.336z" fill-rule="evenodd"/>
+</g></svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24">
+ <path d="M5.066 18.236l.14-.244c.976-1.69 1.341-4.587.815-6.469l-.14-.507.2-.365L11.074 2l9.011 5.203-4.994 8.65-.204.354-.522.134c-1.893.485-4.22 2.252-5.195 3.94l-.14.244-.721-.416-1.041 1.89H3.914l1.893-3.336z" fill-rule="evenodd"/>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><g fill="#fff">
+ <path d="M18.934 18.236l-.14-.244c-.976-1.69-1.341-4.587-.815-6.469l.14-.507-.2-.365L12.926 2 3.914 7.203l4.994 8.65.204.354.522.134c1.893.485 4.22 2.252 5.195 3.94l.14.244.721-.416 1.041 1.89h3.355l-1.893-3.336z" fill-rule="evenodd"/>
+</g></svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><g fill="#36c">
+ <path d="M18.934 18.236l-.14-.244c-.976-1.69-1.341-4.587-.815-6.469l.14-.507-.2-.365L12.926 2 3.914 7.203l4.994 8.65.204.354.522.134c1.893.485 4.22 2.252 5.195 3.94l.14.244.721-.416 1.041 1.89h3.355l-1.893-3.336z" fill-rule="evenodd"/>
+</g></svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24">
+ <path d="M18.934 18.236l-.14-.244c-.976-1.69-1.341-4.587-.815-6.469l.14-.507-.2-.365L12.926 2 3.914 7.203l4.994 8.65.204.354.522.134c1.893.485 4.22 2.252 5.195 3.94l.14.244.721-.416 1.041 1.89h3.355l-1.893-3.336z" fill-rule="evenodd"/>
+</svg>
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#fff">
- <path d="M16 9V8h-6v1h6zm-2 2v-1h-4v1h4zM6 5h1v16H6V5zm2 0h10v13c0 1.7-1.3 3-3 3H8V5z"/>
+ <path d="M16 8V7h-6v1h6zm-2 2V9h-4v1h4zM6 4h1v16H6V4zm2 0h10v13c0 1.7-1.3 3-3 3H8V4z"/>
</g></svg>
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#36c">
- <path d="M16 9V8h-6v1h6zm-2 2v-1h-4v1h4zM6 5h1v16H6V5zm2 0h10v13c0 1.7-1.3 3-3 3H8V5z"/>
+ <path d="M16 8V7h-6v1h6zm-2 2V9h-4v1h4zM6 4h1v16H6V4zm2 0h10v13c0 1.7-1.3 3-3 3H8V4z"/>
</g></svg>
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <path d="M16 9V8h-6v1h6zm-2 2v-1h-4v1h4zM6 5h1v16H6V5zm2 0h10v13c0 1.7-1.3 3-3 3H8V5z"/>
+ <path d="M16 8V7h-6v1h6zm-2 2V9h-4v1h4zM6 4h1v16H6V4zm2 0h10v13c0 1.7-1.3 3-3 3H8V4z"/>
</svg>
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#fff">
- <path d="M8 9V8h6v1H8zm2 2v-1h4v1h-4zm8-6h-1v16h1V5zm-2 0H6v13c0 1.7 1.3 3 3 3h7V5z"/>
+ <path d="M8 8V7h6v1H8zm2 2V9h4v1h-4zm8-6h-1v16h1V4zm-2 0H6v13c0 1.7 1.3 3 3 3h7V4z"/>
</g></svg>
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#36c">
- <path d="M8 9V8h6v1H8zm2 2v-1h4v1h-4zm8-6h-1v16h1V5zm-2 0H6v13c0 1.7 1.3 3 3 3h7V5z"/>
+ <path d="M8 8V7h6v1H8zm2 2V9h4v1h-4zm8-6h-1v16h1V4zm-2 0H6v13c0 1.7 1.3 3 3 3h7V4z"/>
</g></svg>
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
- <path d="M8 9V8h6v1H8zm2 2v-1h4v1h-4zm8-6h-1v16h1V5zm-2 0H6v13c0 1.7 1.3 3 3 3h7V5z"/>
+ <path d="M8 8V7h6v1H8zm2 2V9h4v1h-4zm8-6h-1v16h1V4zm-2 0H6v13c0 1.7 1.3 3 3 3h7V4z"/>
</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#fff">
+ <path d="M18 13H6v-2h12"/>
+</g></svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#36c">
+ <path d="M18 13H6v-2h12"/>
+</g></svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M18 13H6v-2h12"/>
+</svg>
mw.rcfilters.dm.FilterItem.prototype.isHighlightSupported = function () {
return !!this.getCssClass();
};
+
+ /**
+ * Check if the filter is currently highlighted
+ *
+ * @return {boolean}
+ */
+ mw.rcfilters.dm.FilterItem.prototype.isHighlighted = function () {
+ return this.isHighlightEnabled() && !!this.getHighlightColor();
+ };
}( mediaWiki ) );
mw.rcfilters.dm.FiltersViewModel.prototype.setFiltersToDefaults = function () {
var defaultFilterStates = this.getFiltersFromParameters( this.getDefaultParams() );
- this.updateFilters( defaultFilterStates );
+ this.toggleFiltersSelected( defaultFilterStates );
};
/**
* are the selected highlight colors.
*/
mw.rcfilters.dm.FiltersViewModel.prototype.getHighlightParameters = function () {
- var result = { highlight: this.isHighlightEnabled() };
+ var result = { highlight: Number( this.isHighlightEnabled() ) };
this.getItems().forEach( function ( filterItem ) {
result[ filterItem.getName() + '_color' ] = filterItem.getHighlightColor();
* @return {boolean} Current filters are all empty
*/
mw.rcfilters.dm.FiltersViewModel.prototype.areCurrentFiltersEmpty = function () {
- var model = this;
-
// Check if there are either any selected items or any items
// that have highlight enabled
return !this.getItems().some( function ( filterItem ) {
- return (
- filterItem.isSelected() ||
- ( model.isHighlightEnabled() && filterItem.getHighlightColor() )
- );
+ return filterItem.isSelected() || filterItem.isHighlighted();
} );
};
* This is equivalent to display all.
*/
mw.rcfilters.dm.FiltersViewModel.prototype.emptyAllFilters = function () {
- var filters = {};
-
this.getItems().forEach( function ( filterItem ) {
- filters[ filterItem.getName() ] = false;
- } );
+ this.toggleFilterSelected( filterItem.getName(), false );
+ }.bind( this ) );
+ };
- // Update filters
- this.updateFilters( filters );
+ /**
+ * Toggle selected state of one item
+ *
+ * @param {string} name Name of the filter item
+ * @param {boolean} [isSelected] Filter selected state
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.toggleFilterSelected = function ( name, isSelected ) {
+ this.getItemByName( name ).toggleSelected( isSelected );
};
/**
*
* @param {Object} filterDef Filter definitions
*/
- mw.rcfilters.dm.FiltersViewModel.prototype.updateFilters = function ( filterDef ) {
- var name, filterItem;
-
- for ( name in filterDef ) {
- filterItem = this.getItemByName( name );
- filterItem.toggleSelected( filterDef[ name ] );
- }
+ mw.rcfilters.dm.FiltersViewModel.prototype.toggleFiltersSelected = function ( filterDef ) {
+ Object.keys( filterDef ).forEach( function ( name ) {
+ this.toggleFilterSelected( name, filterDef[ name ] );
+ }.bind( this ) );
};
/**
* @return {boolean}
*/
mw.rcfilters.dm.FiltersViewModel.prototype.isHighlightEnabled = function () {
- return this.highlightEnabled;
+ return !!this.highlightEnabled;
};
/**
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24">
- <path d="M5.066 18.236l.14-.244c.976-1.69 1.341-4.587.815-6.469l-.14-.507.2-.365L11.074 2l9.011 5.203-4.994 8.65-.204.354-.522.134c-1.893.485-4.22 2.252-5.195 3.94l-.14.244-.721-.416-1.041 1.89H3.914l1.893-3.336z" fill-rule="evenodd"/>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24">
- <path d="M18.934 18.236l-.14-.244c-.976-1.69-1.341-4.587-.815-6.469l.14-.507-.2-.365L12.926 2 3.914 7.203l4.994 8.65.204.354.522.134c1.893.485 4.22 2.252 5.195 3.94l.14.244.721-.416 1.041 1.89h3.355l-1.893-3.336z" fill-rule="evenodd"/>
-</svg>
* @param {Object} filterStructure Filter definition and structure for the model
*/
mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
- var uri = new mw.Uri();
-
// Initialize the model
this.filtersModel.initializeFilters( filterStructure );
+ this.updateStateBasedOnUrl();
+ };
+
+ /**
+ * Update filter state (selection and highlighting) based
+ * on current URL and default values.
+ */
+ mw.rcfilters.Controller.prototype.updateStateBasedOnUrl = function () {
+ var uri = new mw.Uri();
// Set filter states based on defaults and URL params
- this.filtersModel.updateFilters(
+ this.filtersModel.toggleFiltersSelected(
this.filtersModel.getFiltersFromParameters(
// Merge defaults with URL params for initialization
$.extend(
this.filtersModel.toggleHighlight( !!uri.query.highlight );
this.filtersModel.getItems().forEach( function ( filterItem ) {
var color = uri.query[ filterItem.getName() + '_color' ];
- if ( !color ) {
- return;
+ if ( color ) {
+ filterItem.setHighlightColor( color );
+ } else {
+ filterItem.clearHighlightColor();
}
-
- filterItem.setHighlightColor( color );
} );
// Check all filter interactions
*/
mw.rcfilters.Controller.prototype.resetToDefaults = function () {
this.filtersModel.setFiltersToDefaults();
+ this.filtersModel.clearAllHighlightColors();
// Check all filter interactions
this.filtersModel.reassessFilterInteractions();
* @param {boolean} [isSelected] Filter selected state
*/
mw.rcfilters.Controller.prototype.toggleFilterSelect = function ( filterName, isSelected ) {
- var obj = {},
- filterItem = this.filtersModel.getItemByName( filterName );
+ var filterItem = this.filtersModel.getItemByName( filterName );
isSelected = isSelected === undefined ? !filterItem.isSelected() : isSelected;
if ( filterItem.isSelected() !== isSelected ) {
- obj[ filterName ] = isSelected;
- this.filtersModel.updateFilters( obj );
+ this.filtersModel.toggleFilterSelected( filterName, isSelected );
this.updateChangesList();
// Check filter interactions
- this.filtersModel.reassessFilterInteractions( this.filtersModel.getItemByName( filterName ) );
+ this.filtersModel.reassessFilterInteractions( filterItem );
}
};
* @param {Object} [params] Extra parameters to add to the API call
*/
mw.rcfilters.Controller.prototype.updateURL = function ( params ) {
- var uri;
+ var updatedUri,
+ notEquivalent = function ( obj1, obj2 ) {
+ var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
+ return keys.some( function ( key ) {
+ return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
+ } );
+ };
params = params || {};
- uri = this.getUpdatedUri();
- uri.extend( params );
+ updatedUri = this.getUpdatedUri();
+ updatedUri.extend( params );
- window.history.pushState( { tag: 'rcfilters' }, document.title, uri.toString() );
+ if ( notEquivalent( updatedUri.query, new mw.Uri().query ) ) {
+ window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+ }
};
/**
this.filtersModel.clearHighlightColor( filterName );
this.updateURL();
};
+
+ /**
+ * Clear both highlight and selection of a filter
+ *
+ * @param {string} filterName Name of the filter item
+ */
+ mw.rcfilters.Controller.prototype.clearFilter = function ( filterName ) {
+ var filterItem = this.filtersModel.getItemByName( filterName );
+
+ if ( filterItem.isSelected() || filterItem.isHighlighted() ) {
+ this.filtersModel.clearHighlightColor( filterName );
+ this.filtersModel.toggleFilterSelected( filterName, false );
+ this.updateChangesList();
+ this.filtersModel.reassessFilterInteractions( filterItem );
+ }
+ };
+
+ /**
+ * Synchronize the URL with the current state of the filters
+ * without adding an history entry.
+ */
+ mw.rcfilters.Controller.prototype.replaceUrl = function () {
+ window.history.replaceState(
+ { tag: 'rcfilters' },
+ document.title,
+ this.getUpdatedUri().toString()
+ );
+ };
}( mediaWiki, jQuery ) );
$( '.rcfilters-head' ).addClass( 'mw-rcfilters-ui-ready' );
window.addEventListener( 'popstate', function () {
+ controller.updateStateBasedOnUrl();
controller.updateChangesList();
} );
'href',
'https://www.mediawiki.org/wiki/Special:MyLanguage/Help:New_filters_for_edit_review'
);
+
+ controller.replaceUrl();
}
};
@import 'mw.rcfilters.mixins';
.mw-rcfilters-ui-filterItemHighlightButton {
- .oo-ui-iconElement-icon.oo-ui-icon-highlight {
- /* @embed */
- background-image: url( ../images/marker-ltr.svg );
- }
-
.oo-ui-buttonWidget.oo-ui-popupButtonWidget .oo-ui-buttonElement-button > &-circle {
display: inline-block;
vertical-align: middle;
popup: {
padded: false,
align: 'center',
+ position: 'above',
$content: $popupContent
.append( descLabelWidget.$element ),
$floatableContainer: this.$element,
}
// Respond to user removing the filter
- this.controller.toggleFilterSelect( this.model.getName(), false );
- this.controller.clearHighlightColor( this.model.getName() );
+ this.controller.clearFilter( this.model.getName() );
};
mw.rcfilters.ui.CapsuleItemWidget.prototype.setHighlightColor = function () {
mw.rcfilters.ui.CapsuleItemWidget.prototype.onMouseEnter = function () {
if ( this.model.getDescription() ) {
if ( !this.positioned ) {
- // Recalculate position to be center of the capsule item
- this.popup.$element.css( 'margin-left', ( this.$element.width() / 2 ) );
+ // Recalculate anchor position to be center of the capsule item
+ this.popup.$anchor.css( 'margin-left', ( this.$element.width() / 2 ) );
this.positioned = true;
}
icon: 'highlight',
indicator: 'down',
popup: {
- anchor: false,
+ // TODO: There is a bug in non-anchored popups in
+ // OOUI, so we set this popup to "anchored" until
+ // the bug is fixed.
+ // See: https://phabricator.wikimedia.org/T159906
+ anchor: true,
padded: true,
align: 'backwards',
horizontalPosition: 'end',
$content: this.filterPopup.$element,
$footer: $footer,
classes: [ 'mw-rcfilters-ui-filterWrapperWidget-popup' ],
- width: 650
+ width: 650,
+ hideWhenOutOfView: false
}
} );
this.groups = {};
this.selected = null;
- this.highlightButton = new OO.ui.ButtonWidget( {
+ this.highlightButton = new OO.ui.ToggleButtonWidget( {
+ icon: 'highlight',
label: mw.message( 'rcfilters-highlightbutton-title' ).text(),
classes: [ 'mw-rcfilters-ui-filtersListWidget-hightlightButton' ]
} );
this.highlightButton.connect( this, { click: 'onHighlightButtonClick' } );
this.model.connect( this, {
initialize: 'onModelInitialize',
- highlightChange: 'onHighlightChange'
+ highlightChange: 'onModelHighlightChange'
} );
// Initialize
);
};
- mw.rcfilters.ui.FiltersListWidget.prototype.onHighlightChange = function ( highlightEnabled ) {
+ /**
+ * Respond to model highlight change event
+ *
+ * @param {boolean} highlightEnabled Highlight is enabled
+ */
+ mw.rcfilters.ui.FiltersListWidget.prototype.onModelHighlightChange = function ( highlightEnabled ) {
this.highlightButton.setActive( highlightEnabled );
};
--- /dev/null
+<?php
+
+class RemexDriverTest extends MediaWikiTestCase {
+ static private $remexTidyTestData = [
+ // Tests from Html5Depurate
+ [
+ 'Empty string',
+ "",
+ ""
+ ],
+ [
+ 'Simple p-wrap',
+ "x",
+ "<p>x</p>"
+ ],
+ [
+ 'No p-wrap of blank node',
+ " ",
+ " "
+ ],
+ [
+ 'p-wrap terminated by div',
+ "x<div></div>",
+ "<p>x</p><div></div>"
+ ],
+ [
+ 'p-wrap not terminated by span',
+ "x<span></span>",
+ "<p>x<span></span></p>"
+ ],
+ [
+ 'An element is non-blank and so gets p-wrapped',
+ "<span></span>",
+ "<p><span></span></p>"
+ ],
+ [
+ 'The blank flag is set after a block-level element',
+ "<div></div> ",
+ "<div></div> "
+ ],
+ [
+ 'Blank detection between two block-level elements',
+ "<div></div> <div></div>",
+ "<div></div> <div></div>"
+ ],
+ [
+ 'But p-wrapping of non-blank content works after an element',
+ "<div></div>x",
+ "<div></div><p>x</p>"
+ ],
+ [
+ 'p-wrapping between two block-level elements',
+ "<div></div>x<div></div>",
+ "<div></div><p>x</p><div></div>"
+ ],
+ [
+ 'p-wrap inside blockquote',
+ "<blockquote>x</blockquote>",
+ "<blockquote><p>x</p></blockquote>"
+ ],
+ [
+ 'A comment is blank for p-wrapping purposes',
+ "<!-- x -->",
+ "<!-- x -->"
+ ],
+ [
+ 'A comment is blank even when a p-wrap was opened by a text node',
+ " <!-- x -->",
+ " <!-- x -->"
+ ],
+ [
+ 'A comment does not open a p-wrap',
+ "<!-- x -->x",
+ "<!-- x --><p>x</p>"
+ ],
+ [
+ 'A comment does not close a p-wrap',
+ "x<!-- x -->",
+ "<p>x<!-- x --></p>"
+ ],
+ [
+ 'Empty li',
+ "<ul><li></li></ul>",
+ "<ul><li class=\"mw-empty-elt\"></li></ul>"
+ ],
+ [
+ 'li with element',
+ "<ul><li><span></span></li></ul>",
+ "<ul><li><span></span></li></ul>"
+ ],
+ [
+ 'li with text',
+ "<ul><li>x</li></ul>",
+ "<ul><li>x</li></ul>"
+ ],
+ [
+ 'Empty tr',
+ "<table><tbody><tr></tr></tbody></table>",
+ "<table><tbody><tr class=\"mw-empty-elt\"></tr></tbody></table>"
+ ],
+ [
+ 'Empty p',
+ "<p>\n</p>",
+ "<p class=\"mw-empty-elt\">\n</p>"
+ ],
+ [
+ 'No p-wrapping of an inline element which contains a block element (T150317)',
+ "<small><div>x</div></small>",
+ "<small><div>x</div></small>"
+ ],
+ [
+ 'p-wrapping of an inline element which contains an inline element',
+ "<small><b>x</b></small>",
+ "<p><small><b>x</b></small></p>"
+ ],
+ [
+ 'p-wrapping is enabled in a blockquote in an inline element',
+ "<small><blockquote>x</blockquote></small>",
+ "<small><blockquote><p>x</p></blockquote></small>"
+ ],
+ [
+ 'All bare text should be p-wrapped even when surrounded by block tags',
+ "<small><blockquote>x</blockquote></small>y<div></div>z",
+ "<small><blockquote><p>x</p></blockquote></small><p>y</p><div></div><p>z</p>"
+ ],
+ [
+ 'Split tag stack 1',
+ "<small>x<div>y</div>z</small>",
+ "<p><small>x</small></p><small><div>y</div></small><p><small>z</small></p>"
+ ],
+ [
+ 'Split tag stack 2',
+ "<small><div>y</div>z</small>",
+ "<small><div>y</div></small><p><small>z</small></p>"
+ ],
+ [
+ 'Split tag stack 3',
+ "<small>x<div>y</div></small>",
+ "<p><small>x</small></p><small><div>y</div></small>"
+ ],
+ [
+ 'Split tag stack 4 (modified to use splittable tag)',
+ "a<code>b<i>c<div>d</div></i>e</code>",
+ "<p>a<code>b<i>c</i></code></p><code><i><div>d</div></i></code><p><code>e</code></p>"
+ ],
+ [
+ "Split tag stack regression check 1",
+ "x<span><div>y</div></span>",
+ "<p>x</p><span><div>y</div></span>"
+ ],
+ [
+ "Split tag stack regression check 2 (modified to use splittable tag)",
+ "a<code><i><div>d</div></i>e</code>",
+ "<p>a</p><code><i><div>d</div></i></code><p><code>e</code></p>"
+ ],
+ // Simple tests from pwrap.js
+ [
+ 'Simple pwrap test 1',
+ 'a',
+ '<p>a</p>'
+ ],
+ [
+ '<span> is not a splittable tag, but gets p-wrapped in simple wrapping scenarios',
+ '<span>a</span>',
+ '<p><span>a</span></p>'
+ ],
+ [
+ 'Simple pwrap test 3',
+ 'x <div>a</div> <div>b</div> y',
+ '<p>x </p><div>a</div> <div>b</div><p> y</p>'
+ ],
+ [
+ 'Simple pwrap test 4',
+ 'x<!--c--> <div>a</div> <div>b</div> <!--c-->y',
+ '<p>x<!--c--> </p><div>a</div> <div>b</div> <!--c--><p>y</p>'
+ ],
+ // Complex tests from pwrap.js
+ [
+ 'Complex pwrap test 1',
+ '<i>x<div>a</div>y</i>',
+ '<p><i>x</i></p><i><div>a</div></i><p><i>y</i></p>'
+ ],
+ [
+ 'Complex pwrap test 2',
+ 'a<small>b</small><i>c<div>d</div>e</i>f',
+ '<p>a<small>b</small><i>c</i></p><i><div>d</div></i><p><i>e</i>f</p>'
+ ],
+ [
+ 'Complex pwrap test 3',
+ 'a<small>b<i>c<div>d</div></i>e</small>',
+ '<p>a<small>b<i>c</i></small></p><small><i><div>d</div></i></small><p><small>e</small></p>'
+ ],
+ [
+ 'Complex pwrap test 4',
+ 'x<small><div>y</div></small>',
+ '<p>x</p><small><div>y</div></small>'
+ ],
+ [
+ 'Complex pwrap test 5',
+ 'a<small><i><div>d</div></i>e</small>',
+ '<p>a</p><small><i><div>d</div></i></small><p><small>e</small></p>'
+ ],
+ [
+ 'Complex pwrap test 6',
+ '<i>a<div>b</div>c<b>d<div>e</div>f</b>g</i>',
+ // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
+ // PHP 5 does not allow concatenation in initialisation of a class static variable
+ '<p><i>a</i></p><i><div>b</div></i><p><i>c<b>d</b></i></p><i><b><div>e</div></b></i><p><i><b>f</b>g</i></p>'
+ // @codingStandardsIgnoreEnd
+ ],
+ /* FIXME the second <b> causes a stack split which clones the <i> even
+ * though no <p> is actually generated
+ [
+ 'Complex pwrap test 7',
+ '<i><b><font><div>x</div></font></b><div>y</div><b><font><div>z</div></font></b></i>',
+ '<i><b><font><div>x</div></font></b><div>y</div><b><font><div>z</div></font></b></i>'
+ ],
+ */
+ // New local tests
+ [
+ 'Blank text node after block end',
+ '<small>x<div>y</div> <b>z</b></small>',
+ '<p><small>x</small></p><small><div>y</div></small><p><small> <b>z</b></small></p>'
+ ],
+ [
+ 'Text node fostering (FIXME: wrap missing)',
+ '<table>x</table>',
+ 'x<table></table>'
+ ],
+ [
+ 'Blockquote fostering',
+ '<table><blockquote>x</blockquote></table>',
+ '<blockquote><p>x</p></blockquote><table></table>'
+ ],
+ [
+ 'Block element fostering',
+ '<table><div>x',
+ '<div>x</div><table></table>'
+ ],
+ [
+ 'Formatting element fostering (FIXME: wrap missing)',
+ '<table><b>x',
+ '<b>x</b><table></table>'
+ ],
+ [
+ 'AAA clone of p-wrapped element (FIXME: empty b)',
+ '<b>x<p>y</b>z</p>',
+ '<p><b>x</b></p><b></b><p><b>y</b>z</p>',
+ ],
+ [
+ 'AAA with fostering (FIXME: wrap missing)',
+ '<table><b>1<p>2</b>3</p>',
+ '<b>1</b><p><b>2</b>3</p><table></table>'
+ ],
+ ];
+
+ public function provider() {
+ return self::$remexTidyTestData;
+ }
+
+ /**
+ * @dataProvider provider
+ * @covers MediaWiki\Tidy\RemexCompatFormatter
+ * @covers MediaWiki\Tidy\RemexCompatMunger
+ * @covers MediaWiki\Tidy\RemexDriver
+ * @covers MediaWiki\Tidy\RemexMungerData
+ */
+ public function testTidy( $desc, $input, $expected ) {
+ $r = new MediaWiki\Tidy\RemexDriver( [] );
+ $result = $r->tidy( $input );
+ $this->assertEquals( $expected, $result, $desc );
+ }
+
+ public function html5libProvider() {
+ $files = json_decode( file_get_contents( __DIR__ . '/html5lib-tests.json' ), true );
+ $tests = [];
+ foreach ( $files as $file => $fileTests ) {
+ foreach ( $fileTests as $i => $test ) {
+ $tests[] = [ "$file:$i", $test['data'] ];
+ }
+ }
+ return $tests;
+ }
+
+ /**
+ * This is a quick and dirty test to make sure none of the html5lib tests
+ * generate exceptions. We don't really know what the expected output is.
+ *
+ * @dataProvider html5libProvider
+ * @coversNothing
+ */
+ public function testHtml5Lib( $desc, $input ) {
+ $r = new MediaWiki\Tidy\RemexDriver( [] );
+ $result = $r->tidy( $input );
+ $this->assertTrue( true, $desc );
+ }
+}
'MainNamespaceArticle', null,
new ForeignTitle( 0, '', 'MainNamespaceArticle' ),
],
+ [
+ 'Magic:_The_Gathering', 0,
+ new ForeignTitle( 0, '', 'Magic:_The_Gathering' ),
+ ],
[
'Talk:Nice_talk', 1,
new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
],
+ [
+ 'Talk:Magic:_The_Gathering', 1,
+ new ForeignTitle( 1, 'Talk', 'Magic:_The_Gathering' ),
+ ],
[
'Bogus:Nice_talk', 0,
new ForeignTitle( 0, '', 'Bogus:Nice_talk' ),
'Bogus:Nice_talk', 1,
new ForeignTitle( 1, 'Talk', 'Nice_talk' ),
],
+ // Misconfigured wiki with unregistered namespace (T114115)
+ [
+ 'Nice_talk', 1234,
+ new ForeignTitle( 1234, 'Ns1234', 'Nice_talk' ),
+ ],
];
}
--- /dev/null
+<?php
+
+/**
+ * @covers LanguageCode
+ *
+ * @group Language
+ *
+ * @license GPL-2.0+
+ * @author Thiemo Mättig
+ */
+class LanguageCodeTest extends PHPUnit_Framework_TestCase {
+
+ public function testConstructor() {
+ $instance = new LanguageCode();
+
+ $this->assertInstanceOf( LanguageCode::class, $instance );
+ }
+
+ public function testGetDeprecatedCodeMapping() {
+ $map = LanguageCode::getDeprecatedCodeMapping();
+
+ $this->assertInternalType( 'array', $map );
+ $this->assertContainsOnly( 'string', array_keys( $map ) );
+ $this->assertArrayNotHasKey( '', $map );
+ $this->assertContainsOnly( 'string', $map );
+ $this->assertNotContains( '', $map );
+
+ // Codes special to MediaWiki should never appear in a map of "deprecated" codes
+ $this->assertArrayNotHasKey( 'qqq', $map, 'documentation' );
+ $this->assertNotContains( 'qqq', $map, 'documentation' );
+ $this->assertArrayNotHasKey( 'qqx', $map, 'debug code' );
+ $this->assertNotContains( 'qqx', $map, 'debug code' );
+
+ // Valid language codes that are currently not "deprecated"
+ $this->assertArrayNotHasKey( 'bh', $map, 'family of Bihari languages' );
+ $this->assertArrayNotHasKey( 'no', $map, 'family of Norwegian languages' );
+ $this->assertArrayNotHasKey( 'simple', $map );
+ }
+
+}
'Initial state of filters'
);
- model.updateFilters( {
+ model.toggleFiltersSelected( {
group1filter1: true,
group2filter2: true,
group3filter1: true
);
// Select 1 filter
- model.updateFilters( {
+ model.toggleFiltersSelected( {
hidefilter1: true,
hidefilter2: false,
hidefilter3: false,
);
// Select 2 filters
- model.updateFilters( {
+ model.toggleFiltersSelected( {
hidefilter1: true,
hidefilter2: true,
hidefilter3: false,
);
// Select 3 filters
- model.updateFilters( {
+ model.toggleFiltersSelected( {
hidefilter1: true,
hidefilter2: true,
hidefilter3: true,
);
// Select 1 filter from string_options
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter7: true,
filter8: false,
filter9: false
);
// Select 2 filters from string_options
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter7: true,
filter8: true,
filter9: false
);
// Select 3 filters from string_options
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter7: true,
filter8: true,
filter9: true
// This test is demonstrating wrong usage of the method;
// We should be aware that getFiltersFromParameters is stateless,
// so each call gives us a filter state that only reflects the query given.
- // This means that the two calls to updateFilters() below collide.
+ // This means that the two calls to toggleFiltersSelected() below collide.
// The result of the first is overridden by the result of the second,
// since both get a full state object from getFiltersFromParameters that **only** relates
// to the input it receives.
- model.updateFilters(
+ model.toggleFiltersSelected(
model.getFiltersFromParameters( {
hidefilter1: '1'
} )
);
- model.updateFilters(
+ model.toggleFiltersSelected(
model.getFiltersFromParameters( {
hidefilter6: '1'
} )
);
- // The result here is ignoring the first updateFilters call
+ // The result here is ignoring the first toggleFiltersSelected call
// We should receive default values + hidefilter6 as false
assert.deepEqual(
model.getSelectedState(),
model = new mw.rcfilters.dm.FiltersViewModel();
model.initializeFilters( definition );
- model.updateFilters(
+ model.toggleFiltersSelected(
model.getFiltersFromParameters( {
hidefilter1: '0'
} )
);
- model.updateFilters(
+ model.toggleFiltersSelected(
model.getFiltersFromParameters( {
hidefilter1: '1'
} )
'After checking and then unchecking a \'send_unselected_if_any\' filter (without touching other filters in that group), results are default'
);
- model.updateFilters(
+ model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7'
} )
'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
);
- model.updateFilters(
+ model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7,filter8'
} )
'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
);
- model.updateFilters(
+ model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7,filter8,filter9'
} )
'A \'string_options\' parameter containing all values, results in all filters of the group as unchecked.'
);
- model.updateFilters(
+ model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7,all,filter9'
} )
'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as unchecked.'
);
- model.updateFilters(
+ model.toggleFiltersSelected(
model.getFiltersFromParameters( {
group3: 'filter7,foo,filter9'
} )
'Initial state: default filters are not selected (controller selects defaults explicitly).'
);
- model.updateFilters( {
+ model.toggleFiltersSelected( {
hidefilter1: false,
hidefilter3: false
} );
model.initializeFilters( definition );
// Select a filter that has subset with another filter
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter1: true
} );
);
// Select another filter that has a subset with the same previous filter
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter4: true
} );
model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
);
// Remove one filter (but leave the other) that affects filter2
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter1: false
} );
model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
'Removing a filter only un-includes its subset if there is no other filter affecting.'
);
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter4: false
} );
model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
);
// Select most (but not all) items in each group
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter1: true,
filter2: true,
filter4: true,
);
// Select all items in 'fullCoverage' group (group2)
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter6: true
} );
);
// Select all items in non 'fullCoverage' group (group1)
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter3: true
} );
);
// Uncheck an item from each group
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter3: false,
filter5: false
} );
);
// Select a filter that has a conflict with another
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter1: true // conflicts: filter2, filter4
} );
);
// Select one of the conflicts (both filters are now conflicted and selected)
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter4: true // conflicts: filter 1
} );
model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
// Select another filter from filter4 group, meaning:
// now filter1 no longer conflicts with filter4
- model.updateFilters( {
+ model.toggleFiltersSelected( {
filter6: true // conflicts: filter2
} );
model.reassessFilterInteractions( model.getItemByName( 'filter6' ) );