diff --git a/extension.json b/extension.json index dff3f3e3..90e859b6 100644 --- a/extension.json +++ b/extension.json @@ -158,7 +158,10 @@ "remoteExtPath": "UniversalLanguageSelector/resources" }, "ext.uls.compactlinks": { - "scripts": "js/ext.uls.compactlinks.js", + "packageFiles": [ + "js/ext.uls.compactlinks.js", + "js/ext.uls.launch.js" + ], "styles": "css/ext.uls.compactlinks.less", "dependencies": [ "mediawiki.jqueryMsg", diff --git a/resources/js/ext.uls.common.js b/resources/js/ext.uls.common.js index 582a65f8..59546ff1 100644 --- a/resources/js/ext.uls.common.js +++ b/resources/js/ext.uls.common.js @@ -103,6 +103,35 @@ } catch ( e ) {} }; + /** + * 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 + */ + mw.uls.convertMediaWikiLanguageCodeToULS = function ( code ) { + code = code.toLowerCase(); + return $.uls.data.isRedirect( code ) || code; + }; + + /** + * @param {Element[]} nodes to parse + * @return {Object} that maps language codes to the corresponding DOM elements + */ + mw.uls.getInterlanguageListFromNodes = function ( nodes ) { + var interlanguageList = {}; + + Array.prototype.forEach.call( nodes, function ( el ) { + var langCode = mw.uls.convertMediaWikiLanguageCodeToULS( el.lang ); + interlanguageList[ langCode ] = el; + } ); + + return interlanguageList; + }; + mw.uls.getPreviousLanguages = function () { var previousLanguages = []; diff --git a/resources/js/ext.uls.compactlinks.js b/resources/js/ext.uls.compactlinks.js index 61d763ff..7a10a666 100644 --- a/resources/js/ext.uls.compactlinks.js +++ b/resources/js/ext.uls.compactlinks.js @@ -20,7 +20,8 @@ ( function () { 'use strict'; - var DEFAULT_LIST_SIZE = 9; + var DEFAULT_LIST_SIZE = 9, + launchULS = require( './ext.uls.launch.js' ); /** * @param {Array} target @@ -44,20 +45,6 @@ } } - /** - * 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. * @@ -144,7 +131,9 @@ * @class * @constructor * @param {HTMLElement} listElement Interlanguage list element - * @param {Object} options + * @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; @@ -154,7 +143,9 @@ * @private * @property {Object} interlanguageList */ - this.interlanguageList = null; + this.interlanguageList = mw.uls.getInterlanguageListFromNodes( + listElement.querySelectorAll( '.interlanguage-link-target' ) + ); /** * @private @@ -174,7 +165,6 @@ 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 ) { @@ -213,99 +203,10 @@ * @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 - // eslint-disable-next-line mediawiki/class-doc - $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; - } - } ); + launchULS( + $trigger, + this.interlanguageList + ); }; /** @@ -411,7 +312,7 @@ CompactInterlanguageList.prototype.getLangsInText = function () { var languagesInText = []; Array.prototype.forEach.call( document.querySelectorAll( '#mw-content-text [lang]' ), function ( el ) { - var lang = convertMediaWikiLanguageCodeToULS( el.lang ); + var lang = mw.uls.convertMediaWikiLanguageCodeToULS( el.lang ); if ( languagesInText.indexOf( lang ) === -1 ) { languagesInText.push( lang ); } @@ -430,7 +331,7 @@ return Array.prototype.map.call( document.querySelectorAll( '#p-lang [class*="badge"]' ), function ( el ) { - return convertMediaWikiLanguageCodeToULS( + return mw.uls.convertMediaWikiLanguageCodeToULS( el.querySelector( '.interlanguage-link-target' ).lang ); } @@ -443,14 +344,7 @@ * @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; + return this.interlanguageList; }; /** @@ -524,10 +418,7 @@ // Not all namespaces/pages/actions have #p-lang. return; } - compactList = new CompactInterlanguageList( listElement, { - // Compact the list to this size - max: 9 - } ); + compactList = new CompactInterlanguageList( listElement ); compactList.init(); } diff --git a/resources/js/ext.uls.launch.js b/resources/js/ext.uls.launch.js new file mode 100644 index 00000000..eab55ec0 --- /dev/null +++ b/resources/js/ext.uls.launch.js @@ -0,0 +1,128 @@ +/* eslint-disable no-implicit-globals */ +var commonInterlanguageList; + +/** + * @param {string[]} languageCodes array of language codes available + * @return {Array} of languages filtered to those commonly used + */ +function filterForCommonLanguagesForUser( languageCodes ) { + if ( commonInterlanguageList === null ) { + commonInterlanguageList = mw.uls.getFrequentLanguageList() + .filter( function ( language ) { + return languageCodes.indexOf( language ) >= 0; + } ); + } + + return commonInterlanguageList; +} + +/** + * @param {Object} languagesObject mapping language codes to DOMElements + * @return {Object} mapping language codes to the textContent of DOMElements + */ +function languageObjectTextContent( languagesObject ) { + var newLanguageObject = {}; + Object.keys( languagesObject ).forEach( function ( langCode ) { + newLanguageObject[ langCode ] = languagesObject[ langCode ].textContent; + } ); + return newLanguageObject; +} + +/** + * Launches an instance of UniversalLanguageSelector for changing to another + * article language. + * + * @param {jQuery.Object} $trigger for opening ULS dialog + * @param {Object} languagesObject of the available languages, mapping + * code (string) to Element + */ +function launchULS( $trigger, languagesObject ) { + // 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 ) { + $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 = languagesObject[ 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 = languagesObject[ 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 + // eslint-disable-next-line mediawiki/class-doc + $languageLink.parent().addClass( element.parentNode.className ); + }, + onCancel: function () { + $trigger.removeClass( 'selector-open' ); + }, + languages: languageObjectTextContent( languagesObject ), + ulsPurpose: 'compact-language-links', + // Show common languages + quickList: filterForCommonLanguagesForUser( + Object.keys( languagesObject ) + ), + 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; + } + } ); +} + +module.exports = launchULS;