MoreChatGLM

MoreChatGLM 是一款专为 智谱清言(ChatGLM )平台设计的用户脚本,旨在优化用户界面,增强功能体验。脚本支持以下功能:对话滚动条、聊天气泡样、宽屏对话、清空内容快捷按钮、内容脱敏快捷按钮、内容脱敏功能配置,以及简洁的浮动菜单便捷操作。此脚本提升了 ChatGLM 的美观性与易用性,让聊天更愉快、更高效。

  1. // ==UserScript==
  2. // @name MoreChatGLM
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.0
  5. // @description MoreChatGLM 是一款专为 智谱清言(ChatGLM )平台设计的用户脚本,旨在优化用户界面,增强功能体验。脚本支持以下功能:对话滚动条、聊天气泡样、宽屏对话、清空内容快捷按钮、内容脱敏快捷按钮、内容脱敏功能配置,以及简洁的浮动菜单便捷操作。此脚本提升了 ChatGLM 的美观性与易用性,让聊天更愉快、更高效。
  6. // @author DD1024z
  7. // @match https://chatglm.cn/*
  8. // @icon https://chatglm.cn/img/icons/favicon.ico
  9. // @license Apache License 2.0
  10. // @grant GM_addStyle
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM_info
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. const APP_NAME = 'MoreChatGLM';
  19.  
  20. const defaultConfig = {
  21. displayContentScroll: true,
  22. displayWideScreen: true,
  23. displayChatBubble: true,
  24. displayTopicPopoverMenu: true,
  25. displayClearButton: true,
  26. displaySanitizeButton: true,
  27. sanitizationRules: [
  28. "[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])(\\d{4}|\\d{3}[Xx])->${身份证号码}",
  29. "(0|86|17951)?(13[0-9]|15[0-35-9]|166|17[3-8]|18[0-9]|14[5-79]|19[0-9])[-]?[0-9]{4}[-]?[0-9]{4}->${手机号码}",
  30. "(0\\d{2,3}-?\\d{7,8})->${座机号码}",
  31. "https?:\\/\\/([a-zA-Z0-9_-]+\\.?)+(:\\d+)?(\\/.*)?->${链接}",
  32. "\\w+([-+.\\w])*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*->${电子邮箱}",
  33. "这是匹配的长内容->短内容"
  34. ]
  35. };
  36.  
  37. const config = (() => {
  38. const savedConfig = JSON.parse(localStorage.getItem(APP_NAME));
  39. return { ...defaultConfig, ...savedConfig };
  40. })();
  41.  
  42. function saveConfig(updatedConfig) {
  43. localStorage.setItem(APP_NAME, JSON.stringify(updatedConfig));
  44. }
  45.  
  46. function observeDOMChanges(callback, target = document.body, config = { childList: true, subtree: true }) {
  47. const observer = new MutationObserver(callback);
  48. const observerConfig = config;
  49. observer.observe(target, observerConfig);
  50. return observer;
  51. }
  52.  
  53. const menuItems = [
  54. { text: '对话滚动条', action: applyContentScroll, checked: config.displayContentScroll },
  55. { text: '聊天气泡', action: applyChatBubble, checked: config.displayChatBubble },
  56. { text: '宽屏对话', action: applyWideScreen, checked: config.displayWideScreen },
  57. { text: '最近对话防误删', action: applyTopicPopoverMenu, checked: config.displayTopicPopoverMenu },
  58. { text: '清空内容快捷按钮', action: applyClearButton, checked: config.displayClearButton },
  59. { text: '内容脱敏快捷按钮', action: applySanitizeButton, checked: config.displaySanitizeButton },
  60. { text: '内容脱敏规则配置', action: showModalDataSanitization },
  61. { text: '检查更新', action: checkForUpdates },
  62. { text: '赞赏鼓励', action: showModalAppreciate },
  63. { text: '关于', action: () => window.open('https://github.com/10D24D/MoreChatGLM', '_blank') },
  64. ];
  65.  
  66. function addFloatingMenu() {
  67. const asideHeader = document.querySelector('div.aside-header');
  68. if (!asideHeader) return;
  69.  
  70. const triggerDiv = document.createElement('div');
  71. triggerDiv.id = APP_NAME + '-menu-trigger';
  72. triggerDiv.textContent = APP_NAME;
  73. triggerDiv.style.background = '#007BFF';
  74. triggerDiv.style.color = '#FFFFFF';
  75. triggerDiv.style.padding = '10px';
  76. triggerDiv.style.marginTop = '10px';
  77. triggerDiv.style.borderRadius = '5px';
  78. triggerDiv.style.cursor = 'pointer';
  79. triggerDiv.style.textAlign = 'center';
  80. triggerDiv.style.transition = 'background 0.3s';
  81. triggerDiv.style.width = '90%';
  82. triggerDiv.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)';
  83. triggerDiv.style.fontSize = '14px';
  84. triggerDiv.style.position = 'relative';
  85.  
  86. const menuPanel = document.createElement('div');
  87. menuPanel.id = APP_NAME + '-menu-panel';
  88. menuPanel.style.position = 'absolute';
  89. menuPanel.style.top = '88px';
  90. menuPanel.style.left = '13px';
  91. menuPanel.style.background = '#FFFFFF';
  92. menuPanel.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.2)';
  93. menuPanel.style.borderRadius = '5px';
  94. menuPanel.style.padding = '10px';
  95. menuPanel.style.zIndex = '1000';
  96. menuPanel.style.width = '192px';
  97. menuPanel.classList = 'hidden';
  98.  
  99. menuItems.forEach(item => {
  100. const menuItem = document.createElement('div');
  101. menuItem.style.display = 'flex';
  102. menuItem.style.alignItems = 'center';
  103. menuItem.style.justifyContent = 'space-between';
  104. menuItem.style.padding = '5px 10px';
  105. menuItem.style.cursor = 'pointer';
  106. menuItem.style.borderBottom = '1px solid #DDDDDD';
  107. menuItem.style.height = '25px';
  108. menuItem.style.transition = 'background-color 0.3s';
  109.  
  110. menuItem.onmouseover = () => {
  111. menuItem.style.backgroundColor = '#F1F2F3';
  112. };
  113. menuItem.onmouseout = () => {
  114. menuItem.style.backgroundColor = '#FFFFFF';
  115. };
  116.  
  117. const itemText = document.createElement('span');
  118. itemText.textContent = item.text;
  119.  
  120. menuItem.appendChild(itemText);
  121.  
  122. if (item.hasOwnProperty('checked')) {
  123. const checkbox = document.createElement('input');
  124. checkbox.type = 'checkbox';
  125. checkbox.checked = item.checked;
  126. checkbox.style.width = '20px';
  127. checkbox.style.height = '20px';
  128. checkbox.style.cursor = 'pointer';
  129. checkbox.onchange = () => {
  130. item.checked = checkbox.checked;
  131. item.action(item.checked);
  132. };
  133. menuItem.appendChild(checkbox);
  134. } else {
  135. menuItem.onclick = () => {
  136. item.action();
  137. };
  138. }
  139.  
  140. menuPanel.appendChild(menuItem);
  141. });
  142.  
  143. triggerDiv.addEventListener('mouseenter', () => {
  144. menuPanel.classList.remove('hidden');
  145. });
  146. triggerDiv.addEventListener('mouseleave', (e) => {
  147. if (!menuPanel.contains(e.relatedTarget)) {
  148. menuPanel.classList.add('hidden');
  149. }
  150. });
  151. menuPanel.addEventListener('mouseleave', (e) => {
  152. if (!triggerDiv.contains(e.relatedTarget)) {
  153. menuPanel.classList.add('hidden');
  154. }
  155. });
  156.  
  157. asideHeader.parentElement.insertBefore(triggerDiv, asideHeader.nextSibling);
  158. asideHeader.parentElement.insertBefore(menuPanel, asideHeader.nextSibling);
  159.  
  160. const aside = asideHeader.closest('aside');
  161. observeDOMChanges(() => {
  162. if (aside.classList.contains('collapse-aside')) {
  163. triggerDiv.classList.add('hidden');
  164. } else {
  165. triggerDiv.classList.remove('hidden');
  166. }
  167. }, aside, { attributes: true, attributeFilter: ['class'] })
  168. }
  169.  
  170. function updateDisplayConfig(key, isChecked = false) {
  171. config[key] = isChecked;
  172. saveConfig(config);
  173. }
  174.  
  175. function applyContentScroll(isChecked = false) {
  176. updateDisplayConfig('displayContentScroll', isChecked)
  177. const container = document.querySelector('.conversation-list');
  178. if (container) {
  179. if (isChecked) {
  180. container.classList.remove('scroll-display-none');
  181. } else {
  182. container.classList.add('scroll-display-none');
  183. }
  184. console.log('ContentScroll:', isChecked);
  185. }
  186. };
  187.  
  188. function applyWideScreen(isChecked = false) {
  189. updateDisplayConfig('displayWideScreen', isChecked);
  190.  
  191. let wideScreenStyle = document.getElementById(APP_NAME + '-wide-screen-style');
  192.  
  193. if (!wideScreenStyle) {
  194. wideScreenStyle = document.createElement('style');
  195. wideScreenStyle.id = APP_NAME + '-wide-screen-style';
  196. document.head.appendChild(wideScreenStyle);
  197. }
  198.  
  199. if (isChecked) {
  200. let dynamicStyles = `
  201. .markdown-body, .conversation-item, .interact-operate, .component-box-new {
  202. max-width: 100% !important;
  203. margin: 0 auto !important;
  204. }
  205. `;
  206.  
  207. const uniqueItemAttributes = new Set();
  208. document.querySelectorAll('.dialogue .detail .item').forEach(item => {
  209. const dynamicAttribute = Array.from(item.attributes).find(attr => attr.name.startsWith('data-v-'));
  210. if (dynamicAttribute) {
  211. uniqueItemAttributes.add(dynamicAttribute.name);
  212. }
  213. });
  214.  
  215. if (uniqueItemAttributes.size > 0) {
  216. uniqueItemAttributes.forEach(attr => {
  217. dynamicStyles += `
  218. .dialogue .detail .item[${attr}] {
  219. max-width: 100% !important;
  220. }
  221. `;
  222. });
  223. } else {
  224. dynamicStyles += `
  225. .dialogue .detail .item {
  226. max-width: 100% !important;
  227. }
  228. `;
  229. }
  230.  
  231. const uniqueBottomAttributes = new Set();
  232. document.querySelectorAll('.conversation-bottom').forEach(bottom => {
  233. const dynamicAttribute = Array.from(bottom.attributes).find(attr => attr.name.startsWith('data-v-'));
  234. if (dynamicAttribute) {
  235. uniqueBottomAttributes.add(dynamicAttribute.name);
  236. }
  237. });
  238.  
  239. if (uniqueBottomAttributes.size > 0) {
  240. uniqueBottomAttributes.forEach(attr => {
  241. dynamicStyles += `
  242. .conversation-bottom[${attr}], .dialogue .conversation-bottom[${attr}] {
  243. max-width: 100% !important;
  244. }
  245. `;
  246. });
  247. } else {
  248. dynamicStyles += `
  249. .conversation-bottom, .dialogue .conversation-bottom {
  250. max-width: 100% !important;
  251. }
  252. `;
  253. }
  254.  
  255. wideScreenStyle.textContent = dynamicStyles.replace(/\s+/g, ' ').trim();
  256. } else {
  257. wideScreenStyle.textContent = '';
  258. }
  259.  
  260. console.log('WideScreen:', isChecked);
  261. }
  262.  
  263. function applyTopicPopoverMenu(isChecked = false) {
  264. updateDisplayConfig('displayTopicPopoverMenu', isChecked);
  265.  
  266. let menuStyleElement = document.getElementById(APP_NAME + '-topic-menu-style');
  267.  
  268. if (!menuStyleElement) {
  269. menuStyleElement = document.createElement('style');
  270. menuStyleElement.id = APP_NAME + '-topic-menu-style';
  271. document.head.appendChild(menuStyleElement);
  272. }
  273.  
  274. if (isChecked) {
  275. const topicPopoverMenus = document.querySelectorAll('.history-item');
  276.  
  277. const uniqueAttributes = new Set();
  278.  
  279. topicPopoverMenus.forEach(item => {
  280. const dynamicAttribute = Array.from(item.attributes).find(attr => attr.name.startsWith('data-v-'));
  281. if (dynamicAttribute) {
  282. uniqueAttributes.add(dynamicAttribute.name);
  283. }
  284. });
  285.  
  286. let dynamicStyles = '';
  287. if (uniqueAttributes.size > 0) {
  288. uniqueAttributes.forEach(attr => {
  289. dynamicStyles += `
  290. .history-item[${attr}] .el-popper {
  291. display: none !important;
  292. }
  293. `;
  294. });
  295. } else {
  296. dynamicStyles = `
  297. .history-item .el-popper {
  298. display: none !important;
  299. }
  300. `;
  301. }
  302.  
  303. menuStyleElement.textContent = dynamicStyles.replace(/\s+/g, ' ').trim();
  304. } else {
  305. menuStyleElement.textContent = '';
  306. }
  307.  
  308. console.log('TopicPopoverMenu:', isChecked);
  309. }
  310.  
  311. function applyChatBubble(isChecked = false) {
  312. updateDisplayConfig('displayChatBubble', isChecked);
  313.  
  314. let bubbleStyleElement = document.getElementById(APP_NAME + '-chat-bubble-style');
  315.  
  316. if (!bubbleStyleElement) {
  317. bubbleStyleElement = document.createElement('style');
  318. bubbleStyleElement.id = APP_NAME + '-chat-bubble-style';
  319. document.head.appendChild(bubbleStyleElement);
  320. }
  321.  
  322. if (isChecked) {
  323. const bubbleStyles = `
  324. div.question-text-style {
  325. background-color: #DEEDD7;
  326. border-radius: 0.5rem;
  327. padding: 0.75rem 0.5rem;
  328. margin-left: 12px;
  329. max-width: 100%;
  330. }
  331. div.markdown-body {
  332. background-color: #F1F2F3;
  333. border-radius: 0.5rem;
  334. padding: 0.75rem 1.25rem;
  335. max-width: 100%;
  336. }
  337. `;
  338.  
  339. bubbleStyleElement.textContent = bubbleStyles.replace(/\s+/g, ' ').trim();
  340. hideEmptyMarkdownBodies();
  341. } else {
  342. bubbleStyleElement.textContent = '';
  343. }
  344.  
  345. console.log('ChatBubble:', isChecked);
  346. }
  347.  
  348. function hideEmptyMarkdownBodies() {
  349. const markdownBodies = document.querySelectorAll('div.markdown-body');
  350.  
  351. markdownBodies.forEach(body => {
  352. if (!body.textContent.trim()) {
  353. body.style.display = 'none';
  354. } else {
  355. body.style.display = '';
  356. }
  357. });
  358. }
  359.  
  360. const verInt = function (vs) {
  361. const vl = vs.split('.');
  362. let vi = 0;
  363. for (let i = 0; i < vl.length && i < 3; i++) {
  364. vi += parseInt(vl[i]) * (1000 ** (2 - i));
  365. }
  366. return vi;
  367. };
  368.  
  369. function checkForUpdates(action = "click") {
  370. const downloadURL = `https://raw.githubusercontent.com/10D24D/MoreChatGLM/main/MoreChatGLM.js`;
  371. const updateURL = downloadURL;
  372. GM_xmlhttpRequest({
  373. method: "GET",
  374. url: `${updateURL}?t=${Date.now()}`,
  375. onload: function (response) {
  376. const crv = GM_info.script.version;
  377. const m = response.responseText.match(/@version\s+(\S+)/);
  378. const ltv = m && m[1];
  379. if (ltv && verInt(ltv) > verInt(crv)) {
  380. alert('发现新版本')
  381. window.open(`${downloadURL}?t=${Date.now()}`, '_blank');
  382. } else {
  383. if (action === "click") {
  384. alert('已是最新版')
  385. }
  386. }
  387. }
  388. });
  389. };
  390.  
  391. function showModalAppreciate() {
  392. const existingModal = document.getElementById(APP_NAME + '-appreciate-modal');
  393. if (existingModal) {
  394. existingModal.style.display = 'block';
  395. return;
  396. }
  397.  
  398. const modal = document.createElement('div');
  399. modal.id = APP_NAME + '-appreciate-modal';
  400. modal.style.position = 'fixed';
  401. modal.style.top = '50%';
  402. modal.style.left = '50%';
  403. modal.style.transform = 'translate(-50%, -50%)';
  404. modal.style.width = '80%';
  405. modal.style.maxWidth = '500px';
  406. modal.style.backgroundColor = '#FFFFFF';
  407. modal.style.borderRadius = '8px';
  408. modal.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
  409. modal.style.overflow = 'hidden';
  410. modal.style.zIndex = '1000';
  411.  
  412. modal.innerHTML = `
  413. <div style="padding: 16px; border-bottom: 1px solid #DDDDDD; display: flex; justify-content: space-between; align-items: center;">
  414. <h2 style="margin: 0; font-size: 18px; color: #333333;">赞赏鼓励</h2>
  415. </div>
  416. <div style="padding: 16px;">
  417. <p style="margin: 0 0 16px; color: #555555; font-size: 14px;">本项目由兴趣驱使,优化用户界面,增强功能体验,并共享世界。<br>如果你喜欢作者的项目,可以给作者一个免费的Star或者Follow。</p>
  418. <img src="https://raw.githubusercontent.com/10D24D/MoreChatGLM/main/static/appreciate_wechat.jpg" style="display: block; max-width: 100%; height: auto; margin: 0 auto 16px;">
  419. <div style="display: flex; gap: 8px; justify-content: flex-end;">
  420. <button style="padding: 8px 16px; background-color: #007BFF; color: #FFFFFF; border: none; border-radius: 4px; cursor: pointer;" onclick="window.open('https://github.com/10D24D/MoreChatGLM?tab=readme-ov-file#%E8%B5%9E%E8%B5%8F%E9%BC%93%E5%8A%B1')">更多鼓励方式</button>
  421. <button style="padding: 8px 16px; background-color: #F0F0F0; color: #333333; border: none; border-radius: 4px; cursor: pointer;" onclick="document.getElementById('${modal.id}').style.display = 'none';">取消</button>
  422. </div>
  423. </div>
  424. `;
  425. document.body.appendChild(modal);
  426. }
  427.  
  428. function validateRules(rules) {
  429. return rules.every(rule => {
  430. const [regex, replacement] = rule.split('->').map(item => item.trim());
  431. try {
  432. new RegExp(regex);
  433. return !!replacement;
  434. } catch {
  435. return false;
  436. }
  437. });
  438. }
  439.  
  440. function applyDataSanitizationRules() {
  441. const inputBox = document.querySelector('#search-input-box div.input-box-inner textarea');
  442. if (!inputBox) {
  443. alert('未找到输入框');
  444. return;
  445. }
  446.  
  447. let content = inputBox.value;
  448. config.sanitizationRules.forEach(rule => {
  449. const [regex, replacement] = rule.split('->').map(item => item.trim());
  450. if (regex && replacement) {
  451. try {
  452. const regexObj = new RegExp(regex, 'g');
  453. content = content.replace(regexObj, replacement);
  454. } catch (error) {
  455. console.error('Invalid regex:', regex, error);
  456. }
  457. }
  458. });
  459.  
  460. inputBox.value = content;
  461. inputBox.dispatchEvent(new Event('input', { bubbles: true }));
  462. }
  463.  
  464. function showModalDataSanitization() {
  465. const modalId = APP_NAME + '-data-sanitization-modal';
  466. let modal = document.getElementById(modalId);
  467. if (modal) {
  468. modal.querySelector('#' + APP_NAME + '-data-sanitization-rules').value = config.sanitizationRules.join('\n');
  469. modal.style.display = 'block';
  470. return;
  471. }
  472.  
  473. modal = document.createElement('div');
  474. modal.id = modalId;
  475. modal.style.position = 'fixed';
  476. modal.style.top = '50%';
  477. modal.style.left = '50%';
  478. modal.style.transform = 'translate(-50%, -50%)';
  479. modal.style.width = '80%';
  480. modal.style.maxWidth = '500px';
  481. modal.style.backgroundColor = '#FFFFFF';
  482. modal.style.borderRadius = '8px';
  483. modal.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
  484. modal.style.overflow = 'hidden';
  485. modal.style.zIndex = '1000';
  486.  
  487. modal.innerHTML = `
  488. <div style="padding: 16px; border-bottom: 1px solid #DDDDDD; display: flex; justify-content: space-between; align-items: center;">
  489. <h2 style="margin: 0; font-size: 18px; color: #333333;">内容脱敏规则配置</h2>
  490. </div>
  491. <div style="padding: 16px;">
  492. <p style="margin: 0 0 16px; color: #555555; font-size: 14px; text-align: left; line-height: 20px;">本功能会将聊天输入框里的敏感内容进行脱敏<br>请根据正则表达式语法编写内容脱敏规则,不同的规则用换行间隔。<br>格式:匹配内容(正则表达式)->替换内容</p>
  493. <textarea id="${APP_NAME + '-data-sanitization-rules'}" style="width: 95%; height: 10rem; resize: none; border-radius: 4px; padding: 10px; background-color: #F9F9F9; border: 1px solid #DDDDDD;">${config.sanitizationRules.join('\n')}</textarea>
  494. <div style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;">
  495. <button id="${APP_NAME + '-reset-rules-button'}" style="padding: 10px 20px; background-color: #28A745; color: white; border: none; border-radius: 4px; cursor: pointer;">恢复默认规则</button>
  496. <button id="${APP_NAME + '-save-rules-button'}" style="padding: 10px 20px; background-color: #007BFF; color: white; border: none; border-radius: 4px; cursor: pointer;">保存</button>
  497. <button style="padding: 10px 20px; background-color: #F0F0F0; color: #333333; border: none; border-radius: 4px; cursor: pointer;" onclick="document.getElementById('${modalId}').style.display = 'none';">取消</button>
  498. </div>
  499. </div>
  500. `;
  501.  
  502. document.body.appendChild(modal);
  503.  
  504. document.getElementById(APP_NAME + '-save-rules-button').addEventListener('click', () => {
  505. const rulesText = document.getElementById(APP_NAME + '-data-sanitization-rules').value;
  506. const updatedRules = rulesText.split('\n').filter(rule => rule.trim() !== '');
  507. if (validateRules(updatedRules)) {
  508. config.sanitizationRules = updatedRules;
  509. saveConfig(config);
  510. alert('规则已保存!');
  511. modal.style.display = 'none';
  512. } else {
  513. alert('规则格式不正确,请检查并重试!');
  514. }
  515. });
  516.  
  517. document.getElementById(APP_NAME + '-reset-rules-button').addEventListener('click', () => {
  518. config.sanitizationRules = [...defaultConfig.sanitizationRules];
  519. saveConfig(config);
  520. document.getElementById(APP_NAME + '-data-sanitization-rules').value = config.sanitizationRules.join('\n');
  521. alert('已恢复默认规则!');
  522. });
  523. }
  524.  
  525. function clearInputBoxContent() {
  526. const inputBox = document.querySelector('#search-input-box div.input-box-inner textarea');
  527. if (!inputBox) {
  528. alert('未找到输入框');
  529. return;
  530. }
  531.  
  532. inputBox.value = '';
  533. inputBox.dispatchEvent(new Event('input', { bubbles: true }));
  534. }
  535.  
  536. function applyClearButton(isChecked = false) {
  537. updateDisplayConfig('displayClearButton', isChecked);
  538.  
  539. const inputWrap = document.querySelector('#search-input-box div.input-wrap');
  540. if (!inputWrap) return;
  541.  
  542. let clearButton = document.querySelector('#' + APP_NAME + '-clear-button');
  543. let sanitizeButton = document.querySelector('#' + APP_NAME + '-sanitize-button');
  544. const enterIcon = inputWrap.querySelector('.enter img.enter_icon');
  545.  
  546. if (isChecked) {
  547. if (!clearButton) {
  548. clearButton = document.createElement('button');
  549. clearButton.id = APP_NAME + '-clear-button';
  550. clearButton.textContent = '清空内容';
  551. clearButton.style.backgroundColor = '#FB5531';
  552. clearButton.style.color = '#FFFFFF';
  553. clearButton.style.border = 'none';
  554. clearButton.style.borderRadius = '4px';
  555. clearButton.style.cursor = 'pointer';
  556. clearButton.style.height = '30px';
  557. clearButton.style.width = '70px';
  558. clearButton.onclick = clearInputBoxContent;
  559.  
  560. const clearButtonWrapper = document.createElement('div');
  561. clearButtonWrapper.className = 'enter';
  562. clearButtonWrapper.style.marginRight = '10px';
  563. clearButtonWrapper.style.alignSelf = 'flex-end';
  564. clearButtonWrapper.appendChild(clearButton);
  565.  
  566. if (sanitizeButton) {
  567. sanitizeButton.parentNode.before(clearButtonWrapper);
  568. } else if (enterIcon) {
  569. enterIcon.parentNode.before(clearButtonWrapper);
  570. } else {
  571. inputWrap.appendChild(clearButtonWrapper);
  572. }
  573. }
  574. } else {
  575. if (clearButton) {
  576. clearButton.parentElement.remove();
  577. }
  578. }
  579. console.log('ClearButton:', isChecked);
  580. }
  581.  
  582. function applySanitizeButton(isChecked = false) {
  583. updateDisplayConfig('displaySanitizeButton', isChecked);
  584.  
  585. const inputWrap = document.querySelector('#search-input-box div.input-wrap');
  586. if (!inputWrap) return;
  587.  
  588. let sanitizeButton = document.querySelector('#' + APP_NAME + '-sanitize-button');
  589. const enterIcon = inputWrap.querySelector('.enter img.enter_icon');
  590.  
  591. if (isChecked) {
  592. if (!sanitizeButton) {
  593. sanitizeButton = document.createElement('button');
  594. sanitizeButton.id = APP_NAME + '-sanitize-button';
  595. sanitizeButton.textContent = '内容脱敏';
  596. sanitizeButton.style.backgroundColor = '#288C8C';
  597. sanitizeButton.style.color = '#FFFFFF';
  598. sanitizeButton.style.border = 'none';
  599. sanitizeButton.style.borderRadius = '4px';
  600. sanitizeButton.style.cursor = 'pointer';
  601. sanitizeButton.style.height = '30px';
  602. sanitizeButton.style.width = '70px';
  603. sanitizeButton.onclick = applyDataSanitizationRules;
  604.  
  605. const sanitizeButtonWrapper = document.createElement('div');
  606. sanitizeButtonWrapper.className = 'enter';
  607. sanitizeButtonWrapper.style.marginRight = '10px';
  608. sanitizeButtonWrapper.style.alignSelf = 'flex-end';
  609. sanitizeButtonWrapper.appendChild(sanitizeButton);
  610.  
  611. if (enterIcon) {
  612. enterIcon.parentNode.before(sanitizeButtonWrapper);
  613. } else {
  614. inputWrap.appendChild(sanitizeButtonWrapper);
  615. }
  616. }
  617. } else {
  618. if (sanitizeButton) {
  619. sanitizeButton.parentElement.remove();
  620. }
  621. }
  622. console.log('SanitizeButton:', isChecked);
  623. }
  624.  
  625. function init() {
  626. addFloatingMenu();
  627.  
  628. applyContentScroll(config.displayContentScroll);
  629. applyWideScreen(config.displayWideScreen);
  630. applyChatBubble(config.displayChatBubble);
  631. applyTopicPopoverMenu(config.displayTopicPopoverMenu);
  632. applyClearButton(config.displayClearButton);
  633. applySanitizeButton(config.displaySanitizeButton);
  634.  
  635. observeDOMChanges(() => {
  636. requestAnimationFrame(() => {
  637. if (config.displayContentScroll) applyContentScroll(true);
  638. if (config.displayWideScreen) applyWideScreen(true);
  639. if (config.displayChatBubble) applyChatBubble(true);
  640. if (config.displayClearButton) applyClearButton(true);
  641. if (config.displaySanitizeButton) applySanitizeButton(true);
  642. });
  643. });
  644. }
  645.  
  646. window.addEventListener('load', init);
  647.  
  648. GM_addStyle(`
  649. .hidden {
  650. display: none !important;
  651. }
  652. `);
  653. })();