diff --git a/MultimediaViewer.php b/MultimediaViewer.php index 52badd794..6b548d1f4 100644 --- a/MultimediaViewer.php +++ b/MultimediaViewer.php @@ -579,6 +579,14 @@ $wgResourceModules += array( 'messages' => array( 'multimediaviewer-credit', + 'multimediaviewer-text-embed-credit-text-tbls', + 'multimediaviewer-text-embed-credit-text-tls', + 'multimediaviewer-text-embed-credit-text-tbs', + 'multimediaviewer-text-embed-credit-text-tbl', + 'multimediaviewer-text-embed-credit-text-tb', + 'multimediaviewer-text-embed-credit-text-ts', + 'multimediaviewer-text-embed-credit-text-tl', + 'multimediaviewer-html-embed-credit-text-tbls', 'multimediaviewer-html-embed-credit-text-tls', 'multimediaviewer-html-embed-credit-text-tbs', @@ -711,6 +719,7 @@ $wgResourceModules += array( 'mediawiki.ui.button', 'mmv.ui.reuse.tab', 'mmv.ui.reuse.utils', + 'mmv.embedFileFormatter', ), 'messages' => array( @@ -722,6 +731,10 @@ $wgResourceModules += array( 'multimediaviewer-download-large-button-name', 'multimediaviewer-embed-dimensions', 'multimediaviewer-embed-dimensions-with-file-format', + 'multimediaviewer-download-attribution-cta-header', + 'multimediaviewer-download-attribution-cta', + 'multimediaviewer-attr-plain', + 'multimediaviewer-attr-html', ), ), diff --git a/i18n/en.json b/i18n/en.json index dbd3f70e5..40c8e9093 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -76,6 +76,14 @@ "multimediaviewer-embed-wt": "Wikitext", "multimediaviewer-embed-html": "HTML", "multimediaviewer-embed-explanation": "Use this code to embed the file", + "multimediaviewer-text-embed-credit-text-tbls": "\"$1\" by $2. Licensed under $3 via $4.", + "multimediaviewer-text-embed-credit-text-tls": "\"$1\". Licensed under $2 via $3.", + "multimediaviewer-text-embed-credit-text-tbs": "\"$1\" by $2. Via $3.", + "multimediaviewer-text-embed-credit-text-tbl": "\"$1\" by $2. Licensed under $3.", + "multimediaviewer-text-embed-credit-text-tb": "\"$1\" by $2.", + "multimediaviewer-text-embed-credit-text-ts": "\"$1\". Via $2.", + "multimediaviewer-text-embed-credit-text-tl": "\"$1\". Licensed under $2.", + "multimediaviewer-text-embed-credit-text-t": "\"$1\".", "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.", @@ -108,5 +116,9 @@ "multimediaviewer-author-popup-text": "Author", "multimediaviewer-source-popup-text": "Source", "multimediaviewer-panel-open-popup-text": "More details", - "multimediaviewer-panel-close-popup-text": "Fewer details" + "multimediaviewer-panel-close-popup-text": "Fewer details", + "multimediaviewer-download-attribution-cta-header": "You need to attribute the author", + "multimediaviewer-download-attribution-cta": "Show me how", + "multimediaviewer-attr-plain": "Plain", + "multimediaviewer-attr-html": "HTML" } diff --git a/i18n/qqq.json b/i18n/qqq.json index f8ae61788..8ac60e2d1 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -82,6 +82,14 @@ "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-embed-explanation": "Used below the embed textarea to explain what we expect the user to do.", + "multimediaviewer-text-embed-credit-text-tbls": "Credit text, used when generating plain text for attributing an image.\nWhich one of the multimediaviewer-text-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the author\n* $3 - name of the license\n* $4 - name of the website/institution which was the direct source for this image\nEach of the parameters could be either plain text or a link.", + "multimediaviewer-text-embed-credit-text-tls": "Credit text, used when generating plain text to attribute an image.\nWhich one of the multimediaviewer-text-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the license\n* $3 - name of the website/institution which was the direct source for this image\nEach of the parameters could be either plain text or a link.", + "multimediaviewer-text-embed-credit-text-tbs": "Credit text, used when generating plain text to attribute an image.\nWhich one of the multimediaviewer-text-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the author\n* $3 - name of the website/institution which was the direct source for this image\nEach of the parameters could be either plain text or a link.", + "multimediaviewer-text-embed-credit-text-tbl": "Credit text, used when generating plain text to attribute an image.\nWhich one of the multimediaviewer-text-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the author\n* $3 - name of the license\nEach of the parameters could be either plain text or a link.", + "multimediaviewer-text-embed-credit-text-tb": "Credit text, used when generating plain text to attribute an image.\nWhich one of the multimediaviewer-text-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the author\nEach of the parameters could be either plain text or a link.", + "multimediaviewer-text-embed-credit-text-ts": "Credit text, used when generating plain text to attribute an image.\nWhich one of the multimediaviewer-text-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the website/institution which was the direct source for this image\nEach of the parameters could be either plain text or a link.", + "multimediaviewer-text-embed-credit-text-tl": "Credit text, used when generating plain text to attribute an image.\nWhich one of the multimediaviewer-text-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the license\nEach of the parameters could be either plain text or a link.", + "multimediaviewer-text-embed-credit-text-t": "Credit text, used when generating plain text to attribute an image.\nWhich one of the multimediaviewer-text-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\nEach of the parameters could be either plain text or a link.", "multimediaviewer-html-embed-credit-text-tbls": "Credit text, used when generating HTML to reuse an image.\nWhich one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the author\n* $3 - name of the license\n* $4 - name of the website/institution which was the direct source for this image\nEach 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.\nWhich one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the license\n* $3 - name of the website/institution which was the direct source for this image\nEach 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.\nWhich one of the multimediaviewer-html-embed-credit-text-* messages is used will depend on what information about the image is available.\n* $1 - name of the work (typically the filename without an extension)\n* $2 - name of the author\n* $3 - name of the website/institution which was the direct source for this image\nEach of the parameters could be either plain text or a link.", @@ -114,5 +122,9 @@ "multimediaviewer-author-popup-text": "Tooltip that identifies the author of a file in the media viewer.\n{{Identical|Author}}", "multimediaviewer-source-popup-text": "Tooltip that identifies the source of a file in the media viewer.\n{{Identical|Source}}", "multimediaviewer-panel-open-popup-text": "Tooltip for a button that expands more metadata in the media viewer.\n\nSee also:\n* {{msg-mw|Multimediaviewer-panel-close-popup-text}}", - "multimediaviewer-panel-close-popup-text": "Tooltip that closes the extra metadata view in the media viewer.\n\nSee also:\n* {{msg-mw|Multimediaviewer-panel-open-popup-text}}" + "multimediaviewer-panel-close-popup-text": "Tooltip that closes the extra metadata view in the media viewer.\n\nSee also:\n* {{msg-mw|Multimediaviewer-panel-open-popup-text}}", + "multimediaviewer-download-attribution-cta-header": "Header for telling the user that the author of an image must be attributed, during a download action.", + "multimediaviewer-download-attribution-cta": "Call to action for a user to find out how to attribute the author of an image.", + "multimediaviewer-attr-plain": "Label for a button that lets the user pick plain text as an output format.", + "multimediaviewer-attr-html": "Label for a button that lets the user pick HTML as an output format." } diff --git a/resources/mmv/mmv.EmbedFileFormatter.js b/resources/mmv/mmv.EmbedFileFormatter.js index 729711417..363e90127 100644 --- a/resources/mmv/mmv.EmbedFileFormatter.js +++ b/resources/mmv/mmv.EmbedFileFormatter.js @@ -78,13 +78,20 @@ * Byline construction * @param {string} [author] author name (can contain HTML) * @param {string} [source] source name (can contain HTML) - * @return {string} byline (can contain HTML) + * @param {Function} [formatterFunction] Format function for the text - defaults to whitelisting HTML links, but all else sanitized. + * @return {string} Byline (can contain HTML) */ - EFFP.getByline = function ( author, source ) { - author = author && this.htmlUtils.htmlToTextWithLinks( author ); - source = source && this.htmlUtils.htmlToTextWithLinks( source ); + EFFP.getByline = function ( author, source, formatterFunction ) { + var formatter = this; - if ( author && source) { + formatterFunction = formatterFunction || function ( txt ) { + return formatter.htmlUtils.htmlToTextWithLinks( txt ); + }; + + author = author && formatterFunction( author ); + source = source && formatterFunction( source ); + + if ( author && source ) { return mw.message( 'multimediaviewer-credit', author, @@ -95,32 +102,71 @@ } }; + /** + * Generates the plain text embed code for the image credit line. + * @param {mw.mmv.model.EmbedFileInfo} info + * @return {string} + */ + EFFP.getCreditText = function ( info ) { + var creditText, creditParams, + formatter = this, + titleText = info.imageInfo.title.getNameText(), + titleUrl = this.getLinkUrl( info ), + byline = this.getByline( info.imageInfo.author, info.imageInfo.source, function ( txt ) { + return formatter.htmlUtils.htmlToText( txt ); + } ); + + creditParams = [ + 'multimediaviewer-text-embed-credit-text-t', + titleText + ]; + + if ( byline ) { + creditParams[0] += 'b'; + creditParams.push( byline ); + } + if ( info.imageInfo.license ) { + creditParams[0] += 'l'; + creditParams.push( this.htmlUtils.htmlToText( info.imageInfo.license.longName ) ); + } + + creditParams[0] += 's'; + creditParams.push( info.repoInfo.displayName + ' - ' + titleUrl ); + + creditText = mw.message.apply( mw, creditParams ).plain(); + + return creditText; + }; + /** * Generates the HTML embed code for the image credit line. * @param {mw.mmv.model.EmbedFileInfo} info * @return {string} */ EFFP.getCreditHtml = function ( info ) { - var creditText, creditFormat, creditParams, + var creditText, creditParams, titleText = info.imageInfo.title.getNameText(), titleUrl = this.getLinkUrl( info ), $title = $( '' ).text( titleText ).prop( 'href', titleUrl ), byline = this.getByline( info.imageInfo.author, info.imageInfo.source ); - creditFormat = 't'; - creditParams = [ this.htmlUtils.jqueryToHtml( $title ) ]; + creditParams = [ + 'multimediaviewer-html-embed-credit-text-t', + this.htmlUtils.jqueryToHtml( $title ) + ]; + if ( byline ) { - creditFormat += 'b'; + creditParams[0] += 'b'; creditParams.push( byline ); } if ( info.imageInfo.license ) { - creditFormat += 'l'; + creditParams[0] += 'l'; creditParams.push( info.imageInfo.license.getShortLink() ); } - creditFormat += 's'; + + creditParams[0] += 's'; creditParams.push( this.getSiteLink( info ) ); - creditParams.unshift( 'multimediaviewer-html-embed-credit-text-' + creditFormat ); creditText = mw.message.apply( mw, creditParams ).plain(); return creditText; diff --git a/resources/mmv/ui/mmv.ui.reuse.dialog.js b/resources/mmv/ui/mmv.ui.reuse.dialog.js index c08449171..28a004a0e 100644 --- a/resources/mmv/ui/mmv.ui.reuse.dialog.js +++ b/resources/mmv/ui/mmv.ui.reuse.dialog.js @@ -230,15 +230,15 @@ * @param {mw.mmv.model.Repo} repo * @param {string} caption */ - DP.set = function ( image, repo, caption) { + DP.set = function ( image, repo, caption ) { if ( this.tabs !== null ) { this.tabs.share.set( image ); - this.tabs.download.set( image ); + this.tabs.download.set( image, repo ); this.tabs.embed.set( image, repo, caption ); } else { this.tabsSetValues = { share : [ image ], - download : [ image ], + download : [ image, repo ], embed : [ image, repo, caption ] }; } diff --git a/resources/mmv/ui/mmv.ui.reuse.download.js b/resources/mmv/ui/mmv.ui.reuse.download.js index 527903c1b..9bbcada31 100644 --- a/resources/mmv/ui/mmv.ui.reuse.download.js +++ b/resources/mmv/ui/mmv.ui.reuse.download.js @@ -40,6 +40,10 @@ this.createSizePulldownMenu( this.$pane ); this.createPreviewLink( this.$pane ); + this.formatter = new mw.mmv.EmbedFileFormatter(); + this.currentAttrView = 'plain'; + this.createAttributionButton( this.$pane ); + /** * Default item for the size menu. * @property {OO.ui.MenuItemWidget} @@ -116,6 +120,88 @@ .appendTo( $container ); }; + DP.createAttributionButton = function ( $container ) { + var dl = this, + attributionInput = new oo.ui.TextInputWidget( { + classes: [ 'mw-mmv-download-attr-input' ], + readOnly: true + } ), + attributionSwitch = new oo.ui.ButtonSelectWidget( { + classes: [ 'mw-mmv-download-attr-select' ] + } ), + plainOption = new oo.ui.ButtonOptionWidget( 'plain', { + label: mw.message( 'multimediaviewer-attr-plain' ).text() + } ), + htmlOption = new oo.ui.ButtonOptionWidget( 'html', { + label: mw.message( 'multimediaviewer-attr-html' ).text() + } ); + + attributionSwitch.addItems( [ + plainOption, + htmlOption + ] ); + + attributionSwitch.selectItem( plainOption ); + + attributionSwitch.on( 'select', function ( selection ) { + dl.selectAttribution( selection.getData() ); + + dl.attributionInput.$element.find( 'input' ).focus(); + } ); + + this.$attributionSection = $( '
' ) + .addClass( 'mw-mmv-download-attribution mw-mmv-download-attribution-collapsed' ) + .appendTo( $container ); + + this.$attributionCta = $( '
' ) + .addClass( 'mw-mmv-download-attribution-cta' ) + .append( + $( '

' ) + .addClass( 'mw-mmv-download-attribution-cta-header' ) + .text( mw.message( 'multimediaviewer-download-attribution-cta-header' ).text() ), + $( '

' ) + .text( mw.message( 'multimediaviewer-download-attribution-cta' ).text() ) + ) + .click( function () { + dl.$attributionSection.removeClass( 'mw-mmv-download-attribution-collapsed' ); + dl.attributionInput.$element.find( 'input' ).focus(); + } ) + .appendTo( this.$attributionSection ); + + this.$attributionHow = $( '

' ) + .addClass( 'mw-mmv-download-attribution-how' ) + .append( + $( '

' ) + .addClass( 'mw-mmv-download-attribution-how-header' ) + .text( mw.message( 'multimediaviewer-download-attribution-cta-header' ).text() ), + attributionInput.$element, + attributionSwitch.$element, + $( '

' ) + .addClass( 'mw-mmv-download-attribution-close-button' ) + .click( function () { + dl.$attributionSection.addClass( 'mw-mmv-download-attribution-collapsed' ); + } ) + .text( ' ' ) + ) + .appendTo( this.$attributionSection ); + + this.attributionInput = attributionInput; + }; + + /** + * Selects the specified attribution type. + * @param {'plain'|'html'} [name='plain'] The attribution type to use. + */ + DP.selectAttribution = function ( name ) { + this.currentAttrView = name; + + if ( this.currentAttrView === 'html' ) { + this.attributionInput.setValue( this.htmlCredit ); + } else { + this.attributionInput.setValue( this.textCredit ); + } + }; + /** * Registers listeners. */ @@ -127,6 +213,10 @@ this.$selectionArrow.on( 'click', function () { download.downloadSizeMenu.$element.click(); } ); + + this.attributionInput.$element.find( 'input' ) + .on( 'focus', this.selectAllOnEvent ) + .on( 'mousedown click', this.onlyFocus ); }; /** @@ -137,6 +227,9 @@ this.downloadSizeMenu.getMenu().off( 'choose' ); this.$selectionArrow.off( 'click' ); + + this.attributionInput.$element.find( 'input' ) + .off( 'focus mousedown click' ); }; /** @@ -200,6 +293,16 @@ ); }; + /** + * Sets the text in the attribution input element. + * @param {mw.mmv.model.EmbedFileInfo} embed + */ + DP.setAttributionText = function ( embed ) { + this.htmlCredit = this.formatter.getCreditHtml( embed ); + this.textCredit = this.formatter.getCreditText( embed ); + this.selectAttribution( this.currentAttrView ); + }; + /** * Chops off the extension part of an URL. * @param {string} url @@ -213,8 +316,9 @@ * Sets the data on the element. * * @param {mw.mmv.model.Image} image + * @param {mw.mmv.model.Repo} repo */ - DP.set = function ( image ) { + DP.set = function ( image, repo ) { var sizeOptions = this.downloadSizeMenu.getMenu().getItems(), sizes = this.utils.getPossibleImageSizesForHtml( image.width, image.height ); @@ -229,6 +333,10 @@ // Reset size menu to default item and update download button label now that we have the info this.downloadSizeMenu.getMenu().chooseItem( this.defaultItem ); + + if ( image && repo ) { + this.setAttributionText( new mw.mmv.model.EmbedFileInfo( image, repo ) ); + } }; /** @@ -245,6 +353,15 @@ this.image = null; }; + DP.show = function () { + mw.mmv.ui.reuse.Tab.prototype.show.call( this ); + this.$container.addClass( 'mw-mmv-reuse-download-active' ); + }; + + DP.hide = function () { + mw.mmv.ui.reuse.Tab.prototype.hide.call( this ); + this.$container.removeClass( 'mw-mmv-reuse-download-active' ); + }; mw.mmv.ui.reuse.Download = Download; }( mediaWiki, jQuery, OO ) ); diff --git a/resources/mmv/ui/mmv.ui.reuse.download.less b/resources/mmv/ui/mmv.ui.reuse.download.less index 50f9a6a0b..abb008dad 100644 --- a/resources/mmv/ui/mmv.ui.reuse.download.less +++ b/resources/mmv/ui/mmv.ui.reuse.download.less @@ -1,7 +1,12 @@ @mw-ui-constructive-button-color: #00af89; +@pane-padding: 10px; +@attribution-color: #f2f2f2; +@attribution-logo-size: 64px; .mw-mlb-download-pane { - margin-left: 10px; + padding: 0 @pane-padding; + position: relative; + height: 280px; /* Disable link clicks */ a.disabledLink { @@ -75,4 +80,62 @@ } } + .mw-mmv-download-attribution { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 0 @pane-padding @pane-padding; + + background-color: @attribution-color; + + &-how { + position: relative; + display: block; + padding: 5px; + + .mw-mmv-download-attribution-close-button { + cursor: pointer; + position: absolute; + top: 5px; + right: 0px; + width: 12px; + height: 12px; + /* @embed */ + background-image: url(img/x_gray.svg); + } + } + + &-how-header, + &-cta-header { + font-size: large; + font-weight: bold; + } + + &-cta { + cursor: pointer; + display: none; + padding-left: @attribution-logo-size + 7px; + + /* @embed */ + background-image: url(img/user-ltr.svg); + background-repeat: no-repeat; + background-size: @attribution-logo-size; + background-position: left center; + } + + &.mw-mmv-download-attribution-collapsed { + .mw-mmv-download-attribution-cta { + display: block; + } + + .mw-mmv-download-attribution-how { + display: none; + } + } + } +} + +.mw-mmv-reuse-dialog.mw-mmv-reuse-download-active .mw-mmv-reuse-down-arrow { + background-color: @attribution-color; } diff --git a/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js b/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js index dbef78155..4465e41d7 100644 --- a/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js +++ b/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js @@ -179,4 +179,44 @@ 'Wikitext generated correctly.' ); } ); + QUnit.test( 'getCreditText():', 2, function ( assert ) { + var txt, formatter = new mw.mmv.EmbedFileFormatter(); + + this.sandbox.stub( formatter, 'getLinkUrl' ).returns( 'quuuux' ); + + txt = formatter.getCreditText( { + repoInfo: { + displayName: 'Localcommons' + }, + + imageInfo: { + author: 'Author', + source: 'Source', + title: { + getNameText: function () { return 'Image Title'; } + } + } + } ); + + assert.strictEqual( txt, '"Image Title" by Author - Source. Via Localcommons - quuuux.', 'Sanity check' ); + + txt = formatter.getCreditText( { + repoInfo: { + displayName: 'Localcommons' + }, + + imageInfo: { + author: 'Author', + source: 'Source', + title: { + getNameText: function () { return 'Image Title'; } + }, + license: { + longName: 'Do What the Fuck You Want Public License' + } + } + } ); + + assert.strictEqual( txt, '"Image Title" by Author - Source. Licensed under Do What the Fuck You Want Public License via Localcommons - quuuux.', 'License message works' ); + } ); }( mediaWiki ) ); 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 65fc48652..19bc7e00a 100644 --- a/tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js +++ b/tests/qunit/mmv/ui/mmv.ui.reuse.dialog.test.js @@ -209,11 +209,11 @@ width: 100, height: 80 }, - embedFileInfo = new mw.mmv.model.EmbedFileInfo( title, src, url ); + repoInfo = new mw.mmv.model.Repo( 'Wikipedia', '//wikipedia.org/favicon.ico', true ); reuseDialog.initTabs(); - reuseDialog.set( image, embedFileInfo ); + reuseDialog.set( image, repoInfo ); assert.ok( ! reuseDialog.isOpen, 'Dialog closed by default.' );