var browser = require("webextension-polyfill"); const verbsHelper = require("english-verbs-helper"); const verbsIrregular = require("english-verbs-irregular/dist/verbs.json"); const verbsGerunds = require("english-verbs-gerunds/dist/gerunds.json"); const verbsData = verbsHelper.mergeVerbsData(verbsIrregular, verbsGerunds); /* Replaces verbs, point-of-view, name, pronouns, and "also" terms */ function replaceAll() { let options = browser.storage.local.get(); options.then((options) => { const hostname = window.location.hostname; const isEnabled = !(options.domains === undefined) && options.domains.includes(hostname); if (!isEnabled) { return; } const isNameSet = !(options.name === undefined || options.name == ""); const isPronounsSet = !(options.preset === undefined || options.preset == ""); const isPovSet = !(options.pov === undefined || options.pov == ""); const isPovLegal = isPovSet && (options.pov != "third" || (isNameSet && isPronounsSet)); const isAllSet = isNameSet && isPronounsSet && isPovLegal; if (isPovLegal) { replaceVerbs(options); replacePov(options); replacePlv(options); } if (isNameSet) { replaceName(options); } if (isPronounsSet) { replacePronouns(options); } const isAlsoSet = !(options.also === undefined); if (isAlsoSet) { replaceAlso(options); } if (isAllSet) { replaceExceptions(options); replaceCuts(); replaceCapitals(); replaceMirror(); } }); } /* Replaces all instances of "Y/n" with the user's name */ function replaceName(options) { const searchTerm = /\by\/n\b|\(y\/n\)|\[y\/n\]/ig; walk(document.body, searchTerm, options["name"], directMethod); } /* Replaces all verb keys */ function replaceVerbs(options) { const searchTerm = /(v)r([nb])\/([\w\s]+)\/(?:((?:(?:simple|progressive|perfect|perfect[ _]progressive|participle(?![ _]future))[ _])*(?:past|present|future))\/)*/i; walk(document.body, searchTerm, options, verbMethod); } /* Replaces all pronoun keys */ /* This could be made faster if I give premade regexp expressions for each, but I don't want to do that! */ function replacePronouns(options) { let pronouns = getPronouns(options.preset, options.other); walk(document.body, /([Pp])rn\/s/, pronouns["subjective"], pronounMethod); walk(document.body, /([Pp])rn\/o/, pronouns["objective"], pronounMethod); walk(document.body, /([Pp])rn\/p/, pronouns["possessive"], pronounMethod); walk(document.body, /([Pp])rn\/a/, pronouns["adjective"], pronounMethod); walk(document.body, /([Pp])rn\/r/, pronouns["reflexive"], pronounMethod); walk(document.body, /([Pp])rn\/H/, pronouns["honorific-abbr"], pronounMethod); walk(document.body, /([Pp])rn\/h/, pronouns["honorific"], pronounMethod); walk(document.body, /([Pp])rn\/N/, pronouns["adult"], pronounMethod); walk(document.body, /([Pp])rn\/n/, pronouns["youth"], pronounMethod); walk(document.body, /([Pp])rn\/F/, pronouns["parent"], pronounMethod); walk(document.body, /([Pp])rn\/f/, pronouns["child"], pronounMethod); walk(document.body, /([Pp])rn\/k/, pronouns["sibling"], pronounMethod); walk(document.body, /([Pp])rn\/m/, pronouns["married"], pronounMethod); walk(document.body, /([Pp])rn\/d/, pronouns["dating"], pronounMethod); } /* Gets pronouns based on the provided key (or user input) */ function getPronouns(key, other) { switch (key) { case "she": return { "subjective": "she", "objective": "her", "possessive": "her", "adjective": "hers", "reflexive": "herself", "honorific-abbr": "Ms.", "honorific": "miss", "adult": "woman", "youth": "girl", "parent": "mother", "child": "daughter", "sibling": "sister", "married": "wife", "dating": "girlfriend", "plurality": "singular" }; case "he": return { "subjective": "he", "objective": "him", "possessive": "his", "adjective": "his", "reflexive": "himself", "honorific-abbr": "Mr.", "honorific": "mister", "adult": "man", "youth": "boy", "parent": "father", "child": "son", "sibling": "brother", "married": "husband", "dating": "boyfriend", "plurality": "singular" }; case "they": return { "subjective": "they", "objective": "them", "possessive": "their", "adjective": "theirs", "reflexive": "themself", "honorific-abbr": "Mx.", "honorific": "mix", "adult": "person", "youth": "kid", "parent": "parent", "child": "child", "sibling": "sibling", "married": "spouse", "dating": "partner", "plurality": "plural" }; case "first": return { "subjective": "I", "objective": "me", "possessive": "my", "adjective": "mine", "reflexive": "myself" }; case "second": return { "subjective": "you", "objective": "you", "possessive": "your", "adjective": "yours", "reflexive": "yourself" }; case "first-plural": return { "subjective": "we", "objective": "us", "possessive": "our", "adjective": "ours", "reflexive": "ourselves" }; case "second-plural": return { "subjective": "you", "objective": "you", "possessive": "your", "adjective": "yours", "reflexive": "yourselves" }; case "third-plural": return { "subjective": "they", "objective": "them", "possessive": "their", "adjective": "theirs", "reflexive": "themselves" }; case "other": return other; } } /* Replaces all point-of-view keys */ function replacePov(options) { let pronouns; if (options.pov == "third") { pronouns = getPronouns(options.preset, options.other); walk(document.body, /([Pp])ov\/S/, options.name, pronounMethod); walk(document.body, /([Pp])ov\/O/, options.name, pronounMethod); walk(document.body, /([Pp])ov\/P/, options.name + "’s", pronounMethod); walk(document.body, /([Pp])ov\/A/, options.name + "’s", pronounMethod); walk(document.body, /([Pp])ov\/R/, options.name + "’s self", pronounMethod); } else { pronouns = getPronouns(options.pov, null); walk(document.body, /([Pp])ov\/S/, pronouns["subjective"], pronounMethod); walk(document.body, /([Pp])ov\/O/, pronouns["objective"], pronounMethod); walk(document.body, /([Pp])ov\/P/, pronouns["possessive"], pronounMethod); walk(document.body, /([Pp])ov\/A/, pronouns["adjective"], pronounMethod); walk(document.body, /([Pp])ov\/R/, pronouns["reflexive"], pronounMethod); } walk(document.body, /([Pp])ov\/s/, pronouns["subjective"], pronounMethod); walk(document.body, /([Pp])ov\/o/, pronouns["objective"], pronounMethod); walk(document.body, /([Pp])ov\/p/, pronouns["possessive"], pronounMethod); walk(document.body, /([Pp])ov\/a/, pronouns["adjective"], pronounMethod); walk(document.body, /([Pp])ov\/r/, pronouns["reflexive"], pronounMethod); } /* Replaces plural point-of-view keys */ function replacePlv(options) { let pronouns = getPronouns(options.pov + "-plural", null); walk(document.body, /([Pp])lv\/s/, pronouns["subjective"], pronounMethod); walk(document.body, /([Pp])lv\/o/, pronouns["objective"], pronounMethod); walk(document.body, /([Pp])lv\/p/, pronouns["possessive"], pronounMethod); walk(document.body, /([Pp])lv\/a/, pronouns["adjective"], pronounMethod); walk(document.body, /([Pp])lv\/r/, pronouns["reflexive"], pronounMethod); } /* Replaces additional terms specified by the user */ function replaceAlso(options) { Object.entries(options.also).forEach(([key, value]) => { escapeAndReplace(key, value, true); }); } /* Replaces exception statements by POV */ function replaceExceptions(options) { const searchTerm = /exc\/([^\/]*)\/([^\/]*)\//i; walk(document.body, searchTerm, options, exceptionMethod); } /* Replaces cut statements with cuts */ function replaceCuts() { const searchTerm = /cut\/([^\/]+)\/(-?[\d]+)\//i; walk(document.body, searchTerm, null, cutMethod); } /* Replaces capital statements statements with requested casing */ function replaceCapitals() { const searchTerm = /(cap|Cap|CAP)\/([^\/]+)\//i; walk(document.body, searchTerm, null, capitalMethod); } /* Replaces mirror statements with reversed phrases */ function replaceMirror() { const searchTerm = /mrr\/([^\/]+)\//i; walk(document.body, searchTerm, null, mirrorMethod); } /* Turns a term into regexp format and uses that to replace it */ function escapeAndReplace(searchTerm, replaceValue, caseSensitive) { let searchTermEscaped = searchTerm.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); const flags = caseSensitive ? "g" : "ig" if (searchTermEscaped[0].match(/[a-z]/i)) { searchTermEscaped = `\\b${searchTermEscaped}` } if (searchTermEscaped[searchTermEscaped.length - 1].match((/[a-z]/i))) { searchTermEscaped = `${searchTermEscaped}\\b` } const searchTermRegexp = new RegExp(searchTermEscaped, flags) walk(document.body, searchTermRegexp, replaceValue, directMethod); } /* Traverses a node and its children to run the provided replacement method */ function walk(node, searchTerm, replaceValue, replaceMethod) { if (node.contentEditable == "true" || node.type == "textarea" || node.type == "input") { return; } let child, next; switch (node.nodeType) { case 1: /* ELEMENT_NODE */ case 9: /* DOCUMENT_NODE */ case 11: /* DOCUMENT_FRAGMENT_NODE */ child = node.firstChild; while (child) { next = child.nextSibling; walk(child, searchTerm, replaceValue, replaceMethod); child = next; } break; case 3: /* TEXT_NODE */ replaceMethod(node, searchTerm, replaceValue); } } /* Replaces every instance of the provided terms */ function directMethod(node, searchTerm, replaceValue) { node.nodeValue = node.nodeValue.replaceAll(searchTerm, replaceValue); } /* Replaces all pronouns */ function pronounMethod(node, searchTerm, pronoun) { const match = node.nodeValue.match(searchTerm); if (match == null) { return; } const isCapital = /P/.test(match[1]); let replaceValue = pronoun; if (isCapital) { replaceValue = capitalize(replaceValue); } node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); pronounMethod(node, searchTerm, pronoun); } /* Replaces all verbs, not used for third-person plural */ function verbMethod(node, searchTerm, options) { const match = node.nodeValue.match(searchTerm); if (match == null) { return; } const isCapital = /V/.test(match[1]); const category = match[2]; const verb = match[3]; let tense = match[4]; if (tense == null) { tense = "PAST"; } else { tense = tense.toUpperCase().replaceAll(" ", "_"); } let povIndex; switch (category) { case "B": /* B and b vary by POV, capitalized to indicate a named subject */ if (options.pov == "third") { povIndex = getPovIndex("third-singular", null); } else { povIndex = getPovIndex(options.pov, options); } break; case "b": povIndex = getPovIndex(options.pov, options); break; case "N": /* N and n are always third-person */ povIndex = getPovIndex("third-singular", null); break; case "n": povIndex = getPovIndex("third", options); } let replaceValue = verbsHelper.getConjugation(verbsData, verb, tense, povIndex); if (isCapital) { replaceValue = capitalize(replaceValue); } node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); verbMethod(node, searchTerm, options); } /* Allows for first, second, and third-person POV to read differently */ function exceptionMethod(node, searchTerm, options) { const match = node.nodeValue.match(searchTerm); if (match == null) { return; } let replaceValue; if (options.pov == "third") { replaceValue = match[2]; } else { replaceValue = match[1]; } node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); exceptionMethod(node, searchTerm, options); } /* Allows for custom words to be cut off */ function cutMethod(node, searchTerm, unused) { const match = node.nodeValue.match(searchTerm); if (match == null) { return; } const target = match[1]; const amount = match[2]; if (target.length <= Math.abs(amount)) { node.nodeValue = node.nodeValue.replace(searchTerm, target); cutMethod(node, searchTerm, unused); return; } let replaceValue; if (amount < 0) { replaceValue = target.slice(0, amount); } else { replaceValue = target.slice(amount); } node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); cutMethod(node, searchTerm, unused); } /* Allows for custom words to be capitalized */ function capitalMethod(node, searchTerm, unused) { const match = node.nodeValue.match(searchTerm); if (match == null) { return; } const style = match[1]; const target = match[2]; const isLowerCase = /c/.test(style.slice(0,1)); // cap let replaceValue; if (isLowerCase) { replaceValue = target.toLowerCase(); } else { const isStartCase = /a/.test(style.slice(1,2)); // Cap if (isStartCase) { replaceValue = capitalize(target); } else { // CAP replaceValue = target.toUpperCase(); } } node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); capitalMethod(node, searchTerm, unused); } /* Allows for custom words to be mirrored */ function mirrorMethod(node, searchTerm, unused) { const match = node.nodeValue.match(searchTerm); if (match == null) { return; } const target = match[1]; let replaceValue = target.split('').reverse().join(''); node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); mirrorMethod(node, searchTerm, unused); } /* Returns the input word, capitalized */ function capitalize(word) { return word.charAt(0).toUpperCase() + word.slice(1); } /* Determines the index for point-of-view to be used when conjugating verbs */ function getPovIndex(pov, options) { switch (pov) { case "first": return 0; case "second": return 1; case "third-singular": return 2; case "third": if (getPronouns(options.preset, options.other).plurality == "singular") { return 2; } else { /* Plurality is plural, "they go" */ return 5; } return getThirdIndex(options); } } replaceAll();