From f650813eb557b2dc9f9daccfd7024972f601d48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Tisza?= Date: Wed, 19 Mar 2014 02:00:26 +0000 Subject: [PATCH] Add embed tab to reuse dialog Implements the wikitext part of the mingle card Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/148 Change-Id: I5bcd8e2171f45c70736b7a7cfc695134269ed12d --- MultimediaViewer.php | 68 ++++ MultimediaViewerHooks.php | 2 + resources/mmv/mmv.EmbedFileFormatter.js | 60 ++++ .../mmv/model/mmv.model.EmbedFileInfo.js | 165 +++++++++ resources/mmv/ui/mmv.ui.metadataPanel.js | 7 +- resources/mmv/ui/mmv.ui.reuse.dialog.js | 14 +- resources/mmv/ui/mmv.ui.reuse.embed.js | 340 ++++++++++++++++++ resources/mmv/ui/mmv.ui.reuse.embed.less | 39 ++ .../qunit/mmv/mmv.EmbedFileFormatter.test.js | 48 +++ tests/qunit/mmv/model/mmv.model.test.js | 47 ++- .../qunit/mmv/ui/mmv.ui.reuse.dialog.test.js | 34 +- tests/qunit/mmv/ui/mmv.ui.reuse.embed.test.js | 229 ++++++++++++ 12 files changed, 1041 insertions(+), 12 deletions(-) create mode 100644 resources/mmv/mmv.EmbedFileFormatter.js create mode 100644 resources/mmv/model/mmv.model.EmbedFileInfo.js create mode 100644 resources/mmv/ui/mmv.ui.reuse.embed.js create mode 100644 resources/mmv/ui/mmv.ui.reuse.embed.less create mode 100644 tests/qunit/mmv/mmv.EmbedFileFormatter.test.js create mode 100644 tests/qunit/mmv/ui/mmv.ui.reuse.embed.test.js diff --git a/MultimediaViewer.php b/MultimediaViewer.php index d18d364e0..1d76e1929 100644 --- a/MultimediaViewer.php +++ b/MultimediaViewer.php @@ -96,6 +96,16 @@ call_user_func( function() { ), ), $moduleInfo( 'mmv/model' ) ); + $wgResourceModules['mmv.model.EmbedFileInfo'] = array_merge( array( + 'scripts' => array( + 'mmv.model.EmbedFileInfo.js', + ), + + 'dependencies' => array( + 'mmv.model', + ), + ), $moduleInfo( 'mmv/model' ) ); + $wgResourceModules['mmv.model.FileUsage'] = array_merge( array( 'scripts' => array( 'mmv.model.FileUsage.js', @@ -353,6 +363,29 @@ call_user_func( function() { ), ), $moduleInfo( 'mmv/ui' ) ); + $wgResourceModules['mmv.embedFileFormatter'] = array_merge( array( + 'scripts' => array( + 'mmv.EmbedFileFormatter.js', + ), + + 'dependencies' => array( + 'mmv.base', + 'oojs', + ), + + 'messages' => array( + 'multimediaviewer-credit', + + 'multimediaviewer-html-embed-credit-text-tbls', + 'multimediaviewer-html-embed-credit-text-tls', + 'multimediaviewer-html-embed-credit-text-tbs', + 'multimediaviewer-html-embed-credit-text-tbl', + 'multimediaviewer-html-embed-credit-text-tb', + 'multimediaviewer-html-embed-credit-text-ts', + 'multimediaviewer-html-embed-credit-text-tl', + ), + ), $moduleInfo( 'mmv' ) ); + $wgResourceModules['mmv.ui.reuse.dialog'] = array_merge( array( 'scripts' => array( 'mmv.ui.reuse.dialog.js', @@ -367,6 +400,7 @@ call_user_func( function() { 'oojs', 'oojs-ui', 'mmv.ui.reuse.share', + 'mmv.ui.reuse.embed', ), 'messages' => array( @@ -412,6 +446,40 @@ call_user_func( function() { ), ), $moduleInfo( 'mmv/ui' ) ); + $wgResourceModules['mmv.ui.reuse.embed'] = array_merge( array( + 'scripts' => array( + 'mmv.ui.reuse.embed.js', + ), + + 'styles' => array( + 'mmv.ui.reuse.embed.less', + ), + + 'dependencies' => array( + 'mmv.ui.reuse.tab', + 'oojs', + 'oojs-ui', + 'mmv.model.EmbedFileInfo', + 'mmv.embedFileFormatter', + ), + + 'messages' => array( + 'multimediaviewer-embed-tab', + 'multimediaviewer-embed-html', + 'multimediaviewer-embed-wt', + + 'multimediaviewer-embed-byline', + 'multimediaviewer-embed-license', + 'multimediaviewer-embed-via', + + 'multimediaviewer-default-embed-size', + 'multimediaviewer-original-embed-size', + 'multimediaviewer-large-embed-size', + 'multimediaviewer-medium-embed-size', + 'multimediaviewer-small-embed-size', + ), + ), $moduleInfo( 'mmv/ui' ) ); + $wgResourceModules['mmv.ui.buttons'] = array_merge( array( 'scripts' => array( 'mmv.ui.buttons.js', diff --git a/MultimediaViewerHooks.php b/MultimediaViewerHooks.php index 94f270e6e..412b66817 100644 --- a/MultimediaViewerHooks.php +++ b/MultimediaViewerHooks.php @@ -125,6 +125,7 @@ class MultimediaViewerHooks { 'tests/qunit/mmv/mmv.lightboxinterface.test.js', 'tests/qunit/mmv/mmv.lightboximage.test.js', 'tests/qunit/mmv/mmv.ThumbnailWidthCalculator.test.js', + 'tests/qunit/mmv/mmv.EmbedFileFormatter.test.js', 'tests/qunit/mmv/mmv.performance.test.js', 'tests/qunit/mmv/mmv.logger.test.js', 'tests/qunit/mmv/model/mmv.model.test.js', @@ -146,6 +147,7 @@ class MultimediaViewerHooks { 'tests/qunit/mmv/ui/mmv.ui.metadataPanel.test.js', 'tests/qunit/mmv/ui/mmv.ui.permission.test.js', 'tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js', + 'tests/qunit/mmv/ui/mmv.ui.reuse.embed.test.js', 'tests/qunit/mmv/ui/mmv.ui.reuse.share.test.js', 'tests/qunit/mmv/ui/mmv.ui.reuse.tab.test.js', 'tests/qunit/mmv/mmv.testhelpers.js', diff --git a/resources/mmv/mmv.EmbedFileFormatter.js b/resources/mmv/mmv.EmbedFileFormatter.js new file mode 100644 index 000000000..30e403c52 --- /dev/null +++ b/resources/mmv/mmv.EmbedFileFormatter.js @@ -0,0 +1,60 @@ +/* + * This file is part of the MediaWiki extension MediaViewer. + * + * MediaViewer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * MediaViewer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MediaViewer. If not, see . + */ + +( function( mw ) { + var AFP; + + /** + * Converts data in various formats needed by the Embed sub-dialog + * @class mw.mmv.EmbedFileFormatter + * @constructor + */ + function EmbedFileFormatter() {} + AFP = EmbedFileFormatter.prototype; + + /** + * Helper function to generate thumbnail wikicode + * @param {mw.Title} title + * @param {number} [width] + * @param {string} [caption] + * @return {string} + */ + AFP.getThumbnailWikitext = function ( title, width, caption ) { + var widthSection, captionSection; + + widthSection = width ? '|' + width + 'px' : ''; + captionSection = caption ? '|' + caption : ''; + + return '[[File:' + title.getMain() + widthSection + '|thumb' + captionSection + ']]'; + }; + + /** + * Helper function to generate thumbnail wikicode + * @param {mw.mmv.model.EmbedFileInfo} info + * @param {number} [width] + * @return {string} + */ + AFP.getThumbnailWikitextFromEmbedFileInfo = function ( info, width ) { + var title = info.title, + caption = info.caption; + + return this.getThumbnailWikitext( info.title, width, + caption ? caption.plain : title.getNameText() ); + }; + + mw.mmv.EmbedFileFormatter = EmbedFileFormatter; +}( mediaWiki ) ); diff --git a/resources/mmv/model/mmv.model.EmbedFileInfo.js b/resources/mmv/model/mmv.model.EmbedFileInfo.js new file mode 100644 index 000000000..ba6eb724d --- /dev/null +++ b/resources/mmv/model/mmv.model.EmbedFileInfo.js @@ -0,0 +1,165 @@ +/* + * This file is part of the MediaWiki extension MultimediaViewer. + * + * MultimediaViewer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * MultimediaViewer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MultimediaViewer. If not, see . + */ + +( function ( mw, $ ) { + /** + * Contains information needed to embed and share files. + * @class mw.mmv.model.EmbedFileInfo + * @constructor + * @param {mw.Title} title + * @param {string} src + * @param {string} url + * @param {string} [siteName] + * @param {Object} [license] + * @param {string} [license.plain] + * @param {string} [license.html] + * @param {Object} [author] + * @param {string} [author.plain] + * @param {string} [author.html] + * @param {Object} [source] + * @param {string} [source.plain] + * @param {string} [source.html] + * @param {Object} [caption] + * @param {string} [caption.plain] + * @param {string} [caption.html] + */ + function EmbedFileInfo( + title, + src, + url, + siteName, + license, + author, + source, + caption + ) { + if ( !title || !src || !url ) { + throw 'title, src and url are required and must have a value'; + } + + /** @property {mw.Title} title The title of the file */ + this.title = title; + + /** @property {string} src The URL to the original file */ + this.src = src; + + /** @property {string} url The URL to the file description page */ + this.url = url; + + /** @property {string} [siteName] Human-readable name of the site the file is on */ + this.siteName = siteName; + + /** @property {Object} [license] Description of the license of the file - with links */ + this.license = license; + + /** @property {Object} [author] Author of the file - with links */ + this.author = author; + + /** @property {Object} [source] Source for the file - with links */ + this.source = source; + + /** @property {Object} [caption] Image caption, if any */ + this.caption = caption; + } + + /** + * Helper function to turn HTML to plaintext + * @private + * @param {string} html + * @return {{plain: string, html: string}|null} + */ + function htmlToObject ( html ) { + if ( html && html.length ) { + return { + plain: $( '
' + html + '
' ).text(), + html: html + }; + } else { + return null; + } + } + + /** + * Factory method for creating an info object from html + * @param {mw.Title} title + * @param {string} src + * @param {string} url + * @param {string} [siteName] + * @param {string} [licenseHtml] + * @param {string} [authorHtml] + * @param {string} [sourceHtml] + * @param {string} [captionHtml] + * @return {mw.mmv.model.EmbedFileInfo} + */ + EmbedFileInfo.fromHtml = function ( + title, + src, + url, + siteName, + licenseHtml, + authorHtml, + sourceHtml, + captionHtml + ) { + return new EmbedFileInfo( + title, + src, + url, + siteName, + htmlToObject( licenseHtml ), + htmlToObject( authorHtml ), + htmlToObject( sourceHtml ), + htmlToObject( captionHtml ) + ); + }; + + /** + * Turns image info into EmbedFileInfo + * @param {mw.mmv.model.Image} imageInfo + * @param {string} [siteName] + * @param {string} [caption] + * @return {mw.mmv.model.EmbedFileInfo} + */ + EmbedFileInfo.fromImageInfo = function ( imageInfo, siteName, caption ) { + var title = imageInfo.title, + src = imageInfo.url, + url = imageInfo.descriptionUrl, + license = imageInfo.license, + author = imageInfo.author, + source = imageInfo.source; + + return EmbedFileInfo.fromHtml( title, src, url, siteName, license, author, source, caption ); + }; + + /** + * Turns a jQuery object into a plaintext/HTML pair + * @param $jq + * @return {{plain: string, html:string}|null} + */ + EmbedFileInfo.jqueryToObject = function ( $jq ) { + if ( $jq && $jq.length ) { + return { + plain: $jq.text(), + html: $jq.get( 0 ).outerHTML + }; + } else { + return null; + } + }; + + mw.mmv.model.EmbedFileInfo = EmbedFileInfo; +}( mediaWiki, jQuery ) ); diff --git a/resources/mmv/ui/mmv.ui.metadataPanel.js b/resources/mmv/ui/mmv.ui.metadataPanel.js index 33a6285c9..e477c8972 100644 --- a/resources/mmv/ui/mmv.ui.metadataPanel.js +++ b/resources/mmv/ui/mmv.ui.metadataPanel.js @@ -446,9 +446,12 @@ /** * Sets up the file reuse data in the DOM * @param {mw.mmv.model.Image} image + * @param {string} siteName + * @param {string} caption */ - MPP.setFileReuseData = function ( image ) { - this.fileReuse.set( image ); + MPP.setFileReuseData = function ( image, siteName, caption ) { + this.fileReuse.set( image, + mw.mmv.model.EmbedFileInfo.fromImageInfo( image, siteName, caption ) ); }; /** diff --git a/resources/mmv/ui/mmv.ui.reuse.dialog.js b/resources/mmv/ui/mmv.ui.reuse.dialog.js index da2aca613..0cdee122d 100644 --- a/resources/mmv/ui/mmv.ui.reuse.dialog.js +++ b/resources/mmv/ui/mmv.ui.reuse.dialog.js @@ -68,17 +68,21 @@ // FIXME this should happen outside the dialog and the tabs, but we need to improve DP.initTabs = function () { - var shareTab; + var shareTab, embedTab; this.tabs = { - share: new mw.mmv.ui.reuse.Share( this.$reuseDialog ) + share: new mw.mmv.ui.reuse.Share( this.$reuseDialog ), + embed: new mw.mmv.ui.reuse.Embed( this.$reuseDialog ) }; shareTab = new oo.ui.MenuItemWidget( 'share', { label: mw.message( 'multimediaviewer-share-tab' ).text() } ); + embedTab = new oo.ui.MenuItemWidget( + 'embed', { label: mw.message( 'multimediaviewer-embed-tab' ).text() } ); this.reuseTabs.addItems( [ - shareTab + shareTab, + embedTab ] ); // Default to 'share' tab @@ -154,9 +158,11 @@ /** * Sets data needed by contaned tabs and makes dialog launch link visible. * @param {mw.mmv.model.Image} image + * @param {mw.mmv.model.EmbedFileInfo} info */ - DP.set = function ( image ) { + DP.set = function ( image, info ) { this.tabs.share.set( image ); + this.tabs.embed.set( image, info ); this.$reuseLink.removeClass( 'empty' ); }; diff --git a/resources/mmv/ui/mmv.ui.reuse.embed.js b/resources/mmv/ui/mmv.ui.reuse.embed.js new file mode 100644 index 000000000..f099e0d38 --- /dev/null +++ b/resources/mmv/ui/mmv.ui.reuse.embed.js @@ -0,0 +1,340 @@ +/* + * This file is part of the MediaWiki extension MultimediaViewer. + * + * MultimediaViewer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * MultimediaViewer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MultimediaViewer. If not, see . + */ + +( function ( mw, $, oo ) { + // Shortcut for prototype later + var EP; + + /** + * UI component that provides the user html/wikitext snippets needed to share + * and/or embed a media asset. + * + * @class mw.mmv.ui.reuse.Embed + * @extends mw.mmv.ui.reuse.Tab + * @constructor + * @param {jQuery} $container + */ + function Embed( $container ) { + Embed.super.call( this, $container ); + + /** + * Formatter converting image data into formats needed for output + * @property {mw.mmv.EmbedFileFormatter} + */ + this.formatter = new mw.mmv.EmbedFileFormatter(); + + this.$pane.addClass( 'mw-mlb-embed-pane' ); + + this.$pane.appendTo( this.$container ); + + this.createSnippetTextAreas( this.$pane ); + this.createSizePulldownMenus( this.$pane ); + + /** + * Currently selected embed snippet, defaults to wikitext. + * @property {jQuery} + */ + this.$currentMainEmbedText = this.embedTextWikitext.$element; + + /** + * Currently selected size menu. + * @property {OO.ui.MenuWidget} + */ + this.currentSizeMenu = this.embedWtSizeSwitch.getMenu(); + } + oo.inheritClass( Embed, mw.mmv.ui.reuse.Tab ); + EP = Embed.prototype; + + + /** + * Creates text areas for html and wikitext snippets. + * + * @param {jQuery} $container + */ + EP.createSnippetTextAreas = function( $container ) { + this.embedTextWikitext = new oo.ui.TextInputWidget( { + classes: [ 'mw-mlb-embed-text-wt', 'active' ], + multiline: true, + readOnly: true + } ); + + $( '

' ) + .append( + this.embedTextWikitext.$element + ) + .appendTo( $container ); + }; + + /** + * Creates pulldown menus to select file sizes. + * + * @param {jQuery} $container + */ + EP.createSizePulldownMenus = function( $container ) { + // Wikitext sizes pulldown menu + this.embedWtSizeSwitch = new oo.ui.InlineMenuWidget( { + classes: [ 'mw-mlb-embed-size', 'active' ] + } ); + + this.embedWtSizeChoices = {}; + + this.embedWtSizeSwitch.getMenu().addItems( [ + this.embedWtSizeChoices.default = new oo.ui.MenuItemWidget( { name: 'default' }, { + label: mw.message( 'multimediaviewer-default-embed-size' ).text() + } ), + + this.embedWtSizeChoices.small = new oo.ui.MenuItemWidget( { + name: 'small', + height: null, + width: null + }, + { + label: mw.message( 'multimediaviewer-small-embed-size', 0, 0 ).text(), + selected: true + } ), + + this.embedWtSizeChoices.medium = new oo.ui.MenuItemWidget( { + name: 'medium', + height: null, + width: null + }, + { + label: mw.message( 'multimediaviewer-medium-embed-size', 0, 0 ).text() + } ), + + this.embedWtSizeChoices.large = new oo.ui.MenuItemWidget( { + name: 'large', + height: null, + width: null + }, + { + label: mw.message( 'multimediaviewer-large-embed-size', 0, 0 ).text() + } ) + ] ); + + this.embedWtSizeSwitch.getMenu().selectItem( this.embedWtSizeChoices.default ); + + $( '

' ) + .append( + this.embedWtSizeSwitch.$element + ) + .appendTo( $container ); + }; + + /** + * Registers listeners. + */ + EP.attach = function() { + var embed = this, + $wikitextTextarea = this.embedTextWikitext.$element.find( 'textarea' ); + + // Select all text once element gets focus + this.embedTextWikitext.onDOMEvent( 'focus', $.proxy( this.selectAllOnEvent, $wikitextTextarea ) ); + this.embedTextWikitext.onDOMEvent( 'mousedown click', $.proxy( this.onlyFocus, $wikitextTextarea ) ); + + // Register handlers for switching between file sizes + this.embedWtSizeSwitch.getMenu().on( 'select', $.proxy( embed.handleSizeSwitch, embed ) ); + }; + + /** + * Clears listeners. + */ + EP.unattach = function() { + this.constructor.super.prototype.unattach.call( this ); + + this.embedTextWikitext.offDOMEvent( 'focus mousedown click' ); + this.embedWtSizeSwitch.getMenu().off( 'select' ); + }; + + /** + * Handles size menu change events. + */ + EP.handleSizeSwitch = function ( item ) { + var value = item.getData(); + + this.changeSize( value.width, value.height ); + }; + + /** + * Changes the size, takes different actions based on which sort of + * embed is currently chosen. + * + * @param {number} width New width to set + */ + EP.changeSize = function ( width ) { + this.updateWtEmbedText( width ); + this.select(); + }; + + /** + * Updates the wikitext embed text with a new value for width. + * + * Assumes that the set method has already been called. + * @param {number} width + */ + EP.updateWtEmbedText = function ( width ) { + if ( !this.embedFileInfo ) { + return; + } + + var title = this.embedFileInfo.title, + caption = this.embedFileInfo.caption; + + this.embedTextWikitext.setValue( this.formatter.getThumbnailWikitext( + title, width, caption ? caption.plain : title.getNameText() ) ); + }; + + /** + * Shows the pane. + */ + EP.show = function () { + this.constructor.super.prototype.show.call( this ); + this.select(); + }; + + /** + * Calculates possible image sizes for wikitext snippets. It returns up to + * three possible snippet frame sizes (small, medium, large). + * + * @param {number} width + * @param {number} height + * @returns {Object} + * @returns { {width: number, height: number} } return.small + * @returns { {width: number, height: number} } return.medium + * @returns { {width: number, height: number} } return.large + */ + EP.getPossibleImageSizesForWikitext = function ( width, height ) { + var i, bucketName, + bucketWidth, + buckets = { + 'small': 300, + 'medium': 400, + 'large': 500 + }, + sizes = {}, + bucketNames = Object.keys( buckets ), + widthToHeight = height / width; + + for ( i = 0; i < bucketNames.length; i++ ) { + bucketName = bucketNames[i]; + bucketWidth = buckets[bucketName]; + + if ( width > bucketWidth ) { + sizes[bucketName] = { + width: bucketWidth, + height: Math.round( bucketWidth * widthToHeight ) + }; + } + } + + return sizes; + }; + + /** + * Gets size options for html and wikitext snippets. + * + * @param {number} width + * @param {number} height + * @returns {Object} + * @returns {Object} return.html Collection of possible image sizes for html snippets + * @returns {Object} return.wikitext Collection of possible image sizes for wikitext snippets + */ + EP.getSizeOptions = function ( width, height ) { + var sizes = {}; + + sizes.wikitext = this.getPossibleImageSizesForWikitext( width, height ); + + return sizes; + }; + + /** + * Sets the data on the element. + * + * @param {mw.mmv.model.Image} image + * @param {mw.mmv.model.EmbedFileInfo} embedFileInfo + */ + EP.set = function ( image, embedFileInfo ) { + var wtSizeSwitch = this.embedWtSizeSwitch.getMenu(), + wtSizeOptions = wtSizeSwitch.getItems(), + sizes = this.getSizeOptions( image.width, image.height ); + + this.embedFileInfo = embedFileInfo; + + this.updateMenuOptions( sizes.wikitext, wtSizeOptions ); + + this.currentSizeMenu.selectItem( this.currentSizeMenu.getSelectedItem() ); + }; + + /** + * @private + * + * Updates the menu options based on calculated sizes. + * + * @param {Object} sizes + * @param {OO.ui.MenuItemWidget[]} options + */ + EP.updateMenuOptions = function ( sizes, options ) { + var i, option, data; + + for ( i = 0; i < options.length; i++ ) { + option = options[i]; + data = option.getData(); + + if ( sizes[data.name] ) { + option.setDisabled( false ); + + // These values are later used in the else if case below as flags + // to disable an option that is no longer pertinent. Ex: User went + // from a large image from which we have options(small, med, large) to + // a small image where the only pertinent option is small. + data.width = sizes[data.name].width; + data.height = sizes[data.name].height; + + option.setLabel( + mw.message( + 'multimediaviewer-' + data.name + '-embed-size', + data.width, + data.height + ).text() + ); + } else if ( data.width && data.height ) { + option.setDisabled( true ); + + data.width = null; + data.height = null; + } + } + }; + + /** + * @inheritdoc + */ + EP.empty = function () { + this.embedTextWikitext.setValue( '' ); + + this.embedWtSizeSwitch.getMenu().hide(); + }; + + /** + * Selects the text in the current text box. + */ + EP.select = function () { + this.$currentMainEmbedText.focus(); + }; + + mw.mmv.ui.reuse.Embed = Embed; +}( mediaWiki, jQuery, OO ) ); diff --git a/resources/mmv/ui/mmv.ui.reuse.embed.less b/resources/mmv/ui/mmv.ui.reuse.embed.less new file mode 100644 index 000000000..f840978e5 --- /dev/null +++ b/resources/mmv/ui/mmv.ui.reuse.embed.less @@ -0,0 +1,39 @@ +@switch-color: #f2f2f2; +@active-switch-color: #666666; + +.mw-mlb-embed-text-html, +.mw-mlb-embed-text-wt { + display: none; + width: auto; + + &.active { + display: block; + } +} + +.mw-mlb-reuse-dialog .mw-mlb-embed-pane { + padding: 5px 27px; +} + +.mw-mlb-embed-switch { + div { + padding: 1px 3px; + background-color: @switch-color; + + &.active { + background-color: @active-switch-color; + } + } +} + +.mw-mlb-embed-size { + display: none; + + &.active { + display: block; + } + + .oo-ui-widget-disabled { + display: none; + } +} diff --git a/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js b/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js new file mode 100644 index 000000000..f65e0e3a5 --- /dev/null +++ b/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js @@ -0,0 +1,48 @@ +( function ( mw ) { + QUnit.module( 'mmv.EmbedFileFormatter', QUnit.newMwEnvironment() ); + + QUnit.test( 'EmbedFileFormatter constructor sanity check', 1, function ( assert ) { + var formatter = new mw.mmv.EmbedFileFormatter(); + assert.ok( formatter, 'constructor with no argument works'); + } ); + + QUnit.test( 'getThumbnailWikitext():', 3, function ( assert ) { + var formatter = new mw.mmv.EmbedFileFormatter(), + title = mw.Title.newFromText( 'File:Foobar.jpg' ), + imgUrl = 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg', + filePageUrl = 'https://commons.wikimedia.org/wiki/File:Foobar.jpg', + caption = 'Foobar caption.', + width = 700, + info, + wikitext; + + // Title, width and caption + info = new mw.mmv.model.EmbedFileInfo.fromHtml( title, imgUrl, filePageUrl, undefined, + undefined, undefined, undefined, caption ); + wikitext = formatter.getThumbnailWikitextFromEmbedFileInfo( info, width ); + + assert.strictEqual( + wikitext, + '[[File:Foobar.jpg|700px|thumb|Foobar caption.]]', + 'Wikitext generated correctly.' ); + + // Title, width and no caption + info = new mw.mmv.model.EmbedFileInfo.fromHtml( title, imgUrl, filePageUrl ); + wikitext = formatter.getThumbnailWikitextFromEmbedFileInfo( info , width ); + + assert.strictEqual( + wikitext, + '[[File:Foobar.jpg|700px|thumb|Foobar]]', + 'Wikitext generated correctly.' ); + + // Title, no width and no caption + info = new mw.mmv.model.EmbedFileInfo.fromHtml( title, imgUrl, filePageUrl ); + wikitext = formatter.getThumbnailWikitextFromEmbedFileInfo( info ); + + assert.strictEqual( + wikitext, + '[[File:Foobar.jpg|thumb|Foobar]]', + 'Wikitext generated correctly.' ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/mmv/model/mmv.model.test.js b/tests/qunit/mmv/model/mmv.model.test.js index ac7f25f2c..50759f499 100644 --- a/tests/qunit/mmv/model/mmv.model.test.js +++ b/tests/qunit/mmv/model/mmv.model.test.js @@ -15,7 +15,7 @@ * along with MultimediaViewer. If not, see . */ -( function ( mw ) { +( function ( mw, $ ) { QUnit.module( 'mmv.model', QUnit.newMwEnvironment() ); QUnit.test( 'Image model constructor sanity check', 21, function ( assert ) { @@ -183,4 +183,47 @@ } } ); -}( mediaWiki ) ); + QUnit.test( 'EmbedFileInfo constructor sanity check', 9, function ( assert ) { + var title = mw.Title.newFromText( 'File:Foobar.jpg' ), + src = 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg', + url = 'https://commons.wikimedia.org/wiki/File:Foobar.jpg', + siteName = 'Name of the web site', + $license = $( 'Public License' ), + $author = $( 'Homer' ), + $source = $( 'Iliad' ), + license = { + plain: $license && $license.text(), + html: $license && $license.html() + }, + author = { + plain: $author && $author.text(), + html: $author && $author.get( 0 ).outerHTML + }, + source = { + plain: $source && $source.text(), + html: $source && $source.get( 0 ).outerHTML + }, + caption = { + plain: 'Plain image caption', + html: 'HTML imgae caption' + }, + embedFileInfo = new mw.mmv.model.EmbedFileInfo( + title, src, url, siteName, license, author, source, caption ); + + assert.strictEqual( embedFileInfo.title, title, 'Title is set correctly' ); + assert.strictEqual( embedFileInfo.src, src, 'Src is set correctly' ); + assert.strictEqual( embedFileInfo.url, url, 'Url is set correctly' ); + assert.strictEqual( embedFileInfo.siteName, siteName, 'Site name is set correctly' ); + assert.strictEqual( embedFileInfo.license, license, 'License is set correctly' ); + assert.strictEqual( embedFileInfo.author, author, 'Author is set correctly' ); + assert.strictEqual( embedFileInfo.source, source, 'Source is set correctly' ); + assert.strictEqual( embedFileInfo.caption, caption, 'Caption is set correctly' ); + + try { + embedFileInfo = new mw.mmv.model.EmbedFileInfo( title ); + } catch (e) { + assert.ok( e, 'Exception is thrown when parameters are missing' ); + } + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js b/tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js index 4297116c5..0577fc054 100644 --- a/tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js +++ b/tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js @@ -57,6 +57,30 @@ reuseDialog.handleOpenCloseClick(); } ); + QUnit.test( 'handleTabSelection():', 4, function ( assert ) { + var reuseDialog = makeReuseDialog(); + + reuseDialog.tabs.share.show = function () { + assert.ok( true, 'Share tab shown.' ); + }; + reuseDialog.tabs.embed.hide = function () { + assert.ok( true, 'Embed tab hidden.' ); + }; + + // Share pane is selected + reuseDialog.handleTabSelection( { getData: function () { return 'share'; } } ); + + reuseDialog.tabs.share.hide = function () { + assert.ok( true, 'Share tab hidden.' ); + }; + reuseDialog.tabs.embed.show = function () { + assert.ok( true, 'Embed tab shown.' ); + }; + + // Embed pane is selected + reuseDialog.handleTabSelection( { getData: function () { return 'embed'; } } ); + } ); + QUnit.test( 'attach()/unattach():', 2, function ( assert ) { var reuseDialog = makeReuseDialog(); @@ -154,11 +178,12 @@ descriptionUrl: url, width: 100, height: 80 - }; + }, + embedFileInfo = new mw.mmv.model.EmbedFileInfo( title, src, url ); assert.ok( reuseDialog.$reuseLink.hasClass( 'empty' ), 'Dialog launch link is empty by default.' ); - reuseDialog.set( image ); + reuseDialog.set( image, embedFileInfo ); assert.ok( ! reuseDialog.$reuseLink.hasClass( 'empty' ), 'Dialog launch link is not empty after set().' ); @@ -178,9 +203,10 @@ descriptionUrl: url, width: 100, height: 80 - }; + }, + embedFileInfo = new mw.mmv.model.EmbedFileInfo( title, src, url ); - reuseDialog.set( image ); + reuseDialog.set( image, embedFileInfo ); assert.ok( ! reuseDialog.isOpen, 'Dialog closed by default.' ); diff --git a/tests/qunit/mmv/ui/mmv.ui.reuse.embed.test.js b/tests/qunit/mmv/ui/mmv.ui.reuse.embed.test.js new file mode 100644 index 000000000..9e9e43f95 --- /dev/null +++ b/tests/qunit/mmv/ui/mmv.ui.reuse.embed.test.js @@ -0,0 +1,229 @@ +/* + * This file is part of the MediaWiki extension MultimediaViewer. + * + * MultimediaViewer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * MultimediaViewer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MultimediaViewer. If not, see . + */ + + ( function ( mw, $ ) { + var $qf = $( '#qunit-fixture' ); + + QUnit.module( 'mmv.ui.reuse.Embed', QUnit.newMwEnvironment() ); + + QUnit.test( 'Sanity test, object creation and UI construction', 6, function ( assert ) { + var embed = new mw.mmv.ui.reuse.Embed( $qf ); + + assert.ok( embed, 'Embed UI element is created.' ); + assert.strictEqual( embed.$pane.length, 1, 'Pane div is created.' ); + assert.ok( embed.embedTextWikitext, 'Wikitext snipped text area created.' ); + assert.ok( embed.embedWtSizeSwitch, 'Size selection menu for wikitext created.' ); + assert.strictEqual( embed.$currentMainEmbedText.length, 1, 'Size selection menu for html created.' ); + assert.ok( embed.currentSizeMenu, 'Size selection menu for html created.' ); + } ); + + QUnit.test( 'changeSize(): Wikitext size menu item selected.', 2, function ( assert ) { + var embed = new mw.mmv.ui.reuse.Embed( $qf ), + width = 10, + height = 20; + + embed.updateWtEmbedText = function( w ) { + assert.strictEqual( w, width, 'Correct width passed.' ); + }; + embed.select = function( ) { + assert.ok( true, 'Item selected after update.' ); + }; + + embed.changeSize( width, height ); + } ); + + QUnit.test( 'updateWtEmbedText(): Do nothing if set() not called before.', 0, function ( assert ) { + var embed = new mw.mmv.ui.reuse.Embed( $qf ), + width = 10; + + embed.formatter.getThumbnailWikitext = function() { + assert.ok( false, 'formatter.getThumbnailWikitext() should not have been called.'); + }; + embed.updateWtEmbedText( width ); + } ); + + QUnit.test( 'updateWtEmbedText():', 3, function ( assert ) { + var embed = new mw.mmv.ui.reuse.Embed( $qf ), + title = mw.Title.newFromText( 'File:Foobar.jpg' ), + src = 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg', + url = 'https://commons.wikimedia.org/wiki/File:Foobar.jpg', + embedFileInfo = new mw.mmv.model.EmbedFileInfo( title, src, url ), + width = 10, + height = 20; + + embed.set( { width: width, height: height }, embedFileInfo ); + + embed.formatter.getThumbnailWikitext = function( t, w, c ) { + assert.strictEqual( t, title, 'Title passed correctly.'); + assert.strictEqual( w, width, 'Width passed correctly.'); + assert.strictEqual( c, title.getNameText(), 'Caption passed correctly.'); + }; + embed.updateWtEmbedText( width ); + } ); + + QUnit.test( 'Size options are correct', 3, function ( assert ) { + var embed = new mw.mmv.ui.reuse.Embed( $qf ), + exampleSizes = [ + // Big wide image + { + width: 2048, height: 1536, + expected: { + wikitext: { + small: { width: 300, height: 225 }, + medium: { width: 400, height: 300 }, + large: { width: 500, height: 375 } + } + } + }, + + // Big tall image + { + width: 201, height: 1536, + expected: { + wikitext: {} + } + }, + + // Very small image + { + width: 15, height: 20, + expected: { + wikitext: {} + } + } + ], + i, cursize, opts; + for ( i = 0; i < exampleSizes.length; i++ ) { + cursize = exampleSizes[i]; + opts = embed.getSizeOptions( cursize.width, cursize.height ); + assert.deepEqual( opts, cursize.expected, 'We got the expected results out of the size calculation function.' ); + } + } ); + + QUnit.test( 'set():', 3, function ( assert ) { + var embed = new mw.mmv.ui.reuse.Embed( $qf ), + title = mw.Title.newFromText( 'File:Foobar.jpg' ), + src = 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg', + url = 'https://commons.wikimedia.org/wiki/File:Foobar.jpg', + embedFileInfo = new mw.mmv.model.EmbedFileInfo( title, src, url ), + width = 15, + height = 20; + + embed.updateMenuOptions = function( sizes, options ) { + assert.strictEqual( options.length, 4, 'Options passed correctly.' ); + }; + + assert.ok( !embed.embedFileInfo, 'embedFileInfo not set yet.' ); + + embed.set( { width: width, height: height }, embedFileInfo ); + + assert.ok( embed.embedFileInfo, 'embedFileInfo correctly set.' ); + } ); + + QUnit.test( 'updateMenuOptions():', 3, function ( assert ) { + var embed = new mw.mmv.ui.reuse.Embed( $qf ), + options = embed.embedWtSizeSwitch.getMenu().getItems(), + width = 700, + height = 500, + sizes = embed.getSizeOptions( width, height ), + oldMessage = mw.message; + + mw.message = function( messageKey ) { + assert.ok( messageKey.match(/^multimediaviewer-(small|medium|large)/), 'messageKey passed correctly.' ); + + return { text: $.noop }; + }; + + embed.updateMenuOptions( sizes.wikitext, options ); + + mw.message = oldMessage; + } ); + + QUnit.test( 'empty():', 3, function ( assert ) { + var embed = new mw.mmv.ui.reuse.Embed( $qf ), + title = mw.Title.newFromText( 'File:Foobar.jpg' ), + src = 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg', + url = 'https://commons.wikimedia.org/wiki/File:Foobar.jpg', + embedFileInfo = new mw.mmv.model.EmbedFileInfo( title, src, url ), + width = 15, + height = 20; + + embed.set( { width: width, height: height }, embedFileInfo ); + embed.updateWtEmbedText( width ); + + assert.notStrictEqual( embed.embedTextWikitext.getValue(), '', 'embedTextWikitext is not empty.' ); + + embed.empty(); + + assert.strictEqual( embed.embedTextWikitext.getValue(), '', 'embedTextWikitext is empty.' ); + assert.ok( ! embed.embedWtSizeSwitch.getMenu().isVisible(), 'Wikitext size menu should be hidden.' ); + } ); + + QUnit.test( 'attach()/unattach():', 2, function ( assert ) { + var embed = new mw.mmv.ui.reuse.Embed( $qf ), + title = mw.Title.newFromText( 'File:Foobar.jpg' ), + src = 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg', + url = 'https://commons.wikimedia.org/wiki/File:Foobar.jpg', + embedFileInfo = new mw.mmv.model.EmbedFileInfo( title, src, url ), + width = 15, + height = 20; + + embed.set( { width: width, height: height }, embedFileInfo ); + + embed.selectAllOnEvent = function() { + assert.ok( false, 'selectAllOnEvent should not have been called.' ); + }; + embed.handleSizeSwitch = function() { + assert.ok( false, 'handleTypeSwitch should not have been called.' ); + }; + + // Triggering action events before attaching should do nothing + embed.embedTextWikitext.$element.focus(); + embed.embedWtSizeSwitch.getMenu().emit( + 'select', embed.embedWtSizeSwitch.getMenu().getSelectedItem() ); + + embed.selectAllOnEvent = function() { + assert.ok( true, 'selectAllOnEvent was called.' ); + }; + embed.handleSizeSwitch = function() { + assert.ok( true, 'handleTypeSwitch was called.' ); + }; + + embed.attach(); + + // Action events should be handled now + embed.embedTextWikitext.$element.focus(); + embed.embedWtSizeSwitch.getMenu().emit( + 'select', embed.embedWtSizeSwitch.getMenu().getSelectedItem() ); + + // Test the unattach part + embed.selectAllOnEvent = function() { + assert.ok( false, 'selectAllOnEvent should not have been called.' ); + }; + embed.handleSizeSwitch = function() { + assert.ok( false, 'handleTypeSwitch should not have been called.' ); + }; + + embed.unattach(); + + // Triggering action events now that we are unattached should do nothing + embed.embedTextWikitext.$element.focus(); + embed.embedWtSizeSwitch.getMenu().emit( + 'select', embed.embedWtSizeSwitch.getMenu().getSelectedItem() ); + } ); + +}( mediaWiki, jQuery ) );