User:Nardog/CodeEditorAssist.js

Source: Wikipedia, the free encyclopedia.
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.
mw.loader.using([
	'user.options', 'ext.visualEditor.desktopArticleTarget.init'
], function codeEditorAssist() {
	let isEdit = mw.config.exists('wgCodeEditorCurrentLanguage') &&
		['edit', 'submit'].includes(mw.config.get('wgAction'));
	let veAvailable = mw.libs.ve.isVisualAvailable;
	let isAf = mw.config.exists('aceConfig');
	if (!isEdit && !veAvailable && !isAf) return;
	let context, curEditor, defSettingsMap = new WeakMap();
	let getSettings = (editor, isDef) => {
		let settings = {};
		let defSettings = !isDef && defSettingsMap.get(editor);
		Object.entries(editor.getOptions()).forEach(([k, v]) => {
			if (v === undefined) {
				v = null;
			}
			if (isDef || v !== defSettings[k] ||
				k === 'showInvisibles' || k === 'wrap'
			) {
				settings[k] = v;
			}
		});
		delete settings.mode;
		delete settings.readOnly;
		delete settings.maxLines;
		delete settings.minLines;
		delete settings.firstLineNumber;
		if (isDef) {
			settings.showInvisibles = false;
			settings.wrap = 'off';
		}
		return settings;
	};
	let updateToolbar = () => {
		if (!context) return;
		let names = [];
		if (curEditor.getShowInvisibles() !== context.showInvisibleChars) {
			names.push('invisibleChars');
		}
		if (curEditor.session.getUseWrapMode() !== context.lineWrappingActive) {
			names.push('lineWrapping');
		}
		names.forEach(name => {
			context.modules.toolbar.$toolbar.find(`.tool[rel="${name}"]`)
				.data('action').execute(context);
		});
	};
	let saveRemoveHandler = function (e) {
		this.disabled = true;
		let change = 'userjs-codeeditorassist-settings';
		let value;
		if (!e.data) {
			value = JSON.stringify(getSettings(curEditor));
			change += '=' + value;
		}
		mw.loader.using('mediawiki.api').then(() => (
			new mw.Api().postWithEditToken({
				action: 'globalpreferences',
				change: change
			})
		)).always(response => {
			this.disabled = false;
			if (!response || response.globalpreferences !== 'success') {
				mw.notify(
					e.data
						? `Couldn't remove settings from your global preferences`
						: `Couldn't save settings to your global preferences`,
					{ type: 'error' }
				);
				return;
			}
			if (e.data) {
				delete mw.user.options.values['userjs-codeeditorassist-settings'];
				mw.notify('Removed settings from your global preferences');
			} else {
				mw.user.options.set('userjs-codeeditorassist-settings', value);
				mw.notify('Saved settings to your global preferences');
			}
		});
	};
	let observing;
	let addButtons = () => {
		if (observing) return;
		observing = true;
		let $buttons = $('<div>').addClass('floatright').append(
			$('<button>').text('Save').on('click', saveRemoveHandler),
			' ',
			$('<button>').text('Remove').on('click', true, saveRemoveHandler),
			' ',
			$('<button>').text('Reset').on('click', () => {
				curEditor.setOptions(defSettingsMap.get(curEditor));
				$('#ace_settingsmenu').parent()[0].click();
				curEditor.execCommand('showSettingsMenu');
			})
		);
		new MutationObserver(() => {
			$buttons.appendTo('#ace_settingsmenu > table > tr:last-child > td');
			updateToolbar();
		}).observe(document.body, { childList: true });
	};
	let onFocus = (e, editor) => {
		curEditor = editor;
	};
	let initialize = editor => {
		if (!window.ace) return;
		if (!(editor instanceof ace.Editor)) {
			if (context) {
				editor = context.codeEditor;
			} else {
				let el = document.querySelector('.ace_editor');
				if (!el) return;
				editor = ace.edit(el);
			}
		}
		if (defSettingsMap.has(editor)) return;
		curEditor = editor;
		defSettingsMap.set(editor, getSettings(editor, true));
		let savedSettings = mw.user.options.get('userjs-codeeditorassist-settings');
		if (savedSettings) {
			savedSettings = JSON.parse(savedSettings);
			editor.setOptions(savedSettings);
			updateToolbar();
		}
		editor.on('focus', onFocus);
		addButtons();
	};
	if (isAf) {
		$.when($.ready, mw.loader.using('ext.abuseFilter.ace')).then(initialize);
		return;
	}
	if (veAvailable) {
		mw.hook('ve.loadModules').add(addPlugin => {
			addPlugin(() => {
				let setupEditor = ve.ui.MWAceEditorWidget.prototype.setupEditor;
				ve.ui.MWAceEditorWidget.prototype.setupEditor = function () {
					setupEditor.apply(this, arguments);
					initialize(this.editor);
				};
			});
		});
	}
	if (!isEdit) return;
	mw.loader.load('oojs-ui.styles.icons-interactions');
	mw.hook('codeEditor.configure').add(initialize);
	let promise = new Promise(resolve => {
		mw.hook('codeEditor.configure').add(resolve);
	});
	mw.hook('wikiEditor.toolbarReady').add($textarea => {
		context = $textarea.data('wikiEditorContext');
		promise.then(() => {
			$textarea.wikiEditor('addToToolbar', {
				section: 'main',
				group: 'codeeditor-style',
				tools: {
					settings: {
						label: 'Open code editor settings',
						type: 'button',
						oouiIcon: 'settings',
						action: {
							type: 'callback',
							execute: () => {
								curEditor.execCommand('showSettingsMenu');
							}
						}
					}
				}
			});
		});
	});
});