User:NguoiDungKhongDinhDanh/CodeAjaxEditor.js

From Meta, a Wikimedia project coordination wiki

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)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 * CodeAjaxEditor: Make the code you see on JS/CSS/Lua pages editable without loading CodeEditor.
 * 
 * Skin tested: Vector, Vector 2022, Monobook, Timeless.
 * 
 * After clicking the link that activates it in the "More" menu, you will see a button to submit
 * whatever you typed. Two prompts will then appear to ask for edit summary and minor marking.
 * If you want to abort the submitting progress, cancel the first prompt. In the case of failing
 * to make the edit, all codes will be copied to your clipboard; a notification will then appear.
 * Click it before it disappear will take you to the edit page where you can paste your codes
 * and save manually.
 * 
 * Please note that CodeAjaxEditor is not supposed to be a real editor and may be buggy;
 * The CodeEditor extension should be preferred for syntaxhighlighting and other features.
 * 
 * License: CC BY-SA.
**/

mw.loader.using(['oojs-ui-core', 'mediawiki.util']).done(function() {
	if (!['javascript', 'css', 'sanitized-css', 'Scribunto'].includes(mw.config.get('wgPageContentModel'))) {
		return;
	}
	
	mw.util.addPortletLink('p-cactions', '', 'CAE', 'ca-cae', 'Initialize CodeAjaxEditor');
	
	$('#ca-cae').click(function(e) {
		e.preventDefault();
		if ($('#CAE-pre').length) {
			mw.notify('CodeAjaxEditor has already been enabled!', {
				type: 'warn',
				autoHide: true,
				autoHideSeconds: 10,
				tag: 'CAE-ready'
			});
			return;
		}
	
		var CAE = {};
		window.CAE = CAE;
		
		// For attribution: //stackoverflow.com/q/16580841
		CAE.insert = function(v) {
			var sel, range;
			if (window.getSelection) {
				sel = window.getSelection();
				if (sel.getRangeAt && sel.rangeCount) {
					range = sel.getRangeAt(0);
					range.deleteContents();
					
					var el = document.createElement('div');
					el.innerText = v;
					var frag = document.createDocumentFragment(), node, lastNode;
					while ((node = el.firstChild)) {
						lastNode = frag.appendChild(node);
					}
					range.insertNode(frag);
					
					if (lastNode) {
						range = range.cloneRange();
						range.setStartAfter(lastNode);
						range.collapse(true);
						sel.removeAllRanges();
						sel.addRange(range);
					}
				}
			}
		};
		
		var css = `
			#CAE-submit {
				position: fixed;
				bottom: 2em;
				right: 2em;
			}
			#CAE-pre:focus {
				outline: 0;
			}
		`;
		try {
			mw.util.addCSS(css);
		} catch(e) {
			mw.util.addStyleTag(css);
		}
		
		CAE.confirm = false;
		onbeforeunload = function() { // Alert when user attempt to leave.
			if (!CAE.confirm) return 'You have unsaved changes. Do you really want to leave?';
		};
		
		CAE.submit = new OO.ui.ButtonWidget({
				label: 'Submit',
				flags: [
					'primary',
					'progressive'
				],
				id: 'CAE-submit'
			});
		
		$('body').append(CAE.submit.$element);
		
		var selector = $('.mw-highlight > pre').length > 0 ? '.mw-highlight > pre' : '#mw-content-text pre.mw-code';
		$(selector)
			.attr('id', 'CAE-pre') // Give it an id for ease of select.
			.prop('contenteditable', true); // Main feature.
		
		mw.notify('Content is now editable. Please note that CodeAjaxEditor is not supposed to be a real editor and may be buggy. ' +
					'The CodeEditor extension should be preferred for syntaxhighlighting and other features.', {
			title: 'Initialized.',
			autoHide: true,
			autoHideSecond: 10
		});
		
		$('#CAE-pre').on('keydown', function(e) {
			if (e.which == 9) {
				e.preventDefault();
				CAE.insert('\t');
			} else if (e.which == 13) {
				e.preventDefault();
				CAE.insert('\n');
			} else return;
		});
		
		$('#CAE-submit').click(function() {
			// UX.
			CAE.submit.setLabel('Submitting...');	
			CAE.submit.setDisabled(true);
			
			// Prompt for summary.
			var es, me;
			try {
				es = prompt('Edit summary (cancel to abort):').trim();
				es += ' (via [[:m:User:NguoiDungKhongDinhDanh/CodeAjaxEditor.js|CAE]])';
			} catch (e) {
				CAE.submit.setDisabled(false);
				CAE.submit.setLabel('Submit');
				return console.log('CodeAjaxEditor: Aborted.');
			}			
			me = confirm('Mark this edit as minor?');
			
			// Turn off event handler (i.e. this one).
			$(this).off();
			
			// Dirty but works.
			$('#CAE-pre').html($('#CAE-pre').html().replace(/<br ?\/?>/g, '\n'));
			
			// API request.
			(new mw.Api()).postWithToken('csrf', {
				action: 'edit',
				title: mw.config.get('wgPageName'),
				text: $('#CAE-pre').text(),
				summary: es,
				minor: me,
				nocreate: true,
				format: 'json',
				formatversion: 2
			}).done(function(response) {
				CAE.submit.setLabel('Submitted.');
				mw.notify('Changes made successfully. Reloading...', {
					title: 'Saved!',
					type: 'success',
					autoHide: false,
					tag: 'CAE-notification'
				});
				CAE.confirm = true;
				location.reload();
			}).fail(function(response) {
				// Old browsers support.
				// For attribution: [[:en:User:Qwerfjkl/scripts/copy.js]]
				var ta = document.createElement('textarea');
				ta.textContent = $('#CAE-pre').text();
				ta.style.position = 'fixed';
				document.body.appendChild(ta);
				ta.select();
				document.execCommand('copy');
				document.body.removeChild(ta);
				
				CAE.submit.setLabel('Submitted.');
				
				var e;
				if ($($('#ca-edit > a')[0]).attr('href')) e = $($('#ca-edit > a')[0]).attr('href');
				else if ($($('#ca-viewsource > a')[0]).attr('href')) e = $($('#ca-viewsource > a')[0]).attr('href');
				
				mw.notify('Could not edit the page. Changes has been copied to your clipboard. ' + (e ? 'Click to edit manually.' : ''), {
					title: 'Failed!',
					type: 'error',
					autoHide: true,
					autoHideSecond: 10,
					tag: 'CAE-notification'
				});
				
				if (e) {
					CAE.confirm = true;
					$('.mw-notification-tag-CAE-notification').click(function() {
						location.assign(e);
					});
				}
			});
		});
	});
});