User:DannyS712/DiscussionCloser.js/sandbox.js

Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <nowiki>
// Script to make closing discussions easier
// @author DannyS712
// based on a script by Abelmoschus Esculentus, but completely rewritten from scratch
/* jshint maxerr: 999 */
$(() => {
var DiscussionCloser = {};
window.DiscussionCloser = DiscussionCloser;

DiscussionCloser.config = {
	version: 'DEV',
	debug: true
};

DiscussionCloser.init = function () {
	mw.loader.using(
		[ 'mediawiki.util', 'mediawiki.api' ],
		DiscussionCloser.run
	);
};

DiscussionCloser.debug = function () {
	if ( DiscussionCloser.config.debug ) {
		console.log.apply( null, arguments );
	}
};

DiscussionCloser.run = function () {
	DiscussionCloser.debug( 'Starting DiscussionCloser' );
	DiscussionCloser.addCSS();
	DiscussionCloser.addFakeLinks();
};

DiscussionCloser.onErrHandler = function () {
	// Shared error handler
	alert( 'Something went wrong' );
	console.log.apply( null, arguments );
};

DiscussionCloser.addCSS = function () {
	mw.util.addCSS(`
#DiscussionCloser-wrapper {
	position: fixed;
	z-index: 1;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	overflow: hidden;
	background-color: rgba(0,0,0,0.4);
}
#DiscussionCloser-interface {
	background-color: #d6d6d6;
	margin: 10% auto;
	padding: 2px 20px;
	border: 1px solid #888;
	width: 80%;
	max-width: 60em;
	font-size: 90%;
}
#DiscussionCloser-interface-body {
	min-height: 7em;
	width: 875px;
	height: 400px;
	overflow-y: scroll;
}
#DiscussionCloser-interface-footer {
	min-height: 3em;
}
.DiscussionCloser-bottom {
	margin-bottom: 0.5em;
}
#DiscussionCloser-preview-raw {
	background-color: white;
}
#DiscussionCloser-preview-raw span.mw-editsection {
	display: none;
}

input[name=DiscussionCloser-radios] {
	margin-left: 7px;
	margin-right: 2px;
}
`);
};

DiscussionCloser.addFakeLinks = function () {
	var $sectionHeadings = $( '#mw-content-text' ).find( 'h1,h2,h3,h4,h5,h6' );
	var SECTION_FROM_URL_REGEX = /&section=(\d+)/;
	
	$sectionHeadings.each( function ( idx, header ) {
		var $editSection = $( header ).find( '.mw-editsection' );
		// console.log( $editSection );
		
		var editLinkURL = $editSection.children( 'a:first' ).attr( 'href' );
		// console.log( editLinkURL );
		var sectionNum = SECTION_FROM_URL_REGEX.exec( editLinkURL );
		if ( sectionNum === null ) {
			// Something went wrong
			return;
		}
		sectionNum = parseInt( sectionNum[1] );
		// console.log( sectionNum );
		var $closeBracket = $editSection.children().last();
		// console.log( $closeBracket );
		$closeBracket.before(
			"<span style='color: #54595d'> | </span>",
			$( '<span>' ).append(
				$( '<a>' )
					.text( 'Close discussion' )
					.click( function () {
						console.log( 'Closing discussion for section number: ' + sectionNum );
						console.log( $( this ) );
						DiscussionCloser.getSectionContent( sectionNum ).then(
							function ( currentContent ) {
								console.log( currentContent );
								DiscussionCloser.showForSection( sectionNum, currentContent );
							}
						);
					} )
			)
		);
	} );
};

DiscussionCloser.showForSection = function ( sectionNum, currentContent ) {
	$( 'body' ).prepend( DiscussionCloser.addInterface( sectionNum, currentContent ) );
	document.getElementById( 'DiscussionCloser-interface' ).addEventListener(
		'keydown',
		function ( key ) {
			if ( key.ctrlKey && ( key.keyCode == 10 || key.keyCode == 13 ) ) {
				document.getElementById( 'DiscussionCloser-submit' ).click();
			} else if ( key.keyCode == 27 ) {
				document.getElementById( 'DiscussionCloser-cancel' ).click();
			}
		}
	);
};

DiscussionCloser.getTemplateInfo = function ( template ) {
	// object with
	// - `supportsStatus` (bool)
	// - `topText` (string including %RESULT% and optionally %STATUS% )
	// - `bottomText` (string)
	// - `label` (string to show in the interface)
	var templateInfo = {
		ArchiveTop: {
			supportsStatus: true,
			topText: '{{Archive top |status=%STATUS% |result=%RESULT% }}',
			bottomText: '{{Archive bottom}}',
			label: 'Generic archive'
		},
		ArchiveTopRed: {
			supportsStatus: true,
			topText: '{{Archive top red |status=%STATUS% |result=%RESULT% }}',
			bottomText: '{{Archive bottom}}',
			label: 'Generic (red)'
		},
		ArchiveTopYellow: {
			supportsStatus: true,
			topText: '{{Archive top yellow |status=%STATUS% |result=%RESULT% }}',
			bottomText: '{{Archive bottom}}',
			label: 'Generic (yellow)'
		},
		ArchiveTopGreen: {
			supportsStatus: true,
			topText: '{{Archive top green |status=%STATUS% |result=%RESULT% }}',
			bottomText: '{{Archive bottom}}',
			label: 'Generic (green)'
		},
		ClosedRFCTop: {
			supportsStatus: false,
			topText: '{{Closed rfc top |result=%RESULT% }}',
			bottomText: '{{closed rfc bottom}}',
			label: 'Closed RFC'
		},
		RequestedMove: {
			supportsStatus: false,
			topText: '{{subst:RM top |result=%RESULT% }}',
			bottomText: '{{subst:RM bottom}}',
			label: 'Closed RM'
		},
		HiddenArchive: {
			supportsStatus: false,
			topText: '{{Hidden archive top |result=%RESULT% }}',
			bottomText: '{{Hidden archive bottom}}',
			label: 'Hidden archive'
		}
	};
	if ( template === false ) {
		return templateInfo;
	}
	return templateInfo[ template ];
};

DiscussionCloser.onTopTemplateChange = function ( newTemplate ) {
	var $statusInput = $( '#DiscussionCloser-status-text' );
	if ( DiscussionCloser.getTemplateInfo( newTemplate ).supportsStatus ) {
		$statusInput.prop( 'disabled', false );
	} else {
		$statusInput.prop( 'disabled', true );
	}
};

DiscussionCloser.addPreview = function ( sectionNum, currentContent ) {
	var newText = DiscussionCloser.getNewSectionText( sectionNum, currentContent );
	new mw.Api().get( {
		action: 'parse',
		title: mw.config.get( 'wgPageName' ),
		text: newText,
		prop: 'text',
		pst: '1',
		formatversion: 2
	} ).then(
		function ( result ) {
			// console.log( result );
			var parsedText = result.parse.text;
			$( '#DiscussionCloser-preview-raw' ).html( parsedText );
			$( '#DiscussionCloser-preview-wrapper' ).show();
		},
		DiscussionCloser.onErrHandler
	);
};

DiscussionCloser.hidePreview = function () {
	$( '#DiscussionCloser-preview-wrapper' ).hide();
};

DiscussionCloser.closeDiscussion = function ( sectionNum, currentContent ) {
	console.log( 'Should try and close discussion now' );
	var newText = DiscussionCloser.getNewSectionText( sectionNum, currentContent );
	var summary = DiscussionCloser.getEditSummary( currentContent );
	var editParams = {
		action: 'edit',
		title: mw.config.get( 'wgPageName' ),
		section: sectionNum,
		text: newText,
		summary: summary,
		notminor: true,
		nocreate: true,
		format: 'json',
		formatversion: 2
	};
	// console.log( editParams );
	new mw.Api().postWithEditToken( editParams ).then(
		function ( response ) {
			console.log( response );
			DiscussionCloser.afterCloseEdit( response );
		},
		DiscussionCloser.onErrHandler
	);
};

DiscussionCloser.afterCloseEdit = function ( response ) {
	if ( response && response.edit && response.edit.result && response.edit.result === 'Success' ) {
		// Edit succeeded
		alert( 'Edit saved!' );
		$( '#DiscussionCloser-wrapper' ).remove();
		return;
	}
	alert( 'Something might have gone wrong' );
};

DiscussionCloser.getEditSummary = function ( currentContent ) {
	// Either `/* sectionName */ ` or an empty string, for the start of the edit summary
	var maybeMatch = currentContent.match( /^=+\s*(.*?)\s*=/ );
	var sectionName = '';
	if ( maybeMatch !== null ) {
		sectionName = '/* ' + maybeMatch[1] + ' */ ';
	}
	var userInputSummary = $( '#DiscussionCloser-edit-summary' ).val();
	if ( userInputSummary === '' ) {
		// default
		userInputSummary = 'Closing discussion';
	}
	var fullSummary = sectionName + userInputSummary + ' (with DiscussionCloser ' + DiscussionCloser.config.version + ')';
	return fullSummary;
};

DiscussionCloser.getNewSectionText = function ( sectionNum, currentContent ) {
	var closeTemplate = $( 'input[name="DiscussionCloser-radios"]:checked' ).attr( 'value' );
	var closeTemplateInfo = DiscussionCloser.getTemplateInfo( closeTemplate );
	
	var topText = closeTemplateInfo.topText;
	if ( closeTemplateInfo.supportsStatus ) {
		topText = topText.replace(
			'%STATUS%',
			$( '#DiscussionCloser-status-text' ).val()
		);
	}
	topText = topText.replace(
		'%RESULT%',
		$( '#DiscussionCloser-result-text' ).val()
	);
	
	var newText = currentContent.replace(
		/^(=.*?=\n)/,
		'$1\n' + topText + '\n\n'
	);
	newText = newText + '\n\n' + closeTemplateInfo.bottomText;
	// console.log( newText );
	return newText;
};

DiscussionCloser.getSectionContent = function ( sectionNum ) {
	return new Promise( function ( resolve ) {
		new mw.Api().get( {
			action: 'query',
			prop: 'revisions',
			rvprop: 'content',
			rvslots: '*',
			rvsection: sectionNum,
			titles: mw.config.get( 'wgPageName' ),
			format: 'json',
			formatversion: 2
		} ).then(
			function ( response ) {
				console.log( response );
				var currentContent = response.query.pages[0].revisions[0].slots.main.content;
				resolve( currentContent );
			},
			DiscussionCloser.onErrHandler
		);
	} );
};

DiscussionCloser.addInterface = function ( sectionNum, currentContent ) {
	var $header = $( '<h4>' )
		.attr( 'id', 'DiscussionCloser-interface-header' )
		.text( 'DiscussionCloser' );
	var $body = $( '<div>' )
		.attr( 'id', 'DiscussionCloser-interface-body' );
	var $footer = $( '<div>' )
		.attr( 'id', 'DiscussionCloser-interface-footer' );
	var $interface = $( '<div>' )
		.attr( 'id', 'DiscussionCloser-interface' )
		.append(
			$header,
			$( '<hr>' ),
			$body,
			$( '<hr>' ),
			$footer
		);
	var $wrapper = $( '<div>' )
		.attr( 'id', 'DiscussionCloser-wrapper' )
		.append( $interface );
	
	// Body content
	var $templateOptions = $( '<div>' )
		.addClass( 'DiscussionCloser-bottom' )
		.append(
			$( '<label>' ).text( 'Options: ' ),
			$( '<input>' )
				.attr( 'type', 'radio' )
				.attr( 'name', 'DiscussionCloser-radios' )
				.attr( 'value', 'ArchiveTop' )
				.attr( 'checked', 'true' ),
			$( '<label>' ).text( 'Generic' )
		);
	var allTemplates = DiscussionCloser.getTemplateInfo( false );
	Object.keys( allTemplates ).forEach(
		function ( templateName ) {
			if ( templateName !== 'ArchiveTop' ) {
				$templateOptions.append(
					$( '<input>' )
						.attr( 'type', 'radio' )
						.attr( 'name', 'DiscussionCloser-radios' )
						.attr( 'value', templateName ),
					$( '<label>' ).text( allTemplates[templateName].label )
				);
			}
		}
	);
	$templateOptions.children( 'input' ).on( 'change', function () {
		// console.log( 'Selection changed' );
		// console.log( this );
		DiscussionCloser.onTopTemplateChange( $( this ).attr( 'value' ) );
	} );
	
	var $optionalStatus = $( '<div>' )
		.addClass( 'DiscussionCloser-bottom' )
		.append(
			$( '<label>' ).text( 'Status (optional): ' ),
			$( '<input>' )
				.attr( 'id', 'DiscussionCloser-status-text' )
				.attr( 'type', 'text' )
		);
		
	var $closingComment = $( '<div>' )
		.addClass( 'DiscussionCloser-bottom' )
		.append(
			$( '<label>' ).text( 'Discussion result' ),
			$( '<textarea>' )
				.attr( 'id', 'DiscussionCloser-result-text' )
				.attr( 'rows', 15 )
				.css( 'resize', 'none' )
		);
		
	var $editSummary = $( '<div>' )
		.addClass( 'DiscussionCloser-bottom' )
		.append(
			$( '<label>' ).text( 'Edit summary ("Closing discussion" if left blank): ' ),
			$( '<input>' )
				.attr( 'id', 'DiscussionCloser-edit-summary' )
				.attr( 'type', 'text' )
		);
	
	var $hidePreview = $( '<button>' )
		.attr( 'id', 'DiscussionCloser-preview-hide' )
		.text( 'Hide preview' );
	$hidePreview.click( DiscussionCloser.hidePreview );
	
	var $previewDisplay = $( '<div>' )
		.attr( 'id', 'DiscussionCloser-preview-wrapper' )
		.addClass( 'DiscussionCloser-bottom' )
		.append(
			$( '<label>' ).text( 'Preview:' ),
			$( '<div>' ).attr( 'id', 'DiscussionCloser-preview-raw' ),
			$hidePreview
		);
	$previewDisplay.hide();
	
	$body.append( $templateOptions, $optionalStatus, $closingComment, $editSummary, $previewDisplay );
	
	// Footer buttons
	var $submit = $( '<button>' )
		.attr( 'id', 'DiscussionCloser-submit' )
		.text( 'Close discussion' );
	var $preview = $( '<button>' )
		.attr( 'id', 'DiscussionCloser-preview' )
		.text( 'View preview' );
	var $cancel = $( '<button>' )
		.attr( 'id', 'DiscussionCloser-cancel' )
		.text( 'Cancel' );
	$footer.append( $submit, $preview, $cancel );
	
	// console.log( 'currentContent is: ' + currentContent );
	$submit.click( function () {
		// console.log( 'currentContent is: ' + currentContent );
		DiscussionCloser.closeDiscussion( sectionNum, currentContent );
	} );
	$preview.click( function () {
		// console.log( 'currentContent is: ' + currentContent );
		DiscussionCloser.addPreview( sectionNum, currentContent );
	} );
	$cancel.click( function () {
		// Can't just $wrapper.remove() since at this point it isn't added yet
		$( this ).parents( '#DiscussionCloser-wrapper' ).remove();
	} );
	return $wrapper;
};

});

$( document ).ready( function () {
	var mwConfig = mw.config.get( [ 'wgAction', 'wgPageName' ] );
	if ( mwConfig.wgAction !== 'view' ) {
		return;
	}
	if (
		$( '#ca-addsection' ).length > 0 ||
		mwConfig.wgPageName == 'Wikipedia:Administrators\'_noticeboard/Edit_warring' ||
		mwConfig.wgPageName == 'Wikipedia:In_the_news/Candidates'
	) {
		DiscussionCloser.init();
	}
} );

// </nowiki>