Textarea Typograf

Replaces hyphens, quotation marks, uncanonic smiles and "yo" in some russian words.

当前为 2021-10-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Textarea Typograf
  3. // @namespace https://github.com/glebkema/tampermonkey-textarea-typograf
  4. // @description Replaces hyphens, quotation marks, uncanonic smiles and "yo" in some russian words.
  5. // @author glebkema
  6. // @copyright 2020-2021, Gleb Kemarsky (https://github.com/glebkema)
  7. // @license MIT
  8. // @version 0.5.16
  9. // @match http://*/*
  10. // @match https://*/*
  11. // @grant none
  12. // @run-at context-menu
  13. // ==/UserScript==
  14.  
  15. // ==OpenUserJS==
  16. // @author glebkema
  17. // ==/OpenUserJS==
  18.  
  19. 'use strict';
  20.  
  21. const MODE_ANY = 'any';
  22. const MODE_ANY_BEGINNING = 'anyBeginning';
  23. const MODE_ANY_ENDING = 'anyEnding';
  24. const MODE_EXCEPTIONS = 'exceptions';
  25. const MODE_EXTRA_PREFIXES = 'extraPrefixes';
  26. const MODE_NO_CAPITAL_LETTER = 'noCapitalLetter';
  27. const MODE_NO_PREFIXES = 'noPrefixes';
  28. const MODE_NO_SUFFIXES = 'noSuffixes';
  29. const MODE_STANDARD = 'standard';
  30.  
  31. class Typograf {
  32. run(element) {
  33. if (element && 'textarea' === element.tagName.toLowerCase() && element.value) {
  34. const start = element.selectionStart;
  35. const end = element.selectionEnd;
  36. if (start === end) {
  37. element.value = this.improve(element.value);
  38. } else {
  39. const selected = element.value.substring(start, end);
  40. const theLength = element.value.length;
  41. element.value = element.value.substring(0, start)
  42. + this.improve(selected) + element.value.substring(end, theLength);
  43. }
  44. } else {
  45. // console.info('Start editing a non-empty textarea before calling the script');
  46. }
  47. }
  48.  
  49. improve(text) {
  50. if (text) {
  51. text = this.improveDash(text);
  52. text = this.improveQuotes(text);
  53. text = this.improveSmile(text);
  54. text = this.improveYo(text);
  55. }
  56. return text;
  57. }
  58.  
  59. improveDash(text) {
  60. text = text.replace(/ - /g, ' — ');
  61. return text;
  62. }
  63.  
  64. improveQuotes(text) {
  65. // use only one type + only external if two stand together
  66. // text = text.replace(/(?<=^|[(\s])["„“]/g, '«');
  67. // text = text.replace(/["„“](?=$|[.,;:!?)\s])/g, '»');
  68.  
  69. // use only one type
  70. text = text.replace(/["„“](?=["„“«]*[\wа-яё(])/gi, '«');
  71. text = text.replace(/(?<=[\wа-яё).!?]["„“»]*)["„“]/gi, '»');
  72.  
  73. // nested quotes
  74. // (?:«[^»]*)([«"])([^"»]*)(["»])
  75. // (?=(?:(?<!\w)["«](\w.*?)["»](?!\w))) https://stackoverflow.com/a/39706568/6263942
  76. // («([^«»]|(?R))*») https://stackoverflow.com/a/14952740/6263942
  77. // «((?>[^«»]+|(?R))*)» https://stackoverflow.com/a/26386070/6263942
  78. // «([^«»]*+(?:(?R)[^«»]*)*+)» https://stackoverflow.com/a/26386070/6263942
  79. // «[^»]*(?:(«)[^«»]*+(»)[^«]*)+»
  80. do {
  81. var old = text;
  82. text = text.replace(/(?<=«[^»]*)«(.*?)»/g, '„$1“');
  83. } while ( old !== text );
  84. return text;
  85. }
  86.  
  87. improveSmile(text) {
  88. text = text.replace(/([:;])[—oо]?([D)(|])/g, '$1-$2');
  89. return text;
  90. }
  91.  
  92. improveYo(text) {
  93. // verbs - list of the cores (with a capital letter and yo)
  94. text = this.improveYoVerb(text, MODE_EXCEPTIONS,
  95. 'Льё,Мнё,Рвё,Трё');
  96. text = this.improveYoVerb(text, MODE_EXTRA_PREFIXES,
  97. 'Берё,Вернё,Даё,Живё,Несё,Орё,Плывё,Поё,Ревё,Смеё,Стаё');
  98. text = this.improveYoVerb(text, MODE_NO_CAPITAL_LETTER,
  99. 'Йдё,Ймё');
  100. text = this.improveYoVerb(text, MODE_NO_PREFIXES,
  101. 'Идё,Начнё,Обернё,Придё,Улыбнё');
  102. text = this.improveYoVerb(text, MODE_NO_PREFIXES,
  103. 'Льнё,Прильнё');
  104. text = this.improveYoVerb(text, MODE_NO_SUFFIXES,
  105. 'Шёл');
  106. text = this.improveYoVerb(text, MODE_STANDARD,
  107. 'Бьё,Ведё,Везё,Врё,Вьё,Гнё,Дерё,Ждё,Жмё,Жрё,Прё,Пьё,Ткнё,Чтё,Шлё,Шьё');
  108.  
  109. // verbs - unsystematic cases
  110. let lookBehind = '(?<![гж-нпру-я])'; // +абвдеост, -ы
  111. text = this.replaceYo(text, 'Дерг', 'Дёрг', lookBehind, '(?![б-яё])'); // +а, -у
  112. text = this.replaceYo(text, 'Дерн', 'Дёрн', lookBehind, '(?![б-джзй-нп-тф-ъь-яё])'); // +аеиоуы (сущ. или глагол)
  113.  
  114. // verbs - fix the exceptions
  115. text = this.replaceException(text, 'Раздольём');
  116. text = this.replaceException(text, 'Расстаёт', '(?![а-дж-я])');
  117. text = this.replaceException(text, 'Шлём');
  118.  
  119. // list of the words (with a capital letter and yo)
  120. text = this.improveYoWord(text, null,
  121. 'Её,Ещё,Моё,Неё,Своё,Твоё');
  122. text = this.improveYoWord(text, null,
  123. 'Вдвоём,Втроём,Объём,Остриём,Приём,Причём,Своём,Твоём');
  124. text = this.improveYoWord(text, null,
  125. 'В моём,На моём,О моём'); // only with certain prepositions
  126. text = this.improveYoWord(text, null,
  127. 'Журавлём,Кораблём,Королём,Снегирём,Соловьём');
  128. text = this.improveYoWord(text, null,
  129. 'Копьё,Копьём');
  130. text = this.improveYoWord(text, null,
  131. 'Трёх,Четырём,Четырёх'); // "Трём" уже есть как глагол
  132. text = this.improveYoWord(text, null,
  133. 'Василёк,Мотылёк,Огонёк,Пенёк,Поперёк,Ручеёк');
  134. text = this.improveYoWord(text, null,
  135. 'Затёк,Натёк,Потёк');
  136. text = this.improveYoWord(text, null,
  137. 'Грёза,Грёзы,Слёзы');
  138. text = this.improveYoWord(text, null,
  139. 'Вперёд');
  140. text = this.improveYoWord(text, null,
  141. 'Насчёт');
  142. text = this.improveYoWord(text, MODE_ANY,
  143. 'Съёмк');
  144. text = this.improveYoWord(text, MODE_ANY_BEGINNING,
  145. 'варём');
  146. text = this.improveYoWord(text, MODE_ANY_ENDING,
  147. 'Актёр,Алён,Алёх,Алёш,Алфёр,Аматёр,Амёб,Анкетёр,Антрепренёр,Артём');
  148. text = this.improveYoWord(text, MODE_ANY_ENDING,
  149. 'Вертолёт,Звездолёт,Налёт,Отлёт,Полёт,Пролёт,Самолёт');
  150. text = this.improveYoWord(text, MODE_ANY_ENDING,
  151. 'Партнёр,Проём');
  152. text = this.improveYoWord(text, MODE_ANY_ENDING,
  153. 'Зачёт,Звездочёт,Отчёт,Почёт,Расчёт,Счёт,Учёт');
  154. text = this.improveYoWord(text, MODE_ANY_ENDING,
  155. 'Вёрстк,Расчёск,Чётк');
  156.  
  157. return text;
  158. }
  159.  
  160. improveYoVerb(text, mode, list) {
  161. return this.iterator(text, mode, list, this.replaceYoVerb.bind(this));
  162. }
  163.  
  164. improveYoWord(text, mode, list) {
  165. return this.iterator(text, mode, list, this.replaceYoWord.bind(this));
  166. }
  167.  
  168. iterator(text, mode, list, callback) {
  169. if ('string' === typeof list) {
  170. list = list.split(',');
  171. }
  172. for (let i = 0; i < list.length; i++) {
  173. const replace = list[i].trim();
  174. if (replace) {
  175. const find = this.removeAllYo(replace);
  176. text = callback(text, mode, find, replace);
  177. }
  178. }
  179. return text;
  180. }
  181.  
  182. removeAllYo(text) {
  183. return text.replace(/ё/g, 'е').replace(/Ё/g, 'Е');
  184. }
  185.  
  186. // restore the `e` instead of `yo`
  187. replaceException(text, exception, lookAhead = '') {
  188. const replace = this.removeAllYo(exception);
  189. let regex = new RegExp(exception + lookAhead, 'g');
  190. text = text.replace(regex, replace);
  191. regex = new RegExp('(?<![А-Яa-я])' + exception.toLowerCase() + lookAhead, 'g');
  192. text = text.replace(regex, replace.toLowerCase());
  193. return text;
  194. }
  195.  
  196. replaceYo(text, find, replace,
  197. lookBehind = '(?<![б-джзй-нп-тф-я])', // +аеиоу
  198. // lookAhead = '(?=[мтш])'
  199. lookAhead = '(?=(?:м|мся|т|те|тесь|тся|шь|шься)(?:[^а-яё]|$))'
  200. ) {
  201. let regex;
  202. let findLowerCase = find.toLowerCase();
  203. // NB: \b doesn't work for russian words
  204. // 1) starts with a capital letter = just a begining of the word
  205. if (find !== findLowerCase) {
  206. regex = new RegExp(find + lookAhead, 'g');
  207. text = text.replace(regex, replace);
  208. }
  209. // 2) in lowercase = with a prefix ahead or without it
  210. regex = new RegExp(lookBehind + findLowerCase + lookAhead, 'gi');
  211. text = text.replace(regex, replace.toLowerCase());
  212. return text;
  213. }
  214.  
  215. replaceYoVerb(text, mode, find, replace) {
  216. if (MODE_EXCEPTIONS === mode) {
  217. return this.replaceYo(text, find, replace,
  218. '(?<![б-джзй-нп-тф-я]|зе|ко|фе)' ); // +аеиоу -"зельем" -"корвет" -"фельетон"
  219. // '(?=[мтш])(?!мо)(?!ть)'); // -"мнемо" -"треть"
  220. }
  221. if (MODE_EXTRA_PREFIXES === mode) {
  222. let lookBehind = '(?<![гжк-нпрф-я])'; // +аеиоу +бвдзст
  223. if ('Даё' === replace) {
  224. lookBehind = '(?<![гжк-нпрф-ъь-я])'; // +ы
  225. } else if ('Стаё' === replace) {
  226. lookBehind = '(?<![гжк-нпрф-я]|ра)'; // -"вы/за/от/подрастает"
  227. }
  228. return this.replaceYo(text, find, replace, lookBehind);
  229. }
  230. if (MODE_NO_CAPITAL_LETTER === mode) {
  231. return this.replaceYo(text, find.toLowerCase(), replace);
  232. }
  233. if (MODE_NO_PREFIXES === mode) {
  234. return this.replaceYo(text, find, replace,
  235. '(?<![А-Яа-яЁё])');
  236. }
  237. if (MODE_NO_SUFFIXES === mode) {
  238. return this.replaceYo(text, find, replace,
  239. '(?<![б-джзй-нп-тф-я])', // +аеиоу
  240. '(?![а-яё])');
  241. }
  242. // MODE_STANDARD
  243. return this.replaceYo(text, find, replace);
  244. }
  245.  
  246. replaceYoWord(text, mode, find, replace) {
  247. if (MODE_ANY === mode) {
  248. return this.replaceYo(text, find, replace,
  249. '',
  250. '(?=[а-я])');
  251. }
  252. if (MODE_ANY_BEGINNING === mode) {
  253. return this.replaceYo(text, find, replace,
  254. '',
  255. '(?![а-яё])');
  256. }
  257. if (MODE_ANY_ENDING === mode) {
  258. return this.replaceYo(text, find, replace,
  259. '(?<![А-Яа-яЁё])',
  260. '');
  261. }
  262. return this.replaceYo(text, find, replace,
  263. '(?<![А-Яа-яЁё])',
  264. '(?![а-яё])');
  265. }
  266. }
  267.  
  268. // if it's a browser, not a test
  269. if ('undefined' !== typeof document) {
  270. let typograf = new Typograf();
  271. typograf.run(document.activeElement);
  272. }
  273.  
  274. // if it's a test by Node.js
  275. if (module) {
  276. module.exports = {
  277. Typograf: Typograf,
  278. };
  279. } else {
  280. var module; // hack for Tampermonkey's eslint
  281. }