{"version":3,"file":"mobile.special.watchlist.scripts.js","mappings":"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","sources":["webpack://mfModules/./src/mobile.special.watchlist.scripts/ScrollEndEventEmitter.js","webpack://mfModules/./src/mobile.special.watchlist.scripts/WatchList.js","webpack://mfModules/./src/mobile.special.watchlist.scripts/WatchListGateway.js","webpack://mfModules/./src/mobile.special.watchlist.scripts/mobile.special.watchlist.scripts.js"],"sourcesContent":["const util = require( '../mobile.startup/util' );\n\n/**\n * Class to assist a view in implementing infinite scrolling on some DOM\n * element. This module itself is only responsible for emitting an Event when\n * the bottom of an Element is scrolled to.\n *\n * @class ScrollEndEventEmitter\n * @mixes OO.EventEmitter\n *\n * Use this class in a view to help it do infinite scrolling.\n *\n * 1. Initialize it in the constructor `initialize` and listen to the\n *   EVENT_SCROLL_END event it emits (and call your loading function then)\n * 2. On preRender (once we have the DOM element) set it into the infinite\n *   scrolling object and disable it until we've loaded.\n * 3. Once you have loaded the list and put it in the DOM, enable the\n *   infinite scrolling detection.\n *   - Every time the scroller detection triggers a load, it auto disables\n *     to not trigger multiple times. After you have loaded, manually\n *     re-enable it.\n *\n * Example:\n *     @example\n *     <code>\n *       var\n *         ScrollEndEventEmitter = require( './ScrollEndEventEmitter' ),\n *         eventBus = require( './eventBusSingleton' );\n *       class PhotoList extends View {\n *         //...\n *         initialize: function ( options ) {\n *           this.gateway = new PhotoListGateway( {\n *             username: options.username\n *           } );\n *           // 1. Set up infinite scroll helper and listen to events\n *           this.scrollEndEventEmitter = new ScrollEndEventEmitter( eventBus, 1000 );\n *           this.scrollEndEventEmitter.on( ScrollEndEventEmitter.EVENT_SCROLL_END,\n *             this._loadPhotos.bind( this ) );\n *           super.initialize( options );\n *         },\n *         preRender: function () {\n *           // 2. Disable until we've got the list rendered and set DOM el\n *           this.scrollEndEventEmitter.setElement( this.$el );\n *           this.scrollEndEventEmitter.disable();\n *         },\n *         _loadPhotos: function () {\n *           this.gateway.getPhotos().then( ( photos ) => {\n *             // load photos into the DOM ...\n *             // 3. and (re-)enable infinite scrolling\n *             this.scrollEndEventEmitter.enable();\n *           } );\n *         }\n *       }\n *     </code>\n *\n * @fires ScrollEndEventEmitter#ScrollEndEventEmitter-scrollEnd\n */\nclass ScrollEndEventEmitter {\n\t/**\n\t * @param {Object} eventBus object to listen for scroll:throttled events\n\t * @param {number} [threshold=100] distance in pixels used to calculate if scroll\n\t * position is near the end of the $el\n\t */\n\tconstructor( eventBus, threshold ) {\n\t\tthis.threshold = threshold || 100;\n\t\tthis.eventBus = eventBus;\n\t\tthis.enable();\n\t\tOO.EventEmitter.call( this );\n\t}\n\n\t/**\n\t * Listen to scroll on window and notify this._onScroll\n\t *\n\t * @private\n\t */\n\t_bindScroll() {\n\t\tif ( !this._scrollHandler ) {\n\t\t\tthis._scrollHandler = () => this._onScroll();\n\t\t\tthis.eventBus.on( 'scroll:throttled', this._scrollHandler );\n\t\t}\n\t}\n\n\t/**\n\t * Unbind scroll handler\n\t *\n\t * @private\n\t */\n\t_unbindScroll() {\n\t\tif ( this._scrollHandler ) {\n\t\t\tthis.eventBus.off( 'scroll:throttled', this._scrollHandler );\n\t\t\tthis._scrollHandler = null;\n\t\t}\n\t}\n\n\t/**\n\t * Scroll handler. Triggers load event when near the end of the container.\n\t *\n\t * @private\n\t */\n\t_onScroll() {\n\t\tif ( this.$el && this.enabled && this.scrollNearEnd() ) {\n\t\t\t// Disable when triggering an event. Won't trigger again until\n\t\t\t// re-enabled.\n\t\t\tthis.disable();\n\t\t\tthis.emit( ScrollEndEventEmitter.EVENT_SCROLL_END );\n\t\t}\n\t}\n\n\t/**\n\t * Is the scroll position near the end of the container element?\n\t *\n\t * @private\n\t * @return {boolean}\n\t */\n\tscrollNearEnd() {\n\t\tif ( !this.$el || !this.$el.offset() ) {\n\t\t\treturn false;\n\t\t}\n\t\tconst $window = util.getWindow(),\n\t\t\tscrollBottom = $window.scrollTop() + $window.height(),\n\t\t\tendPosition = this.$el.offset().top + this.$el.outerHeight();\n\t\treturn scrollBottom + this.threshold > endPosition;\n\t}\n\n\t/**\n\t * Enable the ScrollEndEventEmitter so that it triggers events.\n\t */\n\tenable() {\n\t\tthis.enabled = true;\n\t\tthis._bindScroll();\n\t}\n\n\t/**\n\t * Disable the ScrollEndEventEmitter so that it doesn't trigger events.\n\t */\n\tdisable() {\n\t\tthis.enabled = false;\n\t\tthis._unbindScroll();\n\t}\n\n\t/**\n\t * Set the element to compare to scroll position to\n\t *\n\t * @param {jQuery.Object} $el jQuery element where we want to listen for\n\t * scroll end.\n\t */\n\tsetElement( $el ) {\n\t\tthis.$el = $el;\n\t}\n}\nOO.mixinClass( ScrollEndEventEmitter, OO.EventEmitter );\n\n/**\n * Fired when scroll bottom has been reached.\n *\n * @event ScrollEndEventEmitter#ScrollEndEventEmitter-scrollEnd\n */\nScrollEndEventEmitter.EVENT_SCROLL_END = 'ScrollEndEventEmitter-scrollEnd';\n\nmodule.exports = ScrollEndEventEmitter;\n","const\n\tPageList = require( '../mobile.startup/PageList' ),\n\tWatchstarPageList = require( '../mobile.startup/watchstar/WatchstarPageList' ),\n\tScrollEndEventEmitter = require( './ScrollEndEventEmitter' ),\n\tutil = require( '../mobile.startup/util' ),\n\tWatchListGateway = require( './WatchListGateway' );\n\n/**\n * An extension of the WatchstarPageList which preloads pages as all being\n * watched.\n *\n * @uses ScrollEndEventEmitter\n *\n * @fires watched\n * @fires watch\n * @private\n */\nclass WatchList extends WatchstarPageList {\n\t/**\n\t * @param {Object} params Configuration options\n\t * @param {OO.EventEmitter} params.eventBus Object used to listen for scroll:throttled events\n\t */\n\tconstructor( params ) {\n\t\tsuper( util.extend(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tisBorderBox: false\n\t\t\t},\n\t\t\tparams\n\t\t) );\n\t}\n\n\tinitialize( options ) {\n\t\t// Set up infinite scroll helper and listen to events\n\t\tthis.scrollEndEventEmitter = new ScrollEndEventEmitter( options.eventBus );\n\t\tthis.scrollEndEventEmitter.on( ScrollEndEventEmitter.EVENT_SCROLL_END,\n\t\t\t() => this._loadPages() );\n\n\t\tlet lastTitle;\n\t\tif ( options.el ) {\n\t\t\tlastTitle = this.getLastTitle( options.el );\n\t\t}\n\t\tthis.gateway = new WatchListGateway( options.api, lastTitle );\n\t\tsuper.initialize( options );\n\t}\n\n\tpreRender() {\n\t\t// The DOM will be modified. Prevent any false scroll end events from\n\t\t// being emitted.\n\t\tthis.scrollEndEventEmitter.disable();\n\t\tthis.scrollEndEventEmitter.setElement( this.$el );\n\t}\n\n\t/**\n\t * Also sets a watch uploads funnel.\n\t *\n\t * @inheritdoc\n\t */\n\tpostRender() {\n\t\t// Skip a level from WatchstarPageList directly to PageList.\n\t\tPageList.prototype.postRender.apply( this );\n\n\t\tconst $items = this.queryUnitializedItems();\n\n\t\t// WatchList requests list of watched pages. The list contains only\n\t\t// watched pages so it's safe to transform the title map to a status map\n\t\t// with each entry marked watched (true).\n\t\tconst statuses = Object.keys( this.parsePagesFromItems( $items ) )\n\t\t\t.reduce( ( arr, title ) => {\n\t\t\t\tarr[ title ] = true;\n\t\t\t\treturn arr;\n\t\t\t}, {} );\n\t\tthis.renderItems( $items, statuses );\n\n\t\t// The list has been extended. Re-enable scroll end events.\n\t\tthis.scrollEndEventEmitter.enable();\n\t}\n\n\t/**\n\t * Loads pages from the api and triggers render.\n\t * Infinite scroll is re-enabled in postRender.\n\t *\n\t */\n\t_loadPages() {\n\t\tthis.gateway.loadWatchlist().then( ( pages ) => {\n\t\t\tpages.forEach( ( page ) => {\n\t\t\t\tthis.appendPage( page );\n\t\t\t} );\n\t\t\tthis.render();\n\t\t} );\n\t}\n\n\t/**\n\t * Appends a list item\n\t *\n\t * @param {Page} page\n\t */\n\tappendPage( page ) {\n\t\t// wikidata descriptions should not show in this view.\n\t\tconst templateOptions = util.extend( {}, page, {\n\t\t\twikidataDescription: undefined\n\t\t} );\n\t\tthis.$el.append( this.templatePartials.item.render( templateOptions ) );\n\t}\n\n\t/**\n\t * Get the last title from the rendered HTML.\n\t * Used for initializing the API\n\t *\n\t * @param {jQuery.Object} $el Dom element of the list\n\t * @return {string}\n\t */\n\tgetLastTitle( $el ) {\n\t\treturn $el.find( 'li' ).last().attr( 'title' );\n\t}\n}\n\nmodule.exports = WatchList;\n","const\n\tpageJSONParser = require( '../mobile.startup/page/pageJSONParser' ),\n\tutil = require( '../mobile.startup/util' ),\n\textendSearchParams = require( '../mobile.startup/extendSearchParams' );\n\n/**\n * API for interacting with watchlist.\n */\nclass WatchListGateway {\n\t/**\n\t * @param {mw.Api} api\n\t * @param {string} lastTitle of page listed in Watchlist to be used as a continuation parameter\n\t * @private\n\t */\n\tconstructor( api, lastTitle ) {\n\t\tthis.api = api;\n\t\t// Try to keep it in sync with SpecialMobileEditWatchlist::LIMIT (php)\n\t\tthis.limit = 50;\n\n\t\tif ( lastTitle ) {\n\t\t\tthis.continueParams = {\n\t\t\t\tcontinue: 'gwrcontinue||',\n\t\t\t\tgwrcontinue: '0|' + lastTitle.replace( / /g, '_' )\n\t\t\t};\n\t\t\tthis.shouldSkipFirstTitle = true;\n\t\t} else {\n\t\t\tthis.continueParams = {\n\t\t\t\tcontinue: ''\n\t\t\t};\n\t\t\tthis.shouldSkipFirstTitle = false;\n\t\t}\n\n\t\tthis.canContinue = true;\n\t}\n\n\t/**\n\t * Load the list of items on the watchlist\n\t *\n\t * @return {jQuery.Deferred}\n\t */\n\tloadWatchlist() {\n\t\tconst params = extendSearchParams( 'watchlist', {\n\t\t\tprop: [ 'info', 'revisions' ],\n\t\t\trvprop: 'timestamp|user',\n\t\t\tgenerator: 'watchlistraw',\n\t\t\tgwrnamespace: '0',\n\t\t\tgwrlimit: this.limit\n\t\t}, this.continueParams );\n\n\t\tif ( this.canContinue === false ) {\n\t\t\treturn util.Deferred().resolve( [] );\n\t\t}\n\t\treturn this.api.get( params ).then( ( data ) => {\n\t\t\tif ( data.continue !== undefined ) {\n\t\t\t\tthis.continueParams = data.continue;\n\t\t\t} else {\n\t\t\t\tthis.canContinue = false;\n\t\t\t}\n\n\t\t\treturn this.parseData( data );\n\t\t} );\n\t}\n\n\t/**\n\t * Parse api response data into pagelist item format\n\t *\n\t * @param {Object[]} data\n\t * @return {Page[]}\n\t */\n\tparseData( data ) {\n\t\tlet pages;\n\n\t\tif ( !data.query || !data.query.pages ) {\n\t\t\treturn [];\n\t\t}\n\n\t\tpages = data.query.pages;\n\n\t\t// Sort results alphabetically (the api map doesn't have any order). The\n\t\t// watchlist is ordered alphabetically right now.\n\t\tpages.sort( ( p1, p2 ) => p1.title === p2.title ? 0 : ( p1.title < p2.title ? -1 : 1 ) );\n\n\t\t// If we requested from the last item of the previous page, we shall\n\t\t// remove the first result (to avoid it being repeated)\n\t\tif ( this.shouldSkipFirstTitle ) {\n\t\t\tpages = pages.slice( 1 );\n\t\t\tthis.shouldSkipFirstTitle = false;\n\t\t}\n\n\t\t// Transform the items to a sensible format\n\t\treturn pages.map( pageJSONParser.parse );\n\t}\n}\n\nmodule.exports = WatchListGateway;\n","/* global $ */\nconst WatchList = require( './WatchList' ),\n\teventBus = require( '../mobile.startup/eventBusSingleton' );\n\n/**\n * Initialises JavaScript on Special:Watchlist\n *\n * @private\n */\nfunction init() {\n\tconst $watchlist = $( 'ul.mw-mf-watchlist-page-list' );\n\n\t// FIXME: find more elegant way to not show watchlist stars on recent changes\n\tif ( $( '.mw-mf-watchlist-selector' ).length === 0 ) {\n\t\t// eslint-disable-next-line no-new\n\t\tnew WatchList( {\n\t\t\tapi: new mw.Api(),\n\t\t\tel: $watchlist,\n\t\t\tfunnel: 'watchlist',\n\t\t\tskipTemplateRender: true,\n\t\t\teventBus\n\t\t} );\n\t}\n\t// not needed now we have JS view which has infinite scrolling\n\t$watchlist.find( '.mw-mf-watchlist-more' ).remove();\n}\n\n$( () => {\n\tinit();\n} );\n"],"names":["util","ScrollEndEventEmitter","constructor","eventBus","threshold","this","enable","OO","EventEmitter","call","_bindScroll","_scrollHandler","_onScroll","on","_unbindScroll","off","$el","enabled","scrollNearEnd","disable","emit","EVENT_SCROLL_END","offset","$window","getWindow","scrollBottom","scrollTop","height","endPosition","top","outerHeight","setElement","mixinClass","module","exports","PageList","WatchstarPageList","WatchListGateway","params","super","extend","isBorderBox","initialize","options","lastTitle","scrollEndEventEmitter","_loadPages","el","getLastTitle","gateway","api","preRender","postRender","prototype","apply","$items","queryUnitializedItems","statuses","Object","keys","parsePagesFromItems","reduce","arr","title","renderItems","loadWatchlist","then","pages","forEach","page","appendPage","render","templateOptions","wikidataDescription","undefined","append","templatePartials","item","find","last","attr","pageJSONParser","extendSearchParams","limit","continueParams","continue","gwrcontinue","replace","shouldSkipFirstTitle","canContinue","prop","rvprop","generator","gwrnamespace","gwrlimit","Deferred","resolve","get","data","parseData","query","sort","p1","p2","slice","map","parse","WatchList","$","$watchlist","length","mw","Api","funnel","skipTemplateRender","remove","init"],"sourceRoot":""}