diff options
Diffstat (limited to 'MLEB/UniversalLanguageSelector/resources/js')
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", |