User:Inductiveload/jump to file.js

/* ================================================================== * jump_to_file.js * * Adds a link from Page to the transcluding mainspace page * * Adds a link to the file at Commons from a Page: namespace page, * an Index: page or a mainspace page with a "source" tab * * Adds a link to the work and hi-res page image at source (e.g. IA) if possible. * * Configuration is via "High-res options" in the sidebar. * ================================================================== */

/* eslint-disable camelcase, no-use-before-define, no-var

( function {

'strict';

// reference pre-loaded libraries from global scope var getGlobalLibrary = function ( key ) { var scriptName = window.ScriptDeps && window.ScriptDeps[ key ] || key; return window[ scriptName ]; };

var debugSuffix = ''; /* debug-replace: debugSuffix = '.debug'; */ var ILUI = getGlobalLibrary( 'iltools.ui' + debugSuffix );

/* ======================================================================== */

// MW Options and LocalStorage JSON interfaces

var persistUserOption = function ( key, data ) { new mw.Api.saveOption( 'userjs-' + key, JSON.stringify( data ) ); mw.user.options.set( key, data ); };

var getUserOption = function ( key ) { var data = mw.user.options.get( 'userjs-' + key ) || '';

try { data = JSON.parse( data ); } catch ( e ) { data = {}; }		return data; };

var persistLocalStorage = function ( key, data ) { window.localStorage.setItem( 'userjs-' + key, JSON.stringify( data ) ); };

var getLocalStorage = function ( key ) { var lsData = window.localStorage.getItem( 'userjs-' + key ) || '';

try { lsData = JSON.parse( lsData ); } catch ( e ) { lsData = {}; }

return lsData; };

/* ======================================================================== */

var JumpToFile = { signature: 'JumpToFile', strings: { link_title_pat: 'Page transcluded in $1', go_to_file_commons: 'Go to file at Commons', go_to_file_ns: 'Go to file in File namespace', visit_pat: 'Visit document at $1', portlet_title: 'Links', portlet_id: 'p-jumptofile', hiResOptions: 'High-res options', hiResOptionsTip: 'Change settings for download of high-res options' },		state: { offset: 0, loadHiRes: false },		// for things that probably don't change often, allow to cache the results // for a short while to speed up scrubbing though pages maxage: 120, pageIndex: getPageIndex, transcludeIcon: '//upload.wikimedia.org/wikipedia/commons/thumb/9/92/Open_book_nae_02.svg/25px-Open_book_nae_02.svg.png', pageAnchorPrefix: 'pageindex_', useHighresImg: true, infoHostname: 'https://pagelister.toolforge.org', fileIcon: '//upload.wikimedia.org/wikipedia/commons/5/56/Book_go.png', showIiifLinks: false };

function getPageIndex { if ( mw.config.get( 'wgCanonicalNamespace' ) === 'Page' ) { var num = mw.config.get( 'wgPageName' ).substring( mw.config.get( 'wgPageName' ).lastIndexOf( '/' ) + 1 ); return parseInt( num ); }		return null; }

function addLinks(links) { for (let i = 0; i < links.length; ++i) { let portletLink = mw.util.addPortletLink(					JumpToFile.strings.portlet_id,					links[i].href,					' ' + links[i].title,					links[i].id			);

if (links[i].icon) { $(portletLink).find('a').prepend(					$(' ')						.attr({ src: links[i].icon, height: 16 })					);			}

if (links[i].class) { // The following classes are used here: // * srcimglink // * transclusion-link $(portletLink).find('a').addClass(links[i].class); }		}	}

function api_get_tranclusions( namespaces, callback ) {

var ns_str = namespaces.join( '|' ), api = new mw.Api; api.get( {			action: 'query',			format: 'json',			list: 'embeddedin',			einamespace: ns_str,			eititle: mw.config.get( 'wgPageName' )		} ).done( function ( data ) {			callback( data.query.embeddedin );		} ); }

/*	 * Calls the given callback with true if the image is shared */	function apiCheckRepository( filename, callback ) { var api = new mw.Api; api.get( {			action: 'query',			format: 'json',			formatversion: 2,			maxage: JumpToFile.maxage,			prop: 'imageinfo',			titles: 'File:' + filename,			iiprop: ''		} ).done( function ( data ) {			callback( data.query.pages[ 0 ].imagerepository );		} ); }

/**	 * Reject bogus mainspace transclusions (e.g. the page is used in	 * a progress bar) *	 * @param {string} title * @return {boolean} */	function isValidTransclusion( title ) { var base = title.split( '/' )[ 0 ]; return base !== 'Main Page'; }

/**	 * Set up link to pages which transclude this one */	function setUpTransclusionLinks {

if ( mw.config.get( 'wgCanonicalNamespace' ) !== 'Page' ) { return; }

api_get_tranclusions( [ 0, 114 ], function ( embeddedin ) {

var links = []; for ( var i = 0; i < embeddedin.length; ++i ) {

if ( !isValidTransclusion( embeddedin[ i ].title ) ) { continue; }

var href = mw.util.getUrl( embeddedin[ i ].title ) + '#' + JumpToFile.pageAnchorPrefix + getPageIndex,

link = { id: 'ca-ns0_' + i,						href: href, icon: JumpToFile.transcludeIcon, title: JumpToFile.strings.link_title_pat.replace( '$1', embeddedin[ i ].title ), class: 'transclusion-link' };				links.push( link ); }

addLinks( links ); } );	}

function setUpFileLinks {

var filename, ns = mw.config.get( 'wgCanonicalNamespace' );

if ( ns === 'Index' ) { filename = mw.config.get( 'wgTitle' ); } else if ( ns === 'Page' ) { filename = mw.config.get( 'wgTitle' ).replace( /\/\d+$/, '' ); }

if ( !filename ) { // look in the "source" tab // eslint-disable-next-line no-jquery/no-global-selector var $link = $( '#ca-proofread-source a' );

if ( $link.length ) { filename = $link.first.attr( 'href' ); filename = filename.substring( filename.indexOf( ':' ) + 1 ); }		}

set_up_link_from_file( filename ); }

function set_up_link_from_file( filename ) {

apiCheckRepository( filename, function ( repo ) {

var link_href = ''; var shared = [ 'shared', 'wikimediacommons' ].indexOf( repo ) !== -1;

if ( shared ) { link_href = '//commons.wikimedia.org/wiki/File:' + filename; } else { link_href = mw.config.get( 'wgArticlePath' ).replace( '$1', 'File:' + filename ); }

addLinks( [ {				icon: JumpToFile.fileIcon,				href: link_href,				title: shared ?					JumpToFile.strings.go_to_file_commons : JumpToFile.strings.go_to_file_ns,				class: 'srcimglink'			} ] ); } );

var url = new URL( JumpToFile.infoHostname + '/img_links/v1/links' ); url.searchParams.append( 'file', filename ); url.searchParams.append( 'page', JumpToFile.pageIndex + JumpToFile.state.offset );

fetch( url ) .then( function ( data ) {				return data.json;			} ) .then( function ( jsonData ) {				handle_file_links( jsonData );			} ); }

function handle_file_links( linkData ) {

if ( !linkData.links ) { return; }

let highRes;

// Add links to tab const displayLinks = linkData.links .filter( ( link ) => {				// IIIF minfest not very useful to users in this menu				if ( [ 'iiif', 'iiif-manifest' ].indexOf( link.type ) !== -1 ) {					return JumpToFile.showIiifLinks;				}				// show everything else				return true;			} ) .map( ( link ) => {				return {					icon: link.icon,					href: link.url,					title: link.title,					class: 'srcimglink'				};			} );

addLinks( displayLinks );

for ( const link of linkData.links ) { if ( link.highres === true && !highRes ) { highRes = link; } else if ( [ 'dzi', 'iiif' ].indexOf( link.type ) !== -1 ) { highRes = link; }		}

if ( highRes ) { setHighresImg( highRes ); }	}

let originalItemCount;

function setHighresImg( highres ) { // no viewer, nothing to do		if ( !mw.proofreadpage || !mw.proofreadpage.openseadragon || !mw.proofreadpage.openseadragon.viewer ) { return; }

if ( JumpToFile.state.loadHighres &&				highres && ( mw.config.get( 'wgCanonicalNamespace' ) === 'Page' ) ) {

if ( originalItemCount === undefined ) { originalItemCount = mw.proofreadpage.openseadragon.viewer.world.getItemCount; }

if ( highres.type === 'iiif' || highres.type === 'dzi' ) { mw.proofreadpage.openseadragon.viewer .addTiledImage( {						// load with the old image in the background to avoid a large flicker						preload: true,						tileSource: highres.data || highres.url					} ); } else if ( highres.type === 'image' ) { mw.proofreadpage.openseadragon.viewer .addSimpleImage( {						replace: false,						url: highres.url					} ); }

mw.hook( JumpToFile.signature + '.highres_set' ).fire( highres ); } else { // remove any high res images const currCount = mw.proofreadpage.openseadragon.viewer.world.getItemCount; for ( let i = currCount - 1; i >= originalItemCount; --i ) { const item = mw.proofreadpage.openseadragon.viewer.world.getItemAt( i ); mw.proofreadpage.openseadragon.viewer.world.removeItem( item ); }		}	}

var initWindowManager = function {

if ( JumpToFile.windowManager ) { return; }

JumpToFile.windowManager = new OO.ui.WindowManager; // Create and append a window manager, which will open and close the window. $( document.body ).append( JumpToFile.windowManager.$element ); };

var getIndexKey = function { return mw.config.get( 'wgTitle' ).replace( /\/\d+$/, '' ); };

var initialiseLsData = function ( lsData ) { if ( !lsData.offsets ) { lsData.offsets = {}; }	};

var loadSettings = function { var opts = getUserOption( JumpToFile.signature ); var lsData = getLocalStorage( JumpToFile.signature );

initialiseLsData( lsData );

loadSettingsFromData( opts, lsData ); };

var loadSettingsFromData = function ( opts, lsData ) { JumpToFile.state.loadHighres = opts.loadHighres || false; JumpToFile.state.offset = lsData.offsets[ getIndexKey ] || 0; };

var storeOptions = function ( params ) {

// these options save persistently across all sessions var opts = { loadHighres: params[ 0 ] };

persistUserOption( JumpToFile.signature, opts );

// the offset opts go into local storage because they're big and can go stale // TODO persist somewhere on the index page for all users? var filename = getIndexKey;

var lsData = getLocalStorage( JumpToFile.signature );

initialiseLsData( lsData );

lsData.offsets[ filename ] = params[ 1 ];

persistLocalStorage( JumpToFile.signature, lsData );

loadSettingsFromData( opts, lsData );

reloadFileLinks;

return true; };

var reloadFileLinks = function {

if ( JumpToFile.state.$linkUl ) { JumpToFile.state.$linkUl.find( '.srcimglink' ).remove; }

setUpFileLinks; };

var showOptions = function { initWindowManager;

var needs = [ {				type: 'bool', label: 'Load hi-res images', help: 'Load high-resolution images from the upstream source if possible', value: JumpToFile.state.loadHighres },			{				type: 'int', label: 'Source offset', help: 'The offset between the page numbering in the WIkisource file and the file at the source. ' +					'Example: "0" if the files are identical, "1" if the source has a cover sheet and the Wikisource ' + 'file has had that page removed.', value: JumpToFile.state.offset }		];

// Make the window. var dialog = new ILUI.GeneralParamsDialog( {			size: 'medium'		} );

JumpToFile.windowManager.addWindows( [ dialog ] );

JumpToFile.windowManager.openWindow( dialog, {			title: 'JumpToFile options',			needs: needs,			saveCallback: function ( params ) {				return $.Deferred.resolve( storeOptions( params ) );			}		} ); };

var addConfigMenu = function { var portlet = mw.util.addPortletLink( 'p-tb', '#',			JumpToFile.strings.hiResOptions, JumpToFile.signature + '-enable_hires_dl',			JumpToFile.strings.hiResOptionsTip );

$( portlet ).on( 'click', function ( e ) {			e.preventDefault;

showOptions; } );	};

var addLinksMenu = function { const portlet = mw.util.addPortlet(			JumpToFile.strings.portlet_id,			JumpToFile.strings.portlet_title,			'#p-cactions'		);

const skin = mw.config.get("skin"); if (skin === 'vector') { // We can use the returned node directly $(portlet).appendTo("#left-navigation"); } else if (skin === 'vector-2022') { // Need to grab the *-dropdown wrapper Vector 2022 creates $('#' + JumpToFile.strings.portlet_id + "-dropdown") .appendTo("#left-navigation"); } else { // Do nothing since the skin doesn't support dropdowns anyway. }	};

// The loader has handled "wait for DOM ready" mw.hook( JumpToFile.signature + '.config' ).fire( JumpToFile );

loadSettings;

addConfigMenu; addLinksMenu; setUpTransclusionLinks; reloadFileLinks;

} );