close
Jump to content

User:Feeglgeef/wikilambda editsource.js

From Wikifunctions

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.
/*
Install by going to [[Special:mypage/common.js]] and add the following line:
mw.loader.load( '//www.wikifunctions.org/w/index.php?title=User:Feeglgeef/wikilambda_editsource.js&action=raw&ctype=text/javascript' );
*/
/*
Based off of original copy made by [[user:מקף]] at [[user:מקף/wikilambda_editsource.js]].
*/
( function () {
	'use strict';

	const LINK_LABEL = 'Edit Raw JSON';
	const LINK_TOOLTIP = 'Edit this ZObject as raw JSON';
	const DEFAULT_SUMMARY = 'edit via [[User:Feeglgeef/wikilambda_editsource.js|raw-JSON edit tool]]';

	const zid = window.location.href.match( /\/(Z\d+)\b/ )?.[ 1 ];

	function fetchJson( z ) {
		const url = mw.util.getUrl( z, { action: 'raw' } );
		return fetch( url ).then( function ( r ) {
			if ( !r.ok ) {
				throw new Error( 'HTTP ' + r.status + ' fetching ' + z );
			}
			return r.text();
		} );
	}

	function save( z, newJson, summary ) {
		const api = new mw.Api();
		return api.post( {
			action: 'wikilambda_edit',
			format: 'json',
			assert: 'user',
			summary: summary || DEFAULT_SUMMARY,
			zid: z,
			zobject: newJson,
			token: mw.user.tokens.get( 'csrfToken' )
		} );
	}

	function describeError( code, data ) {
		// mw.Api rejects its Deferred as (code, data). `code` is a short
		// string like 'http' or 'ratelimited'; `data` usually carries the
		// detailed API error object. Either may be missing.
		if ( data && data.error && data.error.info ) {
			return ( data.error.code || code ) + ': ' + data.error.info;
		}
		if ( data && data.exception ) {
			return code + ': ' + data.exception;
		}
		if ( typeof code === 'string' ) {
			return code;
		}
		try {
			return JSON.stringify( code );
		} catch ( _ ) {
			return String( code );
		}
	}

	function openEditor( z, content ) {
		const editor = $(
			'<div class="ext-wikilambda-widget-base" id="wikilambda_editsource" ' +
			'style="background-color: ghostwhite; max-width: none; padding: 0.5em;"></div>'
		);

		const textarea = $( '<textarea></textarea>' )
			.css( {
				width: '100%',
				height: '350px',
				'min-height': '200px',
				resize: 'vertical',
				direction: 'ltr',
				'font-family': 'monospace'
			} )
			.val( content );

		const summaryInput = $( '<input>' )
			.attr( {
				type: 'text',
				placeholder: 'Summary (default: "' + DEFAULT_SUMMARY + '")'
			} )
			.css( {
				width: '75%',
				height: '30px',
				'margin-top': '0.25em'
			} );

		const status = $( '<span></span>' )
			.css( { 'margin-right': '0.5em', color: '#555' } );

		const saveBtn = $( '<button>Save</button>' )
			.addClass(
				'cdx-button cdx-button--action-progressive ' +
				'cdx-button--weight-primary cdx-button--size-medium cdx-button--framed'
			)
			.css( { float: 'inline-end', margin: '0 0 0 0.25em' } );

		const closeBtn = $( '<button>Close</button>' )
			.addClass(
				'cdx-button cdx-button--action-default ' +
				'cdx-button--weight-primary cdx-button--size-medium cdx-button--framed'
			)
			.css( { float: 'inline-end', margin: '0' } );

		saveBtn.on( 'click', function () {
			const value = textarea.val();
			if ( !value ) {
				mw.notify( 'Please fill the source', { type: 'warn' } );
				return;
			}
			if ( value === content ) {
				mw.notify( 'No changes detected', { type: 'warn' } );
				return;
			}
			try {
				JSON.parse( value );
			} catch ( e ) {
				mw.notify( 'Invalid JSON: ' + e.message, {
					type: 'error',
					autoHide: false
				} );
				return;
			}

			saveBtn.prop( 'disabled', true );
			closeBtn.prop( 'disabled', true );
			status.text( 'Saving…' );

			save( z, value, summaryInput.val() ).then(
				function () {
					editor.remove();
					mw.notify(
						$( '<a>' )
							.append( $( '<strong>' ).text( 'Saved — click to reload' ) )
							.on( 'click', function () {
								window.location.assign(
									window.location.href.replace(
										/#wikilambda_editsource$/, ''
									)
								);
							} ),
						{ autoHide: false }
					);
				},
				function ( code, data ) {
					saveBtn.prop( 'disabled', false );
					closeBtn.prop( 'disabled', false );
					status.text( '' );
					// Log the raw failure so the user can inspect via devtools.
					mw.log.error( 'wikilambda_edit failed', code, data );
					mw.notify(
						$( '<strong>' ).text( 'Save failed: ' + describeError( code, data ) ),
						{ type: 'error', autoHide: false }
					);
				}
			);
		} );

		closeBtn.on( 'click', function () {
			editor.remove();
		} );

		const buttonBar = $( '<div></div>' )
			.css( { 'margin-top': '0.5em', overflow: 'auto' } )
			.append( saveBtn, closeBtn, status );

		editor.append( textarea, summaryInput, buttonBar );
		$( '#bodyContent' ).prepend( editor );
	}

	function openForCurrentZid() {
		$( '#wikilambda_editsource' ).remove();
		if ( !zid ) {
			return;
		}
		fetchJson( zid ).then(
			function ( body ) {
				openEditor( zid, body );
			},
			function ( err ) {
				mw.notify(
					$( '<strong>' ).text( 'Fetch failed: ' + ( err && err.message ? err.message : err ) ),
					{ type: 'error', autoHide: false }
				);
			}
		);
	}

	$.when(
		mw.loader.using( [ 'mediawiki.util', 'mediawiki.api' ], $.ready )
	).then( function () {
		const contentModel = mw.config.get( 'wgPageContentModel' );
		const eligible =
			contentModel === 'wikilambda' ||
			contentModel === 'Wikibase Item' ||
			!!zid;
		if ( !eligible ) {
			return;
		}

		const node = mw.util.addPortletLink(
			'p-views',
			'#wikilambda_editsource',
			LINK_LABEL,
			'',
			LINK_TOOLTIP,
			'r'
		);
		$( node ).on( 'click', function ( e ) {
			e.preventDefault();
			openForCurrentZid();
		} );
	} );
}() );