summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'MLEB/UniversalLanguageSelector/resources/js')
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.common.js130
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js211
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.displaysettings.js102
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.eventlogger.js228
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.i18n.js2
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.ime.js23
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.inputsettings.js4
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.interface.js207
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.languagesettings.js32
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.launch.js175
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.preferences.js89
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.setlang.js2
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.js11
-rw-r--r--MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.repository.js47
14 files changed, 554 insertions, 709 deletions
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.common.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.common.js
index 1a22a35a..e2072b79 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.common.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.common.js
@@ -32,7 +32,6 @@
mw.uls = mw.uls || {};
mw.uls.previousLanguagesStorageKey = 'uls-previous-languages';
- mw.uls.languageSettingsModules = [ 'ext.uls.inputsettings', 'ext.uls.displaysettings' ];
/**
* Change the language of wiki using API or set cookie and reload the page
@@ -40,68 +39,68 @@
* @param {string} language Language code.
*/
mw.uls.changeLanguage = function ( language ) {
- var deferred = new $.Deferred();
+ mw.uls.setLanguage( language ).then( function () {
+ location.reload();
+ } );
+ };
+
+ /**
+ * Change the language of wiki using API or set cookie.
+ *
+ * @param {string} language Language code.
+ * @return {jQuery.Promise}
+ */
+ mw.uls.setLanguage = function ( language ) {
+ var api = new mw.Api();
function changeLanguageAnon() {
if ( mw.config.get( 'wgULSAnonCanChangeLanguage' ) ) {
mw.cookie.set( 'language', language );
- location.reload();
}
+ return $.Deferred().resolve();
}
- deferred.done( function () {
- var api = new mw.Api();
+ // Track if event logging is enabled
+ mw.hook( 'mw.uls.interface.language.change' ).fire( language );
- if ( mw.user.isAnon() ) {
- changeLanguageAnon();
- return;
- }
+ if ( mw.user.isAnon() ) {
+ return changeLanguageAnon();
+ }
- // TODO We can avoid doing this query if we know global preferences are not enabled
- api.get( {
- action: 'query',
- meta: 'globalpreferences',
- gprprop: 'preferences'
- } ).then( function ( res ) {
- // Check whether global preferences are in use. If they are not, `res.query` is
- // an empty object. `res` will also contain warnings about unknown parameters.
- try {
- return !!res.query.globalpreferences.preferences.language;
- } catch ( e ) {
- return false;
- }
- } ).then( function ( hasGlobalPreference ) {
- var apiModule;
-
- if ( hasGlobalPreference ) {
- apiModule = 'globalpreferenceoverrides';
- mw.storage.set( 'uls-gp', '1' );
- } else {
- apiModule = 'options';
- mw.storage.remove( 'uls-gp' );
- }
+ // TODO We can avoid doing this query if we know global preferences are not enabled
+ return api.get( {
+ action: 'query',
+ meta: 'globalpreferences',
+ gprprop: 'preferences'
+ } ).then( function ( res ) {
+ // Check whether global preferences are in use. If they are not, `res.query` is
+ // an empty object. `res` will also contain warnings about unknown parameters.
+ try {
+ return !!res.query.globalpreferences.preferences.language;
+ } catch ( e ) {
+ return false;
+ }
+ } ).then( function ( hasGlobalPreference ) {
+ var apiModule;
+
+ if ( hasGlobalPreference ) {
+ apiModule = 'globalpreferenceoverrides';
+ mw.storage.set( 'uls-gp', '1' );
+ } else {
+ apiModule = 'options';
+ mw.storage.remove( 'uls-gp' );
+ }
- return api.postWithToken( 'csrf', {
- action: apiModule,
- optionname: 'language',
- optionvalue: language
- } );
- } ).done( function () {
- location.reload();
- } ).fail( function () {
- // Setting the option failed. Maybe the user has logged off.
- // Continue like anonymous user and set cookie.
- changeLanguageAnon();
+ return api.postWithToken( 'csrf', {
+ action: apiModule,
+ optionname: 'language',
+ optionvalue: language
} );
+ } ).catch( function () {
+ // Setting the option failed. Maybe the user has logged off.
+ // Continue like anonymous user and set cookie.
+ return changeLanguageAnon();
} );
-
- mw.hook( 'mw.uls.interface.language.change' ).fire( language, deferred );
-
- // Delay is zero if event logging is not enabled
- setTimeout( function () {
- deferred.resolve();
- }, mw.config.get( 'wgULSEventLogging' ) * 500 );
-
};
mw.uls.setPreviousLanguages = function ( previousLanguages ) {
@@ -113,6 +112,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/MLEB/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js
index 61d763ff..72038c78 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.compactlinks.js
@@ -45,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.
*
* Where available, they're languages deemed useful by the user.
@@ -96,7 +82,7 @@
}
/**
- * Get site-specific highlighted languags. Mostly used on Wikimedia sites.
+ * Get site-specific highlighted languages. Mostly used on Wikimedia sites.
*
* @return {string[]|undefined} Language codes
*/
@@ -144,7 +130,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 +142,9 @@
* @private
* @property {Object} interlanguageList
*/
- this.interlanguageList = null;
+ this.interlanguageList = mw.uls.getInterlanguageListFromNodes(
+ listElement.querySelectorAll( '.interlanguage-link-target' )
+ );
/**
* @private
@@ -174,7 +164,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 ) {
@@ -189,7 +178,6 @@
this.compactList = this.getCompactList();
this.hideOriginal();
this.render();
- this.listen();
};
/**
@@ -208,122 +196,6 @@
};
/**
- * 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
- // 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;
- }
- } );
- };
-
- /**
- * 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}
@@ -364,8 +236,8 @@
getBabelLanguages,
getSitePicks,
getCommonLanguages,
- this.getLangsInText,
- this.getLangsWithBadges,
+ this.getLangsInText.bind( this ),
+ this.getLangsWithBadges.bind( this ),
getExtraCommonLanguages,
getFinalFallback
];
@@ -411,7 +283,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 );
}
@@ -428,49 +300,14 @@
*/
CompactInterlanguageList.prototype.getLangsWithBadges = function () {
return Array.prototype.map.call(
- document.querySelectorAll( '#p-lang [class*="badge"]' ),
+ this.listElement.querySelectorAll( '[class*="badge"] a.interlanguage-link-target' ),
function ( el ) {
- return convertMediaWikiLanguageCodeToULS(
- el.querySelector( '.interlanguage-link-target' ).lang
- );
+ return mw.uls.convertMediaWikiLanguageCodeToULS( el.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.
@@ -484,10 +321,13 @@
};
/**
- * Add the trigger at the bottom of the language list
+ * 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 mw-ui-button';
trigger.title = mw.message( 'ext-uls-compact-link-info' ).plain();
// Use text() because the message needs {{PLURAL:}}
@@ -501,33 +341,30 @@
};
/**
- * Performance cost of calling createCompactList(), as of 2018-09-10.
+ * Performance cost of calling createCompactList(), as of 2021-02-10.
*
* Summary:
- * - DOM Queries: 5 + 1N
+ * - DOM Queries: 5
* * createCompactList (1 querySelector)
- * * getLangsWithBadges (1N querySelector, 1 querySelectorAll)
- * * getInterlanguageList (1 querySelectorAll)
+ * * 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)
+ * * render (1N Element.style) // N defaults to 9
* - Misc: 1
* * addTrigger (1 mw.Message#parser)
*/
function createCompactList() {
var listElement, compactList;
- listElement = document.querySelector( '#p-lang ul' );
+ listElement = document.querySelector( '.mw-portlet-lang ul, #p-lang ul' );
if ( !listElement ) {
- // Not all namespaces/pages/actions have #p-lang.
+ // Not all namespaces will have a list of languages.
return;
}
- compactList = new CompactInterlanguageList( listElement, {
- // Compact the list to this size
- max: 9
- } );
+ compactList = new CompactInterlanguageList( listElement );
compactList.init();
}
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.displaysettings.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.displaysettings.js
index 9a1feab8..3e532bac 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.displaysettings.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.displaysettings.js
@@ -110,6 +110,7 @@
this.$webfonts = null;
this.$parent = $parent;
this.savedRegistry = $.extend( true, {}, mw.webfonts.preferences );
+ this.dirty = false;
}
DisplaySettings.prototype = {
@@ -117,11 +118,36 @@
constructor: DisplaySettings,
/**
+ * Loads the webfonts module sets the `webfonts` property when its safe to do so
+ */
+ setupWebFonts: function () {
+ var d = $.Deferred();
+ mw.loader.using( [ 'ext.uls.webfonts.fonts' ] ).then( function () {
+ if ( this.isWebFontsEnabled ) {
+ mw.webfonts.setup();
+ }
+
+ // Allow the webfonts library to finish loading (hack)
+ setTimeout( function () {
+ this.$webfonts = $( document.body ).data( 'webfonts' );
+ d.resolve();
+ }.bind( this ), 1 );
+ }.bind( this ) );
+ return d;
+ },
+ /**
* Render the module into a given target
*/
render: function () {
+ this.setupWebFonts().then( function () {
+ this.renderAfterDependenciesLoaded();
+ }.bind( this ) );
+ },
+ /**
+ * Render the module into a given target after all
+ */
+ renderAfterDependenciesLoaded: function () {
this.$parent.$settingsPanel.empty();
- this.$webfonts = $( document.body ).data( 'webfonts' );
this.$parent.$settingsPanel.append( this.$template );
this.prepareLanguages();
this.prepareUIFonts();
@@ -132,7 +158,6 @@
// might not be.
this.preview( this.uiLanguage );
this.listen();
- this.dirty = false;
},
prepareWebfontsCheckbox: function () {
@@ -182,30 +207,14 @@
new mw.Api().parse( $.i18n( 'ext-uls-display-settings-anon-log-in-cta' ) )
.done( function ( parsedCta ) {
- var deferred = new $.Deferred();
-
- $loginCta.html( parsedCta ); // The parsed CTA is HTML
- $loginCta.find( 'a' ).on( 'click', function ( event ) {
- event.preventDefault();
- // Because browsers navigate away when clicking a link,
- // we are overriding the normal click behavior to allow
- // the event be logged first - currently there is no
- // local queue for events. Since the hook system does not
- // allow returning values, we have this ugly hack
- // for event logging to delay the page loading if event logging
- // is enabled. The promise is passed to the hook, so that
- // if event logging is enabled, in can resole the promise
- // immediately to avoid extra delays.
- deferred.done( function () {
- location.href = event.target.href;
- } );
-
- mw.hook( 'mw.uls.login.click' ).fire( deferred );
-
- // Delay is zero if event logging is not enabled
- setTimeout( function () {
- deferred.resolve();
- }, mw.config.get( 'wgULSEventLogging' ) * 500 );
+ // The parsed CTA is HTML
+ $loginCta.html( parsedCta );
+ $loginCta.find( 'a' ).on( 'click', function () {
+ // If EventLogging is installed and enabled for ULS, give it a
+ // chance to log this event. There is no promise provided and in
+ // most browsers this will use the Beacon API in the background.
+ // In older browsers, this event will likely get lost.
+ mw.hook( 'mw.uls.login.click' );
} );
} );
@@ -293,8 +302,7 @@
$languages.append( $moreLanguagesButton );
// Show the long language list to select a language for display settings
$moreLanguagesButton.uls( {
- left: displaySettings.$parent.left,
- top: displaySettings.$parent.top,
+ onPosition: this.$parent.position.bind( this.$parent ),
onReady: function () {
var $wrap,
uls = this,
@@ -319,8 +327,6 @@
uls.$menu.toggleClass( 'selector-right', displaySettings.$parent.$window.hasClass( 'selector-right' ) );
},
onVisible: function () {
- var $parent;
-
this.$menu.find( '.uls-languagefilter' )
.prop( 'placeholder', $.i18n( 'ext-uls-display-settings-ui-language' ) );
@@ -331,15 +337,6 @@
return;
}
- $parent = $( '#language-settings-dialog' );
-
- // Re-position the element according to the window that called it
- if ( parseInt( $parent.css( 'left' ), 10 ) ) {
- this.$menu.css( 'left', $parent.css( 'left' ) );
- }
- if ( parseInt( $parent.css( 'top' ), 10 ) ) {
- this.$menu.css( 'top', $parent.css( 'top' ) );
- }
// If the ULS is shown in the sidebar,
// add a caret pointing to the icon
// eslint-disable-next-line no-jquery/no-class-state
@@ -528,7 +525,7 @@
*/
markDirty: function () {
this.dirty = true;
- this.$parent.$window.find( 'button.uls-settings-apply' ).prop( 'disabled', false );
+ this.$parent.enableApplyButton();
},
/**
@@ -546,25 +543,18 @@
displaySettings.markDirty();
if ( this.checked ) {
- mw.loader.using( 'ext.uls.webfonts.fonts', function () {
- mw.webfonts.setup();
-
- // Allow the webfonts library to finish loading
- setTimeout( function () {
- displaySettings.$webfonts = $( document.body ).data( 'webfonts' );
-
- mw.webfonts.preferences.enable();
+ displaySettings.setupWebFonts().then( function () {
+ mw.webfonts.preferences.enable();
- displaySettings.prepareContentFonts();
- displaySettings.prepareUIFonts();
+ displaySettings.prepareContentFonts();
+ displaySettings.prepareUIFonts();
- displaySettings.i18n();
- // eslint-disable-next-line no-jquery/no-sizzle
- displaySettings.$webfonts.apply( $uiFontSelector.find( 'option:selected' ) );
- displaySettings.$webfonts.refresh();
+ displaySettings.i18n();
+ // eslint-disable-next-line no-jquery/no-sizzle
+ displaySettings.$webfonts.apply( $uiFontSelector.find( 'option:selected' ) );
+ displaySettings.$webfonts.refresh();
- $fontSelectors.removeClass( 'hide' );
- }, 1 );
+ $fontSelectors.removeClass( 'hide' );
} );
} else {
$fontSelectors.addClass( 'hide' );
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.eventlogger.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.eventlogger.js
deleted file mode 100644
index a5f4a0be..00000000
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.eventlogger.js
+++ /dev/null
@@ -1,228 +0,0 @@
-/*!
- * ULS Event logger
- *
- * See https://meta.wikimedia.org/wiki/Schema:UniversalLanguageSelector
- *
- * @private
- * @since 2013.08
- *
- * Copyright (C) 2012-2013 Alolita Sharma, Amir Aharoni, Arun Ganesh, Brandon Harris,
- * Niklas Laxström, Pau Giner, Santhosh Thottingal, Siebrand Mazeland 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 General Public Licence 2.0 or later
- * @licence MIT License
- */
-
-( function () {
- 'use strict';
-
- /**
- * Try to emit an EventLogging event with schema 'UniversalLanguageSelector'.
- *
- * If EventLogging is not installed, this simply does nothing.
- *
- * @param {Object} event Event action and optional fields
- */
- function log( event ) {
- event = $.extend( {
- version: 1,
- token: mw.user.id(),
- contentLanguage: mw.config.get( 'wgContentLanguage' ),
- interfaceLanguage: mw.config.get( 'wgUserLanguage' )
- }, event );
-
- mw.track( 'event.UniversalLanguageSelector', event );
- }
-
- /**
- * Log language settings open
- *
- * @param {string} context Where it was opened from
- */
- function ulsSettingsOpen( context ) {
- log( {
- action: 'settings-open',
- context: context
- } );
- }
-
- /**
- * Log language revert
- *
- * @param {jQuery.Deferred} deferred
- */
- function ulsLanguageRevert( deferred ) {
- log( { action: 'ui-lang-revert' } );
- deferred.resolve();
- }
-
- /**
- * Log IME disabling
- *
- * @param {string} context Where the setting was changed.
- */
- function disableIME( context ) {
- log( { action: 'ime-disable', context: context } );
- }
-
- /**
- * Log IME enabling
- *
- * @param {string} context Where the setting was changed.
- */
- function enableIME( context ) {
- log( { action: 'ime-enable', context: context } );
- }
-
- /**
- * Log IME change
- *
- * @param {string} inputMethod
- */
- function changeIME( inputMethod ) {
- log( {
- action: 'ime-change',
- inputMethod: inputMethod
- } );
- }
-
- /**
- * Log login link click in display settings.
- *
- * @param {jQuery.Deferred} deferred
- */
- function loginClick( deferred ) {
- log( { action: 'login-click' } );
- deferred.resolve();
- }
-
- /**
- * Log when "More languages" item in IME menu is clicked.
- */
- function imeMoreLanguages() {
- log( {
- action: 'more-languages-access',
- context: 'ime'
- } );
- }
-
- /**
- * Log interface language change
- *
- * @param {string} language language code
- * @param {jQuery.Deferred} deferred
- */
- function interfaceLanguageChange( language, deferred ) {
- var logParams = {
- action: 'language-change',
- context: 'interface',
- interfaceLanguage: language
- };
-
- log( logParams );
- deferred.resolve();
- }
-
- /**
- * More languages in display settings is clicked
- */
- function interfaceMoreLanguages() {
- log( {
- action: 'more-languages-access',
- context: 'interface'
- } );
- }
-
- /**
- * Log font preference changes
- *
- * @param {string} context Either 'interface' or 'content'
- * @param {string} language
- * @param {string} font
- */
- function fontChange( context, language, font ) {
- var logParams = {
- action: 'font-change',
- context: context
- };
-
- if ( context === 'interface' ) {
- logParams.interfaceFont = font;
- // Override in case the user changed the ui language but hasn't applied it yet
- logParams.interfaceLanguage = language;
- } else {
- logParams.contentFont = font;
- }
-
- log( logParams );
- }
-
- /**
- * Log webfonts disabling
- *
- * @param {string} context Where the setting was changed.
- */
- function disableWebfonts( context ) {
- log( { action: 'webfonts-disable', context: context } );
- }
-
- /**
- * Log webfonts enabling
- *
- * @param {string} context Where the setting was changed.
- */
- function enableWebfonts( context ) {
- log( { action: 'webfonts-enable', context: context } );
- }
-
- /**
- * Log search strings which produce no search results.
- *
- * @param {jQuery.event} event The original event
- * @param {Object} data Information about the failed search
- */
- function noSearchResults( event, data ) {
- log( {
- action: 'no-search-results',
- context: data.query,
- ulsPurpose: data.ulsPurpose,
- title: mw.config.get( 'wgPageName' )
- } );
- }
-
- /**
- * Start listening for event logging
- */
- function listen() {
- // Register handlers for event logging triggers
- mw.hook( 'mw.uls.settings.open' ).add( ulsSettingsOpen );
- mw.hook( 'mw.uls.language.revert' ).add( ulsLanguageRevert );
- mw.hook( 'mw.uls.ime.enable' ).add( enableIME );
- mw.hook( 'mw.uls.ime.disable' ).add( disableIME );
- mw.hook( 'mw.uls.ime.change' ).add( changeIME );
- mw.hook( 'mw.uls.login.click' ).add( loginClick );
- mw.hook( 'mw.uls.ime.morelanguages' ).add( imeMoreLanguages );
- mw.hook( 'mw.uls.interface.morelanguages' ).add( interfaceMoreLanguages );
- mw.hook( 'mw.uls.interface.language.change' ).add( interfaceLanguageChange );
- mw.hook( 'mw.uls.font.change' ).add( fontChange );
- mw.hook( 'mw.uls.webfonts.enable' ).add( enableWebfonts );
- mw.hook( 'mw.uls.webfonts.disable' ).add( disableWebfonts );
-
- $( document.body ).on(
- 'noresults.uls',
- '.uls-menu .uls-languagefilter',
- noSearchResults
- );
- }
-
- listen();
-}() );
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.i18n.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.i18n.js
index f1228e60..4123fd64 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.i18n.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.i18n.js
@@ -31,7 +31,7 @@
$.i18n.fallbacks = {};
/**
- * Load localization messags for a locale to the jquery.i18n
+ * Load localization messages for a locale to the jquery.i18n
* messagestore.
* Also called by RL module ResourceLoaderULSJsonMessageModule
*
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.ime.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.ime.js
index e0f50283..050d92bd 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.ime.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.ime.js
@@ -20,13 +20,15 @@
( function () {
'use strict';
- var mwImeRulesPath, inputSelector, inputPreferences, ulsIMEPreferences, customHelpLink;
+ var mwImeRulesPath, inputSelector, inputPreferences, ulsIMEPreferences, customHelpLink,
+ getULSPreferences = require( 'ext.uls.preferences' ),
+ languageSettingsModules = [ 'ext.uls.displaysettings' ];
mwImeRulesPath = mw.config.get( 'wgExtensionAssetsPath' ) +
'/UniversalLanguageSelector/lib/jquery.ime/';
inputSelector = 'input:not([type]), input[type=text], input[type=search], textarea, [contenteditable]';
- inputPreferences = mw.uls.preferences();
+ inputPreferences = getULSPreferences();
mw.ime = mw.ime || {};
@@ -73,7 +75,7 @@
// we don't want to save isDirty field.
this.registry.isDirty = undefined;
// get updated copy of preferences
- inputPreferences = mw.uls.preferences();
+ inputPreferences = getULSPreferences();
inputPreferences.set( 'ime', this.registry );
inputPreferences.save( callback );
// reset the dirty bit
@@ -82,6 +84,19 @@
load: function () {
this.registry = inputPreferences.get( 'ime' ) || this.registry;
+ // Some validation in case the stored preferences are corrupt
+ if ( typeof this.registry.language !== 'string' ) {
+ this.registry.language = null;
+ }
+ if ( !Array.isArray( this.registry.previousLanguages ) ) {
+ this.registry.previousLanguages = [];
+ }
+ if ( !Array.isArray( this.registry.previousInputMethods ) ) {
+ this.registry.previousInputMethods = [];
+ }
+ if ( !$.isPlainObject( this.registry.imes ) ) {
+ this.registry.imes = {};
+ }
},
disable: function () {
@@ -145,7 +160,7 @@
// Apparently we depend on some styles which are loaded with
// these modules. This needs refactoring.
- mw.loader.using( mw.uls.languageSettingsModules, function () {
+ mw.loader.using( languageSettingsModules, function () {
$moreSettingsLink.languagesettings( {
defaultModule: 'input',
onClose: function () {
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.inputsettings.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.inputsettings.js
index 099e2a1a..b4f69726 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.inputsettings.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.inputsettings.js
@@ -64,7 +64,6 @@
this.$template = $( template );
this.uiLanguage = this.getInterfaceLanguage();
this.contentLanguage = this.getContentLanguage();
- this.$imes = null;
this.$parent = $parent;
// ime system is lazy loaded, make sure it is initialized
mw.ime.init();
@@ -84,7 +83,6 @@
this.dirty = false;
this.$parent.$settingsPanel.empty();
- this.$imes = $( document.body ).data( 'ime' );
this.$parent.$settingsPanel.append( this.$template );
$enabledOnly = this.$template.find( '.enabled-only' );
if ( $.ime.preferences.isEnabled() ) {
@@ -111,7 +109,7 @@
*/
markDirty: function () {
this.dirty = true;
- this.$parent.$window.find( 'button.uls-settings-apply' ).prop( 'disabled', false );
+ this.$parent.enableApplyButton();
},
prepareInputmethods: function ( language ) {
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.interface.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.interface.js
index 9004b4d5..1518afee 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.interface.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.interface.js
@@ -19,6 +19,8 @@
( function () {
'use strict';
+ var languageSettingsModules = [ 'ext.uls.displaysettings' ],
+ launchULS = require( './ext.uls.launch.js' );
/**
* Construct the display settings link
@@ -51,6 +53,20 @@
}
/**
+ * For Vector: Check whether the classic Vector or "new" vector ([[mw:Desktop_improvements]]) is enabled based
+ * on the contents of the page.
+ * For other skins, check if ULSDisplayInputAndDisplaySettingsInInterlanguage contains the current skin.
+ *
+ * @return {bool}
+ */
+ function isUsingStandaloneLanguageButton() {
+ var skin = mw.config.get( 'skin' );
+ // special handling for Vector. This can be removed when Vector is split into 2 separate skins.
+ return skin === 'vector' ? $( '#p-lang-btn' ).length > 0 :
+ mw.config.get( 'wgULSDisplaySettingsInInterlanguage' );
+ }
+
+ /**
* Add display settings link to the settings bar in ULS
*
* @param {Object} uls The ULS object
@@ -59,27 +75,14 @@
var $displaySettings = displaySettings();
uls.$menu.find( '#uls-settings-block' ).append( $displaySettings );
-
// Initialize the trigger
$displaySettings.one( 'click', function () {
- var displaySettingsOptions = {
- defaultModule: 'display'
- },
- ulsPosition = mw.config.get( 'wgULSPosition' ),
- anonMode = ( mw.user.isAnon() &&
- !mw.config.get( 'wgULSAnonCanChangeLanguage' ) );
-
- // If the ULS trigger is shown in the top personal menu,
- // closing the display settings must show the main ULS
- // languages list, unless we are in anon mode and thus
- // cannot show the language list
- if ( ulsPosition === 'personal' && !anonMode ) {
- displaySettingsOptions.onClose = function () {
- uls.show();
- };
- }
- $.extend( displaySettingsOptions, uls.position() );
- $displaySettings.languagesettings( displaySettingsOptions ).trigger( 'click' );
+ $displaySettings.languagesettings( {
+ defaultModule: 'display',
+ onClose: uls.show.bind( uls ),
+ onPosition: uls.position.bind( uls ),
+ onVisible: uls.hide.bind( uls )
+ } ).trigger( 'click' );
} );
}
@@ -92,20 +95,14 @@
var $inputSettings = inputSettings();
uls.$menu.find( '#uls-settings-block' ).append( $inputSettings );
-
// Initialize the trigger
$inputSettings.one( 'click', function () {
- var position = uls.position();
-
$inputSettings.languagesettings( {
defaultModule: 'input',
- onClose: function () {
- uls.show();
- },
- top: position.top,
- left: position.left
+ onClose: uls.show.bind( uls ),
+ onPosition: uls.position.bind( uls ),
+ onVisible: uls.hide.bind( uls )
} ).trigger( 'click' );
-
} );
}
@@ -125,9 +122,20 @@
ulsPosition = mw.config.get( 'wgULSPosition' );
$ulsTrigger = ( ulsPosition === 'interlanguage' ) ?
- $( '.uls-settings-trigger' ) :
+ $( '.uls-settings-trigger, .mw-interlanguage-selector' ) :
$( '.uls-trigger' );
+ // Fallback if no entry point is present
+ if ( !$ulsTrigger.length ) {
+ $ulsTrigger = $( '#pt-preferences' );
+ }
+
+ // Skip tooltip if there is no element to attach the tooltip to.
+ // It will cause errors otherwise.
+ if ( !$ulsTrigger.length ) {
+ return;
+ }
+
function hideTipsy() {
ulsPopup.toggle( false );
}
@@ -143,28 +151,13 @@
clearTimeout( tipsyTimer );
} ).on( 'mouseout', function () {
tipsyTimer = setTimeout( hideTipsy, timeout );
- } );
-
- // hide the tooltip when clicked on it
- $( '.uls-tipsy' ).on( 'click', hideTipsy );
+ } ).on( 'click', hideTipsy );
tipsyTimer = setTimeout( hideTipsy, timeout );
}
- // remove any existing popups
- if ( ulsPopup ) {
- ulsPopup.$element.remove();
- }
if ( ulsPosition === 'interlanguage' ) {
- if ( $ulsTrigger.offset().left > $( window ).width() / 2 ) {
- ulsPopupPosition = 'before';
- } else {
- ulsPopupPosition = 'after';
- }
- // Reverse for RTL
- if ( $( document.documentElement ).prop( 'dir' ) === 'rtl' ) {
- ulsPopupPosition = ( ulsPopupPosition === 'after' ) ? 'before' : 'after';
- }
+ ulsPopupPosition = 'after';
} else {
ulsPopupPosition = 'below';
}
@@ -191,19 +184,14 @@
dir: 'auto'
} )
.on( 'click', function ( event ) {
- var deferred = $.Deferred();
-
event.preventDefault();
- deferred.done( function () {
- mw.uls.changeLanguage( event.target.lang );
- } );
- mw.hook( 'mw.uls.language.revert' ).fire( deferred );
+ // Track if event logging is enabled
+ mw.hook( 'mw.uls.language.revert' ).fire();
- // Delay is zero if event logging is not enabled
- setTimeout( function () {
- deferred.resolve();
- }, mw.config.get( 'wgULSEventLogging' ) * 500 );
+ mw.loader.using( [ 'ext.uls.common' ] ).then( function () {
+ mw.uls.changeLanguage( event.target.lang );
+ } );
} );
if ( mw.storage.get( 'uls-gp' ) === '1' ) {
@@ -236,9 +224,23 @@
} );
}
+ /**
+ * Adds display and input settings to the ULS dialog after loading their code.
+ *
+ * @param {ULS} uls instance
+ */
+ function loadDisplayAndInputSettings( uls ) {
+ return mw.loader.using( languageSettingsModules ).then( function () {
+ addDisplaySettings( uls );
+ addInputSettings( uls );
+ } );
+ }
+
function initInterface() {
var $pLang,
clickHandler,
+ // T273928: No change to the heading should be made in modern Vector when the language button is present
+ isButton = isUsingStandaloneLanguageButton(),
$ulsTrigger = $( '.uls-trigger' ),
anonMode = ( mw.user.isAnon() &&
!mw.config.get( 'wgULSAnonCanChangeLanguage' ) ),
@@ -246,7 +248,7 @@
if ( ulsPosition === 'interlanguage' ) {
// TODO: Refactor this block
- // The interlanguage links section
+ // The interlanguage links section.
$pLang = $( '#p-lang' );
// Add an element near the interlanguage links header
$ulsTrigger = $( '<button>' )
@@ -256,7 +258,7 @@
// Take care of any other elements with this class.
$ulsTrigger = $( '.uls-settings-trigger' );
- if ( !$pLang.find( 'div ul' ).children().length ) {
+ if ( !$pLang.find( 'div ul' ).children().length && isButton ) {
// Replace the title of the interlanguage links area
// if there are no interlanguage links
$pLang.find( 'h3' )
@@ -282,37 +284,38 @@
// Initialize the Language settings window
languageSettingsOptions = {
defaultModule: 'display',
- onVisible: function () {
- var caretRadius,
+ onPosition: function () {
+ var caretRadius, top, left,
ulsTriggerHeight = this.$element.height(),
ulsTriggerWidth = this.$element[ 0 ].offsetWidth,
ulsTriggerOffset = this.$element.offset();
- this.$window.addClass( 'callout' );
-
// Same as border width in mixins.less, or near enough
caretRadius = 12;
if ( ulsTriggerOffset.left > $( window ).width() / 2 ) {
- this.left = ulsTriggerOffset.left - this.$window.width() - caretRadius;
+ left = ulsTriggerOffset.left - this.$window.width() - caretRadius;
this.$window.removeClass( 'selector-left' ).addClass( 'selector-right' );
} else {
- this.left = ulsTriggerOffset.left + ulsTriggerWidth + caretRadius;
+ left = ulsTriggerOffset.left + ulsTriggerWidth + caretRadius;
this.$window.removeClass( 'selector-right' ).addClass( 'selector-left' );
}
// The top of the dialog is aligned in relation to
// the middle of the trigger, so that middle of the
// caret aligns with it. 16 is trigger icon height in pixels
- this.top = ulsTriggerOffset.top +
+ top = ulsTriggerOffset.top +
( ulsTriggerHeight / 2 ) -
( caretRadius + 16 );
- this.position();
+ return { top: top, left: left };
+ },
+ onVisible: function () {
+ this.$window.addClass( 'callout' );
}
};
- mw.loader.using( mw.uls.languageSettingsModules, function () {
+ mw.loader.using( languageSettingsModules, function () {
$ulsTrigger.languagesettings( languageSettingsOptions ).trigger( 'click' );
} );
@@ -329,7 +332,7 @@
mw.hook( 'mw.uls.settings.open' ).fire( eventParams && eventParams.source || 'personal' );
}
} else {
- mw.loader.using( mw.uls.languageSettingsModules, function () {
+ mw.loader.using( languageSettingsModules, function () {
$ulsTrigger.languagesettings();
$ulsTrigger.trigger( 'click', eventParams );
@@ -353,11 +356,7 @@
return mw.uls.getFrequentLanguageList();
},
onReady: function () {
- var uls = this;
- mw.loader.using( mw.uls.languageSettingsModules, function () {
- addDisplaySettings( uls );
- addInputSettings( uls );
- } );
+ loadDisplayAndInputSettings( this );
},
onSelect: function ( language ) {
mw.uls.changeLanguage( language );
@@ -427,7 +426,9 @@
mw.storage.set( 'uls-previous-language-code', currentLanguage );
mw.storage.set( 'uls-previous-language-autonym', currentAutonym );
// Store this language in a list of frequently used languages
- mw.uls.addPreviousLanguage( currentLanguage );
+ mw.loader.using( [ 'ext.uls.common' ] ).then( function () {
+ mw.uls.addPreviousLanguage( currentLanguage );
+ } );
}
}
@@ -444,10 +445,68 @@
} );
}
+ /**
+ * Load and open ULS for content language selection.
+ *
+ * This dialog is primarily for selecting the language of the content, but may also provide
+ * access to display and input settings if isUsingStandaloneLanguageButton() returns true.
+ *
+ * @param {jQuery.Event} ev
+ */
+ function loadContentLanguageSelector( ev ) {
+ var $target = $( ev.currentTarget );
+ ev.preventDefault();
+
+ mw.loader.using( 'ext.uls.mediawiki' ).then( function () {
+ var parent, languageNodes, standalone, uls;
+
+ parent = document.querySelectorAll( '.mw-portlet-lang, #p-lang' )[ 0 ];
+ languageNodes = parent ? parent.querySelectorAll( '.interlanguage-link-target' ) : [];
+ standalone = isUsingStandaloneLanguageButton();
+
+ // Setup click handler for ULS
+ launchULS(
+ $target,
+ mw.uls.getInterlanguageListFromNodes( languageNodes ),
+ // Using this as heuristic for now. May need to reconsider later. Enables
+ // behavior specific to compact language links.
+ !standalone
+ );
+
+ // Trigger the click handler to open ULS once ready
+ if ( standalone ) {
+ // Provide access to display and input settings if this entry point is the single point
+ // of access to all language settings.
+ uls = $target.data( 'uls' );
+ loadDisplayAndInputSettings( uls ).always( function () {
+ $target.trigger( 'click' );
+ } );
+ } else {
+ $target.trigger( 'click' );
+ }
+ } );
+ }
+
+ /** Setup lazy-loading for content language selector */
+ function initContentLanguageSelectorClickHandler() {
+ // FIXME: In Timeless ULS is embedded in a menu which stops event propagation
+ if ( $( '.sidebar-inner' ).length ) {
+ $( '.sidebar-inner #p-lang' )
+ .one( 'click', '.mw-interlanguage-selector', loadContentLanguageSelector );
+ } else {
+ // This button may be created by the new Vector skin, or ext.uls.compactlinks module
+ // if there are many languages. Warning: Both this module and ext.uls.compactlinks
+ // module may run simultaneously. Using event delegation to avoid race conditions where
+ // the trigger may be created after this code.
+ $( document ).one( 'click', '.mw-interlanguage-selector', loadContentLanguageSelector );
+ }
+ }
+
function init() {
initInterface();
initTooltip();
initIme();
+ initContentLanguageSelectorClickHandler();
}
// Early execute of init
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.languagesettings.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.languagesettings.js
index 467f361b..064dbff9 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.languagesettings.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.languagesettings.js
@@ -60,6 +60,7 @@
this.top = this.options.top;
this.modules = {};
this.$settingsPanel = this.$window.find( '#languagesettings-settings-panel' );
+ this.$applyButton = this.$window.find( '.uls-settings-apply' );
this.init();
this.listen();
}
@@ -78,7 +79,7 @@
this.$window.find( '#languagesettings-close, button.uls-settings-cancel' )
.on( 'click', mw.hook( 'mw.uls.settings.cancel' ).fire.bind( this ) );
- this.$window.find( 'button.uls-settings-apply' )
+ this.$applyButton
.on( 'click', mw.hook( 'mw.uls.settings.apply' ).fire.bind( this ) );
// Hide the window when clicked outside
$( document.documentElement ).on( 'click', this.hide.bind( this ) );
@@ -173,12 +174,16 @@
},
position: function () {
+ if ( this.options.onPosition ) {
+ return this.options.onPosition.call( this );
+ }
+
this.top = this.top || this.$element.offset().top + this.$element.outerHeight();
this.left = this.left || '25%';
- this.$window.css( {
+ return {
top: this.top,
left: this.left
- } );
+ };
},
i18n: function () {
@@ -186,7 +191,7 @@
},
show: function () {
- this.position();
+ this.$window.css( this.position() );
if ( !this.initialized ) {
this.render();
@@ -240,16 +245,14 @@
* false to unset the busy mode.
*/
setBusy: function ( busy ) {
- var $applyButton = this.$window.find( 'button.uls-settings-apply' );
-
if ( busy ) {
this.$window.addClass( 'waiting' );
- $applyButton
+ this.$applyButton
.text( $.i18n( 'ext-uls-language-settings-applying' ) )
.prop( 'disabled', true );
} else {
this.$window.removeClass( 'waiting' );
- $applyButton.text( $.i18n( 'ext-uls-language-settings-apply' ) );
+ this.$applyButton.text( $.i18n( 'ext-uls-language-settings-apply' ) );
}
},
@@ -282,8 +285,12 @@
}
},
+ enableApplyButton: function () {
+ this.$applyButton.prop( 'disabled', false );
+ },
+
disableApplyButton: function () {
- this.$window.find( 'button.uls-settings-apply' ).prop( 'disabled', true );
+ this.$applyButton.prop( 'disabled', true );
}
};
@@ -308,9 +315,10 @@
template: windowTemplate,
defaultModule: false, // Name of the default module
onClose: null, // An onClose event handler.
- top: null, // Top position of this window
- left: null, // Left position of this window
- onVisible: null // A callback that runs after the ULS panel becomes visible
+ top: null, // DEPRECATED: Top position of this window
+ left: null, // DEPRECATED: Left position of this window
+ onVisible: null, // A callback that runs after the ULS panel becomes visible
+ onPosition: null // A callback that allows positioning the dialog
};
$.fn.languagesettings.Constructor = LanguageSettings;
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.launch.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.launch.js
new file mode 100644
index 00000000..e2bfe9de
--- /dev/null
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.launch.js
@@ -0,0 +1,175 @@
+/**
+ * Setup code for content language selector dialog
+ */
+
+/* eslint-disable no-implicit-globals */
+var commonInterlanguageList = null;
+
+/**
+ * @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
+ * @param {boolean} forCLS Whether to enable compact language links specific behavior
+ */
+function launchULS( $trigger, languagesObject, forCLS ) {
+ var ulsConfig = {
+ /**
+ * 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,Shift,Command} activation on a link
+ if (
+ event.target instanceof HTMLAnchorElement &&
+ ( event.metaKey || event.shiftKey || event.ctrlKey )
+ ) {
+ return;
+ }
+ location.href = languagesObject[ language ].href;
+ },
+ onPosition: function () {
+ // Override the default positioning. See https://phabricator.wikimedia.org/T276248
+ // Default positioning of jquery.uls is middle of the screen under the trigger.
+ // This code aligns it under the trigger and to the trigger edge depending on which
+ // side of the page the trigger is - should work automatically for both LTR and RTL.
+ var offset, height, width;
+ // These are for the trigger.
+ offset = $trigger.offset();
+ width = $trigger.outerWidth();
+ height = $trigger.outerHeight();
+
+ if ( offset.left + ( width / 2 ) > $( window ).width() / 2 ) {
+ // Midpoint of the trigger is on the right side of the viewport.
+ return {
+ // Right edge of the dialog aligns with the right edge of the trigger.
+ right: $( window ).width() - ( offset.left + width ),
+ top: offset.top + height
+ };
+ } else {
+ // Midpoint of the trigger is on the left side of the viewport.
+ return {
+ // Left edge of the dialog aligns with the left edge of the trigger.
+ left: offset.left,
+ top: offset.top + height
+ };
+ }
+ },
+ onVisible: function () {
+ $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;
+ }
+ };
+
+ if ( forCLS ) {
+ // Styles for these classes are defined in the ext.uls.compactlinks module
+ ulsConfig.onReady = function () {
+ // This class enables the caret
+ this.$menu.addClass( 'interlanguage-uls-menu' );
+ };
+ ulsConfig.onPosition = function () {
+ // Compact language links specific positioning with a caret
+ var top, left, 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;
+
+ // selector-{left,right} control which side the caret appears.
+ // It needs to match the positioning of the dialog.
+ if ( offset.left > $( window ).width() / 2 ) {
+ left = offset.left - this.$menu.outerWidth() - triangleWidth;
+ this.$menu.removeClass( 'selector-left' ).addClass( 'selector-right' );
+ } else {
+ left = offset.left + width + triangleWidth;
+ this.$menu.removeClass( 'selector-right' ).addClass( 'selector-left' );
+ }
+ // Offset from the middle of the trigger
+ top = offset.top + ( height / 2 ) - 27;
+
+ return {
+ left: left,
+ top: top
+ };
+ };
+ }
+
+ // Attach ULS behavior to the trigger. ULS will be shown only once it is clicked.
+ $trigger.uls( ulsConfig );
+}
+
+module.exports = launchULS;
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.preferences.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.preferences.js
index 0afc40b2..41f08108 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.preferences.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.preferences.js
@@ -21,51 +21,11 @@
( function () {
'use strict';
- var ULSPreferences;
-
- /**
- * Wrapper for localStorage, falls back to cookie
- * when localStorage not supported by browser.
- *
- * @return {Object}
- */
- function preferenceStore() {
-
- // If value is detected, set new or modify store
- return {
- /*
- * Set the value to the given key
- * @param {string} key
- * @param {Object} value value to be set
- */
- set: function ( key, value ) {
- // Convert object values to JSON
- if ( typeof value === 'object' ) {
- value = JSON.stringify( value );
- }
-
- try {
- localStorage.setItem( key, value );
- } catch ( e ) {}
- },
- /*
- * Returns the value of the given key
- * @param {string} key
- * @return {Object} value of the key
- */
- get: function ( key ) {
- var data;
-
- try {
- data = JSON.parse( localStorage.getItem( key ) );
- } catch ( e ) {}
-
- return data;
- }
- };
- }
+ var ULSPreferences, instance;
ULSPreferences = function () {
+ // This violates coding conventions for localstorage:
+ // https://www.mediawiki.org/wiki/Manual:Coding_conventions/JavaScript#Keys
this.preferenceName = 'uls-preferences';
this.username = mw.user.getName();
this.isAnon = mw.user.isAnon();
@@ -74,28 +34,19 @@
};
ULSPreferences.prototype = {
- /**
- * Initialize
- */
init: function () {
- var options;
-
if ( this.isAnon ) {
- this.preferences = preferenceStore().get( this.preferenceName );
+ this.preferences = mw.storage.getObject( this.preferenceName );
} else {
- options = mw.user.options.get( this.preferenceName );
- if ( !options ) {
- options = '{}';
- }
- // Try to parse JSON
try {
- this.preferences = JSON.parse( options );
+ this.preferences = JSON.parse( mw.user.options.get( this.preferenceName ) );
} catch ( e ) {
- this.preferences = {};
}
}
- this.preferences = this.preferences || {};
+ if ( !$.isPlainObject( this.preferences ) ) {
+ this.preferences = {};
+ }
},
/**
@@ -124,35 +75,29 @@
* @param {Function} callback
*/
save: function ( callback ) {
- var ulsPreferences = this;
+ var self = this;
callback = callback || function () {};
if ( this.isAnon ) {
// Anonymous user. Save preferences in local storage
- preferenceStore().set( this.preferenceName, this.preferences );
+ mw.storage.setObject( this.preferenceName, this.preferences );
callback.call( this, true );
} else {
// Logged in user. Use MW APIs to change preferences
new mw.Api().saveOption(
- ulsPreferences.preferenceName,
- JSON.stringify( ulsPreferences.preferences )
+ this.preferenceName,
+ JSON.stringify( this.preferences )
).done( function () {
- callback.call( this, true );
+ callback.call( self, true );
} ).fail( function () {
- callback.call( this, false );
+ callback.call( self, false );
} );
}
}
};
- mw.uls = mw.uls || {};
- mw.uls.preferences = function () {
- var data = $( document.body ).data( 'preferences' );
-
- if ( !data ) {
- $( document.body ).data( 'preferences', ( data = new ULSPreferences() ) );
- }
- return data;
+ module.exports = function () {
+ instance = instance || new ULSPreferences();
+ return instance;
};
-
}() );
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.setlang.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.setlang.js
index c26a01ee..3af7090a 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.setlang.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.setlang.js
@@ -1,5 +1,5 @@
/*!
- * Loaded when setlang query paramter is set on the page.
+ * Loaded when setlang query parameter is set on the page.
*
* @private
* @since 2020.01
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.js
index cf942772..9d7d7372 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.js
@@ -20,10 +20,10 @@
( function () {
'use strict';
- var ulsPreferences;
+ var getULSPreferences = require( 'ext.uls.preferences' ),
+ ulsPreferences = getULSPreferences();
mw.webfonts = mw.webfonts || {};
- ulsPreferences = mw.uls.preferences();
mw.webfonts.preferences = {
registry: {
fonts: {},
@@ -52,7 +52,7 @@
save: function ( callback ) {
// get updated copy of preferences
- ulsPreferences = mw.uls.preferences();
+ ulsPreferences = getULSPreferences();
ulsPreferences.set( 'webfonts', this.registry );
ulsPreferences.save( callback );
},
@@ -135,7 +135,10 @@
mw.webfonts.preferences.load();
if ( mw.webfonts.preferences.isEnabled() ) {
- mw.loader.using( 'ext.uls.webfonts.fonts', mw.webfonts.setup );
+ // Queue to next idle period to optimize loading.
+ mw.requestIdleCallback( function () {
+ mw.loader.using( 'ext.uls.webfonts.fonts' ).then( mw.webfonts.setup );
+ } );
}
} );
diff --git a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.repository.js b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.repository.js
index a3bab6ed..2ea5aafe 100644
--- a/MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.repository.js
+++ b/MLEB/UniversalLanguageSelector/resources/js/ext.uls.webfonts.repository.js
@@ -76,9 +76,9 @@
],
"ckb": [
"system",
- "Amiri",
"Lateef",
- "Scheherazade"
+ "Scheherazade",
+ "Amiri"
],
"cr": [
"OskiEast"
@@ -125,6 +125,10 @@
"system",
"OpenDyslexic"
],
+ "eu": [
+ "system",
+ "GochiHand"
+ ],
"fa": [
"system",
"Iranian Sans",
@@ -185,6 +189,10 @@
"Miriam CLM",
"Taamey Frank CLM"
],
+ "hoc": [
+ "system",
+ "Boyo Gagrai"
+ ],
"hu": [
"system",
"OpenDyslexic"
@@ -470,6 +478,9 @@
"woff": "Artaxerxes/Artaxerxes.woff?c1ed7",
"woff2": "Artaxerxes/Artaxerxes.woff2?7a96e"
},
+ "Boyo Gagrai": {
+ "woff2": "BoyoGagrai/BoyoGagrai.woff2?d1060"
+ },
"CharisSIL": {
"woff": "CharisSIL/CharisSIL-R.woff?3a622",
"woff2": "CharisSIL/CharisSIL-R.woff2?b2a18"
@@ -532,6 +543,10 @@
"woff": "GentiumPlus/GentiumPlus-I.woff?ab550",
"woff2": "GentiumPlus/GentiumPlus-I.woff2?35b11"
},
+ "GochiHand": {
+ "woff": "GochiHand/GochiHand-Regular.woff?310cc",
+ "woff2": "GochiHand/GochiHand-Regular.woff2?b6160"
+ },
"Hanuman": {
"woff": "Hanuman/Hanuman.woff?d5078",
"woff2": "Hanuman/Hanuman.woff2?0107a",
@@ -569,8 +584,8 @@
"woff2": "Jomolhari/Jomolhari.woff2?f448a"
},
"Junicode": {
- "woff": "Junicode/Junicode.woff?dc7ef",
- "woff2": "Junicode/Junicode.woff2?7e6d6",
+ "woff": "Junicode/Junicode.woff?19f4e",
+ "woff2": "Junicode/Junicode.woff2?1a244",
"variants": {
"bold": "Junicode Bold",
"bolditalic": "Junicode Bold Italic",
@@ -579,23 +594,23 @@
},
"Junicode Bold": {
"fontweight": "bold",
- "woff": "Junicode/Junicode-Bold.woff?f7ef4",
- "woff2": "Junicode/Junicode-Bold.woff2?d5d04"
+ "woff": "Junicode/Junicode-Bold.woff?c77c1",
+ "woff2": "Junicode/Junicode-Bold.woff2?94fed"
},
"Junicode Bold Italic": {
"fontweight": "bold",
"fontstyle": "italic",
- "woff": "Junicode/Junicode-BoldItalic.woff?3cec9",
- "woff2": "Junicode/Junicode-BoldItalic.woff2?80351"
+ "woff": "Junicode/Junicode-BoldItalic.woff?23d9c",
+ "woff2": "Junicode/Junicode-BoldItalic.woff2?4f1cd"
},
"Junicode Italic": {
"fontstyle": "italic",
- "woff": "Junicode/Junicode-Italic.woff?c458b",
- "woff2": "Junicode/Junicode-Italic.woff2?3fe39"
+ "woff": "Junicode/Junicode-Italic.woff?66b80",
+ "woff2": "Junicode/Junicode-Italic.woff2?b2597"
},
"Kadiri": {
- "woff": "Kadiri/Kadiri.woff?1b711",
- "woff2": "Kadiri/Kadiri.woff2?a266a"
+ "woff": "Kadiri/Kadiri.woff?98297",
+ "woff2": "Kadiri/Kadiri.woff2?0cfa1"
},
"KhmerOS": {
"woff": "KhmerOS/KhmerOS.woff?2ef9e",
@@ -765,8 +780,8 @@
"woff2": "Ponomar/PonomarUnicode.woff2?ea5c5"
},
"Pustaka Bali": {
- "woff": "PustakaBali/PustakaBali.woff?cb41e",
- "woff2": "PustakaBali/PustakaBali.woff2?743c0"
+ "woff": "PustakaBali/PustakaBali.woff?7c072",
+ "woff2": "PustakaBali/PustakaBali.woff2?6b142"
},
"RailwaySans": {
"woff": "RailwaySans/RailwaySans.woff?fda9a",
@@ -817,8 +832,8 @@
"woff2": "UnifrakturMaguntia/UnifrakturMaguntia.woff2?23272"
},
"Vimala": {
- "woff": "Vimala/Vimala.woff?e387b",
- "woff2": "Vimala/Vimala.woff2?70690"
+ "woff": "Vimala/Vimala.woff?f75ba",
+ "woff2": "Vimala/Vimala.woff2?a3b10"
},
"lklug": {
"woff": "lklug/lklug.woff?57de7",