{"version":3,"sources":["webpack://mfModules.[name]/./src/mobile.languages.structured/LanguageSearcher.js","webpack://mfModules.[name]/./src/mobile.languages.structured/mobile.languages.structured.js","webpack://mfModules.[name]/./src/mobile.languages.structured/rtlLanguages.js","webpack://mfModules.[name]/./src/mobile.languages.structured/util.js"],"names":["View","require","util","langUtil","LanguageSearcher","props","languages","getStructuredLanguages","variants","getFrequentlyUsedLanguages","showSuggestedLanguages","deviceLanguage","call","this","extend","className","events","onBannerClick","inputPlaceholder","mw","msg","allLanguagesHeader","toLocaleUpperCase","suggestedLanguagesHeader","noResultsFoundHeader","noResultsFoundMessage","allLanguages","all","allLanguagesCount","length","suggestedLanguages","suggested","suggestedLanguagesCount","showSuggestedLanguagesHeader","onOpen","searcher","setTimeout","mfExtend","template","postRender","$siteLinksList","$el","find","$languageItems","$subheaders","$emptyResultsSection","addBanner","bannerHTML","options","render","onLinkClick","ev","lang","currentTarget","attr","hook","fire","saveLanguageUsageCount","onSearchInput","filterLanguages","target","val","toLowerCase","searchQuery","filteredList","forEach","language","langname","autonym","indexOf","push","variant","addClass","join","removeClass","get","module","exports","m","define","log","mfUtils","rtlLanguages","getDir","dir","frequentlyUsedLanguages","hasOwn","Object","prototype","hasOwnProperty","maxFrequency","minFrequency","missingDir","self","addLangDir","parentLanguage","index","deviceLanguagesWithVariants","slice","getDeviceLanguageOrParent","keys","frequency","map","sort","a","b","toLocaleLowerCase","warn","languageMap","storage","JSON","parse","saveFrequentlyUsedLanguages","set","stringify","languageCode","count"],"mappings":"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","file":"mobile.languages.structured.js","sourcesContent":["const\n\tView = require( '../mobile.startup/View' ),\n\tutil = require( '../mobile.startup/util' ),\n\tlangUtil = require( './util' ),\n\tmfExtend = require( '../mobile.startup/mfExtend' );\n\n/**\n * Overlay displaying a structured list of languages for a page\n *\n * @class LanguageSearcher\n * @extends View\n *\n * @param {Object} props Configuration options\n * @param {Object[]} props.languages list of language objects as returned by the API\n * @param {Array|boolean} props.variants language variant objects\n *  or false if no variants exist\n * @param {boolean} props.showSuggestedLanguages If the suggested languages\n *  section should be rendered.\n * @param {string} [props.deviceLanguage] the device's primary language\n * @param {Function} [props.onOpen] callback that fires on opening the searcher\n * @param {Function} [props.onBannerClick] callback that fires when banner is clicked\n */\nfunction LanguageSearcher( props ) {\n\t/**\n\t * @prop {StructuredLanguages} languages` JSDoc.\n\t */\n\tconst languages = langUtil.getStructuredLanguages(\n\t\tprops.languages,\n\t\tprops.variants,\n\t\tlangUtil.getFrequentlyUsedLanguages(),\n\t\tprops.showSuggestedLanguages,\n\t\tprops.deviceLanguage\n\t);\n\n\tView.call(\n\t\tthis,\n\t\tutil.extend(\n\t\t\t{\n\t\t\t\tclassName: 'language-searcher',\n\t\t\t\tevents: {\n\t\t\t\t\t'click .language-search-banner': props.onBannerClick,\n\t\t\t\t\t'click a': 'onLinkClick',\n\t\t\t\t\t'input .search': 'onSearchInput'\n\t\t\t\t},\n\t\t\t\t// the rest are template properties\n\t\t\t\tinputPlaceholder: mw.msg( 'mobile-frontend-languages-structured-overlay-search-input-placeholder' ),\n\t\t\t\t// we can't rely on CSS only to uppercase the headings. See https://stackoverflow.com/questions/3777443/css-text-transform-not-working-properly-for-turkish-characters\n\t\t\t\tallLanguagesHeader: mw.msg( 'mobile-frontend-languages-structured-overlay-all-languages-header' ).toLocaleUpperCase(),\n\t\t\t\tsuggestedLanguagesHeader: mw.msg( 'mobile-frontend-languages-structured-overlay-suggested-languages-header' ).toLocaleUpperCase(),\n\t\t\t\tnoResultsFoundHeader: mw.msg( 'mobile-frontend-languages-structured-overlay-no-results' ),\n\t\t\t\tnoResultsFoundMessage: mw.msg( 'mobile-frontend-languages-structured-overlay-no-results-body' ),\n\t\t\t\tallLanguages: languages.all,\n\t\t\t\tallLanguagesCount: languages.all.length,\n\t\t\t\tsuggestedLanguages: languages.suggested,\n\t\t\t\tsuggestedLanguagesCount: languages.suggested.length,\n\t\t\t\tshowSuggestedLanguagesHeader: languages.suggested.length > 0\n\t\t\t},\n\t\t\tprops\n\t\t)\n\t);\n\n\t// defer event to be emitted after event handler has been registered\n\tconst onOpen = props.onOpen;\n\tif ( !onOpen ) {\n\t\treturn;\n\t}\n\tconst searcher = this;\n\tsetTimeout( () => {\n\t\tonOpen( searcher );\n\t}, 0 );\n}\n\nmfExtend( LanguageSearcher, View, {\n\t/**\n\t * @inheritdoc\n\t * @memberof LanguageSearcher\n\t * @instance\n\t */\n\ttemplate: util.template( `\n<div class=\"panel\">\n\t<div class=\"panel-body search-box\">\n\t\t<input type=\"search\" class=\"search mw-ui-background-icon-search\" placeholder=\"{{inputPlaceholder}}\">\n\t</div>\n</div>\n\n<div class=\"overlay-content-body\">\n\t{{#showSuggestedLanguagesHeader}}\n\t<h3 class=\"list-header\">{{suggestedLanguagesHeader}}</h3>\n\t{{/showSuggestedLanguagesHeader}}\n\t{{#suggestedLanguagesCount}}\n\t<ol class=\"site-link-list suggested-languages\">\n\t\t{{#suggestedLanguages}}\n\t\t\t<li>\n\t\t\t\t<a href=\"{{url}}\" class=\"{{lang}}\" hreflang=\"{{lang}}\" lang=\"{{lang}}\" dir=\"{{dir}}\">\n\t\t\t\t\t<span class=\"autonym\">{{autonym}}</span>\n\t\t\t\t\t{{#title}}\n\t\t\t\t\t\t<span class=\"title\">{{title}}</span>\n\t\t\t\t\t{{/title}}\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t{{/suggestedLanguages}}\n\t</ol>\n\t{{/suggestedLanguagesCount}}\n\t{{#bannerHTML}}\n\t<div class=\"language-search-banner\">\n\t\t{{{.}}}\n\t</div>\n\t{{/bannerHTML}}\n\t{{#allLanguagesCount}}\n\t<h3 class=\"list-header\">{{allLanguagesHeader}} ({{allLanguagesCount}})</h3>\n\t<ul class=\"site-link-list all-languages\">\n\t\t{{#allLanguages}}\n\t\t\t<li>\n\t\t\t\t<a href=\"{{url}}\" class=\"{{lang}}\" hreflang=\"{{lang}}\" lang=\"{{lang}}\" dir=\"{{dir}}\">\n\t\t\t\t\t<span class=\"autonym\">{{autonym}}</span>\n\t\t\t\t\t{{#title}}\n\t\t\t\t\t\t<span class=\"title\">{{title}}</span>\n\t\t\t\t\t{{/title}}\n\t\t\t\t</a>\n\t\t\t</li>\n\t\t{{/allLanguages}}\n\t</ul>\n\t{{/allLanguagesCount}}\n\t<section class=\"empty-results hidden\">\n\t\t<h4 class=\"empty-results-header\">{{noResultsFoundHeader}}</h4>\n\t\t<p class=\"empty-results-body\">{{noResultsFoundMessage}}</p>\n\t</section>\n</div>\n\t` ),\n\t/**\n\t * @inheritdoc\n\t * @memberof LanguageSearcher\n\t * @instance\n\t */\n\tpostRender: function () {\n\t\t// cache\n\t\tthis.$siteLinksList = this.$el.find( '.site-link-list' );\n\t\tthis.$languageItems = this.$siteLinksList.find( 'a' );\n\t\tthis.$subheaders = this.$el.find( 'h3' );\n\t\tthis.$emptyResultsSection = this.$el.find( '.empty-results' );\n\t},\n\t/**\n\t * Method that can be called outside MF extension to render\n\t * a banner inside the language overlay.\n\t *\n\t * @stable for use inside ContentTranslation\n\t * @param {string} bannerHTML\n\t */\n\taddBanner: function ( bannerHTML ) {\n\t\tthis.options.bannerHTML = bannerHTML;\n\t\tthis.options.showSuggestedLanguagesHeader = true;\n\t\tthis.render();\n\t},\n\t/**\n\t * Article link click event handler\n\t *\n\t * @memberof LanguageSearcher\n\t * @instance\n\t * @param {jQuery.Event} ev\n\t */\n\tonLinkClick: function ( ev ) {\n\t\tconst $link = this.$el.find( ev.currentTarget ),\n\t\t\tlang = $link.attr( 'lang' );\n\n\t\tmw.hook( 'mobileFrontend.languageSearcher.linkClick' ).fire( lang );\n\t\tlangUtil.saveLanguageUsageCount( lang, langUtil.getFrequentlyUsedLanguages() );\n\t},\n\t/**\n\t * Search input handler\n\t *\n\t * @memberof LanguageSearcher\n\t * @instance\n\t * @param {jQuery.Event} ev Event object.\n\t */\n\tonSearchInput: function ( ev ) {\n\t\tthis.filterLanguages( this.$el.find( ev.target ).val().toLowerCase() );\n\t},\n\t/**\n\t * Filter the language list to only show languages that match the current search term.\n\t *\n\t * @memberof LanguageSearcher\n\t * @instance\n\t * @param {string} searchQuery of search term (lowercase).\n\t */\n\tfilterLanguages: function ( searchQuery ) {\n\t\tconst filteredList = [];\n\n\t\tif ( searchQuery ) {\n\t\t\tthis.options.languages.forEach( function ( language ) {\n\t\t\t\tconst langname = language.langname;\n\t\t\t\t// search by language code or language name\n\t\t\t\tif ( language.autonym.toLowerCase().indexOf( searchQuery ) > -1 ||\n\t\t\t\t\t\t( langname && langname.toLowerCase().indexOf( searchQuery ) > -1 ) ||\n\t\t\t\t\t\tlanguage.lang.toLowerCase().indexOf( searchQuery ) > -1\n\t\t\t\t) {\n\t\t\t\t\tfilteredList.push( language.lang );\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\tif ( this.options.variants ) {\n\t\t\t\tthis.options.variants.forEach( function ( variant ) {\n\t\t\t\t\t// search by variant code or variant name\n\t\t\t\t\tif ( variant.autonym.toLowerCase().indexOf( searchQuery ) > -1 ||\n\t\t\t\t\t\tvariant.lang.toLowerCase().indexOf( searchQuery ) > -1\n\t\t\t\t\t) {\n\t\t\t\t\t\tfilteredList.push( variant.lang );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\tthis.$languageItems.addClass( 'hidden' );\n\t\t\tif ( filteredList.length ) {\n\t\t\t\tthis.$siteLinksList.find( '.' + filteredList.join( ',.' ) ).removeClass( 'hidden' );\n\t\t\t\tthis.$emptyResultsSection.addClass( 'hidden' );\n\t\t\t} else {\n\t\t\t\tthis.$emptyResultsSection.removeClass( 'hidden' );\n\t\t\t\t// Fire with the search query and the DOM element corresponding to no-results\n\t\t\t\t// message so that it can be customized in hook handler\n\t\t\t\tmw.hook( 'mobileFrontend.languageSearcher.noresults' )\n\t\t\t\t\t.fire( searchQuery, this.$emptyResultsSection.get( 0 ) );\n\t\t\t}\n\t\t\tthis.$siteLinksList.addClass( 'filtered' );\n\t\t\tthis.$subheaders.addClass( 'hidden' );\n\t\t} else {\n\t\t\tthis.$languageItems.removeClass( 'hidden' );\n\t\t\tthis.$siteLinksList.removeClass( 'filtered' );\n\t\t\tthis.$subheaders.removeClass( 'hidden' );\n\t\t\tthis.$emptyResultsSection.addClass( 'hidden' );\n\t\t}\n\t}\n} );\n\nmodule.exports = LanguageSearcher;\n","const\n\tm = require( '../mobile.startup/moduleLoaderSingleton' ),\n\tLanguageSearcher = require( './LanguageSearcher' );\n\n// Needed because LanguageSearcher is lazy loaded, and if we try to use require\n// (instead of m.require), webpack will excise it into mobile.common.\nm.define( 'mobile.languages.structured/LanguageSearcher', LanguageSearcher );\n","module.exports = [\n\t'aeb',\n\t'aeb-arab',\n\t'ar',\n\t'arc',\n\t'arq',\n\t'arz',\n\t'azb',\n\t'bcc',\n\t'bgn',\n\t'bqi',\n\t'ckb',\n\t'dv',\n\t'fa',\n\t'glk',\n\t'he',\n\t'khw',\n\t'kk-arab',\n\t'kk-cn',\n\t'ks',\n\t'ks-arab',\n\t'ku-arab',\n\t'lki',\n\t'lrc',\n\t'luz',\n\t'mzn',\n\t'nqo',\n\t'pnb',\n\t'ps',\n\t'sd',\n\t'sdh',\n\t'skr',\n\t'skr-arab',\n\t'ug',\n\t'ug-arab',\n\t'ur',\n\t'yi'\n];\n","var\n\tlog = mw.log,\n\tmfUtils = require( '../mobile.startup/util' ),\n\trtlLanguages = require( './rtlLanguages' );\n\n/**\n * @typedef {Object} Language\n * @prop {string} autonym of language e.g. français\n * @prop {string} langname in the user's current language e.g French\n * @prop {string} title of the page in the language e.g. Espagne\n * @prop {string} dir (rtl or ltr)\n * @prop {string} url of the page\n *\n * @typedef {Object} SuggestedLanguage\n * @prop {string} autonym of language e.g. français\n * @prop {string} langname in the user's current language e.g French\n * @prop {string} title of the page in the language e.g. Espagne\n * @prop {string} dir (rtl or ltr)\n * @prop {string} url of the page\n * @prop {number} frequency of times the language has been used by the given user\n *\n * @typedef {Object} StructuredLanguages\n * @prop {Language[]} all languages that are available\n * @prop {SuggestedLanguage[]} suggested languages based on users browsing history\n */\n\n/**\n * Return the device language if it's in the list of article languages.\n * If the language is a variant of a general language, and if the article\n * is not available in that language, then return the general language\n * if article is available in it. For example, if the device language is\n * 'en-gb', and the article is only available in 'en', then return 'en'.\n *\n * @param {Object[]} languages list of language objects as returned by the API\n * @param {string|undefined} deviceLanguage the device's primary language\n * @return {string|undefined} Return undefined if the article is not available in\n *  the (general or variant) device language\n */\nfunction getDeviceLanguageOrParent( languages, deviceLanguage ) {\n\tvar parentLanguage, index,\n\t\thasOwn = Object.prototype.hasOwnProperty,\n\t\tdeviceLanguagesWithVariants = {};\n\n\tif ( !deviceLanguage ) {\n\t\treturn;\n\t}\n\n\t// Are we dealing with a variant?\n\tindex = deviceLanguage.indexOf( '-' );\n\tif ( index !== -1 ) {\n\t\tparentLanguage = deviceLanguage.slice( 0, index );\n\t}\n\n\tlanguages.forEach( function ( language ) {\n\t\tif ( language.lang === parentLanguage || language.lang === deviceLanguage ) {\n\t\t\tdeviceLanguagesWithVariants[ language.lang ] = true;\n\t\t}\n\t} );\n\n\tif ( hasOwn.call( deviceLanguagesWithVariants, deviceLanguage ) ) {\n\t\t// the device language is one of the available languages\n\t\treturn deviceLanguage;\n\t} else if ( hasOwn.call( deviceLanguagesWithVariants, parentLanguage ) ) {\n\t\t// no device language, but the parent language is one of the available languages\n\t\treturn parentLanguage;\n\t}\n}\n\n/**\n * Utility function for the structured language overlay\n *\n * @class util\n * @singleton\n */\nmodule.exports = {\n\t/**\n\t * Determine whether a language is LTR or RTL\n\t * This works around T74153 and T189036\n\t *\n\t * @memberof util\n\t * @instance\n\t * @param {Object} language with 'lang' key.\n\t * @return {Object} language with 'lang' key and new 'dir' key.\n\t */\n\tgetDir: function ( language ) {\n\t\tvar dir = rtlLanguages.indexOf( language.lang ) > -1 ? 'rtl' : 'ltr';\n\t\treturn mfUtils.extend( {}, language, { dir: dir } );\n\t},\n\n\t/**\n\t * Return two sets of languages: suggested and all (everything else)\n\t *\n\t * Suggested languages are the ones that the user has used before. This also\n\t * includes the user device's primary language. Suggested languages are ordered\n\t * by frequency in descending order. The device's language is always at the top.\n\t * This group also includes the variants.\n\t *\n\t * All languages are the languages that are not suggested.\n\t * Languages in this list are ordered in the lexicographical order of\n\t * their language names.\n\t *\n\t * @memberof util\n\t * @instance\n\t * @param {Object[]} languages list of language objects as returned by the API\n\t * @param {Array|boolean} variants language variant objects or false if no variants exist\n\t * @param {Object} frequentlyUsedLanguages list of the frequently used languages\n\t * @param {boolean} showSuggestedLanguages\n\t * @param {string} [deviceLanguage] the device's primary language\n\t * @return {StructuredLanguages}\n\t */\n\tgetStructuredLanguages: function (\n\t\tlanguages,\n\t\tvariants,\n\t\tfrequentlyUsedLanguages,\n\t\tshowSuggestedLanguages,\n\t\tdeviceLanguage\n\t) {\n\t\tvar hasOwn = Object.prototype.hasOwnProperty,\n\t\t\tmaxFrequency = 0,\n\t\t\tminFrequency = 0,\n\t\t\tmissingDir = 0,\n\t\t\tsuggestedLanguages = [],\n\t\t\tallLanguages = [],\n\t\t\tself = this;\n\n\t\t// Is the article available in the user's device language?\n\t\tdeviceLanguage = getDeviceLanguageOrParent( languages, deviceLanguage );\n\t\tif ( deviceLanguage ) {\n\t\t\tObject.keys( frequentlyUsedLanguages ).forEach( function ( language ) {\n\t\t\t\tvar frequency = frequentlyUsedLanguages[ language ];\n\t\t\t\tmaxFrequency = maxFrequency < frequency ? frequency : maxFrequency;\n\t\t\t\tminFrequency = minFrequency > frequency ? frequency : minFrequency;\n\t\t\t} );\n\n\t\t\t// Make the device language the most frequently used one so that\n\t\t\t// it appears at the top of the list when sorted by frequency.\n\t\t\tfrequentlyUsedLanguages[ deviceLanguage ] = maxFrequency + 1;\n\t\t}\n\n\t\t/**\n\t\t * @param {Object} language\n\t\t * @return {Object} which has 'dir' key.\n\t\t */\n\t\tfunction addLangDir( language ) {\n\t\t\tif ( language.dir ) {\n\t\t\t\treturn language;\n\t\t\t} else {\n\t\t\t\tmissingDir++;\n\t\t\t\treturn self.getDir( language );\n\t\t\t}\n\t\t}\n\n\t\t// Separate languages into suggested and all languages.\n\t\tif ( showSuggestedLanguages ) {\n\t\t\tlanguages.map( addLangDir ).forEach( function ( language ) {\n\t\t\t\tif ( hasOwn.call( frequentlyUsedLanguages, language.lang ) ) {\n\t\t\t\t\tlanguage.frequency = frequentlyUsedLanguages[language.lang];\n\t\t\t\t\tsuggestedLanguages.push( language );\n\t\t\t\t} else {\n\t\t\t\t\tallLanguages.push( language );\n\t\t\t\t}\n\t\t\t} );\n\t\t} else {\n\t\t\tallLanguages = languages.map( addLangDir );\n\t\t}\n\n\t\t// Add variants to the suggested languages list and assign the lowest\n\t\t// frequency because the variant hasn't been clicked on yet.\n\t\t// Note that the variants data doesn't contain the article title, thus\n\t\t// we cannot show it for the variants.\n\t\tif ( variants && showSuggestedLanguages ) {\n\t\t\tvariants.map( addLangDir ).forEach( function ( variant ) {\n\t\t\t\tif ( hasOwn.call( frequentlyUsedLanguages, variant.lang ) ) {\n\t\t\t\t\tvariant.frequency = frequentlyUsedLanguages[variant.lang];\n\t\t\t\t} else {\n\t\t\t\t\tvariant.frequency = minFrequency - 1;\n\t\t\t\t}\n\t\t\t\tsuggestedLanguages.push( variant );\n\t\t\t} );\n\t\t}\n\n\t\t// sort suggested languages in descending order by frequency\n\t\tsuggestedLanguages = suggestedLanguages.sort( function ( a, b ) {\n\t\t\treturn b.frequency - a.frequency;\n\t\t} );\n\n\t\t/**\n\t\t * Compare language names lexicographically\n\t\t *\n\t\t * @param {Object} a first language\n\t\t * @param {Object} b second language\n\t\t * @return {number} Comparison value, 1 or -1\n\t\t */\n\t\tfunction compareLanguagesByLanguageName( a, b ) {\n\t\t\treturn a.autonym.toLocaleLowerCase() < b.autonym.toLocaleLowerCase() ? -1 : 1;\n\t\t}\n\n\t\tallLanguages = allLanguages.sort( compareLanguagesByLanguageName );\n\n\t\t// This works around T74153\n\t\tlog.warn(\n\t\t\tmissingDir === 0 ? 'Direction is provided. Please remove handling in getStructuredLanguages' :\n\t\t\t\t'`dir` attribute was missing from languages. Is T74153 resolved?'\n\t\t);\n\n\t\treturn {\n\t\t\tsuggested: suggestedLanguages,\n\t\t\tall: allLanguages\n\t\t};\n\t},\n\n\t/**\n\t * Return a map of frequently used languages on the current device.\n\t *\n\t * @memberof util\n\t * @instance\n\t * @return {Object}\n\t */\n\tgetFrequentlyUsedLanguages: function () {\n\t\tvar languageMap = mw.storage.get( 'langMap' );\n\n\t\treturn languageMap ? JSON.parse( languageMap ) : {};\n\t},\n\n\t/**\n\t * Save the frequently used languages to the user's device\n\t *\n\t * @memberof util\n\t * @instance\n\t * @param {Object} languageMap\n\t */\n\tsaveFrequentlyUsedLanguages: function ( languageMap ) {\n\t\tmw.storage.set( 'langMap', JSON.stringify( languageMap ) );\n\t},\n\n\t/**\n\t * Increment the current language usage by one and save it to the device.\n\t * Cap the result at 100.\n\t *\n\t * @memberof util\n\t * @instance\n\t * @param {string} languageCode\n\t * @param {Object} frequentlyUsedLanguages list of the frequently used languages\n\t */\n\tsaveLanguageUsageCount: function ( languageCode, frequentlyUsedLanguages ) {\n\t\tvar count = frequentlyUsedLanguages[ languageCode ] || 0;\n\n\t\tcount += 1;\n\t\t// cap at 100 as this is enough data to work on\n\t\tfrequentlyUsedLanguages[ languageCode ] = count > 100 ? 100 : count;\n\t\tthis.saveFrequentlyUsedLanguages( frequentlyUsedLanguages );\n\t}\n};\n"],"sourceRoot":""}