mediawiki-extensions-Visual.../modules/ve-mw/ui/widgets/ve.ui.MWReferenceSearchWidget.js
Ed Sanders d8d3a8c98e Defer building of MWReferenceSearchWidget index
It can be very slow on pages with lots of references and
isn't required until you click 'Use existing reference'.

What is required is knowing if the list will be empty for
greying out the button, but that can be precomputed very cheaply.

Change-Id: I56909801a5685bb04e0c83cfb95463f705b8dfae
2014-07-23 15:12:31 +01:00

216 lines
5.4 KiB
JavaScript

/*!
* VisualEditor UserInterface MWReferenceSearchWidget class.
*
* @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Creates an ve.ui.MWReferenceSearchWidget object.
*
* @class
* @extends OO.ui.SearchWidget
*
* @constructor
* @param {Object} [config] Configuration options
*/
ve.ui.MWReferenceSearchWidget = function VeUiMWReferenceSearchWidget( config ) {
// Configuration intialization
config = ve.extendObject( {
'placeholder': ve.msg( 'visualeditor-reference-input-placeholder' )
}, config );
// Parent constructor
OO.ui.SearchWidget.call( this, config );
// Properties
this.index = [];
this.indexEmpty = true;
this.built = false;
// Initialization
this.$element.addClass( 've-ui-mwReferenceSearchWidget' );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWReferenceSearchWidget, OO.ui.SearchWidget );
/* Events */
/**
* @event select
* @param {ve.dm.MWReferenceModel|null} data Reference model, null if no item is selected
*/
/* Methods */
/**
* Handle query change events.
*
* @method
* @param {string} value New value
*/
ve.ui.MWReferenceSearchWidget.prototype.onQueryChange = function () {
// Parent method
OO.ui.SearchWidget.prototype.onQueryChange.call( this );
// Populate
this.addResults();
};
/**
* Handle select widget select events.
*
* @method
* @param {OO.ui.OptionWidget} item Selected item
* @fires select
*/
ve.ui.MWReferenceSearchWidget.prototype.onResultsSelect = function ( item ) {
var data;
if ( item ) {
data = item.getData();
// Resolve indexed values
if ( this.index[data] ) {
data = this.index[data].reference;
}
} else {
data = null;
}
this.emit( 'select', data );
};
/**
* Set the internal list and check if it contains any references
* @param {ve.dm.InternalList} internalList Internal list
*/
ve.ui.MWReferenceSearchWidget.prototype.setInternalList = function ( internalList ) {
var i, iLen, groupNames, groupName, groups = internalList.getNodeGroups();
this.internalList = internalList;
groupNames = ve.getObjectKeys( groups );
for ( i = 0, iLen = groupNames.length; i < iLen; i++ ) {
groupName = groupNames[i];
if ( groupName.lastIndexOf( 'mwReference/' ) !== 0 ) {
continue;
}
if ( groups[groupName].indexOrder.length ) {
this.indexEmpty = false;
return;
}
}
};
/**
* Build a serchable index of references.
*
* @method
*/
ve.ui.MWReferenceSearchWidget.prototype.buildIndex = function () {
if ( this.built ) {
return;
}
var i, iLen, j, jLen, ref, group, groupName, groupNames, view, text, firstNodes, indexOrder,
refGroup, refNode, matches, name, citation,
groups = this.internalList.getNodeGroups();
function extractAttrs() {
text += ' ' + this.getAttribute( 'href' );
}
this.index = [];
groupNames = ve.getObjectKeys( groups ).sort();
for ( i = 0, iLen = groupNames.length; i < iLen; i++ ) {
groupName = groupNames[i];
if ( groupName.lastIndexOf( 'mwReference/' ) !== 0 ) {
continue;
}
group = groups[groupName];
firstNodes = group.firstNodes;
indexOrder = group.indexOrder;
for ( j = 0, jLen = indexOrder.length; j < jLen; j++ ) {
refNode = firstNodes[indexOrder[j]];
ref = ve.dm.MWReferenceModel.static.newFromReferenceNode( refNode );
view = new ve.ce.InternalItemNode( this.internalList.getItemNode( ref.getListIndex() ) );
// HACK: PHP parser doesn't wrap single lines in a paragraph
if ( view.$element.children().length === 1 && view.$element.children( 'p' ).length === 1 ) {
// unwrap inner
view.$element.children().replaceWith( view.$element.children().contents() );
}
refGroup = ref.getGroup();
citation = ( refGroup && refGroup.length ? refGroup + ' ' : '' ) + ( j + 1 );
matches = ref.getListKey().match( /^literal\/(.*)$/ );
name = matches && matches[1] || '';
// Hide previously auto-generated reference names
if ( name.match( /^:[0-9]+$/ ) ) {
name = '';
}
// Make visible text, citation and reference name searchable
text = [ view.$element.text().toLowerCase(), citation, name ].join( ' ' );
// Make URLs searchable
view.$element.find( 'a[href]' ).each( extractAttrs );
this.index.push( {
'$element': view.$element.clone(),
'text': text,
'reference': ref,
'citation': citation,
'name': name
} );
view.destroy();
}
}
// Re-populate
this.onQueryChange();
this.built = true;
};
/**
* Check whether buildIndex will create an empty index based on the current internalList.
*
* @returns {boolean} Index is empty
*/
ve.ui.MWReferenceSearchWidget.prototype.isIndexEmpty = function () {
return this.indexEmpty;
};
/**
* Handle media query response events.
*
* @method
*/
ve.ui.MWReferenceSearchWidget.prototype.addResults = function () {
var i, len, item, $citation, $name,
value = this.query.getValue(),
query = value.toLowerCase(),
items = [];
for ( i = 0, len = this.index.length; i < len; i++ ) {
item = this.index[i];
if ( item.text.indexOf( query ) >= 0 ) {
$citation = this.$( '<div>' )
.addClass( 've-ui-mwReferenceSearchWidget-citation' )
.text( '[' + item.citation + ']' );
$name = this.$( '<div>' )
.addClass( 've-ui-mwReferenceSearchWidget-name' )
.text( item.name );
items.push(
new ve.ui.MWReferenceResultWidget( i, {
'$': this.$, 'label': $citation.add( $name ).add( item.$element )
} )
);
}
}
this.results.addItems( items );
};