User:DannyS712/EFFPRH/sandbox.js

In this article, we will delve into the fascinating world of User:DannyS712/EFFPRH/sandbox.js, exploring its multiple facets and meanings. _Var1 has been the object of interest and debate throughout history, awakening the curiosity and study of experts in various disciplines. From its impact on contemporary society to its ancestral origins, User:DannyS712/EFFPRH/sandbox.js has become a topic of unquestionable relevance. Through a detailed analysis, we will analyze the different perspectives that exist around User:DannyS712/EFFPRH/sandbox.js, seeking to shed light on its true meaning and its influence in today's world.
// <nowiki>
// Script to respond to edit filter false positive reports
// @author DannyS712
$(() => {
const EFFPRH = {};
window.EFFPRH = EFFPRH;

EFFPRH.config = {
	debug: false,
	version: '0-dev'
};
EFFPRH.editSummary = 'Respond to false positive report via ]'
	+ ' (v ' + EFFPRH.config.version + ')';

EFFPRH.init = function () {
	mw.loader.using(
		,
		EFFPRH.run
	);
};

EFFPRH.run = function () {
	EFFPRH.addStyle();
	// Add links to each section to open a dialog
	$('span.mw-headline').each( function () {
		const $editSectionLinks = $( this ).parent().find( '.mw-editsection' );
		if ( $editSectionLinks.length === 0 ) {
			// Missing links span, nothing to do
			return;
		}
		const sectionNum = EFFPRH.getHeadingSectionNum( $editSectionLinks );
		if ( sectionNum === -1 ) {
			// Missing link, no idea what section this is
			return;
		}
		// Add a hidden div after the headline that will be where the Vue
		// display goes
		$( this ).parent().after(
			$( '<div>' ).attr( 'id', 'script-EFFPRH-' + sectionNum )
		);
		const reporterName = $( this ).text();
		EFFPRH.addHandlerLink( $editSectionLinks, reporterName, sectionNum );
	} );
};

/**
 * Add styles for our interface.
 */
EFFPRH.addStyle = function () {
	mw.util.addCSS(`
		.script-EFFPRH-handler {
			background-color: #e0e0e0;
			border: 1px solid black;
			margin: 10px 0 10px 0;
		}
		/* Override normal rules for indenting lists */
		.cdx-menu ul {
			margin-left: 0px;
		}
		/* Separate the dropdown and input */
		.cdx-menu {
			margin-bottom: 10px;
		}
		/* Reduce vertical space in the dropdown options */
		.cdx-menu-item__content {
			line-height: 1em;
		}
		/* Center form elements and labels */
		.script-EFFPRH-handler td {
			vertical-align: middle;
		}
		/* Don't use the grey background in the preview */
		.script-EFFPRH-preview {
			background-color: white;
		}
	`);
};

/**
 * Get the section number for a response, given the jQuery element for the
 * <span> with the edit section link. Returns -1 on failure.
 */
EFFPRH.getHeadingSectionNum = function ( $editSectionLinks ) {
	const editSectionUrl = $editSectionLinks.find( 'a:first' ).attr( 'href' );
	if ( editSectionUrl === undefined ) {
		return -1;
	}
	const sectionMatch = editSectionUrl.match( /&section=(\d+)(?:$|&)/ );
	if ( sectionMatch === null ) {
		return -1;
	}
	return parseInt( sectionMatch );
};

/**
 * Add a link next to the edit section link that will launch the report handler.
 */
EFFPRH.addHandlerLink = function ( $editSectionLinks, reporterName, sectionNum ) {
	const $handlerLink = $( '<a>' )
		.attr( 'id', 'script-EFFPRH-launch-' + sectionNum )
		.text( 'Review report' );
	$handlerLink.click(
		function () {
			// Only allow running once per link (until the Vue handler is removed)
			if ( $( this ).hasClass( 'script-EFFPRH-disabled' ) ) {
				return;
			}
			$( this ).addClass( 'script-EFFPRH-disabled' );
			EFFPRH.showHandler( reporterName, sectionNum );
		}
	);
	// Add before the closing ] of the links
	$editSectionLinks.children().last().before(
		' | ',
		$handlerLink
	);
};

// Handler options, see {{EFFP}}
EFFPRH.responseOptions = [
	{ value: 'none', label: 'None' },
	{ value: 'done', label: 'Done (no change to filter)' },
	{ value: 'defm', label: 'Done (may need a change to filter)' },
	{ value: 'notdone', label: 'Not Done (filter working properly)' },
	{ value: 'ndefm', label: 'Not Done (may need a change to filter)' },
	{ value: 'redlink', label: 'Not Done (notable people)' },
	{ value: 'alreadydone', label: 'Already Done' },
	{ value: 'denied', label: 'Decline (edits are vandalism)' },
	{ value: 'checking', label: 'Checking' },
	{ value: 'blocked', label: 'User blocked' },
	{ value: 'talk', label: 'Request on article talk page' },
	{ value: 'fixed', label: 'Fixed filter' },
	{ value: 'question', label: 'Question' },
	{ value: 'note', label: 'Note' },
	{ value: 'private', label: 'Private filter' },
	{ value: 'pin', label: 'Pin' },
	{ value: 'moot', label: 'Moot (filter working properly)' },
	{ value: 'mootefm', label: 'Moot (may need a change to filter)' }
];

/**
 * Actually show the handler for a given reporter name and section number.
 */
EFFPRH.showHandler = function ( reporterName, sectionNum ) {
	const targetDivId = 'script-EFFPRH-' + sectionNum;
	// Need a reference so that it can be unmounted
	let vueAppInstance;
	// We shouldn't use the mw.loader access directly, but I'm not
	// pasing around the `require` function everywhere
	const cdx = mw.loader.require( '@wikimedia/codex' );
	// Extra component to render wikitext preview
	const previewRenderer = EFFPRH.getPreviewComponent();
	const handlerApp = {
		components: {
			CdxButton: cdx.CdxButton,
			CdxSelect: cdx.CdxSelect,
			CdxTextInput: cdx.CdxTextInput,
			CdxToggleButton: cdx.CdxToggleButton,
			previewRenderer: previewRenderer
		},
		data: function () {
			return {
				reporterName: reporterName,
				sectionNum: sectionNum,
				responseOptions: EFFPRH.responseOptions,
				selectedResponse: 'none',
				commentValue: '',

				// Debug information of the state
				showDebug: EFFPRH.config.debug,

				// Preview
				showPreview: false,

				// Overall state
				haveSubmitted: false,
				editMade: false,
				editError: false
			};
		},
		computed: {
			canSubmit: function () {
				return !this.haveSubmitted && this.selectedResponse !== 'none';
			},
			previewToggleLabel: function () {
				return ( this.showPreview ? 'Hide preview' : 'Show preview' );
			},
			responseWikiText: function () {
				// Computed here so that we can use it for the api preview,
				// does not include the leading newline
				let responseText = ': {{EFFP|' + this.selectedResponse + '}}';
				if ( this.commentValue ) {
					responseText += ' ' + this.commentValue;
				}
				responseText += ' --~~~~';
				return responseText;
			}
		},
		methods: {
			reloadPage: function () {
				// Needs to be a function instead of using href so that we
				// can force the page to reload
				location.assign(
					mw.util.getUrl( mw.config.get( 'wgPageName' ) + '#' + this.reporterName )
				);
				location.reload();
			},
			submitHandler: function () {
				this.haveSubmitted = true;
				EFFPRH.respondToReport(
					this.reporterName,
					this.sectionNum,
					this.responseWikiText
				).then(
					// arrow functions to simplify `this`
					() => this.editMade = true,
					() => this.editError = true
				);
			},
			cancelHandler: function () {
				if ( vueAppInstance === undefined ) {
					console.log( 'Cannot unmount, no vueAppInstance' );
				} else {
					vueAppInstance.unmount();
					// Restore link
					$( '#script-EFFPRH-launch-' + sectionNum ).removeClass(
						'script-EFFPRH-disabled'
					);
				}
			}
		},
		template: `
<div class="script-EFFPRH-handler">
<p>Responding to report by {{ reporterName }}.</p>
<p v-if="showDebug">Section {{ sectionNum }}, selected response: {{ selectedResponse }}, comment: {{ commentValue }}.</p>
<!-- Table so that we can align the labels and fields -->
<table><tbody>
<tr>
	<td><span>Action:</span></td>
	<td><cdx-select v-model:selected="selectedResponse" :menu-items="responseOptions" default-label="Response to report" :disabled="haveSubmitted" /></td>
</tr>
<tr>
	<td><span>Comment:</span></td>
	<td><cdx-text-input v-model="commentValue" :disabled="haveSubmitted" /></td>
</tr>
</tbody></table>
<br />
<ul v-show="haveSubmitted">
<li>Submitting...</li>
<li v-show="editMade">Success! <a v-on:click="reloadPage"><strong>Reload the page</strong></a></li>
<li v-show="editError">Uh-oh, something went wrong. Please check the console for details.</li>
</ul>
<cdx-button weight="primary" action="progressive" :disabled="!canSubmit" v-on:click="submitHandler">Submit</cdx-button>
<cdx-button weight="primary" action="destructive" :disabled="haveSubmitted" v-on:click="cancelHandler">Cancel</cdx-button>
<cdx-toggle-button v-model="showPreview" :disabled="!canSubmit">{{ previewToggleLabel }}</cdx-toggle-button>
<!-- v-if so that we don't call the api to parse and render a preview when its not needed, do not render with no response template chosen -->
<preview-renderer v-if="showPreview && canSubmit" :wikitext="responseWikiText"></preview-renderer>
</div>`
	};
	vueAppInstance = Vue.createMwApp( handlerApp );
	vueAppInstance.mount( '#' + targetDivId );
};

/**
 * Extra component: preview of wikitext being added.
 */
EFFPRH.getPreviewComponent = function () {
	return {
		props: {
			wikitext: { type: String, default: '' }
		},
		data: function () {
			return {
				previewHtml: '',
				haveHtml: false
			};
		},
		methods: {
			// Separate from the watcher so that can be called on mounted too
			loadPreview: function ( wikitextToPreview ) {
				new mw.Api().get( {
					action: 'parse',
					formatversion: 2,
					title: mw.config.get( 'wgPageName' ),
					text: wikitextToPreview,
					prop: 'text|wikitext',
					pst: true,
					disablelimitreport: true,
					disableeditsection: true,
					sectionpreview: true
				} ).then(
					( res ) => {
						console.log( res );
						if ( res
							&& res.parse
							&& res.parse.wikitext === this.wikitext
							&& res.parse.text
						) {
							this.previewHtml = res.parse.text;
							this.haveHtml = true;
						}
					}
				);
			}
		},
		watch: {
			wikitext: function ( newValue ) {
				// Reset when the wikitext to preview changes
				this.previewHtml = '';
				this.haveHtml = false;
				this.loadPreview( newValue );
			}
		},
		mounted: function () {
			// Preview starting wikitext
			this.loadPreview( this.wikitext );
		},
		template: `
<div class="script-EFFPRH-preview">
<hr>
<div v-if="haveHtml" v-html="previewHtml"></div>
<div v-else>Loading preview of {{ wikitext }}</div>
</div>`
	};
};

/**
 * Actually make the page edit to respond to the report. Returns a promise
 * for the edit succeeding or not.
 */
EFFPRH.respondToReport = function (
	reporterName,
	sectionNum,
	responseWikiText
) {
	return new Promise( function ( resolve, reject ) {
		// wikitext is computed in Vue app so that it can have a preview too,
		// we just need to add the leading newline
		const wikitextToAdd = '\n' + responseWikiText;
		const editParams = {
			action: 'edit',
			title: mw.config.get( 'wgPageName' ),
			section: sectionNum,
			summary: '/* ' + reporterName + ' */ ' + EFFPRH.editSummary,
			notminor: true,
			baserevid: mw.config.get( 'wgCurRevisionId' ),
			nocreate: true,
			appendtext: wikitextToAdd,
			assert: 'user',
			assertuser: mw.config.get( 'wgUserName' )
		};
		if ( EFFPRH.config.debug ) {
			console.log( { ...editParams } );
		}
		new mw.Api().postWithEditToken( editParams )
			.then(
				( res ) => { console.log( res ); resolve(); },
				( err ) => { console.log( err ); reject(); }
			);
	} );
};

});

$( document ).ready( () => {
	if (
		mw.config.get( 'wgPageName' ) === 'Wikipedia:Edit_filter/False_positives/Reports'
		|| mw.config.get( 'wgPageName' ) === 'User:DannyS712/EFFPRH/sandbox'
	) {
		window.EFFPRH.init();
	}
});

// </nowiki>