Grok Code Filter Menu 1.21.13

Добавляет меню фильтров к блокам кода в чате Grok с сохранением настроек

当前为 2025-03-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Grok Code Filter Menu 1.21.13
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.21.13
  5. // @description Добавляет меню фильтров к блокам кода в чате Grok с сохранением настроек
  6. // @author tapeavion
  7. // @license MIT
  8. // @match https://grok.com/*
  9. // @match https://grok.com/chat/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=grok.com
  11. // @grant GM_addStyle
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. GM_addStyle(`
  20. button.inline-flex.items-center.justify-center.gap-2.whitespace-nowrap.font-medium.cursor-pointer.focus-visible\\:outline-none.focus-visible\\:ring-1.focus-visible\\:ring-ring.disabled\\:opacity-50.disabled\\:cursor-default.\\[\\&_svg\\]\\:pointer-events-none.\\[\\&_svg\\]\\:shrink-0.\\[\\&_svg\\]\\:-mx-0\\.5.text-secondary.hover\\:text-primary.disabled\\:hover\\:text-secondary.disabled\\:hover\\:bg-inherit.bg-popover.dark\\:hover\\:bg-neutral-600.hover\\:bg-neutral-200.h-8.rounded-lg.px-3.text-xs {
  21. background: #1d5752 !important;
  22. }
  23. `);
  24.  
  25. GM_addStyle(`
  26. .absolute.bottom-0.w-\\[calc\\(100\\%-2rem\\)\\].h-full.rounded-t-\\[40px\\].bg-background {
  27. background: #faebd700 !important;
  28. }
  29. `);
  30.  
  31. // Стили
  32. GM_addStyle(`
  33. .filter-menu-btn {
  34. position: absolute;
  35. top: 4px;
  36. height: 32px !important;
  37. right: 335px;
  38. z-index: 9999;
  39. padding: 4px 8px;
  40. background: #1d5752;
  41. color: #b9bcc1;
  42. border: none;
  43. border-radius: 8px;
  44. cursor: pointer;
  45. font-size: 12px;
  46. transition: background 0.2s ease, color 0.2s ease;
  47. }
  48. .filter-menu-btn:hover {
  49. background: #4a8983;
  50. color: #ffffff;
  51. }
  52. .filter-menu {
  53. position: absolute;
  54. top: 32px;
  55. right: 160px;
  56. background: #2d2d2d;
  57. border: 1px solid #444;
  58. border-radius: 8px;
  59. padding: 5px;
  60. z-index: 9999;
  61. display: none;
  62. box-shadow: 0 2px 4px rgba(0,0,0,0.3);
  63. width: 200px;
  64. max-height: 400px;
  65. overflow-y: auto;
  66. }
  67. .filter-item {
  68. display: flex;
  69. align-items: center;
  70. padding: 5px 0;
  71. color: #a0a0a0;
  72. font-size: 12px;
  73. }
  74. .filter-item input[type="checkbox"] {
  75. margin-right: 5px;
  76. }
  77. .filter-item label {
  78. flex: 1;
  79. cursor: pointer;
  80. }
  81. .filter-slider {
  82. display: none;
  83. margin: 5px 0 5px 20px;
  84. width: calc(100% - 20px);
  85. }
  86. .filter-slider-label {
  87. display: none;
  88. color: #a0a0a0;
  89. font-size: 12px;
  90. margin: 2px 0 2px 20px;
  91. }
  92. .language-select {
  93. width: 100%;
  94. padding: 5px;
  95. margin-bottom: 5px;
  96. background: #3a3a3a;
  97. color: #a0a0a0;
  98. border: none;
  99. border-radius: 4px;
  100. font-size: 12px;
  101. }
  102. .color-picker {
  103. margin: 5px 0 5px 20px;
  104. width: calc(100% - 20px);
  105. }
  106. .color-picker-label {
  107. display: block;
  108. color: #a0a0a0;
  109. font-size: 12px;
  110. margin: 2px 0 2px 20px;
  111. }
  112. `);
  113.  
  114. // Определение языка пользователя
  115. const userLang = navigator.language || navigator.languages[0];
  116. const isRussian = userLang.startsWith('ru');
  117. const defaultLang = isRussian ? 'ru' : 'en';
  118. const savedLang = GM_getValue('filterMenuLang', defaultLang);
  119.  
  120. // Локализация
  121. const translations = {
  122. ru: {
  123. filtersBtn: 'Фильтры',
  124. sliderLabel: 'Степень:',
  125. commentColorLabel: 'Цвет комментариев:',
  126. filters: [
  127. { name: 'Негатив', value: 'invert', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
  128. { name: 'Сепия', value: 'sepia', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
  129. { name: 'Ч/Б', value: 'grayscale', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
  130. { name: 'Размытие', value: 'blur', hasSlider: true, min: 0, max: 5, step: 0.1, default: 2, unit: 'px' },
  131. { name: 'Контраст', value: 'contrast', hasSlider: true, min: 0, max: 3, step: 0.1, default: 2 },
  132. { name: 'Яркость', value: 'brightness', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1.5 },
  133. { name: 'Поворот оттенка', value: 'hue-rotate', hasSlider: true, min: 0, max: 360, step: 1, default: 90, unit: 'deg' },
  134. { name: 'Насыщенность', value: 'saturate', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1 },
  135. { name: 'Прозрачность', value: 'opacity', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 }
  136. ],
  137. langSelect: 'Выберите язык:',
  138. langOptions: [
  139. { value: 'ru', label: 'Русский' },
  140. { value: 'en', label: 'English' }
  141. ]
  142. },
  143. en: {
  144. filtersBtn: 'Filters',
  145. sliderLabel: 'Level:',
  146. commentColorLabel: 'Comment color:',
  147. filters: [
  148. { name: 'Invert', value: 'invert', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
  149. { name: 'Sepia', value: 'sepia', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
  150. { name: 'Grayscale', value: 'grayscale', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 },
  151. { name: 'Blur', value: 'blur', hasSlider: true, min: 0, max: 5, step: 0.1, default: 2, unit: 'px' },
  152. { name: 'Contrast', value: 'contrast', hasSlider: true, min: 0, max: 3, step: 0.1, default: 2 },
  153. { name: 'Brightness', value: 'brightness', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1.5 },
  154. { name: 'Hue Rotate', value: 'hue-rotate', hasSlider: true, min: 0, max: 360, step: 1, default: 90, unit: 'deg' },
  155. { name: 'Saturate', value: 'saturate', hasSlider: true, min: 0, max: 3, step: 0.1, default: 1 },
  156. { name: 'Opacity', value: 'opacity', hasSlider: true, min: 0, max: 1, step: 0.1, default: 1 }
  157. ],
  158. langSelect: 'Select language:',
  159. langOptions: [
  160. { value: 'ru', label: 'Русский' },
  161. { value: 'en', label: 'English' }
  162. ]
  163. }
  164. };
  165.  
  166. // Глобальная переменная для текущего цвета комментариев
  167. let currentCommentColor = GM_getValue('commentColor', '#5c6370');
  168.  
  169. // Функция создания меню фильтров
  170. function addFilterMenu(codeBlock) {
  171. if (codeBlock.querySelector('.filter-menu-btn')) return;
  172.  
  173. let currentLang = savedLang;
  174. const filterBtn = document.createElement('button');
  175. filterBtn.className = 'filter-menu-btn';
  176. filterBtn.textContent = translations[currentLang].filtersBtn;
  177.  
  178. const filterMenu = document.createElement('div');
  179. filterMenu.className = 'filter-menu';
  180.  
  181. const targetBlock = codeBlock.querySelector('div[style*="background: rgb(18, 19, 20)"]') || codeBlock;
  182.  
  183. // Загружаем сохраненные настройки
  184. const savedFilterStates = GM_getValue('codeFilterStates', {});
  185. const savedFilterValues = GM_getValue('codeFilterValues', {});
  186.  
  187. // Инициализируем значения по умолчанию
  188. const filters = translations[currentLang].filters;
  189. filters.forEach(filter => {
  190. if (!(filter.value in savedFilterStates)) {
  191. savedFilterStates[filter.value] = false;
  192. }
  193. if (!(filter.value in savedFilterValues)) {
  194. savedFilterValues[filter.value] = filter.default;
  195. }
  196. });
  197.  
  198. // Применяем сохраненные фильтры
  199. function applyFilters() {
  200. const activeFilters = filters
  201. .filter(filter => savedFilterStates[filter.value])
  202. .map(filter => {
  203. const unit = filter.unit || '';
  204. const value = savedFilterValues[filter.value];
  205. return `${filter.value}(${value}${unit})`;
  206. });
  207. targetBlock.style.filter = activeFilters.length > 0 ? activeFilters.join(' ') : 'none';
  208. }
  209.  
  210. // Применяем цвет комментариев
  211. function applyCommentColor() {
  212. const commentElements = codeBlock.querySelectorAll('span[style*="color: rgb(92, 99, 112)"]');
  213. commentElements.forEach(element => {
  214. element.style.color = currentCommentColor;
  215. });
  216. }
  217. applyFilters();
  218. applyCommentColor();
  219.  
  220. // Создаем выпадающий список для выбора языка
  221. const langSelect = document.createElement('select');
  222. langSelect.className = 'language-select';
  223. const langLabel = document.createElement('label');
  224. langLabel.textContent = translations[currentLang].langSelect;
  225. langLabel.style.color = '#a0a0a0';
  226. langLabel.style.fontSize = '12px';
  227. langLabel.style.marginBottom = '2px';
  228. langLabel.style.display = 'block';
  229.  
  230. translations[currentLang].langOptions.forEach(option => {
  231. const opt = document.createElement('option');
  232. opt.value = option.value;
  233. opt.textContent = option.label;
  234. if (option.value === currentLang) {
  235. opt.selected = true;
  236. }
  237. langSelect.appendChild(opt);
  238. });
  239.  
  240. // Создаем элемент для выбора цвета комментариев
  241. const colorPickerLabel = document.createElement('label');
  242. colorPickerLabel.className = 'color-picker-label';
  243. colorPickerLabel.textContent = translations[currentLang].commentColorLabel;
  244.  
  245. const colorPicker = document.createElement('input');
  246. colorPicker.type = 'color';
  247. colorPicker.className = 'color-picker';
  248. colorPicker.value = currentCommentColor;
  249.  
  250. colorPicker.addEventListener('input', () => {
  251. currentCommentColor = colorPicker.value;
  252. GM_setValue('commentColor', currentCommentColor);
  253. // Применяем цвет ко всем блокам на странице
  254. document.querySelectorAll('span[style*="color: rgb(92, 99, 112)"]').forEach(element => {
  255. element.style.color = currentCommentColor;
  256. });
  257. });
  258.  
  259. // Функция обновления интерфейса при смене языка
  260. function updateLanguage(lang) {
  261. currentLang = lang;
  262. GM_setValue('filterMenuLang', currentLang);
  263. filterBtn.textContent = translations[currentLang].filtersBtn;
  264. langLabel.textContent = translations[currentLang].langSelect;
  265. colorPickerLabel.textContent = translations[currentLang].commentColorLabel;
  266. filterMenu.innerHTML = ''; // Очищаем меню
  267. filterMenu.appendChild(langLabel);
  268. filterMenu.appendChild(langSelect);
  269. filterMenu.appendChild(colorPickerLabel);
  270. filterMenu.appendChild(colorPicker);
  271. renderFilters();
  272. }
  273.  
  274. // Обработчик смены языка
  275. langSelect.addEventListener('change', () => {
  276. updateLanguage(langSelect.value);
  277. });
  278.  
  279. // Рендеринг фильтров
  280. function renderFilters() {
  281. const filters = translations[currentLang].filters;
  282. filters.forEach(filter => {
  283. const filterItem = document.createElement('div');
  284. filterItem.className = 'filter-item';
  285.  
  286. const checkbox = document.createElement('input');
  287. checkbox.type = 'checkbox';
  288. checkbox.checked = savedFilterStates[filter.value];
  289. checkbox.id = `filter-${filter.value}`;
  290.  
  291. const label = document.createElement('label');
  292. label.htmlFor = `filter-${filter.value}`;
  293. label.textContent = filter.name;
  294.  
  295. const sliderLabel = document.createElement('label');
  296. sliderLabel.className = 'filter-slider-label';
  297. sliderLabel.textContent = translations[currentLang].sliderLabel;
  298.  
  299. const slider = document.createElement('input');
  300. slider.type = 'range';
  301. slider.className = 'filter-slider';
  302. slider.min = filter.min;
  303. slider.max = filter.max;
  304. slider.step = filter.step;
  305. slider.value = savedFilterValues[filter.value];
  306.  
  307. if (checkbox.checked && filter.hasSlider) {
  308. slider.style.display = 'block';
  309. sliderLabel.style.display = 'block';
  310. }
  311.  
  312. checkbox.addEventListener('change', () => {
  313. savedFilterStates[filter.value] = checkbox.checked;
  314. GM_setValue('codeFilterStates', savedFilterStates);
  315. if (filter.hasSlider) {
  316. slider.style.display = checkbox.checked ? 'block' : 'none';
  317. sliderLabel.style.display = checkbox.checked ? 'block' : 'none';
  318. }
  319. applyFilters();
  320. });
  321.  
  322. slider.addEventListener('input', () => {
  323. savedFilterValues[filter.value] = slider.value;
  324. GM_setValue('codeFilterValues', savedFilterValues);
  325. applyFilters();
  326. });
  327.  
  328. filterItem.appendChild(checkbox);
  329. filterItem.appendChild(label);
  330. filterMenu.appendChild(filterItem);
  331. filterMenu.appendChild(sliderLabel);
  332. filterMenu.appendChild(slider);
  333. });
  334. }
  335.  
  336. // Инициализация
  337. filterMenu.appendChild(langLabel);
  338. filterMenu.appendChild(langSelect);
  339. filterMenu.appendChild(colorPickerLabel);
  340. filterMenu.appendChild(colorPicker);
  341. renderFilters();
  342.  
  343. // Обработчики для кнопки
  344. filterBtn.addEventListener('click', () => {
  345. filterMenu.style.display = filterMenu.style.display === 'block' ? 'none' : 'block';
  346. });
  347.  
  348. document.addEventListener('click', (e) => {
  349. if (!filterBtn.contains(e.target) && !filterMenu.contains(e.target)) {
  350. filterMenu.style.display = 'none';
  351. }
  352. });
  353.  
  354. codeBlock.style.position = 'relative';
  355. codeBlock.appendChild(filterBtn);
  356. codeBlock.appendChild(filterMenu);
  357. }
  358.  
  359. // Функция поиска и обработки блоков кода
  360. function processCodeBlocks() {
  361. const codeBlocks = document.querySelectorAll('div[class*="relative"][class*="mt-3"][class*="mb-3"][class*="-mx-4"]');
  362. console.log('Найдено блоков кода:', codeBlocks.length);
  363. codeBlocks.forEach(block => {
  364. if (block.querySelector('div[style*="background: rgb(18, 19, 20)"]')) {
  365. addFilterMenu(block);
  366. const savedFilterStates = GM_getValue('codeFilterStates', {});
  367. const savedFilterValues = GM_getValue('codeFilterValues', {});
  368. const targetBlock = block.querySelector('div[style*="background: rgb(18, 19, 20)"]') || block;
  369. const filters = [
  370. { value: 'invert' },
  371. { value: 'sepia' },
  372. { value: 'grayscale' },
  373. { value: 'blur', unit: 'px' },
  374. { value: 'contrast' },
  375. { value: 'brightness' },
  376. { value: 'hue-rotate', unit: 'deg' },
  377. { value: 'saturate' },
  378. { value: 'opacity' }
  379. ];
  380. const activeFilters = filters
  381. .filter(filter => savedFilterStates[filter.value])
  382. .map(filter => {
  383. const unit = filter.unit || '';
  384. const value = savedFilterValues[filter.value] || (filter.value === 'blur' ? 2 : filter.value === 'brightness' ? 1.5 : filter.value === 'contrast' ? 2 : filter.value === 'hue-rotate' ? 90 : 1);
  385. return `${filter.value}(${value}${unit})`;
  386. });
  387. targetBlock.style.filter = activeFilters.length > 0 ? activeFilters.join(' ') : 'none';
  388. const commentElements = block.querySelectorAll('span[style*="color: rgb(92, 99, 112)"]');
  389. commentElements.forEach(element => {
  390. element.style.color = currentCommentColor;
  391. });
  392. }
  393. });
  394. }
  395.  
  396. setTimeout(processCodeBlocks, 1000);
  397. processCodeBlocks();
  398.  
  399. const observer = new MutationObserver(() => {
  400. processCodeBlocks();
  401. });
  402. observer.observe(document.body, { childList: true, subtree: true });
  403. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址