Jump to content

User:L10nM4st3r/wEditor.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)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
if (location.href.includes("&diff=")){
	mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Cacycle/wikEdDiff.js&action=raw&ctype=text/javascript');
}


var wikEdDiffConfig = {};
wikEdDiffConfig.clipLinesRightMax = 4;
wikEdDiffConfig.clipLinesLeftMax = 2;

var wEditData = {
	// Is the user creating a new page
	is_new_page: false,
	// Is viewing source code, cannot edit
	is_viewing_source: false,
	// Used for showing changes
	old_text: "",
	// The section the user is editing. Will be -1 if editing the entire page.
	edit_section: -1,

	// Required for the undo/redo to work properly
	undoIndex: 0,
	undo_hisory: [],
	undo_hisory_cursor: [],	
	lastUndoType: "",
	undoRedoCooldown: false,
}


const TEXT_VALUES = {
	previewText:				"Show Preview",
	previewTooltip:				"Preview your changes",
	changesText:				"Show Changes",
	changesTooltip:				"Show which changes you made to the text",
	saveEditText:				"Save Changes",
	saveEditTooltip:			"Save your changes",
	savePageText:				"Save",
	savePageTooltip:			"Save this page",
	editSummaryPlaceholder:			"Edit Summary",
	editSummaryCreatePlaceholder:		"Summary",
	editSummaryTooltip:			"Enter a short summary",
	minorEditLabel:				"Minor Edit",
	watchPageLabel:				"Watch This Page",
	editSummaryClearTooltip:		"Clear summary",
	insertWikilinkTooltip:			"Insert Wiki-link Brackets",
	insertTemplateTooltip:			"Insert Template Brackets",
	undoTooltip:				"Undo",
	redoTooltip:				"Redo",
}


{
	function wEditBox_contentOnKeyPress(e){
		if (wEditData.undoRedoCooldown) return;

		var textarea = document.getElementById("wedit-content");

		var thisUndoType = "key";
		var isNullInput = e.key === "Shift" || e.key === "Control" || e.key === "Alt" || e.key === "Meta" || e.ctrlKey || e.altKey;

		if (e.shiftKey && e.ctrlKey && e.key === 'Z') wEditBox_redo();
		else if (!e.shiftKey && e.ctrlKey && e.key === 'z') wEditBox_undo();
		else if (e.ctrlKey && e.key === 'y') wEditBox_redo();

		// Disable the default undo-redo
		if (e.ctrlKey && (e.key === 'z' || e.key === 'y')) {
			e.preventDefault();
			return;
		}

		if (e.key === 'Enter')
			thisUndoType = "newline";
		else if (e.key === 'Backspace' || e.key === 'Delete')
			thisUndoType = "backspace";
		else if (e.key === ' ')
			thisUndoType = "space";
		else if ([";", "!", "\"", "£", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", "=", "[", "]", "{", "}", ":", "@", "'", "#", "~", "?", "/", ">", "<", ".", ",", "|", "\\", "`", "¬", "¦"].includes(e.key))
			thisUndoType = e.key;
		else if (e.ctrlKey && e.key === 'v') {
			isNullInput = false;
			thisUndoType = "paste" + Math.random().toString()
		}
		else if (e.ctrlKey && e.key === 'x') {
			isNullInput = false;
			thisUndoType = "cut" + Math.random().toString();
		}
		// Press tab to crreate an indent
		else if (e.key === 'Tab'){
			wEditBox_insertText("\t");
			thisUndoType = "indent";
			e.preventDefault();
		}

		if (wEditData.undoIndex+1 < wEditData.undo_hisory.length && !isNullInput) {
			wEditData.undo_hisory.length = wEditData.undoIndex + 1;
			wEditData.undo_hisory_cursor.length = wEditData.undoIndex + 1;
		}


		if (wEditData.undo_hisory_cursor.length === 0)
			wEditData.undo_hisory_cursor.push(textarea.selectionStart);
		if (thisUndoType !== wEditData.lastUndoType && !isNullInput) {
			wEditData.lastUndoType = thisUndoType;
			wEditData.undoIndex += 1;
			wEditData.undo_hisory.push("");
			wEditData.undo_hisory_cursor.push(textarea.selectionStart);
		}

		if (!isNullInput) {
			wEditData.undoRedoCooldown = true;
			setTimeout(() => {
				wEditData.undoRedoCooldown = false;
				wEditData.undo_hisory[wEditData.undo_hisory.length-1] = textarea.value;
				wEditData.undo_hisory_cursor[wEditData.undo_hisory.length-1] = textarea.selectionStart;
			}, 1);
		}

		document.getElementById("wedit-undo").disabled = !wEditData.undoIndex > 0;
		document.getElementById("wedit-redo").disabled = wEditData.undoIndex+1 >= wEditData.undo_hisory.length;
	}


	// Load the editor UI
	if (mw.config.get("wgAction") === "edit" || mw.config.get("wgAction") === "submit") {
		// Load the diff.js library.
		mw.loader.load("https://en.wikipedia.org/w/index.php?title=User:Cacycle/diff.js&action=raw&ctype=text/javascript");

		var pageName = replaceAll(mw.config.get("wgPageName").toLowerCase(), " ", "_");
		var pageTitle = document.getElementsByClassName("mw-first-heading")[0].innerText;
		if (pageTitle.toLowerCase().startsWith("view source for " + pageName))
			wEditData.is_viewing_source = true;
		if (replaceAll(pageTitle.toLowerCase(), " ", "_") === "creating_" + pageName)
			wEditData.is_new_page = true;

		if (location.href.includes("&section=")) {
			var href = location.href;
			href = href.slice(href.indexOf("&section=") + 9, href.length);

			// Make sure to strip additional arguments, such as "summary="
			if (href.includes("&")) href.slice(0, href.indexOf("&"));
			wEditData.edit_section = Number(href);
		}

		if (!wEditData.is_viewing_source) {
			var editTextBoxValue = document.getElementById("wpTextbox1").value;
			wEditData.undo_hisory.push(editTextBoxValue);
			var editSummaryValue = document.getElementsByName('wpSummary')[0].value;
			wEditData.old_text = editTextBoxValue;
			var topDiv = document.createElement("div");

			var editform = document.getElementById("editform");
			editform.style.display = "none";

			var container = document.createElement("div");
			container.innerHTML = generateEditor();
			editform.parentNode.insertBefore(container, editform.nextNode);

			document.getElementById("wedit-content").value = editTextBoxValue;
			document.getElementById("wedit-content").addEventListener("keydown", wEditBox_contentOnKeyPress);
			document.getElementById("wedit-summary").value = editSummaryValue;
			document.getElementById("wedit-summary").addEventListener("keydown", wEditBox_summaryOnKeyPress);
		}
	}


	function generateEditor() {
		var header = '<style>.unselectable {-webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;} .checkbox {margin-left: 10px; margin-right: 7px} </style>';

		var textbox = '<textarea onclick="wEditData.lastUndoType=\'\'" id="wedit-content" style="width:100%; max-height:250em; min-height:10em; height: 25em; resize:vertical; overflow:auto"></textarea>';

		var output = header + '<div style="background-color:#cbdaf2">' + generateToolBar() + textbox + generateSubmitBar() + '</div><div style="min-height:160px" id="wedit-previewContent"></div>';
		return output;
	}


	function generateToolBar() {
		var category1 = `
			<input type="button" id="wedit-undo" disabled="true" style="background: url(https://upload.wikimedia.org/wikipedia/commons/thumb/5/5e/Undo_Inkscape_Icon.svg/16px-Undo_Inkscape_Icon.svg.png?20201113140121); background-size: 100% 100%; cursor:pointer; width: 32px; height:32px" onclick="wEditBox_undo()" title="${TEXT_VALUES.undoTooltip}"></input>
			<input type="button" id="wedit-redo" disabled="true" style="background: url(https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Redo_Inkscape_Icon.svg/16px-Redo_Inkscape_Icon.svg.png?20201113143731); background-size: 100% 100%; cursor:pointer; width: 32px; height:32px" onclick="wEditBox_redo()" title="${TEXT_VALUES.redoTooltip}"></input>`;
		var category1Box = '<div style="padding: 5px 5px 5px 5px; margin: 7px 7px 7px 7px; border: 1px black solid; flex:1">' + category1 + '</div>';

		var category2 = `
			<input style="background: url(https://upload.wikimedia.org/wikipedia/commons/d/d6/WikiText.svg); background-size: 100% 100%; cursor:pointer; display:inline; width:32px; height:32px" title="${TEXT_VALUES.insertWikilinkTooltip}" type="button" onclick="wEditBox_insertWikiLink()"></input>
			<input style="background: url(https://upload.wikimedia.org/wikipedia/commons/a/ab/CurlyBracketsBlue.svg); background-size: 100% 100%; cursor:pointer; display:inline; width:32px; height:32px" title="${TEXT_VALUES.insertTemplateTooltip}" type="button" onclick="wEditBox_insertTemplateBrackets()"></input>`;
		var category2Box = '<div style="padding: 5px 5px 5px 5px; margin: 7px 7px 7px 7px; border: 1px black solid; flex:1">' + category2 + '</div>';

		var output = '<div style="background-color:#cbdaf2; display:flex">' + category1Box + category2Box + '</div>';
		return output;
	}


	function generateSubmitBar() {
		if (wEditData.is_viewing_source) return "";

		var editSummaryInput = '<input style="flex:1" id="wedit-summary" title="' + TEXT_VALUES.editSummaryTooltip + '" placeholder="' + (wEditData.is_new_page ? TEXT_VALUES.editSummaryCreatePlaceholder : TEXT_VALUES.editSummaryPlaceholder) + '" type="text" maxlength="400"></input>';

		var editSummaryClear = '<input style="cursor:pointer; display:inline; width:2em" id="wedit-summary-clear" title="' + TEXT_VALUES.editSummaryClearTooltip + '" type="button" value="X" onclick="document.getElementById(\'wedit-summary\').value=\'\'; document.getElementById(\'wedit-summary\').focus();"></input>';

		var minorEdit = wEditData.is_new_page ? "" : '<label class="unselectable" for="wedit-minor"><input id="wedit-minor" name="wedit-minor"' + (document.getElementById("wpMinoredit").checked ? " checked=true" : "") + ' class="checkbox" type="checkbox"></input>' + TEXT_VALUES.minorEditLabel + '</label>';

		var watchPageToggle = '<label class="unselectable" for="wedit-watch"><input id="wedit-watch" name="wedit-watch"' + (document.getElementById("wpWatchthis").checked ? " checked=true" : "") + ' class="checkbox" type="checkbox"></input>' + TEXT_VALUES.watchPageLabel + '</label>';


		var saveEdit = '<input style="cursor:pointer; background: linear-gradient(0.99turn, #6daafc, #ebeef2); display:inline; flex:1; height: 2em" id="wedit-save-edit" title="' + (wEditData.is_new_page ? TEXT_VALUES.savePageTooltip : TEXT_VALUES.saveEditTooltip) + '" type="button" value="' + (wEditData.is_new_page ? TEXT_VALUES.savePageText : TEXT_VALUES.saveEditText) + '" onclick="wEditBox_saveEdit();"></input>';

		var previewPage = '<input style="cursor:pointer; display:inline; flex:1; height: 2em" id="wedit-show-preview" title="' + TEXT_VALUES.previewTooltip + '" type="button" value="' + TEXT_VALUES.previewText + '" onclick="wEditBox_showPreview();"></input>';

		var viewChanges = wEditData.is_new_page ? "" : '<input style="cursor:pointer; display:inline; flex:1; height: 2em" id="wedit-show-showChanges" title="' + TEXT_VALUES.changesTooltip + '" type="button" value="' + TEXT_VALUES.changesText + '" onclick="wEditBox_showChanges();"></input>';


		var editSummaryBar = '<div style="display:flex; margin: 6px 3px 6px 3px;">' + editSummaryClear + editSummaryInput + '</div>';
		var saveButtonsBar = '<div style="display:flex; margin: 6px 3px 6px 3px;">' + saveEdit + previewPage + viewChanges + '</div>';

		return editSummaryBar + minorEdit + watchPageToggle + saveButtonsBar;
	}
}


function wEditBox_showChanges() {
	var wikEdDiff = new WikEdDiff();
	document.getElementById('wedit-previewContent').innerHTML = wikEdDiff.diff( wEditData.old_text, document.getElementById("wedit-content").value );
}


function wEditBox_showPreview() {
	new mw.Api().get({
		"action": "parse",
		"title": mw.config.get("wgPageName"),
		"text": document.getElementById('wedit-content').value,
		"prop": "text|categories|templates|limitreporthtml",
		"disableeditsection": 1,
		"preview": 1,
		"pst": 1,
		"formatversion": "2",
	}).then(function(ret) {
		document.getElementById('wedit-previewContent').innerHTML = ret.parse.text["*"] + ret.parse.limitreporthtml["*"];
	}, function(error, errorData){
		mw.notify("Error occurred: " + JSON.stringify(errorData), {type: "error"})
	})
}



function wEditBox_undo() {
	if (wEditData.undoIndex > 0) {
		var textarea = document.getElementById("wedit-content");
		textarea.focus();
		wEditData.lastUndoType = "";
		wEditData.undoIndex -= 1;
		textarea.value = wEditData.undo_hisory[wEditData.undoIndex];

		textarea.selectionStart = wEditData.undo_hisory_cursor[wEditData.undoIndex];
		textarea.selectionEnd = textarea.selectionStart;

		document.getElementById("wedit-undo").disabled = !wEditData.undoIndex > 0
		document.getElementById("wedit-redo").disabled = wEditData.undoIndex+1 >= wEditData.undo_hisory.length
	}
}

function wEditBox_redo() {
	if (wEditData.undoIndex+1 < wEditData.undo_hisory.length) {
		var textarea = document.getElementById("wedit-content");
		textarea.focus();
		wEditData.lastUndoType = "";
		wEditData.undoIndex += 1;
		textarea.value = wEditData.undo_hisory[wEditData.undoIndex];

		textarea.selectionStart = wEditData.undo_hisory_cursor[wEditData.undoIndex];
		textarea.selectionEnd = textarea.selectionStart;

		document.getElementById("wedit-undo").disabled = !wEditData.undoIndex > 0
		document.getElementById("wedit-redo").disabled = wEditData.undoIndex+1 >= wEditData.undo_hisory.length
	}
}


// Insert text to the textbox
function wEditBox_insertText(text){
	var textarea = document.getElementById("wedit-content");
	// Make sure to insert it at the cursor position
	var newCursorPosition = textarea.selectionStart + text.length;
	textarea.focus();
	textarea.value = textarea.value.substring(0, textarea.selectionStart) + text + textarea.value.substring(textarea.selectionEnd, textarea.value.length);
	// Set the cursor to be after the inserted text
	textarea.selectionStart = newCursorPosition;
	textarea.selectionEnd = newCursorPosition;
}


function wEditBox_summaryOnKeyPress(event){
	// If player pressed the enter key in the summary box, post the edit.
	if(event.key === "Enter") wEditBox_saveEdit();
}


function wEditBox_saveEdit() {
	// Make sure the user can't spam the save button, which may cause unexpected behaviour.
	document.getElementById("wedit-save-edit").disabled = true;
	// Create this page
	if (wEditData.is_new_page) {
		new mw.Api().create(mw.config.get("wgPageName"), {
			summary: document.getElementById('wedit-summary').value,
			watchlist: document.getElementById("wedit-watch").checked ? "watch" : "unwatch"
		}, document.getElementById('wedit-content').value
		).then(function(){
			// Page saved, open it
			location.href = "https://" + mw.config.get("wgServerName") + "/wiki/" + mw.config.get("wgPageName");
		}, function(e){
			mw.notify("Cannot save page. Error: " + e);
		});
	// Editing the entire page
	} else if (wEditData.edit_section === -1) {
		new mw.Api().edit(mw.config.get("wgPageName"), function(revision) {
			return {
				text: document.getElementById('wedit-content').value,
				summary: document.getElementById('wedit-summary').value,
				minor: document.getElementById("wedit-minor").checked,
				watchlist: document.getElementById("wedit-watch").checked ? "watch" : "unwatch"
			}
		}).then(function(){
			// Page saved, open it
			location.href = "https://" + mw.config.get("wgServerName") + "/wiki/" + mw.config.get("wgPageName");
		}, function(e){
			mw.notify("Cannot save page. Error: " + e);
		});
	// Editing a specific section
	} else {
		new mw.Api().edit(mw.config.get("wgPageName"), function(revision) {
			return {
				text: document.getElementById('wedit-content').value,
				section: wEditData.edit_section,
				summary: document.getElementById('wedit-summary').value,
				minor: document.getElementById("wedit-minor").checked,
				watchlist: document.getElementById("wedit-watch").checked ? "watch" : "unwatch"
			}
		}).then(function(){
			// Page saved, open it
			location.href = "https://" + mw.config.get("wgServerName") + "/wiki/" + mw.config.get("wgPageName");
		}, function(e){
			mw.notify("Cannot save page. Error: " + e);
		});
	}
}


// Saves the current text of the text editor as a new item in the undo history
function wEditBox_storeUndoableAction(){
	if (wEditData.undoRedoCooldown) return; // This line is very important, do not remove, or it will result in empty undo history
	// Get the textarea
	var textarea = document.getElementById("wedit-content");

	// Delete all undoable actions after this one, if the user has pressed redo
	if (wEditData.undoIndex+1 < wEditData.undo_hisory.length) {
		wEditData.undo_hisory.length = wEditData.undoIndex + 1;
		wEditData.undo_hisory_cursor.length = wEditData.undoIndex + 1;
	}

	// Store the undo history data
	wEditData.lastUndoType = "";
	wEditData.undoIndex += 1;
	wEditData.undo_hisory.push(textarea.value);
	wEditData.undo_hisory_cursor.push(textarea.selectionStart);

	document.getElementById("wedit-undo").disabled = !wEditData.undoIndex > 0
	document.getElementById("wedit-redo").disabled = wEditData.undoIndex+1 >= wEditData.undo_hisory.length
}


function wEditBox_insertTemplateBrackets() {
	wEditBox_insertText("{{}}");
	// Set cursor to be in between the brackets
	var textarea = document.getElementById("wedit-content");
	textarea.selectionStart -= 2;
	textarea.selectionEnd -= 2;
	// Make this action undoble
	wEditBox_storeUndoableAction();
}


function wEditBox_insertWikiLink() {
	wEditBox_insertText("[[]]");
	// Set cursor to be in between the brackets
	var textarea = document.getElementById("wedit-content");
	textarea.selectionStart -= 2;
	textarea.selectionEnd -= 2;
	// Make this action undoble
	wEditBox_storeUndoableAction();
}

function replaceAll(text, replace, to_text){
    return text.split(replace).join(to_text)
}