2023-08-21 08:08:23 +00:00
|
|
|
'use strict';
|
|
|
|
|
2016-02-03 21:03:41 +00:00
|
|
|
/*
|
|
|
|
* VisualEditor user interface MWCitationDialog 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
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2024-07-02 09:34:21 +00:00
|
|
|
* Dialog for inserting and editing MediaWiki citations that use the templates that are set up for
|
|
|
|
* the VisualEditor citation tool.
|
2016-02-03 21:03:41 +00:00
|
|
|
*
|
|
|
|
* @constructor
|
2024-02-28 08:57:24 +00:00
|
|
|
* @extends ve.ui.MWTemplateDialog
|
2016-02-03 21:03:41 +00:00
|
|
|
* @param {Object} [config] Configuration options
|
|
|
|
*/
|
|
|
|
ve.ui.MWCitationDialog = function VeUiMWCitationDialog( config ) {
|
|
|
|
// Parent constructor
|
|
|
|
ve.ui.MWCitationDialog.super.call( this, config );
|
|
|
|
|
|
|
|
// Properties
|
|
|
|
this.referenceModel = null;
|
|
|
|
this.referenceNode = null;
|
|
|
|
this.inDialog = '';
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
2021-09-20 09:00:00 +00:00
|
|
|
OO.inheritClass( ve.ui.MWCitationDialog, ve.ui.MWTransclusionDialog );
|
2016-02-03 21:03:41 +00:00
|
|
|
|
|
|
|
/* Static Properties */
|
|
|
|
|
2019-02-15 14:57:44 +00:00
|
|
|
ve.ui.MWCitationDialog.static.name = 'cite';
|
2016-02-03 21:03:41 +00:00
|
|
|
|
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the reference node to be edited.
|
|
|
|
*
|
|
|
|
* @return {ve.dm.MWReferenceNode|null} Reference node to be edited, null if none exists
|
|
|
|
*/
|
|
|
|
ve.ui.MWCitationDialog.prototype.getReferenceNode = function () {
|
2023-08-21 08:08:23 +00:00
|
|
|
const selectedNode = this.getFragment().getSelectedNode();
|
2016-02-03 21:03:41 +00:00
|
|
|
|
|
|
|
if ( selectedNode instanceof ve.dm.MWReferenceNode ) {
|
|
|
|
return selectedNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2024-02-28 08:57:24 +00:00
|
|
|
* @override
|
2016-02-03 21:03:41 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWCitationDialog.prototype.getSelectedNode = function () {
|
2023-08-21 08:08:23 +00:00
|
|
|
const referenceNode = this.getReferenceNode();
|
2016-02-03 21:03:41 +00:00
|
|
|
|
|
|
|
if ( referenceNode ) {
|
2023-08-21 08:08:23 +00:00
|
|
|
const branches = referenceNode.getInternalItem().getChildren();
|
|
|
|
const leaves = branches &&
|
2016-02-03 21:03:41 +00:00
|
|
|
branches.length === 1 &&
|
|
|
|
branches[ 0 ].canContainContent() &&
|
|
|
|
branches[ 0 ].getChildren();
|
2024-05-31 14:36:02 +00:00
|
|
|
const transclusionNode = leaves &&
|
2016-02-03 21:03:41 +00:00
|
|
|
leaves.length === 1 &&
|
|
|
|
leaves[ 0 ] instanceof ve.dm.MWTransclusionNode &&
|
|
|
|
leaves[ 0 ];
|
|
|
|
|
2024-05-31 14:36:02 +00:00
|
|
|
// Only use the selected node if it is the same template as this dialog expects
|
|
|
|
if ( transclusionNode && transclusionNode.isSingleTemplate( this.citationTemplate ) ) {
|
|
|
|
return transclusionNode;
|
|
|
|
}
|
2019-01-09 22:31:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2024-02-28 08:57:24 +00:00
|
|
|
* @override
|
2016-02-03 21:03:41 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWCitationDialog.prototype.initialize = function ( data ) {
|
|
|
|
// Parent method
|
|
|
|
ve.ui.MWCitationDialog.super.prototype.initialize.call( this, data );
|
|
|
|
|
|
|
|
// HACK: Use the same styling as single-mode transclusion dialog - this should be generalized
|
|
|
|
this.$content.addClass( 've-ui-mwTransclusionDialog-single' );
|
2019-07-22 16:46:57 +00:00
|
|
|
|
|
|
|
this.$content.on( 'change', this.onInputChange.bind( this ) );
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2024-02-28 08:57:24 +00:00
|
|
|
* @override
|
2016-02-03 21:03:41 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWCitationDialog.prototype.getSetupProcess = function ( data ) {
|
|
|
|
return ve.ui.MWCitationDialog.super.prototype.getSetupProcess.call( this, data )
|
2024-05-31 14:27:11 +00:00
|
|
|
.first( () => {
|
2016-02-03 21:03:41 +00:00
|
|
|
data = data || {};
|
|
|
|
this.inDialog = data.inDialog;
|
2019-01-09 22:31:21 +00:00
|
|
|
this.citationTemplate = data.template;
|
2019-02-15 14:57:44 +00:00
|
|
|
this.citationTitle = data.title;
|
2019-07-22 16:46:57 +00:00
|
|
|
|
|
|
|
this.trackedCitationInputChange = false;
|
2024-05-31 14:27:11 +00:00
|
|
|
} )
|
|
|
|
.next( () => {
|
2019-02-15 14:57:44 +00:00
|
|
|
this.updateTitle();
|
2016-02-03 21:03:41 +00:00
|
|
|
|
|
|
|
// Initialization
|
2019-01-09 22:31:21 +00:00
|
|
|
this.referenceNode = this.getReferenceNode();
|
|
|
|
if ( this.referenceNode ) {
|
|
|
|
this.referenceModel = ve.dm.MWReferenceModel.static.newFromReferenceNode(
|
|
|
|
this.referenceNode
|
|
|
|
);
|
2016-02-03 21:03:41 +00:00
|
|
|
}
|
2024-05-31 14:27:11 +00:00
|
|
|
} );
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
|
|
|
|
2019-02-15 14:57:44 +00:00
|
|
|
/**
|
2024-02-28 08:57:24 +00:00
|
|
|
* @override
|
2019-02-15 14:57:44 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWCitationDialog.prototype.updateTitle = function () {
|
|
|
|
if ( this.citationTitle ) {
|
|
|
|
this.title.setLabel( this.citationTitle );
|
|
|
|
} else {
|
|
|
|
// Parent method
|
|
|
|
ve.ui.MWCitationDialog.super.prototype.updateTitle.call( this );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-02-03 21:03:41 +00:00
|
|
|
/**
|
2024-02-28 08:57:24 +00:00
|
|
|
* @override
|
2016-02-03 21:03:41 +00:00
|
|
|
*/
|
2018-11-29 22:16:23 +00:00
|
|
|
ve.ui.MWCitationDialog.prototype.setApplicableStatus = function () {
|
|
|
|
ve.ui.MWCitationDialog.super.prototype.setApplicableStatus.call( this );
|
2019-02-21 21:56:51 +00:00
|
|
|
// Parent method disables 'done' if no changes were made (this is okay for us), and
|
2018-11-29 22:16:23 +00:00
|
|
|
// disables 'insert' if transclusion is empty (but it is never empty in our case).
|
|
|
|
// Instead, disable 'insert' if no parameters were added.
|
2021-09-27 09:17:38 +00:00
|
|
|
this.actions.setAbilities( { insert: this.transclusionModel.containsValuableData() } );
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2024-02-28 08:57:24 +00:00
|
|
|
* @override
|
2016-02-03 21:03:41 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWCitationDialog.prototype.getActionProcess = function ( action ) {
|
|
|
|
if (
|
|
|
|
this.inDialog !== 'reference' &&
|
2019-02-21 21:56:51 +00:00
|
|
|
( action === 'done' || action === 'insert' )
|
2016-02-03 21:03:41 +00:00
|
|
|
) {
|
2024-05-31 14:27:11 +00:00
|
|
|
return new OO.ui.Process( () => {
|
2023-08-21 08:08:23 +00:00
|
|
|
const deferred = $.Deferred();
|
2024-05-31 14:31:13 +00:00
|
|
|
this.checkRequiredParameters().done( () => {
|
|
|
|
const surfaceModel = this.getFragment().getSurface();
|
2023-08-21 08:08:23 +00:00
|
|
|
const doc = surfaceModel.getDocument();
|
|
|
|
const internalList = doc.getInternalList();
|
2024-05-31 14:31:13 +00:00
|
|
|
const obj = this.transclusionModel.getPlainObject();
|
2016-02-03 21:03:41 +00:00
|
|
|
|
2019-01-09 22:31:21 +00:00
|
|
|
// We had a reference, but no template node (or wrong kind of template node)
|
2024-05-31 14:31:13 +00:00
|
|
|
if ( this.referenceModel && !this.selectedNode ) {
|
|
|
|
const refDoc = this.referenceModel.getDocument();
|
2024-01-30 11:18:52 +00:00
|
|
|
// Empty the existing reference, whatever it contained. This allows
|
|
|
|
// the dialog to be used for arbitrary references (to replace their
|
|
|
|
// contents with a citation).
|
2019-01-09 22:31:21 +00:00
|
|
|
refDoc.commit(
|
2024-01-30 11:18:52 +00:00
|
|
|
ve.dm.TransactionBuilder.static
|
|
|
|
.newFromRemoval( refDoc, refDoc.getDocumentRange(), true )
|
2019-01-09 22:31:21 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-05-31 14:31:13 +00:00
|
|
|
if ( !this.referenceModel ) {
|
|
|
|
// Collapse returns a new fragment, so update this.fragment
|
|
|
|
this.fragment = this.getFragment().collapseToEnd();
|
|
|
|
this.referenceModel = new ve.dm.MWReferenceModel( doc );
|
|
|
|
this.referenceModel.insertInternalItem( surfaceModel );
|
|
|
|
this.referenceModel.insertReferenceNode( this.getFragment() );
|
2016-02-03 21:03:41 +00:00
|
|
|
}
|
|
|
|
|
2024-05-31 14:31:13 +00:00
|
|
|
const item = this.referenceModel.findInternalItem( surfaceModel );
|
2016-02-03 21:03:41 +00:00
|
|
|
if ( item ) {
|
2024-05-31 14:31:13 +00:00
|
|
|
if ( this.selectedNode ) {
|
|
|
|
this.transclusionModel.updateTransclusionNode(
|
|
|
|
surfaceModel, this.selectedNode
|
2016-02-03 21:03:41 +00:00
|
|
|
);
|
|
|
|
} else if ( obj !== null ) {
|
2024-05-31 14:31:13 +00:00
|
|
|
this.transclusionModel.insertTransclusionNode(
|
2024-01-30 11:18:52 +00:00
|
|
|
// HACK: This is trying to place the cursor inside the first
|
|
|
|
// content branch node but this theoretically not a safe
|
|
|
|
// assumption - in practice, the citation dialog will only reach
|
|
|
|
// this code if we are inserting (not updating) a transclusion, so
|
|
|
|
// the referenceModel will have already initialized the internal
|
|
|
|
// node with a paragraph - getting the range of the item covers
|
|
|
|
// the entire paragraph so we have to get the range of it's first
|
|
|
|
// (and empty) child
|
2024-05-31 14:31:13 +00:00
|
|
|
this.getFragment().clone(
|
2018-11-07 15:51:51 +00:00
|
|
|
new ve.dm.LinearSelection( item.getChildren()[ 0 ].getRange() )
|
2016-05-15 11:16:51 +00:00
|
|
|
),
|
|
|
|
'inline'
|
2016-02-03 21:03:41 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-30 11:18:52 +00:00
|
|
|
// HACK: Scorch the earth - this is only needed because without it, the
|
|
|
|
// references list won't re-render properly, and can be removed once
|
|
|
|
// someone fixes that
|
2024-05-31 14:31:13 +00:00
|
|
|
this.referenceModel.setDocument(
|
2016-02-03 21:03:41 +00:00
|
|
|
doc.cloneFromRange(
|
2024-05-31 14:31:13 +00:00
|
|
|
internalList.getItemNode( this.referenceModel.getListIndex() ).getRange()
|
2016-02-03 21:03:41 +00:00
|
|
|
)
|
|
|
|
);
|
2024-05-31 14:31:13 +00:00
|
|
|
this.referenceModel.updateInternalItem( surfaceModel );
|
2016-02-03 21:03:41 +00:00
|
|
|
|
2024-05-31 14:31:13 +00:00
|
|
|
this.close( { action: action } );
|
2016-02-03 21:03:41 +00:00
|
|
|
} ).always( deferred.resolve );
|
|
|
|
|
|
|
|
return deferred;
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parent method
|
|
|
|
return ve.ui.MWCitationDialog.super.prototype.getActionProcess.call( this, action );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2024-02-28 08:57:24 +00:00
|
|
|
* @override
|
2016-02-03 21:03:41 +00:00
|
|
|
*/
|
|
|
|
ve.ui.MWCitationDialog.prototype.getTeardownProcess = function ( data ) {
|
|
|
|
return ve.ui.MWCitationDialog.super.prototype.getTeardownProcess.call( this, data )
|
2024-05-31 14:27:11 +00:00
|
|
|
.first( () => {
|
2016-02-03 21:03:41 +00:00
|
|
|
// Cleanup
|
|
|
|
this.referenceModel = null;
|
|
|
|
this.referenceNode = null;
|
2024-05-31 14:27:11 +00:00
|
|
|
} );
|
2016-02-03 21:03:41 +00:00
|
|
|
};
|
2019-02-15 14:57:44 +00:00
|
|
|
|
2019-07-22 16:46:57 +00:00
|
|
|
/**
|
|
|
|
* Handle change events on the transclusion inputs
|
|
|
|
*
|
|
|
|
* @param {jQuery.Event} ev The browser event
|
|
|
|
*/
|
|
|
|
ve.ui.MWCitationDialog.prototype.onInputChange = function () {
|
|
|
|
if ( !this.trackedCitationInputChange ) {
|
|
|
|
ve.track( 'activity.' + this.constructor.static.name, { action: 'manual-template-input' } );
|
|
|
|
this.trackedCitationInputChange = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-02-15 14:57:44 +00:00
|
|
|
/* Registration */
|
|
|
|
|
|
|
|
ve.ui.windowFactory.register( ve.ui.MWCitationDialog );
|