Files
mediawiki-extensions-Univer…/resources/js/ext.uls.compactlinks.js
Jon Robson 14e3be1cd3 Revise logic for creating compact links button on Vector 2022
The existing strpos code is not working and the code is loading on
Vector 2022, it also appears to be required on Vector 2022 as without
it the language button does not appear to work (these should be
decoupled in a later patch)

The easiest possible solution here, is to move the check to the
client side and not create the trigger button in Vector 2022. e.g. do
the equivalent check for the node existence in the client instead
of the server side.

Bug: T353850
Change-Id: I44835e672bce97a7d3a98d9d75c4805ee2cc448d
2024-01-04 04:09:32 +00:00

385 lines
11 KiB
JavaScript

/*!
* Compact the interlanguage links in the sidebar
*
* Copyright (C) 2012-2014 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon Harris,
* Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland, Niharika Kohli
* and other contributors. See CREDITS for a list.
*
* UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't
* have to do anything special to choose one license or the other and you don't
* have to notify anyone which license you are using. You are free to use
* UniversalLanguageSelector in commercial projects as long as the copyright
* header is left intact. See files GPL-LICENSE and MIT-LICENSE for details.
*
* @file
* @ingroup Extensions
* @licence GNU GPL-2.0-or-later
* @licence MIT License
*/
( function () {
'use strict';
var DEFAULT_LIST_SIZE = 9;
/**
* @param {Array} target
* @param {Array} source
* @param {string|string[]|undefined} items Language code, or list of language codes
*/
function addMatchWithoutDuplicate( target, source, items ) {
var i;
if ( items === undefined ) {
return;
}
items = !Array.isArray( items ) ? [ items ] : items;
for ( i = 0; i < items.length; i++ ) {
if (
// Only add if unique and matches source
target.indexOf( items[ i ] ) === -1 &&
source.indexOf( items[ i ] ) !== -1
) {
target.push( items[ i ] );
}
}
}
/**
* Get user-defined assistant languages on wikis with Translate extension.
*
* Where available, they're languages deemed useful by the user.
*
* @return {string[]|undefined} Language codes
*/
function getAssistantLanguages() {
var assistantLanguages = mw.user.options.get( 'translate-editlangs' );
if ( !assistantLanguages || assistantLanguages === 'default' ) {
return;
}
return assistantLanguages.split( /,\s*/ );
}
/**
* Get previously selected languages.
*
* Previous languages are a good suggestion because the user has
* explicitly chosen them in the past.
*
* @return {string[]} Language codes
*/
function getPreviousLanguages() {
return mw.uls.getPreviousLanguages();
}
/**
* Get languages from the Babel box on the user's user page.
*
* @return {string[]|undefined} Language codes
*/
function getBabelLanguages() {
return mw.config.get( 'wgULSBabelLanguages' );
}
/**
* Get site-specific highlighted languages. Mostly used on Wikimedia sites.
*
* @return {string[]|undefined} Language codes
*/
function getSitePicks() {
return mw.config.get( 'wgULSCompactLinksPrepend' );
}
/**
* Get probable languages predicted by ULS.
*
* @return {string[]} Language codes
*/
function getCommonLanguages() {
return mw.uls.getFrequentLanguageList();
}
/**
* Get globally common languages.
*
* These are not user-specific. This helps to avoid biasing the compact list
* to language codes that sort to the beginning of the alphabet in the
* final stage.
*
* @return {string[]} Language codes
*/
function getExtraCommonLanguages() {
return [
'zh', 'en', 'hi', 'ur', 'es', 'ar', 'ru', 'id', 'ms', 'pt',
'fr', 'de', 'bn', 'ja', 'pnb', 'pa', 'jv', 'te', 'ta', 'ko', 'mr', 'tr', 'vi',
'it', 'fa', 'sv', 'nl', 'pl'
];
}
/**
* The final strategy is the original interlanguage list.
*
* @param {string[]} languages Language codes
* @return {string[]} Language codes
*/
function getFinalFallback( languages ) {
return languages;
}
/**
* @class
* @constructor
* @param {HTMLElement} listElement Interlanguage list element
* @param {Object} [options]
* @param {number} [options.max] maximum number of languages to show
* in the compacted list. This defaults to DEFAULT_LIST_SIZE.
*/
function CompactInterlanguageList( listElement, options ) {
this.listElement = listElement;
this.options = options || {};
/**
* @private
* @property {Object} interlanguageList
*/
this.interlanguageList = mw.uls.getInterlanguageListFromNodes(
listElement.querySelectorAll( '.interlanguage-link-target' )
);
/**
* @private
* @property {Object} interlanguageList
*/
this.compactList = null;
this.commonInterlanguageList = null;
this.$trigger = null;
this.compactSize = 0;
this.listSize = 0;
}
/**
* Initialize the plugin
*/
CompactInterlanguageList.prototype.init = function () {
var max = this.options.max || DEFAULT_LIST_SIZE;
this.listSize = Object.keys( this.interlanguageList ).length;
if ( this.listSize <= max ) {
// Not enough languages to compact the list
mw.hook( 'mw.uls.compactlinks.initialized' ).fire( false );
return;
}
// If we're only a bit beyond max, limit to 7 instead of 9.
// FIXME: This assumes the max is 9.
this.compactSize = ( this.listSize <= 12 ) ? 7 : max;
this.compactList = this.getCompactList();
this.hideOriginal();
this.render();
};
/**
* Render the compacted interlanguage list and triggers
*/
CompactInterlanguageList.prototype.render = function () {
var language;
for ( language in this.compactList ) {
this.compactList[ language ].parentNode.style.display = '';
}
// If there is an interlanguage selector in the page already
// there is no need to add a trigger and Codex styles (T353850).
if ( !$( '.mw-interlanguage-selector' ).length ) {
mw.loader.using( '@wikimedia/codex' ).then( function () {
this.addTrigger();
}.bind( this ) );
}
mw.hook( 'mw.uls.compactlinks.initialized' ).fire( true );
};
/**
* Get the compacted interlanguage list as associative array
*
* @return {Object}
*/
CompactInterlanguageList.prototype.getCompactList = function () {
var language, languages, compactLanguages, i, compactedList;
compactedList = {};
languages = Object.keys( this.interlanguageList );
compactLanguages = this.compact( languages );
for ( i = 0; i < compactLanguages.length; i++ ) {
language = compactLanguages[ i ];
compactedList[ language ] = this.interlanguageList[ language ];
}
return compactedList;
};
/**
* Get compacting strategies.
*
* The items will be executed in the given order till the required
* compact size is achieved. Each strategy is given two arrays: `candidates`
* and `languages`. The candidates array is a list the callback should add to.
* The languages list contains language codes actually available for the current
* page, the callback may use this to optimise their search for candidates,
* although compact() will filter out irrelevant candidates so strategies should
* only use this if it helps narrow their search for candidates, avoid needless
* filtering that compact() will do already.
*
* @return {Function[]} Array of compacting functions
*/
CompactInterlanguageList.prototype.getCompactStrategies = function () {
return [
getAssistantLanguages,
getPreviousLanguages,
getBabelLanguages,
getSitePicks,
getCommonLanguages,
this.getLangsInText.bind( this ),
this.getLangsWithBadges.bind( this ),
getExtraCommonLanguages,
getFinalFallback
];
};
/**
* Compact a given array of languages
*
* @param {Array} languages
* @return {Array} Compacted array
*/
CompactInterlanguageList.prototype.compact = function ( languages ) {
var i, strategies, found,
compactLanguages = [];
strategies = this.getCompactStrategies();
for ( i = 0; i < strategies.length; i++ ) {
found = strategies[ i ]( languages );
// Add language codes from 'found' that are also in 'languages'
// to 'compactLanguages' (if not already in there).
addMatchWithoutDuplicate( compactLanguages, languages, found );
if ( compactLanguages.length >= this.compactSize ) {
// We have more than enough items. Stop here.
compactLanguages = compactLanguages.slice( 0, this.compactSize );
break;
}
}
return compactLanguages;
};
/**
* Get language codes that are used in the page's text content.
*
* This is done by looking for HTML elements with a "lang" attribute—they
* are likely to appear in a foreign name, for example.
*
* The reader doesn't necessarily know this language, but it
* appears relevant to the page.
*
* @return {string[]} Language codes
*/
CompactInterlanguageList.prototype.getLangsInText = function () {
var languagesInText = [];
Array.prototype.forEach.call( document.querySelectorAll( '#mw-content-text [lang]' ), function ( el ) {
var lang = mw.uls.convertMediaWikiLanguageCodeToULS( el.lang );
if ( languagesInText.indexOf( lang ) === -1 ) {
languagesInText.push( lang );
}
} );
return languagesInText;
};
/**
* Get languages in which a related page has any kind of a badge,
* such as "featured article". The "badge-*" classes are added by Wikibase.
*
* @return {string[]} Language codes
*/
CompactInterlanguageList.prototype.getLangsWithBadges = function () {
return Array.prototype.map.call(
this.listElement.querySelectorAll( '[class*="badge"] a.interlanguage-link-target' ),
function ( el ) {
return mw.uls.convertMediaWikiLanguageCodeToULS( el.lang );
}
);
};
/**
* Hide languages in the interlanguage list.
*
* The most relevant ones are unhidden in #render.
*/
CompactInterlanguageList.prototype.hideOriginal = function () {
var links = this.listElement.querySelectorAll( '.interlanguage-link' ),
i = links.length;
while ( i-- ) {
links[ i ].style.display = 'none';
}
};
/**
* Add the trigger at the bottom of the language list.
*
* Click handler is setup in ext.uls.interface module.
*/
CompactInterlanguageList.prototype.addTrigger = function () {
var trigger = document.createElement( 'button' );
// TODO: Should we have a different class name where the CLS styles are attached?
trigger.className = 'mw-interlanguage-selector cdx-button';
trigger.title = mw.message( 'ext-uls-compact-link-info' ).plain();
// Use text() because the message needs {{PLURAL:}}
trigger.textContent = mw.message(
'ext-uls-compact-link-count',
mw.language.convertNumber( this.listSize - this.compactSize )
).text();
this.listElement.appendChild( trigger );
this.$trigger = $( trigger );
};
/**
* Performance cost of calling createCompactList(), as of 2021-02-10.
*
* Summary:
* - DOM Queries: 5
* * createCompactList (1 querySelector)
* * CompactInterlanguageList constructor (1 querySelectorAll)
* * getLangsWithBadges (1 querySelectorAll)
* * getLangsInText (1 querySelectorAll)
* * hideOriginal (1 querySelectorAll)
* - DOM Writes: 1 + 2N
* * addTrigger (1 appendChild)
* * hideOriginal (1N Element.style)
* * render (1N Element.style) // N defaults to 9
* - Misc: 1
* * addTrigger (1 mw.Message#parser)
*/
function createCompactList() {
var listElement, compactList;
listElement = document.querySelector( '.mw-portlet-lang ul, #p-lang ul' );
if ( !listElement ) {
// Not all namespaces will have a list of languages.
return;
}
compactList = new CompactInterlanguageList( listElement );
compactList.init();
}
// Early execute of createCompactList
if ( document.readyState === 'interactive' ) {
createCompactList();
} else {
$( createCompactList );
}
}() );