Jump to content
Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Gadget-AuthorAssistant.js: Difference between revisions

MediaWiki interface page
Machine-seeded from the topic schema; no approved revision
 
Machine-seeded from the topic schema; no approved revision
Line 7: Line 7:
var title = mw.config.get( 'wgPageName' ).replace( /_/g, ' ' );
var title = mw.config.get( 'wgPageName' ).replace( /_/g, ' ' );
var action = mw.config.get( 'wgAction' );
var action = mw.config.get( 'wgAction' );
if ( ( ns !== 0 && ns !== 6 ) || action !== 'view' || !mw.config.get( 'wgUserName' ) ) {
// On a view page the assistant saves as you; on the source editor it puts the draft into the
// editor so you save with the normal form. In the visual editor, switch to source editing first.
var editing = ( action === 'edit' || action === 'submit' ) && $( '#wpTextbox1' ).length > 0;
if ( ( ns !== 0 && ns !== 6 ) || ( action !== 'view' && !editing ) || !mw.config.get( 'wgUserName' ) ) {
return;
return;
}
}
Line 22: Line 25:
var warn = $( '<div class="okc-warn">' ).hide();
var warn = $( '<div class="okc-warn">' ).hide();
var diff = $( '<div class="okc-diff">' );
var diff = $( '<div class="okc-diff">' );
var saveBtn = $( '<button class="okc-primary">' ).text( 'Save this as my edit' );
var saveBtn = $( '<button class="okc-primary">' ).text( editing ? 'Put this in the editor' : 'Save this as my edit' );
var discardBtn = $( '<button class="okc-secondary" style="margin-left:8px">' ).text( 'Discard' );
var discardBtn = $( '<button class="okc-secondary" style="margin-left:8px">' ).text( 'Discard' );
var proposal = null;
var proposal = null;
Line 51: Line 54:
draftBtn.prop( 'disabled', true );
draftBtn.prop( 'disabled', true );
result.hide();
result.hide();
api.get( { action: 'query', prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', titles: title, formatversion: 2 } ).then( function ( data ) {
var current = editing
var page = data.query.pages[ 0 ];
? $.Deferred().resolve( { wikitext: $( '#wpTextbox1' ).val(), revid: undefined } ).promise()
var rev = page.revisions && page.revisions[ 0 ];
: api.get( { action: 'query', prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', titles: title, formatversion: 2 } ).then( function ( data ) {
var wikitext = rev ? rev.slots.main.content : '';
var page = data.query.pages[ 0 ];
var rev = page.revisions && page.revisions[ 0 ];
return { wikitext: rev ? rev.slots.main.content : '', revid: rev ? rev.revid : undefined };
} );
current.then( function ( cur ) {
var wikitext = cur.wikitext;
var rev = { revid: cur.revid };
return fetch( '/assistant/draft', {
return fetch( '/assistant/draft', {
method: 'POST', credentials: 'same-origin',
method: 'POST', credentials: 'same-origin',
Line 66: Line 75:
explanation.text( d.explanation );
explanation.text( d.explanation );
if ( d.warning ) { warn.text( d.warning ).show(); } else { warn.hide(); }
if ( d.warning ) { warn.text( d.warning ).show(); } else { warn.hide(); }
return api.post( { action: 'compare', fromtitle: title, totext: d.wikitext, toslots: 'main', 'totext-main': d.wikitext, 'tocontentmodel-main': 'wikitext', prop: 'diff', formatversion: 2 } ).then( function ( c ) {
return api.post( { action: 'compare', fromslots: 'main', 'fromtext-main': wikitext, 'fromcontentmodel-main': 'wikitext', toslots: 'main', 'totext-main': d.wikitext, 'tocontentmodel-main': 'wikitext', prop: 'diff', formatversion: 2 } ).then( function ( c ) {
var body = c.compare.body || c.compare[ '*' ] || '';
var body = c.compare.body || c.compare[ '*' ] || '';
diff.html( body ? '<table class="diff"><colgroup><col class="diff-marker"><col class="diff-content"><col class="diff-marker"><col class="diff-content"></colgroup>' + body + '</table>' : '<p>No difference.</p>' );
diff.html( body ? '<table class="diff"><colgroup><col class="diff-marker"><col class="diff-content"><col class="diff-marker"><col class="diff-content"></colgroup>' + body + '</table>' : '<p>No difference.</p>' );
Line 80: Line 89:
saveBtn.on( 'click', function () {
saveBtn.on( 'click', function () {
if ( !proposal ) { return; }
if ( !proposal ) { return; }
if ( editing ) {
$( '#wpTextbox1' ).val( proposal.wikitext );
var summary = $( '#wpSummary' );
if ( summary.length && !summary.val() ) { summary.val( proposal.summary ); }
status.text( 'The draft is in the editor. Review it and save with the normal button; the edit will be yours.' );
result.hide();
root.removeClass( 'okc-is-open' );
return;
}
saveBtn.prop( 'disabled', true );
saveBtn.prop( 'disabled', true );
status.text( 'Saving as ' + mw.config.get( 'wgUserName' ) + '…' );
status.text( 'Saving as ' + mw.config.get( 'wgUserName' ) + '…' );

Revision as of 06:32, 17 September 2026

/* The author's assistant (PRD 5.7, ADR D9).
   Drafts a change to the current page from your instruction and the sources you name, shows the
   diff, and saves only when you click, through your own session under your own name. The service
   at /assistant/ holds no credential; it confirms who you are from this session and drafts. */
( function () {
	var ns = mw.config.get( 'wgNamespaceNumber' );
	var title = mw.config.get( 'wgPageName' ).replace( /_/g, ' ' );
	var action = mw.config.get( 'wgAction' );
	// On a view page the assistant saves as you; on the source editor it puts the draft into the
	// editor so you save with the normal form. In the visual editor, switch to source editing first.
	var editing = ( action === 'edit' || action === 'submit' ) && $( '#wpTextbox1' ).length > 0;
	if ( ( ns !== 0 && ns !== 6 ) || ( action !== 'view' && !editing ) || !mw.config.get( 'wgUserName' ) ) {
		return;
	}
	var api = new mw.Api();
	var root = $( '<div id="okc-assistant">' );
	var panel = $( '<div class="okc-panel">' );
	var openBtn = $( '<button class="okc-open">' ).text( 'Author’s assistant' );
	var instruction = $( '<textarea rows="4" placeholder="What should change on this page? For example: add these two sources under the Sources heading, or fill in the population from the source below.">' );
	var urls = $( '<input type="text" placeholder="Optional: web addresses to draw on, separated by spaces">' );
	var draftBtn = $( '<button class="okc-primary">' ).text( 'Draft the change' );
	var status = $( '<p>' );
	var result = $( '<div>' ).hide();
	var explanation = $( '<p>' );
	var warn = $( '<div class="okc-warn">' ).hide();
	var diff = $( '<div class="okc-diff">' );
	var saveBtn = $( '<button class="okc-primary">' ).text( editing ? 'Put this in the editor' : 'Save this as my edit' );
	var discardBtn = $( '<button class="okc-secondary" style="margin-left:8px">' ).text( 'Discard' );
	var proposal = null;

	panel.append(
		$( '<h3>' ).text( 'Author’s assistant' ),
		$( '<p>' ).text( 'Tell it what to change. You will see the diff before anything is saved, and the edit will be yours: your name, your responsibility, still needing a reviewer’s approval.' ),
		instruction, urls, draftBtn, status,
		result.append( explanation, warn, diff, saveBtn, discardBtn,
			$( '<p class="okc-note">' ).text( 'The edit summary will say the assistant helped and name any sources used.' ) )
	);
	root.append( panel, openBtn );
	$( 'body' ).append( root );

	openBtn.on( 'click', function () {
		root.toggleClass( 'okc-is-open' );
		if ( root.hasClass( 'okc-is-open' ) ) {
			fetch( '/assistant/whoami', { credentials: 'same-origin' } ).then( function ( r ) {
				if ( !r.ok ) { status.text( r.status === 404 ? 'The assistant is switched off on this instance.' : 'Please log in to use the assistant.' ); draftBtn.prop( 'disabled', true ); }
			} );
		}
	} );

	draftBtn.on( 'click', function () {
		var text = instruction.val().trim();
		if ( !text ) { status.text( 'Say what should change.' ); return; }
		status.text( 'Reading the page and drafting…' );
		draftBtn.prop( 'disabled', true );
		result.hide();
		var current = editing
			? $.Deferred().resolve( { wikitext: $( '#wpTextbox1' ).val(), revid: undefined } ).promise()
			: api.get( { action: 'query', prop: 'revisions', rvprop: 'content|ids', rvslots: 'main', titles: title, formatversion: 2 } ).then( function ( data ) {
				var page = data.query.pages[ 0 ];
				var rev = page.revisions && page.revisions[ 0 ];
				return { wikitext: rev ? rev.slots.main.content : '', revid: rev ? rev.revid : undefined };
			} );
		current.then( function ( cur ) {
			var wikitext = cur.wikitext;
			var rev = { revid: cur.revid };
			return fetch( '/assistant/draft', {
				method: 'POST', credentials: 'same-origin',
				headers: { 'content-type': 'application/json' },
				body: JSON.stringify( { title: title, wikitext: wikitext, instruction: text, urls: urls.val().split( /\s+/ ).filter( Boolean ) } )
			} ).then( function ( r ) {
				if ( !r.ok ) { return r.json().then( function ( e ) { throw new Error( e.detail || r.status ); } ); }
				return r.json();
			} ).then( function ( d ) {
				proposal = { wikitext: d.wikitext, summary: d.summary, baserevid: rev ? rev.revid : undefined };
				explanation.text( d.explanation );
				if ( d.warning ) { warn.text( d.warning ).show(); } else { warn.hide(); }
				return api.post( { action: 'compare', fromslots: 'main', 'fromtext-main': wikitext, 'fromcontentmodel-main': 'wikitext', toslots: 'main', 'totext-main': d.wikitext, 'tocontentmodel-main': 'wikitext', prop: 'diff', formatversion: 2 } ).then( function ( c ) {
					var body = c.compare.body || c.compare[ '*' ] || '';
					diff.html( body ? '<table class="diff"><colgroup><col class="diff-marker"><col class="diff-content"><col class="diff-marker"><col class="diff-content"></colgroup>' + body + '</table>' : '<p>No difference.</p>' );
					status.text( d.sources.length ? 'Sources used: ' + d.sources.join( ', ' ) : 'Drafted.' );
					result.show();
				} );
			} );
		} ).catch( function ( e ) {
			status.text( 'The assistant could not draft that: ' + ( e && e.message ? e.message : e ) );
		} ).always( function () { draftBtn.prop( 'disabled', false ); } );
	} );

	saveBtn.on( 'click', function () {
		if ( !proposal ) { return; }
		if ( editing ) {
			$( '#wpTextbox1' ).val( proposal.wikitext );
			var summary = $( '#wpSummary' );
			if ( summary.length && !summary.val() ) { summary.val( proposal.summary ); }
			status.text( 'The draft is in the editor. Review it and save with the normal button; the edit will be yours.' );
			result.hide();
			root.removeClass( 'okc-is-open' );
			return;
		}
		saveBtn.prop( 'disabled', true );
		status.text( 'Saving as ' + mw.config.get( 'wgUserName' ) + '…' );
		api.postWithToken( 'csrf', { action: 'edit', title: title, text: proposal.wikitext, summary: proposal.summary, baserevid: proposal.baserevid, formatversion: 2 } ).then( function () {
			location.reload();
		}, function ( code, e ) {
			status.text( 'Save failed: ' + ( e && e.error ? e.error.info : code ) );
			saveBtn.prop( 'disabled', false );
		} );
	} );

	discardBtn.on( 'click', function () { proposal = null; result.hide(); status.text( 'Discarded. Nothing was saved.' ); } );
}() );