Textarea Typograf

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

当前为 2021-06-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, glebkema (https://github.com/glebkema)
  7. // @license MIT
  8. // @version 0.4.24
  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_ENDINGS = 'endings';
  22. const MODE_EXCEPTIONS = 'exceptions';
  23. const MODE_EXTRA_PREFIXES = 'extraPrefixes';
  24. const MODE_NO_CAPITAL_LETTER = 'noCapitalLetter';
  25. const MODE_NO_PREFIXES = 'noPrefixes';
  26. const MODE_NO_SUFFIXES = 'noSuffixes';
  27. const MODE_STANDARD = 'standard';
  28.  
  29. class Typograf {
  30. run(element) {
  31. if (element && 'textarea' === element.tagName.toLowerCase() && element.value) {
  32. const start = element.selectionStart;
  33. const end = element.selectionEnd;
  34. if (start === end) {
  35. element.value = this.improve(element.value);
  36. } else {
  37. const selected = element.value.substring(start, end);
  38. const theLength = element.value.length;
  39. element.value = element.value.substring(0, start)
  40. + this.improve(selected) + element.value.substring(end, theLength);
  41. }
  42. } else {
  43. // console.info('Start editing a non-empty textarea before calling the script');
  44. }
  45. }
  46.  
  47. improve(text) {
  48. if (text) {
  49. text = this.improveDash(text);
  50. text = this.improveQuotes(text);
  51. text = this.improveSmile(text);
  52. text = this.improveYo(text);
  53. }
  54. return text;
  55. }
  56.  
  57. improveDash(text) {
  58. text = text.replace(/ - /g, ' — ');
  59. return text;
  60. }
  61.  
  62. improveQuotes(text) {
  63. text = text.replace(/(?<=^|[(\s])"/g, '«');
  64. text = text.replace(/"(?=$|[.,;:!?)\s])/g, '»');
  65. return text;
  66. }
  67.  
  68. improveSmile(text) {
  69. text = text.replace(/([:;])[—oо]?([D)(|])/g, '$1-$2');
  70. return text;
  71. }
  72.  
  73. improveYo(text) {
  74. // verbs - list of the cores (with a capital letter and yo)
  75. text = this.improveYoVerb(text, MODE_EXCEPTIONS,
  76. 'Льё,Мнё,Рвё,Трё');
  77. text = this.improveYoVerb(text, MODE_EXTRA_PREFIXES,
  78. 'Берё,Вернё,Даё,Орё,Плывё,Поё,Стаё');
  79. text = this.improveYoVerb(text, MODE_NO_CAPITAL_LETTER,
  80. 'Йдё,Ймё');
  81. text = this.improveYoVerb(text, MODE_NO_PREFIXES,
  82. 'Идё,Льнё,Начнё,Обернё,Придё,Прильнё,Улыбнё');
  83. text = this.improveYoVerb(text, MODE_NO_SUFFIXES,
  84. 'Шёл');
  85. text = this.improveYoVerb(text, MODE_STANDARD,
  86. 'Бьё,Ведё,Везё,Врё,Вьё,Ждё,Жмё,Жрё,Несё,Прё,Пьё,Ткнё,Чтё,Шлё,Шьё');
  87.  
  88. // verbs - fix the exceptions
  89. text = this.replaceException(text, 'Расстаёт', '(?![а-дж-я])');
  90. text = this.replaceException(text, 'Шлём');
  91.  
  92. // list of the words (with a capital letter and yo)
  93. text = this.improveYoWord(text, null,
  94. 'Её,Ещё,Моё,Неё,Своё,Твоё');
  95. text = this.improveYoWord(text, null,
  96. 'Вдвоём,Втроём,Объём,Остриём,Приём,Причём,Огнём,Своём,Твоём');
  97. text = this.improveYoWord(text, null,
  98. 'Василёк,Мотылёк,Огонёк,Пенёк,Ручеёк');
  99. text = this.improveYoWord(text, null,
  100. 'Затёк,Натёк,Потёк');
  101. text = this.improveYoWord(text, null,
  102. 'Грёза,Грёзы,Слёзы');
  103. text = this.improveYoWord(text, MODE_ENDINGS,
  104. 'Партнёр,Проём');
  105. text = this.improveYoWord(text, MODE_ENDINGS,
  106. 'Зачёт,Отчёт,Расчёт,Учёт');
  107.  
  108. return text;
  109. }
  110.  
  111. improveYoVerb(text, mode, list) {
  112. return this.iterator(text, mode, list, this.replaceYoVerb.bind(this));
  113. }
  114.  
  115. improveYoWord(text, mode, list) {
  116. return this.iterator(text, mode, list, this.replaceYoWord.bind(this));
  117. }
  118.  
  119. iterator(text, mode, list, callback) {
  120. if ('string' === typeof list) {
  121. list = list.split(',');
  122. }
  123. for (let i = 0; i < list.length; i++) {
  124. const replace = list[i].trim();
  125. if (replace) {
  126. const find = this.removeAllYo(replace);
  127. text = callback(text, mode, find, replace);
  128. }
  129. }
  130. return text;
  131. }
  132.  
  133. removeAllYo(text) {
  134. return text.replace(/ё/g, 'е').replace(/Ё/g, 'Е');
  135. }
  136.  
  137. replaceException(text, exception, lookAhead = '') {
  138. const replace = this.removeAllYo(exception);
  139. let regex = new RegExp(exception + lookAhead, 'g');
  140. text = text.replace(regex, replace);
  141. regex = new RegExp('(?<![А-Яa-я])' + exception.toLowerCase() + lookAhead, 'g');
  142. text = text.replace(regex, replace.toLowerCase());
  143. return text;
  144. }
  145.  
  146. replaceYo(text, find, replace,
  147. lookBehind = '(?<![б-джзй-нп-тф-я])', // +аеиоу
  148. // lookAhead = '(?=[мтш])'
  149. lookAhead = '(?=(?:м|мся|т|те|тесь|тся|шь|шься)(?:[^а-яё]|$))'
  150. ) {
  151. let regex;
  152. let findLowerCase = find.toLowerCase();
  153. // NB: \b doesn't work for russian words
  154. // 1) starts with a capital letter = just a begining of the word
  155. if (find !== findLowerCase) {
  156. regex = new RegExp(find + lookAhead, 'g');
  157. text = text.replace(regex, replace);
  158. }
  159. // 2) in lowercase = with a prefix ahead or without it
  160. regex = new RegExp(lookBehind + findLowerCase + lookAhead, 'gi');
  161. text = text.replace(regex, replace.toLowerCase());
  162. return text;
  163. }
  164.  
  165. replaceYoVerb(text, mode, find, replace) {
  166. if (MODE_EXCEPTIONS === mode) {
  167. return this.replaceYo(text, find, replace,
  168. '(?<![б-джзй-нп-тф-я]|зе|ко|фе)' ); // +аеиоу -"зельем" -"корвет" -"фельетон"
  169. // '(?=[мтш])(?!мо)(?!ть)'); // -"мнемо" -"треть"
  170. }
  171. if (MODE_EXTRA_PREFIXES === mode) {
  172. let lookBehind = '(?<![гжк-нпрф-я])'; // +аеиоу +бвдзст
  173. if ('Даё' === replace) {
  174. lookBehind = '(?<![гжк-нпрф-ъь-я])'; // +ы
  175. } else if ('Стаё' === replace) {
  176. lookBehind = '(?<![гжк-нпрф-я]|ра)'; // -"вы/за/от/подрастает"
  177. }
  178. return this.replaceYo(text, find, replace, lookBehind);
  179. }
  180. if (MODE_NO_CAPITAL_LETTER === mode) {
  181. return this.replaceYo(text, find.toLowerCase(), replace);
  182. }
  183. if (MODE_NO_PREFIXES === mode) {
  184. return this.replaceYo(text, find, replace,
  185. '(?<![А-Яа-яЁё])');
  186. }
  187. if (MODE_NO_SUFFIXES === mode) {
  188. return this.replaceYo(text, find, replace,
  189. '(?<![б-джзй-нп-тф-я])', // +аеиоу
  190. '(?![а-яё])');
  191. }
  192. // MODE_STANDARD
  193. return this.replaceYo(text, find, replace);
  194. }
  195.  
  196. replaceYoWord(text, mode, find, replace) {
  197. if (MODE_ENDINGS === mode) {
  198. return this.replaceYo(text, find, replace,
  199. '(?<![А-Яа-яЁё])',
  200. '');
  201. }
  202. return this.replaceYo(text, find, replace,
  203. '(?<![А-Яа-яЁё])',
  204. '(?![а-яё])');
  205. }
  206. }
  207.  
  208. // if it's a browser, not a test
  209. if ('undefined' !== typeof document) {
  210. let typograf = new Typograf();
  211. typograf.run(document.activeElement);
  212. }
  213.  
  214. // if it's a test by Node.js
  215. if (module) {
  216. module.exports = {
  217. Typograf: Typograf,
  218. };
  219. } else {
  220. var module; // hack for Tampermonkey's eslint
  221. }