User:Fred Gandt/controlSiteWidth.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.
$( document ).ready( () => {
	"use strict";
	const CSW_TOOLBOX_LI = mw.util.addPortletLink( "p-tb", "#", "Control site width", "Control site width and alignment" ),
		MW_HEAD = document.getElementById( "mw-head" ),
		USER_NAME = mw.config.get( "wgUserName" ),
		USER_CSW_CSS_TITLE = `User:${USER_NAME}/controlSiteWidth.css`,
		SCRIPT_TITLE = "User:Fred Gandt/controlSiteWidth.js",
		USER_OPTION_NAME = "userjs-fg-control-site-width",
		cE = element => document.createElement( element ),
		CSW_ALIGN_FIELDSET_LEGEND = cE( "legend" ),
		CSW_ALIGN_FIELDSET = cE( "fieldset" ),
		CSW_COLOR = cE( "input" ),
		CSW_WIDTH = cE( "input" ),
		CSW_SAVE = cE( "input" ),
		CSW_FORM = cE( "form" ),
		BODY = document.body,
		label = ( npt, txt, disp ) => {
			const LABEL = cE( "label" );
			LABEL.textContent = txt;
			LABEL.style.display = disp || "block";
			LABEL.append( npt );
			return LABEL;
		},
		radio = val => {
			const RADIO = cE( "input" );
			RADIO.type = "radio";
			RADIO.name = "align";
			RADIO.value = val[ 1 ];
			RADIO.checked = user_options.align === val[ 1 ];
			RADIO.style.marginRight = ".5em";
			return label( RADIO, `${val[ 0 ]} ` );
		},
		getFormValues = () => ( {
			width: CSW_WIDTH.value,
			color: CSW_COLOR.value,
			align: CSW_FORM.align.value
		} ),
		api = ( data, fnc ) => {
			data.format = "json";
			$.ajax( {
				type: "POST",
				url: "/w/api.php",
				dataType: data.format,
				data: data,
				success: data => fnc( data ),
				error: data => console.error( data ) // TODO: inform the user
			} );
		},
		api_editCSWCSS = () => {
			api( {
				action: "edit",
				title: USER_CSW_CSS_TITLE,
				text: `#mw-panel { background-color: #f6f6f6; width: 11em; left: unset; padding: 0 }
#footer { background-color: #f6f6f6; padding: 1em 1em 3em }
body { background-color: ${user_options.color}; margin: ${user_options.align} }
body, #mw-head { max-width: ${user_options.width}px }
#mw-head { right: unset }`,
				contentformat: "text/css",
				contentmodel: "css",
				notminor: "1",
				recreate: "1",
				summary: `semi-automated edit to establish rules in accordance with user settings derived by [[${SCRIPT_TITLE}]]`,
				token: mw.user.tokens.values.csrfToken
			}, data => console.warn( "TODO: inform the user" ) );
		};
	let user_options = mw.user.options.values[ USER_OPTION_NAME ];
	if ( user_options ) {
		user_options = JSON.parse( user_options );
	} else {
		if ( !confirm( `The script "${SCRIPT_TITLE}" needs to edit your common.css; allow it?` ) ) {
			return;
		}
		user_options = { width: innerWidth, color: "#f6f6f6", align: "center" };
		api( {
			action: "edit",
			title: `User:${USER_NAME}/common.css`,
			appendtext: `
/* Due to the way Cascading Style Sheets work; this import should be maintained at the end of this stylesheet */
@import url( "/w/index.php?title=User:${mw.util.wikiUrlencode( USER_NAME )}/controlSiteWidth.css&action=raw&ctype=text/css" );`,
			contentformat: "text/css",
			contentmodel: "css",
			notminor: "1",
			recreate: "1",
			summary: `semi-automated edit adding import of rules in accordance with user settings derived by [[${SCRIPT_TITLE}]]`,
			token: mw.user.tokens.values.csrfToken
		}, data => {
			api_editCSWCSS();
			console.warn( "TODO: inform the user" );
		} );
	}
	CSW_WIDTH.style.width = "8ch";
	CSW_WIDTH.value = user_options.width;
	CSW_WIDTH.type = "number";
	CSW_ALIGN_FIELDSET_LEGEND.textContent = "Align";
	CSW_ALIGN_FIELDSET.style.borderColor = "#a7d7f9";
	CSW_ALIGN_FIELDSET.style.paddingBottom = ".5em";
	CSW_ALIGN_FIELDSET.style.margin = "0 0 .5em";
	CSW_ALIGN_FIELDSET.append( CSW_ALIGN_FIELDSET_LEGEND, ...[
		[ "Left", "0 auto 0 0" ],
		[ "Center", "0 auto" ],
		[ "Right", "0 0 0 auto" ]
	].map( val => radio( val ) ) );
	CSW_COLOR.value = user_options.color;
	CSW_COLOR.type = "color";
	CSW_SAVE.style.marginLeft = "1em";
	CSW_SAVE.type = "button";
	CSW_SAVE.disabled = true;
	CSW_SAVE.value = "Save";
	CSW_FORM.setAttribute( "style", "display: none; border: 1px solid #a7d7f9; margin-top: .5em; padding: .5em;" );
	CSW_FORM.append( label( CSW_WIDTH, "Width in pixels" ), CSW_ALIGN_FIELDSET, label( CSW_COLOR, "Background color", "inline" ), CSW_SAVE );
	CSW_TOOLBOX_LI.firstElementChild.addEventListener( "click", evt => {
		evt.preventDefault();
		const CSW_FORM_DISPLAYED = CSW_FORM.style.display === "block";
		CSW_FORM.style.display = CSW_FORM_DISPLAYED ? "none" : "block";
	} );
	CSW_SAVE.addEventListener( "click", () => {
		if ( confirm( `Saving these settings will initiate an edit on your user page ${USER_CSW_CSS_TITLE}; continue?` ) ) {
			Object.assign( user_options, getFormValues() );
			CSW_SAVE.disabled = true;
			api( {
				action: "options",
				optionname: USER_OPTION_NAME,
				optionvalue: JSON.stringify( user_options ),
				token: mw.user.tokens.values.csrfToken
			}, data => {
				if ( data.options && data.options === "success" ) {
					CSW_FORM.style.display = "none";
				} else {
					console.warn( "TODO: inform the user" );
				}
			} );
			api_editCSWCSS();
		}
	}, { passive: true } );
	CSW_FORM.addEventListener( "input", evt => {
		const FV = getFormValues();
		CSW_SAVE.disabled = ( FV.width === user_options.width && FV.color === user_options.color && FV.align === user_options.align );
		BODY.style.maxWidth = MW_HEAD.style.maxWidth = `${FV.width}px`;
		BODY.style.backgroundColor = FV.color;
		BODY.style.margin = FV.align;
	} );
	CSW_TOOLBOX_LI.append( CSW_FORM );
} );