// ==UserScript== // @name GitHub RTL Comments // @version 1.3.4 // @description A userscript that adds a button to insert RTL text blocks in comments // @license MIT // @author Rob Garrison // @namespace https://github.com/Mottie // @match https://github.com/* // @match https://gist.github.com/* // @run-at document-idle // @grant GM_addStyle // @grant GM.addStyle // @connect github.com // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?updated=20180103 // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=1108163 // @require https://greasyfork.org/scripts/28239-rangy-inputs-mod-js/code/rangy-inputs-modjs.js?version=181769 // @icon https://github.githubassets.com/pinned-octocat.svg // @updateURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-rtl-comments.user.js // @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-rtl-comments.user.js // @supportURL https://github.com/Mottie/GitHub-userscripts/issues // ==/UserScript== (() => { "use strict"; const icon = ` `, // maybe using ⁧ RTL text ⁦ (isolates) is a better combo? openRTL = "‏", // https://en.wikipedia.org/wiki/Right-to-left_mark closeRTL = "‎", // https://en.wikipedia.org/wiki/Left-to-right_mark regexOpen = /\u200f/ig, regexClose = /\u200e/ig, regexSplit = /(\u200f|\u200e)/ig; GM.addStyle(` .ghu-rtl-css { direction:rtl; text-align:right; } /* delegated binding; ignore clicks on svg & path */ .ghu-rtl > * { pointer-events:none; } /* override RTL on code blocks */ .js-preview-body pre, .markdown-body pre, .js-preview-body code, .markdown-body code { direction:ltr; text-align:left; unicode-bidi:normal; } `); // Add RTL button function addRtlButton() { let el, button, toolbars = $$(".toolbar-commenting"), indx = toolbars.length; if (indx) { button = document.createElement("button"); button.type = "button"; button.className = "btn-octicon ghu-rtl toolbar-item tooltipped tooltipped-n"; button.setAttribute("aria-label", "RTL"); button.setAttribute("tabindex", "-1"); button.innerHTML = icon; while (indx--) { el = toolbars[indx]; if (!$(".ghu-rtl", el)) { el.insertBefore(button.cloneNode(true), el.childNodes[0]); } } } checkRTL(); } function checkContent(el) { // check the contents, and wrap in either a span or div let indx, // useDiv, html = el.innerHTML, parts = html.split(regexSplit), len = parts.length; for (indx = 0; indx < len; indx++) { if (regexOpen.test(parts[indx])) { // check if the content contains HTML // useDiv = regexTestHTML.test(parts[indx + 1]); // parts[indx] = (useDiv ? ""; parts[indx] = "
"; } else if (regexClose.test(parts[indx])) { // parts[indx] = useDiv ? "
" : ""; parts[indx] = ""; } } el.innerHTML = parts.join(""); // remove empty paragraph wrappers (may have previously contained the mark) return el.innerHTML.replace(/

<\/p>/g, ""); } function checkRTL() { let clone, indx = 0, div = document.createElement("div"), containers = $$(".js-preview-body, .markdown-body"), len = containers.length, // main loop loop = () => { let el, tmp, max = 0; while (max < 10 && indx < len) { if (indx > len) { return; } el = containers[indx]; tmp = el.innerHTML; if (regexOpen.test(tmp) || regexClose.test(tmp)) { clone = div.cloneNode(); clone.innerHTML = tmp; // now we can replace all instances el.innerHTML = checkContent(clone); max++; } indx++; } if (indx < len) { setTimeout(() => { loop(); }, 200); } }; loop(); } function addBindings() { window.rangyInput.init(); $("body").addEventListener("click", event => { let textarea, target = event.target; if (target && target.classList.contains("ghu-rtl")) { textarea = closest(".previewable-comment-form", target); textarea = $(".comment-form-textarea", textarea); textarea.focus(); // add extra white space around the tags window.rangyInput.surroundSelectedText( textarea, " " + openRTL + " ", " " + closeRTL + " " ); return false; } }); } function $(selector, el) { return (el || document).querySelector(selector); } function $$(selector, el) { return Array.from((el || document).querySelectorAll(selector)); } function closest(selector, el) { while (el && el.nodeType === 1) { if (el.matches(selector)) { return el; } el = el.parentNode; } return null; } document.addEventListener("ghmo:container", addRtlButton); document.addEventListener("ghmo:preview", checkRTL); addBindings(); addRtlButton(); })();