mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Cite
synced 2024-11-16 03:23:22 +00:00
18f616b9b8
This code has been developed over three years now in the repo of MediaWiki's integration of VisualEditor. It has grown and developed significantly during that time, but now is pretty stable. A number of hacks inside the MediaWiki- VisualEditor code base have been used to prevent this code from being loaded on wikis where the Cite extension is not deployed, but this state of affairs is and always was meant to be temporary. This code is under the MIT licence which is a tad messy, but not impossible. It's clearly labelled as such. The list of authors has been updated to take into account the influx of new functionality. Bug: T41621 Bug: T104928 Change-Id: I39936ed83d5a60471a0a75da753f498e80aef234
234 lines
6 KiB
JavaScript
234 lines
6 KiB
JavaScript
/*!
|
|
* VisualEditor UserInterface MWReferenceSearchWidget class.
|
|
*
|
|
* @copyright 2011-2016 Cite 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 initialization
|
|
config = ve.extendObject( {
|
|
placeholder: ve.msg( 'cite-ve-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 );
|
|
|
|
/* 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();
|
|
};
|
|
|
|
/**
|
|
* 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 = Object.keys( 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;
|
|
}
|
|
}
|
|
this.indexEmpty = true;
|
|
};
|
|
|
|
/**
|
|
* 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 ) {
|
|
var i, len;
|
|
for ( 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 () {
|
|
var n, i, iLen, j, jLen, refModel, group, groupName, groupNames, view, text, firstNodes, indexOrder,
|
|
refGroup, refNode, matches, name, citation,
|
|
groups = this.internalList.getNodeGroups();
|
|
|
|
if ( this.built ) {
|
|
return;
|
|
}
|
|
|
|
function extractAttrs() {
|
|
text += ' ' + this.getAttribute( 'href' );
|
|
}
|
|
|
|
this.index = [];
|
|
groupNames = Object.keys( 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;
|
|
|
|
n = 0;
|
|
for ( j = 0, jLen = indexOrder.length; j < jLen; j++ ) {
|
|
refNode = firstNodes[ indexOrder[ j ] ];
|
|
// Exclude placeholder references
|
|
if ( refNode.getAttribute( 'placeholder' ) ) {
|
|
continue;
|
|
}
|
|
// Only increment counter for real references
|
|
n++;
|
|
refModel = ve.dm.MWReferenceModel.static.newFromReferenceNode( refNode );
|
|
view = new ve.ui.MWPreviewElement(
|
|
refModel.getDocument().getInternalList().getItemNode( refModel.getListIndex() )
|
|
);
|
|
|
|
refGroup = refModel.getGroup();
|
|
citation = ( refGroup && refGroup.length ? refGroup + ' ' : '' ) + n;
|
|
matches = refModel.getListKey().match( /^literal\/(.*)$/ );
|
|
name = matches && matches[ 1 ] || '';
|
|
// Hide previously auto-generated reference names
|
|
if ( name.match( /^:[0-9]+$/ ) ) {
|
|
name = '';
|
|
}
|
|
|
|
// TODO: At some point we need to make sure this text is updated in
|
|
// case the view node is still rendering. This shouldn't happen because
|
|
// all references are supposed to be in the store and therefore are
|
|
// immediately rendered, but we shouldn't trust that on principle to
|
|
// account for edge cases.
|
|
|
|
// 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: refModel,
|
|
citation: citation,
|
|
name: name
|
|
} );
|
|
}
|
|
}
|
|
|
|
// Re-populate
|
|
this.onQueryChange();
|
|
|
|
this.built = true;
|
|
};
|
|
|
|
/**
|
|
* Check whether buildIndex will create an empty index based on the current internalList.
|
|
*
|
|
* @return {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 = $( '<div>' )
|
|
.addClass( 've-ui-mwReferenceSearchWidget-citation' )
|
|
.text( '[' + item.citation + ']' );
|
|
$name = $( '<div>' )
|
|
.addClass( 've-ui-mwReferenceSearchWidget-name' )
|
|
.text( item.name );
|
|
items.push(
|
|
new ve.ui.MWReferenceResultWidget( {
|
|
data: item.reference,
|
|
label: $citation.add( $name ).add( item.$element )
|
|
} )
|
|
);
|
|
}
|
|
}
|
|
|
|
this.results.addItems( items );
|
|
};
|