Yandex Translate Selection

Добавляет контекстное меню для перевода выделенного текста через Яндекс.Переводчик

  1. // ==UserScript==
  2. // @name Yandex Translate Selection
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Добавляет контекстное меню для перевода выделенного текста через Яндекс.Переводчик
  6. // @author motorrin
  7. // @match *://*/*
  8. // @grant GM_registerMenuCommand
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. // Создаем и добавляем стили
  15. const style = document.createElement('style');
  16. style.textContent = `
  17. .translate-menu-item {
  18. padding: 8px 12px;
  19. cursor: pointer;
  20. background: #292C2F;
  21. color: #fffff0;
  22. border: 1px solid #fffff0;
  23. border-radius: 4px;
  24. text-decoration: none;
  25. white-space: nowrap;
  26. font-size: 13px;
  27. }
  28. .translate-menu-item .ya-letter {
  29. color: #ff3333;
  30. }
  31. .translate-menu {
  32. position: fixed;
  33. background: transparent;
  34. z-index: 10000;
  35. }
  36. .translate-menu-item:hover {
  37. opacity: 0.9;
  38. }
  39. `;
  40. document.head.appendChild(style);
  41.  
  42. let currentMenu = null;
  43. let menuTimeout = null;
  44. let translatorWindow = null;
  45. let lastScrollPosition = window.scrollY;
  46. let scrollTimeout = null;
  47.  
  48. // Функция открытия переводчика в новом окне
  49. function openTranslator(text) {
  50. // Закрываем предыдущее окно переводчика, если оно существует
  51. if (translatorWindow && !translatorWindow.closed) {
  52. translatorWindow.close();
  53. }
  54.  
  55. // Вычисляем размеры и позицию для нового окна
  56. const width = 800;
  57. const height = 600;
  58. const left = (window.screen.width - width) / 2;
  59. const top = (window.screen.height - height) / 2;
  60.  
  61. // Открываем новое окно с заданными параметрами
  62. translatorWindow = window.open(
  63. `https://translate.yandex.ru/?text=${encodeURIComponent(text)}`,
  64. 'YandexTranslate',
  65. `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,status=no,menubar=no,toolbar=no`
  66. );
  67.  
  68. // Фокусируем новое окно
  69. if (translatorWindow) {
  70. translatorWindow.focus();
  71. }
  72. }
  73.  
  74. // Функция закрытия меню
  75. function closeMenu() {
  76. if (currentMenu) {
  77. currentMenu.remove();
  78. currentMenu = null;
  79. }
  80. }
  81.  
  82. // Отслеживаем прокрутку страницы
  83. document.addEventListener('scroll', function() {
  84. // Используем throttling для оптимизации производительности
  85. if (!scrollTimeout) {
  86. scrollTimeout = setTimeout(() => {
  87. if (Math.abs(window.scrollY - lastScrollPosition) > 5) { // Минимальный порог прокрутки
  88. closeMenu();
  89. }
  90. lastScrollPosition = window.scrollY;
  91. scrollTimeout = null;
  92. }, 50);
  93. }
  94. }, { passive: true }); // Оптимизация производительности
  95.  
  96. // Отслеживаем отпускание кнопки мыши
  97. document.addEventListener('mouseup', function(e) {
  98. // Очищаем предыдущий таймер, если он есть
  99. if (menuTimeout) {
  100. clearTimeout(menuTimeout);
  101. }
  102.  
  103. // Устанавливаем новый таймер
  104. menuTimeout = setTimeout(() => {
  105. const selectedText = window.getSelection().toString().trim();
  106.  
  107. // Удаляем предыдущее меню
  108. closeMenu();
  109.  
  110. if (selectedText) {
  111. const selection = window.getSelection();
  112. const range = selection.getRangeAt(0);
  113. const rect = range.getBoundingClientRect();
  114.  
  115. const menu = document.createElement('div');
  116. menu.className = 'translate-menu';
  117.  
  118. const menuItem = document.createElement('div');
  119. menuItem.className = 'translate-menu-item';
  120. menuItem.innerHTML = 'Перевести в <span class="ya-letter">Я</span>ндекс';
  121. menuItem.onclick = (e) => {
  122. e.preventDefault();
  123. openTranslator(selectedText);
  124. closeMenu();
  125. };
  126.  
  127. menu.appendChild(menuItem);
  128.  
  129. // Позиционируем меню выше выделенного текста
  130. const menuVerticalOffset = 35;
  131. menu.style.left = `${rect.left}px`;
  132. menu.style.top = `${rect.top - menuVerticalOffset}px`;
  133.  
  134. document.body.appendChild(menu);
  135. currentMenu = menu;
  136.  
  137. // Корректируем позицию, если меню выходит за пределы экрана
  138. const menuRect = menu.getBoundingClientRect();
  139.  
  140. if (menuRect.top < 0) {
  141. menu.style.top = `${rect.bottom + 10}px`;
  142. }
  143.  
  144. if (menuRect.right > window.innerWidth) {
  145. menu.style.left = `${window.innerWidth - menuRect.width - 10}px`;
  146. }
  147. }
  148. }, 200);
  149. });
  150.  
  151. // Закрываем меню при клике вне его
  152. document.addEventListener('mousedown', function(e) {
  153. if (currentMenu && !currentMenu.contains(e.target)) {
  154. closeMenu();
  155. }
  156. });
  157.  
  158. // Очищаем таймер при отмене выделения
  159. document.addEventListener('selectionchange', function() {
  160. if (!window.getSelection().toString().trim() && menuTimeout) {
  161. clearTimeout(menuTimeout);
  162. }
  163. });
  164. })();