summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js')
-rw-r--r--www/wiki/extensions/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js541
1 files changed, 541 insertions, 0 deletions
diff --git a/www/wiki/extensions/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js b/www/wiki/extensions/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js
new file mode 100644
index 00000000..d31b49f5
--- /dev/null
+++ b/www/wiki/extensions/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js
@@ -0,0 +1,541 @@
+/*!
+ * 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 ] );
+ }
+ }
+ }
+
+ /**
+ * Normalize a language code for ULS usage.
+ *
+ * MediaWiki language codes (especially on WMF sites) are inconsistent
+ * with ULS codes. We need to use ULS codes to access the proper data.
+ *
+ * @param {string} code
+ * @return {string} Normalized language code
+ */
+ function convertMediaWikiLanguageCodeToULS( code ) {
+ code = code.toLowerCase();
+ return $.uls.data.isRedirect( code ) || code;
+ }
+
+ /**
+ * 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 languags. 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
+ */
+ function CompactInterlanguageList( listElement, options ) {
+ this.listElement = listElement;
+ this.options = options || {};
+
+ /**
+ * @private
+ * @property {Object} interlanguageList
+ */
+ this.interlanguageList = null;
+
+ /**
+ * @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.interlanguageList = this.getInterlanguageList();
+ 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();
+ this.listen();
+ };
+
+ /**
+ * Render the compacted interlanguage list and triggers
+ */
+ CompactInterlanguageList.prototype.render = function () {
+ var language;
+
+ for ( language in this.compactList ) {
+ this.compactList[ language ].parentNode.style.display = '';
+ }
+
+ this.addTrigger();
+
+ mw.hook( 'mw.uls.compactlinks.initialized' ).fire( true );
+ };
+
+ /**
+ * Attaches the actual selector to the trigger.
+ *
+ * @param {jQuery} $trigger Element to use as trigger.
+ */
+ CompactInterlanguageList.prototype.createSelector = function ( $trigger ) {
+ var languageCode,
+ languages = Object.keys( this.interlanguageList ),
+ self = this,
+ ulsLanguageList = {};
+
+ for ( languageCode in this.interlanguageList ) {
+ ulsLanguageList[ languageCode ] = this.interlanguageList[ languageCode ].textContent;
+ }
+
+ // Attach ULS to the trigger
+ $trigger.uls( {
+ onReady: function () {
+ this.$menu.addClass( 'interlanguage-uls-menu' );
+ },
+ /**
+ * Language selection handler
+ *
+ * @param {string} language language code
+ * @param {Object} event jQuery event object
+ */
+ onSelect: function ( language, event ) {
+ self.$trigger.removeClass( 'selector-open' );
+ mw.uls.addPreviousLanguage( language );
+
+ // Switch the current tab to the new language,
+ // unless it was Ctrl-click or Command-click
+ if ( !event.metaKey && !event.shiftKey ) {
+ location.href = self.interlanguageList[ language ].href;
+ }
+ },
+ onVisible: function () {
+ var offset, height, width, triangleWidth;
+ // The panel is positioned carefully so that our pointy triangle,
+ // which is implemented as a square box rotated 45 degrees with
+ // rotation origin in the middle. See the corresponding style file.
+
+ // These are for the trigger
+ offset = $trigger.offset();
+ width = $trigger.outerWidth();
+ height = $trigger.outerHeight();
+
+ // Triangle width is: who knows now, but this still looks fine.
+ triangleWidth = 12;
+
+ if ( offset.left > $( window ).width() / 2 ) {
+ this.left = offset.left - this.$menu.outerWidth() - triangleWidth;
+ this.$menu.removeClass( 'selector-left' ).addClass( 'selector-right' );
+ } else {
+ this.left = offset.left + width + triangleWidth;
+ this.$menu.removeClass( 'selector-right' ).addClass( 'selector-left' );
+ }
+ // Offset from the middle of the trigger
+ this.top = offset.top + ( height / 2 ) - 27;
+
+ this.$menu.css( {
+ left: this.left,
+ top: this.top
+ } );
+ $trigger.addClass( 'selector-open' );
+ },
+ languageDecorator: function ( $languageLink, language ) {
+ var element = self.interlanguageList[ language ];
+ // Set href, text, and tooltip exactly same as what was in
+ // interlanguage link. The ULS autonym might be different in some
+ // cases like sr. In ULS it is "српски", while in interlanguage links
+ // it is "српски / srpski"
+ $languageLink
+ .prop( {
+ href: element.href,
+ title: element.title
+ } )
+ .text( element.textContent );
+
+ // This code is to support badges used in Wikimedia
+ $languageLink.parent().addClass( element.parentNode.className );
+ },
+ onCancel: function () {
+ $trigger.removeClass( 'selector-open' );
+ },
+ languages: ulsLanguageList,
+ ulsPurpose: 'compact-language-links',
+ // Show common languages
+ quickList: self.getCommonLanguages( languages ),
+ noResultsTemplate: function () {
+ var $defaultTemplate = $.fn.lcd.defaults.noResultsTemplate.call( this );
+ // Customize the message
+ $defaultTemplate
+ .find( '.uls-no-results-found-title' )
+ .data( 'i18n', 'ext-uls-compact-no-results' );
+ return $defaultTemplate;
+ }
+ } );
+ };
+
+ /**
+ * Bind to event handlers and listen for events
+ */
+ CompactInterlanguageList.prototype.listen = function () {
+ var self = this;
+
+ this.$trigger.one( 'click', function () {
+ // Load the ULS now.
+ mw.loader.using( 'ext.uls.mediawiki' ).then( function () {
+ self.createSelector( self.$trigger );
+ self.$trigger.trigger( 'click' );
+ } );
+ } );
+ };
+
+ /**
+ * 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,
+ this.getLangsWithBadges,
+ 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 = 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(
+ document.querySelectorAll( '#p-lang [class*="badge"]' ),
+ function ( el ) {
+ return convertMediaWikiLanguageCodeToULS(
+ el.querySelector( '.interlanguage-link-target' ).lang
+ );
+ }
+ );
+ };
+
+ /**
+ * Get the list of languages links.
+ *
+ * @return {Object} Map of language codes to elements.
+ */
+ CompactInterlanguageList.prototype.getInterlanguageList = function () {
+ var interlanguageList = {};
+
+ Array.prototype.forEach.call( this.listElement.querySelectorAll( '.interlanguage-link-target' ), function ( el ) {
+ var langCode = convertMediaWikiLanguageCodeToULS( el.lang );
+ interlanguageList[ langCode ] = el;
+ } );
+
+ return interlanguageList;
+ };
+
+ /**
+ * Get common languages - the most probable languages predicted by ULS.
+ *
+ * @param {string[]} languages Language codes
+ * @return {string[]} List of all common language codes
+ */
+ CompactInterlanguageList.prototype.getCommonLanguages = function ( languages ) {
+ if ( this.commonInterlanguageList === null ) {
+ this.commonInterlanguageList = mw.uls.getFrequentLanguageList()
+ .filter( function ( language ) {
+ return languages.indexOf( language ) >= 0;
+ } );
+ }
+
+ return this.commonInterlanguageList;
+ };
+
+ /**
+ * 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
+ */
+ CompactInterlanguageList.prototype.addTrigger = function () {
+ var trigger = document.createElement( 'button' );
+ trigger.className = 'mw-interlanguage-selector mw-ui-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 2018-09-10.
+ *
+ * Summary:
+ * - DOM Queries: 5 + 1N
+ * * createCompactList (1 querySelector)
+ * * getLangsWithBadges (1N querySelector, 1 querySelectorAll)
+ * * getInterlanguageList (1 querySelectorAll)
+ * * getLangsInText (1 querySelectorAll)
+ * * hideOriginal (1 querySelectorAll)
+ * - DOM Writes: 1 + 2N
+ * * addTrigger (1 appendChild)
+ * * hideOriginal (1N Element.style)
+ * * render (1N Element.style)
+ * - Misc: 1
+ * * addTrigger (1 mw.Message#parser)
+ */
+ function createCompactList() {
+ var listElement, compactList;
+ listElement = document.querySelector( '#p-lang ul' );
+ if ( !listElement ) {
+ // Not all namespaces/pages/actions have #p-lang.
+ return;
+ }
+ compactList = new CompactInterlanguageList( listElement, {
+ // Compact the list to this size
+ max: 9
+ } );
+ compactList.init();
+
+ }
+
+ // Early execute of createCompactList
+ if ( document.readyState === 'interactive' ) {
+ createCompactList();
+ } else {
+ $( createCompactList );
+ }
+
+}() );