From 56f923ae218ab8025157377e0bb5b91a7b85b4fa Mon Sep 17 00:00:00 2001 From: Gilles Dubuc Date: Mon, 17 Mar 2014 09:07:53 +0100 Subject: [PATCH] Add share panel, replace reuse Mingle: 147 Change-Id: I5678002ac84965a80f5e63635151032e6c293194 --- MultimediaViewer.i18n.php | 122 ++++++++-- MultimediaViewer.php | 59 ++++- MultimediaViewerHooks.php | 3 + docs/external.js | 10 + resources/mmv/mmv.ThumbnailWidthCalculator.js | 3 - resources/mmv/mmv.js | 2 +- resources/mmv/mmv.less | 13 +- .../mmv/provider/mmv.provider.UserInfo.js | 1 - resources/mmv/{ => ui}/img/link-hover.svg | 0 resources/mmv/{ => ui}/img/link.svg | 0 resources/mmv/ui/mmv.mixins.less | 11 + resources/mmv/ui/mmv.ui.canvas.js | 2 - resources/mmv/ui/mmv.ui.fileReuse.js | 198 ---------------- resources/mmv/ui/mmv.ui.fileReuse.less | 15 -- resources/mmv/ui/mmv.ui.js | 37 ++- resources/mmv/ui/mmv.ui.metadataPanel.js | 26 +- resources/mmv/ui/mmv.ui.reuse.dialog.js | 224 ++++++++++++++++++ resources/mmv/ui/mmv.ui.reuse.dialog.less | 126 ++++++++++ resources/mmv/ui/mmv.ui.reuse.share.js | 105 ++++++++ resources/mmv/ui/mmv.ui.reuse.share.less | 30 +++ resources/mmv/ui/mmv.ui.reuse.tab.js | 73 ++++++ tests/qunit/mmv/model/mmv.model.test.js | 1 + .../qunit/mmv/ui/mmv.ui.metadataPanel.test.js | 5 +- .../qunit/mmv/ui/mmv.ui.reuse.dialog.test.js | 196 +++++++++++++++ tests/qunit/mmv/ui/mmv.ui.reuse.share.test.js | 88 +++++++ tests/qunit/mmv/ui/mmv.ui.reuse.tab.test.js | 43 ++++ 26 files changed, 1110 insertions(+), 283 deletions(-) rename resources/mmv/{ => ui}/img/link-hover.svg (100%) rename resources/mmv/{ => ui}/img/link.svg (100%) delete mode 100644 resources/mmv/ui/mmv.ui.fileReuse.js delete mode 100644 resources/mmv/ui/mmv.ui.fileReuse.less create mode 100644 resources/mmv/ui/mmv.ui.reuse.dialog.js create mode 100644 resources/mmv/ui/mmv.ui.reuse.dialog.less create mode 100644 resources/mmv/ui/mmv.ui.reuse.share.js create mode 100644 resources/mmv/ui/mmv.ui.reuse.share.less create mode 100644 resources/mmv/ui/mmv.ui.reuse.tab.js create mode 100644 tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js create mode 100644 tests/qunit/mmv/ui/mmv.ui.reuse.share.test.js create mode 100644 tests/qunit/mmv/ui/mmv.ui.reuse.tab.test.js diff --git a/MultimediaViewer.i18n.php b/MultimediaViewer.i18n.php index cd98f28ec..4a08f296d 100644 --- a/MultimediaViewer.i18n.php +++ b/MultimediaViewer.i18n.php @@ -61,11 +61,6 @@ $messages['en'] = array( 'multimediaviewer-permission-link' => 'view terms', 'multimediaviewer-permission-viewmore' => 'View more', - 'multimediaviewer-use-file' => 'Use this file', - 'multimediaviewer-use-file-owt' => 'Use this file on a wiki page, as a thumbnail', - 'multimediaviewer-use-file-own' => 'Use this file on a wiki page, inline', - 'multimediaviewer-use-file-offwiki' => 'Use this file on another website', - 'multimediaviewer-about-mmv' => 'About Media Viewer', 'multimediaviewer-discuss-mmv' => 'Leave feedback', @@ -83,6 +78,36 @@ $messages['en'] = array( 'multimediaviewer-fileusage-link' => 'View all uses', 'multimediaviewer-fileusage-local-section' => 'On this site', 'multimediaviewer-fileusage-global-section' => 'On other sites', + + 'multimediaviewer-reuse-link' => 'Use this file', + + 'multimediaviewer-share-tab' => 'Share', + 'multimediaviewer-embed-tab' => 'Embed', + + 'multimediaviewer-link-to-page' => 'Link to file description page', + 'multimediaviewer-link-to-file' => 'Link to original file', + + 'multimediaviewer-embed-wt' => 'Wikitext', + 'multimediaviewer-embed-html' => 'HTML', + + // Ridiculously complicated messages for embedding + 'multimediaviewer-html-embed-credit-text-tbls' => '"$1" by $2. Licensed under $3 via $4.', + 'multimediaviewer-html-embed-credit-text-tls' => '"$1". Licensed under $2 via $3.', + 'multimediaviewer-html-embed-credit-text-tbs' => '"$1" by $2. Via $3.', + 'multimediaviewer-html-embed-credit-text-tbl' => '"$1" by $2. Licensed under $3.', + 'multimediaviewer-html-embed-credit-text-tb' => '"$1" by $2.', + 'multimediaviewer-html-embed-credit-text-ts' => '"$1". Via $2.', + 'multimediaviewer-html-embed-credit-text-tl' => '"$1". Licensed under $2.', + 'multimediaviewer-embed-byline' => 'By $1', + 'multimediaviewer-embed-license' => 'Licensed under $1.', + 'multimediaviewer-embed-via' => 'Via $1.', + + // Embed size choices + 'multimediaviewer-default-embed-size' => 'Default thumbnail size', + 'multimediaviewer-original-embed-size' => 'Original size - $1 × $2 px', + 'multimediaviewer-large-embed-size' => 'Large - $1 × $2 px', + 'multimediaviewer-medium-embed-size' => 'Medium - $1 × $2 px', + 'multimediaviewer-small-embed-size' => 'Small - $1 × $2 px', ); /** Message documentation (Message documentation) @@ -160,24 +185,7 @@ They will usually be derived from the HTML output from wikitext on a file descri 'multimediaviewer-permission-link' => 'Text of the link (on top of the metadata box) which shows additional license terms', 'multimediaviewer-permission-viewmore' => 'Text of the link (at the cutoff of the license term preview) which shows additional license terms. {{Identical|View more}}', - 'multimediaviewer-use-file' => 'Link that opens a dialog with options for sharing the file, e.g. onwiki or on another site. Similar to the Commons gadget stockPhoto.', - 'multimediaviewer-use-file-owt' => 'Label for input box which has wikitext used to show an image with the thumb option and a helpful caption. -The wikitext is like [[filename|thumb|desc]]. - -Similar to the Commons gadget stockPhoto. - -See also: -* {{msg-mw|Multimediaviewer-use-file-own}}', - 'multimediaviewer-use-file-own' => 'Label for input box which has wikitext used to show an image inline with a helpful title attribute. - -The wikitext is like [[filename|desc]]. - -Similar to the Commons gadget stockPhoto. - -See also: -* {{msg-mw|Multimediaviewer-use-file-owt}}', - 'multimediaviewer-use-file-offwiki' => 'Label for HTML used to show an image on an external site, with a link back to the wiki. Similar to the Commons gadget stockPhoto.', 'multimediaviewer-about-mmv' => 'Text for a link to a page with more information about Media Viewer software.', 'multimediaviewer-discuss-mmv' => 'Text for a link to a page where the user can discuss the Media Viewer software. {{Identical|Leave feedback}}', @@ -235,6 +243,76 @@ Followed by {{msg-mw|Multimediaviewer-fileusage-link}} and the list of pages. See also: * {{msg-mw|Multimediaviewer-fileusage-local-section}}', + + 'multimediaviewer-reuse-link' => 'Text of the link on the metadata panel which opens the reuse panel', + 'multimediaviewer-share-tab' => 'Tab title text for the file reuse panel - used for the section with shareable URLs.', + 'multimediaviewer-embed-tab' => 'Tab title text for the file reuse panel - used for the section with embeddable HTML and wikitext.', + + 'multimediaviewer-link-to-page' => 'Used as alt-text to describe a URL that goes to a File: page for an image.', + 'multimediaviewer-link-to-file' => 'Used as alt-text to describe a URL that goes to an image file.', + + 'multimediaviewer-embed-wt' => 'Used to represent a choice for embedding a file in a wiki page, as wikitext.', + 'multimediaviewer-embed-html' => 'Used to represent a choice for embedding a file in an HTML document, as HTML.', + + 'multimediaviewer-html-embed-credit-text-tbls' => 'Credit text, used when generating HTML to reuse an image. +Which one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available. +* $1 - name of the work (typically the filename without an extension) +* $2 - name of the author +* $3 - name of the license +* $4 - name of the website/institution which was the direct source for this image +Each of the parameters could be either plain text or a link.', + 'multimediaviewer-html-embed-credit-text-tls' => 'Credit text, used when generating HTML to reuse an image. +Which one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available. +* $1 - name of the work (typically the filename without an extension) +* $2 - name of the license +* $3 - name of the website/institution which was the direct source for this image +Each of the parameters could be either plain text or a link.', + 'multimediaviewer-html-embed-credit-text-tbs' => 'Credit text, used when generating HTML to reuse an image. +Which one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available. +* $1 - name of the work (typically the filename without an extension) +* $2 - name of the author +* $3 - name of the website/institution which was the direct source for this image +Each of the parameters could be either plain text or a link.', + 'multimediaviewer-html-embed-credit-text-tbl' => 'Credit text, used when generating HTML to reuse an image. +Which one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available. +* $1 - name of the work (typically the filename without an extension) +* $2 - name of the author +* $3 - name of the license +Each of the parameters could be either plain text or a link.', + 'multimediaviewer-html-embed-credit-text-tb' => 'Credit text, used when generating HTML to reuse an image. +Which one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available. +* $1 - name of the work (typically the filename without an extension) +* $2 - name of the author +Each of the parameters could be either plain text or a link.', + 'multimediaviewer-html-embed-credit-text-ts' => 'Credit text, used when generating HTML to reuse an image. +Which one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available. +* $1 - name of the work (typically the filename without an extension) +* $2 - name of the website/institution which was the direct source for this image +Each of the parameters could be either plain text or a link.', + 'multimediaviewer-html-embed-credit-text-tl' => 'Credit text, used when generating HTML to reuse an image. +Which one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available. +* $1 - name of the work (typically the filename without an extension) +* $2 - name of the license +Each of the parameters could be either plain text or a link.', + + 'multimediaviewer-embed-byline' => 'Byline (author credit) text, used when generating wikitext/HTML to reuse the image. $1 is author name.', + 'multimediaviewer-embed-license' => 'License information, used when generating wikitext/HTML to reuse the image. $1 is the license name.', + 'multimediaviewer-embed-via' => 'Source information (e. g. "via Flickr"), used when generating wikitext/HTML to reuse the image. +$1 is source (probably a website or institution name)', + + 'multimediaviewer-default-embed-size' => 'Text of size selector option which will generate wikitext for a thumbnail without explicit size.', + 'multimediaviewer-original-embed-size' => 'Text of size selector option which will generate wikitext for a thumbnail with the original (full) size. +* $1 - width in pixels +* $2 - height in pixels', + 'multimediaviewer-large-embed-size' => 'Text of size selector option which will generate wikitext for a thumbnail with small size. +* $1 - width in pixels +* $2 - height in pixels', + 'multimediaviewer-medium-embed-size' => 'Text of size selector option which will generate wikitext for a thumbnail with medium size. +* $1 - width in pixels +* $2 - height in pixels', + 'multimediaviewer-small-embed-size' => 'Text of size selector option which will generate wikitext for a thumbnail with large size. +* $1 - width in pixels +* $2 - height in pixels', ); /** Arabic (العربية) diff --git a/MultimediaViewer.php b/MultimediaViewer.php index 79f56d49b..d18d364e0 100644 --- a/MultimediaViewer.php +++ b/MultimediaViewer.php @@ -316,13 +316,12 @@ call_user_func( function() { ), 'dependencies' => array( - 'mediawiki.language', 'mmv.ui', 'mmv.ui.categories', 'mmv.ui.description', - 'mmv.ui.fileReuse', 'mmv.ui.fileUsage', 'mmv.ui.permission', + 'mmv.ui.reuse.dialog', 'moment', 'oojs', ), @@ -354,18 +353,62 @@ call_user_func( function() { ), ), $moduleInfo( 'mmv/ui' ) ); - $wgResourceModules['mmv.ui.fileReuse'] = array_merge( array( + $wgResourceModules['mmv.ui.reuse.dialog'] = array_merge( array( 'scripts' => array( - 'mmv.ui.fileReuse.js', + 'mmv.ui.reuse.dialog.js', ), 'styles' => array( - 'mmv.ui.fileReuse.less', + 'mmv.ui.reuse.dialog.less', ), 'dependencies' => array( 'mmv.ui', 'oojs', + 'oojs-ui', + 'mmv.ui.reuse.share', + ), + + 'messages' => array( + 'multimediaviewer-reuse-link', + ), + ), $moduleInfo( 'mmv/ui' ) ); + + $wgResourceModules['mmv.ui.reuse.tab'] = array_merge( array( + 'scripts' => array( + 'mmv.ui.reuse.tab.js', + ), + + 'dependencies' => array( + 'mmv.ui', + 'oojs', + ), + + 'messages' => array( + 'multimediaviewer-reuse-link', + ), + ), $moduleInfo( 'mmv/ui' ) ); + + $wgResourceModules['mmv.ui.reuse.share'] = array_merge( array( + 'scripts' => array( + 'mmv.ui.reuse.share.js', + ), + + 'styles' => array( + 'mmv.ui.reuse.share.less', + ), + + 'dependencies' => array( + 'mmv.ui.reuse.tab', + 'oojs', + 'oojs-ui', + ), + + 'messages' => array( + 'multimediaviewer-share-tab', + + 'multimediaviewer-link-to-file', + 'multimediaviewer-link-to-page', ), ), $moduleInfo( 'mmv/ui' ) ); @@ -438,15 +481,11 @@ call_user_func( function() { 'jquery.hidpi', 'jquery.scrollTo', 'jquery.throttle-debounce', - 'jquery.ui.dialog', + 'jquery.hidpi', ), 'messages' => array( 'multimediaviewer-file-page', - 'multimediaviewer-use-file', - 'multimediaviewer-use-file-owt', - 'multimediaviewer-use-file-own', - 'multimediaviewer-use-file-offwiki', 'multimediaviewer-desc-nil', ), ), $moduleInfo( 'mmv' ) ); diff --git a/MultimediaViewerHooks.php b/MultimediaViewerHooks.php index 328a529fc..94f270e6e 100644 --- a/MultimediaViewerHooks.php +++ b/MultimediaViewerHooks.php @@ -145,6 +145,9 @@ class MultimediaViewerHooks { 'tests/qunit/mmv/ui/mmv.ui.fileUsage.test.js', '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.share.test.js', + 'tests/qunit/mmv/ui/mmv.ui.reuse.tab.test.js', 'tests/qunit/mmv/mmv.testhelpers.js', ), 'dependencies' => array( diff --git a/docs/external.js b/docs/external.js index dd786b398..3e095f355 100644 --- a/docs/external.js +++ b/docs/external.js @@ -25,6 +25,16 @@ * An HTML element. */ +/** + * @class OO.ui.MenuItemWidget + * + */ + +/** + * @class OO.ui.MenuWidget + * + */ + /** * @class XMLHttpRequest * An AJAX request diff --git a/resources/mmv/mmv.ThumbnailWidthCalculator.js b/resources/mmv/mmv.ThumbnailWidthCalculator.js index e9c30f12d..f7295987d 100644 --- a/resources/mmv/mmv.ThumbnailWidthCalculator.js +++ b/resources/mmv/mmv.ThumbnailWidthCalculator.js @@ -82,7 +82,6 @@ }; /** - * @method * Finds the smallest bucket which is large enough to hold the target size * (i. e. the smallest bucket whose size is equal to or greater than the target). * If none of the buckets are large enough, returns the largest bucket. @@ -106,7 +105,6 @@ }; /** - * @method * @protected * Finds the largest width for an image so that it will still fit into a given bounding box, * based on the size of a sample (some smaller version of the same image, like the thumbnail @@ -132,7 +130,6 @@ }; /** - * @method * Finds the largest width for an image so that it will still fit into a given bounding box, * based on the size of a sample (some smaller version of the same image, like the thumbnail * shown in the article) which is used to calculate the ratio. diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js index ea325b93f..380c2422f 100755 --- a/resources/mmv/mmv.js +++ b/resources/mmv/mmv.js @@ -627,7 +627,7 @@ viewer.prevImage(); } ).on( 'mmv-resize.mmvp', function () { viewer.resize( viewer.ui ); - }); + } ); }; /** diff --git a/resources/mmv/mmv.less b/resources/mmv/mmv.less index 2337b13b5..ac66cb473 100644 --- a/resources/mmv/mmv.less +++ b/resources/mmv/mmv.less @@ -1,3 +1,5 @@ +@import "ui/mmv.mixins"; + /** * Animation helper from core */ @@ -186,6 +188,10 @@ body.mobile .mw-mlb-controls, background-color: rgb(0,0,0); } +.mw-mlb-title-contain { + position: relative; +} + .mw-mlb-license, .mw-mlb-title-contain { vertical-align: middle; @@ -380,12 +386,7 @@ body.mw-mlb-lightbox-open #content { &.pointing-down { background-position: center top; - transform: rotate(180deg); - -moz-transform: rotate(180deg); - -o-transform: rotate(180deg); - -webkit-transform: rotate(180deg); - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2.0); - -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2.0)"; + .rotate(180deg); } } diff --git a/resources/mmv/provider/mmv.provider.UserInfo.js b/resources/mmv/provider/mmv.provider.UserInfo.js index 425601624..b018dd820 100644 --- a/resources/mmv/provider/mmv.provider.UserInfo.js +++ b/resources/mmv/provider/mmv.provider.UserInfo.js @@ -31,7 +31,6 @@ oo.inheritClass( UserInfo, mw.mmv.provider.Api ); /** - * @method * Runs an API GET request to get the user info. * @param {string} username * @param {mw.mmv.model.Repo} repoInfo diff --git a/resources/mmv/img/link-hover.svg b/resources/mmv/ui/img/link-hover.svg similarity index 100% rename from resources/mmv/img/link-hover.svg rename to resources/mmv/ui/img/link-hover.svg diff --git a/resources/mmv/img/link.svg b/resources/mmv/ui/img/link.svg similarity index 100% rename from resources/mmv/img/link.svg rename to resources/mmv/ui/img/link.svg diff --git a/resources/mmv/ui/mmv.mixins.less b/resources/mmv/ui/mmv.mixins.less index 6d80cdac5..f7e9a8a98 100644 --- a/resources/mmv/ui/mmv.mixins.less +++ b/resources/mmv/ui/mmv.mixins.less @@ -28,3 +28,14 @@ filter: e(%("progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ffffff',GradientType=0 )", rgbahex(@invisible), rgbahex(@backgroundColor))); // IE6-9 } + +.rotate(@degrees: 45deg) { + -webkit-transform: rotate(@degrees); + -moz-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + -o-transform: rotate(@degrees); + transform: rotate(@degrees); + + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=(@degrees / 90.0)); + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=(@degrees / 90.0))"; +} \ No newline at end of file diff --git a/resources/mmv/ui/mmv.ui.canvas.js b/resources/mmv/ui/mmv.ui.canvas.js index 89fd496b9..043747541 100644 --- a/resources/mmv/ui/mmv.ui.canvas.js +++ b/resources/mmv/ui/mmv.ui.canvas.js @@ -164,7 +164,6 @@ }; /** - * @method * Sets page thumbnail for display if blowupFactor <= MAX_BLOWUP_FACTOR. Otherwise thumb is not set. * The image gets also blured to avoid pixelation if blowupFactor > BLUR_BLOWUP_FACTOR_THRESHOLD. * We set SVG files to the maximum screen size available. @@ -267,7 +266,6 @@ }; /** - * @method * Gets the widths for a given lightbox image. * @param {mw.mmv.LightboxImage} image * @returns {mw.mmv.model.ThumbnailWidth} diff --git a/resources/mmv/ui/mmv.ui.fileReuse.js b/resources/mmv/ui/mmv.ui.fileReuse.js deleted file mode 100644 index f9f4763a6..000000000 --- a/resources/mmv/ui/mmv.ui.fileReuse.js +++ /dev/null @@ -1,198 +0,0 @@ -/* - * 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 FRP; - - /** - * Represents the file reuse dialog and link to open it. - * @class mw.mmv.ui.FileReuse - * @extends mw.mmv.ui.Element - */ - function FileReuse( $container ) { - var reuse = this; - - mw.mmv.ui.Element.call( this, $container ); - - this.$useFileLi = $( '
  • ' ) - .addClass( 'mw-mlb-usefile-li empty' ) - .appendTo( this.$container ); - - this.$useFile = $( '' ) - .addClass( 'mw-mlb-usefile' ) - .prop( 'href', '#' ) - .text( mw.message( 'multimediaviewer-use-file' ).text() ) - .click( function () { - mw.mmv.logger.log( 'use-this-file-link-click' ); - reuse.openDialog(); - return false; - } ) - .appendTo( this.$useFileLi ); - } - - oo.inheritClass( FileReuse, mw.mmv.ui.Element ); - - FRP = FileReuse.prototype; - - /** - * @method - * Saves some data about the image on the $useFile element for later setup. - * @param {mw.Title} title - * @param {string} src The URL for the full-size image - * @param {boolean} isLocal Whether the file is on this wiki or not - */ - FRP.set = function ( title, src, isLocal, author, license ) { - this.$useFile.data( 'title', title ); - this.$useFile.data( 'src', src ); - this.$useFile.data( 'isLocal', isLocal ); - this.$useFile.data( 'author', author ); - this.$useFile.data( 'license', license ); - this.$useFileLi.removeClass( 'empty' ); - }; - - /** - * @method - * @inheritdoc - */ - FRP.empty = function () { - this.$useFile.data( 'title', null ); - this.$useFile.data( 'link', null ); - this.$useFile.data( 'src', null ); - this.$useFile.data( 'isLocal', null ); - this.$useFile.data( 'author', null ); - this.$useFile.data( 'license', null ); - this.$useFileLi.addClass( 'empty' ); - }; - - /** - * @method - * Opens a dialog with information about file reuse. - */ - FRP.openDialog = function () { - // Only open dialog once - if ( this.$dialog ) { - return false; - } - - function selectAllOnEvent() { - this.select(); - } - - var fileTitle = this.$useFile.data( 'title' ), - - filename = fileTitle.getPrefixedText(), - desc = fileTitle.getNameText(), - - linkPrefix = this.$useFile.data( 'isLocal' ) ? mw.config.get( 'wgServer' ) : '', - src = this.$useFile.data( 'src' ), - link = this.$useFile.data( 'link' ) || src, - pattern = /^\/[^\/]/, - finalLink = pattern.test(link) ? linkPrefix +link: link, - - license = this.$useFile.data( 'license' ) || '', - author = this.$useFile.data( 'author' ) || '', - linkTitle = ( license + ( author ? ' (' + author + ')' : '' ) ).trim(), - - owtId = 'mw-mlb-use-file-onwiki-thumb', - ownId = 'mw-mlb-use-file-onwiki-normal', - owId = 'mw-mlb-use-file-offwiki', - - reuse = this, - - $owtLabel = $( '
    ' ) - .append( $owtLabel, - $owtField - ), - - $ownLabel = $( '
    ' ) - .append( - $ownLabel, - $ownField - ), - - $owLabel = $( '' ), - - - $offWiki = $( '
    ' ) - .append( - $owLabel, - $owField - ); - - this.$dialog = $( '
    ' ) - .addClass( 'mw-mlb-use-file-dialog' ) - .append( - $onWikiThumb, - $onWikiNormal, - $offWiki - ) - .dialog( { - width: 750, - close: function () { - // Delete the dialog object - reuse.$dialog = undefined; - } - } ); - - $owtField.focus(); - - return false; - }; - - /** - * Closes the dialog forcefully - */ - FRP.closeDialog = function () { - if ( this.$dialog ) { - this.$dialog.dialog( 'close' ); - } - }; - - mw.mmv.ui.FileReuse = FileReuse; -}( mediaWiki, jQuery, OO ) ); diff --git a/resources/mmv/ui/mmv.ui.fileReuse.less b/resources/mmv/ui/mmv.ui.fileReuse.less deleted file mode 100644 index 56ebc1c5d..000000000 --- a/resources/mmv/ui/mmv.ui.fileReuse.less +++ /dev/null @@ -1,15 +0,0 @@ -.mw-mlb-image-links li.mw-mlb-usefile-li:before { - /* @embed */ - background-image: url(img/use-ltr.svg) !important; -} - -.mw-mlb-use-file-dialog label { - input { - width: 100%; - } - - label { - display: block; - font-weight: bold; - } -} diff --git a/resources/mmv/ui/mmv.ui.js b/resources/mmv/ui/mmv.ui.js index e48c5efa4..e0c1adb15 100644 --- a/resources/mmv/ui/mmv.ui.js +++ b/resources/mmv/ui/mmv.ui.js @@ -16,6 +16,8 @@ */ ( function ( mw, $ ) { + var EP; + /** * Represents a UI element. * @class mw.mmv.ui.Element @@ -30,13 +32,13 @@ /** @property {Object.} eventsRegistered Events that this element has registered with the DOM. */ this.eventsRegistered = {}; } + EP = Element.prototype; /** - * @method * Helper function for whitelisting HTML to only keep links and text. Works in-place. * @param {jQuery} $el */ - Element.prototype.whitelistHtml = function ( $el ) { + EP.whitelistHtml = function ( $el ) { var child, $prev, $child = $el.children().first(); while ( $child && $child.length ) { @@ -64,26 +66,39 @@ }; /** - * @method * @abstract * Sets the data for the element. */ - Element.prototype.set = function () {}; + EP.set = function () {}; /** - * @method * @abstract * Empties the element. */ - Element.prototype.empty = function () {}; + EP.empty = function () {}; + + /** + * @abstract + * Registers listeners. + */ + EP.attach = function() {}; + + /** + * @abstract + * Clears listeners. + */ + EP.unattach = function() { + this.clearEvents(); + }; /** - * @method * Add event handler in a way that will be auto-cleared on lightbox close * @param {string} name Name of event, like 'keydown' * @param {Function} handler Callback for the event + * + * TODO: Unit tests */ - Element.prototype.handleEvent = function ( name, handler ) { + EP.handleEvent = function ( name, handler ) { if ( this.eventsRegistered[name] === undefined ) { this.eventsRegistered[name] = []; } @@ -92,10 +107,11 @@ }; /** - * @method * Remove all events that have been registered on this element. + * + * TODO: Unit tests */ - Element.prototype.clearEvents = function () { + EP.clearEvents = function () { var i, handlers, thisevent, events = Object.keys( this.eventsRegistered ); @@ -109,5 +125,6 @@ }; mw.mmv.ui = {}; + mw.mmv.ui.reuse = {}; mw.mmv.ui.Element = Element; }( mediaWiki, jQuery ) ); diff --git a/resources/mmv/ui/mmv.ui.metadataPanel.js b/resources/mmv/ui/mmv.ui.metadataPanel.js index 06b69f89e..33a6285c9 100644 --- a/resources/mmv/ui/mmv.ui.metadataPanel.js +++ b/resources/mmv/ui/mmv.ui.metadataPanel.js @@ -29,6 +29,7 @@ */ function MetadataPanel( $container, $controlBar ) { mw.mmv.ui.Element.call( this, $container ); + this.$controlBar = $controlBar; /** @@ -57,9 +58,13 @@ $.scrollTo().on( 'scroll.mmvp', $.throttle( 250, function() { panel.scroll(); } ) ); + + this.fileReuse.attach(); }; MPP.unattach = function() { + this.fileReuse.unattach(); + this.fileReuse.closeDialog(); this.clearEvents(); $.scrollTo().off( 'scroll.mmvp' ); @@ -239,7 +244,7 @@ this.initializeDatetime(); this.initializeLocation(); - this.fileReuse = new mw.mmv.ui.FileReuse( this.$imageLinks ); + this.fileReuse = new mw.mmv.ui.reuse.Dialog( this.$container, this.$titleDiv ); this.categories = new mw.mmv.ui.Categories( this.$imageLinks ); this.fileUsage = new mw.mmv.ui.FileUsage( @@ -440,18 +445,10 @@ /** * Sets up the file reuse data in the DOM - * @param {mw.Title} title - * @param {string} imageUrl - * @param {boolean} isLocal + * @param {mw.mmv.model.Image} image */ - MPP.setFileReuseData = function ( title, imageUrl, isLocal ) { - this.fileReuse.set( - title, - imageUrl, - isLocal, - this.$license.data( 'license' ), - this.$author.text() - ); + MPP.setFileReuseData = function ( image ) { + this.fileReuse.set( image ); }; /** @@ -628,7 +625,6 @@ fileTitle = image.filePageTitle; this.setFileTitle( fileTitle.getNameText() ); - this.setFileReuseData( fileTitle, imageData.url, repoData.isLocal ); this.setRepoDisplay( repoData.displayName, repoData.favIcon, repoData.isLocal ); this.setFilePageLink( imageData.descriptionUrl ); @@ -676,6 +672,9 @@ if ( user ) { this.setUserPageLink( repoData, imageData.lastUploader, user.gender ); } + + // File reuse steals a bunch of information from the DOM, so do it last + this.setFileReuseData( imageData, repoData.displayName, image.caption ); }; /** @@ -741,7 +740,6 @@ }; /** - * @method * Makes sure that the given element (which must be a descendant of the metadata panel) is * in view. If it isn't, scrolls the panel smoothly to reveal it. * @param {HTMLElement|jQuery|string} target diff --git a/resources/mmv/ui/mmv.ui.reuse.dialog.js b/resources/mmv/ui/mmv.ui.reuse.dialog.js new file mode 100644 index 000000000..da2aca613 --- /dev/null +++ b/resources/mmv/ui/mmv.ui.reuse.dialog.js @@ -0,0 +1,224 @@ +/* + * 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 DP; + + /** + * Represents the file reuse dialog and the link to open it. + * @class mw.mmv.ui.reuse.Dialog + * @extends mw.mmv.ui.Element + * @param {jQuery} $container the element to which the dialog will be appended + * @param {jQuery} $linkContainer the element to which the link to open the dialog will be appended + */ + function Dialog( $container, $linkContainer ) { + mw.mmv.ui.Element.call( this, $container ); + + this.$reuseLink = $( '' ) + .addClass( 'mw-mlb-reuse-link empty' ) + .text( mw.message( 'multimediaviewer-reuse-link' ).text() ); + + this.$reuseLink.appendTo( $linkContainer ); + + this.$reuseDialog = $( '
    ' ) + .addClass( 'mw-mlb-reuse-dialog' ); + + this.reuseTabs = new oo.ui.MenuWidget( { + classes: [ 'mw-mlb-reuse-tabs' ] + } ); + // MenuWidget has a nasty tendency to hide itself, maybe we're not using it right? + this.reuseTabs.hide = $.noop; + this.reuseTabs.$element.show().appendTo( this.$reuseDialog ); + + this.$downArrow = $( '
    ' ) + .addClass( 'mw-mlb-reuse-down-arrow' ) + .appendTo( this.$reuseDialog ); + + this.$reuseDialog.appendTo( this.$container ); + + /** @property {boolean} Whether or not the dialog is open. */ + this.isOpen = false; + + /** + * @property {Object.} List of tab ui objects. + */ + this.tabs = null; + + this.initTabs(); + } + + oo.inheritClass( Dialog, mw.mmv.ui.Element ); + + DP = Dialog.prototype; + + // FIXME this should happen outside the dialog and the tabs, but we need to improve + DP.initTabs = function () { + var shareTab; + + this.tabs = { + share: new mw.mmv.ui.reuse.Share( this.$reuseDialog ) + }; + + shareTab = new oo.ui.MenuItemWidget( + 'share', { label: mw.message( 'multimediaviewer-share-tab' ).text() } ); + + this.reuseTabs.addItems( [ + shareTab + ] ); + + // Default to 'share' tab + this.selectedTab = 'share'; + this.reuseTabs.selectItem( shareTab ); + }; + + /** + * Handles click on link that opens/closes the dialog. + */ + DP.handleOpenCloseClick = function() { + mw.mmv.logger.log( 'use-this-file-link-click' ); + + if ( this.isOpen ) { + this.closeDialog(); + } else { + this.openDialog(); + } + + return false; + }; + + /** + * Handles tab selection. + */ + DP.handleTabSelection = function ( option ) { + var tab; + + this.selectedTab = option.getData(); + + for ( tab in this.tabs ) { + if ( tab === this.selectedTab ) { + this.tabs[tab].show(); + } else { + this.tabs[tab].hide(); + } + } + }; + + /** + * Registers listeners. + */ + DP.attach = function() { + var dialog = this, + tab; + + this.$reuseLink.on( 'click', $.proxy( dialog.handleOpenCloseClick, dialog ) ); + this.reuseTabs.on( 'select', $.proxy( dialog.handleTabSelection, dialog ) ); + + for ( tab in this.tabs ) { + this.tabs[tab].attach(); + } + }; + + /** + * Clears listeners. + */ + DP.unattach = function() { + var tab; + + this.constructor.super.prototype.unattach.call( this ); + + this.stopListeningToOutsideClick(); + this.$reuseLink.off( 'click' ); + this.reuseTabs.off( 'select' ); + + for ( tab in this.tabs ) { + this.tabs[tab].unattach(); + } + }; + + + /** + * Sets data needed by contaned tabs and makes dialog launch link visible. + * @param {mw.mmv.model.Image} image + */ + DP.set = function ( image ) { + this.tabs.share.set( image ); + this.$reuseLink.removeClass( 'empty' ); + }; + + /** + * @inheritdoc + */ + DP.empty = function () { + this.closeDialog(); + + for ( var tab in this.tabs ) { + this.tabs[tab].empty(); + } + + this.$reuseLink.addClass( 'empty' ); + }; + + /** + * Opens a dialog with information about file reuse. + */ + DP.openDialog = function () { + this.startListeningToOutsideClick(); + this.$reuseDialog.show(); + this.$reuseLink.addClass( 'open' ); + this.isOpen = true; + this.tabs[this.selectedTab].show(); + }; + + /** + * Closes the reuse dialog. + */ + DP.closeDialog = function () { + this.stopListeningToOutsideClick(); + this.$reuseDialog.hide(); + this.$reuseLink.removeClass( 'open' ); + this.isOpen = false; + }; + + /** + * Sets up the event handler which closes the dialog when the user clicks outside. + */ + DP.startListeningToOutsideClick = function () { + var dialog = this; + + this.outsideClickHandler = this.outsideClickHandler || function ( e ) { + var $clickTarget = $( e.target ); + + if ( $clickTarget.closest( dialog.$reuseDialog ).length ) { + return; + } + + dialog.closeDialog(); + return false; + }; + $( document ).on( 'click.mmv', this.outsideClickHandler ); + }; + + /** + * Removes the event handler set up by startListeningToOutsideClick(). + */ + DP.stopListeningToOutsideClick = function () { + $( document ).off( 'click.mmv', this.outsideClickHandler ); + }; + + mw.mmv.ui.reuse.Dialog = Dialog; +}( mediaWiki, jQuery, OO ) ); diff --git a/resources/mmv/ui/mmv.ui.reuse.dialog.less b/resources/mmv/ui/mmv.ui.reuse.dialog.less new file mode 100644 index 000000000..c8b0818c2 --- /dev/null +++ b/resources/mmv/ui/mmv.ui.reuse.dialog.less @@ -0,0 +1,126 @@ +@import "mmv.mixins"; + +.mw-mlb-reuse-link { + @reuse-link-color: rgb(136, 136, 136); + + position: absolute; + right: 0; + bottom: 0; + + border-left: 1px solid @reuse-link-color; + padding: 15px; + + .unselectable; + font-size: 1.25em; + color: @reuse-link-color; + cursor: pointer; + opacity: 0.8; + transition: opacity 0.25s; + + &:before { + display: inline-block; + width: 1em; + height: 1em; + + // fix odd position caused by bottom of icon and bottom of SVG not being aligned + position: relative; + top: 0.1em; + + /* @embed */ + background-image: url(img/use-ltr.svg); + background-size: 1em 1em; + margin-right: 0.25em; + content: ' '; + vertical-align: baseline; + } + + &.open { + opacity: 1; + } +} + +// not-so-great but quick way of ensuring the reuse link does not get overflowed with text +.mw-mlb-title-contain { + padding-right: 175px; +} + +.mw-mlb-reuse-dialog { + @background-color: rgb(255, 255, 255); + @shadow-color: rgb(0, 0, 0); + @border-radius: 3px; + @divider-border-height: 1px; + @dialog-width: 450px; + @dialog-height: 350px; + + // positioned relative to the metadata panel + position: absolute; + right: 5px; + top: -1 * (@dialog-height + 5px); + width: @dialog-width; + height: @dialog-height; + background-color: @background-color; + box-shadow: 2px 2px 2px @shadow-color; + .box-round(@border-radius); + + .mw-mlb-reuse-tabs { + @divider-color: rgb(204, 204, 204); + + position: static; + box-shadow: none; + border-bottom: @divider-border-height solid @divider-color; + .box-round(@border-radius @border-radius 0 0); + + .oo-ui-iconedElement-icon.oo-ui-icon-check { + display: none; + } + + li { + @tab-border-height: 3px; + @highlighted-tab-color: rgb(225, 243, 255); + @selected-tab-color: rgb(0, 113, 188); + + display: inline-block; + padding: 10px 25px; + + font-size: 1.2em; + + &.oo-ui-optionWidget-highlighted { + border-bottom: (@tab-border-height - @divider-border-height) solid @highlighted-tab-color; + } + + &.oo-ui-optionWidget-selected, + &.oo-ui-optionWidget-highlighted.oo-ui-optionWidget-selected { + border-bottom: @tab-border-height solid @selected-tab-color; + } + + &:first-child { + .box-round(@border-radius 0 0 0); + } + } + } + + .mw-mlb-reuse-pane { + display: none; + padding: 10px; + + &.active { + display: block; + } + } + + .mw-mlb-reuse-down-arrow { + @arrow-size: 20px; + @arrow-border-color: rgb(170, 170, 170); + @arrow-border-size: 1px; + + background-color: @background-color; + width: @arrow-size; + height: @arrow-size; + border-right: @arrow-border-size solid @arrow-border-color; + border-bottom: @arrow-border-size solid @arrow-border-color; + .rotate(45deg); + position: absolute; + right: 60px; + bottom: -1 * ( @arrow-size / 2 ); + } +} diff --git a/resources/mmv/ui/mmv.ui.reuse.share.js b/resources/mmv/ui/mmv.ui.reuse.share.js new file mode 100644 index 000000000..389171caa --- /dev/null +++ b/resources/mmv/ui/mmv.ui.reuse.share.js @@ -0,0 +1,105 @@ +/* + * 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 SP; + + /** + * Represents the file reuse dialog and link to open it. + * @class mw.mmv.ui.reuse.Share + * @extends mw.mmv.ui.reuse.Tab + * @param {jQuery} $container + */ + function Share( $container ) { + Share.super.call( this, $container ); + + this.init(); + } + oo.inheritClass( Share, mw.mmv.ui.reuse.Tab ); + SP = Share.prototype; + + SP.init = function () { + this.$pane.addClass( 'mw-mlb-share-pane active' ) + .appendTo( this.$container ); + + this.pageInput = new oo.ui.TextInputWidget( { + classes: [ 'mw-mlb-share-page' ], + readOnly: true + } ); + + this.$pageLink = $( '' ) + .addClass( 'mw-mlb-share-page-link' ) + .prop( 'alt', mw.message( 'multimediaviewer-link-to-page' ).text() ) + .prop( 'target', '_blank' ) + .html( ' ' ) + .appendTo( this.$pane ); + + this.pageInput.$element.appendTo( this.$pane ); + + this.$pane.appendTo( this.$container ); + }; + + /** + * Shows the pane. + */ + SP.show = function () { + this.constructor.super.prototype.show.call( this ); + this.pageInput.$element.focus(); + }; + + /** + * @inheritdoc + * @param {mw.mmv.model.Image} image + */ + SP.set = function ( image ) { + // FIXME this should be handled by mmv.js to be DRY + var url = image.descriptionUrl + '#mediaviewer/' + image.title.getMainText(); + this.pageInput.setValue( url ); + this.$pageLink.prop( 'href', url ); + }; + + /** + * @inheritdoc + */ + SP.empty = function () { + this.pageInput.setValue( '' ); + this.$pageLink.prop( 'href', null ); + }; + + /** + * @inheritdoc + */ + SP.attach = function() { + var $input = this.pageInput.$element.find( 'input' ); + + this.pageInput.onDOMEvent( 'focus', $.proxy( this.selectAllOnEvent, $input ) ); + this.pageInput.onDOMEvent( 'mousedown click', $.proxy( this.onlyFocus, $input ) ); + }; + + /** + * @inheritdoc + */ + SP.unattach = function() { + this.constructor.super.prototype.unattach.call( this ); + + this.pageInput.offDOMEvent( 'focus mousedown click' ); + }; + + + mw.mmv.ui.reuse.Share = Share; +}( mediaWiki, jQuery, OO ) ); diff --git a/resources/mmv/ui/mmv.ui.reuse.share.less b/resources/mmv/ui/mmv.ui.reuse.share.less new file mode 100644 index 000000000..4f13a1314 --- /dev/null +++ b/resources/mmv/ui/mmv.ui.reuse.share.less @@ -0,0 +1,30 @@ +.mw-mlb-share-page { + // override OOJS-UI fixed width + width: auto; + // make sure this is a new block formatting context so that the float shrinks it instead of + // running into it and pushing the input down (there is probably a better way of doing this) + overflow: hidden; +} + +.mw-mlb-share-page-link { + float: left; + width: 1.5em; + height: 1.5em; + + // position approximately to the middle - probably fragile but couldn't find a better way as the + // height of OOJS-UI input widget has both em and px parts and not possible to calculate exactly + margin: 8px 0; + padding: 0 10px; + + /* @embed */ + background-image: url(img/link.svg); + background-size: contain; + background-position: center; + background-repeat: no-repeat; + + &:hover { + /* @embed */ + background-image: url(img/link-hover.svg); + text-decoration: none; + } +} diff --git a/resources/mmv/ui/mmv.ui.reuse.tab.js b/resources/mmv/ui/mmv.ui.reuse.tab.js new file mode 100644 index 000000000..3282e78bf --- /dev/null +++ b/resources/mmv/ui/mmv.ui.reuse.tab.js @@ -0,0 +1,73 @@ +/* + * 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, $, oo ) { + var TP; + + /** + * A tab in a mw.mmv.ui.Pane component + * @class mw.mmv.ui.reuse.Tab + * @extends mw.mmv.ui.Element + * @param {jQuery} $container + * @constructor + */ + function Tab( $container ) { + Tab.super.call( this, $container ); + + /** + * Container for the tab. + * @property {jQuery} + */ + this.$pane = $( '
    ' ).addClass( 'mw-mlb-reuse-pane' ); + + } + oo.inheritClass( Tab, mw.mmv.ui.Element ); + TP = Tab.prototype; + + /** + * Shows the pane. + */ + TP.show = function () { + this.$pane.addClass( 'active' ); + }; + + /** + * Hides the pane. + */ + TP.hide = function () { + this.$pane.removeClass( 'active' ); + }; + + /** + * Makes the entire input/textarea selected when focused. + */ + TP.selectAllOnEvent = function () { + this.select(); + }; + + /** + * Reduces the action of clicks to solely focusing the input/textarea. + * Essentialy disables clicking inside the text to select a portion of it. + */ + TP.onlyFocus = function ( e ) { + this.focus(); + e.preventDefault(); + return false; + }; + + mw.mmv.ui.reuse.Tab = Tab; +}( mediaWiki, jQuery, OO ) ); diff --git a/tests/qunit/mmv/model/mmv.model.test.js b/tests/qunit/mmv/model/mmv.model.test.js index 05158bf7b..ac7f25f2c 100644 --- a/tests/qunit/mmv/model/mmv.model.test.js +++ b/tests/qunit/mmv/model/mmv.model.test.js @@ -182,4 +182,5 @@ assert.ok( e, 'Exception is thrown when gender parameter is not understood'); } } ); + }( mediaWiki ) ); diff --git a/tests/qunit/mmv/ui/mmv.ui.metadataPanel.test.js b/tests/qunit/mmv/ui/mmv.ui.metadataPanel.test.js index 57e6e3a54..defe2b9a3 100644 --- a/tests/qunit/mmv/ui/mmv.ui.metadataPanel.test.js +++ b/tests/qunit/mmv/ui/mmv.ui.metadataPanel.test.js @@ -127,7 +127,10 @@ filePageTitle : mw.Title.newFromText( 'File:' + title + '.jpg' ) }, imageData = { - hasCoords: function() { return false; }, + title: image.filePageTitle, + url: 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg', + descriptionUrl: 'https://commons.wikimedia.org/wiki/File:Foobar.jpg', + hasCoords: function() { return false; } }, repoData = { getArticlePath : function() { return 'Foo'; } diff --git a/tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js b/tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js new file mode 100644 index 000000000..4297116c5 --- /dev/null +++ b/tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js @@ -0,0 +1,196 @@ +/* + * 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, $ ) { + function makeReuseDialog() { + var $fixture = $( '#qunit-fixture' ); + return new mw.mmv.ui.reuse.Dialog( $fixture, $( '
    ' ).appendTo( $fixture ) ); + } + + QUnit.module( 'mmv.ui.reuse.Dialog', QUnit.newMwEnvironment() ); + + QUnit.test( 'Sanity test, object creation and UI construction', 4, function ( assert ) { + var reuseDialog = makeReuseDialog(); + + assert.ok( reuseDialog, 'Reuse UI element is created.' ); + assert.strictEqual( reuseDialog.$reuseLink.length, 1, 'Reuse link created.' ); + assert.strictEqual( reuseDialog.$reuseDialog.length, 1, 'Reuse dialog div created.' ); + assert.ok( reuseDialog.reuseTabs, 'Reuse tabs created.' ); + } ); + + QUnit.test( 'handleOpenCloseClick():', 2, function ( assert ) { + var reuseDialog = makeReuseDialog(); + + reuseDialog.openDialog = function () { + assert.ok( true, 'openDialog called.' ); + }; + reuseDialog.closeDialog = function () { + assert.ok( false, 'closeDialog should not have been called.' ); + }; + + // Dialog is closed by default, we should open it + reuseDialog.handleOpenCloseClick(); + + reuseDialog.openDialog = function () { + assert.ok( false, 'openDialog should not have been called.' ); + }; + reuseDialog.closeDialog = function () { + assert.ok( true, 'closeDialog called.' ); + }; + reuseDialog.isOpen = true; + + // Dialog open now, we should close it. + reuseDialog.handleOpenCloseClick(); + } ); + + QUnit.test( 'attach()/unattach():', 2, function ( assert ) { + var reuseDialog = makeReuseDialog(); + + reuseDialog.handleOpenCloseClick = function() { + assert.ok( false, 'handleOpenCloseClick should not have been called.' ); + }; + reuseDialog.handleTabSelection = function() { + assert.ok( false, 'handleTabSelection should not have been called.' ); + }; + + // Triggering action events before attaching should do nothing + reuseDialog.$reuseLink.trigger( 'click' ); + reuseDialog.reuseTabs.emit( 'select' ); + + reuseDialog.handleOpenCloseClick = function() { + assert.ok( true, 'handleOpenCloseClick called.' ); + }; + reuseDialog.handleTabSelection = function() { + assert.ok( true, 'handleTabSelection called.' ); + }; + + reuseDialog.attach(); + + // Action events should be handled now + reuseDialog.$reuseLink.trigger( 'click' ); + reuseDialog.reuseTabs.emit( 'select' ); + + // Test the unattach part + reuseDialog.handleOpenCloseClick = function() { + assert.ok( false, 'handleOpenCloseClick should not have been called.' ); + }; + reuseDialog.handleTabSelection = function() { + assert.ok( false, 'handleTabSelection should not have been called.' ); + }; + + reuseDialog.unattach(); + + // Triggering action events now that we are unattached should do nothing + reuseDialog.$reuseLink.trigger( 'click' ); + reuseDialog.reuseTabs.emit( 'select' ); + } ); + + QUnit.test( 'start/stopListeningToOutsideClick():', 11, function ( assert ) { + var reuseDialog = makeReuseDialog(), + realCloseDialog = reuseDialog.closeDialog; + + function clickOutsideDialog() { + var event = new $.Event( 'click', { target: reuseDialog.$container[0] } ); + reuseDialog.$container.trigger( event ); + return event; + } + function clickInsideDialog() { + var event = new $.Event( 'click', { target: reuseDialog.$reuseDialog[0] } ); + reuseDialog.$reuseDialog.trigger( event ); + return event; + } + + function assertDialogDoesNotCatchClicks() { + var event; + reuseDialog.closeDialog = function() { assert.ok( false, 'Dialog is not affected by click' ); }; + event = clickOutsideDialog(); + assert.ok( !event.isDefaultPrevented(), 'Dialog does not affect click' ); + assert.ok( !event.isPropagationStopped(), 'Dialog does not affect click propagation' ); + } + function assertDialogCatchesOutsideClicksOnly() { + var event; + reuseDialog.closeDialog = function() { assert.ok( false, 'Dialog is not affected by inside click' ); }; + event = clickInsideDialog(); + assert.ok( !event.isDefaultPrevented(), 'Dialog does not affect inside click' ); + assert.ok( !event.isPropagationStopped(), 'Dialog does not affect inside click propagation' ); + reuseDialog.closeDialog = function() { assert.ok( true, 'Dialog is closed by outside click' ); }; + event = clickOutsideDialog(); + assert.ok( event.isDefaultPrevented(), 'Dialog catches outside click' ); + assert.ok( event.isPropagationStopped(), 'Dialog stops outside click propagation' ); + } + + assertDialogDoesNotCatchClicks(); + reuseDialog.openDialog(); + assertDialogCatchesOutsideClicksOnly(); + realCloseDialog.call( reuseDialog ); + assertDialogDoesNotCatchClicks(); + reuseDialog.openDialog(); + reuseDialog.unattach(); + assertDialogDoesNotCatchClicks(); + } ); + + QUnit.test( 'set()/empty():', 3, function ( assert ) { + var reuseDialog = makeReuseDialog(), + 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', + image = { // fake mw.mmv.model.Image + title: title, + url: src, + descriptionUrl: url, + width: 100, + height: 80 + }; + + assert.ok( reuseDialog.$reuseLink.hasClass( 'empty' ), 'Dialog launch link is empty by default.' ); + + reuseDialog.set( image ); + + assert.ok( ! reuseDialog.$reuseLink.hasClass( 'empty' ), 'Dialog launch link is not empty after set().' ); + + reuseDialog.empty(); + + assert.ok( reuseDialog.$reuseLink.hasClass( 'empty' ), 'Dialog launch link is empty now.' ); + } ); + + QUnit.test( 'openDialog()/closeDialog():', 3, function ( assert ) { + var reuseDialog = makeReuseDialog(), + 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', + image = { // fake mw.mmv.model.Image + title: title, + url: src, + descriptionUrl: url, + width: 100, + height: 80 + }; + + reuseDialog.set( image ); + + assert.ok( ! reuseDialog.isOpen, 'Dialog closed by default.' ); + + reuseDialog.openDialog(); + + assert.ok( reuseDialog.isOpen, 'Dialog open now.' ); + + reuseDialog.closeDialog(); + + assert.ok( ! reuseDialog.isOpen, 'Dialog closed now.' ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/mmv/ui/mmv.ui.reuse.share.test.js b/tests/qunit/mmv/ui/mmv.ui.reuse.share.test.js new file mode 100644 index 000000000..113f43f54 --- /dev/null +++ b/tests/qunit/mmv/ui/mmv.ui.reuse.share.test.js @@ -0,0 +1,88 @@ +/* + * 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, $ ) { + function makeShare() { + return new mw.mmv.ui.reuse.Share( $( '#qunit-fixture' ) ); + } + + QUnit.module( 'mmv.ui.reuse.share', QUnit.newMwEnvironment() ); + + QUnit.test( 'Sanity test, object creation and UI construction', 4, function ( assert ) { + var share = makeShare(); + + assert.ok( share, 'Share UI element is created.' ); + assert.strictEqual( share.$pane.length, 1, 'Pane div created.' ); + assert.ok( share.pageInput, 'Text field created.' ); + assert.ok( share.$pageLink, 'Link created.' ); + } ); + + QUnit.test( 'set()/empty():', 3, function ( assert ) { + var share = makeShare(), + image = { // fake mw.mmv.model.Image + title: new mw.Title( 'File:Foobar.jpg' ), + url: 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg', + }; + + assert.notStrictEqual( ! share.pageInput.getValue(), '', 'pageInput is empty.' ); + + share.set( image ); + + assert.notStrictEqual( share.pageInput.getValue(), '', 'pageInput is not empty.' ); + + share.empty(); + + assert.notStrictEqual( ! share.pageInput.getValue(), '', 'pageInput is empty.' ); + } ); + + QUnit.test( 'attach()/unattach():', 1, function ( assert ) { + var share = makeShare(), + image = { + title: new mw.Title( 'File:Foobar.jpg' ), + url: 'https://upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg', + }; + + share.set( image ); + + share.selectAllOnEvent = function() { + assert.ok( false, 'selectAllOnEvent should not have been called.' ); + }; + + // Triggering action events before attaching should do nothing + share.pageInput.$element.focus(); + + share.selectAllOnEvent = function () { + assert.ok( true, 'selectAllOnEvent was called.' ); + }; + + share.attach(); + + // Action events should be handled now + share.pageInput.$element.focus(); + + // Test the unattach part + share.selectAllOnEvent = function() { + assert.ok( false, 'selectAllOnEvent should not have been called.' ); + }; + + share.unattach(); + + // Triggering action events now that we are unattached should do nothing + share.pageInput.$element.focus(); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/mmv/ui/mmv.ui.reuse.tab.test.js b/tests/qunit/mmv/ui/mmv.ui.reuse.tab.test.js new file mode 100644 index 000000000..73642545e --- /dev/null +++ b/tests/qunit/mmv/ui/mmv.ui.reuse.tab.test.js @@ -0,0 +1,43 @@ +/* + * 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 $fixture = $( '#qunit-fixture' ); + + function makeReuseTab() { + return new mw.mmv.ui.reuse.Tab( $( '
    ' ).appendTo( $fixture ), $fixture ); + } + + QUnit.module( 'mmv.ui.reuse.Tab', QUnit.newMwEnvironment() ); + + QUnit.test( 'Object creation, UI construction and basic funtionality', 5, function ( assert ) { + var reuseTab = makeReuseTab(); + + assert.ok( reuseTab, 'Reuse UI element is created.' ); + assert.strictEqual( reuseTab.$pane.length, 1, 'Pane created.' ); + + assert.ok( !reuseTab.$pane.hasClass( 'active' ), 'Tab is not active.' ); + + reuseTab.show(); + + assert.ok( reuseTab.$pane.hasClass( 'active' ), 'Tab is active.' ); + + reuseTab.hide(); + + assert.ok( !reuseTab.$pane.hasClass( 'active' ), 'Tab is not active.' ); + } ); +}( mediaWiki, jQuery ) );