User:Sohom Datta/jump to file.js

From Wikisource
Jump to navigation Jump to search
Note: After saving, changes may not occur immediately. Click here to learn how to bypass your browser's cache.
  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Cmd-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (Cmd-Shift-R on a Mac)
  • Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Clear the cache in Tools → Preferences

For details and instructions about other browsers, see Wikipedia:Bypass your cache.

/* ==================================================================
 * 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(
					$('<img>')
						.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();

}() );