Caveduck Modifier

修改Caveduck網站的樣式。

当前为 2024-11-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Caveduck Modifier
  3. // @namespace https://labs.muyi.tw/caveduck_modifier/
  4. // @version 0.26
  5. // @description 修改Caveduck網站的樣式。
  6. // @license AGPL-3.0-or-later
  7. // @author 慕儀
  8. // @match *://caveduck.io/talk/*
  9. // @match *://caveduck.io/created-characters/edit/*
  10. // @match *://caveduck.io/*-editor/*
  11. // @match *://caveduck.io/prompt-build-script/*
  12. // @grant GM_addStyle
  13. // @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDEyODAgMTI4MCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgZmlsbDogI2ZmZjsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjZjJiNDEyOwogICAgICB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KICA8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMjguNy4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogMS4yLjAgQnVpbGQgMTQyKSAgLS0+CiAgPGc+CiAgICA8ZyBpZD0iQ2F2ZWR1Y2siPgogICAgICA8ZyBpZD0iQ2F2ZWR1Y2stMiIgZGF0YS1uYW1lPSJDYXZlZHVjayI+CiAgICAgICAgPHBhdGggZD0iTTEwOTYuMSw0NTMuNWMzMDUuMiw0OTcuMywxNTIuNiw3NDYtNDU3LjgsNzQ2Uy0xMjQuNiw5NTAuOCwxODAuNiw0NTMuNWMzMDUuMi00OTcuMyw2MTAuMy00OTcuMyw5MTUuNSwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTExNDcuNiw0NTMuNWMyMDguOCwzNjEuNywxMDQuNCw1NDIuNS0zMTMuMiw1NDIuNXMtNTIyLTE4MC44LTMxMy4yLTU0Mi41YzIwOC44LTM2MS43LDQxNy42LTM2MS43LDYyNi41LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNjg0LDcyMS4yYzEwMC4yLDE3My42LDUwLjEsMjYwLjQtMTUwLjMsMjYwLjRzLTI1MC42LTg2LjgtMTUwLjMtMjYwLjRjMTAwLjItMTczLjYsMjAwLjUtMTczLjYsMzAwLjcsMFoiLz4KICAgICAgICA8cGF0aCBkPSJNODcxLjksNjEyLjdjMjUuMSw0My40LDEyLjUsNjUuMS0zNy42LDY1LjFzLTYyLjYtMjEuNy0zNy42LTY1LjFjMjUuMS00My40LDUwLjEtNDMuNCw3NS4yLDBaIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPg==
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. let inLanguage, sw_fontOverride, sw_shortButtons, sw_replaceText, sw_mdCorrection, sw_autoHeight;
  20. let debouncedAutoHeight;
  21. const tarAutoHeight = `prompt-input`;
  22. const tarAutoScrollHeight = `lorebook-data-input textarea`;
  23. const textReplaceSelector = `#chatMessages b:not([data-text-replaced]), #chatMessages p:not([data-text-replaced])`;
  24. const cURL = window.location.href;
  25. const $ = (selector) => document.querySelectorAll(selector);
  26. const $$ = (selector) => document.querySelector(selector);
  27. const o_editMyInfoButton = $$('button[ng-click="onEditMyInfoButtonClicked()"]');
  28. const o_editUserNoteButton = $$('button[ng-click="onEditUserNoteButtonClicked()"]');
  29. const o_optionButton = $$('#optionButton');
  30. const o_editButton = $$('#editButton');
  31. const o_imgButton = $$('.hidden[ng-show~="backgroundImage"]');
  32. const muyiStyles = 'https://labs.muyi.tw/caveduck_modifier/style2.css?v=11311281016';
  33. const fontStyles = `
  34. user-input-form div[ng-repeat] textarea,
  35. #chatMessages b,
  36. #chatMessages p,
  37. form[ng-if~="!!chat.editMode"] textarea {
  38. font: normal clamp(16px, .8vw, 32px) / 1.75em var(--m_ff1);
  39. }
  40. #chatMessages b {
  41. font-family: var(--m_ff2);
  42. font-weight: 400;
  43. }
  44. form[ng-if~="!!chat.editMode"] textarea {
  45. font-size: var(--m_font-size);
  46. }
  47. user-input-form div[ng-repeat] textarea {
  48. font-size: var(--m_font-size);
  49. }
  50. `;
  51. const charMap = {
  52. '\\.{2,}': '⋯⋯',
  53. '⋯': '⋯⋯',
  54. '⋯{3,}': '⋯⋯',
  55. '!': '!',
  56. '\\?': '?',
  57. '~': '~',
  58. ';': ';',
  59. ':': ':',
  60. ',': ',',
  61. '\\.': '。',
  62. '\\(': '(',
  63. '\\)': ')'
  64. };
  65. // 主要動作
  66. function mainAction() {
  67. if (sw_autoHeight && (mURL('*/created-characters/edit/*')||mURL('*/prompt-build-script/*')||mURL('*/lorebook-editor/*'))) initializeAutoHeight();
  68. if (['zh-hant', 'zh-hans', 'ja', 'ko'].includes(inLanguage) && (sw_replaceText)) replaceTextContent();
  69. if (sw_mdCorrection && mURL('*/talk/*')) o_imgButton.classList.add('mt_fix');
  70. }
  71. // 添加自訂樣式
  72. function addCustomStyles() {
  73. GM_addStyle(fontStyles);
  74. console.log("Custom styles added.");
  75. }
  76.  
  77. // 自動調整高度的核心函式
  78. function autoHeight(el) {
  79. el.style.height = 'auto';
  80. el.style.overflow = 'auto';
  81. }
  82.  
  83. function autoScrollHeight(el) {
  84. autoHeight(el);
  85. el.style.height = `${el.scrollHeight}px`;
  86. }
  87.  
  88. // 初始化符合條件的元素
  89. function initializeAutoHeight() {
  90. if (!debouncedAutoHeight) {
  91. debouncedAutoHeight = debounce(() => {
  92. $(tarAutoHeight).forEach(autoHeight);
  93. $(tarAutoScrollHeight).forEach(autoScrollHeight);
  94. }, 200, 2);
  95. debouncedAutoHeight();
  96. window.addEventListener('keydown', debouncedAutoHeight);
  97. window.addEventListener('click', debouncedAutoHeight);
  98. }
  99. }
  100.  
  101.  
  102. // 替換指定選擇符的內容
  103. function replaceTextContent() {
  104. const processedAttribute = "data-text-replaced"; // 標記屬性名稱
  105. const el = $(`${textReplaceSelector}:not([${processedAttribute}])`);
  106. el.forEach((el) => {
  107. let originalText = el.textContent;
  108. for (const [pattern, replacement] of Object.entries(charMap)) {
  109. originalText = originalText.replace(new RegExp(pattern, 'g'), replacement);
  110. }
  111. el.textContent = originalText;
  112. el.setAttribute(processedAttribute, ""); // 添加標記屬性
  113. });
  114. }
  115.  
  116. // 延遲觸發的去抖函式
  117. function debounce(func, delay, repeat) {
  118. let timer = null;
  119. let count = 1;
  120. return () => {
  121. func();
  122. if (timer) clearInterval(timer);
  123. timer = setInterval(() => {
  124. func();
  125. count += 1;
  126. if (count >= repeat) {
  127. clearInterval(timer);
  128. }
  129. }, delay);
  130. };
  131. }
  132.  
  133. // 啟動 MutationObserver
  134. function initializeObserver() {
  135. const observer = new MutationObserver(() => {
  136. mainAction();
  137. });
  138. observer.observe(document.body, { childList: true, subtree: true });
  139. console.log("MutationObserver initialized.");
  140. }
  141.  
  142. // 檢查 inLanguage 並啟動必要功能
  143. function checkInLanguage() {
  144. const script = document.querySelector('script[type="application/ld+json"]');
  145. if (script) {
  146. try {
  147. const jsonData = JSON.parse(script.textContent);
  148. inLanguage = jsonData[0]?.inLanguage || '';
  149. } catch (error) {
  150. console.error("Failed to parse JSON:", error);
  151. }
  152. }
  153. }
  154.  
  155. function createSettingsUI() {
  156. const locale = {
  157. 'zh-hant': {
  158. fontCheckbox: ['覆蓋字型', '作用頁:Talk<br>用慕儀喜歡的自訂字型取代預設字型。'],
  159. sbCheckbox: ['快捷按鈕', '作用頁:Talk<br>將「我的資訊」與「使用者筆記」按鈕移到右側。'],
  160. replaceCheckbox: ['取代符號', '作用頁:Talk<br>Claude 3 Haiku會使用錯誤的中文標點符號,這個功能可以修正它。'],
  161. mdCorrection: ['行動裝置修正', '作用頁:Talk<br>修正行動裝置的顯示問題。'],
  162. autoHeightCheckbox: ['編輯頁無卷軸', '作用頁:Edit Character、Lorebook、Custom prompt<br>慕儀認為每個項目使用卷軸十分愚蠢,勾選此項可以將其設為自動高度。'],
  163. toggleButton: '慕儀\n神器',
  164. reloadButton: '套用並重載',
  165. },
  166. 'en': {
  167. fontCheckbox: ['Override Font', 'Effective page: Talk<br>Replace default font with MuYi\'s preferred custom font.'],
  168. sbCheckbox: ['Shortcut buttons', 'Effective page: Talk<br>Move the "My Information" and "User Notes" buttons to the right side.'],
  169. replaceCheckbox: ['Replace Symbols', 'Effective page: Talk<br>Claude 3 Haiku uses incorrect Chinese punctuation. This feature fixes it.'],
  170. mdCorrection: ['Mobile Device Correction', 'Effective page: Talk<br>Fix the display issues of mobile devices.'],
  171. autoHeightCheckbox: ['No Scrollbar for Edit Page', 'Effective page: Edit Character、Lorebook、Custom prompt<br>MuYi thinks using scrollbars for each item is stupid. Enable this to auto-height.'],
  172. toggleButton: 'MuYi\'s\nToolbox',
  173. reloadButton: 'Apply and reload',
  174. },
  175. };
  176.  
  177. const lang = ['zh-hant', 'zh-hans'].includes(inLanguage) ? 'zh-hant' : 'en';
  178. const texts = locale[lang];
  179.  
  180. const checkboxes = [
  181. { key: 'fontCheckbox', id: 'enableFontOverride' },
  182. { key: 'sbCheckbox', id: 'enableSbCheckbox' },
  183. { key: 'replaceCheckbox', id: 'enableReplaceText' },
  184. { key: 'mdCorrection', id: 'enableMdCorrection' },
  185. { key: 'autoHeightCheckbox', id: 'enableAutoHeight' },
  186. ];
  187.  
  188. // 創建核取方塊
  189. const createCheckbox = ({ key, id }) => {
  190. const container = document.createElement('div');
  191. const checkbox = document.createElement('input');
  192. const label = document.createElement('label');
  193. const desc = document.createElement('div');
  194. desc.className = 'desc';
  195.  
  196. checkbox.type = 'checkbox';
  197. checkbox.id = id;
  198.  
  199. const isChecked = JSON.parse(localStorage.getItem(id) || 'false');
  200. checkbox.checked = isChecked;
  201. label.setAttribute('for', id);
  202. label.textContent = texts[key][0];
  203. desc.innerHTML = texts[key][1];
  204. label.appendChild(desc);
  205. checkbox.addEventListener('change', () => {
  206. localStorage.setItem(id, checkbox.checked);
  207. });
  208. container.appendChild(checkbox);
  209. container.appendChild(label);
  210. return container;
  211. };
  212.  
  213.  
  214. // 創建按鈕和設定視窗
  215. const mt = document.createElement('div');
  216. mt.id = 'mt';
  217.  
  218. const toggleButton = document.createElement('button');
  219. toggleButton.className = 'button--red mt_toggleButton';
  220. toggleButton.textContent = texts.toggleButton;
  221. if (lang === 'zh-hant') toggleButton.style.fontSize = '1rem';
  222.  
  223. const settingsPanel = document.createElement('div');
  224. settingsPanel.className = 'mt_fixed mt_settingsPanel';
  225. settingsPanel.style.display = 'none';
  226.  
  227. // 添加核取方塊
  228. checkboxes.forEach((checkbox) => {
  229. settingsPanel.appendChild(createCheckbox(checkbox));
  230. });
  231.  
  232. // 重整按鈕
  233. const reloadButton = document.createElement('button');
  234. reloadButton.textContent = texts.reloadButton;
  235. reloadButton.className = 'button--red';
  236. reloadButton.addEventListener('click', () => location.reload());
  237. settingsPanel.appendChild(reloadButton);
  238.  
  239. // 切換視窗顯示
  240. toggleButton.addEventListener('click', () => {
  241. settingsPanel.style.display = settingsPanel.style.display === 'none' ? 'block' : 'none';
  242. });
  243.  
  244. // 添加到頁面
  245. document.body.appendChild(mt);
  246. mt.appendChild(toggleButton);
  247. document.body.appendChild(settingsPanel);
  248.  
  249. // 快捷按鈕
  250. if (sw_shortButtons && mURL('*/talk/*')) {
  251. ['👤', '📝'].forEach((text, index) => {
  252. const button = document.createElement('button');
  253. button.textContent = text;
  254. button.addEventListener('click', () => {
  255. o_optionButton.click();
  256. o_editButton.click();
  257. (index === 0 ? o_editMyInfoButton : o_editUserNoteButton).click();
  258. });
  259. mt.appendChild(button);
  260. });
  261. }
  262. }
  263.  
  264.  
  265. function mURL(pattern) {
  266. const patternParts = pattern.split('*');
  267. let lastIndex = 0;
  268. for (let part of patternParts) {
  269. if (part === "") continue;
  270. const index = cURL.indexOf(part, lastIndex);
  271. if (index === -1) return false;
  272. lastIndex = index + part.length;
  273. }
  274. return true;
  275. }
  276.  
  277. function checkSettings() {
  278. checkInLanguage();
  279. sw_fontOverride = JSON.parse(localStorage.getItem('enableFontOverride') || 'false');
  280. sw_shortButtons = JSON.parse(localStorage.getItem('enableSbCheckbox') || 'false');
  281. sw_replaceText = JSON.parse(localStorage.getItem('enableReplaceText') || 'false');
  282. sw_autoHeight = JSON.parse(localStorage.getItem('enableAutoHeight') || 'false');
  283. sw_mdCorrection = JSON.parse(localStorage.getItem('enableMdCorrection') || 'false');
  284.  
  285. if (sw_fontOverride) addCustomStyles();
  286. mainAction();
  287. }
  288.  
  289. function setStylesheet() {
  290. const link = document.createElement("link");
  291. link.rel = 'stylesheet';
  292. link.href = muyiStyles;
  293. document.head.appendChild(link);
  294. }
  295.  
  296. window.addEventListener('load', () => {
  297. setStylesheet();
  298. checkSettings();
  299. createSettingsUI();
  300. setTimeout(initializeObserver, 100);
  301. });
  302.  
  303. })();