2023-08-21 08:08:23 +00:00
|
|
|
'use strict';
|
|
|
|
|
2016-02-03 21:03:41 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor UserInterface MWReferenceSearchWidget class.
|
|
|
|
*
|
2018-01-03 01:05:45 +00:00
|
|
|
* @copyright 2011-2018 VisualEditor Team's Cite sub-team and others; see AUTHORS.txt
|
2017-12-29 12:12:35 +00:00
|
|
|
* @license MIT
|
2016-02-03 21:03:41 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an ve.ui.MWReferenceSearchWidget object.
|
|
|
|
*
|
|
|
|
* @constructor
|
2024-02-28 08:57:24 +00:00
|
|
|
* @extends OO.ui.SearchWidget
|
2016-02-03 21:03:41 +00:00
|
|
|
* @param {Object} [config] Configuration options
|
2024-02-22 20:07:39 +00:00
|
|
|
* @property {Object[]|null} index Null when the index needs to be rebuild
|
2016-02-03 21:03:41 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget = function VeUiMWReferenceSearchWidget( config ) {
|
|
|
|
// Configuration initialization
|
|
|
|
config = ve.extendObject( {
|
|
|
|
placeholder: ve.msg( 'cite-ve-reference-input-placeholder' )
|
|
|
|
}, config );
|
|
|
|
|
|
|
|
// Parent constructor
|
2016-11-02 12:43:14 +00:00
|
|
|
ve.ui.MWReferenceSearchWidget.super.call( this, config );
|
2016-02-03 21:03:41 +00:00
|
|
|
|
|
|
|
// Properties
|
2024-02-22 20:07:39 +00:00
|
|
|
this.index = null;
|
2024-04-22 09:34:46 +00:00
|
|
|
this.wasUsedActively = false;
|
2016-02-03 21:03:41 +00:00
|
|
|
|
|
|
|
// Initialization
|
|
|
|
this.$element.addClass( 've-ui-mwReferenceSearchWidget' );
|
2024-04-22 09:34:46 +00:00
|
|
|
this.$results.on( 'scroll', this.trackActiveUsage.bind( this ) );
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
|
|
|
OO.inheritClass( ve.ui.MWReferenceSearchWidget, OO.ui.SearchWidget );
|
|
|
|
|
2024-02-22 18:28:17 +00:00
|
|
|
/* Static Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {ve.dm.InternalList} internalList
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget.static.isIndexEmpty = function ( internalList ) {
|
|
|
|
const groups = internalList.getNodeGroups();
|
|
|
|
// Doing this live every time is cheap because it stops on the first non-empty group
|
|
|
|
for ( const groupName in groups ) {
|
|
|
|
if ( groupName.indexOf( 'mwReference/' ) === 0 && groups[ groupName ].indexOrder.length ) {
|
2024-07-18 07:07:55 +00:00
|
|
|
// No need to filter subrefs here, as it's impossible to have subrefs without parents
|
2024-02-22 18:28:17 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2016-02-03 21:03:41 +00:00
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.onQueryChange = function () {
|
|
|
|
// Parent method
|
2016-11-02 12:43:14 +00:00
|
|
|
ve.ui.MWReferenceSearchWidget.super.prototype.onQueryChange.call( this );
|
2016-02-03 21:03:41 +00:00
|
|
|
|
|
|
|
// Populate
|
2024-02-22 20:08:02 +00:00
|
|
|
this.getResults().addItems( this.buildSearchResults( this.getQuery().getValue() ) );
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
|
|
|
|
2024-04-22 09:34:46 +00:00
|
|
|
/**
|
|
|
|
* @param {jQuery.Event} e Key down event
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.onQueryKeydown = function ( e ) {
|
|
|
|
// Parent method
|
|
|
|
ve.ui.MWReferenceSearchWidget.super.prototype.onQueryKeydown.call( this, e );
|
|
|
|
|
|
|
|
this.trackActiveUsage();
|
|
|
|
};
|
|
|
|
|
2024-07-16 14:06:07 +00:00
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.clearSearch = function () {
|
|
|
|
this.getQuery().setValue( '' );
|
|
|
|
this.wasUsedActively = false;
|
|
|
|
};
|
|
|
|
|
2024-04-22 09:34:46 +00:00
|
|
|
/**
|
|
|
|
* Track when the user looks for references to reuse using scrolling or filtering results
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.trackActiveUsage = function () {
|
|
|
|
if ( this.wasUsedActively ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://phabricator.wikimedia.org/T362347
|
2024-06-25 14:57:12 +00:00
|
|
|
ve.track( 'activity.reference', { action: 'reuse-dialog-use' } );
|
2024-04-22 09:34:46 +00:00
|
|
|
this.wasUsedActively = true;
|
|
|
|
};
|
|
|
|
|
2016-02-03 21:03:41 +00:00
|
|
|
/**
|
|
|
|
* 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 ) {
|
2024-02-22 17:58:01 +00:00
|
|
|
this.results.unselectItem();
|
2016-02-03 21:03:41 +00:00
|
|
|
|
|
|
|
this.internalList = internalList;
|
|
|
|
this.internalList.connect( this, { update: 'onInternalListUpdate' } );
|
|
|
|
this.internalList.getListNode().connect( this, { update: 'onListNodeUpdate' } );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle the updating of the InternalList object.
|
|
|
|
*
|
|
|
|
* This will occur after a document transaction.
|
|
|
|
*
|
|
|
|
* @param {string[]} groupsChanged A list of groups which have changed in this transaction
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.onInternalListUpdate = function ( groupsChanged ) {
|
2024-05-31 14:27:11 +00:00
|
|
|
if ( groupsChanged.some( ( groupName ) => groupName.indexOf( 'mwReference/' ) === 0 ) ) {
|
2024-02-22 20:07:39 +00:00
|
|
|
this.index = null;
|
2016-02-03 21:03:41 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle the updating of the InternalListNode.
|
|
|
|
*
|
|
|
|
* This will occur after changes to any InternalItemNode.
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.onListNodeUpdate = function () {
|
2024-02-22 20:07:39 +00:00
|
|
|
this.index = null;
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2024-02-22 20:07:39 +00:00
|
|
|
* Manually re-populates the list of search results after {@see setInternalList} was called.
|
2016-02-03 21:03:41 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.buildIndex = function () {
|
2024-02-22 20:07:39 +00:00
|
|
|
this.onQueryChange();
|
2024-02-22 20:08:02 +00:00
|
|
|
};
|
2016-02-03 21:03:41 +00:00
|
|
|
|
2024-02-22 20:08:02 +00:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @return {Object[]}
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.buildSearchIndex = function () {
|
2024-07-24 15:12:10 +00:00
|
|
|
const docRefs = ve.dm.MWDocumentReferences.static.refsForDoc( this.internalList.getDocument() );
|
2024-02-22 20:08:02 +00:00
|
|
|
const groups = this.internalList.getNodeGroups();
|
2023-08-21 08:08:23 +00:00
|
|
|
const groupNames = Object.keys( groups ).sort();
|
2016-02-03 21:03:41 +00:00
|
|
|
|
2024-07-18 07:07:55 +00:00
|
|
|
// FIXME: Temporary hack, to be removed soon
|
|
|
|
// eslint-disable-next-line no-jquery/no-class-state
|
|
|
|
const filterExtends = this.$element.hasClass( 've-ui-citoidInspector-extends' );
|
|
|
|
|
2024-07-24 15:12:10 +00:00
|
|
|
let index = [];
|
2023-08-21 08:08:23 +00:00
|
|
|
for ( let i = 0; i < groupNames.length; i++ ) {
|
|
|
|
const groupName = groupNames[ i ];
|
2023-05-16 15:16:04 +00:00
|
|
|
if ( groupName.indexOf( 'mwReference/' ) !== 0 ) {
|
2024-07-24 15:12:10 +00:00
|
|
|
// FIXME: Should be impossible to reach
|
2016-02-03 21:03:41 +00:00
|
|
|
continue;
|
|
|
|
}
|
2024-07-24 15:12:10 +00:00
|
|
|
const groupedByParent = docRefs.getGroupRefsByParents( groupName );
|
|
|
|
let flatNodes = [];
|
|
|
|
if ( filterExtends ) {
|
|
|
|
flatNodes = ( groupedByParent[ '' ] || [] );
|
|
|
|
} else {
|
|
|
|
// flatMap
|
|
|
|
( groupedByParent[ '' ] || [] ).forEach( ( parentNode ) => {
|
|
|
|
flatNodes.push( parentNode );
|
|
|
|
flatNodes = flatNodes.concat( groupedByParent[ parentNode.getAttribute( 'listKey' ) ] || [] );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
index = index.concat( flatNodes.map( ( node ) => {
|
|
|
|
const listKey = node.getAttribute( 'listKey' );
|
|
|
|
// remove `mwReference/` prefix
|
|
|
|
const group = groupName.slice( 12 );
|
|
|
|
const footnoteNumber = docRefs.getIndexNumber( group, listKey );
|
|
|
|
const citation = ( group ? group + ' ' : '' ) + footnoteNumber;
|
2016-02-03 21:03:41 +00:00
|
|
|
|
2020-09-24 12:09:51 +00:00
|
|
|
// Use [\s\S]* instead of .* to catch esoteric whitespace (T263698)
|
2024-07-24 15:12:10 +00:00
|
|
|
const matches = listKey.match( /^literal\/([\s\S]*)$/ );
|
2023-08-21 08:08:23 +00:00
|
|
|
const name = matches && matches[ 1 ] || '';
|
2016-02-03 21:03:41 +00:00
|
|
|
|
2023-08-21 08:08:23 +00:00
|
|
|
let $element;
|
2016-02-03 21:03:41 +00:00
|
|
|
// Make visible text, citation and reference name searchable
|
2024-07-19 09:36:09 +00:00
|
|
|
let text = ( citation + ' ' + name ).toLowerCase();
|
2024-07-24 15:12:10 +00:00
|
|
|
const itemNode = this.internalList.getItemNode( node.getAttribute( 'listIndex' ) );
|
2023-05-09 15:08:34 +00:00
|
|
|
if ( itemNode.length ) {
|
|
|
|
$element = new ve.ui.MWPreviewElement( itemNode, { useView: true } ).$element;
|
|
|
|
text = $element.text().toLowerCase() + ' ' + text;
|
|
|
|
// Make URLs searchable
|
2024-05-31 14:27:11 +00:00
|
|
|
$element.find( 'a[href]' ).each( ( k, element ) => {
|
|
|
|
text += ' ' + element.getAttribute( 'href' );
|
2023-05-09 15:08:34 +00:00
|
|
|
} );
|
|
|
|
} else {
|
|
|
|
$element = $( '<span>' )
|
|
|
|
.addClass( 've-ce-mwReferencesListNode-muted' )
|
|
|
|
.text( ve.msg( 'cite-ve-referenceslist-missingref-in-list' ) );
|
|
|
|
}
|
2016-02-03 21:03:41 +00:00
|
|
|
|
2024-07-24 15:12:10 +00:00
|
|
|
return {
|
2023-05-09 15:08:34 +00:00
|
|
|
$element: $element,
|
2016-02-03 21:03:41 +00:00
|
|
|
text: text,
|
2024-07-24 15:12:10 +00:00
|
|
|
// TODO: return a simple node
|
|
|
|
reference: ve.dm.MWReferenceModel.static.newFromReferenceNode( node ),
|
2016-02-03 21:03:41 +00:00
|
|
|
citation: citation,
|
|
|
|
name: name
|
2024-07-24 15:12:10 +00:00
|
|
|
};
|
|
|
|
} ) );
|
2016-02-03 21:03:41 +00:00
|
|
|
}
|
|
|
|
|
2024-02-22 20:08:02 +00:00
|
|
|
return index;
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether buildIndex will create an empty index based on the current internalList.
|
|
|
|
*
|
|
|
|
* @return {boolean} Index is empty
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.isIndexEmpty = function () {
|
2024-02-22 18:28:17 +00:00
|
|
|
return !this.internalList ||
|
|
|
|
ve.ui.MWReferenceSearchWidget.static.isIndexEmpty( this.internalList );
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
|
|
|
|
2024-02-22 20:08:02 +00:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {string} query
|
|
|
|
* @return {ve.ui.MWReferenceResultWidget[]}
|
|
|
|
*/
|
|
|
|
ve.ui.MWReferenceSearchWidget.prototype.buildSearchResults = function ( query ) {
|
|
|
|
query = query.trim().toLowerCase();
|
2023-08-21 08:08:23 +00:00
|
|
|
const items = [];
|
2016-02-03 21:03:41 +00:00
|
|
|
|
2024-02-22 20:07:39 +00:00
|
|
|
if ( !this.index ) {
|
|
|
|
this.index = this.buildSearchIndex();
|
|
|
|
}
|
|
|
|
|
2023-08-21 08:08:23 +00:00
|
|
|
for ( let i = 0; i < this.index.length; i++ ) {
|
|
|
|
const item = this.index[ i ];
|
2016-02-03 21:03:41 +00:00
|
|
|
if ( item.text.indexOf( query ) >= 0 ) {
|
2023-08-21 08:08:23 +00:00
|
|
|
const $citation = $( '<div>' )
|
2016-02-03 21:03:41 +00:00
|
|
|
.addClass( 've-ui-mwReferenceSearchWidget-citation' )
|
|
|
|
.text( '[' + item.citation + ']' );
|
2023-08-21 08:08:23 +00:00
|
|
|
const $name = $( '<div>' )
|
2016-02-03 21:03:41 +00:00
|
|
|
.addClass( 've-ui-mwReferenceSearchWidget-name' )
|
2023-02-23 11:09:15 +00:00
|
|
|
.toggleClass( 've-ui-mwReferenceSearchWidget-name-autogenerated', /^:\d+$/.test( item.name ) )
|
2016-02-03 21:03:41 +00:00
|
|
|
.text( item.name );
|
|
|
|
items.push(
|
|
|
|
new ve.ui.MWReferenceResultWidget( {
|
|
|
|
data: item.reference,
|
|
|
|
label: $citation.add( $name ).add( item.$element )
|
|
|
|
} )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-22 20:08:02 +00:00
|
|
|
return items;
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|