您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Replaces hyphens, quotation marks, uncanonic smiles and "yo" in some russian words.
- // ==UserScript==
- // @name Textarea Typograf
- // @namespace https://github.com/glebkema/tampermonkey-textarea-typograf
- // @description Replaces hyphens, quotation marks, uncanonic smiles and "yo" in some russian words.
- // @author glebkema
- // @copyright 2020-2024, Gleb Kemarsky (https://github.com/glebkema)
- // @license MIT
- // @version 0.7.12
- // @match http://*/*
- // @match https://*/*
- // @grant none
- // @run-at context-menu
- // ==/UserScript==
- // ==OpenUserJS==
- // @author glebkema
- // ==/OpenUserJS==
- 'use strict';
- class Typograf {
- MODE_ANY = 'any';
- MODE_ANY_BEGINNING = 'anyBeginning';
- MODE_ANY_BEGINNING_EXCEPT_O_AND_Y = 'anyBeginningExceptOAndY';
- MODE_ANY_BEGINNING_EXCEPT_Y = 'anyBeginningExceptY';
- MODE_ANY_ENDING = 'anyEnding';
- MODE_ANY_ENDING_EXCEPT_D = 'anyEndingExceptD';
- MODE_ANY_ENDING_EXCEPT_I_AND_SOFT_SIGN = 'anyEndingExceptIAndSoftSign';
- MODE_ANY_ENDING_EXCEPT_L = 'anyEndingExceptL';
- MODE_ANY_ENDING_EXCEPT_N = 'anyEndingExceptN';
- MODE_ANY_EXCEPT_I = 'anyExceptI';
- MODE_ANY_EXCEPT_K = 'anyExceptK';
- MODE_ANY_EXCEPT_R = 'anyExceptR';
- MODE_AS_IS = 'asIs';
- MODE_ENDINGS_1 = 'endings1';
- MODE_ENDINGS_2 = 'endings2';
- MODE_ENDINGS_3 = 'endings3';
- MODE_EXCEPTIONS = 'exceptions';
- MODE_EXTRA_PREFIXES = 'extraPrefixes';
- MODE_NO_CAPITAL_LETTER = 'noCapitalLetter';
- MODE_NO_PREFIXES = 'noPrefixes';
- MODE_NO_SUFFIXES = 'noSuffixes';
- MODE_STANDARD = 'standard';
- verbCores = {
- [this.MODE_EXCEPTIONS]: 'Льё,Мнё,Рвё,Трё',
- [this.MODE_EXTRA_PREFIXES]: 'Берё,Боднё,Вернё,Даё,Живё,Несё,Орё,Пасё,Плывё,Поё,Ревё,Смеё,Стаё',
- [this.MODE_NO_CAPITAL_LETTER]: 'Йдё,Ймё',
- [this.MODE_NO_PREFIXES]: 'Идё,Начнё,Обернё,Придаё,Придё,Улыбнё',
- [this.MODE_NO_SUFFIXES]: 'Берёгся,Шёл',
- [this.MODE_STANDARD]: 'Бережё,Блеснё,Блюдё,Блюё,Бьё,Ведё,Везё,Врё,Вьё,Гнё,Дерё,Ждё,Жмё,Жрё,Льнё,Прё,Пьё,Ткнё,Чтё,Шлё,Шьё',
- };
- words = {
- [this.MODE_AS_IS]:
- // alphabetically
- 'Бёдер,Белёк,Бельём,Бобёр,Бобылём,'
- + 'Взахлёб,Вперёд,'
- + 'Запёк,'
- + 'Копьё,Копьём,'
- + 'Отстранён,' // MODE_ENDINGS_3 для других форм этого слова
- + 'Предпочёл,Прочёл,'
- + 'Рулём,'
- + 'Твёрже,'
- // groups
- + 'Василёк,Мотылёк,Огонёк,Пенёк,Поперёк,Ручеёк,'
- + 'Вдвоём,Втроём,Объём,Остриём,Причём,Своём,Твоём,'
- + 'Грёза,Грёзы,Слёзы,'
- + 'Её,Ещё,Моё,Неё,Своё,Твоё,'
- + 'Журавлём,Кораблём,Королём,Снегирём,Соловьём,'
- + 'Затёк,Натёк,Потёк,'
- + 'Трёх,Четырём,Четырёх,', // "Трём" уже есть как глагол
- [this.MODE_ANY]: 'ёхонек,ёхоньк,ёшенек,ёшеньк,'
- + 'бомбёж,гиллёз,надёг,ощёк,счётн,уёмн,шёрстн,циллёз,ъёмкост,' // стёгивал,стёгнут,
- + 'Пролёт,Самолёт,'
- + 'Отчёт,Расчёт,'
- + 'Веретён,Гнёзд,Звёздн,Лёгочн,Лётчи,Надёжн,Налёт,Разъём,Съёмк,'
- // adjectives
- + 'бережённ,ворённ,мягчённ,ретённ,таённ,теплённ',
- [this.MODE_ANY_BEGINNING]: 'атырёв,атырём,варём,'
- + 'арьё,арьём,ерьё,ерьём,ырьё,ырьём,'
- + 'берёг', // NB: except as is: "берег моря"
- [this.MODE_ANY_BEGINNING_EXCEPT_O_AND_Y]:
- // adjectives
- 'Точён', // - сосредоточено
- [this.MODE_ANY_BEGINNING_EXCEPT_Y]:
- // adjectives
- 'несённ,'
- + 'тёкш,Тёрт,тёрш,'
- + 'Шёрстн',
- [this.MODE_ANY_ENDING]:
- // alphabetically
- 'Актёр,Алён,Алёх,Алёш,Алфёр,Аматёр,Амёб,Анкетёр,Антрепренёр,Артём,'
- + 'Бабёнк,Бабёф,Балансёр,Балдёж,Банкомёт,Баталёр,Бёдра,Бельёвщиц,Бережён,Берёз,Бесён,Бесслёзн,Бечёвк,Бечёво,Билетёр,Бирюлёв,Благословлён,Блёстк,Бобрён,Боксёр,Бородён,Боронён,Бочкарёв,'
- + 'Вёрстк,'
- + 'Ворьё,' // NB: ворьё,ворьём но подворье,подспорье
- + 'Жёстк,'
- + 'Лёгки,'
- + 'Партнёр,Проём,'
- + 'Расчёск,Ребён,'
- + 'Серьёз,'
- + 'Трёш,'
- + 'Чётк,'
- // cognate words
- + 'Вертолёт,Звездолёт,Отлёт,Перелёт,Полёт,'
- + 'Запёкш,Запечён,Испечён,'
- + 'Заём,Наём,'
- + 'Зачёт,Звездочёт,Почёт,Счёт,Учёт',
- [this.MODE_ANY_ENDING_EXCEPT_D]: 'Одёж',
- [this.MODE_ANY_ENDING_EXCEPT_I_AND_SOFT_SIGN]: 'Твёрд',
- [this.MODE_ANY_ENDING_EXCEPT_L]: 'Приём',
- [this.MODE_ANY_ENDING_EXCEPT_N]: 'Трёх',
- [this.MODE_ENDINGS_1]: 'Зелён', // [аоуык]
- [this.MODE_ENDINGS_2]: 'Учён', // [аоуы]
- [this.MODE_ENDINGS_3]: 'Включён,Остранён', // [н]
- [this.MODE_ANY_EXCEPT_I]: 'бретён,скажён,творён',
- [this.MODE_ANY_EXCEPT_K]: 'бъё',
- [this.MODE_ANY_EXCEPT_R]: 'омёт',
- }
- run(element) {
- if (element && 'textarea' === element.tagName.toLowerCase() && element.value) {
- const start = element.selectionStart;
- const end = element.selectionEnd;
- if (start === end) {
- element.value = this.improve(element.value);
- } else {
- const selected = element.value.substring(start, end);
- const theLength = element.value.length;
- element.value = element.value.substring(0, start)
- + this.improve(selected) + element.value.substring(end, theLength);
- }
- } else {
- // console.info('Start editing a non-empty textarea before calling the script');
- }
- }
- improve(text) {
- if (text) {
- text = this.improveDash(text);
- text = this.improveQuotes(text);
- text = this.improveSmile(text);
- text = this.improveYo(text);
- }
- return text;
- }
- improveDash(text) {
- text = text.replace(/ - /g, ' — ');
- return text;
- }
- improveQuotes(text) {
- // use only one type + only external if two stand together
- // text = text.replace(/(?<=^|[(\s])["„“]/g, '«');
- // text = text.replace(/["„“](?=$|[.,;:!?)\s])/g, '»');
- // use only one type
- text = text.replace(/["„“”](?=["„“”«]*[\wа-яё(])/gi, '«');
- text = text.replace(/(?<=[\wа-яё).!?]["„“”»]*)["„“”]/gi, '»');
- // nested quotes
- // (?:«[^»]*)([«"])([^"»]*)(["»])
- // (?=(?:(?<!\w)["«](\w.*?)["»](?!\w))) https://stackoverflow.com/a/39706568/6263942
- // («([^«»]|(?R))*») https://stackoverflow.com/a/14952740/6263942
- // «((?>[^«»]+|(?R))*)» https://stackoverflow.com/a/26386070/6263942
- // «([^«»]*+(?:(?R)[^«»]*)*+)» https://stackoverflow.com/a/26386070/6263942
- // «[^»]*(?:(«)[^«»]*+(»)[^«]*)+»
- do {
- var old = text;
- text = text.replace(/(?<=«[^»]*)«(.*?)»/g, '„$1“');
- } while ( old !== text );
- return text;
- }
- improveSmile(text) {
- // fix uncanonical smiles
- text = text.replace(/([:;])[—oо]?([D)(|])/g, '$1-$2');
- // remove the dot before the smile
- text = text.replace(/(?<=[А-ЯЁа-яё])\.\s*(?=[:;]-[D)(|])/g, ' ');
- return text;
- }
- improveYo(text) {
- // verbs - cores
- for (let mode in this.verbCores) {
- text = this.improveverbCores(text, mode, this.verbCores[mode]);
- }
- // verbs - unsystematic cases
- let lookBehind = '(?<![гж-нпру-я])'; // +абвдеост, -ы
- text = this.replaceYo(text, 'Дерг', 'Дёрг', lookBehind, '(?![б-яё])'); // +а, -у
- text = this.replaceYo(text, 'Дерн', 'Дёрн', lookBehind, '(?![б-джзй-нп-тф-ъь-яё])'); // +аеиоуы (сущ. или глагол)
- lookBehind = '(?<![бвге-зй-ру-я])'; // +адист
- text = this.replaceYo(text, 'Стег', 'Стёг', lookBehind, '(?!ал|ать|ну)');
- text = this.replaceYo(text, 'Стегнут', 'Стёгнут', lookBehind, '(?!ь)'); // NB: расстёгнутый
- text = this.replaceYo(text, 'черкива', 'чёркива', '(?<=[адты])', '(?=[елт])');
- // verbs - fix the exceptions
- lookBehind = '(?<![А-Яa-я])';
- text = this.replaceException(text, 'Раздольём', lookBehind);
- text = this.replaceException(text, 'Расстаёт', lookBehind, '(?![а-дж-я])');
- text = this.replaceException(text, 'Шлём', lookBehind);
- // words with a certain preposition
- // NB: before `words` for combinations like `Всё чётко`
- text = this.improveYoWord(text, null, 'В моём,На моём,О моём');
- text = this.improveYoWord(text, null, 'В нём,О нём,При нём');
- text = this.improveYoWord(text, null, 'Всё верно,Всё напрасно,Всё очень просто,Всё понятно,Всё правильно,Всё просто,Всё путём,Всё равно,Всё так же,Всё то же,Всё точно,Всё чётко,Всё ясно');
- text = this.improveYoWord(text, null, 'Всё, на чём/Всё, о чём/Всё, про что/Всё, с чем/Всё, что/Всё-таки', '/');
- text = this.improveYoWord(text, null, 'Ни на чём/Ни о чём/Ни при чём', '/');
- // words
- for (let mode in this.words) {
- text = this.improveYoWord(text, mode, this.words[mode]);
- }
- return text;
- }
- improveverbCores(text, mode, list, divider = ',') {
- return this.iterator(text, mode, list, divider, this.replaceverbCores.bind(this));
- }
- improveYoWord(text, mode, list, divider = ',') {
- return this.iterator(text, mode, list, divider, this.replaceYoWord.bind(this));
- }
- iterator(text, mode, list, divider, callback) {
- if ('string' === typeof list) {
- list = list.split(divider);
- }
- for (let i = 0; i < list.length; i++) {
- const replace = list[i].trim();
- if (replace) {
- const find = this.removeAllYo(replace);
- text = callback(text, mode, find, replace);
- }
- }
- return text;
- }
- removeAllYo(text) {
- return text.replace(/ё/g, 'е').replace(/Ё/g, 'Е');
- }
- // restore the `e` instead of `yo`
- replaceException(text, exception, lookBehind = '', lookAhead = '') {
- const replace = this.removeAllYo(exception);
- let regex = new RegExp(exception + lookAhead, 'g');
- text = text.replace(regex, replace);
- regex = new RegExp(lookBehind + exception.toLowerCase() + lookAhead, 'g');
- text = text.replace(regex, replace.toLowerCase());
- return text;
- }
- replaceYo(text, find, replace,
- lookBehind = '(?<![б-джзй-нп-тф-я])', // +аеиоу
- // lookAhead = '(?=[мтш])'
- lookAhead = '(?=(?:м|мся|т|те|тесь|тся|шь|шься)(?:[^а-яё]|$))'
- ) {
- let regex;
- let findLowerCase = find.toLowerCase();
- // NB: \b doesn't work for russian words
- // 1) starts with a capital letter = just a begining of the word
- if (find !== findLowerCase) {
- regex = new RegExp(find + lookAhead, 'g');
- text = text.replace(regex, replace);
- }
- // 2) in lowercase = with a prefix ahead or without it
- regex = new RegExp(lookBehind + findLowerCase + lookAhead, 'g' + ('' === lookBehind ? '' : 'i'));
- text = text.replace(regex, replace.toLowerCase());
- return text;
- }
- replaceverbCores(text, mode, find, replace) {
- if (this.MODE_EXCEPTIONS === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![б-джзй-нп-тф-я]|зе|ко|фе)' ); // +аеиоу -"зельем" -"корвет" -"фельетон"
- // '(?=[мтш])(?!мо)(?!ть)'); // -"мнемо" -"треть"
- }
- if (this.MODE_EXTRA_PREFIXES === mode) {
- let lookBehind = '(?<![гжк-нпрф-я])'; // +аеиоу +бвдзст
- if ('Даё' === replace) {
- lookBehind = '(?<![гжик-нпрф-ъь-я]|ла|па)'; // -и +ы >>> +"Придаёт" -"Обладает" -"Попадает"
- } else if ('Пасё' === replace) {
- lookBehind = '(?<![б-зй-нпртф-я])'; // "напасёшься"
- } else if ('Стаё' === replace) {
- lookBehind = '(?<![гжк-нпрф-я]|ра)'; // -"вы/за/от/подрастает"
- }
- return this.replaceYo(text, find, replace, lookBehind);
- }
- if (this.MODE_NO_CAPITAL_LETTER === mode) {
- return this.replaceYo(text, find.toLowerCase(), replace);
- }
- if (this.MODE_NO_PREFIXES === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![А-Яа-яЁё])');
- }
- if (this.MODE_NO_SUFFIXES === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![б-джзй-нпртф-я])', // +аеиоу +с
- '(?![а-яё])');
- }
- // MODE_STANDARD
- return this.replaceYo(text, find, replace);
- }
- replaceYoWord(text, mode, find, replace) {
- if (this.MODE_ANY === mode) {
- return this.replaceYo(text, find, replace,
- '',
- '');
- }
- if (this.MODE_ANY_BEGINNING === mode) {
- return this.replaceYo(text, find, replace,
- '',
- '(?![а-яё])');
- }
- if (this.MODE_ANY_BEGINNING_EXCEPT_O_AND_Y === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![оы])',
- '');
- }
- if (this.MODE_ANY_BEGINNING_EXCEPT_Y === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![ы])',
- '');
- }
- if (this.MODE_ANY_ENDING === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![А-Яа-яЁё])',
- '');
- }
- if (this.MODE_ANY_ENDING_EXCEPT_D === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![А-Яа-яЁё])',
- '(?![д])');
- }
- if (this.MODE_ANY_ENDING_EXCEPT_I_AND_SOFT_SIGN === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![А-Яа-яЁё])',
- '(?![иь])');
- }
- if (this.MODE_ANY_ENDING_EXCEPT_L === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![А-Яа-яЁё])',
- '(?![л])');
- }
- if (this.MODE_ANY_ENDING_EXCEPT_N === mode) {
- return this.replaceYo(text, find, replace,
- '(?<![А-Яа-яЁё])',
- '(?![н])');
- }
- if (this.MODE_ANY_EXCEPT_I === mode) {
- return this.replaceYo(text, find, replace,
- '',
- '(?![и])');
- }
- if (this.MODE_ANY_EXCEPT_K === mode) {
- return this.replaceYo(text, find, replace,
- '',
- '(?![к])');
- }
- if (this.MODE_ANY_EXCEPT_R === mode) {
- return this.replaceYo(text, find, replace,
- '',
- '(?![р])');
- }
- if (this.MODE_ENDINGS_1 === mode) {
- return this.replaceYo(text, find, replace,
- '',
- '(?=[аоуык])');
- }
- if (this.MODE_ENDINGS_2 === mode) {
- return this.replaceYo(text, find, replace,
- '',
- '(?=[аоуы])');
- }
- if (this.MODE_ENDINGS_3 === mode) {
- return this.replaceYo(text, find, replace,
- '',
- '(?=н)');
- }
- // MODE_AS_IS
- return this.replaceYo(text, find, replace,
- '(?<![А-Яа-яЁё])',
- '(?![а-яё])');
- }
- }
- // if it's a browser, not a test
- if ('undefined' !== typeof document) {
- let typograf = new Typograf();
- typograf.run(document.activeElement);
- }
- // if it's a test by Node.js
- if (module) {
- module.exports = {
- Typograf: Typograf,
- };
- } else {
- var module; // hack for Tampermonkey's eslint
- }