summaryrefslogtreecommitdiff
path: root/www/wiki/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js')
-rw-r--r--www/wiki/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js473
1 files changed, 473 insertions, 0 deletions
diff --git a/www/wiki/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js b/www/wiki/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js
new file mode 100644
index 00000000..08266f09
--- /dev/null
+++ b/www/wiki/resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js
@@ -0,0 +1,473 @@
+/*!
+ * MediaWiki Widgets - MediaSearchWidget class.
+ *
+ * @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
+ * @license The MIT License (MIT); see LICENSE.txt
+ */
+( function ( $, mw ) {
+
+ /**
+ * Creates an mw.widgets.MediaSearchWidget object.
+ *
+ * @class
+ * @extends OO.ui.SearchWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @param {number} [size] Vertical size of thumbnails
+ */
+ mw.widgets.MediaSearchWidget = function MwWidgetsMediaSearchWidget( config ) {
+ // Configuration initialization
+ config = $.extend( {
+ placeholder: mw.msg( 'mw-widgets-mediasearch-input-placeholder' )
+ }, config );
+
+ // Parent constructor
+ mw.widgets.MediaSearchWidget.super.call( this, config );
+
+ // Properties
+ this.providers = {};
+ this.lastQueryValue = '';
+ this.searchQueue = new mw.widgets.MediaSearchQueue( {
+ limit: this.constructor.static.limit,
+ threshold: this.constructor.static.threshold
+ } );
+
+ this.queryTimeout = null;
+ this.itemCache = {};
+ this.promises = [];
+ this.lang = config.lang || 'en';
+ this.$panels = config.$panels;
+
+ this.externalLinkUrlProtocolsRegExp = new RegExp(
+ '^(' + mw.config.get( 'wgUrlProtocols' ) + ')',
+ 'i'
+ );
+
+ // Masonry fit properties
+ this.rows = [];
+ this.rowHeight = config.rowHeight || 200;
+ this.layoutQueue = [];
+ this.numItems = 0;
+ this.currentItemCache = [];
+
+ this.resultsSize = {};
+
+ this.selected = null;
+
+ this.noItemsMessage = new OO.ui.LabelWidget( {
+ label: mw.msg( 'mw-widgets-mediasearch-noresults' ),
+ classes: [ 'mw-widget-mediaSearchWidget-noresults' ]
+ } );
+ this.noItemsMessage.toggle( false );
+
+ // Events
+ this.$results.on( 'scroll', this.onResultsScroll.bind( this ) );
+ this.$query.append( this.noItemsMessage.$element );
+ this.results.connect( this, {
+ change: 'onResultsChange',
+ remove: 'onResultsRemove'
+ } );
+
+ this.resizeHandler = OO.ui.debounce( this.afterResultsResize.bind( this ), 500 );
+
+ // Initialization
+ this.$element.addClass( 'mw-widget-mediaSearchWidget' );
+ };
+
+ /* Inheritance */
+
+ OO.inheritClass( mw.widgets.MediaSearchWidget, OO.ui.SearchWidget );
+
+ /* Static properties */
+
+ mw.widgets.MediaSearchWidget.static.limit = 10;
+
+ mw.widgets.MediaSearchWidget.static.threshold = 5;
+
+ /* Methods */
+
+ /**
+ * Respond to window resize and check if the result display should
+ * be updated.
+ */
+ mw.widgets.MediaSearchWidget.prototype.afterResultsResize = function () {
+ var items = this.currentItemCache;
+
+ if (
+ items.length > 0 &&
+ (
+ this.resultsSize.width !== this.$results.width() ||
+ this.resultsSize.height !== this.$results.height()
+ )
+ ) {
+ this.resetRows();
+ this.itemCache = {};
+ this.processQueueResults( items );
+ if ( this.results.getItems().length > 0 ) {
+ this.lazyLoadResults();
+ }
+
+ // Cache the size
+ this.resultsSize = {
+ width: this.$results.width(),
+ height: this.$results.height()
+ };
+ }
+ };
+
+ /**
+ * Teardown the widget; disconnect the window resize event.
+ */
+ mw.widgets.MediaSearchWidget.prototype.teardown = function () {
+ $( window ).off( 'resize', this.resizeHandler );
+ };
+
+ /**
+ * Setup the widget; activate the resize event.
+ */
+ mw.widgets.MediaSearchWidget.prototype.setup = function () {
+ $( window ).on( 'resize', this.resizeHandler );
+ };
+
+ /**
+ * Query all sources for media.
+ *
+ * @method
+ */
+ mw.widgets.MediaSearchWidget.prototype.queryMediaQueue = function () {
+ var search = this,
+ value = this.getQueryValue();
+
+ if ( value === '' ) {
+ return;
+ }
+
+ this.query.pushPending();
+ search.noItemsMessage.toggle( false );
+
+ this.searchQueue.setSearchQuery( value );
+ this.searchQueue.get( this.constructor.static.limit )
+ .then( function ( items ) {
+ if ( items.length > 0 ) {
+ search.processQueueResults( items );
+ search.currentItemCache = search.currentItemCache.concat( items );
+ }
+
+ search.query.popPending();
+ search.noItemsMessage.toggle( search.results.getItems().length === 0 );
+ if ( search.results.getItems().length > 0 ) {
+ search.lazyLoadResults();
+ }
+
+ } );
+ };
+
+ /**
+ * Process the media queue giving more items
+ *
+ * @method
+ * @param {Object[]} items Given items by the media queue
+ */
+ mw.widgets.MediaSearchWidget.prototype.processQueueResults = function ( items ) {
+ var i, len, title,
+ resultWidgets = [],
+ inputSearchQuery = this.getQueryValue(),
+ queueSearchQuery = this.searchQueue.getSearchQuery();
+
+ if ( inputSearchQuery === '' || queueSearchQuery !== inputSearchQuery ) {
+ return;
+ }
+
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ title = new mw.Title( items[ i ].title ).getMainText();
+ // Do not insert duplicates
+ if ( !Object.prototype.hasOwnProperty.call( this.itemCache, title ) ) {
+ this.itemCache[ title ] = true;
+ resultWidgets.push(
+ new mw.widgets.MediaResultWidget( {
+ data: items[ i ],
+ rowHeight: this.rowHeight,
+ maxWidth: this.results.$element.width() / 3,
+ minWidth: 30,
+ rowWidth: this.results.$element.width()
+ } )
+ );
+ }
+ }
+ this.results.addItems( resultWidgets );
+
+ };
+
+ /**
+ * Get the sanitized query value from the input
+ *
+ * @return {string} Query value
+ */
+ mw.widgets.MediaSearchWidget.prototype.getQueryValue = function () {
+ var queryValue = this.query.getValue().trim();
+
+ if ( queryValue.match( this.externalLinkUrlProtocolsRegExp ) ) {
+ queryValue = queryValue.match( /.+\/([^/]+)/ )[ 1 ];
+ }
+ return queryValue;
+ };
+
+ /**
+ * Handle search value change
+ *
+ * @param {string} value New value
+ */
+ mw.widgets.MediaSearchWidget.prototype.onQueryChange = function () {
+ // Get the sanitized query value
+ var queryValue = this.getQueryValue();
+
+ if ( queryValue === this.lastQueryValue ) {
+ return;
+ }
+
+ // Parent method
+ mw.widgets.MediaSearchWidget.super.prototype.onQueryChange.apply( this, arguments );
+
+ // Reset
+ this.itemCache = {};
+ this.currentItemCache = [];
+ this.resetRows();
+
+ // Empty the results queue
+ this.layoutQueue = [];
+
+ // Change resource queue query
+ this.searchQueue.setSearchQuery( queryValue );
+ this.lastQueryValue = queryValue;
+
+ // Queue
+ clearTimeout( this.queryTimeout );
+ this.queryTimeout = setTimeout( this.queryMediaQueue.bind( this ), 350 );
+ };
+
+ /**
+ * Handle results scroll events.
+ *
+ * @param {jQuery.Event} e Scroll event
+ */
+ mw.widgets.MediaSearchWidget.prototype.onResultsScroll = function () {
+ var position = this.$results.scrollTop() + this.$results.outerHeight(),
+ threshold = this.results.$element.outerHeight() - this.rowHeight * 3;
+
+ // Check if we need to ask for more results
+ if ( !this.query.isPending() && position > threshold ) {
+ this.queryMediaQueue();
+ }
+
+ this.lazyLoadResults();
+ };
+
+ /**
+ * Lazy-load the images that are visible.
+ */
+ mw.widgets.MediaSearchWidget.prototype.lazyLoadResults = function () {
+ var i, elementTop,
+ items = this.results.getItems(),
+ resultsScrollTop = this.$results.scrollTop(),
+ position = resultsScrollTop + this.$results.outerHeight();
+
+ // Lazy-load results
+ for ( i = 0; i < items.length; i++ ) {
+ elementTop = items[ i ].$element.position().top;
+ if ( elementTop <= position && !items[ i ].hasSrc() ) {
+ // Load the image
+ items[ i ].lazyLoad();
+ }
+ }
+ };
+
+ /**
+ * Reset all the rows; destroy the jQuery elements and reset
+ * the rows array.
+ */
+ mw.widgets.MediaSearchWidget.prototype.resetRows = function () {
+ var i, len;
+
+ for ( i = 0, len = this.rows.length; i < len; i++ ) {
+ this.rows[ i ].$element.remove();
+ }
+
+ this.rows = [];
+ this.itemCache = {};
+ };
+
+ /**
+ * Find an available row at the end. Either we will need to create a new
+ * row or use the last available row if it isn't full.
+ *
+ * @return {number} Row index
+ */
+ mw.widgets.MediaSearchWidget.prototype.getAvailableRow = function () {
+ var row;
+
+ if ( this.rows.length === 0 ) {
+ row = 0;
+ } else {
+ row = this.rows.length - 1;
+ }
+
+ if ( !this.rows[ row ] ) {
+ // Create new row
+ this.rows[ row ] = {
+ isFull: false,
+ width: 0,
+ items: [],
+ $element: $( '<div>' )
+ .addClass( 'mw-widget-mediaResultWidget-row' )
+ .css( {
+ overflow: 'hidden'
+ } )
+ .data( 'row', row )
+ .attr( 'data-full', false )
+ };
+ // Append to results
+ this.results.$element.append( this.rows[ row ].$element );
+ } else if ( this.rows[ row ].isFull ) {
+ row++;
+ // Create new row
+ this.rows[ row ] = {
+ isFull: false,
+ width: 0,
+ items: [],
+ $element: $( '<div>' )
+ .addClass( 'mw-widget-mediaResultWidget-row' )
+ .css( {
+ overflow: 'hidden'
+ } )
+ .data( 'row', row )
+ .attr( 'data-full', false )
+ };
+ // Append to results
+ this.results.$element.append( this.rows[ row ].$element );
+ }
+
+ return row;
+ };
+
+ /**
+ * Respond to change results event in the results widget.
+ * Override the way SelectWidget and GroupElement append the items
+ * into the group so we can append them in groups of rows.
+ *
+ * @param {mw.widgets.MediaResultWidget[]} items An array of item elements
+ */
+ mw.widgets.MediaSearchWidget.prototype.onResultsChange = function ( items ) {
+ var search = this;
+
+ if ( !items.length ) {
+ return;
+ }
+
+ // Add method to a queue; this queue will only run when the widget
+ // is visible
+ this.layoutQueue.push( function () {
+ var i, j, ilen, jlen, itemWidth, row, effectiveWidth,
+ resizeFactor,
+ maxRowWidth = search.results.$element.width() - 15;
+
+ // Go over the added items
+ row = search.getAvailableRow();
+ for ( i = 0, ilen = items.length; i < ilen; i++ ) {
+
+ // Check item has just been added
+ if ( items[ i ].row !== null ) {
+ continue;
+ }
+
+ itemWidth = items[ i ].$element.outerWidth( true );
+
+ // Add items to row until it is full
+ if ( search.rows[ row ].width + itemWidth >= maxRowWidth ) {
+ // Mark this row as full
+ search.rows[ row ].isFull = true;
+ search.rows[ row ].$element.attr( 'data-full', true );
+
+ // Find the resize factor
+ effectiveWidth = search.rows[ row ].width;
+ resizeFactor = maxRowWidth / effectiveWidth;
+
+ search.rows[ row ].$element.attr( 'data-effectiveWidth', effectiveWidth );
+ search.rows[ row ].$element.attr( 'data-resizeFactor', resizeFactor );
+ search.rows[ row ].$element.attr( 'data-row', row );
+
+ // Resize all images in the row to fit the width
+ for ( j = 0, jlen = search.rows[ row ].items.length; j < jlen; j++ ) {
+ search.rows[ row ].items[ j ].resizeThumb( resizeFactor );
+ }
+
+ // find another row
+ row = search.getAvailableRow();
+ }
+
+ // Add the cumulative
+ search.rows[ row ].width += itemWidth;
+
+ // Store reference to the item and to the row
+ search.rows[ row ].items.push( items[ i ] );
+ items[ i ].setRow( row );
+
+ // Append the item
+ search.rows[ row ].$element.append( items[ i ].$element );
+
+ }
+
+ // If we have less than 4 rows, call for more images
+ if ( search.rows.length < 4 ) {
+ search.queryMediaQueue();
+ }
+ } );
+ this.runLayoutQueue();
+ };
+
+ /**
+ * Run layout methods from the queue only if the element is visible.
+ */
+ mw.widgets.MediaSearchWidget.prototype.runLayoutQueue = function () {
+ var i, len;
+
+ if ( this.$element.is( ':visible' ) ) {
+ for ( i = 0, len = this.layoutQueue.length; i < len; i++ ) {
+ this.layoutQueue.pop()();
+ }
+ }
+ };
+
+ /**
+ * Respond to removing results event in the results widget.
+ * Clear the relevant rows.
+ *
+ * @param {OO.ui.OptionWidget[]} items Removed items
+ */
+ mw.widgets.MediaSearchWidget.prototype.onResultsRemove = function ( items ) {
+ if ( items.length > 0 ) {
+ // In the case of the media search widget, if any items are removed
+ // all are removed (new search)
+ this.resetRows();
+ this.currentItemCache = [];
+ }
+ };
+
+ /**
+ * Set language for the search results.
+ *
+ * @param {string} lang Language
+ */
+ mw.widgets.MediaSearchWidget.prototype.setLang = function ( lang ) {
+ this.lang = lang;
+ };
+
+ /**
+ * Get language for the search results.
+ *
+ * @return {string} lang Language
+ */
+ mw.widgets.MediaSearchWidget.prototype.getLang = function () {
+ return this.lang;
+ };
+}( jQuery, mediaWiki ) );