MediaWiki:MediaUploader.js

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
( function () {
	'use strict';

	// Wait for the ext.uploadWizard module to be ready before patching prototypes
	mw.loader.using( [ 'ext.uploadWizard', 'oojs-ui-core', 'oojs-ui-widgets' ] ).then( function () {
		var uw = mw.uploadWizard;
		if ( !uw || !uw.deed || !uw.deed.ThirdParty || !mw.UploadWizardDetails ) {
			return;
		}

		// Define Bahá'í source definitions
		var BAHAI_SOURCES = {
			bwn: {
				label: 'Bahá’í World News Service (news.bahai.org)',
				template: 'bwn',
				license: '{{Baha\'i World News Service}}',
				fields: [
					{ key: 'article', label: 'Article number (e.g., 1868)', placeholder: '1868' }
				],
				getCategory: function ( vals ) {
					return vals.article ? 'Category:BWNS ' + vals.article : null;
				}
			},
			bns: {
				label: 'Bahá’í News (1924–1990)',
				template: 'bns',
				license: '{{Bn-excerpt}}',
				fields: [
					{ key: 'issue', label: 'Issue number (e.g., 161)', placeholder: '161' },
					{ key: 'page', label: 'Page number (e.g., 6)', placeholder: '6' }
				],
				getCategory: function ( vals ) {
					return vals.issue ? 'Category:Baha\'i News No ' + vals.issue : null;
				}
			},
			bws: {
				label: 'The Bahá’í World (1925–2024)',
				template: 'bws',
				license: '{{Baha\'i World excerpt}}',
				fields: [
					{ key: 'volume', label: 'Volume number (e.g., 16)', placeholder: '16' },
					{ key: 'page', label: 'Page number (e.g., 28)', placeholder: '28' }
				],
				getCategory: function ( vals ) {
					return vals.volume ? 'Category:BW Volume ' + vals.volume : null;
				}
			},
			ab: {
				label: 'The American Bahá’í (1970–2001)',
				template: 'ab',
				license: '{{Abn-copyright}}',
				fields: [
					{ key: 'volume', label: 'Volume number (e.g., 1)', placeholder: '1' },
					{ key: 'issue', label: 'Issue number (e.g., 4)', placeholder: '4' },
					{ key: 'page', label: 'Page number (e.g., 1)', placeholder: '1' }
				],
				getCategory: function ( vals ) {
					return ( vals.volume && vals.issue ) ? 'Category:AB Volume ' + vals.volume + ' No ' + vals.issue : null;
				}
			},
			sw: {
				label: 'Star of the West',
				template: 'sw',
				license: '{{SOTW excerpt}}',
				fields: [
					{ key: 'volume', label: 'Volume number (e.g., 1)', placeholder: '1' },
					{ key: 'issue', label: 'Issue number (e.g., 2)', placeholder: '2' },
					{ key: 'page', label: 'Page number (e.g., 9)', placeholder: '9' }
				],
				getCategory: function ( vals ) {
					return vals.volume ? 'Category:SW Volume ' + vals.volume + ' pictures' : null;
				}
			}
		};

		// =========================================================================
		// 1. Patch uw.deed.ThirdParty.prototype.setFormFields
		// =========================================================================
		var origSetFormFields = uw.deed.ThirdParty.prototype.setFormFields;
		uw.deed.ThirdParty.prototype.setFormFields = function ( $selector ) {
			var deed = this;
			origSetFormFields.call( this, $selector );

			this.bahaiSourceType = null;
			this.bahaiParamInputs = {};

			// Build radio options
			var radioOptions = [];
			Object.keys( BAHAI_SOURCES ).forEach( function ( key ) {
				radioOptions.push( new OO.ui.RadioOptionWidget( {
					data: key,
					label: BAHAI_SOURCES[ key ].label
				} ) );
			} );
			radioOptions.push( new OO.ui.RadioOptionWidget( {
				data: 'other',
				label: 'Other source (enter URL or publication below)'
			} ) );

			this.bahaiRadioSelect = new OO.ui.RadioSelectWidget( {
				items: radioOptions
			} );

			var $bahaiContainer = $( '<div>' )
				.addClass( 'bahai-mediauploader-source-selector' )
				.css( { marginBottom: '1em' } );

			var $paramsContainer = $( '<div>' )
				.addClass( 'bahai-mediauploader-source-params' )
				.css( {
					marginTop: '0.75em',
					marginLeft: '1.75em',
					padding: '0.75em 1em',
					borderLeft: '3px solid var(--border-color-ui-widget, #a2a9b1)',
					backgroundColor: 'var(--background-color-ui-subtle, #f8f9fa)'
				} )
				.hide();

			$bahaiContainer.append(
				this.bahaiRadioSelect.$element,
				$paramsContainer
			);

			// Insert radio selector right above the original Source input widget
			this.sourceInput.$element.before( $bahaiContainer );

			// Grab wrappers for Source help text, Author, and License so we can toggle them
			var $sourceHelp = this.sourceInputField.$element.find( '.mediauploader-details-help' );
			var $authorWrapper = this.authorInputField.$element.closest( '.mediauploader-thirdparty-fields' );
			var $licenseWrapper = this.licenseInputField.$element.closest( '.mediauploader-thirdparty-license' );

			// Helper to sync parameter inputs into the underlying sourceInput
			function updateBahaiSourceValue() {
				var type = deed.bahaiSourceType;
				if ( !type || type === 'other' ) {
					return;
				}
				var spec = BAHAI_SOURCES[ type ];
				var values = [];
				var valMap = {};
				var allFilled = true;

				spec.fields.forEach( function ( f ) {
					var val = deed.bahaiParamInputs[ f.key ] ? deed.bahaiParamInputs[ f.key ].getValue().trim() : '';
					if ( !val ) {
						allFilled = false;
					}
					values.push( val );
					valMap[ f.key ] = val;
				} );

				// Show or hide the duplicate check warning
				if ( deed.bahaiCategoryWarning ) {
					var catTitle = spec.getCategory ? spec.getCategory( valMap ) : null;
					if ( catTitle ) {
						var catUrl = mw.util.getUrl( catTitle );
						var $msg = $( '<span>' ).append(
							'Most images from this source have already been uploaded. Please check ',
							$( '<a>' )
								.attr( { href: catUrl, target: '_blank', rel: 'noopener' } )
								.css( 'font-weight', 'bold' )
								.text( catTitle ),
							' to make sure your image does not already exist before continuing.'
						);
						deed.bahaiCategoryWarning.setLabel( $msg );
						deed.bahaiCategoryWarning.toggle( true );
					} else {
						deed.bahaiCategoryWarning.toggle( false );
					}
				}

				if ( allFilled ) {
					deed.sourceInput.setValue( '{{' + spec.template + '|' + values.join( '|' ) + '}}' );
				} else {
					// Keep empty so validation catches incomplete parameters
					deed.sourceInput.setValue( '' );
				}
			}

			// Handle radio button selection
			this.bahaiRadioSelect.on( 'choose', function ( item ) {
				var key = item.getData();
				deed.bahaiSourceType = key;
				deed.bahaiParamInputs = {};
				$paramsContainer.empty();

				// Clear previous validation errors when switching
				deed.sourceInputField.setErrors( [] );

				if ( key === 'other' ) {
					$paramsContainer.hide();
					$sourceHelp.show();
					deed.sourceInput.$element.show();
					if ( !deed.sourceInput.getValue().indexOf( '{{' ) ) {
						deed.sourceInput.setValue( '' );
					}
					if ( deed.authorInput.getValue() === 'N/A' ) {
						deed.authorInput.setValue( '' );
					}
					$authorWrapper.show();
					$licenseWrapper.show();
				} else {
					var spec = BAHAI_SOURCES[ key ];
					$sourceHelp.hide();
					deed.sourceInput.$element.hide();
					$authorWrapper.hide();
					$licenseWrapper.hide();

					// Set dummy author to satisfy internal methods if called
					deed.authorInput.setValue( 'N/A' );

					// Build specific inputs for this Bahá'í source
					spec.fields.forEach( function ( f ) {
						var input = new OO.ui.TextInputWidget( {
							placeholder: f.placeholder
						} );
						input.on( 'change', updateBahaiSourceValue );
						deed.bahaiParamInputs[ f.key ] = input;

						var fieldLayout = new OO.ui.FieldLayout( input, {
							label: f.label,
							align: 'top'
						} );
						$paramsContainer.append( fieldLayout.$element );
					} );

					// Add warning widget for checking existing category
					deed.bahaiCategoryWarning = new OO.ui.MessageWidget( {
						type: 'warning',
						inline: false
					} );
					deed.bahaiCategoryWarning.$element.css( 'margin-top', '1em' );
					deed.bahaiCategoryWarning.toggle( false );
					$paramsContainer.append( deed.bahaiCategoryWarning.$element );

					$paramsContainer.show();
					updateBahaiSourceValue();
				}
			} );

			// Override sourceInput.getErrors to provide clear feedback for Bahá'í source fields
			var origGetErrors = this.sourceInput.getErrors;
			this.sourceInput.getErrors = function () {
				if ( !deed.bahaiSourceType ) {
					return $.Deferred().resolve( [
						mw.message( 'mediauploader-error-blank' )
					] ).promise();
				}
				if ( deed.bahaiSourceType !== 'other' ) {
					var spec = BAHAI_SOURCES[ deed.bahaiSourceType ];
					var missing = [];
					spec.fields.forEach( function ( f ) {
						var val = deed.bahaiParamInputs[ f.key ] ? deed.bahaiParamInputs[ f.key ].getValue().trim() : '';
						if ( !val ) {
							missing.push( f.label );
						}
					} );
					if ( missing.length > 0 ) {
						return $.Deferred().resolve( [
							mw.message( 'mediauploader-error-blank' )
						] ).promise();
					}
					return $.Deferred().resolve( [] ).promise();
				}
				return origGetErrors.call( this );
			};

			// Initially hide the raw source input, help text, author, and license until the user selects "other"
			$sourceHelp.hide();
			this.sourceInput.$element.hide();
			$authorWrapper.hide();
			$licenseWrapper.hide();
		};

		// =========================================================================
		// 2. Patch uw.deed.ThirdParty.prototype.getFields & getLicenseWikiText
		// =========================================================================
		var origGetFields = uw.deed.ThirdParty.prototype.getFields;
		uw.deed.ThirdParty.prototype.getFields = function () {
			if ( this.bahaiSourceType && this.bahaiSourceType !== 'other' ) {
				// Only validate the source field when a Bahá'í source is chosen
				return [ this.sourceInputField ];
			}
			return origGetFields.call( this );
		};

		var origGetLicenseWikiText = uw.deed.ThirdParty.prototype.getLicenseWikiText;
		uw.deed.ThirdParty.prototype.getLicenseWikiText = function () {
			if ( this.bahaiSourceType && this.bahaiSourceType !== 'other' ) {
				return BAHAI_SOURCES[ this.bahaiSourceType ].license;
			}
			return origGetLicenseWikiText.call( this );
		};

		// =========================================================================
		// 3. Patch mw.UploadWizardDetails for Step 4 ("Describe") UI & Wikitext
		// =========================================================================
		function isBahaiSourceDeed( upload ) {
			var deed = upload && upload.deedChooser && upload.deedChooser.deed;
			return Boolean(
				deed &&
				deed.name === 'thirdparty' &&
				deed.bahaiSourceType &&
				deed.bahaiSourceType !== 'other'
			);
		}

		// Adjust Step 4 fields (Rename "Description" -> "Caption" and hide "Date")
		var origBuildInterface = mw.UploadWizardDetails.prototype.buildInterface;
		mw.UploadWizardDetails.prototype.buildInterface = function () {
			origBuildInterface.apply( this, arguments );

			if ( isBahaiSourceDeed( this.upload ) ) {
				var captionFieldKey = mw.UploadWizard.config.content.captionField || 'description';
				if ( this.fieldWrapperMap[ captionFieldKey ] ) {
					this.fieldWrapperMap[ captionFieldKey ].setLabel( 'Caption' );
				}
				if ( this.fieldWrapperMap.date ) {
					this.fieldWrapperMap.date.toggle( false );
					// Ensure hidden date field does not block validation or trigger warnings
					this.fieldMap.date.getErrors = function () {
						return $.Deferred().resolve( [] ).promise();
					};
					this.fieldMap.date.getWarnings = function () {
						return $.Deferred().resolve( [] ).promise();
					};
				}
			}
		};

		// Output {{cs}} instead of {{Information}} when a Bahá'í source is selected
		var origGetWikiText = mw.UploadWizardDetails.prototype.getWikiText;
		mw.UploadWizardDetails.prototype.getWikiText = function () {
			if ( !isBahaiSourceDeed( this.upload ) ) {
				return origGetWikiText.apply( this, arguments );
			}

			var deed = this.upload.deedChooser.deed;
			var captionFieldKey = mw.UploadWizard.config.content.captionField || 'description';
			var caption = '';

			if ( this.fieldMap[ captionFieldKey ] ) {
				// Use getCaption() if available so it doesn't wrap in {{en|1=...}} unless desired
				if ( typeof this.fieldMap[ captionFieldKey ].getCaption === 'function' ) {
					caption = this.fieldMap[ captionFieldKey ].getCaption().trim();
				} else {
					caption = this.fieldMap[ captionFieldKey ].getWikiText().trim();
				}
			}

			var source = deed.getSourceWikiText( this.upload ).trim();
			var license = deed.getLicenseWikiText().trim();
			var categories = this.fieldMap.categories ? this.fieldMap.categories.getWikiText().trim() : '';

			var wikiText = '== File info ==\n' +
				'{{cs\n' +
				'| caption = ' + caption + '\n' +
				'| source = ' + source + '\n' +
				'}}\n\n' +
				'== File license ==\n' +
				license;

			if ( categories ) {
				wikiText += '\n\n' + categories;
			}

			return wikiText;
		};
	} );
}() );