MediaWiki:Gadget-Stockphoto.js - Wikimedia Commons
Article Images
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
/* * Stockphoto - shows download and attribution buttons * * Revision: 2022-12-05 * Dependencies: mediawiki.util, mediawiki.user * Source: https://commons.wikimedia.org/wiki/MediaWiki:Gadget-Stockphoto.js * * Copyright 2010-09/10 by Magnus Manske * Copyright 2011-2022 Timo Tijhof * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* eslint-env browser */ /* global $, mw */ (function () { 'use strict'; var stockPhoto, modalLoad; if ( // Already loaded window.stockPhoto // Unsupported skin || !/^(?:vector(?:-2022)?|monobook|timeless)$/.test(mw.config.get('skin')) // Not a file page || mw.config.get('wgCanonicalNamespace') !== 'File' // Not regular view || mw.config.get('wgAction') !== 'view' ) { // Do not load return; } /** * @param {string} opt.pageName * @param {string} opt.originalUrl * @param {jQuery} opt.dom Keys $aut, $src, $attr, $credit, $licenseAut, $creator * @param {jQuery} opt.$licenses */ function File(opt) { var re, file = this; if ( !mw.config.get('wbEntity') ) return; // Try the SDC caption first. This might usefully be enhanced to try // languages other than the user's preferred one. var caption = mw.config.get('wbEntity').labels[mw.config.get('wgUserLanguage')]; if (caption != undefined) { file.title = caption.value; } else { // Fall back to file name // Strip namespace prefix and file suffix, use spaces for underscore. file.title = opt.pageName.replace(/^[^:]+:|\.[^.]+$/g, '').replace(/_+/g, ' '); } file.url = opt.originalUrl; file.downloadUrl = opt.originalUrl + '?download'; file.backlink = 'https://commons.wikimedia.org/wiki/' + mw.util.wikiUrlencode(opt.pageName); file.audio = opt.dom.$file.find('audio').length > 0; file.audio_or_video = opt.dom.$file.find('audio, video').length > 0; this.gfdlNote = false; this.attrRequired = true; this.computeMetadata(opt.dom, opt.$licenses); } File.prototype.getLicenseLabel = function (short) { if (/^cc[- ]/i.test(short)) { // CC-BY-SA-3.0 -> CC BY-SA 3.0 // CC BY-SA 3.0 -> CC BY-SA 3.0 return short.toUpperCase() .replace(/^(CC)-/, '$1 ') .replace(/[- ]([\d.]+)$/, ' $1'); } return short; }; File.prototype.getLicensePrio = function (short) { // 1. Public domain or CC 0 return /^(public domain|cc0)/i.test(short) ? 1 : // 2. Creative Commons, e.g. "CC-BY-SA-#" (most templates), // or "CC BY-SA #" (some templates) /^cc[- ]by(-sa)?[- ]\d/i.test(short) ? 2 : // 3. Eveything else (e.g. GFDL) 3; }; // Set #author, #attribution, and #licenses File.prototype.computeMetadata = function (dom, $licenses) { var credit, creditHtml, attribution, licenses, via = stockPhoto.i18n.via_wikimedia_commons, file = this, author = dom.$aut.text().trim(), source = dom.$src.text().trim(); // Clean up 'author' if (dom.$aut.find('.boilerplate').length) { author = ''; } // Detect manual "unknown", or {{Unknown}}, or {{Unknown|author}} with its display-none duplicate if (author.match(/^([Uu]nknown( author)?|Unknown authorUnknown author)$/)) { author = ''; } author = author.replace(/\s*\(talk\)$/i, ''); if (author.indexOf('Original uploader was') !== -1) { author = author.replace(/\s*Original uploader was\s*/g, ''); } if (author.slice(0, 3) === '[▼]') { author = author.slice(3); // ▼ (Black Down-Pointing Triangle) author = author.split('Description')[0].trim(); } // Clean up 'source' if (dom.$src.find('.boilerplate').length) { source = ''; } if (dom.$src.find('.int-own-work').length) { source = ''; } // Fallback if (!author && source.length < 50) { author = source; } // Parse licenses licenses = []; $licenses.each(function () { var $tpl = $(this), short = $tpl.find('.licensetpl_short').text().trim(); if (!short) { return; } licenses.push({ link: $tpl.find('.licensetpl_link').text().trim(), short: short, label: file.getLicenseLabel(short), prio: file.getLicensePrio(short), link_req: $tpl.find('.licensetpl_link_req').text().trim(), attr_req: $tpl.find('.licensetpl_attr_req').text().trim() }); }); // Use prefered if possible (like CommonsMetadata API) licenses.sort(function (a, b) { return a.prio - b.prio; }); if (licenses[0] && licenses[0].prio < 3) { licenses.splice(1); } file.licenses = licenses.map(function (v) { if (v.attr_req === 'false') { file.attrRequired = false; } if (v.short.indexOf('GFDL') !== -1) { file.gfdlNote = true; } if (v.link_req === 'true' && v.link) { return { html: '<a href="' + v.link + '">' + mw.html.escape(v.label) + '</a>', txt: v.label + ' <' + v.link + '>' }; } else { return { html: mw.html.escape(v.label), txt: v.label }; } }); // Determine the credit line if (dom.$credit.length) { // Custom credit line credit = dom.$credit.text(); creditHtml = dom.$credit.html(); } if (!credit) { // No custom credit line // Default credit line: attribution + (if required) license + via promo if (dom.$attr.length) { attribution = dom.$attr.text(); } else if (dom.$licenseAut.length) { attribution = dom.$licenseAut.text(); } else if (dom.$creator.length) { attribution = dom.$creator.text(); } if (!attribution) { // No custom attribution // Default attribution: author or (short) source. // If no author/source, point to the file description page instead. attribution = (author || stockPhoto.i18n.see_page_for_author); } credit = attribution + file.getLicense() + ', ' + via; creditHtml = mw.html.element('a', { href: file.backlink }, attribution) + file.getLicense(true) + ', ' + mw.html.escape(via || ''); } file.credit = credit; file.creditHtml = creditHtml; }; File.prototype.getLicense = function (useHtml) { var l1, l2, licenses = this.licenses.map(function (l) { return useHtml ? l.html : l.txt; }); if (!licenses.length) { return ', ' + stockPhoto.i18n.see_page_for_license; } if (licenses.length === 1) { return ', ' + licenses[0]; } if (licenses.length >= 2) { l2 = licenses.pop(); l1 = licenses.pop(); licenses.push(l1 + ' ' + stockPhoto.i18n.or + ' ' + l2); } return ' (' + licenses.join(', ') + ')'; }; stockPhoto = { ui_icon_download: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/92/Gnome-document-save.svg/50px-Gnome-document-save.svg.png', ui_icon_web: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/c0/Gnome-emblem-web.svg/50px-Gnome-emblem-web.svg.png', ui_icon_wiki: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Tango_style_Wikipedia_Icon.svg/50px-Tango_style_Wikipedia_Icon.svg.png', ui_icon_email: 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ee/Gnome-mail-send.svg/50px-Gnome-mail-send.svg.png', ui_icon_help: 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/Dialog-information_on.svg/50px-Dialog-information_on.svg.png', ui_icon_remove: 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/9d/Emblem-unreadable.svg/40px-Emblem-unreadable.svg.png', information_template_hints: ['fileinfotpl_desc', 'fileinfotpl_src'], horizontal_ui: ['pdf', 'djvu'], ogg_icon: '/w/resources/assets/file-type-icons/fileicon-ogg.png', init: function () { var $enable, $orgItems, has_information, $imgBox, xoff, yoff, horizontal, html, $base, re; // Original filetoc items $orgItems = $('#filetoc').find('a[href="#file"], a[href="#filehistory"], a[href="#filelinks"], a[href="#metadata"], a[href="#globalusage"]').parent(); if ($.cookie('StockPhotoDisabled')) { $enable = $('<li>') .append($('<a href="#"></a>').text(stockPhoto.i18n.reuse)) .click(function (e){ e.preventDefault(); $(this).remove(); $.cookie('StockPhotoDisabled', null, { path: '/' }); stockPhoto.init(); }); $('#filetoc').append($enable); $orgItems = $orgItems.add($enable); return; } if (!$('#file').length) { return; } has_information = stockPhoto.information_template_hints.some(function (v) { return document.querySelector('#' + v); }); // No {{Information}} if (!has_information) { return; } // Has one or more problemtags // Changed to also include renames and normal deletes if (document.querySelector('.nuke')) { return; } // * ".multipageimage": For paged tiff files. // * "#file img": For all other images (e.g. JPEG, PNG, SVG, etc.). $imgBox = $('.multipageimage, #file img, #file video, #file audio'); xoff = $imgBox.width() + 40; yoff = $('#file').position().top + 5; stockPhoto.small_horizontal_layout = ( // Small for logged-in !mw.user.isAnon() // Small for media with short height || ($imgBox.height() < 300) ); re = new RegExp('\\.(?:' + stockPhoto.horizontal_ui.map(mw.util.escapeRegExp).join('|') + ')$', 'i'); horizontal = ( // Anything small stockPhoto.small_horizontal_layout || re.test(mw.config.get('wgTitle')) // Window width || (document.documentElement.clientWidth < 1030) ); // Initialize values stockPhoto.share_this(-1); html = '<div class="'; html += (horizontal ? 'stockphoto-layout-horizontal' : 'stockphoto-layout-vertical'); html += (stockPhoto.small_horizontal_layout ? ' stockphoto-layout-horizontal-small' : ''); html += '"'; if (!horizontal) { if (document.querySelector('body.rtl')) { html += ' style="right: ' + xoff + 'px; top:' + yoff + 'px;"'; } else { html += ' style="left: ' + xoff + 'px; top:' + yoff + 'px;"'; } } html += '></div>'; $base = $(html).append( stockPhoto.add_button_row(stockPhoto.ui_icon_download, stockPhoto.call_download, stockPhoto.i18n.download, stockPhoto.i18n.all_sizes), stockPhoto.add_button_row(stockPhoto.ui_icon_web, stockPhoto.call_web, stockPhoto.i18n.use_this_file_web_short, stockPhoto.i18n.on_a_website), stockPhoto.add_button_row(stockPhoto.ui_icon_wiki, stockPhoto.call_wiki, stockPhoto.i18n.use_this_file_wiki_short, stockPhoto.i18n.on_a_wiki), stockPhoto.add_button_row(stockPhoto.ui_icon_email, 'mailto:?subject=' + encodeURIComponent(stockPhoto.file.title) + '&body=' + encodeURIComponent(stockPhoto.file.backlink + '\n\n' + stockPhoto.file.credit), stockPhoto.i18n.email_link_short, stockPhoto.i18n.to_this_file), stockPhoto.add_button_row(stockPhoto.ui_icon_help, mw.util.getUrl('Special:MyLanguage/Commons:Reusing_content_outside_Wikimedia'), stockPhoto.i18n.information, stockPhoto.i18n.about_reusing), $('<span class="stockphoto_buttonrow"><a title="' + stockPhoto.i18n.remove_icons + '" class="stockphoto_buttonrow_icon"><img width="20" src="' + stockPhoto.ui_icon_remove + '"></a></span>') .click(function () { $.cookie('StockPhotoDisabled', true, { expires: 60, // days path: '/' }); $base.remove(); $orgItems.show(); }) ); if (stockPhoto.small_horizontal_layout) { $orgItems.hide(); $('#filetoc').append($base); } else { $('#filetoc').after($base); } }, add_button_row: function (icon_url, fn, txt, html) { var ret, size = 50; if (stockPhoto.small_horizontal_layout) { size = 20; } // HiDPI "Retina" icon var realSize = size; if (window.devicePixelRatio > 1.0) { realSize *= 2; } icon_url = icon_url.replace('/50px-', '/' + realSize + 'px-'); ret = document.createElement(typeof fn === 'string' ? 'a' : 'span'); ret.className = 'stockphoto_buttonrow'; ret.title = txt + ' ' + html; if (typeof fn === 'string') { ret.href = fn; } else { ret.role = 'button'; ret.tabIndex = 0; ret.onclick = fn; } ret.innerHTML = '<span class="stockphoto_buttonrow_icon"><img width="' + size + '" height="' + size + '" src="' + icon_url + '"></span>' + '<span class="stockphoto_buttonrow_text"><a>' + txt + '</a><small>' + html + '</small></span>'; return ret; }, stockphoto_get_thumbnail_url: function (width, download) { var thumb_url, alt_title; if (stockPhoto.file.audio) { return stockPhoto.ogg_icon; } alt_title = mw.config.get('wgCanonicalNamespace') + ':' + mw.config.get('wgTitle'); $('#file img').each(function () { var i = this.alt; if (i && i !== alt_title) { return; } thumb_url = this.src; }); // Special case for videos if (!thumb_url && $('#file video').length) { thumb_url = $('#file video').attr('poster'); } if (!thumb_url || !thumb_url.length) return; var components = new URL(thumb_url); var pathComponents = components.pathname.split('/'); var last = pathComponents.pop().replace(/^\d+px-/, width + 'px-'); pathComponents.push(last); components.pathname = pathComponents.join('/'); if (download) { var params = new URLSearchParams(components.search); params.append('download', ''); components.search = params.toString(); } return components.toString(); }, make_html_textarea: function () { var width, type, height, thumb_url, t; width = $('#stockphoto_html_select').val(); type = $('input[name="stockphoto_code_type"]:checked').val(); // Iframe share for media player if (stockPhoto.file.audio_or_video && type === 'html') { // Get the ratio (from the audio/video element) var $element = $('#file video, #file audio'); height = width * $element.height() / $element.width(); if (stockPhoto.file.audio) { // For audio that has zero height height = 20; } $('#stockphoto_html').text('<iframe src="' + stockPhoto.file.backlink + '?embedplayer=yes" width="' + width + '" height="'+ height + '" frameborder="0" loading="lazy" allow="autoplay; picture-in-picture" allowfullscreen></iframe>'); return; } thumb_url = stockPhoto.stockphoto_get_thumbnail_url(width); if (type === 'html') { t = '<a title="' + mw.html.escape(stockPhoto.file.credit) + '" href="' + stockPhoto.file.backlink + '"><img width="' + width + '" alt="' + mw.html.escape(stockPhoto.file.title) + '" src="' + thumb_url + '"></a>'; } else if (type === 'bbcode') { t = '[url=' + stockPhoto.file.backlink + '][img]' + thumb_url + '[/img][/url]\n[url=' + stockPhoto.file.backlink + ']' + stockPhoto.file.title + '[/url]\n' + stockPhoto.file.credit; } $('#stockphoto_html').text(t); }, // Event 'change' on input#stockphoto_attribution_html refresh_attribution: function () { $('#stockphoto_attribution').val(stockPhoto.file[this.checked ? 'creditHtml' : 'credit']); }, createDialogRow: function (label, prefill, id, prepend, append) { var idtext = id ? ('id="' + id + '"') : ''; return '<div class="stockphoto_dialog_row"><b>' + label + ':</b><br>' + (prepend || '') + '<input type="text" readonly ' + idtext + ' onclick="select()" value="' + mw.html.escape(prefill) + '">' + (append || '') + '</div>'; }, share_this: function (ui_mode) { var widths, html, dtitle, dl_links, best_fit, pixelStr, widthSearchMatch, imageWidth, power, i; stockPhoto.file = new File({ pageName: mw.config.get('wgPageName'), originalUrl: $('div.fullMedia a').prop('href') || '', dom: { $file: $('#file').eq(0), $src: $('#fileinfotpl_src + td'), $aut: $('#fileinfotpl_aut + td'), $attr: $('.licensetpl_attr').eq(0), $credit: $('#fileinfotpl_credit + td'), $creator: $('#creator'), $licenseAut: $('.licensetpl_aut').eq(0) }, $licenses: $('.licensetpl') }); // Grab width in pixel from DOM, and trim it down // This does not yet work for SVGs or videos widths = []; try { pixelStr = $('.fileInfo').contents().get(0).data; widthSearchMatch = /([0-9 ,.\u00a0]+)\s*×/.exec(pixelStr); imageWidth = parseInt(widthSearchMatch[1].replace(/[ ,.\u00a0]/g, ''), 10); if (isNaN(imageWidth)) { throw new Error('Cannot parse'); } // Calculate to which power of two we should go power = Math.floor(Math.log(imageWidth) / Math.log(2)); // Push 6 width to array for (i = 0; i < 5; i++) { widths.push(Math.pow(2, power-i)); } widths = widths.reverse(); } catch (e) { widths = [640, 800, 1024]; } if (ui_mode === -1) { return; } modalLoad = modalLoad || mw.loader.using('jquery.ui'); html = ''; html += stockPhoto.createDialogRow(stockPhoto.i18n.page_url, stockPhoto.file.backlink); html += stockPhoto.createDialogRow(stockPhoto.i18n.file_url, stockPhoto.file.url); html += stockPhoto.createDialogRow(stockPhoto.i18n.attribution, stockPhoto.file.credit, 'stockphoto_attribution', '<blockquote class="stockphoto_attribution_preview" onclick="nextSibling.select();return false;">' + stockPhoto.file.creditHtml + '</blockquote>', ' <input id="stockphoto_attribution_html" type="checkbox"> <label for="stockphoto_attribution_html">' + stockPhoto.i18n.html + '</label>' ); if (stockPhoto.file.gfdlNote) { html += '<span class="stockphoto_note">' + stockPhoto.i18n.gfdl_warning + '</span>'; } if (!stockPhoto.file.attrRequired) { html += '<br><span class="stockphoto_note">' + stockPhoto.i18n.no_attr + '</span>'; } switch (ui_mode) { case 1: dtitle = stockPhoto.i18n.download_this_file; if (stockPhoto.file.downloadUrl) { html += '<div><b>' + stockPhoto.i18n.download_image_file + ':</b><br>'; dl_links = []; widths.forEach(function (v) { if (stockPhoto.file.audio_or_video) { return; } dl_links.push('<a href="' + stockPhoto.stockphoto_get_thumbnail_url(v, true) + '" download>' + v + 'px</a>'); }); dl_links.push('<a href="' + stockPhoto.file.downloadUrl + '" download>' + stockPhoto.i18n.full_resolution + '</a>'); if (dl_links.length) { html += dl_links.join(' | '); } else { html += '<i>' + stockPhoto.i18n.not_available + '</i>'; } html += '</div>'; } break; case 2: dtitle = stockPhoto.i18n.use_this_file_web; html += '<div class="stockphoto_dialog_row"><div style="float: right;">'; html += '<input type="radio" name="stockphoto_code_type" value="html" id="stockphoto_code_type_html" onchange="stockPhoto.make_html_textarea();" checked> <label for="stockphoto_code_type_html">' + stockPhoto.i18n.html + '</label> '; html += '<input type="radio" name="stockphoto_code_type" value="bbcode" id="stockphoto_code_type_bbcode" onchange="stockPhoto.make_html_textarea();"> <label for="stockphoto_code_type_bbcode">' + stockPhoto.i18n.bbcode + '</label> '; html += '<select id="stockphoto_html_select" onchange="stockPhoto.make_html_textarea();">'; best_fit = 75; if (stockPhoto.file.audio) { best_fit = 300; html += '<option value="300" selected>300' + stockPhoto.i18n.px_wide_icon + '</option>'; } else { widths.forEach(function (v) { if (v <= $('#file img, #file video').width()) { best_fit = v; } html += '<option value="' + v + '">' + v + stockPhoto.i18n.px_wide + '</option>'; }); } html += '</select></div>'; html += '<b>' + stockPhoto.i18n.embed_this_file + '</b><textarea onclick="select()" id="stockphoto_html" readonly>'; html += '</textarea></div>'; break; case 3: dtitle = stockPhoto.i18n.use_this_file_wiki; html = stockPhoto.createDialogRow(stockPhoto.i18n.thumbnail, '[[File:' + mw.config.get('wgTitle') + '|thumb|' + stockPhoto.file.title + ']]'); html += stockPhoto.createDialogRow(stockPhoto.i18n.image, '[[File:' + mw.config.get('wgTitle') + '|' + stockPhoto.file.title + ']]'); break; } modalLoad.done(function () { $('<div style="display: none;"></div>').html(html).dialog({ modal: true, width: 610, height: 'auto', title: dtitle, close: function () { $(this).remove(); } }); $('#stockphoto_html_select').val(best_fit); stockPhoto.make_html_textarea(); $('#stockphoto_attribution_html').on('change', stockPhoto.refresh_attribution); }); }, call_download: function () { stockPhoto.share_this(1); }, call_web: function () { stockPhoto.share_this(2); }, call_wiki: function () { stockPhoto.share_this(3); }, i18n: { // Download: // - Button label download: 'Download', // - Button caption all_sizes: 'all sizes', // - Dialog title download_this_file: 'Download this file', // Use web: // - Button label use_this_file_web_short: 'Use this file', // - Button caption on_a_website: 'on the web', // - Dialog title use_this_file_web: 'Use this file on the web', // Use wiki: // - Button label use_this_file_wiki_short: 'Use this file', // - Button caption on_a_wiki: 'on a wiki', // - Dialog title use_this_file_wiki: 'Use this file on a wiki', thumbnail: 'Thumbnail', image: 'Image', // Email: // - Button label email_link_short: 'Email a link', // - Button caption to_this_file: 'to this file', // Reuse: // - Button label information: 'Information', // - Button caption about_reusing: 'about reusing', // Disable (button caption) remove_icons: 'Remove these icons', // Enable (button label) reuse: 'Reuse this file', via_wikimedia_commons: 'via Wikimedia Commons', see_page_for_author: 'See page for author', see_page_for_license: 'see page for license', page_url: 'Page URL', file_url: 'File URL', attribution: 'Attribution', no_attr: 'Attribution not legally required', or: 'or', gfdl_warning: 'Using this file might require attaching a full copy of the <a href="//en.wikipedia.org/wiki/GNU_Free_Documentation_License">GFDL</a>', download_image_file: 'Download image file', full_resolution: 'Full resolution', not_available: 'not available', share_this_file: 'Share this file', embed_this_file: 'Embed this file', html: 'HTML', bbcode: 'BBCode', px_wide_icon: 'px wide (icon)', px_wide: 'px wide' } }; // Export window.stockPhoto = stockPhoto; var promises = [ $.ready, mw.loader.using( [ 'mediawiki.user' ] ) ]; if (mw.config.get('wgUserLanguage') !== 'en') { var translationPromise = $.ajax({ url: mw.config.get('wgScript') + '?title=' + mw.util.wikiUrlencode('MediaWiki:Gadget-Stockphoto.js/' + mw.config.get('wgUserLanguage')) + '&action=raw&ctype=text/javascript', dataType: 'script', // For performance, use 'crossDomain' to trigger <script> instead of XHR. // Browsers do cache scripts, but not XHR. crossDomain: true, cache: true }); promises.push( translationPromise ); } $.when(...promises).done(() => { stockPhoto.init(); }); // on subpages [[MediaWiki:stockPhoto.js/langcode]]: // stockPhoto.i18n = { ... } }());