/*! * 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: $( '
' ) }, config ) ); this.$text = $( '
' ) .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 = $( '' ) .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; };