modules/ve-mw/ui/widgets/ve.ui.MWMediaInfoFieldWidget.js
/*!
* VisualEditor user interface MWMediaDialog class.
*
* @copyright See AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/* global moment */
/**
* MWMediaInfoFieldWidget widget for displaying media information from the API.
*
* @class
* @extends OO.ui.Widget
* @mixes OO.ui.mixin.IconElement
* @mixes OO.ui.mixin.TitledElement
*
* @constructor
* @param {jQuery|string|OO.ui.HtmlSnippet} content API response data from which to build the display
* @param {Object} [config] Configuration options
* @param {string} [config.href] A url encapsulating the field text. If a label is attached it will include the label.
* @param {string} [config.labelMsg] A ve.msg() label string for the field.
* @param {boolean} [config.isDate=false] Field text is a date that will be converted to 'fromNow' string.
* @param {string} [config.type='attribute'] Field type, either 'description' or 'attribute'
* @param {string} [config.descriptionHeight='4em'] Height limit for description fields
*/
ve.ui.MWMediaInfoFieldWidget = function VeUiMWMediaInfoFieldWidget( content, config ) {
// Configuration initialization
config = config || {};
// Parent constructor
ve.ui.MWMediaInfoFieldWidget.super.call( this, config );
// Mixin constructors
OO.ui.mixin.IconElement.call( this, config );
OO.ui.mixin.LabelElement.call( this, ve.extendObject( { $label: $( '<div>' ) }, config ) );
this.$text = $( '<div>' )
.addClass( 've-ui-mwMediaInfoFieldWidget-text' );
this.type = config.type || 'attribute';
// Initialization
if ( typeof content === 'string' ) {
let datetime;
if ( config.isDate && ( datetime = moment( content ) ).isValid() ) {
content = datetime.fromNow();
}
if ( config.labelMsg ) {
// Messages defined in ve.ui.MWMediaDialog#buildMediaInfoPanel
// eslint-disable-next-line mediawiki/msg-doc
content = ve.msg( config.labelMsg, content );
}
if ( config.href ) {
// This variable may contain either jQuery objects or strings
// eslint-disable-next-line no-jquery/variable-pattern
content = $( '<a>' )
.attr( 'href',
// For the cases where we get urls that are "local"
// without http(s) prefix, we will add that prefix
// ourselves
!/^(https?:)?\/\//.test( config.href ) ?
'//' + config.href :
config.href
)
.text( content );
}
}
if ( typeof content === 'string' ) {
this.$text.text( content );
} else if ( content instanceof OO.ui.HtmlSnippet ) {
// eslint-disable-next-line no-jquery/no-html
this.$text.html( content.toString() );
} else if ( content instanceof $ ) {
// eslint-disable-next-line no-jquery/no-append-html
this.$text.append( content );
} else {
throw new Error( 'Unexpected metadata field content' );
}
this.$element
.append( this.$icon, this.$label )
.addClass( 've-ui-mwMediaInfoFieldWidget' )
// The following classes are used here:
// * ve-ui-mwMediaInfoFieldWidget-description
// * ve-ui-mwMediaInfoFieldWidget-attribute
.addClass( 've-ui-mwMediaInfoFieldWidget-' + this.type );
this.$icon.addClass( 've-ui-mwMediaInfoFieldWidget-icon' );
if ( this.type === 'description' ) {
// Limit height
this.readMoreButton = new OO.ui.ButtonWidget( {
framed: false,
icon: 'expand',
label: ve.msg( 'visualeditor-dialog-media-info-readmore' ),
classes: [ 've-ui-mwMediaInfoFieldWidget-readmore' ]
} );
this.readMoreButton.toggle( false );
this.readMoreButton.connect( this, { click: 'onReadMoreClick' } );
this.$text
.css( 'maxHeight', config.descriptionHeight || '4em' );
this.$element
.append( this.readMoreButton.$element );
}
this.setLabel( this.$text );
};
/* Setup */
OO.inheritClass( ve.ui.MWMediaInfoFieldWidget, OO.ui.Widget );
OO.mixinClass( ve.ui.MWMediaInfoFieldWidget, OO.ui.mixin.IconElement );
OO.mixinClass( ve.ui.MWMediaInfoFieldWidget, OO.ui.mixin.LabelElement );
/* Static Properties */
ve.ui.MWMediaInfoFieldWidget.static.tagName = 'div';
/**
* Define a height threshold for the description fields.
* If the rendered field's height is under the defined limit
* (max-height + threshold) we should remove the max-height
* and display the field as-is.
* This prevents cases where "read more" appears but only
* exposes only a few pixels or a line extra.
*
* @property {number} Threshold in pixels
*/
ve.ui.MWMediaInfoFieldWidget.static.threshold = 24;
/**
* Toggle the read more button according to whether it should be
* visible or not.
*/
ve.ui.MWMediaInfoFieldWidget.prototype.initialize = function () {
if ( this.getType() === 'description' ) {
const actualHeight = this.$text.prop( 'scrollHeight' );
const containerHeight = this.$text.outerHeight( true );
if ( actualHeight < containerHeight + this.constructor.static.threshold ) {
// The contained result is big enough to show. Remove the maximum height
this.$text
.css( 'maxHeight', '' );
} else {
// Only show the readMore button if it should be shown
this.readMoreButton.toggle( containerHeight < actualHeight );
}
}
};
/**
* Respond to read more button click event.
*/
ve.ui.MWMediaInfoFieldWidget.prototype.onReadMoreClick = function () {
this.readMoreButton.toggle( false );
this.$text.css( 'maxHeight', this.$text.prop( 'scrollHeight' ) );
};
/**
* Get field type; 'attribute' or 'description'
*
* @return {string} Field type
*/
ve.ui.MWMediaInfoFieldWidget.prototype.getType = function () {
return this.type;
};