{"version":3,"sources":["webpack://mfModules.[name]/./src/mobile.special.uploads.scripts/PhotoItem.js","webpack://mfModules.[name]/./src/mobile.special.uploads.scripts/PhotoList.js","webpack://mfModules.[name]/./src/mobile.special.uploads.scripts/PhotoListGateway.js","webpack://mfModules.[name]/./src/mobile.special.uploads.scripts/mobile.special.uploads.scripts.js"],"names":["View","require","PhotoItem","apply","this","arguments","mfExtend","template","mw","get","tagName","module","exports","icons","PhotoListGateway","ScrollEndEventEmitter","PhotoList","options","gatewayOptions","url","api","username","category","gateway","scrollEndEventEmitter","eventBus","on","EVENT_SCROLL_END","_loadPhotos","bind","call","defaults","spinner","toHtmlString","preRender","setElement","$el","disable","postRender","$end","find","$list","isEmpty","length","showEmptyMessage","parseHTML","text","msg","insertBefore","hideEmptyMessage","hide","showSpinner","show","hideSpinner","updateEmptyUI","appendPhotos","photosData","self","forEach","photo","appendTo","enableScroll","enabled","enable","getPhotos","then","response","photos","canContinue","catch","util","limit","continueParams","continue","getDescription","title","replace","prototype","getWidth","config","small","_getImageDataFromPage","page","img","imageinfo","thumburl","timestamp","description","descriptionUrl","descriptionurl","getQuery","query","extend","action","prop","iiprop","iiurlwidth","generator","gaiuser","gaisort","gaidir","gailimit","gcmtitle","gcmtype","gcmdir","gcmlimit","origin","ajax","resp","pages","Object","keys","map","id","sort","a","b","undefined","test","user","pageParams","split","currentUserName","getName","corsUrl","userName","$","init","Api"],"mappings":"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","file":"mobile.special.uploads.scripts.js","sourcesContent":["var View = require( '../mobile.startup/View' ),\n\tmfExtend = require( '../mobile.startup/mfExtend' );\n\n/**\n * Single photo item in gallery\n * @class PhotoItem\n * @extends View\n */\nfunction PhotoItem() {\n\tView.apply( this, arguments );\n}\n\nmfExtend( PhotoItem, View, {\n\t/**\n\t * @memberof PhotoItem\n\t * @instance\n\t */\n\ttemplate: mw.template.get( 'mobile.special.uploads.scripts', 'PhotoItem.hogan' ),\n\t/**\n\t * @memberof PhotoItem\n\t * @instance\n\t */\n\ttagName: 'li'\n} );\nmodule.exports = PhotoItem;\n","var icons = require( '../mobile.startup/icons' ),\n\tPhotoListGateway = require( './PhotoListGateway' ),\n\tPhotoItem = require( './PhotoItem' ),\n\tmfExtend = require( '../mobile.startup/mfExtend' ),\n\tScrollEndEventEmitter = require( '../mobile.startup/ScrollEndEventEmitter' ),\n\tView = require( '../mobile.startup/View' );\n\n/**\n * Creates a list of photo items\n * @class PhotoList\n * @uses PhotoListApi\n * @uses PhotoItem\n * @uses ScrollEndEventEmitter\n * @extends View\n *\n * @param {Object} options Configuration options\n * @param {OO.EventEmitter} options.eventBus Object used to listen for scroll:throttled events\n * @param {string} options.url for overriding default URI for API queries\n */\nfunction PhotoList( options ) {\n\tvar gatewayOptions = {\n\t\turl: options.url,\n\t\tapi: options.api\n\t};\n\n\tif ( options.username ) {\n\t\tgatewayOptions.username = options.username;\n\t} else if ( options.category ) {\n\t\tgatewayOptions.category = options.category;\n\t}\n\tthis.gateway = new PhotoListGateway( gatewayOptions );\n\t// Set up infinite scroll\n\tthis.scrollEndEventEmitter = new ScrollEndEventEmitter( options.eventBus, 1000 );\n\tthis.scrollEndEventEmitter.on( ScrollEndEventEmitter.EVENT_SCROLL_END,\n\t\tthis._loadPhotos.bind( this ) );\n\tView.call( this, options );\n}\n\nmfExtend( PhotoList, View, {\n\t/**\n\t * @memberof PhotoList\n\t * @instance\n\t */\n\ttemplate: mw.template.get( 'mobile.special.uploads.scripts', 'PhotoList.hogan' ),\n\t/**\n\t * @memberof PhotoList\n\t * @instance\n\t * @mixes View#defaults\n\t * @property {Object} defaults Default options hash.\n\t * @property {string} defaults.spinner HTML of the spinner icon.\n\t * @property {mw.Api} defaults.api instance of an api\n\t */\n\tdefaults: {\n\t\tspinner: icons.spinner().toHtmlString()\n\t},\n\t/**\n\t * @inheritdoc\n\t * @memberof PhotoList\n\t * @instance\n\t */\n\tpreRender: function () {\n\t\t// Disable until we've got the list rendered\n\t\tthis.scrollEndEventEmitter.setElement( this.$el );\n\t\tthis.scrollEndEventEmitter.disable();\n\t},\n\t/**\n\t * @inheritdoc\n\t * @memberof PhotoList\n\t * @instance\n\t */\n\tpostRender: function () {\n\t\tthis.$end = this.$el.find( '.end' );\n\t\tthis.$list = this.$el.find( 'ul' );\n\n\t\tthis._loadPhotos();\n\t},\n\t/**\n\t * Check to see if the current view is an empty list.\n\t * @memberof PhotoList\n\t * @instance\n\t * @return {boolean} whether no images have been rendered\n\t */\n\tisEmpty: function () {\n\t\treturn this.$list.find( 'li' ).length === 0;\n\t},\n\t/**\n\t * Renders an empty message prior to the list.\n\t * FIXME: Should be handled in template, not a method.\n\t * @memberof PhotoList\n\t * @instance\n\t */\n\tshowEmptyMessage: function () {\n\t\tthis.parseHTML( '<p class=\"content empty\">' ).text( mw.msg( 'mobile-frontend-donate-image-nouploads' ) )\n\t\t\t.insertBefore( this.$list );\n\t},\n\t/**\n\t * Hides the message saying the list is empty\n\t * FIXME: Should be handled in template, not a method.\n\t * @memberof PhotoList\n\t * @instance\n\t */\n\thideEmptyMessage: function () {\n\t\tthis.$el.find( '.empty' ).hide();\n\t},\n\t/**\n\t * Shows loading spinner\n\t * @memberof PhotoList\n\t * @instance\n\t */\n\tshowSpinner: function () {\n\t\tthis.$end.show();\n\t},\n\t/**\n\t * Hides loading spinner\n\t * @memberof PhotoList\n\t * @instance\n\t */\n\thideSpinner: function () {\n\t\tthis.$end.hide();\n\t},\n\t/**\n\t * Shows/hides empty state if PhotoList is empty.\n\t * @memberof PhotoList\n\t * @instance\n\t */\n\tupdateEmptyUI: function () {\n\t\tif ( this.isEmpty() ) {\n\t\t\tthis.showEmptyMessage();\n\t\t} else {\n\t\t\tthis.hideEmptyMessage();\n\t\t}\n\t},\n\t/**\n\t * Append an array of photos to the view.\n\t * @memberof PhotoList\n\t * @instance\n\t * @param {Array} photosData Array of objects describing a new {PhotoItem}\n\t */\n\tappendPhotos: function ( photosData ) {\n\t\tvar self = this;\n\t\tphotosData.forEach( function ( photo ) {\n\t\t\tnew PhotoItem( photo ).appendTo( self.$list );\n\t\t} );\n\t},\n\t/**\n\t * Enables infinite scroll if it's disabled\n\t * @memberof PhotoList\n\t * @instance\n\t */\n\tenableScroll: function () {\n\t\tif ( this.scrollEndEventEmitter.enabled === false ) {\n\t\t\tthis.scrollEndEventEmitter.enable();\n\t\t}\n\t},\n\t/**\n\t * Load photos into the view using {{PhotoListApi}} when the end is near\n\t * and no current API requests are underway.\n\t * @memberof PhotoList\n\t * @instance\n\t * @private\n\t */\n\t_loadPhotos: function () {\n\t\tvar self = this;\n\n\t\tself.showSpinner();\n\n\t\tthis.gateway.getPhotos().then( function ( response ) {\n\t\t\tvar photos = response.photos || [],\n\t\t\t\tcanContinue = response.canContinue;\n\n\t\t\tself.appendPhotos( photos );\n\t\t\tself.updateEmptyUI();\n\t\t\tif ( canContinue ) {\n\t\t\t\tself.enableScroll();\n\t\t\t}\n\n\t\t\tself.hideSpinner();\n\t\t} ).catch( function () {\n\t\t\tself.updateEmptyUI();\n\t\t\tself.hideSpinner();\n\n\t\t\t// try loading again if request failed\n\t\t\tself.enableScroll();\n\t\t} );\n\t}\n} );\n\nmodule.exports = PhotoList;\n","var util = require( '../mobile.startup/util' );\n\n/**\n * API for retrieving gallery photos\n * @class PhotoListGateway\n *\n * @param {Object} options Configuration options\n * @param {mw.Api} options.api\n * @param {string} options.url for overriding default URI for API queries\n */\nfunction PhotoListGateway( options ) {\n\tthis.api = options.api;\n\tthis.url = options.url;\n\tthis.username = options.username;\n\tthis.category = options.category;\n\tthis.limit = 10;\n\tthis.continueParams = {\n\t\tcontinue: ''\n\t};\n\tthis.canContinue = true;\n}\n\n/**\n * Returns a description based on the file name using\n * a regular expression that strips the file type suffix,\n * namespace prefix and any\n * date suffix in format YYYY-MM-DD HH-MM\n * @memberof PhotoListGateway\n * @instance\n * @private\n * @param {string} title Title of file\n * @return {string} Description for file\n */\nfunction getDescription( title ) {\n\ttitle = title.replace( /\\.[^. ]+$/, '' ); // replace filename suffix\n\t// strip namespace: prefix and date suffix from remainder\n\treturn title.replace( /^[^:]*:/, '' )\n\t\t.replace( / \\d{4}-\\d{1,2}-\\d{1,2} \\d{1,2}-\\d{1,2}$/, '' );\n}\n\nPhotoListGateway.prototype = {\n\t/**\n\t * Returns the value in pixels of a medium thumbnail\n\t * @memberof PhotoListGateway\n\t * @instance\n\t * @return {number}\n\t */\n\tgetWidth: function () {\n\t\treturn mw.config.get( 'wgMFThumbnailSizes' ).small;\n\t},\n\t/**\n\t * Extracts image data from api response\n\t * @memberof PhotoListGateway\n\t * @instance\n\t * @private\n\t * @param {Object} page as returned by api request\n\t * @return {Object} describing image.\n\t */\n\t_getImageDataFromPage: function ( page ) {\n\t\tvar img = page.imageinfo[0];\n\t\treturn {\n\t\t\turl: img.thumburl,\n\t\t\ttitle: page.title,\n\t\t\ttimestamp: img.timestamp,\n\t\t\tdescription: getDescription( page.title ),\n\t\t\tdescriptionUrl: img.descriptionurl\n\t\t};\n\t},\n\t/**\n\t * Get the associated query needed to retrieve images from API based\n\t * on currently configured options.\n\t * @memberof PhotoListGateway\n\t * @instance\n\t * @return {Object}\n\t */\n\tgetQuery: function () {\n\t\tvar query = util.extend( {\n\t\t\taction: 'query',\n\t\t\tprop: 'imageinfo',\n\t\t\t// FIXME: [API] have to request timestamp since api returns an object\n\t\t\t// rather than an array thus we need a way to sort\n\t\t\tiiprop: 'url|timestamp',\n\t\t\tiiurlwidth: this.getWidth()\n\t\t}, this.continueParams );\n\n\t\tif ( this.username ) {\n\t\t\tutil.extend( query, {\n\t\t\t\tgenerator: 'allimages',\n\t\t\t\tgaiuser: this.username,\n\t\t\t\tgaisort: 'timestamp',\n\t\t\t\tgaidir: 'descending',\n\t\t\t\tgailimit: this.limit\n\t\t\t} );\n\t\t} else if ( this.category ) {\n\t\t\tutil.extend( query, {\n\t\t\t\tgenerator: 'categorymembers',\n\t\t\t\tgcmtitle: 'Category:' + this.category,\n\t\t\t\tgcmtype: 'file',\n\t\t\t\t// FIXME [API] a lot of duplication follows due to the silly way generators work\n\t\t\t\tgcmdir: 'descending',\n\t\t\t\tgcmlimit: this.limit\n\t\t\t} );\n\t\t}\n\n\t\tif ( this.url ) {\n\t\t\t// A foreign api is being accessed! Enable anonymous CORS queries!\n\t\t\tquery.origin = '*';\n\t\t}\n\t\treturn query;\n\t},\n\t/**\n\t * Request photos beginning with the current value of endTimestamp\n\t * @memberof PhotoListGateway\n\t * @instance\n\t * @return {jQuery.Deferred} where parameter is a list of JavaScript\n\t *  objects describing an image.\n\t */\n\tgetPhotos: function () {\n\t\tvar self = this;\n\n\t\treturn this.api.ajax( this.getQuery(), { url: this.url } ).then( function ( resp ) {\n\t\t\tvar photos = [];\n\t\t\tif ( resp.query && resp.query.pages ) {\n\t\t\t\t// FIXME: [API] in an ideal world imageData would be a sorted array\n\t\t\t\t// but it is a map of {[id]: page}\n\t\t\t\tphotos = Object.keys( resp.query.pages ).map( function ( id ) {\n\t\t\t\t\treturn self._getImageDataFromPage( resp.query.pages[id] );\n\t\t\t\t} ).sort( function ( a, b ) {\n\t\t\t\t\treturn a.timestamp < b.timestamp ? 1 : -1;\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\tif ( resp.continue !== undefined ) {\n\t\t\t\tself.continueParams = resp.continue;\n\t\t\t} else {\n\t\t\t\tself.canContinue = false;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcanContinue: self.canContinue,\n\t\t\t\t// FIXME: Should reply with a list of PhotoItem or Photo classes.\n\t\t\t\tphotos: photos\n\t\t\t};\n\t\t} );\n\t}\n};\n\nPhotoListGateway.test = {\n\tgetDescription: getDescription\n};\n\nmodule.exports = PhotoListGateway;\n","/* global $ */\nvar\n\tuser = mw.user,\n\tPhotoList = require( './PhotoList' ),\n\teventBus = require( '../mobile.startup/eventBusSingleton' ),\n\tpageParams = mw.config.get( 'wgPageName' ).split( '/' ),\n\tcurrentUserName = user.getName(),\n\tcorsUrl = mw.config.get( 'wgMFPhotoUploadEndpoint' ) || undefined,\n\tuserName = pageParams[1] ? pageParams[1] : currentUserName;\n\n/**\n * Initialise a photo upload button at the top of the page.\n * @param {mw.Api} api\n */\nfunction init( api ) {\n\t// check there are no errors on the page before attempting\n\t// we might have an invalid username\n\tif ( $( '.errorbox' ).length === 0 ) {\n\t\tnew PhotoList( {\n\t\t\turl: corsUrl,\n\t\t\tapi: api,\n\t\t\tusername: userName,\n\t\t\teventBus: eventBus\n\t\t} ).appendTo( '#mw-content-text .content' );\n\t}\n}\n\n// Assume we are on the special page.\nif ( userName ) {\n\t$( function () {\n\t\tinit( new mw.Api() );\n\t} );\n}\n"],"sourceRoot":""}