jenkins-bot has submitted this change and it was merged.
Change subject: Clean up lots of minor things in SurfaceFragment
......................................................................
Clean up lots of minor things in SurfaceFragment
* Use isNull() to check for null fragments
** Update isNull() to check for !this.range
*** We were previously checking for !this.surface, but this.surface &&
!this.range is also possible
** Add null handling where missing
** Return a clone rather than this for null fragments in methods that return a
new fragment
* Move willAutoSelect() and setAutoSelect() to a reasonable place and near each
other
* Add clone() and implement methods that return new fragments using clone()
** This reduces the duplication of constructor parameters
* Break out range translation into getTranslatedRange()
** Use this in update()
* Add a change() method that calls surface.change() and autoselects if needed
** Use this to eliminate lots of !this.noAutoSelect && tx.translateRange
duplication
** Get rid of lots of local tx variables that are no longer needed
* Document that getSurface()/getDocument()/getRange() can return null
Change-Id: Icb0c497290fa26db94f53fc859c08e37f99aee15
---
M modules/ve/dm/ve.dm.SurfaceFragment.js
1 file changed, 175 insertions(+), 152 deletions(-)
Approvals:
Esanders: Looks good to me, approved
jenkins-bot: Verified
diff --git a/modules/ve/dm/ve.dm.SurfaceFragment.js
b/modules/ve/dm/ve.dm.SurfaceFragment.js
index 120f812..e2133d0 100644
--- a/modules/ve/dm/ve.dm.SurfaceFragment.js
+++ b/modules/ve/dm/ve.dm.SurfaceFragment.js
@@ -48,27 +48,67 @@
/* Methods */
/**
+ * Translate the current range for one or more transactions.
+ *
+ * @param {ve.dm.Transaction|ve.dm.Transaction[]} txs Transaction(s) to
translate for
+ * @param {boolean} [noUpdate] Use this.range directly rather than trying to
update it first
+ * Only use this if calling this.update() will lead to problems (e.g.
recursion)
+ * @returns {ve.Range|null} Translated range
+ */
+ve.dm.SurfaceFragment.prototype.getTranslatedRange = function ( txs, noUpdate
) {
+ var i, len, range;
+ if ( !this.range ) {
+ return null;
+ }
+ if ( !ve.isArray( txs ) ) {
+ txs = [ txs ];
+ }
+
+ if ( !noUpdate ) {
+ this.update();
+ }
+ range = this.range;
+ for ( i = 0, len = txs.length; i < len; i++ ) {
+ range = txs[i].translateRange( range );
+ }
+ return range;
+};
+
+/**
* Update range based on un-applied transactions in the surface.
*
* @method
*/
ve.dm.SurfaceFragment.prototype.update = function () {
- var i, length, txs;
+ if ( this.isNull() ) {
+ // Handle null range
+ return;
+ }
+
+ var txs;
// Small optimisation: check history pointer is in the past
if ( this.historyPointer < this.document.getCompleteHistoryLength() ) {
txs = this.document.getCompleteHistorySince(
this.historyPointer );
- for ( i = 0, length = txs.length; i < length; i++ ) {
- this.range = txs[i].translateRange( this.range );
- this.historyPointer++;
- }
+ this.range = this.getTranslatedRange( txs, true );
+ this.historyPointer += txs.length;
}
+};
+
+/**
+ * Process a set of transactions on the surface, and update the selection if
the fragment
+ * is auto-selecting.
+ *
+ * @param {ve.dm.Transaction|ve.dm.Transaction[]} txs Transaction(s) to process
+ */
+ve.dm.SurfaceFragment.prototype.change = function ( txs ) {
+ this.surface.change( txs, !this.noAutoSelect &&
this.getTranslatedRange( txs ) );
};
/**
* Get the surface the fragment is a part of.
*
* @method
- * @returns {ve.dm.Surface} Surface of fragment
+ * @returns {ve.dm.Surface|null} Surface of fragment
*/
ve.dm.SurfaceFragment.prototype.getSurface = function () {
return this.surface;
@@ -78,7 +118,7 @@
* Get the document of the surface the fragment is a part of.
*
* @method
- * @returns {ve.dm.Document} Document of surface of fragment
+ * @returns {ve.dm.Document|null} Document of surface of fragment
*/
ve.dm.SurfaceFragment.prototype.getDocument = function () {
return this.document;
@@ -91,11 +131,11 @@
*
* @method
* @param {boolean} noCopy Return the range by reference, not a copy
- * @returns {ve.Range} Surface range
+ * @returns {ve.Range|null} Surface range
*/
ve.dm.SurfaceFragment.prototype.getRange = function ( noCopy ) {
this.update();
- return noCopy ? this.range : this.range.clone();
+ return this.range && !noCopy ? this.range.clone() : this.range;
};
/**
@@ -105,7 +145,39 @@
* @returns {boolean} Fragment is a null fragment
*/
ve.dm.SurfaceFragment.prototype.isNull = function () {
- return this.surface === undefined;
+ return !this.range;
+};
+
+/**
+ * Check if the surface's selection will be updated automatically when changes
are made.
+ *
+ * @method
+ * @returns {boolean} Will automatically update surface selection
+ */
+ve.dm.SurfaceFragment.prototype.willAutoSelect = function () {
+ return !this.noAutoSelect;
+};
+
+/**
+ * Change whether to automatically update the surface selection when making
changes.
+ *
+ * @method
+ * @param {boolean} [autoSelect=true] Automatically update surface selection
+ * @chainable
+ */
+ve.dm.SurfaceFragment.prototype.setAutoSelect = function ( autoSelect ) {
+ this.noAutoSelect = !autoSelect;
+ return this;
+};
+
+/**
+ * Get a clone of this SurfaceFragment, optionally with a different range.
+ *
+ * @param {ve.Range} [range] If set, use this range rather than the old
fragment's range
+ * @returns {ve.dm.SurfaceFragment} Clone of this fragment
+ */
+ve.dm.SurfaceFragment.prototype.clone = function ( range ) {
+ return new this.constructor( this.surface, range || this.getRange(),
this.noAutoSelect );
};
/**
@@ -117,15 +189,9 @@
* @returns {ve.dm.SurfaceFragment} Adjusted fragment
*/
ve.dm.SurfaceFragment.prototype.adjustRange = function ( start, end ) {
- // Handle null fragment
- if ( !this.surface ) {
- return this;
- }
- return new ve.dm.SurfaceFragment(
- this.surface,
- new ve.Range( this.getRange( true ).start + ( start || 0 ),
this.getRange( true ).end + ( end || 0 ) ),
- this.noAutoSelect
- );
+ var newRange, oldRange = this.getRange( true );
+ newRange = oldRange && new ve.Range( oldRange.start + ( start || 0 ),
oldRange.end + ( end || 0 ) );
+ return this.clone( newRange );
};
/**
@@ -136,15 +202,8 @@
* @returns {ve.dm.SurfaceFragment} Truncated fragment
*/
ve.dm.SurfaceFragment.prototype.truncateRange = function ( limit ) {
- // Handle null fragment
- if ( !this.surface ) {
- return this;
- }
- return new ve.dm.SurfaceFragment(
- this.surface,
- this.getRange().truncate( limit ),
- this.noAutoSelect
- );
+ var range = this.getRange( true );
+ return this.clone( range && range.truncate( limit ) );
};
/**
@@ -154,13 +213,8 @@
* @returns {ve.dm.SurfaceFragment} Collapsed fragment
*/
ve.dm.SurfaceFragment.prototype.collapseRangeToStart = function () {
- // Handle null fragment
- if ( !this.surface ) {
- return this;
- }
- return new ve.dm.SurfaceFragment(
- this.surface, new ve.Range( this.getRange( true ).start ),
this.noAutoSelect
- );
+ var range = this.getRange( true );
+ return this.clone( range && new ve.Range( range.start ) );
};
/**
@@ -170,13 +224,8 @@
* @returns {ve.dm.SurfaceFragment} Collapsed fragment
*/
ve.dm.SurfaceFragment.prototype.collapseRangeToEnd = function () {
- // Handle null fragment
- if ( !this.surface ) {
- return this;
- }
- return new ve.dm.SurfaceFragment(
- this.surface, new ve.Range( this.getRange( true ).end ),
this.noAutoSelect
- );
+ var range = this.getRange( true );
+ return this.clone( range && new ve.Range( range.end ) );
};
/**
@@ -186,20 +235,16 @@
* @returns {ve.dm.SurfaceFragment} Trimmed fragment
*/
ve.dm.SurfaceFragment.prototype.trimRange = function () {
- // Handle null fragment
- if ( !this.surface ) {
- return this;
+ var oldRange = this.getRange(), newRange = oldRange;
+ if ( oldRange ) {
+ if ( this.document.getText( oldRange ).trim().length === 0 ) {
+ // oldRange is only whitespace
+ newRange = new ve.Range( oldRange.start );
+ } else {
+ newRange = this.document.data.trimOuterSpaceFromRange(
oldRange );
+ }
}
- // If range is only whitespace
- if ( this.document.getText( this.getRange() ).trim().length === 0 ) {
- // Collapse range
- return new ve.dm.SurfaceFragment(
- this.surface, new ve.Range( this.getRange( true ).start
), this.noAutoSelect
- );
- }
- return new ve.dm.SurfaceFragment(
- this.surface, this.document.data.trimOuterSpaceFromRange(
this.getRange() ), this.noAutoSelect
- );
+ return this.clone( newRange );
};
/**
@@ -217,49 +262,50 @@
* @returns {ve.dm.SurfaceFragment} Expanded fragment
*/
ve.dm.SurfaceFragment.prototype.expandRange = function ( scope, type ) {
+ var node, nodes, parent, newRange, oldRange = this.getRange();
// Handle null fragment
- if ( !this.surface ) {
- return this;
+ if ( this.isNull() ) {
+ return this.clone();
}
- var range, node, nodes, parent;
+
switch ( scope || 'parent' ) {
case 'word':
- if ( this.getRange( true ).getLength() > 0 ) {
- range = ve.Range.newCoveringRange( [
+ if ( oldRange.getLength() > 0 ) {
+ newRange = ve.Range.newCoveringRange( [
this.document.data.getNearestWordRange(
this.getRange( true ).start ),
this.document.data.getNearestWordRange(
this.getRange( true ).end )
] );
- if ( this.getRange( true ).isBackwards() ) {
- range = range.flip();
+ if ( oldRange.isBackwards() ) {
+ newRange = newRange.flip();
}
} else {
// optimisation for zero-length ranges
- range = this.document.data.getNearestWordRange(
this.getRange( true ).start );
+ newRange =
this.document.data.getNearestWordRange( oldRange.start );
}
break;
case 'annotation':
- range =
this.document.data.getAnnotatedRangeFromSelection( this.getRange(), type );
+ newRange =
this.document.data.getAnnotatedRangeFromSelection( oldRange, type );
// Adjust selection if it does not contain the
annotated range
- if ( this.getRange( true ).start > range.start ||
this.getRange( true ).end < range.end ) {
+ if ( oldRange.start > newRange.start || oldRange.end <
newRange.end ) {
// Maintain range direction
- if ( this.getRange( true ).from >
this.getRange( true ).to ) {
- range = range.flip();
+ if ( oldRange.from > oldRange.to ) {
+ newRange = newRange.flip();
}
} else {
// Otherwise just keep the range as is
- range = this.getRange();
+ newRange = oldRange;
}
break;
case 'root':
- range = new ve.Range( 0, this.document.getData().length
);
+ newRange = new ve.Range( 0,
this.document.getData().length );
break;
case 'siblings':
// Grow range to cover all siblings
- nodes = this.document.selectNodes( this.getRange(),
'siblings' );
+ nodes = this.document.selectNodes( oldRange, 'siblings'
);
if ( nodes.length === 1 ) {
- range = nodes[0].node.getOuterRange();
+ newRange = nodes[0].node.getOuterRange();
} else {
- range = new ve.Range(
+ newRange = new ve.Range(
nodes[0].node.getOuterRange().start,
nodes[nodes.length -
1].node.getOuterRange().end
);
@@ -267,7 +313,7 @@
break;
case 'closest':
// Grow range to cover closest common ancestor node of
given type
- node = this.document.selectNodes( this.getRange(),
'siblings' )[0].node;
+ node = this.document.selectNodes( oldRange, 'siblings'
)[0].node;
parent = node.getParent();
while ( parent && parent.getType() !== type ) {
node = parent;
@@ -276,31 +322,21 @@
if ( !parent ) {
return new ve.dm.SurfaceFragment( null );
}
- range = parent.getOuterRange();
+ newRange = parent.getOuterRange();
break;
case 'parent':
// Grow range to cover the closest common parent node
- node = this.document.selectNodes( this.getRange(),
'siblings' )[0].node;
+ node = this.document.selectNodes( oldRange, 'siblings'
)[0].node;
parent = node.getParent();
if ( !parent ) {
return new ve.dm.SurfaceFragment( null );
}
- range = parent.getOuterRange();
+ newRange = parent.getOuterRange();
break;
default:
throw new Error( 'Invalid scope argument: ' + scope );
}
- return new ve.dm.SurfaceFragment( this.surface, range,
this.noAutoSelect );
-};
-
-/**
- * Check if the surface's selection will be updated automatically when changes
are made.
- *
- * @method
- * @returns {boolean} Will automatically update surface selection
- */
-ve.dm.SurfaceFragment.prototype.willAutoSelect = function () {
- return !this.noAutoSelect;
+ return this.clone( newRange );
};
/**
@@ -312,7 +348,7 @@
*/
ve.dm.SurfaceFragment.prototype.getData = function ( deep ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return [];
}
return this.document.getData( this.getRange(), deep );
@@ -322,11 +358,11 @@
* Get plain text for the fragment.
*
* @method
- * @returns {Array} Fragment text
+ * @returns {string} Fragment text
*/
ve.dm.SurfaceFragment.prototype.getText = function () {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return '';
}
var i, length,
@@ -353,7 +389,7 @@
*/
ve.dm.SurfaceFragment.prototype.getAnnotations = function ( all ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return new ve.dm.AnnotationSet( this.getDocument().getStore() );
}
if ( this.getRange( true ).getLength() ) {
@@ -373,7 +409,7 @@
*/
ve.dm.SurfaceFragment.prototype.getLeafNodes = function () {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return [];
}
return this.document.selectNodes( this.getRange(), 'leaves' );
@@ -393,7 +429,7 @@
*/
ve.dm.SurfaceFragment.prototype.getCoveredNodes = function () {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return [];
}
return this.document.selectNodes( this.getRange(), 'covered' );
@@ -411,22 +447,10 @@
*/
ve.dm.SurfaceFragment.prototype.getSiblingNodes = function () {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return [];
}
return this.document.selectNodes( this.getRange(), 'siblings' );
-};
-
-/**
- * Change whether to automatically update the surface selection when making
changes.
- *
- * @method
- * @param {boolean} [value=true] Automatically update surface selection
- * @chainable
- */
-ve.dm.SurfaceFragment.prototype.setAutoSelect = function ( value ) {
- this.noAutoSelect = !value;
- return this;
};
/**
@@ -437,7 +461,7 @@
*/
ve.dm.SurfaceFragment.prototype.select = function () {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
this.surface.setSelection( this.getRange() );
@@ -454,7 +478,7 @@
*/
ve.dm.SurfaceFragment.prototype.changeAttributes = function ( attr, type ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
@@ -481,7 +505,7 @@
);
}
if ( txs.length ) {
- this.surface.change( txs );
+ this.change( txs );
}
return this;
};
@@ -503,10 +527,10 @@
*/
ve.dm.SurfaceFragment.prototype.annotateContent = function ( method,
nameOrAnnotation, data ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
- var annotation, annotations, i, ilen, tx, txs = [], newRange =
this.getRange();
+ var annotation, annotations, i, ilen, tx, txs = [];
if ( nameOrAnnotation instanceof ve.dm.Annotation ) {
annotations = [ nameOrAnnotation ];
} else {
@@ -523,10 +547,8 @@
for ( i = 0, ilen = annotations.length; i < ilen; i++ ) {
tx = ve.dm.Transaction.newFromAnnotation(
this.document, this.getRange(), method, annotations[i] );
txs.push( tx );
- newRange = tx.translateRange( newRange );
-
}
- this.surface.change( txs, !this.noAutoSelect && newRange );
+ this.change( txs );
} else {
// Apply annotation to stack
if ( method === 'set' ) {
@@ -554,10 +576,10 @@
*/
ve.dm.SurfaceFragment.prototype.insertContent = function ( content, annotate )
{
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
- var tx, annotations;
+ var annotations;
if ( this.getRange( true ).getLength() ) {
this.removeContent();
}
@@ -572,8 +594,13 @@
ve.dm.Document.static.addAnnotationsToData(
content, annotations );
}
}
- tx = ve.dm.Transaction.newFromInsertion( this.document,
this.getRange( true ).start, content );
- this.surface.change( tx, !this.noAutoSelect &&
tx.translateRange( this.getRange() ) );
+ this.change(
+ ve.dm.Transaction.newFromInsertion(
+ this.document,
+ this.getRange( true ).start,
+ content
+ )
+ );
}
return this;
};
@@ -586,13 +613,11 @@
*/
ve.dm.SurfaceFragment.prototype.removeContent = function () {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
- var tx;
if ( this.getRange( true ).getLength() ) {
- tx = ve.dm.Transaction.newFromRemoval( this.document,
this.getRange() );
- this.surface.change( tx, !this.noAutoSelect &&
tx.translateRange( this.getRange() ) );
+ this.change( ve.dm.Transaction.newFromRemoval( this.document,
this.getRange() ) );
}
return this;
};
@@ -607,13 +632,12 @@
*/
ve.dm.SurfaceFragment.prototype.convertNodes = function ( type, attr ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
- var tx = ve.dm.Transaction.newFromContentBranchConversion(
+ this.change( ve.dm.Transaction.newFromContentBranchConversion(
this.document, this.getRange(), type, attr
- );
- this.surface.change( tx, !this.noAutoSelect && tx.translateRange(
this.getRange() ) );
+ ) );
return this;
};
@@ -638,14 +662,15 @@
*/
ve.dm.SurfaceFragment.prototype.wrapNodes = function ( wrapper ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
if ( !ve.isArray( wrapper ) ) {
wrapper = [wrapper];
}
- var tx = ve.dm.Transaction.newFromWrap( this.document, this.getRange(),
[], [], [], wrapper );
- this.surface.change( tx, !this.noAutoSelect && tx.translateRange(
this.getRange() ) );
+ this.change(
+ ve.dm.Transaction.newFromWrap( this.document, this.getRange(),
[], [], [], wrapper )
+ );
return this;
};
@@ -664,10 +689,10 @@
*/
ve.dm.SurfaceFragment.prototype.unwrapNodes = function ( outerDepth,
innerDepth ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
- var i, tx, innerUnwrapper = [], outerUnwrapper = [];
+ var i, innerUnwrapper = [], outerUnwrapper = [];
if ( this.getRange( true ).end - this.getRange( true ).start <
innerDepth * 2 ) {
throw new Error( 'cannot unwrap by greater depth than maximum
theoretical depth of selection' );
@@ -680,9 +705,9 @@
outerUnwrapper.push( this.surface.getDocument().data.getData(
this.getRange( true ).start - i ) );
}
- tx = ve.dm.Transaction.newFromWrap( this.document, this.getRange(),
outerUnwrapper, [], innerUnwrapper, [] );
- this.surface.change( tx, !this.noAutoSelect && tx.translateRange(
this.getRange() ) );
-
+ this.change( ve.dm.Transaction.newFromWrap(
+ this.document, this.getRange(), outerUnwrapper, [],
innerUnwrapper, []
+ ) );
return this;
};
@@ -709,10 +734,10 @@
*/
ve.dm.SurfaceFragment.prototype.rewrapNodes = function ( depth, wrapper ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
- var i, tx, unwrapper = [];
+ var i, unwrapper = [];
if ( !ve.isArray( wrapper ) ) {
wrapper = [wrapper];
@@ -726,9 +751,9 @@
unwrapper.push( this.surface.getDocument().data.getData(
this.getRange( true ).start + i ) );
}
- tx = ve.dm.Transaction.newFromWrap( this.document, this.getRange(), [],
[], unwrapper, wrapper );
- this.surface.change( tx, !this.noAutoSelect && tx.translateRange(
this.getRange() ) );
-
+ this.change(
+ ve.dm.Transaction.newFromWrap( this.document, this.getRange(),
[], [], unwrapper, wrapper )
+ );
return this;
};
@@ -753,19 +778,17 @@
*/
ve.dm.SurfaceFragment.prototype.wrapAllNodes = function ( wrapper ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
-
- var tx;
if ( !ve.isArray( wrapper ) ) {
wrapper = [wrapper];
}
- tx = ve.dm.Transaction.newFromWrap( this.document, this.getRange(), [],
wrapper, [], [] );
- this.surface.change( tx, !this.noAutoSelect && tx.translateRange(
this.getRange() ) );
-
+ this.change(
+ ve.dm.Transaction.newFromWrap( this.document, this.getRange(),
[], wrapper, [], [] )
+ );
return this;
};
@@ -789,10 +812,10 @@
*/
ve.dm.SurfaceFragment.prototype.rewrapAllNodes = function ( depth, wrapper ) {
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
- var i, tx, unwrapper = [],
+ var i, unwrapper = [],
innerRange = new ve.Range( this.getRange( true ).start + depth,
this.getRange( true ).end - depth );
if ( !ve.isArray( wrapper ) ) {
@@ -807,9 +830,9 @@
unwrapper.push( this.surface.getDocument().data.getData(
this.getRange( true ).start + i ) );
}
- tx = ve.dm.Transaction.newFromWrap( this.document, innerRange,
unwrapper, wrapper, [], [] );
- this.surface.change( tx, !this.noAutoSelect && tx.translateRange(
this.getRange() ) );
-
+ this.change(
+ ve.dm.Transaction.newFromWrap( this.document, innerRange,
unwrapper, wrapper, [], [] )
+ );
return this;
};
@@ -827,7 +850,7 @@
*/
ve.dm.SurfaceFragment.prototype.isolateAndUnwrap = function ( isolateForType )
{
// Handle null fragment
- if ( !this.surface ) {
+ if ( this.isNull() ) {
return this;
}
var nodes, startSplitNode, endSplitNode,
@@ -854,7 +877,7 @@
}
tx = ve.dm.Transaction.newFromInsertion(
fragment.getDocument(), insertBefore ? startOffset : endOffset, data );
- fragment.surface.change( tx, !fragment.noAutoSelect &&
tx.translateRange( fragment.getRange() ) );
+ fragment.change( tx );
startOffset += adjustment;
endOffset += adjustment;
--
To view, visit https://gerrit.wikimedia.org/r/124465
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Icb0c497290fa26db94f53fc859c08e37f99aee15
Gerrit-PatchSet: 2
Gerrit-Project: VisualEditor/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits