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() { browser.storage.local.get(null, (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)); 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); } }); } /* 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 isPlural = getPronouns(options.preset, options.other).plurality == "plural"; if (isPlural) { walk(document.body, /([Vv])r([Nn])\/([\w\s]+)\/([\w\s]+)\//, options, pluralPrnVerbMethod); } else { walk(document.body, /([Vv])r[Nn]\/([\w\s]+)\/([\w\s]+)\//, options, prnVerbMethod); } const isPluralThird = isPlural && options.pov == "third"; if (isPluralThird) { walk(document.body, /([Vv])r([Bb])\/([\w\s]+)\/([\w\s]+)\//, options, pluralThirdVerbMethod); } else { walk(document.body, /([Vv])r[Bb]\/([\w\s]+)\/([\w\s]+)\//, 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); replacePronounSet("Prn/s", "prn/s", pronouns["subjective"]); replacePronounSet("Prn/o", "prn/o", pronouns["objective"]); replacePronounSet("Prn/p", "prn/p", pronouns["possessive"]); replacePronounSet("Prn/a", "prn/a", pronouns["adjective"]); replacePronounSet("Prn/r", "prn/r", pronouns["reflexive"]); replacePronounSet("Prn/H", "prn/H", pronouns["honorific-abbr"]); replacePronounSet("Prn/h", "prn/h", pronouns["honorific"]); replacePronounSet("Prn/N", "prn/N", pronouns["adult"]); replacePronounSet("Prn/n", "prn/n", pronouns["youth"]); replacePronounSet("Prn/F", "prn/f", pronouns["parent"]); replacePronounSet("Prn/f", "prn/f", pronouns["child"]); replacePronounSet("Prn/k", "prn/k", pronouns["sibling"]); replacePronounSet("Prn/m", "prn/m", pronouns["married"]); replacePronounSet("Prn/d", "prn/d", pronouns["dating"]); } /* 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 pronoun keys in capital and lowercase with their equivalent pronouns */ function replacePronounSet(keyCapital, key, pronoun) { escapeAndReplace(keyCapital, capitalize(pronoun), true); escapeAndReplace(key, pronoun, true); } /* Replaces all point-of-view keys */ function replacePov(options) { let pronouns; if (options.pov == "third") { pronouns = getPronouns(options.preset, options.other); replacePronounSet("Pov/S", "pov/S", options.name); replacePronounSet("Pov/O", "pov/O", options.name); replacePronounSet("Pov/P", "pov/P", options.name + "'s"); replacePronounSet("Pov/A", "pov/A", options.name + "'s"); replacePronounSet("Pov/R", "pov/R", options.name + "'s self"); } else { pronouns = getPronouns(options.pov, null); replacePronounSet("Pov/S", "pov/S", pronouns["subjective"]); replacePronounSet("Pov/O", "pov/O", pronouns["objective"]); replacePronounSet("Pov/P", "pov/P", pronouns["possessive"]); replacePronounSet("Pov/A", "pov/A", pronouns["adjective"]); replacePronounSet("Pov/R", "pov/R", pronouns["reflexive"]); } replacePronounSet("Pov/s", "pov/s", pronouns["subjective"]); replacePronounSet("Pov/o", "pov/o", pronouns["objective"]); replacePronounSet("Pov/p", "pov/p", pronouns["possessive"]); replacePronounSet("Pov/a", "pov/a", pronouns["adjective"]); replacePronounSet("Pov/r", "pov/r", pronouns["reflexive"]); } /* Replaces plural point-of-view keys */ function replacePlv(options) { let pronouns = getPronouns(options.pov + "-plural", null); replacePronounSet("Plv/s", "plv/s", pronouns["subjective"]); replacePronounSet("Plv/o", "plv/o", pronouns["objective"]); replacePronounSet("Plv/p", "plv/p", pronouns["possessive"]); replacePronounSet("Plv/a", "plv/a", pronouns["adjective"]); replacePronounSet("Plv/r", "plv/r", pronouns["reflexive"]); } /* Replaces additional terms specified by the user */ function replaceAlso(options) { Object.entries(options.also).forEach(([key, value]) => { escapeAndReplace(key, value, true); }); } /* 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 all of the provided terms in a node */ function directMethod(node, searchTerm, replaceValue) { node.nodeValue = node.nodeValue.replaceAll(searchTerm, replaceValue); } /* Replaces all verbs, not used for third-person plural */ /* TODO Is the "options" variable here readable? It's not replaceValue and that seems bad */ function verbMethod(node, searchTerm, options) { let match = node.nodeValue.match(searchTerm); if (match == null) { return; } const isCapital = /V/.test(match[1]); const verb = match[2]; let tense = getTense(match[3]); const isTenseProvided = tense != null; if (!isTenseProvided) { tense = "PAST"; } let replaceValue = verbsHelper.getConjugation(verbsData, verb, tense, getPovIndex(options)); if (isCapital) { replaceValue = capitalize(replaceValue); } if (isTenseProvided) { node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); } else { const searchTermShortVerb = /([Vv])r[Bb]\/([\w\s]+)\// node.nodeValue = node.nodeValue.replace(searchTermShortVerb, replaceValue); } verbMethod(node, searchTerm, options); } /* Replaces all verbs, used only for third-person plural */ function pluralThirdVerbMethod(node, searchTerm, options) { let match = node.nodeValue.match(searchTerm); if (match == null) { return; } const isCapital = /V/.test(match[1]); const isNamedActor = /B/.test(match[2]); const verb = match[3]; let tense = getTense(match[4]); const isTenseProvided = tense != null; if (!isTenseProvided) { tense = "PAST"; } let replaceValue; if (isNamedActor) { replaceValue = verbsHelper.getConjugation(verbsData, verb, tense, 2); } else { replaceValue = verbsHelper.getConjugation(verbsData, verb, tense, getPovIndex(options)); } if (isCapital) { replaceValue = capitalize(replaceValue); } if (isTenseProvided) { node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); } else { const searchTermShortVerb = /([Vv])r([Bb])\/([\w\s]+)\// node.nodeValue = node.nodeValue.replace(searchTermShortVerb, replaceValue); } pluralThirdVerbMethod(node, searchTerm, options); } /* Replaces verbs by pronoun rather than POV, not used for plural */ function prnVerbMethod(node, searchTerm, options) { let match = node.nodeValue.match(searchTerm); if (match == null) { return; } const isCapital = /V/.test(match[1]); const verb = match[2]; let tense = getTense(match[3]); const isTenseProvided = tense != null; if (!isTenseProvided) { tense = "PAST"; } let replaceValue = verbsHelper.getConjugation(verbsData, verb, tense, 2); if (isCapital) { replaceValue = capitalize(replaceValue); } if (isTenseProvided) { node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); } else { const searchTermShortVerb = /([Vv])r[Bb]\/([\w\s]+)\// node.nodeValue = node.nodeValue.replace(searchTermShortVerb, replaceValue); } prnVerbMethod(node, searchTerm, options); } /* Replaces verbs by pronoun rather than POV, used only for plural */ function pluralPrnVerbMethod(node, searchTerm, options) { let match = node.nodeValue.match(searchTerm); if (match == null) { return; } const isCapital = /V/.test(match[1]); const isNamedActor = /N/.test(match[2]); const verb = match[3]; let tense = getTense(match[4]); const isTenseProvided = tense != null; if (!isTenseProvided) { tense = "PAST"; } let replaceValue; if (isNamedActor) { replaceValue = verbsHelper.getConjugation(verbsData, verb, tense, 2); } else { replaceValue = verbsHelper.getConjugation(verbsData, verb, tense, 5); } if (isCapital) { replaceValue = capitalize(replaceValue); } if (isTenseProvided) { node.nodeValue = node.nodeValue.replace(searchTerm, replaceValue); } else { const searchTermShortVerb = /([Vv])r([Bb])\/([\w\s]+)\// node.nodeValue = node.nodeValue.replace(searchTermShortVerb, replaceValue); } pluralPrnVerbMethod(node, searchTerm, options); } /* 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(options) { switch (options.pov) { case "first": return 0; case "second": return 1; case "third": if (getPronouns(options.preset, options.other).plurality == "singular") { return 2; } else { /* Plurality is plural, "they go" */ return 5; } } } /* Gets the verb tense from a regexp match */ function getTense(match) { const tense = match.toUpperCase().replaceAll(" ", "_"); const tenses = [ // SIMPLE 'SIMPLE_PAST', 'PAST', 'SIMPLE_PRESENT', 'PRESENT', 'SIMPLE_FUTURE', 'FUTURE', // PROGRESSIVE 'PROGRESSIVE_PAST', 'PROGRESSIVE_PRESENT', 'PROGRESSIVE_FUTURE', // PERFECT 'PERFECT_PAST', 'PERFECT_PRESENT', 'PERFECT_FUTURE', // PERFECT PROGRESSIVE 'PERFECT_PROGRESSIVE_PAST', 'PERFECT_PROGRESSIVE_PRESENT', 'PERFECT_PROGRESSIVE_FUTURE', // PARTICIPLE 'PARTICIPLE_PRESENT', 'PARTICIPLE_PAST', ]; if (tenses.indexOf(tense) == -1) { return null; } else { return tense; } } replaceAll();