mediawiki-extensions-Visual.../modules/ve-mw/ui/widgets/ve.ui.MWReferenceSearchWidget.js
Alex Monk 79d953f6b0 Only build reference search index when it's been changed
Using the same tricks as ve.ce.MWReferenceListNode... Like listening to list
node updates, which will probably catch unrelated changes? I'm not sure about
this.

Bug: 68890
Change-Id: Id9443c9574063933bc8fb35d09ab8b80b69bcd44
2014-08-04 14:05:36 +01:00

250 lines
6.3 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();
if ( this.results.getSelectedItem() ) {
this.results.getSelectedItem().setSelected( false );
}
this.internalList = internalList;
this.internalList.connect( this, { 'update': 'onInternalListUpdate' } );
this.internalList.getListNode().connect( this, { 'update': 'onListNodeUpdate' } );
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;
}
}
};
/**
* Handle the updating of the InternalList object.
*
* This will occur after a document transaction.
*
* @method
* @param {string[]} groupsChanged A list of groups which have changed in this transaction
*/
ve.ui.MWReferenceSearchWidget.prototype.onInternalListUpdate = function ( groupsChanged ) {
for ( var i = 0, len = groupsChanged.length; i < len; i++ ) {
if ( groupsChanged[i].indexOf( 'mwReference/' ) === 0 ) {
this.built = false;
break;
}
}
};
/**
* Handle the updating of the InternalListNode.
*
* This will occur after changes to any InternalItemNode.
*
* @method
*/
ve.ui.MWReferenceSearchWidget.prototype.onListNodeUpdate = function () {
this.built = false;
};
/**
* Build a searchable 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,
'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 );
};