Clean up reflist usage of MWGroupReferences

Switch this usage entirely to the new interface, moving much of the
junk out.

Bug: T372871
Change-Id: I445af469a823144b0b6fa5e6f4f23a858939e9e6
This commit is contained in:
Adam Wight 2024-08-23 16:25:51 +02:00
parent ab7d60ca05
commit 35e8af7d40
4 changed files with 112 additions and 32 deletions

View file

@ -25,6 +25,7 @@
"ve.ui.MW": "https://doc.wikimedia.org/VisualEditor/master/js/{type}.html", "ve.ui.MW": "https://doc.wikimedia.org/VisualEditor/master/js/{type}.html",
"mw.": "https://doc.wikimedia.org/mediawiki-core/master/js/{type}.html", "mw.": "https://doc.wikimedia.org/mediawiki-core/master/js/{type}.html",
"ve.dm.MWDocumentReferences": true, "ve.dm.MWDocumentReferences": true,
"ve.dm.MWGroupReferences": true,
"ve.dm.MWReference": true, "ve.dm.MWReference": true,
"ve.ce.MWReference": true, "ve.ce.MWReference": true,
"ve.ui.MWReference": true "ve.ui.MWReference": true

View file

@ -27,7 +27,6 @@ ve.ce.MWReferencesListNode = function VeCeMWReferencesListNode() {
this.internalList = null; this.internalList = null;
this.listNode = null; this.listNode = null;
this.modified = false; this.modified = false;
this.docRefs = null;
// DOM changes // DOM changes
this.$element.addClass( 've-ce-mwReferencesListNode' ); this.$element.addClass( 've-ce-mwReferencesListNode' );
@ -175,12 +174,11 @@ ve.ce.MWReferencesListNode.prototype.update = function () {
return; return;
} }
this.docRefs = ve.dm.MWDocumentReferences.static.refsForDoc( model.getDocument() );
const internalList = model.getDocument().internalList;
const refGroup = model.getAttribute( 'refGroup' ); const refGroup = model.getAttribute( 'refGroup' );
const listGroup = model.getAttribute( 'listGroup' );
const nodes = internalList.getNodeGroup( listGroup ); const docRefs = ve.dm.MWDocumentReferences.static.refsForDoc( model.getDocument() );
const hasModelReferences = !!( nodes && nodes.indexOrder.length ); const groupRefs = docRefs.getGroupRefs( refGroup );
const hasModelReferences = !groupRefs.isEmpty();
let emptyText; let emptyText;
if ( refGroup !== '' ) { if ( refGroup !== '' ) {
@ -240,14 +238,11 @@ ve.ce.MWReferencesListNode.prototype.update = function () {
this.$refmsg.text( emptyText ); this.$refmsg.text( emptyText );
this.$element.append( this.$refmsg ); this.$element.append( this.$refmsg );
} else { } else {
const groupRefs = this.docRefs.getGroupRefs( listGroup ); // Render all at once.
this.$reflist.append( this.$reflist.append(
// FIXME: Clean up access functions. groupRefs.getTopLevelKeysInReflistOrder()
Object.keys( groupRefs.footnoteNumberLookup )
.filter( ( listKey ) => groupRefs.footnoteNumberLookup[ listKey ][ 1 ] === -1 )
.sort( ( aKey, bKey ) => groupRefs.footnoteNumberLookup[ aKey ][ 0 ] - groupRefs.footnoteNumberLookup[ bKey ][ 0 ] )
.map( ( listKey ) => this.renderListItem( .map( ( listKey ) => this.renderListItem(
nodes, internalList, groupRefs, refGroup, listKey groupRefs, refGroup, listKey
) ) ) )
); );
@ -260,31 +255,22 @@ ve.ce.MWReferencesListNode.prototype.update = function () {
* Render a reference list item * Render a reference list item
* *
* @private * @private
* @param {Object} nodes Node group object, containing nodes and key order array
* @param {ve.dm.InternalList} internalList Internal list
* @param {ve.dm.MWGroupReferences} groupRefs object holding calculated information about all group refs * @param {ve.dm.MWGroupReferences} groupRefs object holding calculated information about all group refs
* @param {string} refGroup Reference group * @param {string} refGroup Reference group
* @param {string} key top-level reference key, doesn't necessarily exist * @param {string} key top-level reference key, doesn't necessarily exist
* @return {jQuery} Rendered list item * @return {jQuery} Rendered list item
*/ */
ve.ce.MWReferencesListNode.prototype.renderListItem = function ( nodes, internalList, groupRefs, refGroup, key ) { ve.ce.MWReferencesListNode.prototype.renderListItem = function ( groupRefs, refGroup, key ) {
const keyedNodes = nodes.keyedNodes[ key ] || []; const ref = groupRefs.getInternalModelNode( key );
const node = keyedNodes ? keyedNodes[ 0 ] : null; const backlinkNodes = groupRefs.getRefUsages( key );
const listIndex = node ? node.getAttribute( 'listIndex' ) : null; const subrefs = groupRefs.getSubrefs( key );
const backlinkNodes = keyedNodes.filter(
// Exclude placeholders and references defined inside the references list node
( backRefNode ) => !backRefNode.getAttribute( 'placeholder' ) && !backRefNode.findParent( ve.dm.MWReferencesListNode )
);
const subrefs = groupRefs.subRefsByParent[ key ] || [];
const $li = $( '<li>' ) const $li = $( '<li>' )
.css( '--footnote-number', `"${ groupRefs.getIndexLabel( key ) }."` ) .css( '--footnote-number', `"${ groupRefs.getIndexLabel( key ) }."` )
.append( this.renderBacklinks( backlinkNodes, refGroup ), ' ' ); .append( this.renderBacklinks( backlinkNodes, refGroup ), ' ' );
// Generate reference HTML from first item in key if ( ref && ref.length ) {
const modelNode = internalList.getItemNode( listIndex ); const refPreview = new ve.ui.MWPreviewElement( ref, { useView: true } );
if ( modelNode && modelNode.length ) {
const refPreview = new ve.ui.MWPreviewElement( modelNode, { useView: true } );
$li.append( $li.append(
$( '<span>' ) $( '<span>' )
.addClass( 'reference-text' ) .addClass( 'reference-text' )
@ -295,7 +281,8 @@ ve.ce.MWReferencesListNode.prototype.renderListItem = function ( nodes, internal
const surface = this.getRoot().getSurface().getSurface(); const surface = this.getRoot().getSurface().getSurface();
// TODO: attach to the singleton click handler on the surface // TODO: attach to the singleton click handler on the surface
$li.on( 'mousedown', ( e ) => { $li.on( 'mousedown', ( e ) => {
if ( ve.isUnmodifiedLeftClick( e ) && modelNode && modelNode.length ) { if ( ve.isUnmodifiedLeftClick( e ) ) {
const node = groupRefs.getRefNode( key );
const items = ve.ui.contextItemFactory.getRelatedItems( [ node ] ) const items = ve.ui.contextItemFactory.getRelatedItems( [ node ] )
.filter( ( item ) => item.name !== 'mobileActions' ); .filter( ( item ) => item.name !== 'mobileActions' );
if ( items.length ) { if ( items.length ) {
@ -336,7 +323,7 @@ ve.ce.MWReferencesListNode.prototype.renderListItem = function ( nodes, internal
$li.append( $li.append(
$( '<ol>' ).append( $( '<ol>' ).append(
subrefs.map( ( subNode ) => this.renderListItem( subrefs.map( ( subNode ) => this.renderListItem(
nodes, internalList, groupRefs, refGroup, subNode.getAttribute( 'listKey' ) groupRefs, refGroup, subNode.getAttribute( 'listKey' )
) ) ) )
) )
); );

View file

@ -85,7 +85,8 @@ ve.dm.MWDocumentReferences.prototype.updateGroup = function ( groupName ) {
* @return {ve.dm.MWGroupReferences} * @return {ve.dm.MWGroupReferences}
*/ */
ve.dm.MWDocumentReferences.prototype.getGroupRefs = function ( groupName ) { ve.dm.MWDocumentReferences.prototype.getGroupRefs = function ( groupName ) {
return this.cachedByGroup[ groupName.startsWith( 'mwReference/' ) ? groupName : 'mwReference/' + groupName ]; return this.cachedByGroup[ groupName.startsWith( 'mwReference/' ) ? groupName : 'mwReference/' + groupName ] ||
new ve.dm.MWGroupReferences();
}; };
ve.dm.MWDocumentReferences.prototype.getAllGroupNames = function () { ve.dm.MWDocumentReferences.prototype.getAllGroupNames = function () {

View file

@ -11,7 +11,6 @@
* This structure is persisted in memory until a document change affects a ref * This structure is persisted in memory until a document change affects a ref
* tag from this group, at which point it will be fully recalculated. * tag from this group, at which point it will be fully recalculated.
* *
* @private
* @constructor * @constructor
*/ */
ve.dm.MWGroupReferences = function VeDmMWGroupReferences() { ve.dm.MWGroupReferences = function VeDmMWGroupReferences() {
@ -19,13 +18,33 @@ ve.dm.MWGroupReferences = function VeDmMWGroupReferences() {
OO.EventEmitter.call( this ); OO.EventEmitter.call( this );
// Properties // Properties
/**
* Lookup from listKey to a pair of integers which are the [major, minor] footnote numbers
* that will be rendered on the ref in some digit system. Note that top-level refs always
* have minor number `-1`.
*
* @member {Object.<string, number[]>}
*/
this.footnoteNumberLookup = {}; this.footnoteNumberLookup = {};
// FIXME: push labeling to presentation code and drop from here. /**
* Lookup from listKey to a rendered footnote number or subref number like "1.2", in the
* local content language.
*
* FIXME: push labeling to presentation code and drop from here.
*
* @member {Object.<string, string>}
*/
this.footnoteLabelLookup = {}; this.footnoteLabelLookup = {};
/**
* Lookup from parent listKey to subrefs.
*
* @member {Object.<string, ve.dm.MWReferenceNode[]>}
*/
this.subRefsByParent = {}; this.subRefsByParent = {};
/** @private */ /** @private */
this.topLevelCounter = 1; this.topLevelCounter = 1;
/** @private */
this.nodeGroup = null; this.nodeGroup = null;
}; };
@ -100,6 +119,20 @@ ve.dm.MWGroupReferences.prototype.addSubref = function ( parentKey, listKey, sub
}; };
/** /**
* Check whether the group has any references.
*
* @return {boolean}
*/
ve.dm.MWGroupReferences.prototype.isEmpty = function () {
// Use an internal shortcut, otherwise we could do something like
// !!nodes.indexOrder.length
return this.topLevelCounter === 1;
};
/**
* List all document references in the order they first appear, ignoring reuses
* and placeholders.
*
* @return {ve.dm.MWReferenceNode[]} * @return {ve.dm.MWReferenceNode[]}
*/ */
ve.dm.MWGroupReferences.prototype.getAllRefsInDocumentOrder = function () { ve.dm.MWGroupReferences.prototype.getAllRefsInDocumentOrder = function () {
@ -110,6 +143,64 @@ ve.dm.MWGroupReferences.prototype.getAllRefsInDocumentOrder = function () {
.map( ( nodes ) => nodes[ 0 ] ); .map( ( nodes ) => nodes[ 0 ] );
}; };
/**
* List all reference listKeys in the order they appear in the reflist including
* named refs, unnamed refs, and those that don't resolve
*
* @return {string[]} Reference listKeys
*/
ve.dm.MWGroupReferences.prototype.getTopLevelKeysInReflistOrder = function () {
return Object.keys( this.footnoteNumberLookup )
.sort( ( aKey, bKey ) => this.footnoteNumberLookup[ aKey ][ 0 ] - this.footnoteNumberLookup[ bKey ][ 0 ] )
// TODO: Function could be split here, if a use case is found for a list of
// all numbers including subrefs.
.filter( ( listKey ) => this.footnoteNumberLookup[ listKey ][ 1 ] === -1 );
};
/**
* Return the defining reference node for this key
*
* @see #getInternalModelNode
*
* @param {string} key in listKey format
* @return {ve.dm.MWReferenceNode|undefined}
*/
ve.dm.MWGroupReferences.prototype.getRefNode = function ( key ) {
const keyedNodes = this.nodeGroup.keyedNodes[ key ];
return keyedNodes && keyedNodes[ 0 ];
};
/**
* Return the internalList internal item if it exists.
*
* @see #getRefNode
*
* @param {string} key in listKey format
* @return {ve.dm.InternalItemNode|undefined}
*/
ve.dm.MWGroupReferences.prototype.getInternalModelNode = function ( key ) {
const ref = this.getRefNode( key );
return ref && ref.getInternalItem();
};
/**
* Return document nodes for each usage of a ref key. This excludes usages
* under the `<references>` section, so note that nested references won't behave
* as expected. The reflist item for a ref is not counted as a reference,
* either.
*
* FIXME: Implement backlinks from within a nested ref within the footnote body.
*
* @param {string} key in listKey format
* @return {ve.dm.MWReferenceNode[]}
*/
ve.dm.MWGroupReferences.prototype.getRefUsages = function ( key ) {
return ( this.nodeGroup.keyedNodes[ key ] || [] )
.filter( ( node ) => !node.getAttribute( 'placeholder' ) &&
!node.findParent( ve.dm.MWReferencesListNode )
);
};
/** /**
* @param {string} parentKey parent ref key * @param {string} parentKey parent ref key
* @return {ve.dm.MWReferenceNode[]} List of subrefs for this parent * @return {ve.dm.MWReferenceNode[]} List of subrefs for this parent