File: ve.dm.MWLanguageVariantNode.js

package info (click to toggle)
mediawiki 1%3A1.43.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 417,464 kB
  • sloc: php: 1,062,949; javascript: 664,290; sql: 9,714; python: 5,458; xml: 3,489; sh: 1,131; makefile: 64
file content (353 lines) | stat: -rw-r--r-- 10,344 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
/*!
 * VisualEditor DataModel MWLanguageVariantNode class.
 *
 * @copyright See AUTHORS.txt
 * @license The MIT License (MIT); see LICENSE.txt
 */

/**
 * DataModel MediaWiki language variant node, used to represent
 * LanguageConverter markup.
 *
 * @class
 * @abstract
 * @extends ve.dm.LeafNode
 * @mixes ve.dm.FocusableNode
 *
 * @constructor
 */
ve.dm.MWLanguageVariantNode = function VeDmMWLanguageVariantNode() {
	// Parent constructor
	ve.dm.MWLanguageVariantNode.super.apply( this, arguments );

	// Mixin constructors
	ve.dm.FocusableNode.call( this );
};

/* Inheritance */

OO.inheritClass( ve.dm.MWLanguageVariantNode, ve.dm.LeafNode );

OO.mixinClass( ve.dm.MWLanguageVariantNode, ve.dm.FocusableNode );

/* Static members */

ve.dm.MWLanguageVariantNode.static.name = 'mwLanguageVariant';

ve.dm.MWLanguageVariantNode.static.matchTagNames = null;

ve.dm.MWLanguageVariantNode.static.matchRdfaTypes = [ 'mw:LanguageVariant' ];

ve.dm.MWLanguageVariantNode.static.getHashObject = function ( dataElement ) {
	return {
		type: dataElement.type,
		variantInfo: dataElement.attributes.variantInfo
	};
};

/**
 * Node type to use when the contents are inline
 *
 * @static
 * @property {string}
 * @inheritable
 */
ve.dm.MWLanguageVariantNode.static.inlineType = 'mwLanguageVariantInline';

/**
 * Node type to use when the contents are a block
 *
 * @static
 * @property {string}
 * @inheritable
 */
ve.dm.MWLanguageVariantNode.static.blockType = 'mwLanguageVariantBlock';

/**
 * Node type to use when the contents are hidden
 *
 * @static
 * @property {string}
 * @inheritable
 */
ve.dm.MWLanguageVariantNode.static.hiddenType = 'mwLanguageVariantHidden';

/**
 * Migrate field names from old Parsoid spec to new field names.
 * This method will go away after the next Parsoid flag day.
 *
 * @static
 * @private
 * @param {Object} dataMwv
 * @return {Object}
 */
ve.dm.MWLanguageVariantNode.static.migrateFieldNames = function ( dataMwv ) {
	// Field name migration: `bidir`=>`twoway`; `unidir`=>`oneway`
	// This will go away eventually.
	if ( dataMwv.bidir ) {
		dataMwv.twoway = dataMwv.bidir;
		delete dataMwv.bidir;
	}
	if ( dataMwv.unidir ) {
		dataMwv.oneway = dataMwv.unidir;
		delete dataMwv.unidir;
	}
	return dataMwv;
};

/**
 * @inheritdoc
 */
ve.dm.MWLanguageVariantNode.static.toDataElement = function ( domElements, converter ) {
	const firstElement = domElements[ 0 ],
		dataMwvJSON = firstElement.getAttribute( 'data-mw-variant' ),
		dataMwv = dataMwvJSON ? JSON.parse( dataMwvJSON ) : {};

	this.migrateFieldNames( dataMwv );

	const dataElement = {
		attributes: {
			variantInfo: dataMwv,
			originalVariantInfo: dataMwvJSON
		}
	};

	if ( firstElement.tagName === 'META' ) {
		dataElement.type = this.hiddenType;
		return dataElement;
	}

	const isInline = this.isHybridInline( domElements, converter );
	dataElement.type = isInline ? this.inlineType : this.blockType;
	return dataElement;
};

/**
 * @inheritdoc
 */
ve.dm.MWLanguageVariantNode.static.toDomElements = function ( dataElement, doc, converter ) {
	const variantInfo = dataElement.attributes.variantInfo,
		tagName = this.matchTagNames[ 0 ],
		rdfaType = this.matchRdfaTypes[ 0 ],
		domElement = doc.createElement( tagName );

	let dataMwvJSON = JSON.stringify( variantInfo );
	// Preserve exact equality of this attribute for selser.
	if ( dataElement.attributes.originalVariantInfo ) {
		if ( OO.compare(
			this.migrateFieldNames(
				JSON.parse( dataElement.attributes.originalVariantInfo )
			),
			variantInfo
		) ) {
			dataMwvJSON = dataElement.attributes.originalVariantInfo;
		}
	}

	domElement.setAttribute( 'typeof', rdfaType );
	domElement.setAttribute( 'data-mw-variant', dataMwvJSON );
	if ( converter.doesModeNeedRendering() && tagName !== 'META' ) {
		// Fill in contents of span for diff/cut-and-paste/etc.
		this.insertPreviewElements( domElement, variantInfo );
	}
	return [ domElement ];
};

/**
 * Add previews for language variant markup inside their <span> nodes.
 * This ensures that template expansion, cut-and-paste, etc have reasonable
 * renderings.
 *
 * @static
 * @param {HTMLElement} container Container element to process
 * @param {Object|null} opts Preview options
 * @param {boolean} [opts.describeAll=false] Treat all rules as if the
 *   "describe" flag was set. This displays every language and its associated
 *   text, not just the one appropriate for the current user.
 */
ve.dm.MWLanguageVariantNode.static.processVariants = function ( container, opts ) {
	Array.prototype.forEach.call( container.querySelectorAll( '[typeof="mw:LanguageVariant"]' ), ( element ) => {
		const dataMwvJSON = element.getAttribute( 'data-mw-variant' );
		if ( dataMwvJSON && element.tagName !== 'META' ) {
			this.insertPreviewElements(
				element, JSON.parse( dataMwvJSON ), opts
			);
		}
	} );
};

/**
 * Insert language variant preview for specified element.
 *
 * @static
 * @param {HTMLElement} element Element to insert preview inside of.
 * @param {Object} variantInfo Language variant information object.
 * @param {Object|null} opts Preview options
 * @param {boolean} [opts.describeAll=false] Treat all rules as if the
 *   "describe" flag was set. This displays every language and its associated
 *   text, not just the one appropriate for the current user.
 * @return {HTMLElement} el
 */
ve.dm.MWLanguageVariantNode.static.insertPreviewElements = function ( element, variantInfo, opts ) {
	// Note that `element` can't be a <meta> (or other void tag)
	element.innerHTML = this.getPreviewHtml( variantInfo, opts );
	// This recurses into the children added by the `html` clause to ensure
	// that nested variants are expanded.
	this.processVariants( element, opts );
	return element;
};

/**
 * Helper method to return an appropriate HTML preview string for a
 * language converter node, based on the language variant information
 * object and the user's currently preferred variant.
 *
 * @static
 * @private
 * @param {Object} variantInfo Language variant information object.
 * @param {Object|null} opts Preview options
 * @param {boolean} [opts.describeAll=false] Treat all rules as if the
 *   "describe" flag was set. This displays every language and its associated
 *   text, not just the one appropriate for the current user.
 * @return {string} HTML string
 */
ve.dm.MWLanguageVariantNode.static.getPreviewHtml = function ( variantInfo, opts ) {
	if ( variantInfo.disabled ) {
		return variantInfo.disabled.t;
	} else if ( variantInfo.name ) {
		return ve.init.platform.getLanguageName( variantInfo.name.t.toLowerCase() );
	} else if ( variantInfo.filter ) {
		return variantInfo.filter.t;
	} else if ( variantInfo.describe || ( opts && opts.describeAll ) ) {
		let html = '';
		if ( variantInfo.twoway && variantInfo.twoway.length ) {
			variantInfo.twoway.forEach( ( item ) => {
				html += ve.init.platform.getLanguageName( item.l.toLowerCase() ) + ':' +
					item.t + ';';
			} );
		} else if ( variantInfo.oneway && variantInfo.oneway.length ) {
			variantInfo.oneway.forEach( ( item ) => {
				html += item.f + '⇒' +
					ve.init.platform.getLanguageName( item.l.toLowerCase() ) + ':' +
					item.t + ';';
			} );
		}
		return html;
	} else {
		let languageIndex;
		if ( variantInfo.twoway && variantInfo.twoway.length ) {
			languageIndex = this.matchLanguage( variantInfo.twoway );
			return variantInfo.twoway[ languageIndex ].t;
		} else if ( variantInfo.oneway && variantInfo.oneway.length ) {
			languageIndex = this.matchLanguage( variantInfo.oneway );
			return variantInfo.oneway[ languageIndex ].t;
		}
	}
	return '';
};

/**
 * @inheritdoc
 */
ve.dm.MWLanguageVariantNode.static.describeChanges = function () {
	// TODO: Provide a more detailed description of markup changes
	return ve.msg( 'visualeditor-changedesc-mwlanguagevariant' );
};

/**
 * @inheritdoc ve.dm.Node
 */
ve.dm.MWLanguageVariantNode.static.cloneElement = function () {
	// Parent method
	const clone = ve.dm.MWLanguageVariantNode.super.static.cloneElement.apply( this, arguments );
	delete clone.attributes.originalVariantInfo;
	return clone;
};

/**
 * Match the currently-selected language variant against the most appropriate
 * among a provided list of language codes.
 *
 * @static
 * @param {Object[]} [items] An array of objects, each of which have a field
 *  named `l` equal to a language code.
 * @return {number} The index in `items` with the most appropriate language
 *  code.
 */
ve.dm.MWLanguageVariantNode.static.matchLanguage = function ( items ) {
	const userVariant = mw.config.get( 'wgUserVariant' ),
		fallbacks = mw.config.get( 'wgVisualEditor' ).pageVariantFallbacks,
		languageCodes =
			( userVariant ? [ userVariant ] : [] ).concat( fallbacks || [] );
	for ( let j = 0; j < languageCodes.length; j++ ) {
		const code = languageCodes[ j ].toLowerCase();
		for ( let i = 0; i < items.length; i++ ) {
			if (
				items[ i ].l === '*' ||
				items[ i ].l.toLowerCase() === code
			) {
				return i;
			}
		}
	}
	// Bail: just show the first item.
	return 0;
};

/* Methods */

/**
 * Helper function to get the description object for this markup node.
 *
 * @return {Object}
 */
ve.dm.MWLanguageVariantNode.prototype.getVariantInfo = function () {
	return this.element.attributes.variantInfo;
};

/**
 * Helper function to discriminate between hidden and shown rules.
 *
 * @return {boolean} True if this node represents a conversion rule
 *  with no shown output
 */
ve.dm.MWLanguageVariantNode.prototype.isHidden = function () {
	return false;
};

/**
 * Helper function to discriminate between various types of language
 * converter markup.
 *
 * @return {string}
 */
ve.dm.MWLanguageVariantNode.prototype.getRuleType = function () {
	return this.constructor.static.getRuleType( this.getVariantInfo() );
};

/**
 * Helper function to discriminate between various types of language
 * converter markup.
 *
 * @static
 * @param {Object} variantInfo Language variant information object.
 * @return {string}
 */
ve.dm.MWLanguageVariantNode.static.getRuleType = function ( variantInfo ) {
	if ( variantInfo.disabled ) {
		return 'disabled';
	}
	if ( variantInfo.filter ) {
		return 'filter';
	}
	if ( variantInfo.name ) {
		return 'name';
	}
	if ( variantInfo.twoway ) {
		return 'twoway';
	}
	if ( variantInfo.oneway ) {
		return 'oneway';
	}
	return 'unknown'; // should never happen
};