Editing.js – MediaWiki

From Bohemia Interactive Community
Jump to navigation Jump to search

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.
/*****************************************************************************************
 * JavaScript here will be loaded by MediaWiki:Common.js when "editing mode" is detected.
 *
 *****************************************************************************************/

(function() { // BEGIN WRAPPER

	/*****************************************************************************************
	 * Variables required by all functions below
	 */

	var editingTextbox = document.getElementById("wpTextbox1");

	/*****************************************************************************************
	 * Add checkbox toggle of allowance to use TAB key in editing textarea to insert TAB chars
	 * Maintainer: [[User:Fred Gandt]]
	 */
	function allowTabs() {
		var ecb = document.getElementsByClassName("editCheckboxes")[0],
			cb = document.createElement("input"),
			cb_lbl = document.createElement("label"),
			tabsAllowed = function(evt) {
				if (evt.keyCode == 9) {
					evt.preventDefault();
					var ss = editingTextbox.selectionStart;
					editingTextbox.value = editingTextbox.value.substr(0, ss) + String.fromCharCode(evt.keyCode) + editingTextbox.value.substr(editingTextbox.selectionEnd);
					editingTextbox.setSelectionRange(ss + 1, ss + 1);
				}
			}

		initTabsAllowed = function() {
			editingTextbox.addEventListener("keydown", tabsAllowed, false);
			editingTextbox.form.action += "&allowTabs";
		};
		cb.setAttribute("id", "allowTabs");
		cb.setAttribute("type", "checkbox");
		cb.setAttribute("style", "margin-left: 3em");
		cb_lbl.setAttribute("for", "allowTabs");
		cb_lbl.setAttribute("title", "Allows insertion of Tab characters when and where the Tab key is pressed");
		cb_lbl.innerHTML = " Allow tabulations usage";
		cb.addEventListener("change", function() {
			if (this.checked) {
				initTabsAllowed();
			} else {
				editingTextbox.removeEventListener("keydown", tabsAllowed, false);
				editingTextbox.form.action = editingTextbox.form.action.replace("&allowTabs", "");
			}
		}, false);

		if (~window.location.search.indexOf("&allowTabs")) {
			cb.checked = true;
			initTabsAllowed();
		}

		ecb.appendChild(cb);
		ecb.appendChild(cb_lbl);
	}

	/*****************************************************************************************
	 * Prevents page body scrolling when scrolling of the editing textarea reaches the top or bottom.
	 * Maintainer: [[User:Fred Gandt]]
	 */
	function holdStill() {
		editingTextbox.addEventListener("mousewheel", function(evt) {
			evt.preventDefault();
			editingTextbox.scrollTop -= evt.wheelDelta / 3;
		}, false);
	}

	/*****************************************************************************************
	 * Add a checkbox to preview darkmode (?useskin=darkvector)
	 * Maintainer: [[User:Lou Montana]]
	 */
	function darkModePreview() {
		var ecb = document.getElementsByClassName("editCheckboxes")[0],
			cb = document.createElement("input"),
			label = document.createElement("label"),
			actionDarkAdd = "&useskin=darkvector";
			actionLightAdd = "&useskin=vector";

		cb.setAttribute("id", "previewDarkMode");
		cb.setAttribute("type", "checkbox");
		cb.setAttribute("style", "margin-left: 3em");
		label.setAttribute("for", "previewDarkMode");
		label.setAttribute("title", "Preview Dark Mode render (next preview)");
		label.innerHTML = " Preview Dark Mode";
		cb.addEventListener("change", function() {
			if (this.checked) {
				editingTextbox.form.action = editingTextbox.form.action.replace(actionLightAdd, "")
				editingTextbox.form.action += actionDarkAdd;
			} else {
				editingTextbox.form.action = editingTextbox.form.action.replace(actionDarkAdd, "");
				editingTextbox.form.action += actionLightAdd;
			}
		}, false);

		cb.checked = document.getElementsByTagName("body")[0].classList.contains("skin-darkvector");
		let event = new Event("change");
		cb.dispatchEvent(event);

		ecb.appendChild(cb);
		ecb.appendChild(label);
	}

	/*****************************************************************************************
	 * Allow moving source code textarea's selected line(s) with Alt+Up / Alt+Down
	 * Allow duplicating source code textarea's selected line(s) with Alt+Shift+Up / Alt+Shift+Down
	 * Allow duplicating code textarea's current line/selection with Ctrl+D
	 * Maintainer: [[User:Lou Montana]]
	 */
	function allowLineMoveAndDuplication() {

		editingTextbox.addEventListener('keydown', (ev) => {

			// duplicate line below
			if (ev.ctrlKey && !ev.altKey && !ev.shiftKey && (ev.key == 'd' || ev.key == 'D')) // Ctrl+D
			{
				duplicateSelection(editingTextbox);
				ev.preventDefault();
				return;
			}

			if (ev.key != 'ArrowUp' && ev.key != 'ArrowDown')
				return;

			let up = ev.key == 'ArrowUp';

			// move line(s) up/down
			if (!ev.ctrlKey && ev.altKey && !ev.shiftKey) // Alt up/down
			{
				moveLines(editingTextbox, up);
				return;
			}

			// duplicate line(s) up/down
			if (!ev.ctrlKey && ev.altKey && ev.shiftKey) // Alt+Shift+Up/Down
			{
				duplicateLines(editingTextbox, up);
				return;
			}
		});
	}

	/**
	 *
	 * @param {HTMLTextAreaElement} editingTextbox
	 * @param {bool} moveUp
	 */
	function moveLines(editingTextbox, moveUp) {

		let lines = editingTextbox.value.split('\n');
		let linesCount = lines.length;
		if (linesCount < 1)
			return;

		let [firstHighlightedLine, lastHighlightedLine] = getHighlightedLines(lines, editingTextbox.selectionStart, editingTextbox.selectionEnd);

		if ((moveUp && firstHighlightedLine == 0) || (!moveUp && lastHighlightedLine == linesCount - 1))
			return;

		let selectionStart = editingTextbox.selectionStart;
		let selectionEnd = editingTextbox.selectionEnd;

		if (moveUp)
		{
			let movedLine = lines[firstHighlightedLine - 1];
			lines.splice(firstHighlightedLine - 1, 1);
			lines.splice(lastHighlightedLine, 0, movedLine);
			selectionStart -= movedLine.length + 1;
			selectionEnd -= movedLine.length + 1;
		}
		else
		{
			let movedLine = lines[lastHighlightedLine + 1];
			lines.splice(lastHighlightedLine + 1, 1);
			lines.splice(firstHighlightedLine, 0, movedLine);
			selectionStart += movedLine.length + 1;
			selectionEnd += movedLine.length + 1;
		}

		setTextAreaValue(editingTextbox, lines, selectionStart, selectionEnd);
	}

	/**
	 * triggered by Ctrl+D
	 * @param  {HTMLTextAreaElement} editingTextbox
	 */
	function duplicateSelection(editingTextbox) {

		let originalValue = editingTextbox.value;

		// no selection; duplicate current line below
		if (editingTextbox.selectionStart == editingTextbox.selectionEnd)
		{
			let lines = originalValue.split('\n');
			let [firstHighlightedLine] = getHighlightedLines(lines, editingTextbox.selectionStart, editingTextbox.selectionEnd);
			lines.splice(firstHighlightedLine, 0, lines[firstHighlightedLine]);
			setTextAreaValue(editingTextbox, lines);
			return;
		}

		// selection that should simply be inserted after the selection
		let selection = originalValue.substring(editingTextbox.selectionStart, editingTextbox.selectionEnd);
		originalValue = originalValue.slice(0, editingTextbox.selectionEnd) + selection + originalValue.slice(editingTextbox.selectionEnd);
		let lines = originalValue.split('\n');
		setTextAreaValue(editingTextbox, lines);
	}

	/**
	 * @param {HTMLTextAreaElement} editingTextbox
	 * @param {bool} dupeUp
	 */
	function duplicateLines(editingTextbox, dupeUp) {

		let lines = editingTextbox.value.split('\n');
		let linesCount = lines.length;

		let [firstHighlightedLine, lastHighlightedLine] = getHighlightedLines(lines, editingTextbox.selectionStart, editingTextbox.selectionEnd);

		if ((dupeUp && firstHighlightedLine == 0) || (!dupeUp && lastHighlightedLine == linesCount - 1))
			return;

		let selectionStart = editingTextbox.selectionStart;
		let selectionEnd = editingTextbox.selectionEnd;
		if (dupeUp)
		{
			let selectionOffset = 0;
			for (let i = firstHighlightedLine; i < lastHighlightedLine + 1; ++i)
			{
				selectionOffset += lines[i].length + 1;
				lines.splice(firstHighlightedLine, 0, lines[i]);
			}
			selectionStart += selectionOffset;
			selectionEnd += selectionOffset;
		}
		else
		{
			for (let i = lastHighlightedLine; i >= firstHighlightedLine; --i)
			{
				lines.splice(lastHighlightedLine + 1, 0, lines[i]);
			}
		}

		setTextAreaValue(editingTextbox, lines, selectionStart, selectionEnd);
	}

	/**
	 *
	 * @param {HTMLTextAreaElement} editingTextbox
	 * @param {array} lines
	 * @returns {array} [firstHighlightedLine, lastHighlightedLine, selectionStartColumn, selectionEndColumn]
	 */
	function getHighlightedLines(lines, selectionStart, selectionEnd) {

		let linesCount = lines.length;
		let firstHighlightedLine = -1;
		let lastHighlightedLine = -1;
		let selectionStartColumn = -1;
		let selectionEndColumn = -1;
		let calcLength = 0;
		let lineLength;
		for (let i = 0; i < linesCount; ++i) {

			lineLength = lines[i].length;

			if (selectionStartColumn == -1 && selectionStart <= calcLength + lineLength) {

				firstHighlightedLine = i;
				selectionStartColumn = selectionStart - calcLength;
			}

			if (selectionEndColumn == -1 && selectionEnd <= calcLength + lineLength) {

				lastHighlightedLine = i;
				selectionEndColumn = selectionStart - calcLength;
			}

			if (selectionEndColumn != -1 && selectionEndColumn != -1)
				break;

			calcLength += lineLength + 1; // "\n"
		}

		if (selectionStartColumn == -1 || selectionEndColumn == -1)
			return;

		if (firstHighlightedLine < lastHighlightedLine)
			return [firstHighlightedLine, lastHighlightedLine, selectionStartColumn, selectionEndColumn];
		else
			return [lastHighlightedLine, firstHighlightedLine, selectionStartColumn, selectionEndColumn];
	}

	/**
	 *
	 * @param {HTMLTextAreaElement} editingTextbox
	 * @param {Array} lines
	 * @param {number} selectionStart
	 * @param {number} selectionEnd
	 */
	function setTextAreaValue(editingTextbox, lines, selectionStart, selectionEnd) {

		if (!selectionStart && selectionStart !== 0)
			selectionStart = editingTextbox.selectionStart;

		if (!selectionEnd && selectionEnd !== 0)
			selectionEnd = editingTextbox.selectionEnd;

		// editingTextbox.value = lines.join('\n');
		editingTextbox.select();
		document.execCommand('insertText', false, lines.join('\n'));

		editingTextbox.selectionStart = selectionStart;
		editingTextbox.selectionEnd = selectionEnd;
	}

	/*****************************************************************************************
	 * Call the required functions from those above
	 */

	allowTabs();
	darkModePreview();
	allowLineMoveAndDuplication();

}()); // END WRAPPER