MediaWiki:Gadget-ImportPagelist.js

Catatan: Setelah disimpan, Anda mungkin perlu melewati tembolok peramban web untuk melihat perubahan.

  • Firefox/Safari: Tekan dan tahan Shift sembari mengeklik Reload, atau tekan Ctrl-F5 atau Ctrl-R (⌘-R di Mac)
  • Google Chrome: Tekan Ctrl-Shift-R (⌘-Shift-R di Mac)
  • Internet Explorer / Edge: Tahan Ctrl sembari mengeklik Refresh, atau tekan Ctrl-F5
  • Opera: Tekan Ctrl-F5.
/**
 * Import pagelist gadget
 *
 * Adds a link to import a pagelist from a given IA identifier
 *
 * Uses the pagelister.toolforge.org tool
 *
 * Changelog:
 *   * 2020-10-24:  Initial version
 *   * 2020-11-27:  Update cover image from title page, if there is one
 *   * 2021-01-06:  Handle Hathi links
 */

/* eslint-disable
		camelcase
*/

'use strict';

// IIFE used when including as a user script (to allow debug or config)
// Default gadget use will get an IIFE wrapper as well
( function () {

	var gadgetName = 'import_pagelist',

		IPL = {
			enabled: true,
			configured: false,
			host: 'https://pagelister.toolforge.org',
			linkLabel: 'Import pagelist',
			link_style: { 'font-size': '92%', float: 'right' },
			error_message: 'An error occurred:',
			bad_response_msg: 'Bad response from pagelist server',
			cancel_btn: 'Cancel',
			import_btn: 'Import',
			dialog_title: 'Import pagelist'
		};

	/*
	* Read config from user. function fired by hook
 	*/
	function apply_config( cfg ) {
		console.log( 'Configuring ' + gadgetName );

		// Bail if the config looks like junkA
		if ( !cfg || typeof cfg !== 'object' ) {
			console.error( 'Invalid ' + gadgetName + ' config', cfg );
			return;
		}

		if ( typeof cfg.enabled === 'boolean' ) {
			IPL.enabled = cfg.enabled;
		}

		if ( typeof cfg.host === 'string' ) {
			IPL.host = cfg.host;
		}

		IPL.configured = true;
	}

	function install_dialog() {

		var ParamDialog = function ( config ) {
			ParamDialog.super.call( this, config );
		};
		OO.inheritClass( ParamDialog, OO.ui.ProcessDialog );

		// Specify a name for .addWindows()
		ParamDialog.static.name = 'importPageListDialog';
		ParamDialog.static.title = IPL.dialog_title;
		// Specify the static configurations: title and action set
		ParamDialog.static.actions = [ {
			flags: 'primary',
			label: IPL.import_btn,
			action: 'open'
		},
		{
			flags: 'safe',
			label: IPL.cancel_btn
		}
		];

		// Customize the initialize() function to add content and layouts:
		ParamDialog.prototype.initialize = function () {
			ParamDialog.super.prototype.initialize.call( this );
			this.panel = new OO.ui.PanelLayout( {
				padded: true,
				expanded: false
			} );
			this.content = new OO.ui.FieldsetLayout();

			this.inputs = {};
			this.fields = {};

			this.inputs.source = new OO.ui.DropdownInputWidget( {
				options: [
					{ data: 'ia', label: 'Internet Archive' },
					{ data: 'ht', label: 'Hathi Trust' }
				] } );

			// this.inputs['source'].selectItem( option1 );

			this.fields.source = new OO.ui.FieldLayout( this.inputs.source, {
				label: 'Source',
				align: 'right'
			} );
			this.content.addItems( [ this.fields.source ] );

			this.inputs.id = new OO.ui.TextInputWidget();

			this.fields.id = new OO.ui.FieldLayout( this.inputs.id, {
				label: 'ID',
				align: 'right'
			} );
			this.content.addItems( [ this.fields.id ] );

			this.inputs.offset = new OO.ui.NumberInputWidget( {
				value: 0,
				step: 1
			} );

			this.fields.offset = new OO.ui.FieldLayout( this.inputs.offset, {
				label: 'Offset',
				help: 'Offset between the source and the file. If the file are identical, this is 0. If a cover page has been removed, it is 1',
				align: 'right'
			} );
			this.content.addItems( [ this.fields.offset ] );

			this.panel.$element.append( this.content.$element );

			var toolLink = '<a href="' + IPL.host + '">' + IPL.host.replace( /https?:\/\//, '' ) + '</a>';

			this.panel.$element.append( $( '<hr/><p style="font-size:90%;">Note: this tool will access ' + toolLink + '.</p>' ) );

			this.$body.append( this.panel.$element );
		};

		ParamDialog.prototype.prefill_id = function ( link ) {
			this.inputs.id.setValue( link.id );
			this.inputs.source.setValue( link.source );
		};

		// Use getSetupProcess() to set up the window with data passed to it at the time
		// of opening (e.g., url: 'http://www.mediawiki.org', in this example).
		ParamDialog.prototype.getSetupProcess = function ( data ) {
			var self = this;
			data = data || {};
			return ParamDialog.super.prototype.getSetupProcess.call( this, data )
				.next( function () {
					// Fire off a request to see if we can find any useful IDs in the commons page
					find_likely_ids( function ( data ) {
						self.prefill_id( data );
					} );
				}, this );
		};

		function report_error( msg ) {
			alert( IPL.error_message + '\n\n' + msg );
		}

		// Specify processes to handle the actions.
		ParamDialog.prototype.getActionProcess = function ( action ) {
			var dialog = this;
			if ( action === 'open' ) {
				// Create a new process to handle the action
				return new OO.ui.Process( function () {
					set_pl_enabled( false );

					fetch( IPL.host + '/pagelist/v1/list?' + new URLSearchParams( {
						source: dialog.inputs.source.getValue(),
						id: dialog.inputs.id.getValue(),
						offset: dialog.inputs.offset.getNumericValue(),
					} ) )
						.then( function ( response ) { return response.json(); } )
						.then( function ( data ) {
							if ( data ) {

								if ( data.errors !== undefined ) {
									var errors = data.errors.map( function ( e ) {
										return e.msg;
									} ).join( '\n' );
									report_error( errors );
								} else if ( data.pagelist !== undefined ) {
									var plv = data.pagelist;
									set_pl_value( plv );
									set_cover_value( plv );
								} else {
									report_error( IPL.bad_response_msg );
								}
							}
						} )
						.finally( function () { set_pl_enabled( true ); } );

					dialog.close( {
						action: action
					} );
				}, this );
			}
			// Fallback to parent handler
			return ParamDialog.super.prototype.getActionProcess.call( this, action );
		};

		ParamDialog.prototype.getTeardownProcess = function ( data ) {
			return ParamDialog.super.prototype.getTeardownProcess.call( this, data )
				.first( function () {
					// Perform any cleanup as needed
				}, this );
		};

		// Create and append a window manager.
		var windowManager = new OO.ui.WindowManager();
		$( 'body' ).append( windowManager.$element );

		// Create a new process dialog window.
		var paramDlg = new ParamDialog();

		// Add the window to window manager using the addWindows() method.
		windowManager.addWindows( [ paramDlg ] );

		// Open the window!
		windowManager.openWindow( paramDlg );

		// focus the input in just a moment
		setTimeout( function () {
			paramDlg.$body.find( 'input' )[ 0 ].focus();
		}, 300 );
	}

	function get_ia_id_from_url( url ) {
		var rx = /(details|manage|download)\/([^/]*)/,
			match = rx.exec( url.pathname );

		if ( match ) {
			return match[ 2 ];
		}

		return null;
	}

	function get_ht_id_from_url( url ) {
		if ( url.hostname.match( /hathitrust.org$/ ) ) {
			// null if not found
			return url.searchParams.get( 'id' );
		} else if ( url.hostname.match( /hdl.handle.net$/ ) ) {
			return url.pathname.split( '/' )[ 2 ];
		}
		return null;
	}

	// Find IA links in a page's content
	function find_useful_links( data ) {

		for ( var page in data.query.pages ) {
			var extlinks = data.query.pages[ page ].extlinks;

			if ( extlinks ) {
				for ( var i = 0; i < extlinks.length; i++ ) {
					var l = extlinks[ i ].url;

					if ( l.startsWith( '//' ) ) {
						l = window.location.protocol + l;
					}
					var url = new URL( l );

					if ( url.hostname.match( /archive.org$/ ) ) {
						var id = get_ia_id_from_url( url );
						if ( id !== null ) {
							return { source: 'ia', id: id };
						}
					} else if ( url.hostname.match( /hathitrust.org$/ ) ||
						( url.hostname.match( /hdl.handle.net$/ ) ) ) {
						var id = get_ht_id_from_url( url );
						if ( id !== null ) {
							return { source: 'ht', id: id };
						}
					}
				}
			}

			var iwlinks = data.query.pages[ page ].iwlinks;

			if ( iwlinks ) {
				for ( var i = 0; i < iwlinks.length; i++ ) {
					var l = iwlinks[ i ];
						if ( l.prefix === 'iarchive' ) {
						return { source: 'ia', id: l.title };
					}
				}
			}
		}

		return null;
	}

	function find_likely_ids( callback ) {
		// Get all external links from the Commons file page
		mw.loader.using( 'mediawiki.ForeignApi' ).done( function () {
			var api = new mw.ForeignApi( 'https://commons.wikimedia.org/w/api.php' ),
				com_pg = 'File:' + mw.config.get( 'wgTitle' );
			api.get( {
				action: 'query',
				format: 'json',
				formatversion: 2,
				prop: 'extlinks|iwlinks',
				titles: com_pg,
				ellimit: 100,
				iwlimit: 100
			} ).done( function ( data ) {
				var link = find_useful_links( data );

				if ( link !== null ) {
					callback( link );
				}

			} ).fail( function ( data ) {
				console.log( 'Commons GET Failed:', data );
			} );
		} );
	}

	function set_pl_value( plv ) {
		OO.ui.infuse( $( '#wpprpindex-Pages' ).parent() ).setValue( plv );
	}

	function set_cover_value( plv ) {
		// Set the cover image if there is one
		var match = /(\d+)=["']?[Tt]itle["']\s/.exec( plv );

		if ( match ) {
			$( '#wpprpindex-Image' ).val( match[ 1 ] );
		}
	}

	function set_pl_enabled( enabled ) {
		OO.ui.infuse( $( '#wpprpindex-Pages' ).parent() ).setDisabled( !enabled );
	}

	// User clicked - install the dialog
	function activate() {
		mw.loader.using( [ 'mediawiki.util', 'oojs-ui-core', 'oojs-ui-widgets' ],
			install_dialog );
	}

	// Insert the button next to the right field
	function insert_button() {
		var $btn = $( '<a>' )
			.attr( 'href', '#' )
			.append( IPL.linkLabel )
			.addClass( 'import_pagelist_link' )
			.on( 'click', activate );

		// eslint-disable-next-line no-jquery/no-global-selector
		$( '#wpprpindex-Pages' )
			.closest( '.oo-ui-fieldLayout-body' )
			.children( '.oo-ui-fieldLayout-header' )
			.find( 'label' )
			.append( $btn );
	}

	function add_css_rule( rule, css ) {
		css = JSON.stringify( css ).replace( /"/g, '' ).replace( /,/g, ';' );
		$( '<style>' ).prop( 'type', 'text/css' ).html( rule + css ).appendTo( 'head' );
	}

	function setConfigs( cfg ) {
		if ( cfg.host ) {
			IPL.host = cfg.host;
		}
	}

	function iplSetup() {

		var blankCfg = {};

		// Get user config, if any
		mw.hook( gadgetName + '.config' ).fire( blankCfg );
		setConfigs( blankCfg );

		// only care for editing in the Index: namespace
		if ( !( mw.config.get( 'wgAction' ) === 'edit' || mw.config.get( 'wgAction' ) ===
			'submit' ) ||
		mw.config.get( 'wgCanonicalNamespace' ) !== 'Index' ) {
			return;
		}

		add_css_rule( '.import_pagelist_link', IPL.link_style );

		insert_button();
	}
	$( iplSetup );

}() );