MediaWiki:Common.js: Difference between revisions

From Criminal Defense Wiki
Jump to navigationJump to search
No edit summary
No edit summary
 
(One intermediate revision by the same user not shown)
Line 117: Line 117:


document.addEventListener( 'click', function ( e ) {
document.addEventListener( 'click', function ( e ) {
if ( !mw.config.get( 'wgUserName' ) ) {
return; // anonymous visitors get no popover
}
if ( e.target.closest( '.claim-flag-popover' ) ) {
if ( e.target.closest( '.claim-flag-popover' ) ) {
return; // clicks inside the popover do nothing
return; // clicks inside the popover do nothing
Line 331: Line 334:
} );
} );
} );
} );
}() );
/* Mark logged-in users so highlights can be shown only to them. */
( function () {
if ( mw.config.get( 'wgUserName' ) ) {
document.documentElement.classList.add( 'flagger-user' );
}
}() );
}() );

Latest revision as of 01:56, 30 August 2026

/* Any JavaScript here will be loaded for all users on every page load. */


/* For wikiDefense Flagger. For more information consult documentation or contact Brandyn Lu */

/* Claim-flag popover: click a highlight to see the model's reasoning. */
( function () {
	var POPOVER_ID = 'claim-flag-popover';

	function closePopover() {
		var existing = document.getElementById( POPOVER_ID );
		if ( existing ) {
			existing.remove();
		}
		document.querySelectorAll( '.review-highlight.is-open' ).forEach( function ( el ) {
			el.classList.remove( 'is-open' );
		} );
	}

		function escapeRegExp( str ) {
		return str.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
	}

	function dismissFlag( span ) {
		var sentence = span.childNodes[ 0 ] ? span.childNodes[ 0 ].textContent : '';
		if ( !sentence ) {
			mw.notify( 'Could not identify the flagged text.', { type: 'error' } );
			return;
		}

		var title = mw.config.get( 'wgPageName' );
		var api = new mw.Api();

		api.get( {
			action: 'query',
			prop: 'revisions',
			rvprop: 'content',
			rvslots: 'main',
			titles: title,
			formatversion: 2
		} ).done( function ( data ) {
			var page = data.query.pages[ 0 ];
			if ( !page || page.missing ) {
				mw.notify( 'Could not load page content.', { type: 'error' } );
				return;
			}

			var wikitext = page.revisions[ 0 ].slots.main.content;

			// Find the {{Highlight|...}} call whose first parameter is this sentence.
			var pattern = new RegExp(
				'\\{\\{Highlight\\|' + escapeRegExp( sentence ) + '(?:\\|[^}]*)?\\}\\}'
			);

			if ( !pattern.test( wikitext ) ) {
				mw.notify( 'Could not find this flag in the page source.', { type: 'error' } );
				return;
			}

			var cleaned = wikitext.replace( pattern, sentence );

			api.postWithToken( 'csrf', {
				action: 'edit',
				title: title,
				text: cleaned,
				summary: 'Dismissed one claim flag',
				formatversion: 2
			} ).done( function () {
				mw.notify( 'Flag dismissed. Reloading…' );
				window.location.reload();
			} ).fail( function ( code, result ) {
				mw.notify( 'Could not save: ' + code, { type: 'error' } );
				console.error( 'Dismiss failed:', code, result );
			} );

		} ).fail( function ( code ) {
			mw.notify( 'Could not fetch page: ' + code, { type: 'error' } );
		} );
	}
	function openPopover( span ) {
		closePopover();

		var reason = span.getAttribute( 'data-reason' ) || 'No explanation available.';

		var popover = document.createElement( 'div' );
		popover.id = POPOVER_ID;
		popover.className = 'claim-flag-popover';

		var close = document.createElement( 'button' );
		close.className = 'claim-flag-close';
		close.textContent = '×';
		close.title = 'Close';
		close.addEventListener( 'click', function ( e ) {
			e.stopPropagation();
			closePopover();
		} );

		var body = document.createElement( 'div' );
		body.className = 'claim-flag-body';
		body.textContent = reason;

		var dismiss = document.createElement( 'button' );
		dismiss.className = 'claim-flag-dismiss';
		dismiss.textContent = 'Dismiss this flag';
		dismiss.addEventListener( 'click', function ( e ) {
			e.stopPropagation();
			dismissFlag( span );
		} );

		popover.appendChild( close );
		popover.appendChild( body );
		popover.appendChild( dismiss );

		span.classList.add( 'is-open' );
		span.appendChild( popover );
	}

	document.addEventListener( 'click', function ( e ) {
		if ( !mw.config.get( 'wgUserName' ) ) {
			return; // anonymous visitors get no popover
		}
		if ( e.target.closest( '.claim-flag-popover' ) ) {
			return; // clicks inside the popover do nothing
		}

		var span = e.target.closest( '.review-highlight' );
		if ( span ) {
			if ( span.classList.contains( 'is-open' ) ) {
				closePopover();
			} else {
				openPopover( span );
			}
		} else {
			closePopover();
		}
	} );

	document.addEventListener( 'keydown', function ( e ) {
		if ( e.key === 'Escape' ) {
			closePopover();
		}
	} );
}() );


/* Sidebar tool: remove all claim flags from the current page. */
( function () {
	// Matches {{Highlight|<text>}} or {{Highlight|<text>|<reason>}},
	// capturing only the first parameter (the original sentence).
	var HIGHLIGHT_RE = /\{\{Highlight\|([\s\S]*?)(?:\|[^}]*)?\}\}/g;

	function resetHighlights() {
		var title = mw.config.get( 'wgPageName' );
		var api = new mw.Api();

		api.get( {
			action: 'query',
			prop: 'revisions',
			rvprop: 'content',
			rvslots: 'main',
			titles: title,
			formatversion: 2
		} ).done( function ( data ) {
			var page = data.query.pages[ 0 ];
			if ( !page || page.missing ) {
				mw.notify( 'Could not load page content.', { type: 'error' } );
				return;
			}

			var wikitext = page.revisions[ 0 ].slots.main.content;
			var matches = wikitext.match( HIGHLIGHT_RE );
			var count = matches ? matches.length : 0;

			if ( count === 0 ) {
				mw.notify( 'No highlights found on this page.' );
				return;
			}

			if ( !window.confirm(
				'Remove all ' + count + ' claim flag(s) from this page?\n\n' +
				'This edits the page. It can be undone from the page history.'
			) ) {
				return;
			}

			var cleaned = wikitext.replace( HIGHLIGHT_RE, '$1' );

			api.postWithToken( 'csrf', {
				action: 'edit',
				title: title,
				text: cleaned,
				summary: 'Removed ' + count + ' claim-flag highlight(s) via sidebar tool',
				formatversion: 2
			} ).done( function () {
				mw.notify( 'Removed ' + count + ' highlight(s). Reloading…' );
				window.location.reload();
			} ).fail( function ( code, result ) {
				mw.notify( 'Edit failed: ' + code, { type: 'error' } );
				console.error( 'Reset highlights failed:', code, result );
			} );

		} ).fail( function ( code ) {
			mw.notify( 'Could not fetch page: ' + code, { type: 'error' } );
		} );
	}

	mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () {
		if ( mw.config.get( 'wgNamespaceNumber' ) !== 0 ) {
			return; // article namespace only
		}
		if ( !mw.config.get( 'wgUserName' ) ) {
			return; // logged-in users only
		}

		var portletIds = [ 'p-cactions', 'p-tb', 'p-views' ];
		var link = null;

		for ( var i = 0; i < portletIds.length && !link; i++ ) {
			link = mw.util.addPortletLink(
				portletIds[ i ],
				'#',
				'Reset all highlights',
				'ca-reset-highlights',
				'Remove all claim flags from this page'
			);
		}

		if ( link ) {
			link.addEventListener( 'click', function ( e ) {
				e.preventDefault();
				resetHighlights();
			} );
		} else {
			console.warn( 'Reset highlights: no suitable portlet found.' );
		}
	} );
}() );

/* Sysop-only global toggle for claim-flag highlighting. */
( function () {
	var TEMPLATE_PAGE = 'Template:Highlight';
	var TEMPLATE_ON = '<includeonly><span class="review-highlight" ' +
		'data-reason="{{{2|}}}">{{{1}}}</span></includeonly>' +
		'<noinclude>Usage: {{Highlight|text|reason}} — highlighting is ON.</noinclude>';
	var TEMPLATE_OFF = '<includeonly>{{{1}}}</includeonly>' +
		'<noinclude>Usage: {{Highlight|text|reason}} — highlighting is OFF.</noinclude>';

	function purgeTransclusions( api ) {
		return api.post( {
			action: 'purge',
			generator: 'transcludedin',
			titles: TEMPLATE_PAGE,
			gtilimit: 500,
			gtinamespace: 0,
			forcelinkupdate: 1,
			formatversion: 2
		} );
	}

	function setState( enable ) {
		var verb = enable ? 'enable' : 'disable';
		if ( !window.confirm(
			'This will ' + verb + ' claim-flag highlighting for the entire wiki.\n\nContinue?'
		) ) {
			return;
		}
		var api = new mw.Api();
		api.postWithToken( 'csrf', {
			action: 'edit',
			title: TEMPLATE_PAGE,
			text: enable ? TEMPLATE_ON : TEMPLATE_OFF,
			summary: 'Claim-flag highlighting ' + ( enable ? 'enabled' : 'disabled' ),
			formatversion: 2
		} ).done( function () {
			mw.notify( 'Template updated. Refreshing affected pages…' );
			purgeTransclusions( api ).always( function () {
				window.location.reload();
			} );
		} ).fail( function ( code, result ) {
			mw.notify( 'Could not update template: ' + code, { type: 'error' } );
			console.error( 'Toggle failed:', code, result );
		} );
	}

	function insertToggle( isOn ) {
		var portletIds = [ 'p-cactions', 'p-tb', 'p-views' ];
		var link = null;

		for ( var i = 0; i < portletIds.length && !link; i++ ) {
			link = mw.util.addPortletLink(
				portletIds[ i ],
				'#',
				isOn ? 'Flags: ON' : 'Flags: OFF',
				'ca-flagger-toggle',
				'Claim-flag highlighting is ' + ( isOn ? 'ON' : 'OFF' ) +
					' wiki-wide. Click to ' + ( isOn ? 'disable' : 'enable' ) + '.'
			);
		}

		if ( !link ) {
			console.warn( 'Flagger toggle: no suitable portlet found.' );
			return;
		}

		link.addEventListener( 'click', function ( e ) {
			e.preventDefault();
			setState( !isOn );
		} );
	}

	mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( function () {
		if ( mw.config.get( 'wgUserGroups' ).indexOf( 'sysop' ) === -1 ) {
			return; // admins only
		}

		new mw.Api().get( {
			action: 'query',
			prop: 'revisions',
			rvprop: 'content',
			rvslots: 'main',
			titles: TEMPLATE_PAGE,
			formatversion: 2
		} ).done( function ( data ) {
			var page = data.query.pages[ 0 ];
			if ( !page || page.missing ) {
				console.warn( 'Flagger toggle: template page not found.' );
				return;
			}
			var content = page.revisions[ 0 ].slots.main.content;
			var isOn = content.indexOf( 'review-highlight' ) !== -1;
			insertToggle( isOn );
		} ).fail( function () {
			console.warn( 'Flagger toggle: could not read template.' );
		} );
	} );
}() );


/* Mark logged-in users so highlights can be shown only to them. */
( function () {
	if ( mw.config.get( 'wgUserName' ) ) {
		document.documentElement.classList.add( 'flagger-user' );
	}
}() );