wikimedia/mediawiki-extensions-MobileFrontend

View on GitHub
resources/dist/mobile.languages.structured.js.map.json

Summary

Maintainability
Test Coverage
{"version":3,"file":"mobile.languages.structured.js","mappings":"2IAAA,IACCA,EAAOC,EAAS,gCAChBC,EAAOD,EAAS,gCAChBE,EAAWF,EAAS,6CAkBrB,SAASG,EAAkBC,GAI1B,IAAMC,EAAYH,EAASI,uBAC1BF,EAAMC,UACND,EAAMG,SACNL,EAASM,6BACTJ,EAAMK,uBACNL,EAAMM,gBAGPX,EAAKY,KACJC,KACAX,EAAKY,OACJ,CACCC,UAAW,oBACXC,OAAQ,CACP,UAAW,cACX,gCAAiC,sBACjC,gBAAiB,iBAGlBC,iBAAkBC,GAAGC,IAAK,yEAE1BC,mBAAoBF,GAAGC,IAAK,qEAAsEE,oBAClGC,yBAA0BJ,GAAGC,IAAK,2EAA4EE,oBAC9GE,qBAAsBL,GAAGC,IAAK,2DAC9BK,sBAAuBN,GAAGC,IAAK,gEAC/BM,aAAcnB,EAAUoB,IACxBC,kBAAmBrB,EAAUoB,IAAIE,OACjCC,mBAAoBvB,EAAUwB,UAC9BC,wBAAyBzB,EAAUwB,UAAUF,OAC7CI,6BAA8B1B,EAAUwB,UAAUF,OAAS,GAE5DvB,IAKF,IAAM4B,EAAS5B,EAAM4B,OACrB,GAAMA,EAAN,CAGA,IAAMC,EAAWrB,KACjBsB,YAAY,WACXF,EAAQC,EACT,GAAG,EAJH,CAKD,CAjEYjC,EAAS,mCAmErBmC,CAAUhC,EAAkBJ,EAAM,CAMjCqC,SAAUnC,EAAKmC,SAAS,ukDAwDxBC,WAAY,WAEXzB,KAAK0B,eAAiB1B,KAAK2B,IAAIC,KAAM,mBACrC5B,KAAK6B,eAAiB7B,KAAK0B,eAAeE,KAAM,KAChD5B,KAAK8B,YAAc9B,KAAK2B,IAAIC,KAAM,MAClC5B,KAAK+B,qBAAuB/B,KAAK2B,IAAIC,KAAM,iBAC5C,EAUAI,UAAW,SAAWC,EAAYC,GACjClC,KAAKmC,QAAQF,WAAaA,EAC1BjC,KAAKmC,QAAQC,oBAAsBF,EACnClC,KAAKmC,QAAQhB,8BAA+B,EAC5CnB,KAAKqC,QACN,EACAC,oBAAqB,WACpBtC,KAAK2B,IAAIC,KAAM,WAAYW,IAAKvC,KAAKmC,QAAQC,qBAAsBI,QAAS,QAC7E,EAQAC,YAAa,SAAWC,GACvB,IACCC,EADa3C,KAAK2B,IAAIC,KAAMc,EAAGE,eAClBC,KAAM,QAOpBxC,GAAGyC,KAAM,6CAA8CC,KAAMJ,GAC7DrD,EAAS0D,uBAAwBL,EAAMrD,EAASM,6BACjD,EAQAqD,cAAe,SAAWP,GACzB,IAAMQ,OAAoCC,IAArBT,EAAGU,cAA8B,oBAAsB,KAE5EpD,KAAKqD,gBAAiBX,EAAGY,OAAOC,MAAMC,cAAeN,EACtD,EASAG,gBAAiB,SAAWI,EAAaP,GACxC,IAAMQ,EAAe,GAEhBD,GACJzD,KAAKmC,QAAQ1C,UAAUkE,SAAS,SAAWC,GAC1C,IAAMC,EAAWD,EAASC,UAErBD,EAASE,QAAQN,cAAcO,QAASN,IAAiB,GAC1DI,GAAYA,EAASL,cAAcO,QAASN,IAAiB,GAC/DG,EAASjB,KAAKa,cAAcO,QAASN,IAAiB,IAEvDC,EAAaM,KAAMJ,EAASjB,KAE9B,IAEK3C,KAAKmC,QAAQxC,UACjBK,KAAKmC,QAAQxC,SAASgE,SAAS,SAAWM,IAEpCA,EAAQH,QAAQN,cAAcO,QAASN,IAAiB,GAC5DQ,EAAQtB,KAAKa,cAAcO,QAASN,IAAiB,IAErDC,EAAaM,KAAMC,EAAQtB,KAE7B,IAGD3C,KAAK6B,eAAeqC,SAAU,UACzBR,EAAa3C,QACjBf,KAAK0B,eAAeE,KAAK,IAADuC,OAClB9D,GAAGhB,KAAK+E,aAAcV,EAAaW,KAAM,SAC7CC,YAAa,UACftE,KAAK+B,qBAAqBmC,SAAU,YAEpClE,KAAK+B,qBAAqBuC,YAAa,UASvCjE,GAAGyC,KAAM,6CACPC,KAAMU,EAAazD,KAAK+B,qBAAqBwC,IAAK,GAAKrB,IAE1DlD,KAAK0B,eAAewC,SAAU,YAC9BlE,KAAK8B,YAAYoC,SAAU,YAE3BlE,KAAK6B,eAAeyC,YAAa,UACjCtE,KAAK0B,eAAe4C,YAAa,YACjCtE,KAAK8B,YAAYwC,YAAa,UAC9BtE,KAAK+B,qBAAqBmC,SAAU,UAEtC,IAGDM,EAAOC,QAAUlF,C,+EC7PjB,IACCmF,EAAItF,EAAS,iDACbG,EAAmBH,EAAS,yDAI7BsF,EAAEC,OAAQ,+CAAgDpF,E,0DCH1DiF,EAAOC,QAAU,CAChB,MACA,MACA,WACA,MACA,KACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,KACA,KACA,MACA,KACA,MACA,MACA,UACA,QACA,KACA,UACA,UACA,MACA,MACA,MACA,UACA,MACA,MACA,MACA,KACA,KACA,MACA,MACA,WACA,KACA,UACA,KACA,K,wDC5CD,IACCG,EAAUxF,EAAS,gCACnByF,EAAezF,EAAS,qDA4EzBoF,EAAOC,QAAU,CAahBK,OAAQ,SAAWlB,GAClB,IAAMmB,EAAMF,EAAad,QAASH,EAASjB,OAAU,EAAI,MAAQ,MACjE,OAAOiC,EAAQ3E,OAAQ,CAAC,EAAG2D,EAAU,CAAEmB,IAAAA,GACxC,EAwBArF,uBAAwB,SACvBD,EACAE,EACAqF,EACAnF,EACAC,GAEA,IAAMmF,EAASC,OAAOC,UAAUC,eAC/BC,EAAOrF,KAEJsF,EAAe,EAClBC,EAAe,EACfvE,EAAqB,GACrBJ,EAAe,GAoBhB,SAAS4E,EAAY5B,GACpB,OAAKA,EAASmB,IACNnB,EAEAyB,EAAKP,OAAQlB,EAEtB,CAgDA,OAvEA9D,EA/FF,SAAoCL,EAAWK,GAC9C,IAAI2F,EAGHR,EAASC,OAAOC,UAAUC,eAC1BM,EAA8B,CAAC,EAEhC,GAAM5F,EAAN,CAKA,IAAM6F,EAAQ7F,EAAeiE,QAAS,KAWtC,OAVgB,IAAX4B,IACJF,EAAiB3F,EAAe8F,MAAO,EAAGD,IAG3ClG,EAAUkE,SAAS,SAAWC,GACxBA,EAASjB,OAAS8C,GAAkB7B,EAASjB,OAAS7C,IAC1D4F,EAA6B9B,EAASjB,OAAS,EAEjD,IAEKsC,EAAOlF,KAAM2F,EAA6B5F,GAEvCA,EACImF,EAAOlF,KAAM2F,EAA6BD,GAE9CA,OAFD,CAjBP,CAqBD,CAiEmBI,CAA2BpG,EAAWK,GAClDA,IACJoF,OAAOY,KAAMd,GAA0BrB,SAAS,SAAWC,GAC1D,IAAMmC,EAAYf,EAAyBpB,GAC3C0B,EAAeA,EAAeS,EAAYA,EAAYT,EACtDC,EAAeA,EAAeQ,EAAYA,EAAYR,CACvD,IAIAP,EAAyBlF,GAAmBwF,EAAe,GAgBvDzF,EACJJ,EAAUuG,IAAKR,GAAa7B,SAAS,SAAWC,GAC1CqB,EAAOlF,KAAMiF,EAAyBpB,EAASjB,OACnDiB,EAASmC,UAAYf,EAAwBpB,EAASjB,MACtD3B,EAAmBgD,KAAMJ,IAEzBhD,EAAaoD,KAAMJ,EAErB,IAEAhD,EAAenB,EAAUuG,IAAKR,GAO1B7F,GAAYE,GAChBF,EAASqG,IAAKR,GAAa7B,SAAS,SAAWM,GACzCgB,EAAOlF,KAAMiF,EAAyBf,EAAQtB,MAClDsB,EAAQ8B,UAAYf,EAAwBf,EAAQtB,MAEpDsB,EAAQ8B,UAAYR,EAAe,EAEpCvE,EAAmBgD,KAAMC,EAC1B,IAIDjD,EAAqBA,EAAmBiF,MAAM,SAAWC,EAAGC,GAC3D,OAAOA,EAAEJ,UAAYG,EAAEH,SACxB,IAaAnF,EAAeA,EAAaqF,MAJ5B,SAAyCC,EAAGC,GAC3C,OAAOD,EAAEpC,QAAQsC,oBAAsBD,EAAErC,QAAQsC,qBAAuB,EAAI,CAC7E,IAGO,CACNnF,UAAWD,EACXH,IAAKD,EAEP,EASAhB,2BAA4B,WAC3B,IAAMyG,EAAchG,GAAGiG,QAAQ/B,IAAK,WAEpC,OAAO8B,EAAcE,KAAKC,MAAOH,GAAgB,CAAC,CACnD,EASAI,4BAA6B,SAAWJ,GACvChG,GAAGiG,QAAQI,IAAK,UAAWH,KAAKI,UAAWN,GAC5C,EAWArD,uBAAwB,SAAW4D,EAAc5B,GAChD,IAAI6B,EAAQ7B,EAAyB4B,IAAkB,EAEvDC,GAAS,EAET7B,EAAyB4B,GAAiBC,EAAQ,IAAM,IAAMA,EAC9D7G,KAAKyG,4BAA6BzB,EACnC,E","sources":["webpack://mfModules/./src/mobile.languages.structured/LanguageSearcher.js","webpack://mfModules/./src/mobile.languages.structured/mobile.languages.structured.js","webpack://mfModules/./src/mobile.languages.structured/rtlLanguages.js","webpack://mfModules/./src/mobile.languages.structured/util.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 * @class Hooks~LanguageSearcher\n * @classdesc Overlay displaying a structured list of languages for a page, only accessible via\n * the  {@link Hooks~'mobileFrontend.languageSearcher.onOpen' mobileFrontend.languageSearcher.onOpen hook}.\n * @hideconstructor\n * @extends module:mobile.startup/View\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 */\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 a': 'onLinkClick',\n\t\t\t\t\t'click .language-search-banner': 'onSearchBannerClick',\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\" 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 * @memberof LanguageSearcher\n\t * @param {string} bannerHTML\n\t * @param {string} firstMissingLanguage\n\t */\n\taddBanner: function ( bannerHTML, firstMissingLanguage ) {\n\t\tthis.options.bannerHTML = bannerHTML;\n\t\tthis.options.bannerFirstLanguage = firstMissingLanguage;\n\t\tthis.options.showSuggestedLanguagesHeader = true;\n\t\tthis.render();\n\t},\n\tonSearchBannerClick: function () {\n\t\tthis.$el.find( '.search' ).val( this.options.bannerFirstLanguage ).trigger( 'input' );\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\t\t/**\n\t\t * Internal for use in GrowthExperiments only.\n\t\t *\n\t\t * @event ~'mobileFrontend.languageSearcher.linkClick'\n\t\t * @memberof Hooks\n\t\t */\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\tconst searchOrigin = ev.originalEvent === undefined ? 'entrypoint-banner' : 'ui';\n\n\t\tthis.filterLanguages( ev.target.value.toLowerCase(), searchOrigin );\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 * @param {'entrypoint-banner'|'ui'} searchOrigin for internal use by CX entrypoints only\n\t */\n\tfilterLanguages: function ( searchQuery, searchOrigin ) {\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(\n\t\t\t\t\t`.${ mw.util.escapeRegExp( filteredList.join( ',.' ) ) }`\n\t\t\t\t).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\t/**\n\t\t\t\t * Internal for use in ContentTranslation only.\n\t\t\t\t *\n\t\t\t\t * @event ~'mobileFrontend.editorOpening'\n\t\t\t\t * @memberof Hooks\n\t\t\t\t */\n\t\t\t\tmw.hook( 'mobileFrontend.languageSearcher.noresults' )\n\t\t\t\t\t.fire( searchQuery, this.$emptyResultsSection.get( 0 ), searchOrigin );\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","/**\n * @private\n */\nmodule.exports = [\n\t'acm',\n\t'aeb',\n\t'aeb-arab',\n\t'apc',\n\t'ar',\n\t'arc',\n\t'arq',\n\t'ary',\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'hno',\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'ms-arab',\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","const\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 * @ignore\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 * @ignore\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\tlet parentLanguage;\n\n\tconst\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\tconst index = 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 * @private\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 * and the fact that adding dir attribute to HTML in core\n\t * at time of writing is memory-intensive\n\t * (I7cd8a3117f49467e3ff26f35371459a667c71470)\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\tconst dir = rtlLanguages.indexOf( language.lang ) > -1 ? 'rtl' : 'ltr';\n\t\treturn mfUtils.extend( {}, language, { 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 * @ignore\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\tconst hasOwn = Object.prototype.hasOwnProperty,\n\t\t\tself = this;\n\n\t\tlet maxFrequency = 0,\n\t\t\tminFrequency = 0,\n\t\t\tsuggestedLanguages = [],\n\t\t\tallLanguages = [];\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\tconst 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\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\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\tconst 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\tlet 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"],"names":["View","require","util","langUtil","LanguageSearcher","props","languages","getStructuredLanguages","variants","getFrequentlyUsedLanguages","showSuggestedLanguages","deviceLanguage","call","this","extend","className","events","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","firstMissingLanguage","options","bannerFirstLanguage","render","onSearchBannerClick","val","trigger","onLinkClick","ev","lang","currentTarget","attr","hook","fire","saveLanguageUsageCount","onSearchInput","searchOrigin","undefined","originalEvent","filterLanguages","target","value","toLowerCase","searchQuery","filteredList","forEach","language","langname","autonym","indexOf","push","variant","addClass","concat","escapeRegExp","join","removeClass","get","module","exports","m","define","mfUtils","rtlLanguages","getDir","dir","frequentlyUsedLanguages","hasOwn","Object","prototype","hasOwnProperty","self","maxFrequency","minFrequency","addLangDir","parentLanguage","deviceLanguagesWithVariants","index","slice","getDeviceLanguageOrParent","keys","frequency","map","sort","a","b","toLocaleLowerCase","languageMap","storage","JSON","parse","saveFrequentlyUsedLanguages","set","stringify","languageCode","count"],"sourceRoot":""}