'use strict'; /*! * VisualEditor MWReferenceContextItem class. * * @copyright 2011-2018 VisualEditor Team's Cite sub-team and others; see AUTHORS.txt * @license MIT */ const MWDocumentReferences = require( './ve.dm.MWDocumentReferences.js' ); const MWReferenceModel = require( './ve.dm.MWReferenceModel.js' ); const MWReferenceNode = require( './ve.dm.MWReferenceNode.js' ); const Options = require( './ve.ui.MWSubReferenceHelpDialogOptions.js' ); /** * Context item for a MWReference. * * @constructor * @extends ve.ui.LinearContextItem * @param {ve.ui.LinearContext} context Context the item is in * @param {ve.dm.Model} model Model the item is related to * @param {Object} [config] */ ve.ui.MWReferenceContextItem = function VeUiMWReferenceContextItem() { // Parent constructor ve.ui.MWReferenceContextItem.super.apply( this, arguments ); /** @member {ve.ui.MWPreviewElement} */ this.view = null; /** @member {ve.ui.MWPreviewElement} */ this.detailsView = null; /** @member {ve.dm.MWGroupReferences} */ this.groupRefs = null; // Initialization this.$element.addClass( 've-ui-mwReferenceContextItem' ); this.showHelp = !OO.ui.isMobile() && !Options.loadBoolean( 'hide-subref-help' ); }; /* Inheritance */ OO.inheritClass( ve.ui.MWReferenceContextItem, ve.ui.LinearContextItem ); /* Static Properties */ ve.ui.MWReferenceContextItem.static.name = 'reference'; ve.ui.MWReferenceContextItem.static.icon = 'reference'; ve.ui.MWReferenceContextItem.static.label = OO.ui.deferMsg( 'cite-ve-dialogbutton-reference-title' ); ve.ui.MWReferenceContextItem.static.modelClasses = [ MWReferenceNode ]; ve.ui.MWReferenceContextItem.static.commandName = 'reference'; /* Methods */ /** * Get a DOM rendering of a normal reference, or the main ref for a details * reference. * * @private * @return {jQuery} DOM rendering of reference */ ve.ui.MWReferenceContextItem.prototype.getMainRefPreview = function () { // Render a placeholder for missing refs. let refNode = this.getReferenceNode(); let errorMsgKey = 'cite-ve-referenceslist-missingref'; // Render main ref if this is a subref, or a placeholder if missing. const mainRefKey = this.model.getAttribute( 'mainRefKey' ); if ( mainRefKey && refNode ) { refNode = this.groupRefs.getInternalModelNode( mainRefKey ); errorMsgKey = 'cite-ve-dialog-reference-missing-parent-ref'; } if ( !refNode ) { return $( '
' ) .addClass( 've-ui-mwReferenceContextItem-muted' ) // The following messages are used here: // * cite-ve-referenceslist-missingref // * cite-ve-dialog-reference-missing-parent-ref .text( ve.msg( errorMsgKey ) ); } // Render normal ref. this.view = new ve.ui.MWPreviewElement( refNode, { useView: true } ); // The $element property may be rendered into asynchronously, update the // context's size when the rendering is complete if that's the case this.view.once( 'render', this.context.updateDimensions.bind( this.context ) ); return this.view.$element; }; /** * Get a preview of the reference details. * * @private * @return {jQuery|undefined} */ ve.ui.MWReferenceContextItem.prototype.getDetailsPreview = function () { if ( !this.model.getAttribute( 'mainRefKey' ) ) { return; } const editDetails = new OO.ui.Layout( { classes: [ 've-ui-mwReferenceContextItem-subrefHeader' ], content: [ new OO.ui.ButtonWidget( { framed: false, invisibleLabel: true, icon: this.isReadOnly() ? 'eye' : 'edit', label: ve.msg( this.isReadOnly() ? 'visualeditor-contextitemwidget-label-view' : 'visualeditor-contextitemwidget-label-secondary' ), classes: [ 've-ui-mwReferenceContextItem-editButton' ] } ).on( 'click', () => { // Phabricator T396734 ve.track( 'activity.subReference', { action: 'context-edit-details' } ); return this.onEditSubref(); } ) ] } ); this.detailsView = new ve.ui.MWPreviewElement( this.getReferenceNode(), { useView: true } ); // The $element property may be rendered into asynchronously, update the // context's size when the rendering is complete if that's the case this.detailsView.once( 'render', this.context.updateDimensions.bind( this.context ) ); return new OO.ui.HorizontalLayout( { classes: [ 've-ui-mwReferenceContextItem-detailsPreview' ], items: [ this.detailsView, editDetails ] } ).$element; }; /** * Override default edit button, when a subref is present. */ ve.ui.MWReferenceContextItem.prototype.onEditButtonClick = function () { const mainRefKey = this.model.getAttribute( 'mainRefKey' ); if ( !mainRefKey ) { ve.ui.LinearContextItem.prototype.onEditButtonClick.apply( this ); return; } // Edit the main ref--like when editing a list-defined ref! // TODO: Make this into a reusable command. const groupRefs = MWDocumentReferences.static .refsForDoc( this.getFragment().getDocument() ) .getGroupRefs( this.model.getAttribute( 'listGroup' ) ); const mainRefNode = groupRefs.getRefNode( mainRefKey ); const mainModelItem = ve.ui.contextItemFactory.getRelatedItems( [ mainRefNode ] ) .find( ( item ) => item.name !== 'mobileActions' ); if ( mainModelItem ) { const mainContextItem = ve.ui.contextItemFactory.lookup( mainModelItem.name ); if ( mainContextItem ) { const surface = this.context.getSurface(); const command = surface.commandRegistry.lookup( mainContextItem.static.commandName ); const fragmentArgs = { fragment: surface.getModel().getLinearFragment( mainRefNode.getOuterRange(), true ), selectFragmentOnClose: false }; const newArgs = ve.copy( command.args ); if ( command.name === 'reference' ) { newArgs[ 1 ] = fragmentArgs; } else { ve.extendObject( newArgs[ 0 ], fragmentArgs ); } command.execute( surface, newArgs, 'context' ); } } }; /** * @private */ ve.ui.MWReferenceContextItem.prototype.onEditSubref = function () { ve.ui.LinearContextItem.prototype.onEditButtonClick.apply( this ); }; /** * Get a DOM rendering of a warning if this reference is reused. * * @private * @return {jQuery|undefined} */ ve.ui.MWReferenceContextItem.prototype.getReuseWarning = function () { const listKey = this.model.getAttribute( 'mainRefKey' ) || this.model.getAttribute( 'listKey' ); const totalUsageCount = this.groupRefs.getTotalUsageCount( listKey ); if ( totalUsageCount <= 1 ) { return; } if ( !mw.config.get( 'wgCiteSubReferencing' ) ) { return $( '
' ) .addClass( 've-ui-mwReferenceContextItem-muted' ) .text( ve.msg( 'cite-ve-dialog-reference-editing-reused', totalUsageCount ) ); } return new OO.ui.Layout( { classes: [ 've-ui-mwReferenceContextItem-reuse-layout' ], content: [ new OO.ui.LabelWidget( { classes: [ 've-ui-mwReferenceContextItem-reuse' ], label: ve.msg( 'cite-ve-dialog-reference-editing-reused-short', totalUsageCount ) } ) ] } ).$element; }; /** * Get a DOM rendering of a button to add details. * * @private * @return {jQuery|undefined} */ ve.ui.MWReferenceContextItem.prototype.getAddDetailsButton = function () { if ( !mw.config.get( 'wgCiteSubReferencing' ) || this.model.getAttribute( 'mainRefKey' ) ) { return; } const listKey = this.model.getAttribute( 'listKey' ); if ( this.groupRefs.getTotalUsageCount( listKey ) < 2 ) { return; } const openAddDetailsDialog = () => { // Phabricator T396734 ve.track( 'activity.subReference', { action: 'context-add-details' } ); const ref = MWReferenceModel.static.newFromReferenceNode( this.model ); ve.ui.commandRegistry.lookup( 'reference' ).execute( this.context.getSurface(), // Arguments for calling ve.ui.MWReferenceDialog.getSetupProcess() [ 'reference', { createSubRef: ref } ], 'context' ); }; const button = new OO.ui.ButtonWidget( { label: ve.msg( 'cite-ve-dialog-reference-add-details-button' ), classes: [ 've-ui-mwReferenceContextItem-addDetailsButton' ], framed: false, icon: 'add' } ).on( 'click', () => { if ( !this.showHelp ) { openAddDetailsDialog(); return; } const windowManager = this.context.getSurface().getDialogs(); windowManager.openWindow( 'subrefHelp' ).closing.then( ( action ) => { if ( action === 'dismiss' ) { this.showHelp = false; Options.saveBoolean( 'hide-subref-help', true ); button.$element.find( '.mw-pulsating-dot' ).remove(); openAddDetailsDialog(); } } ); } ); if ( this.showHelp ) { button.$element.append( $( '' ).addClass( 'mw-pulsating-dot' ) ); } return button.$element; }; /** * Get the reference node in the containing document (not the internal list document) * * @return {ve.dm.InternalItemNode|null} Reference item node */ ve.ui.MWReferenceContextItem.prototype.getReferenceNode = function () { if ( !this.model.isEditable() ) { return null; } if ( !this.referenceNode ) { this.referenceNode = this.groupRefs.getInternalModelNode( this.model.getAttribute( 'listKey' ) ); } return this.referenceNode; }; /** * @override */ ve.ui.MWReferenceContextItem.prototype.getDescription = function () { return this.model.isEditable() ? this.getMainRefPreview().text() : ve.msg( 'cite-ve-referenceslist-missingref' ); }; /** * @override */ ve.ui.MWReferenceContextItem.prototype.setup = function () { this.groupRefs = MWDocumentReferences.static.refsForDoc( this.getFragment().getDocument() ) .getGroupRefs( this.model.getAttribute( 'listGroup' ) ); // Parent method return ve.ui.MWReferenceContextItem.super.prototype.setup.apply( this, arguments ); }; /** * @override */ ve.ui.MWReferenceContextItem.prototype.renderBody = function () { const detailsPreview = this.getDetailsPreview(); const detailsButton = this.getAddDetailsButton(); // attach reuse warning to a different place for mobile const mainPreview = this.context.isMobile() ? [ this.getMainRefPreview(), this.getReuseWarning() ] : [ this.getReuseWarning(), this.getMainRefPreview() ]; const $detailsSeparator = $( '
' ) .addClass( 've-ui-mwReferenceContextItem-addDetailsSeparator' ); this.$body.empty().append( mainPreview, detailsPreview || detailsButton ? $detailsSeparator : null, detailsPreview, detailsButton ); }; /** * @override */ ve.ui.MWReferenceContextItem.prototype.teardown = function () { if ( this.view ) { this.view.destroy(); } if ( this.detailsView ) { this.detailsView.destroy(); } // Call parent ve.ui.MWReferenceContextItem.super.prototype.teardown.call( this ); }; module.exports = ve.ui.MWReferenceContextItem;