Grammar Fixer

Detect and fix your/you're, there/their/they're, and contraction mistakes. With Web Discord Support

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

Advertisement:

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

Advertisement:

// ==UserScript==
// @name         Grammar Fixer
// @namespace    https://forums.somethingawful.com
// @version      1.1.0
// @description  Detect and fix your/you're, there/their/they're, and contraction mistakes. With Web Discord Support
// @match        *://*/*
// @grant        none
// @author       Chunjee
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    // Set to true to auto-correct, false to just highlight
    const AUTO_FIX = true;
    const WORD_BOUNDARY_PATTERN = (word) => `(^|[\\s.,!?;:'"()\\[\\]{}])(${word})(?=[\\s.,!?;:'"()\\[\\]{}]|$)`;

    const patterns = [
        // there -> their patterns (possessive contexts)
        { wrong: /\bthere\s+(money|car|house|dog|cat|problem|fault|idea|plan|way|own|job|life|family|friend|work|business|account)\b/gi,
          correct: "their $1",
          description: "there → their" },

        // their -> they're patterns
        { wrong: /\btheir\s+(going|not|sure|probably|definitely|investing|working|coming|leaving|staying|running|walking|talking|thinking|planning|doing|making|taking|giving|getting|looking|feeling|being|having|saying|trying)\b/gi,
         correct: "they're $1",
         description: "their → they're" },

        // there -> they're patterns
        { wrong: /\bthere\s+(going|not|sure|probably|definitely|always|never)\b/gi,
          correct: "they're $1",
          description: "there → they're" },

        // your -> you're patterns
        { wrong: /\byour\s+(trying|going|not|really|probably|definitely|always|never)\b/gi,
          correct: "you're $1",
          description: "your → you're" },

        // Common contractions missing apostrophes
        { wrong: /\bcant\b/gi,
          correct: "can't",
          description: "cant → can't" },

        { wrong: /\bdont\b/gi,
          correct: "don't",
          description: "dont → don't" },

        { wrong: /\bwont\b/gi,
          correct: "won't",
          description: "wont → won't" },

        { wrong: /\bdidnt\b/gi,
          correct: "didn't",
          description: "didnt → didn't" },

        { wrong: /\bcouldnt\b/gi,
          correct: "couldn't",
          description: "couldnt → couldn't" },

        { wrong: /\bwouldnt\b/gi,
          correct: "wouldn't",
          description: "wouldnt → wouldn't" },

        { wrong: /\bshouldnt\b/gi,
          correct: "shouldn't",
          description: "shouldnt → shouldn't" },

        { wrong: /\bisnt\b/gi,
          correct: "isn't",
          description: "isnt → isn't" },

        { wrong: /\barent\b/gi,
          correct: "aren't",
          description: "arent → aren't" },

        { wrong: /\bwasnt\b/gi,
          correct: "wasn't",
          description: "wasnt → wasn't" },

        { wrong: /\bwerent\b/gi,
          correct: "weren't",
          description: "werent → weren't" },

        { wrong: /\bhasnt\b/gi,
          correct: "hasn't",
          description: "hasnt → hasn't" },

        { wrong: /\bhavent\b/gi,
          correct: "haven't",
          description: "havent → haven't" },

        { wrong: /\bhadnt\b/gi,
          correct: "hadn't",
          description: "hadnt → hadn't" },
        { wrong: /\bthats\b/gi,
         correct: "that's",
         description: "thats → that's" },

        { wrong: /\bwhats\b/gi,
         correct: "what's",
         description: "whats → what's" },

        { wrong: /\blets\b/gi,
         correct: "let's",
         description: "lets → let's" },

        { wrong: /\bits\s+(not|been|going|time)\b/gi,
         correct: "it's $1",
         description: "its → it's" },

        { wrong: /\bim\b/gi,
         correct: "I'm",
         description: "im → I'm" },

        { wrong: /\bive\b/gi,
         correct: "I've",
         description: "ive → I've" },

        { wrong: /\bwhos\b/gi,
         correct: "who's",
         description: "whos → who's" },

        { wrong: /\bhes\b/gi,
         correct: "he's",
         description: "hes → he's" },

        { wrong: /\bshes\b/gi,
         correct: "she's",
         description: "shes → she's" },

        { wrong: /\btheyll\b/gi,
         correct: "they'll",
         description: "theyll → they'll" },

        { wrong: /\byoull\b/gi,
         correct: "you'll",
         description: "youll → you'll" }
    ];

    // Check if we're on Discord
    const isDiscord = window.location.hostname.includes('discord.com');

    function processTextNode(node) {
        let text = node.textContent;
        let modified = false;

        patterns.forEach(pattern => {
            if (pattern.wrong.test(text)) {
                if (AUTO_FIX) {
                    text = text.replace(pattern.wrong, pattern.correct);
                    modified = true;
                } else {
                    // Highlight mode
                    const parent = node.parentNode;
                    const wrapper = document.createElement('span');
                    wrapper.innerHTML = text.replace(pattern.wrong,
                        '<mark style="background-color: yellow; cursor: help;" title="' +
                        pattern.description + '">$&</mark>');
                    parent.replaceChild(wrapper, node);
                    modified = true;
                }
            }
        });

        if (AUTO_FIX && modified) {
            node.textContent = text;
        }
    }

    function walkNodes(node) {
        if (node.nodeType === Node.TEXT_NODE) {
            processTextNode(node);
        } else if (node.nodeType === Node.ELEMENT_NODE &&
                   !['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT', 'CODE', 'PRE'].includes(node.tagName)) {
            Array.from(node.childNodes).forEach(walkNodes);
        }
    }

    // Discord-specific processing
    function replaceInElement(el) {
        el.childNodes.forEach(node => {
            if (node.nodeType === Node.TEXT_NODE) {
                processTextNode(node);
            } else if (node.nodeType === Node.ELEMENT_NODE && !['CODE', 'PRE'].includes(node.tagName)) {
                replaceInElement(node);
            }
        });
    }

    if (isDiscord) {
        // Discord-specific observer
        const observer = new MutationObserver(() => {
            document.querySelectorAll('[id^=message-content-]').forEach(el => {
                if (el.dataset.grammarFixed) return;
                replaceInElement(el);
                el.dataset.grammarFixed = 'true';
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // Process existing Discord messages
        document.querySelectorAll('[id^=message-content-]').forEach(el => {
            replaceInElement(el);
            el.dataset.grammarFixed = 'true';
        });
    } else {
        // General site processing
        walkNodes(document.body);

        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        walkNodes(node);
                    }
                });
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }
})();