mediawiki-extensions-Visual.../modules/ve-mw/ui/widgets/ve.ui.MWMediaSearchWidget.js
Thalia 7ec7acf108 Take only the last part of a URL query in media search widget
Quick fix to the problem that searching for the file name or
page title currently returns the image, but searching for the
URL does not.

Bug: T121354
Change-Id: I13e665226e5dc15ba626126dc4806ce8f4e0040b
2016-10-13 21:54:10 +00:00

456 lines
11 KiB
JavaScript

/*!
* VisualEditor UserInterface MWMediaSearchWidget class.
*
* @copyright 2011-2016 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Creates an ve.ui.MWMediaSearchWidget object.
*
* @class
* @extends OO.ui.SearchWidget
*
* @constructor
* @param {Object} [config] Configuration options
* @param {number} [size] Vertical size of thumbnails
*/
ve.ui.MWMediaSearchWidget = function VeUiMWMediaSearchWidget( config ) {
// Configuration initialization
config = ve.extendObject( {
placeholder: ve.msg( 'visualeditor-media-input-placeholder' )
}, config );
// Parent constructor
ve.ui.MWMediaSearchWidget.super.call( this, config );
// Properties
this.providers = {};
this.lastQueryValue = '';
this.searchQueue = new ve.dm.MWMediaSearchQueue( {
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;
// 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: ve.msg( 'visualeditor-dialog-media-noresults' ),
classes: [ 've-ui-mwMediaSearchWidget-noresults' ]
} );
this.noItemsMessage.toggle( false );
// Events
this.$results.on( 'scroll', this.onResultsScroll.bind( this ) );
this.$query.append( this.noItemsMessage.$element );
this.results.connect( this, {
add: 'onResultsAdd',
remove: 'onResultsRemove'
} );
this.resizeHandler = ve.debounce( this.afterResultsResize.bind( this ), 500 );
// Initialization
this.$element.addClass( 've-ui-mwMediaSearchWidget' );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWMediaSearchWidget, OO.ui.SearchWidget );
/* Static properties */
ve.ui.MWMediaSearchWidget.static.limit = 10;
ve.ui.MWMediaSearchWidget.static.threshold = 5;
/* Methods */
/**
* Respond to window resize and check if the result display should
* be updated.
*/
ve.ui.MWMediaSearchWidget.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.
*/
ve.ui.MWMediaSearchWidget.prototype.teardown = function () {
$( window ).off( 'resize', this.resizeHandler );
};
/**
* Setup the widget; activate the resize event.
*/
ve.ui.MWMediaSearchWidget.prototype.setup = function () {
$( window ).on( 'resize', this.resizeHandler );
};
/**
* Query all sources for media.
*
* @method
*/
ve.ui.MWMediaSearchWidget.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
*/
ve.ui.MWMediaSearchWidget.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 ve.ui.MWMediaResultWidget( {
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
*/
ve.ui.MWMediaSearchWidget.prototype.getQueryValue = function () {
var queryValue = this.query.getValue().trim();
if ( queryValue.match( ve.init.platform.getExternalLinkUrlProtocolsRegExp() ) ) {
queryValue = queryValue.match( /.+\/([^\/]+)/ )[ 1 ];
}
return queryValue;
};
/**
* Handle search value change
*
* @param {string} value New value
*/
ve.ui.MWMediaSearchWidget.prototype.onQueryChange = function () {
// Get the sanitized query value
var queryValue = this.getQueryValue();
if ( queryValue === this.lastQueryValue ) {
return;
}
// Parent method
ve.ui.MWMediaSearchWidget.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
*/
ve.ui.MWMediaSearchWidget.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.
*/
ve.ui.MWMediaSearchWidget.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.
*/
ve.ui.MWMediaSearchWidget.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
*/
ve.ui.MWMediaSearchWidget.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( 've-ui-mwMediaResultWidget-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( 've-ui-mwMediaResultWidget-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 add 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 {ve.ui.MWMediaResultWidget[]} items An array of item elements
*/
ve.ui.MWMediaSearchWidget.prototype.onResultsAdd = function ( items ) {
var search = this;
// 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++ ) {
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.
*/
ve.ui.MWMediaSearchWidget.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
*/
ve.ui.MWMediaSearchWidget.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
*/
ve.ui.MWMediaSearchWidget.prototype.setLang = function ( lang ) {
this.lang = lang;
};
/**
* Get language for the search results.
*
* @return {string} lang Language
*/
ve.ui.MWMediaSearchWidget.prototype.getLang = function () {
return this.lang;
};