Codeforces Better!

一个适用于 Codeforces 的 Tampermonkey 脚本,增强功能与界面。

当前为 2025-01-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Codeforces Better!
  3. // @namespace https://greasyfork.org/users/747162
  4. // @version 1.78.0
  5. // @author 北极小狐
  6. // @match *://*.codeforces.com/*
  7. // @match *://*.codeforc.es/*
  8. // @run-at document-start
  9. // @connect www2.deepl.com
  10. // @connect api-free.deepl.com
  11. // @connect api.deepl.com
  12. // @connect api.deeplx.org
  13. // @connect www.iflyrec.com
  14. // @connect dict.youdao.com
  15. // @connect api.interpreter.caiyunai.com
  16. // @connect translate.google.com
  17. // @connect openai.api2d.net
  18. // @connect api.openai.com
  19. // @connect www.luogu.com.cn
  20. // @connect vjudge.net
  21. // @connect clist.by
  22. // @connect greasyfork.org
  23. // @connect rextester.com
  24. // @connect wandbox.org
  25. // @connect sustech.edu.cn
  26. // @connect aowuucdn.oss-cn-beijing.aliyuncs.com
  27. // @connect aowuucdn.oss-accelerate.aliyuncs.com
  28. // @connect 127.0.0.1
  29. // @connect *
  30. // @grant GM_xmlhttpRequest
  31. // @grant GM_info
  32. // @grant GM_setValue
  33. // @grant GM_getValue
  34. // @grant GM_listValues
  35. // @grant GM_deleteValue
  36. // @grant GM_addStyle
  37. // @grant GM_setClipboard
  38. // @grant GM_getResourceText
  39. // @icon https://aowuucdn.oss-accelerate.aliyuncs.com/codeforces.png
  40. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/turndown/7.2.0/turndown.min.js#sha512-sJzEecN5Nk8cq81zKtGq6/z9Z/r3q38zV9enY75IVxiG7ybtlNUt864sL4L1Kf36bYIwxTMVKQOtU4VhD7hGrw==
  41. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/markdown-it/13.0.2/markdown-it.js#sha512-2LtYcLGnCbAWz9nDIrfG2pHFiFu9n+3oGecQlzLuYsLgen/oxiYscGWnDST9J9EZanlsQkDD0ZP2n/6peDuALQ==
  42. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/crypto-js/4.2.0/crypto-js.min.js#sha512-a+SUDuwNzXDvz4XrIcXHuCf089/iJAoN4lmrXJg18XnduKK6YlDHNRalv4yd1N40OKI80tFidF+rqTFKGPoWFQ==
  43. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/chroma-js/2.4.2/chroma.min.js#sha512-zInFF17qBFVvvvFpIfeBzo7Tj7+rQxLeTJDmbxjBz5/zIr89YVbTNelNhdTT+/DCrxoVzBeUPVFJsczKbB7sew==
  44. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/xterm/5.5.0/xterm.js#sha512-Gujw5GajF5is3nMoGv9X+tCMqePLL/60qvAv1LofUZTV9jK8ENbM9L+maGmOsNzuZaiuyc/fpph1KT9uR5w3CQ==
  45. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/dexie/4.0.7/dexie.min.js#sha512-882VotT07mOQRzqIxsyxHzJX0XUaoeee3qXp4THg1A0KI0XFnWFAaLFQm0x6OW3pHSIipVZW+gzQ1w9b6uvkVw==
  46. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/i18next/23.11.5/i18next.min.js#sha512-3RSGkmT48HnO+hlmzGYDx5/w2LIBX0O5hSuYX6KWAxmvVlSjFgoxIaWa2tlMExheGvt3lLyxeTsXfpC47yb8CQ==
  47. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/i18next-http-backend/2.5.2/i18nextHttpBackend.min.js#sha512-bBb+wrGRTx4MvHpksYb1Iv5oJ1o8ineCqpc0cnTgdJQhuAFJJ93SEVXxUOCptvt0vAqYdjzWO5emorYUBt6Ceg==
  48. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/jquery-i18next/1.2.1/jquery-i18next.min.js#sha512-79RgNpOyaf8AvNEUdanuk1x6g53UPoB6Fh2uogMkOMGADBG6B0DCzxc+dDktXkVPg2rlxGvPeAFKoZxTycVooQ==
  49. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/highlight.js/11.9.0/highlight.min.js#sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==
  50. // @require https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/dialog-polyfill/0.5.6/dialog-polyfill.min.js#sha512-qUIG93zKzcLBVD5RGRbx2PBmbVRu+tJIl+EPLTus0z8I1AMru9sQYdlf6cBacSzYmZVncB9rcc8rYBnazqgrxA==
  51. // @require https://update.greasyfork.org/scripts/484742/1311040/i18nextChainedBackendjs.js#sha512-JYm2AqU8EvoEOnCucDItAsNtmGcjbxccOXjnwNFp87zdlyclpEephXrgR2sMlWj/gL4DCJUN3X0JhI1omaRO0A==
  52. // @require https://update.greasyfork.org/scripts/484743/1311041/i18next-localstorage-backendjs.js#sha512-kY1lU3DCvgzkWkOl47sIlmLKdgDcO4T3NYN6p/ET4oi3fnKO74sHUt1xYGtksIHXciKF8Jt+N4RDqG3CRoeYww==
  53. // @resource acwing_cpp_code_completer https://aowuucdn.oss-accelerate.aliyuncs.com/acwing_cpp_code_completer-0.0.11.json#sha512-DQVpao4qMMExToRdid0g/S0nbO/C9hwCECjI5aW8A0g7nvi8hEcD2Lw3QIqdJBV7haP15oJOocfwuiw7ryTO9w==
  54. // @resource wandboxlist https://wandbox.org/api/list.json
  55. // @resource xtermcss https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/xterm/5.5.0/xterm.min.css#sha512-XpXUuzg5afNt1bsgnrOesXP70TLH8tXYYK5sK+Y0UV+YBvJn9EfRFYWy4HT3TVDfH0nl1CO0lwOxIrt2gk9qjg==
  56. // @resource selectpagecss https://aowuucdn.oss-accelerate.aliyuncs.com/css/selectpage.css#sha512-cRXJfA2tEcAxHEKylJfxteY17N7j9fia3waahHOVnvl63uVZT9OQ7jjjpofZMVZ4JSX3BRET+mI8UvKnsXd3NA==
  57. // @resource dialogpolyfillcss https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/dialog-polyfill/0.5.6/dialog-polyfill.min.css#sha512-J2+1q+RsZuJXabBfH1q/fgRr6jMy9By5SwVLk7bScEW7NFJkMUXxfeOyyxtDe6fsaJ4jsciexSlGrPYn9YbBIg==
  58. // @license GPL3
  59. // @compatible Chrome
  60. // @compatible Firefox
  61. // @compatible Edge
  62. // @incompatible safari
  63. // @supportURL https://github.com/beijixiaohu/OJBetter/issues
  64. // @name:zh-TW Codeforces Better!
  65. // @name:en Codeforces Better!
  66. // @name:de Codeforces Better!
  67. // @name:fr Codeforces Better!
  68. // @name:ko Codeforces Better!
  69. // @name:pt Codeforces Better!
  70. // @name:ja Codeforces Better!
  71. // @name:es Codeforces Better!
  72. // @name:it Codeforces Better!
  73. // @name:hi Codeforces Better!
  74. // @description 一个适用于 Codeforces 的 Tampermonkey 脚本,增强功能与界面。
  75. // @description:zh-TW 一個適用於 Codeforces 的 Tampermonkey 腳本,增強功能與界面。
  76. // @description:en A Tampermonkey script for Codeforces that enhances functionality and interface.
  77. // @description:de Ein Tampermonkey-Skript für Codeforces, das Funktionalität und Benutzeroberfläche verbessert.
  78. // @description:fr Un script Tampermonkey pour Codeforces qui améliore les fonctionnalités et l'interface.
  79. // @description:ko Codeforces를 위한 Tampermonkey 스크립트로 기능과 인터페이스를 개선합니다.
  80. // @description:pt Um script Tampermonkey para Codeforces que aprimora a funcionalidade e a interface.
  81. // @description:ja Codeforces用のTampermonkeyスクリプトで機能とインターフェースを強化します。
  82. // @description:es Un script Tampermonkey para Codeforces que mejora la funcionalidad y la interfaz.
  83. // @description:it Uno script Tampermonkey per Codeforces che migliora la funzionalità e l'interfaccia.
  84. // @description:hi Codeforces के लिए एक Tampermonkey स्क्रिप्ट जो कार्यक्षमता और इंटरफ़ेस को बेहतर बनाता है।
  85. // ==/UserScript==
  86.  
  87. /**
  88. * @namespace OJBetter
  89. * @desc 主命名空间
  90. */
  91. const OJBetter = {};
  92.  
  93. /**
  94. * @namespace state
  95. * @desc 描述脚本的当前状态。
  96. * @memberof OJBetter
  97. */
  98. OJBetter.state = {
  99. /** @type {string} 脚本名*/
  100. name: GM_info.script.name,
  101. /** @type {string} 格式化后的脚本名*/
  102. formatName: undefined,
  103. /** @type {string} 版本号*/
  104. version: GM_info.script.version,
  105. /** @type {boolean?} 是否跳过页面加载等待 */
  106. notWaiteLoaded: undefined,
  107. /** @type {string} 最后公告版本,用于标识版本更新完成提示 */
  108. lastAnnounceVer: undefined,
  109. /** @type {string} 最后读取的有效公告版本 */
  110. lastReadAnnounceVer: undefined,
  111. /** @type {number} 当前已打开的模态对话框数量*/
  112. openDialogCount: 0,
  113. };
  114.  
  115. /**
  116. * @namespace common
  117. * @desc 通用设置和属性。
  118. * @memberof OJBetter
  119. */
  120. OJBetter.common = {
  121. /** @type {string} 网站的主机地址 */
  122. hostAddress: location.origin,
  123. /** @type {string} 网站当前真实的黑暗模式 */
  124. realDarkMode: undefined,
  125. /** @type {string?} Codeforces的CSRF令牌 */
  126. cf_csrf_token: undefined,
  127. /** @type {Array?} 任务队列 */
  128. taskQueue: undefined,
  129. /** @type {object} OJBetter数据库连接实例*/
  130. database: undefined,
  131. /** @type {object} turndownService实例*/
  132. turndownService: undefined,
  133. };
  134.  
  135. /**
  136. * @namespace basic
  137. * @desc 基本的用户界面设置。
  138. * @memberof OJBetter
  139. */
  140. OJBetter.basic = {
  141. /** @type {string} 黑暗模式设置 */
  142. darkMode: undefined,
  143. /** @type {boolean?} 是否展开折叠块 */
  144. expandFoldingblocks: undefined,
  145. /** @type {boolean?} 是否开启折叠块渲染性能优化 */
  146. renderPerfOpt: undefined,
  147. /** @type {boolean?} 是否开启下拉选择框性能优化 */
  148. selectElementPerfOpt: undefined,
  149. /** @type {boolean?} 评论区分页 */
  150. commentPaging: undefined,
  151. /** @type {boolean?} 显示跳转到Luogu按钮 */
  152. showJumpToLuogu: undefined,
  153. /** @type {boolean?} 显示跳转到Virtual Judge按钮 */
  154. showCF2vjudge: undefined,
  155. /** @type {boolean?} 比赛排行榜重新着色 */
  156. standingsRecolor: undefined,
  157. /** @type {boolean?} 隐藏题目问题标签 */
  158. hiddenProblemTag: undefined,
  159. };
  160.  
  161. /**
  162. * @namespace typeOfPage
  163. * @desc 页面类型判断。
  164. * @memberof OJBetter
  165. */
  166. OJBetter.typeOfPage = {
  167. /** @type {boolean?} 是否是轻量站 */
  168. is_mSite: undefined,
  169. /** @type {boolean?} 是否是训练营页面 */
  170. is_gym: undefined,
  171. /** @type {boolean?} 是否是acmsguru页面 */
  172. is_acmsguru: undefined,
  173. /** @type {boolean?} 是否是旧版LaTeX页面 */
  174. is_oldLatex: undefined,
  175. /** @type {boolean?} 是否是题目集页面 */
  176. is_contest: undefined,
  177. /** @type {boolean?} 是否是题目页面 */
  178. is_problem: undefined,
  179. /** @type {boolean?} 是否是完整的问题集页面 */
  180. is_completeProblemset: undefined,
  181. /** @type {boolean?} 是否是问题集中的问题页面 */
  182. is_problemset_problem: undefined,
  183. /** @type {boolean?} 是否是问题集页面 */
  184. is_problemset: undefined,
  185. /** @type {boolean?} 是否是Codeforces排名页面 */
  186. is_cfStandings: undefined,
  187. /** @type {boolean?} 是否是提交页面 */
  188. is_submitPage: undefined,
  189. /** @type {boolean?} 是否是代码状态页面 */
  190. is_statePage: undefined,
  191. /** @type {boolean?} 是否是提交记录页面 */
  192. is_submissions: undefined,
  193. /** @type {boolean?} 是否是提交记录详情页面 */
  194. is_submission: undefined,
  195. };
  196.  
  197. /**
  198. * @namespace localization
  199. * @desc 本地化设置。
  200. * @memberof OJBetter
  201. */
  202. OJBetter.localization = {
  203. /** @type {string?} 网站语言 */
  204. websiteLang: undefined,
  205. /** @type {string?} 脚本语言 */
  206. scriptLang: undefined,
  207. };
  208.  
  209. /**
  210. * @namespace translation
  211. * @desc 翻译设置。
  212. * @memberof OJBetter
  213. */
  214. OJBetter.translation = {
  215. /** @type {string?} 翻译服务选择 */
  216. choice: undefined,
  217. /** @type {string?} 目标语言 */
  218. targetLang: undefined,
  219. comment: {
  220. /** @type {string?} 评论翻译服务选择 */
  221. choice: undefined,
  222. /** @type {string?} 评论翻译模式 */
  223. transMode: undefined,
  224. },
  225. auto: {
  226. /** @type {boolean?} 自动翻译开关 */
  227. enabled: undefined,
  228. /** @type {number?} 短文本长度限制 */
  229. shortTextLength: undefined,
  230. mixTrans: {
  231. /** @type {boolean?} 混合翻译开关 */
  232. enabled: undefined,
  233. /** @type {Array?} 混合翻译服务列表 */
  234. servers: undefined,
  235. },
  236. },
  237. memory: {
  238. /** @type {boolean?} 翻译记忆开关 */
  239. enabled: undefined,
  240. /** @type {Object?} 翻译记忆树 */
  241. ttTree: undefined,
  242. },
  243. /** @type {string?} 重翻译时的行为 */
  244. retransAction: undefined,
  245. /** @type {number?} 等待时间 */
  246. waitTime: undefined,
  247. /** @type {boolean?} 替换符 */
  248. replaceSymbol: undefined,
  249. /** @type {boolean?} 过滤文本中的*号 */
  250. filterTextWithoutEmphasis: undefined,
  251. /** @type {boolean?} 强制使用turndown转换 */
  252. forceTurndownConversion: undefined,
  253. };
  254.  
  255. /**
  256. * @namespace clist
  257. * @desc Clist相关设置。
  258. * @memberof OJBetter
  259. */
  260. OJBetter.clist = {
  261. enabled: {
  262. /** @type {boolean?} 比赛页面开关 */
  263. contest: undefined,
  264. /** @type {boolean?} 问题页面开关 */
  265. problem: undefined,
  266. /** @type {boolean?} 问题集页面开关 */
  267. problemset: undefined,
  268. },
  269. /** @type {boolean?} Rating数据防剧透 */
  270. ratingHidden: undefined,
  271. /** @type {string?} Clist key */
  272. authorization: undefined,
  273. };
  274.  
  275. /**
  276. * @namespace monaco
  277. * @desc Monaco编辑器配置。
  278. * @memberof OJBetter
  279. */
  280. OJBetter.monaco = {
  281. /** @type {boolean?} 在问题页面上启用Monaco编辑器 */
  282. enableOnProblemPage: undefined,
  283. /** @type {boolean?} 美化pre代码块 */
  284. beautifyPreBlocks: undefined,
  285. /** @type {boolean} Monaco编辑器加载完成标志 */
  286. loaderOnload: false,
  287. lsp: {
  288. /** @type {Array?} LSP套接字数组 */
  289. socket: [],
  290. /** @type {boolean?} 是否启用LSP */
  291. enabled: undefined,
  292. /** @type {string?} 工作路径 */
  293. workUri: undefined,
  294. /** @type {string?} 套接字URL */
  295. socketUrl: undefined,
  296. },
  297. complet: {
  298. /** @type {boolean?} 是否启用C++代码补全模板 */
  299. cppCodeTemplate: undefined,
  300. /** @type {Object?} 自定义配置 */
  301. customConfig: undefined,
  302. },
  303. /** @type {Object?} Monaco编辑器实例 */
  304. editor: null,
  305. /** @type {Array?} 代码块美化的Monaco编辑器实例 */
  306. beautifyEditor: [],
  307. /** @type {string?} 在线编译器选择 */
  308. onlineCompilerChoice: undefined,
  309. /** @type {string?} 记忆编译器语言选择 */
  310. compilerSelection: undefined,
  311. /** @type {string?} 当前选择的语言 */
  312. nowLangSelect: undefined,
  313. setting: {
  314. /** @type {Array?} 语言设置数组 */
  315. language: [],
  316. /** @type {string?} 位置 */
  317. position: undefined,
  318. /** @type {boolean} 位置初始化标志 */
  319. position_initialized: false,
  320. /** @type {number?} 字体大小 */
  321. fontsize: undefined,
  322. /** @type {boolean?} 鼠标滚动锁定 */
  323. alwaysConsumeMouseWheel: undefined,
  324. /** @type {boolean?} 提交代码二次确认 */
  325. isCodeSubmitDoubleConfirm: undefined,
  326. /** @type {boolean?} 测试通过后自动提交 */
  327. autoSubmitAfterPass: undefined,
  328. /** @type {string?} 提交按钮位置 */
  329. submitButtonPosition: undefined,
  330. /** @type {boolean?} 自动保存代码 */
  331. autoMemoryCode: undefined,
  332. },
  333. };
  334.  
  335. /**
  336. * @namespace deepl
  337. * @desc DeepL翻译服务配置。
  338. * @memberof OJBetter
  339. */
  340. OJBetter.deepl = {
  341. /** @type {Object?} DeepL配置对象 */
  342. configs: undefined,
  343. config: {
  344. /** @type {string?} 类型 */
  345. type: undefined,
  346. /** @type {string?} 名称 */
  347. name: undefined,
  348. /** @type {string?} API类型 */
  349. apiGenre: undefined,
  350. /** @type {string?} API密钥 */
  351. key: undefined,
  352. /** @type {string?} 代理 */
  353. proxy: undefined,
  354. /** @type {Object?} 额外请求头 */
  355. header: undefined,
  356. /** @type {Object?} 额外请求数据 */
  357. data: undefined,
  358. quota: {
  359. /** @type {string?} 余额URL */
  360. url: undefined,
  361. /** @type {string?} 余额请求方法 */
  362. method: undefined,
  363. /** @type {Object?} 余额请求头 */
  364. header: undefined,
  365. /** @type {Object?} 余额请求数据 */
  366. data: undefined,
  367. /** @type {number?} 剩余配额 */
  368. surplus: undefined,
  369. },
  370. },
  371. /** @type {boolean?} 启用重点保护 */
  372. enableEmphasisProtection: undefined,
  373. /** @type {boolean?} 启用链接保护 */
  374. enableLinkProtection: undefined,
  375. };
  376.  
  377. /**
  378. * @namespace chatgpt
  379. * @desc ChatGPT服务配置。
  380. * @memberof OJBetter
  381. */
  382. OJBetter.chatgpt = {
  383. /** @type {Object?} ChatGPT配置对象 */
  384. configs: undefined,
  385. config: {
  386. /** @type {string?} 名称 */
  387. name: undefined,
  388. /** @type {string?} 模型 */
  389. model: undefined,
  390. /** @type {string?} API密钥 */
  391. key: undefined,
  392. /** @type {string?} 代理 */
  393. proxy: undefined,
  394. /** @type {Object?} 额外请求头 */
  395. header: undefined,
  396. /** @type {Object?} 额外请求数据 */
  397. data: undefined,
  398. quota: {
  399. /** @type {string?} 余额URL */
  400. url: undefined,
  401. /** @type {string?} 余额请求方法 */
  402. method: undefined,
  403. /** @type {Object?} 余额请求头 */
  404. header: undefined,
  405. /** @type {Object?} 余额请求数据 */
  406. data: undefined,
  407. /** @type {number?} 剩余配额 */
  408. surplus: undefined,
  409. },
  410. },
  411. /** @type {boolean?} 是否为流式传输 */
  412. isStream: undefined,
  413. /** @type {string?} 是否使用自定义Prompt */
  414. customPrompt: undefined,
  415. /** @type {boolean?} 是否作为系统Prompt */
  416. asSystemPrompt: undefined,
  417. };
  418.  
  419. /**
  420. * @namespace preference
  421. * @desc 偏好设置
  422. * @memberof OJBetter
  423. */
  424. OJBetter.preference = {
  425. /** @type {boolean?} 是否显示加载动画 */
  426. showLoading: undefined,
  427. /** @type {boolean?} 是否显示悬停目标区域 */
  428. hoverTargetAreaDisplay: undefined,
  429. /** @type {string?} 按钮图标大小 */
  430. iconButtonSize: undefined,
  431. /** @type {string?} 评测状态文本替换规则 */
  432. judgeStatusReplaceText: undefined,
  433. };
  434.  
  435. /**
  436. * @namespace dev
  437. * @desc 维护
  438. * @memberof OJBetter
  439. */
  440. OJBetter.dev = {
  441. /** @type {boolean?} 是否显示规则标记 */
  442. isRuleMarkingEnabled: undefined,
  443. };
  444.  
  445. /**
  446. * @namespace about
  447. * @desc 关于页信息
  448. * @memberof OJBetter
  449. */
  450. OJBetter.about = {
  451. /** @type {string?} 更新通道 */
  452. updateChannel: undefined,
  453. /** @type {string?} 更新源 */
  454. updateSource: undefined,
  455. };
  456.  
  457. /**
  458. * @namespace supportList
  459. * @desc 支持列表
  460. * @memberof OJBetter
  461. */
  462. OJBetter.supportList = {
  463. /** @type {object} 翻译支持列表和对应语言代码*/
  464. translationSupport: {
  465. deepl: {
  466. zh: "ZH",
  467. de: "DE",
  468. fr: "FR",
  469. ko: "KO",
  470. pt: "PT",
  471. ja: "JA",
  472. es: "ES",
  473. it: "IT",
  474. },
  475. iflyrec: { zh: "1" },
  476. youdao: {
  477. zh: "zh-CHS",
  478. "zh-Hant": "zh-CHT",
  479. de: "de",
  480. fr: "fr",
  481. ko: "ko",
  482. pt: "pt",
  483. ja: "ja",
  484. es: "es",
  485. it: "it",
  486. hi: "hi",
  487. },
  488. google: {
  489. zh: "zh-CN",
  490. "zh-Hant": "zh-TW",
  491. de: "de",
  492. fr: "fr",
  493. ko: "ko",
  494. pt: "pt",
  495. ja: "ja",
  496. es: "es",
  497. it: "it",
  498. hi: "hi",
  499. },
  500. caiyun: {
  501. zh: "auto2zh",
  502. ja: "auto2ja",
  503. ko: "auto2ko",
  504. es: "auto2es",
  505. fr: "auto2fr",
  506. },
  507. openai: {
  508. zh: "Chinese",
  509. "zh-Hant": "Traditional Chinese",
  510. de: "German",
  511. fr: "French",
  512. ko: "Korean",
  513. pt: "Portuguese",
  514. ja: "Japanese",
  515. es: "Spanish",
  516. it: "Italian",
  517. hi: "Hindi",
  518. },
  519. },
  520. /** @type {object} 更新源支持列表*/
  521. updateSourceSupportList: {
  522. greasyfork: {
  523. release: true,
  524. dev: false,
  525. },
  526. github: {
  527. release: true,
  528. dev: true,
  529. },
  530. aliyunoss: {
  531. release: true,
  532. dev: true,
  533. },
  534. },
  535. };
  536.  
  537. // ------------------------------
  538. // 一些工具函数
  539. // ------------------------------
  540.  
  541. /**
  542. * 延迟函数
  543. * @param {number} ms 延迟时间(毫秒)
  544. * @returns {Promise<void>}
  545. */
  546. function OJB_delay(ms) {
  547. return new Promise((resolve) => setTimeout(resolve, ms));
  548. }
  549.  
  550. /**
  551. * 等待直到指定的条件函数返回true。
  552. *
  553. * @param {() => boolean} conditionCheck 一个无参数的函数,用于检查条件是否满足。当函数返回true时,表示条件已满足。
  554. * @param {number} [interval=100] 检查条件的间隔时间,单位为毫秒。默认为100毫秒。
  555. * @returns {Promise<void>} 返回一个Promise,在条件满足时解决。
  556. */
  557. async function OJB_waitUntilTrue(conditionCheck, interval = 100) {
  558. return new Promise((resolve) => {
  559. const checkCondition = async () => {
  560. if (conditionCheck()) {
  561. resolve();
  562. } else {
  563. await OJB_delay(interval);
  564. checkCondition();
  565. }
  566. };
  567. checkCondition();
  568. });
  569. }
  570.  
  571. /**
  572. * 动态加载JavaScript库并返回一个Promise,该Promise在脚本加载完成后解决。
  573. *
  574. * @param {string} url - 要加载的JavaScript库的URL地址。
  575. * @param {string} [expectedHash] - 可选的Base64编码的SHA-512哈希值,用于校验脚本内容。格式为 "sha512-<Base64编码的哈希值>"。
  576. * @returns {Promise<void>} 一个Promise,它在脚本加载并执行完成后解决。
  577. */
  578. async function OJB_LoadJS(url, expectedHash) {
  579. /**
  580. * 计算给定数据的SHA-512哈希值,并将其转换为十六进制字符串。
  581. *
  582. * @param {string} data - 要计算哈希值的数据。
  583. * @returns {Promise<string>} 一个Promise,它解析为数据的SHA-512哈希值的十六进制字符串。
  584. */
  585. const calculateHash = async (data) => {
  586. const encoder = new TextEncoder();
  587. const dataBuffer = encoder.encode(data);
  588. const hashBuffer = await crypto.subtle.digest("SHA-512", dataBuffer);
  589. const hashArray = Array.from(new Uint8Array(hashBuffer));
  590. return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
  591. };
  592.  
  593. /**
  594. * 将Base64编码的字符串转换为十六进制字符串。
  595. *
  596. * @param {string} base64 - Base64编码的字符串。
  597. * @returns {string} 转换后的十六进制字符串。
  598. */
  599. const base64ToHex = (base64) => {
  600. const binaryString = atob(base64);
  601. const byteArray = new Uint8Array(binaryString.length);
  602. for (let i = 0; i < binaryString.length; i++) {
  603. byteArray[i] = binaryString.charCodeAt(i);
  604. }
  605. return Array.from(byteArray)
  606. .map((b) => b.toString(16).padStart(2, "0"))
  607. .join("");
  608. };
  609.  
  610. try {
  611. const response = await fetch(url);
  612. if (!response.ok)
  613. throw new Error(`Failed to fetch script: ${response.statusText}`);
  614. const scriptContent = await response.text();
  615.  
  616. if (expectedHash) {
  617. // 去掉前缀 "sha512-"
  618. const base64Hash = expectedHash.replace(/^sha512-/, "");
  619. const actualHash = await calculateHash(scriptContent);
  620. const expectedHashHex = base64ToHex(base64Hash);
  621. if (actualHash !== expectedHashHex)
  622. throw new Error("SHA-512 hash mismatch");
  623. }
  624.  
  625. const scriptElement = document.createElement("script");
  626. scriptElement.textContent = scriptContent;
  627. document.head.prepend(scriptElement);
  628.  
  629. return Promise.resolve();
  630. } catch (error) {
  631. return Promise.reject(error);
  632. }
  633. }
  634.  
  635. /**
  636. * 安全地创建JQuery对象
  637. * @description 通过字符串创建JQuery对象时,如果字符串以空格开头,在某些Jquery版本中会发生错误,过滤空格以安全的创建元素。
  638. * @param {string} string - 字符串。
  639. * @returns {JQuery} JQuery对象
  640. */
  641. const OJB_safeCreateJQElement = function (string) {
  642. return $(string.replace(/^\s+/, ""));
  643. };
  644.  
  645. /**
  646. * 将数字或者字符串解析为数字。
  647. * @memberof OJBetter.common
  648. * @param {string} val 要解析的字符串
  649. * @param {boolean} [strict=false] 是否进行严格类型检查
  650. * @returns {number} 解析结果
  651. * @throws {Error} 如果解析失败,则抛出错误
  652. */
  653. const OJB_parseNumber = (val, strict = false) => {
  654. const num = Number(val);
  655. if (isNaN(num) || (strict && val.toString() !== num.toString())) {
  656. throw new Error("Invalid number");
  657. }
  658. return num;
  659. };
  660.  
  661. /**
  662. * 将字符串解析为布尔值
  663. * @param {string} val - 要解析的字符串
  664. * @param {boolean} strict - 是否进行严格类型检查
  665. * @returns {boolean} - 解析结果
  666. * @throws {Error} - 如果解析失败,则抛出错误
  667. */
  668. const OJB_parseBoolean = (val, strict) => {
  669. if (strict) {
  670. if (val === true || val === false) return val;
  671. throw new Error("Invalid boolean");
  672. }
  673. return val === "true" ? true : val === "false" ? false : val;
  674. };
  675.  
  676. /**
  677. * 将字符串解析为对象
  678. * @param {string} val - 要解析的字符串
  679. * @returns {Object} - 解析结果
  680. * @throws {Error} - 如果解析失败,则抛出错误
  681. */
  682. const OJB_parseObject = (val) => {
  683. try {
  684. return JSON.parse(val);
  685. } catch {
  686. throw new Error("Invalid JSON");
  687. }
  688. };
  689.  
  690. /**
  691. * 将字符串解析为键值对数组
  692. * @param {string} val - 要解析的字符串
  693. * @returns {Object[]} - 解析结果
  694. * @throws {Error} - 如果解析失败,则抛出错误
  695. */
  696. const OJB_parseLinePairArray = (val) => {
  697. if (typeof val !== "string" || val.trim() === "") return [];
  698. return val
  699. .split("\n")
  700. .filter((line) => line.trim() !== "")
  701. .map((line) => {
  702. const indexOfFirstColon = line.indexOf(":");
  703. if (indexOfFirstColon === -1)
  704. throw new Error('Invalid LinePairArray format: ":" is missing');
  705. const key = line.substring(0, indexOfFirstColon).trim();
  706. const value = line.substring(indexOfFirstColon + 1).trim();
  707. return { [key]: value };
  708. });
  709. };
  710.  
  711. /**
  712. * 移除文本中的HTML标签
  713. * @param {string} text - 包含HTML标签的文本
  714. * @returns {string} - 移除HTML标签后的文本
  715. */
  716. const OJB_removeHTMLTags = function (text) {
  717. return text.replace(/<\/?[a-zA-Z]+("[^"]*"|'[^']*'|[^'">])*>/g, "");
  718. };
  719.  
  720. /**
  721. * 获取对象中指定路径表达式的值
  722. * @param {Object} obj - 要计算的对象
  723. * @param {string} pathOrExpression - 要计算的路径表达式
  724. * @returns {any} - 计算结果
  725. * @example
  726. * const obj = {
  727. * "a": {
  728. * "b": 1
  729. * },
  730. * "c": 2
  731. * };
  732. * OJB_evaluatePathOrExpression(obj, "a.b"); // 1
  733. * OJB_evaluatePathOrExpression(obj, "a.b + c"); // 3
  734. * OJB_evaluatePathOrExpression(obj, "a.b + a.c"); // 1
  735. */
  736. function OJB_evaluatePathOrExpression(obj, pathOrExpression) {
  737. const hasOperator = /[\+\-\*\/]/.test(pathOrExpression);
  738. const getPathValue = (obj, path) => {
  739. return path.split(".").reduce((acc, part) => {
  740. return acc !== undefined && acc !== null && acc.hasOwnProperty(part)
  741. ? acc[part]
  742. : undefined;
  743. }, obj);
  744. };
  745. const evaluateExpression = (obj, expression) => {
  746. const tokens = expression
  747. .split(/([\+\-\*\/])/)
  748. .map((token) => token.trim());
  749. const values = tokens.map((token) => {
  750. if (/[\+\-\*\/]/.test(token)) {
  751. return token;
  752. } else {
  753. const value = getPathValue(obj, token);
  754. return value !== undefined ? value : 0;
  755. }
  756. });
  757. const evaluatedExpression = values.join(" ");
  758. try {
  759. return Function(`'use strict'; return (${evaluatedExpression});`)();
  760. } catch (e) {
  761. console.error("Expression evaluation error:", e);
  762. return undefined;
  763. }
  764. };
  765. return hasOperator
  766. ? evaluateExpression(obj, pathOrExpression)
  767. : getPathValue(obj, pathOrExpression);
  768. }
  769.  
  770. /**
  771. * 获取 GM 存储的值并根据类型进行处理
  772. * @param {string} key - 要检索的值的键。
  773. * @param {any} defaultValue - 如果值未找到,则返回的默认值。
  774. * @param {Object} [options={}] - 配置选项对象。
  775. * @param {string} [options.type='string'] - 期望的值的类型。可选值:'string', 'number', 'boolean', 'object', 'array', 'linePairArray'。
  776. * @param {boolean} [options.strict=false] - 用于数字和布尔类型,表示是否进行严格类型检查。
  777. * @param {string} [options.pathOrExpression=''] - 用于对象或数组类型,表示路径表达式或获取元素的索引。
  778. * @returns {any} - 检索到的值。
  779. */
  780. const OJB_getGMValue = (
  781. key,
  782. defaultValue,
  783. { type = "string", strict = false, pathOrExpression = "" } = {}
  784. ) => {
  785. let value = GM_getValue(key);
  786. if (value === undefined || value === null || value === "") {
  787. GM_setValue?.(key, defaultValue);
  788. return defaultValue;
  789. }
  790.  
  791. const parsers = {
  792. string: (val) => val,
  793. number: (val) => OJB_parseNumber(val, strict),
  794. boolean: (val) => OJB_parseBoolean(val, strict),
  795. object: OJB_parseObject,
  796. array: OJB_parseObject,
  797. linePairArray: OJB_parseLinePairArray,
  798. };
  799.  
  800. if (!(type in parsers)) {
  801. console.error(`Unsupported type: ${type}`);
  802. return defaultValue;
  803. }
  804.  
  805. try {
  806. value = parsers[type](value);
  807. } catch (e) {
  808. console.error("Error:", e.message);
  809. return defaultValue;
  810. }
  811.  
  812. // The pathOrExpression processing is not applicable to linePairArray type
  813. if ((type === "object" || type === "array") && pathOrExpression) {
  814. const evaluated = OJB_evaluatePathOrExpression(value, pathOrExpression);
  815. if (evaluated === undefined) {
  816. console.error("Path or expression evaluation returned undefined");
  817. return defaultValue;
  818. }
  819. value = evaluated;
  820. }
  821.  
  822. return value;
  823. };
  824.  
  825. /**
  826. * 版本号比较方法
  827. * @param {string} version1 版本号1
  828. * @param {string} version2 版本号2
  829. * @returns {number} -1: version1 < version2, 0: version1 = version2, 1: version1 > version2
  830. */
  831. const OJB_compareVersions = function (version1 = "0", version2 = "0") {
  832. const v1Array = version1.split(".").map(Number);
  833. const v2Array = version2.split(".").map(Number);
  834. const length = Math.max(v1Array.length, v2Array.length);
  835. for (let i = 0; i < length; i++) {
  836. const diff = (v1Array[i] || 0) - (v2Array[i] || 0);
  837. if (diff) return Math.sign(diff);
  838. }
  839. return 0;
  840. };
  841.  
  842. /**
  843. * 获取上一个主版本号
  844. * @param {string} currentVersion 当前版本号
  845. * @returns {string} 上一个主版本号
  846. */
  847. const OJB_getPreviousVersion = function (currentVersion) {
  848. const versionArray = currentVersion.split(".").map(Number);
  849. let lastNonZeroIndex = versionArray.length - 1;
  850. while (lastNonZeroIndex >= 0 && versionArray[lastNonZeroIndex] === 0) {
  851. lastNonZeroIndex--;
  852. }
  853. if (lastNonZeroIndex >= 0) {
  854. versionArray[lastNonZeroIndex]--;
  855. for (let i = lastNonZeroIndex + 1; i < versionArray.length; i++) {
  856. versionArray[i] = 0;
  857. }
  858. }
  859. return versionArray.join(".");
  860. };
  861.  
  862. /**
  863. * 在指定根节点下观察指定选择器的元素,当元素存在时,执行回调函数
  864. * @param {Object} options - 配置对象
  865. * @param {string} options.selector - CSS选择器文本
  866. * @param {Function} options.callback - 回调函数,接收变动的节点作为参数
  867. * @param {Boolean} [options.triggerOnExist=true] - 如果为true,元素已存在时立即触发一次回调
  868. * @param {Element} [options.root=document.body] - 在哪个根节点下监听变化
  869. * @param {Boolean} [options.subtree=false] - 是否监听子树变化(即非直接子元素)
  870. */
  871. function OJB_observeElement({
  872. selector,
  873. callback,
  874. triggerOnExist = true,
  875. root = document.body,
  876. subtree = false,
  877. }) {
  878. // 尝试获取选择器指定的元素
  879. const targetNode = root.querySelector(selector);
  880.  
  881. if (targetNode) {
  882. // 如果元素已存在,直接开始观察
  883. observeAndReport(targetNode, callback);
  884. // 如果triggerOnExist为true,则立即触发一次回调
  885. if (triggerOnExist) {
  886. callback(targetNode);
  887. }
  888. } else {
  889. // 如果元素不存在,监听DOM变化直到该元素被添加
  890. const observer = new MutationObserver((mutations) => {
  891. mutations.forEach((mutation) => {
  892. mutation.addedNodes.forEach((node) => {
  893. if (node.nodeType === Node.ELEMENT_NODE && node.matches(selector)) {
  894. observeAndReport(node, callback);
  895. if (triggerOnExist) {
  896. callback(node);
  897. }
  898. observer.disconnect(); // 停止监听
  899. }
  900. });
  901. });
  902. });
  903.  
  904. observer.observe(root, { childList: true, subtree, attributes: false });
  905. }
  906.  
  907. function observeAndReport(node, callback) {
  908. const childObserver = new MutationObserver((mutations) => {
  909. mutations.forEach((mutation) => {
  910. mutation.addedNodes.forEach((addedNode) => {
  911. if (addedNode.nodeType === Node.ELEMENT_NODE) {
  912. callback(addedNode); // 执行回调函数
  913. }
  914. });
  915. });
  916. });
  917.  
  918. childObserver.observe(node, {
  919. childList: true,
  920. subtree: true,
  921. attributes: false,
  922. });
  923. }
  924. }
  925.  
  926. /**
  927. * 初始化全局变量
  928. */
  929. async function initVar() {
  930. const { hostname, href } = window.location;
  931. OJBetter.state.formatName = (() =>
  932. OJBetter.state.name
  933. .toLowerCase()
  934. .replace(/\s+/g, "-")
  935. .replace(/[^a-z0-9-]/g, ""))();
  936. OJBetter.state.lastAnnounceVer = OJB_getGMValue("lastAnnounceVer", "0");
  937. OJBetter.state.lastReadAnnounceVer = OJB_getGMValue(
  938. "lastReadAnnounceVer",
  939. "0"
  940. );
  941. OJBetter.typeOfPage.is_mSite = /^m[0-9]/.test(hostname);
  942. OJBetter.typeOfPage.is_oldLatex = $(".tex-span").length;
  943. OJBetter.typeOfPage.is_gym =
  944. href.includes("gym") && href.includes("/problem/");
  945. OJBetter.typeOfPage.is_acmsguru =
  946. href.includes("acmsguru") && href.includes("/problem/");
  947. OJBetter.typeOfPage.is_contest =
  948. /\/contest\/[\d\/\s]+$/.test(href) && !href.includes("/problem/");
  949. OJBetter.typeOfPage.is_problem = href.includes("/problem/");
  950. OJBetter.typeOfPage.is_completeProblemset = /problems\/?$/.test(href);
  951. OJBetter.typeOfPage.is_problemset_problem =
  952. href.includes("/problemset/") && href.includes("/problem/");
  953. OJBetter.typeOfPage.is_problemset =
  954. href.includes("/problemset") && !href.includes("/problem/");
  955. OJBetter.typeOfPage.is_submitPage = href.includes("/submit");
  956. OJBetter.typeOfPage.is_statePage =
  957. href.includes("/status") || /\/my\/?$/.test(href);
  958. OJBetter.typeOfPage.is_submissions = href.includes("/submissions");
  959. OJBetter.typeOfPage.is_submission = href.includes("/submission");
  960. OJBetter.typeOfPage.is_cfStandings =
  961. href.includes("/standings") &&
  962. $(".standings tr:first th:nth-child(n+5)")
  963. .map(function () {
  964. return $(this).find("span").text();
  965. })
  966. .get()
  967. .every((score) => /^[0-9]+$/.test(score));
  968. OJBetter.localization.websiteLang = OJB_getGMValue(
  969. "localizationLanguage",
  970. "zh"
  971. );
  972. OJBetter.localization.scriptLang = OJB_getGMValue("scriptL10nLanguage", "zh");
  973. OJBetter.basic.renderPerfOpt = OJB_getGMValue("renderPerfOpt", false);
  974. OJBetter.basic.selectElementPerfOpt = OJB_getGMValue(
  975. "selectElementPerfOpt",
  976. false
  977. );
  978. OJBetter.basic.commentPaging = OJB_getGMValue("commentPaging", true);
  979. OJBetter.basic.showJumpToLuogu = OJB_getGMValue("showJumpToLuogu", true);
  980. OJBetter.basic.showCF2vjudge = OJB_getGMValue("showCF2vjudge", true);
  981. OJBetter.basic.standingsRecolor = OJB_getGMValue("standingsRecolor", true);
  982. OJBetter.basic.hiddenProblemTag = OJB_getGMValue("hiddenProblemTag", false);
  983. OJBetter.state.notWaiteLoaded = OJB_getGMValue("notWaiteLoaded", false);
  984. OJBetter.translation.targetLang = OJB_getGMValue("transTargetLang", "zh");
  985. OJBetter.translation.choice = OJB_getGMValue("translation", "deepl");
  986. OJBetter.translation.comment.transMode = OJB_getGMValue(
  987. "commentTranslationMode",
  988. "0"
  989. );
  990. OJBetter.translation.comment.choice = OJB_getGMValue(
  991. "commentTranslationChoice",
  992. "0"
  993. );
  994. OJBetter.translation.memory.enabled = OJB_getGMValue(
  995. "memoryTranslateHistory",
  996. true
  997. );
  998. OJBetter.translation.auto.enabled = OJB_getGMValue("autoTranslation", false);
  999. OJBetter.translation.auto.shortTextLength = OJB_getGMValue(
  1000. "shortTextLength",
  1001. "2000"
  1002. );
  1003. OJBetter.translation.retransAction = OJB_getGMValue("retransAction", "0");
  1004. OJBetter.translation.waitTime = OJB_getGMValue("transWaitTime", "200");
  1005. OJBetter.translation.auto.mixTrans.enabled = OJB_getGMValue(
  1006. "allowMixTrans",
  1007. true
  1008. );
  1009. OJBetter.translation.auto.mixTrans.servers = OJB_getGMValue(
  1010. "mixedTranslation",
  1011. ["deepl", "iflyrec", "youdao", "caiyun"]
  1012. );
  1013. OJBetter.common.taskQueue = new TaskQueue();
  1014. OJBetter.translation.replaceSymbol = OJB_getGMValue("replaceSymbol", "2");
  1015. OJBetter.translation.filterTextWithoutEmphasis = OJB_getGMValue(
  1016. "filterTextWithoutEmphasis",
  1017. false
  1018. );
  1019. OJBetter.translation.forceTurndownConversion = OJB_getGMValue(
  1020. "forceTurndownConversion",
  1021. false
  1022. );
  1023. OJBetter.clist.enabled.contest = OJB_getGMValue(
  1024. "showClistRating_contest",
  1025. false
  1026. );
  1027. OJBetter.clist.enabled.problem = OJB_getGMValue(
  1028. "showClistRating_problem",
  1029. false
  1030. );
  1031. OJBetter.clist.enabled.problemset = OJB_getGMValue(
  1032. "showClistRating_problemset",
  1033. false
  1034. );
  1035. OJBetter.clist.ratingHidden = OJB_getGMValue("RatingHidden", false);
  1036. OJBetter.clist.authorization = OJB_getGMValue("clist_Authorization", "");
  1037. //deepl
  1038. OJBetter.deepl.config.type = OJB_getGMValue("deepl_type", "free");
  1039. OJBetter.deepl.configs = OJB_getGMValue("deepl_config", {
  1040. choice: "",
  1041. configurations: [],
  1042. });
  1043. if (
  1044. OJBetter.deepl.configs.choice !== "" &&
  1045. OJBetter.deepl.configs.configurations.length !== 0
  1046. ) {
  1047. const choice = OJBetter.deepl.configs.choice;
  1048. const configuration = OJBetter.deepl.configs.configurations.find(
  1049. (obj) => obj.name === choice
  1050. );
  1051. if (configuration == undefined) {
  1052. let existingConfig = GM_getValue("deepl_config");
  1053. existingConfig.choice = "";
  1054. GM_setValue("deepl_config", existingConfig);
  1055. location.reload();
  1056. }
  1057. OJBetter.deepl.config.name = configuration.name;
  1058. OJBetter.deepl.config.apiGenre = configuration.apiGenre;
  1059. OJBetter.deepl.config.key = configuration.key;
  1060. OJBetter.deepl.config.proxy = configuration.proxy;
  1061. OJBetter.deepl.config.header = OJB_parseLinePairArray(
  1062. configuration._header
  1063. );
  1064. OJBetter.deepl.config.data = OJB_parseLinePairArray(configuration._data);
  1065. OJBetter.deepl.config.quota.url = configuration.quota_url;
  1066. OJBetter.deepl.config.quota.method = configuration.quota_method;
  1067. OJBetter.deepl.config.quota.header = OJB_parseLinePairArray(
  1068. configuration.quota_header
  1069. );
  1070. OJBetter.deepl.config.quota.data = OJB_parseLinePairArray(
  1071. configuration.quota_data
  1072. );
  1073. OJBetter.deepl.config.quota.surplus = configuration.quota_surplus;
  1074. }
  1075. OJBetter.deepl.enableEmphasisProtection = OJB_getGMValue(
  1076. "enableEmphasisProtection",
  1077. true
  1078. );
  1079. OJBetter.deepl.enableLinkProtection = OJB_getGMValue(
  1080. "enableLinkProtection",
  1081. true
  1082. );
  1083. //openai
  1084. OJBetter.chatgpt.isStream = OJB_getGMValue("openai_isStream", true);
  1085. OJBetter.chatgpt.customPrompt = OJB_getGMValue("openai_customPrompt", "");
  1086. OJBetter.chatgpt.asSystemPrompt = OJB_getGMValue(
  1087. "openai_asSystemPrompt",
  1088. false
  1089. );
  1090. OJBetter.chatgpt.configs = OJB_getGMValue("chatgpt_config", {
  1091. choice: "",
  1092. configurations: [],
  1093. });
  1094. if (
  1095. OJBetter.chatgpt.configs.choice !== "" &&
  1096. OJBetter.chatgpt.configs.configurations.length !== 0
  1097. ) {
  1098. const choice = OJBetter.chatgpt.configs.choice;
  1099. const configuration = OJBetter.chatgpt.configs.configurations.find(
  1100. (obj) => obj.name === choice
  1101. );
  1102. if (configuration == undefined) {
  1103. let existingConfig = GM_getValue("chatgpt_config");
  1104. existingConfig.choice = "";
  1105. GM_setValue("chatgpt_config", existingConfig);
  1106. location.reload();
  1107. }
  1108. OJBetter.chatgpt.config.name = configuration.name;
  1109. OJBetter.chatgpt.config.model = configuration.model;
  1110. OJBetter.chatgpt.config.key = configuration.key;
  1111. OJBetter.chatgpt.config.proxy = configuration.proxy;
  1112. OJBetter.chatgpt.config.header = OJB_parseLinePairArray(
  1113. configuration._header
  1114. );
  1115. OJBetter.chatgpt.config.data = OJB_parseLinePairArray(configuration._data);
  1116. OJBetter.chatgpt.config.quota.url = configuration.quota_url;
  1117. OJBetter.chatgpt.config.quota.method = configuration.quota_method;
  1118. OJBetter.chatgpt.config.quota.header = OJB_parseLinePairArray(
  1119. configuration.quota_header
  1120. );
  1121. OJBetter.chatgpt.config.quota.data = OJB_parseLinePairArray(
  1122. configuration.quota_data
  1123. );
  1124. OJBetter.chatgpt.config.quota.surplus = configuration.quota_surplus;
  1125. }
  1126. // 编辑器
  1127. if (!OJBetter.typeOfPage.is_mSite)
  1128. OJBetter.common.cf_csrf_token = Codeforces.getCsrfToken();
  1129. else OJBetter.common.cf_csrf_token = "";
  1130. OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "61");
  1131. OJBetter.monaco.setting.fontsize = OJB_getGMValue("editorFontSize", "15");
  1132. OJBetter.monaco.enableOnProblemPage = OJB_getGMValue(
  1133. "problemPageCodeEditor",
  1134. true
  1135. );
  1136. OJBetter.monaco.beautifyPreBlocks = OJB_getGMValue("beautifyPreBlocks", true);
  1137. OJBetter.monaco.complet.cppCodeTemplate = OJB_getGMValue(
  1138. "cppCodeTemplateComplete",
  1139. true
  1140. );
  1141. OJBetter.monaco.onlineCompilerChoice = OJB_getGMValue(
  1142. "onlineCompilerChoice",
  1143. "official"
  1144. );
  1145. OJBetter.monaco.setting.isCodeSubmitDoubleConfirm = OJB_getGMValue(
  1146. "isCodeSubmitConfirm",
  1147. true
  1148. );
  1149. OJBetter.monaco.setting.autoSubmitAfterPass = OJB_getGMValue(
  1150. "autoSubmitAfterPass",
  1151. false
  1152. );
  1153. OJBetter.monaco.setting.alwaysConsumeMouseWheel = OJB_getGMValue(
  1154. "alwaysConsumeMouseWheel",
  1155. true
  1156. );
  1157. OJBetter.monaco.setting.submitButtonPosition = OJB_getGMValue(
  1158. "submitButtonPosition",
  1159. "bottom"
  1160. );
  1161. OJBetter.monaco.setting.autoMemoryCode = OJB_getGMValue(
  1162. "autoMemoryCode",
  1163. true
  1164. );
  1165. //自定义补全
  1166. OJBetter.monaco.complet.customConfig = OJB_getGMValue("Complet_config", {
  1167. choice: -1,
  1168. configurations: [],
  1169. });
  1170. // monaco
  1171. OJBetter.monaco.lsp.enabled = OJB_getGMValue("useLSP", false);
  1172. OJBetter.monaco.setting.position = OJB_getGMValue(
  1173. "monacoEditor_position",
  1174. "initial"
  1175. );
  1176. OJBetter.monaco.lsp.workUri = OJB_getGMValue(
  1177. "OJBetter_Bridge_WorkUri",
  1178. "C:/OJBetter_Bridge"
  1179. );
  1180. OJBetter.monaco.lsp.socketUrl = OJB_getGMValue(
  1181. "OJBetter_Bridge_SocketUrl",
  1182. "ws://127.0.0.1:2323/"
  1183. );
  1184. OJBetter.preference.showLoading = OJB_getGMValue("showLoading", true);
  1185. OJBetter.preference.hoverTargetAreaDisplay = OJB_getGMValue(
  1186. "hoverTargetAreaDisplay",
  1187. false
  1188. );
  1189. OJBetter.basic.expandFoldingblocks = OJB_getGMValue(
  1190. "expandFoldingblocks",
  1191. true
  1192. );
  1193. OJBetter.preference.iconButtonSize = OJB_getGMValue("iconButtonSize", "16");
  1194. OJBetter.preference.judgeStatusReplaceText = OJB_getGMValue(
  1195. "judgeStatusReplaceText",
  1196. ""
  1197. );
  1198. OJBetter.dev.isRuleMarkingEnabled = OJB_getGMValue(
  1199. "isRuleMarkingEnabled",
  1200. false
  1201. );
  1202. OJBetter.about.updateChannel = OJB_getGMValue("updateChannel", "release");
  1203. OJBetter.about.updateSource = OJB_getGMValue("updateSource", "greasyfork");
  1204. }
  1205.  
  1206. /**
  1207. * 显示警告消息
  1208. */
  1209. function showWarnMessage() {
  1210. if (OJBetter.typeOfPage.is_oldLatex) {
  1211. const loadingMessage = new LoadingMessage();
  1212. loadingMessage.updateStatus(
  1213. `${OJBetter.state.name} —— ${i18next.t("warning.is_oldLatex", {
  1214. ns: "alert",
  1215. })}`,
  1216. "warning"
  1217. );
  1218. }
  1219. if (OJBetter.typeOfPage.is_acmsguru) {
  1220. const loadingMessage = new LoadingMessage();
  1221. loadingMessage.updateStatus(
  1222. `${OJBetter.state.name} —— ${i18next.t("warning.is_acmsguru", {
  1223. ns: "alert",
  1224. })}`,
  1225. "warning"
  1226. );
  1227. }
  1228. if (OJBetter.translation.comment.transMode == "1") {
  1229. const loadingMessage = new LoadingMessage();
  1230. loadingMessage.updateStatus(
  1231. `${OJBetter.state.name} —— ${i18next.t("warning.trans_segment", {
  1232. ns: "alert",
  1233. })}`,
  1234. "warning"
  1235. );
  1236. }
  1237. if (OJBetter.translation.comment.transMode == "2") {
  1238. const loadingMessage = new LoadingMessage();
  1239. loadingMessage.updateStatus(
  1240. `${OJBetter.state.name} —— ${i18next.t("warning.trans_select", {
  1241. ns: "alert",
  1242. })}`,
  1243. "warning"
  1244. );
  1245. }
  1246. if (
  1247. OJBetter.typeOfPage.is_submitPage &&
  1248. OJBetter.monaco.enableOnProblemPage
  1249. ) {
  1250. const loadingMessage = new LoadingMessage();
  1251. loadingMessage.updateStatus(
  1252. `${OJBetter.state.name} —— ${i18next.t("warning.is_submitPage", {
  1253. ns: "alert",
  1254. })}`,
  1255. "warning"
  1256. );
  1257. }
  1258. if (OJBetter.translation.forceTurndownConversion) {
  1259. const loadingMessage = new LoadingMessage();
  1260. loadingMessage.updateStatus(
  1261. `${OJBetter.state.name} —— ${i18next.t(
  1262. "warning.forceTurndownConversion",
  1263. { ns: "alert" }
  1264. )}`,
  1265. "warning"
  1266. );
  1267. }
  1268. }
  1269.  
  1270. // 常量
  1271. const helpCircleHTML =
  1272. '<div class="help-icon"><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896zm23.744 191.488c-52.096 0-92.928 14.784-123.2 44.352-30.976 29.568-45.76 70.4-45.76 122.496h80.256c0-29.568 5.632-52.8 17.6-68.992 13.376-19.712 35.2-28.864 66.176-28.864 23.936 0 42.944 6.336 56.32 19.712 12.672 13.376 19.712 31.68 19.712 54.912 0 17.6-6.336 34.496-19.008 49.984l-8.448 9.856c-45.76 40.832-73.216 70.4-82.368 89.408-9.856 19.008-14.08 42.24-14.08 68.992v9.856h80.96v-9.856c0-16.896 3.52-31.68 10.56-45.76 6.336-12.672 15.488-24.64 28.16-35.2 33.792-29.568 54.208-48.576 60.544-55.616 16.896-22.528 26.048-51.392 26.048-86.592 0-42.944-14.08-76.736-42.24-101.376-28.16-25.344-65.472-37.312-111.232-37.312zm-12.672 406.208a54.272 54.272 0 0 0-38.72 14.784 49.408 49.408 0 0 0-15.488 38.016c0 15.488 4.928 28.16 15.488 38.016A54.848 54.848 0 0 0 523.072 768c15.488 0 28.16-4.928 38.72-14.784a51.52 51.52 0 0 0 16.192-38.72 51.968 51.968 0 0 0-15.488-38.016 55.936 55.936 0 0 0-39.424-14.784z"></path></svg></div>';
  1273. const closeIcon = `<svg t="1696693011050" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4322" width="14" height="14"><path d="M0 0h1024v1024H0z" fill-opacity="0" p-id="4323"></path><path d="M240.448 168l2.346667 2.154667 289.92 289.941333 279.253333-279.253333a42.666667 42.666667 0 0 1 62.506667 58.026666l-2.133334 2.346667-279.296 279.210667 279.274667 279.253333a42.666667 42.666667 0 0 1-58.005333 62.528l-2.346667-2.176-279.253333-279.253333-289.92 289.962666a42.666667 42.666667 0 0 1-62.506667-58.005333l2.154667-2.346667 289.941333-289.962666-289.92-289.92a42.666667 42.666667 0 0 1 57.984-62.506667z" p-id="4324"></path></svg>`;
  1274. const translateIcon = `<svg t="1696837407077" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6325" width="22" height="22"><path d="M536.380952 121.904762a73.142857 73.142857 0 0 1 73.142858 73.142857v219.428571h219.428571a73.142857 73.142857 0 0 1 73.142857 73.142858v341.333333a73.142857 73.142857 0 0 1-73.142857 73.142857H487.619048a73.142857 73.142857 0 0 1-73.142858-73.142857v-219.428571H195.047619a73.142857 73.142857 0 0 1-73.142857-73.142858V195.047619a73.142857 73.142857 0 0 1 73.142857-73.142857h341.333333zM243.809524 682.666667v97.523809h97.523809v73.142857h-97.523809a73.142857 73.142857 0 0 1-73.142857-73.142857v-97.523809h73.142857z m585.142857-195.047619h-219.428571v48.761904a73.142857 73.142857 0 0 1-73.142858 73.142858h-48.761904v219.428571h341.333333V487.619048z m-115.760762 89.526857L787.21219 780.190476h-62.025142l-14.043429-42.715428h-76.068571L620.739048 780.190476h-60.854858l74.605715-203.044571h78.701714z m-38.034286 50.029714h-3.510857l-21.065143 63.488h45.348572l-20.772572-63.488zM536.380952 195.047619H195.047619v341.333333h341.333333V195.047619z
  1275. m-195.072 49.883429l44.78781 1.072762v37.278476h87.698286v145.359238h-87.698286v65.974857h-44.78781v-65.974857h-87.698285v-145.359238h87.698285v-38.351238z m0 83.139047h-44.787809v56.05181h44.787809v-56.05181z m89.307429 0h-44.519619v56.05181h44.519619v-56.05181zM780.190476 170.666667a73.142857 73.142857 0 0 1 73.142857 73.142857v97.523809h-73.142857v-97.523809h-97.523809V170.666667h97.523809z" p-id="6326"></path></svg>`;
  1276. const clistIcon = `<svg width="37.7pt" height="10pt" viewBox="0 0 181 48" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="#0057b8ff"><path fill="#0057b8" opacity="1.00" d=" M 17.36 0.00 L 18.59 0.00 C 23.84 6.49 30.28 11.92 36.01 17.98 C 34.01 19.99 32.01 21.99 30.00 23.99 C 26.02 19.97 22.02 15.98 18.02 11.99 C 14.01 15.98 10.01 19.99 6.00 23.99 C 4.16 22.04 2.30 20.05 0.00 18.61 L 0.00 17.37 C 3.44 15.11 6.00 11.84 8.96 9.03 C 11.79 6.05 15.09 3.47 17.36 0.00 Z" /></g><g id="#a0a0a0ff"><path fill="#a0a0a0" opacity="1.00" d=" M 56.76 13.74 C 61.48 4.80 76.07 3.90 81.77 12.27 C 83.09 13.94 83.44 16.10 83.91 18.12 C 81.53 18.23 79.16 18.24 76.78 18.23 C 75.81 15.72 73.99 13.31 71.14 12.95 C 67.14 12.02 63.45 15.29 62.48 18.99 C 61.30 23.27 61.71 28.68 65.34 31.70 C 67.82 34.05 72.19 33.93 74.61 31.55 C 75.97 30.18 76.35 28.23 76.96 26.48 C 79.36 26.43 81.77 26.44 84.17 26.56 C 83.79 30.09 82.43 33.49 79.89 36.02 C 74.14 41.35 64.17 40.80 58.77 35.25 C 53.52 29.56 53.18 20.38 56.76 13.74 Z" />
  1277. <path fill="#a0a0a0" opacity="1.00" d=" M 89.01 7.20 C 91.37 7.21 93.74 7.21 96.11 7.22 C 96.22 15.71 96.10 24.20 96.18 32.69 C 101.25 32.76 106.32 32.63 111.39 32.79 C 111.40 34.86 111.41 36.93 111.41 39.00 C 103.94 39.00 96.47 39.00 89.00 39.00 C 89.00 28.40 88.99 17.80 89.01 7.20 Z" /><path fill="#a0a0a0" opacity="1.00" d=" M 115.00 7.21 C 117.33 7.21 119.66 7.21 121.99 7.21 C 122.01 17.81 122.00 28.40 122.00 39.00 C 119.67 39.00 117.33 39.00 115.00 39.00 C 115.00 28.40 114.99 17.80 115.00 7.21 Z" /><path fill="#a0a0a0" opacity="1.00" d=" M 133.35 7.47 C 139.11 5.56 146.93 6.28 150.42 11.87 C 151.42 13.39 151.35 15.31 151.72 17.04 C 149.33 17.05 146.95 17.05 144.56 17.03 C 144.13 12.66 138.66 11.12 135.34 13.30 C 133.90 14.24 133.54 16.87 135.35 17.61 C 139.99 20.02 145.90 19.54 149.92 23.19 C 154.43 26.97 153.16 35.36 147.78 37.72 C 143.39 40.03 137.99 40.11 133.30 38.69 C 128.80 37.34 125.34 32.90 125.91 28.10 C 128.22 28.10 130.53 28.11 132.84 28.16 C 132.98 34.19 142.68 36.07 145.18 30.97 C 146.11 27.99 142.17 27.05 140.05 26.35 C 135.54 25.04 129.83 24.33 127.50 19.63 C 125.30 14.78 128.42 9.00 133.35 7.47 Z" />
  1278. <path fill="#a0a0a0" opacity="1.00" d=" M 153.31 7.21 C 161.99 7.21 170.67 7.21 179.34 7.21 C 179.41 9.30 179.45 11.40 179.48 13.50 C 176.35 13.50 173.22 13.50 170.09 13.50 C 170.05 21.99 170.12 30.48 170.05 38.98 C 167.61 39.00 165.18 39.00 162.74 39.00 C 162.64 30.52 162.73 22.04 162.69 13.55 C 159.57 13.49 156.44 13.49 153.32 13.50 C 153.32 11.40 153.31 9.31 153.31 7.21 Z" /></g><g id="#ffd700ff"><path fill="#ffd700" opacity="1.00" d=" M 12.02 29.98 C 14.02 27.98 16.02 25.98 18.02 23.98 C 22.01 27.99 26.03 31.97 30.00 35.99 C 34.01 31.99 38.01 27.98 42.02 23.99 C 44.02 25.98 46.02 27.98 48.01 29.98 C 42.29 36.06 35.80 41.46 30.59 48.00 L 29.39 48.00 C 24.26 41.42 17.71 36.08 12.02 29.98 Z" /></g></svg>`;
  1279.  
  1280. /**
  1281. * 连接数据库
  1282. */
  1283. async function initDB() {
  1284. OJBetter.common.database = new Dexie("CFBetterDB");
  1285. OJBetter.common.database.version(3).stores({
  1286. samplesData: "&url",
  1287. editorCode: "&url",
  1288. translateData: "&url",
  1289. localizeSubsData: "&lang",
  1290. });
  1291.  
  1292. // 等待数据库打开
  1293. await OJBetter.common.database.open();
  1294. }
  1295.  
  1296. /**
  1297. * 清空数据库
  1298. */
  1299. async function clearDatabase() {
  1300. const isConfirmed = await OJB_createDialog(
  1301. i18next.t("isClearDatabase.title", { ns: "dialog" }),
  1302. i18next.t("isClearDatabase.content", { ns: "dialog" }),
  1303. [
  1304. i18next.t("isClearDatabase.buttons.0", { ns: "dialog" }),
  1305. i18next.t("isClearDatabase.buttons.1", { ns: "dialog" }),
  1306. ]
  1307. );
  1308. if (!isConfirmed) {
  1309. try {
  1310. // 开启一个读写事务,包含数据库中的所有表
  1311. await OJBetter.common.database.transaction(
  1312. "rw",
  1313. OJBetter.common.database.tables,
  1314. async () => {
  1315. // 遍历所有表
  1316. for (const table of OJBetter.common.database.tables) {
  1317. // 清空当前表
  1318. await table.clear();
  1319. }
  1320. }
  1321. );
  1322. console.log("All tables in the database have been cleared.");
  1323. alert("All tables in the database have been cleared.");
  1324. } catch (error) {
  1325. console.error("Error clearing the database:", error);
  1326. }
  1327. }
  1328. }
  1329.  
  1330. /**
  1331. * 导出数据库
  1332. * @returns {Promise<string>} 数据库的JSON字符串
  1333. */
  1334. async function exportDatabase() {
  1335. try {
  1336. // 创建一个存储数据的对象
  1337. const exportData = {};
  1338. // 获取数据库中所有表的名称
  1339. const tableNames = OJBetter.common.database.tables.map(
  1340. (table) => table.name
  1341. );
  1342.  
  1343. // 遍历每一个表,获取数据
  1344. for (const tableName of tableNames) {
  1345. const tableData = await OJBetter.common.database
  1346. .table(tableName)
  1347. .toArray();
  1348. exportData[tableName] = tableData;
  1349. }
  1350.  
  1351. // 将数据对象转换为JSON字符串
  1352. const jsonData = JSON.stringify(exportData, null, 4);
  1353. return jsonData;
  1354. } catch (error) {
  1355. console.error("Error exporting database:", error);
  1356. }
  1357. }
  1358.  
  1359. /**
  1360. * 导入数据库
  1361. * @param {string} jsonData 数据库的JSON字符串
  1362. */
  1363. async function importDatabase(jsonData) {
  1364. const isConfirmed = await OJB_createDialog(
  1365. i18next.t("isImportDatabase.title", { ns: "dialog" }),
  1366. i18next.t("isImportDatabase.content", { ns: "dialog" }),
  1367. [
  1368. i18next.t("isImportDatabase.buttons.0", { ns: "dialog" }),
  1369. i18next.t("isImportDatabase.buttons.1", { ns: "dialog" }),
  1370. ]
  1371. );
  1372. if (!isConfirmed) {
  1373. try {
  1374. // 将JSON字符串解析为对象
  1375. const importData = JSON.parse(jsonData);
  1376.  
  1377. // 开启一个事务,并清空现有数据
  1378. await OJBetter.common.database.transaction(
  1379. "rw",
  1380. OJBetter.common.database.tables,
  1381. async () => {
  1382. // 清空所有表的数据
  1383. for (const tableName of OJBetter.common.database.tables.map(
  1384. (table) => table.name
  1385. )) {
  1386. await OJBetter.common.database.table(tableName).clear();
  1387. }
  1388.  
  1389. // 插入新数据
  1390. for (const [tableName, rows] of Object.entries(importData)) {
  1391. await OJBetter.common.database.table(tableName).bulkAdd(rows);
  1392. }
  1393. }
  1394. );
  1395. alert("Data imported successfully");
  1396. } catch (error) {
  1397. console.error("Error importing database:", error);
  1398. }
  1399. }
  1400. }
  1401.  
  1402. /**
  1403. * 将数据下载为文件
  1404. * @param {string} data 数据
  1405. * @param {string} filename 文件名,默认为'export.json'
  1406. * @param {string} fileType 文件MIME类型,默认为'application/json'
  1407. * @returns {void}
  1408. */
  1409. function downloadDataAsFile(
  1410. data,
  1411. filename = "export.json",
  1412. fileType = "application/json"
  1413. ) {
  1414. // 创建一个blob对象,指定文件类型
  1415. const blob = new Blob([data], { type: fileType });
  1416. const url = URL.createObjectURL(blob);
  1417.  
  1418. // 创建一个隐藏的a标签,模拟点击进行下载
  1419. const a = document.createElement("a");
  1420. a.href = url;
  1421. a.download = filename;
  1422. document.body.appendChild(a);
  1423. a.click();
  1424.  
  1425. // 清理
  1426. document.body.removeChild(a);
  1427. URL.revokeObjectURL(url);
  1428. }
  1429.  
  1430. /**
  1431. * 从文件中读取数据
  1432. * @param {Function} callback 回调函数
  1433. * @returns {void}
  1434. */
  1435. function readFileInput(callback) {
  1436. const fileInput = document.createElement("input");
  1437. fileInput.type = "file";
  1438. fileInput.accept = ".json";
  1439. fileInput.style.display = "none"; // 隐藏input元素
  1440.  
  1441. fileInput.onchange = (e) => {
  1442. const file = e.target.files[0];
  1443. if (file) {
  1444. const reader = new FileReader();
  1445. reader.onload = (e) => {
  1446. const fileContent = e.target.result;
  1447. if (callback && typeof callback === "function") {
  1448. callback(fileContent); // 调用回调函数,传入文件内容
  1449. }
  1450. };
  1451. reader.readAsText(file);
  1452. }
  1453. };
  1454.  
  1455. document.body.appendChild(fileInput);
  1456. fileInput.click();
  1457. document.body.removeChild(fileInput);
  1458. }
  1459.  
  1460. /**
  1461. * 清除所有设置
  1462. */
  1463. async function deleteAllConfigSettings() {
  1464. const isConfirmed = await OJB_createDialog(
  1465. i18next.t("isDeleteAllConfigSettings.title", { ns: "dialog" }),
  1466. i18next.t("isDeleteAllConfigSettings.content", { ns: "dialog" }),
  1467. [
  1468. i18next.t("isDeleteAllConfigSettings.buttons.0", { ns: "dialog" }),
  1469. i18next.t("isDeleteAllConfigSettings.buttons.1", { ns: "dialog" }),
  1470. ]
  1471. );
  1472. if (!isConfirmed) {
  1473. const keys = GM_listValues();
  1474.  
  1475. keys.forEach((key) => {
  1476. GM_deleteValue(key);
  1477. });
  1478.  
  1479. alert("All settings have been deleted.");
  1480. window.location.reload();
  1481. }
  1482. }
  1483.  
  1484. /**
  1485. * 导出设置到JSON
  1486. * @returns {string} JSON字符串
  1487. */
  1488. function exportSettingsToJSON() {
  1489. const keys = GM_listValues();
  1490. let settings = {};
  1491.  
  1492. keys.forEach((key) => {
  1493. settings[key] = GM_getValue(key);
  1494. });
  1495.  
  1496. return JSON.stringify(settings, null, 4);
  1497. }
  1498.  
  1499. /**
  1500. * 从JSON导入设置
  1501. * @param {string} jsonData JSON字符串
  1502. * @returns {void}
  1503. */
  1504. async function importSettingsFromJSON(jsonData) {
  1505. const isConfirmed = await OJB_createDialog(
  1506. i18next.t("isImportSettings.title", { ns: "dialog" }),
  1507. i18next.t("isImportSettings.content", { ns: "dialog" }),
  1508. [
  1509. i18next.t("isImportSettings.buttons.0", { ns: "dialog" }),
  1510. i18next.t("isImportSettings.buttons.1", { ns: "dialog" }),
  1511. ]
  1512. );
  1513. if (!isConfirmed) {
  1514. let settings;
  1515. try {
  1516. settings = JSON.parse(jsonData);
  1517. } catch (e) {
  1518. console.error("JSON parsing error:", e);
  1519. return;
  1520. }
  1521.  
  1522. Object.keys(settings).forEach((key) => {
  1523. GM_setValue(key, settings[key]);
  1524. });
  1525.  
  1526. alert("Settings imported successfully!");
  1527. window.location.reload();
  1528. }
  1529. }
  1530.  
  1531. /**
  1532. * 加载元素本地化语言数据
  1533. * @param {JQuery} element jQuery元素
  1534. * @param {number} [retries=10] 重试次数
  1535. * @param {number} [interval=50] 重试间隔
  1536. */
  1537. function elementLocalize(element, retries = 10, interval = 50) {
  1538. if ($.isFunction(element.localize)) {
  1539. element.localize();
  1540. } else if (retries > 0) {
  1541. setTimeout(elementLocalize, interval, element, retries - 1, interval);
  1542. } else {
  1543. console.error("Unable to localize", element);
  1544. }
  1545. }
  1546.  
  1547. // 切换系统黑暗监听
  1548. const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
  1549. const changeEventListeners = [];
  1550.  
  1551. /**
  1552. * 处理颜色模式变化事件
  1553. * @param {MediaQueryListEvent} event - 媒体查询事件对象
  1554. */
  1555. const handleColorSchemeChange = (event) => {
  1556. const theme = event.matches ? "dark" : "light";
  1557.  
  1558. // 更新页面主题
  1559. $("html").attr("data-theme", theme);
  1560. OJBetter.common.realDarkMode = theme;
  1561.  
  1562. const updateMonacoTheme = (theme) => {
  1563. const intervalId = setInterval(() => {
  1564. if (OJBetter?.monaco?.editor) {
  1565. monaco.editor.setTheme(theme);
  1566. clearInterval(intervalId);
  1567. }
  1568. }, 100);
  1569.  
  1570. OJBetter.monaco.beautifyEditor.forEach((editor) => {
  1571. editor.updateOptions({ theme });
  1572. });
  1573. };
  1574.  
  1575. if (event.matches) {
  1576. updateMonacoTheme("vs-dark");
  1577. } else {
  1578. const originalColor = $(this).data("original-color");
  1579. $(this).css("background-color", originalColor);
  1580. updateMonacoTheme("vs");
  1581. }
  1582. };
  1583.  
  1584. /**
  1585. * 黑暗模式
  1586. */
  1587. (function setDark() {
  1588. /**
  1589. * 初始化
  1590. */
  1591. function setDarkTheme() {
  1592. const htmlElement = document.querySelector("html");
  1593. if (htmlElement) {
  1594. htmlElement.setAttribute("data-theme", "dark");
  1595. const intervalId = setInterval(() => {
  1596. if (OJBetter.monaco && OJBetter.monaco.editor) {
  1597. monaco.editor.setTheme("vs-dark");
  1598. clearInterval(intervalId);
  1599. }
  1600. }, 100);
  1601. } else {
  1602. setTimeout(setDarkTheme, 100);
  1603. }
  1604. }
  1605.  
  1606. // 设置黑暗模式和监听器
  1607. OJBetter.basic.darkMode = OJB_getGMValue("darkMode", "follow");
  1608. if (OJBetter.basic.darkMode == "dark") {
  1609. OJBetter.common.realDarkMode = "dark";
  1610. setDarkTheme();
  1611. } else if (OJBetter.basic.darkMode == "light") {
  1612. OJBetter.common.realDarkMode = "light";
  1613. } else if (OJBetter.basic.darkMode == "follow") {
  1614. OJBetter.common.realDarkMode = window.matchMedia(
  1615. "(prefers-color-scheme: dark)"
  1616. ).matches
  1617. ? "dark"
  1618. : "light";
  1619. // 添加事件监听器
  1620. changeEventListeners.push(handleColorSchemeChange);
  1621. mediaQueryList.addEventListener("change", handleColorSchemeChange);
  1622.  
  1623. if (window.matchMedia("(prefers-color-scheme: dark)").matches)
  1624. setDarkTheme();
  1625. }
  1626.  
  1627. // 定义全局变量
  1628. GM_addStyle(`
  1629. /* 黑暗支持 */
  1630. html[data-theme=dark]:root {
  1631. color-scheme: light dark;
  1632. }
  1633. /* 颜色 */
  1634. :root {
  1635. /* 文字颜色 */
  1636. --ojb-color-text-primary: #a0adb9; /* 主要文字颜色 */
  1637. --ojb-color-text-secondary: #9AA4B1; /* 次要文字颜色 */
  1638. --ojb-color-text-tertiary: #9BA5B2; /* 第三级文字颜色 */
  1639. --ojb-color-text-success: #43A047; /* 成功状态文字颜色 */
  1640. --ojb-color-text-highlight: #cbd6e2; /* 高亮文字颜色 */
  1641. --ojb-color-text-disabled: #506778; /* 禁用状态文字颜色 */
  1642. --ojb-color-text-icon-success: #2e7d32; /* 成功状态图标颜色 */
  1643. --ojb-color-text-link: #4b8eda; /* 链接颜色 */
  1644.  
  1645. /* 背景颜色 */
  1646. --ojb-color-bg-primary: #22272e; /* 主背景颜色 */
  1647. --ojb-color-bg-secondary: #2d333b; /* 次级背景颜色 */
  1648. --ojb-color-bg-disabled: #24292e; /* 禁用元素背景颜色 */
  1649.  
  1650. /* 边框颜色 */
  1651. --ojb-color-border-primary: #48535F; /* 主要边框颜色 */
  1652. --ojb-color-border-disabled: #404950; /* 禁用状态边框颜色 */
  1653. --ojb-color-border-dashed-hover: #03A9F4; /* 虚线边框悬浮颜色 */
  1654. --ojb-color-border-radio-checked: #326154; /* 选中的单选框边框颜色 */
  1655.  
  1656. /* 阴影颜色 */
  1657. --ojb-shadow-standard: 0px 0px 0.5px 0.5px #3A4048; /* 标准阴影 */
  1658. --ojb-shadow-menu-modal: 0px 0px 0px 4px #2d333b; /* 菜单和模态框阴影 */
  1659.  
  1660. /* 区域遮罩颜色 */
  1661. --ojb-overlay-background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px); /* 区域遮罩背景 */
  1662.  
  1663. /* 文字阴影 */
  1664. --ojb-text-shadow-icon: 1px 1px 0px #2d333b, 1px -1px 0px #2d333b, -1px -1px 0px #2d333b, -1px 1px 0px #2d333b; /* 图标文字阴影 */
  1665. }
  1666. /* 边框样式 */
  1667. :root {
  1668. /* 边框样式 */
  1669. --ojb-border-width: 1px; /* 边框宽度 */
  1670. --ojb-border-style-solid: solid; /* 实线样式 */
  1671. --ojb-border-style-dashed: dashed; /* 虚线样式 */
  1672. --ojb-border-radius-small: 4px; /* 小圆角 */
  1673. --ojb-border-radius-medium: 8px; /* 中圆角 */
  1674. --ojb-border-radius-large: 12px; /* 大圆角 */
  1675.  
  1676. /* 组合边框样式 */
  1677. --ojb-border-solid-primary: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-primary); /* 主要实线边框 */
  1678. --ojb-border-dashed: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-primary); /* 主要虚线边框 */
  1679. --ojb-border-dashed-hover: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-dashed-hover); /* 悬浮虚线边框 */
  1680. --ojb-border-solid-disabled: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-disabled); /* 禁用状态实线边框 */
  1681. }
  1682. `);
  1683.  
  1684. // OJBetter界面样式
  1685. GM_addStyle(`
  1686. /* 主要文字颜色 */
  1687. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1688. html[data-theme=dark] .alert-warning, html[data-theme=dark] .markItUpEditor,
  1689. html[data-theme=dark] .translate-problem-statement, html[data-theme=dark] .OJBetter_setting_menu,
  1690. html[data-theme=dark] .help_tip .tip_text,
  1691. html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
  1692. html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
  1693. html[data-theme=dark] #items-per-page, html[data-theme=dark] #pagBar,
  1694. html[data-theme=dark] .OJBetter_setting_sidebar li a:link,
  1695. html[data-theme=dark] .popup .content{
  1696. color: var(--ojb-color-text-primary);
  1697. }
  1698. /* 次要文字颜色 */
  1699. html[data-theme=dark] .ojb_btn:hover, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_statusBar,
  1700. html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #addCustomTest,
  1701. html[data-theme=dark] #customTestBlock, html[data-theme=dark] .OJBetter_setting_list.alert_info{
  1702. color: var(--ojb-color-text-secondary);
  1703. }
  1704. /* 文字颜色3 */
  1705. html[data-theme=dark] .ojb_btn{
  1706. color: var(--ojb-color-text-tertiary);
  1707. }
  1708. /* 文字颜色 浅绿 */
  1709. html[data-theme=dark] #SubmitButton{
  1710. color: var(--ojb-color-text-success);
  1711. }
  1712. /* 禁止文字颜色 */
  1713. html[data-theme=dark] .ojb_btn[disabled]{
  1714. color: var(--ojb-color-text-disabled);
  1715. }
  1716. /* 主要背景层次 */
  1717. html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] li#add_button:hover,
  1718. html[data-theme=dark] .ojb_btn:hover,
  1719. html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
  1720. html[data-theme=dark] #OJBetter_SubmitForm input,
  1721. html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
  1722. html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
  1723. html[data-theme=dark] .OJBetter_setting_sidebar li a.active, html[data-theme=dark] .OJBetter_setting_sidebar li,
  1724. html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-track, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-track,
  1725. html[data-theme=dark] .OJBetter_modal, html[data-theme=dark] .OJBetter_modal button:hover,
  1726. html[data-theme=dark] .popup .content,
  1727. html[data-theme=dark] .config_bar_list, html[data-theme=dark] #LSPLog,
  1728. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
  1729. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before,
  1730. html[data-theme=dark] .OJBetter_setting_menu a, html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button:hover,
  1731. html[data-theme=dark] .OJBetter_setting_menu select{
  1732. background-color: var(--ojb-color-bg-primary);
  1733. background-image: none;
  1734. }
  1735. /* 次要背景层次 */
  1736. html[data-theme=dark] .ojb_btn,
  1737. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1738. html[data-theme=dark] .alert-warning, html[data-theme=dark] .SumoSelect>.optWrapper>.options li.opt:hover,
  1739. html[data-theme=dark] .translate-problem-statement-panel,
  1740. html[data-theme=dark] .translate-problem-statement,
  1741. html[data-theme=dark] .OJBetter_setting_list,
  1742. html[data-theme=dark] .OJBetter_setting_menu hr,
  1743. html[data-theme=dark] .OJBetter_setting_sidebar li a,
  1744. html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-thumb, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-thumb,
  1745. html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] .test-for-popup pre,
  1746. html[data-theme=dark] .popup .content pre, html[data-theme=dark] .popup .content pre code,
  1747. html[data-theme=dark] ul.config_bar_ul::-webkit-scrollbar-thumb, html[data-theme=dark] #OJBetter_statusBar,
  1748. html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] .sampleDiv,
  1749. html[data-theme=dark] #addCustomTest, html[data-theme=dark] #LSPLog li:nth-child(odd),
  1750. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before,
  1751. html[data-theme=dark] .config::before, html[data-theme=dark] .config li.tempConfig_add_button:hover,
  1752. html[data-theme=dark] .OJBetter_setting_menu details, html[data-theme=dark] #config_bar_menu,
  1753. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button,
  1754. html[data-theme=dark] .OJBetter_setting_menu .badge, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton{
  1755. background-color: var(--ojb-color-bg-secondary);
  1756. }
  1757. /* 禁止背景层次 */
  1758. html[data-theme=dark] .ojb_btn[disabled]{
  1759. background-color: var(--ojb-color-bg-disabled);
  1760. }
  1761. /* 实线边框颜色-圆角 */
  1762. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1763. html[data-theme=dark] .alert-warning, html[data-theme=dark] .translate-problem-statement{
  1764. border: var(--ojb-border-solid-primary);
  1765. border-radius: 2px;
  1766. }
  1767. /* 实线边框颜色-无圆角 */
  1768. html[data-theme=dark] .ojb_btn,
  1769. html[data-theme=dark] .OJBetter_setting_list, html[data-theme=dark] .config_bar_list,
  1770. html[data-theme=dark] label.config_bar_ul_li_text,
  1771. html[data-theme=dark] .OJBetter_setting_sidebar li, html[data-theme=dark] .OJBetter_setting_menu select,
  1772. html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_SubmitForm select,
  1773. html[data-theme=dark] #OJBetter_editor, html[data-theme=dark] #OJBetter_statusBar,
  1774. html[data-theme=dark] #OJBetter_SubmitForm #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #customTestBlock,
  1775. html[data-theme=dark] #OJBetter_SubmitForm #addCustomTest, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton,
  1776. html[data-theme=dark] .OJBetter_setting_menu input,
  1777. html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
  1778. html[data-theme=dark] .OJBetter_setting_menu textarea,
  1779. html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea,
  1780. html[data-theme=dark] #CompilerSetting select, html[data-theme=dark] #CompilerSetting textarea, html[data-theme=dark] #CompilerBox,
  1781. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
  1782. html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] .config::before,
  1783. html[data-theme=dark] #statePanel, html[data-theme=dark] .test-case, html[data-theme=dark] .OJBetter_setting_menu .badge{
  1784. border: var(--ojb-border-solid-primary);
  1785. }
  1786. html[data-theme=dark] #customTestBlock #customTests{
  1787. border-top: var(--ojb-border-solid-primary);
  1788. }
  1789. html[data-theme=dark] .OJBetter_setting_sidebar {
  1790. border-right: var(--ojb-border-solid-primary);
  1791. }
  1792. /* 实线边框-禁止 */
  1793. html[data-theme=dark] .ojb_btn[disabled]{
  1794. border: var(--ojb-border-solid-disabled);
  1795. }
  1796. /* 虚线边框 */
  1797. html[data-theme=dark] li#add_button,
  1798. html[data-theme=dark] .OJBetter_setting_menu_label_text{
  1799. border: var(--ojb-border-dashed);
  1800. }
  1801. /* 虚线边框-悬浮 */
  1802. html[data-theme=dark] li#add_button:hover{
  1803. border: var(--ojb-border-dashed-hover);
  1804. background-color: var(--ojb-color-bg-secondary);
  1805. color: var(--ojb-color-border-dashed-hover);
  1806. }
  1807. /* 无边框 */
  1808. html[data-theme=dark] .translate-problem-statement-panel .ojb_btn{
  1809. border: none;
  1810. }
  1811. /* 区域遮罩 */
  1812. html[data-theme=dark] .ojb-overlay::before {
  1813. background: var(--ojb-overlay-background);
  1814. color: var(--ojb-color-text-secondary);
  1815. text-shadow: 0px 0px 2px #000000;
  1816. }
  1817. /* 阴影 */
  1818. html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .translate-problem-statement{
  1819. box-shadow: var(--ojb-shadow-standard);
  1820. }
  1821. /* 图标按钮状态样式 */
  1822. html[data-theme=dark] .ojb_btn_popover.success i:before, html[data-theme=dark] .ojb_btn_popover.success i {
  1823. color: var(--ojb-color-text-icon-success);
  1824. }
  1825. html[data-theme=dark] .ojb_btn_popover i:before {
  1826. text-shadow: var(--ojb-text-shadow-icon);
  1827. }
  1828. /* 其他样式 */
  1829. html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .OJBetter_modal{
  1830. box-shadow: var(--ojb-shadow-menu-modal);
  1831. border: 1px solid var(--ojb-color-bg-secondary);
  1832. }
  1833. html[data-theme=dark] input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
  1834. color: var(--ojb-color-text-primary);
  1835. border: 1px solid var(--ojb-color-border-radio-checked);
  1836. }
  1837. html[data-theme=dark] .alert{
  1838. text-shadow: none;
  1839. }
  1840. `);
  1841.  
  1842. // 网站界面样式
  1843. GM_addStyle(`
  1844. /* 文字颜色1 */
  1845. html[data-theme=dark] body, html[data-theme=dark] .title,
  1846. html[data-theme=dark] .problem-statement, html[data-theme=dark] #pageContent,
  1847. html[data-theme=dark] .ttypography, html[data-theme=dark] .roundbox, html[data-theme=dark] .info,
  1848. html[data-theme=dark] .ttypography .bordertable, html[data-theme=dark] .ttypography .bordertable thead th,
  1849. html[data-theme=dark] .ttypography h1, html[data-theme=dark] .ttypography h2, html[data-theme=dark] .ttypography h3,
  1850. html[data-theme=dark] .ttypography h4, html[data-theme=dark] .ttypography h5, html[data-theme=dark] .ttypography h6,
  1851. html[data-theme=dark] .datatable table, html[data-theme=dark] .problem-statement .sample-tests pre,
  1852. html[data-theme=dark] .ace-chrome .ace_gutter,
  1853. html[data-theme=dark] .setting-name,
  1854. html[data-theme=dark] .user-black, html[data-theme=dark] .comments label.show-archived,
  1855. html[data-theme=dark] .comments label.show-archived *, html[data-theme=dark] table{
  1856. color: var(--ojb-color-text-primary) !important;
  1857. }
  1858. html[data-theme=dark] h1 a, html[data-theme=dark] h2 a, html[data-theme=dark] h3 a, html[data-theme=dark] h4 a{
  1859. color: var(--ojb-color-text-secondary);
  1860. }
  1861. /* 文字颜色2 */
  1862. html[data-theme=dark] .contest-state-phase, html[data-theme=dark] .legendary-user-first-letter,
  1863. html[data-theme=dark] .lang-chooser,
  1864. html[data-theme=dark] .second-level-menu-list li a, html[data-theme=dark] #footer,
  1865. html[data-theme=dark] .ttypography .tt, html[data-theme=dark] select,
  1866. html[data-theme=dark] .roundbox .caption, html[data-theme=dark] .topic .title *,
  1867. html[data-theme=dark] .user-admin{
  1868. color: var(--ojb-color-text-secondary) !important;
  1869. }
  1870. /* 文字颜色3 */
  1871. html[data-theme=dark] #program-source-text-copy{
  1872. color: var(--ojb-color-text-secondary);
  1873. }
  1874. html[data-theme=dark] input{
  1875. color: var(--ojb-color-text-secondary) !important;
  1876. }
  1877. /* 文字颜色4 */
  1878. html[data-theme=dark] .ttypography .MathJax{
  1879. color: var(--ojb-color-text-highlight) !important;
  1880. }
  1881. html[data-theme=dark] .MathJax span{
  1882. color: var(--ojb-color-text-highlight);
  1883. }
  1884. /* 链接颜色 */
  1885. html[data-theme=dark] a:link {
  1886. color: var(--ojb-color-text-link);
  1887. }
  1888. html[data-theme=dark] a:visited {
  1889. color: var(--ojb-color-text-secondary);
  1890. }
  1891. html[data-theme=dark] .menu-box a, html[data-theme=dark] .sidebox th a{
  1892. color: var(--ojb-color-text-secondary) !important;
  1893. }
  1894. /* 按钮 */
  1895. html[data-theme=dark] .second-level-menu-list li.backLava {
  1896. border-radius: 6px;
  1897. overflow: hidden;
  1898. filter: invert(1) hue-rotate(.5turn);
  1899. }
  1900. html[data-theme=dark] input:hover{
  1901. background-color: var(--ojb-color-bg-primary) !important;
  1902. }
  1903. /* 背景层次1 */
  1904. html[data-theme=dark] body, html[data-theme=dark] .ttypography .bordertable thead th,
  1905. html[data-theme=dark] .datatable table, html[data-theme=dark] .datatable .dark, html[data-theme=dark] li#add_button,
  1906. html[data-theme=dark] .problem-statement .sample-tests pre, html[data-theme=dark] .markItUpEditor,
  1907. html[data-theme=dark] .SumoSelect>.CaptionCont, html[data-theme=dark] .SumoSelect>.optWrapper,
  1908. html[data-theme=dark] .SumoSelect>.optWrapper.multiple>.options li.opt span i, html[data-theme=dark] .ace_scroller,
  1909. html[data-theme=dark] textarea, html[data-theme=dark] .state, html[data-theme=dark] .ace-chrome .ace_gutter-active-line,
  1910. html[data-theme=dark] .sidebar-menu ul li:hover, html[data-theme=dark] .sidebar-menu ul li.active,
  1911. html[data-theme=dark] .popup .content, html[data-theme=dark] .file.input-view .text, html[data-theme=dark] .file.output-view .text,
  1912. html[data-theme=dark] .file.answer-view .text, html[data-theme=dark] .file.checker-comment-view .text{
  1913. background-color: var(--ojb-color-bg-primary) !important;
  1914. background-image: none;
  1915. }
  1916. /* 背景层次2 */
  1917. html[data-theme=dark] .roundbox, html[data-theme=dark] .roundbox .dark, html[data-theme=dark] .bottom-links,
  1918. html[data-theme=dark] .spoiler-content, html[data-theme=dark] input,
  1919. html[data-theme=dark] .problem-statement .test-example-line-even, html[data-theme=dark] .highlight-blue,
  1920. html[data-theme=dark] .ttypography .tt, html[data-theme=dark] select,
  1921. html[data-theme=dark] .SumoSelect>.optWrapper>.options li.opt:hover,
  1922. html[data-theme=dark] .aceEditorTd, html[data-theme=dark] .ace-chrome .ace_gutter,
  1923. html[data-theme=dark] .datatable,
  1924. html[data-theme=dark] .highlighted-row td, html[data-theme=dark] .highlighted-row th,
  1925. html[data-theme=dark] .pagination span.active, html[data-theme=dark] .test-for-popup pre,
  1926. html[data-theme=dark] .source-popup pre{
  1927. background-color: var(--ojb-color-bg-secondary) !important;
  1928. }
  1929. /* 实线边框颜色-圆角 */
  1930. html[data-theme=dark] .roundbox, html[data-theme=dark] .roundbox .rtable td,
  1931. html[data-theme=dark] .sidebar-menu ul li,
  1932. html[data-theme=dark] input, html[data-theme=dark] .ttypography .tt, html[data-theme=dark] #items-per-page,
  1933. html[data-theme=dark] .datatable td, html[data-theme=dark] .datatable th,
  1934. html[data-theme=dark] textarea, html[data-theme=dark] .input-output-copier{
  1935. border: var(--ojb-border-solid-primary) !important;
  1936. border-radius: 2px;
  1937. }
  1938. /* 实线边框颜色-无圆角 */
  1939. html[data-theme=dark] .problem-statement .sample-tests .input,
  1940. html[data-theme=dark] .problem-statement .sample-tests .output, html[data-theme=dark] .pagination span.active,
  1941. html[data-theme=dark] .test-for-popup pre{
  1942. border: var(--ojb-border-solid-primary) !important;
  1943. }
  1944. html[data-theme=dark] .roundbox .titled, html[data-theme=dark] .roundbox .rtable th {
  1945. border-bottom: var(--ojb-border-solid-primary) !important;
  1946. }
  1947. html[data-theme=dark] .roundbox .bottom-links, html[data-theme=dark] #footer{
  1948. border-top: var(--ojb-border-solid-primary) !important;
  1949. }
  1950. html[data-theme=dark] .topic .content {
  1951. border-left: 4px solid var(--ojb-color-border-primary) !important;
  1952. }
  1953. html[data-theme=dark] hr {
  1954. border-color: var(--ojb-color-border-primary) !important;
  1955. }
  1956. /* 虚线边框 */
  1957. html[data-theme=dark] .comment-table{
  1958. border: var(--ojb-border-dashed);
  1959. }
  1960. /* input-output-copier特殊处理 */
  1961. html[data-theme=dark] .html2md-panel.input-output-copier,
  1962. html[data-theme=dark] .translateDiv.input-output-copier,
  1963. html[data-theme=dark] #OJBetter_SubmitForm.input-output-copier{
  1964. border: none !important;
  1965. }
  1966. html[data-theme=dark] .html2md-panel.input-output-copier:hover,
  1967. html[data-theme=dark] #OJBetter_SubmitForm.input-output-copier:hover{
  1968. background-color: #ffffff00 !important;
  1969. }
  1970. /* focus-visible */
  1971. html[data-theme=dark] input:focus-visible, html[data-theme=dark] textarea, html[data-theme=dark] select{
  1972. border-width: 1.5px !important;
  1973. outline: none;
  1974. }
  1975. /* 图片-亮度 */
  1976. html[data-theme=dark] img, html[data-theme=dark] #facebox .popup a{
  1977. opacity: .75;
  1978. }
  1979. /* 反转 */
  1980. html[data-theme=dark] .SumoSelect>.CaptionCont>label>i, html[data-theme=dark] .delete-resource-link,
  1981. html[data-theme=dark] #program-source-text, html[data-theme=dark] .spoiler-content pre,
  1982. html[data-theme=dark] .popup .content pre code{
  1983. filter: invert(1) hue-rotate(.5turn);
  1984. }
  1985. /* 阴影 */
  1986. html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .translate-problem-statement{
  1987. box-shadow: var(--ojb-shadow-standard);
  1988. }
  1989. /* 图标按钮状态样式 */
  1990. html[data-theme=dark] .ojb_btn_popover.success i:before, html[data-theme=dark] .ojb_btn_popover.success i {
  1991. color: var(--ojb-color-text-icon-success);
  1992. }
  1993. html[data-theme=dark] .ojb_btn_popover i:before {
  1994. text-shadow: var(--ojb-text-shadow-icon);
  1995. }
  1996. /* 评测状态文字颜色 */
  1997. html[data-theme=dark] .verdict-accepted, html[data-theme=dark] .verdict-accepted-challenged,
  1998. html[data-theme=dark] .verdict-successful-challenge{
  1999. color: #0a0 !important;
  2000. }
  2001. html[data-theme=dark] .verdict-failed, html[data-theme=dark] .verdict-challenged{
  2002. color: red !important;
  2003. }
  2004. html[data-theme=dark] .verdict-rejected, html[data-theme=dark] .verdict-unsuccessful-challenge{
  2005. color: #673ab7 !important;
  2006. }
  2007. html[data-theme=dark] .verdict-waiting {
  2008. color: gray !important;
  2009. }
  2010. /* 样例hover样式 */
  2011. html[data-theme=dark] .problem-statement .darkhighlight {
  2012. background-color: #455a64 !important;
  2013. }
  2014. /* 其他样式 */
  2015. html[data-theme=dark] .rated-user{
  2016. display: initial;
  2017. }
  2018. html[data-theme=dark] .datatable .ilt, html[data-theme=dark] .datatable .irt,
  2019. html[data-theme=dark] .datatable .ilb, html[data-theme=dark] .datatable .irb,
  2020. html[data-theme=dark] .datatable .lt, html[data-theme=dark] .datatable .rt,
  2021. html[data-theme=dark] .datatable .lb, html[data-theme=dark] .datatable .rb{
  2022. background: none;
  2023. }
  2024. html[data-theme=dark] .problems .accepted-problem td.id{
  2025. border-left: 6px solid #47837d !important;
  2026. }
  2027. html[data-theme=dark] .problems .rejected-problem td.id{
  2028. border-left: 6px solid #ef9a9a !important;
  2029. }
  2030. html[data-theme=dark] .problems .accepted-problem td.act {
  2031. background-color: #47837d !important;
  2032. border-radius: 0px;
  2033. }
  2034. html[data-theme=dark] .problems .rejected-problem td.act{
  2035. background-color: #ef9a9a !important;
  2036. border-radius: 0px;
  2037. }
  2038. html[data-theme=dark] .collapsible-topic.collapsed .content .collapsible-topic-options:before{
  2039. background-image: linear-gradient(#22272e00, #22272e);
  2040. }
  2041. `);
  2042. })();
  2043.  
  2044. /**
  2045. * 黑暗模式额外的处理事件
  2046. */
  2047. function darkModeStyleAdjustment() {
  2048. $(".test-example-line").off("mouseenter mouseleave"); // 移除上面原本的事件
  2049. // 为奇数行添加悬停效果
  2050. $(".test-example-line-odd").hover(
  2051. function () {
  2052. $(this).addClass("darkhighlight");
  2053. $(this)
  2054. .prevUntil(":not(.test-example-line-odd)")
  2055. .addClass("darkhighlight");
  2056. $(this)
  2057. .nextUntil(":not(.test-example-line-odd)")
  2058. .addClass("darkhighlight");
  2059. },
  2060. function () {
  2061. $(this).removeClass("darkhighlight");
  2062. $(this)
  2063. .prevUntil(":not(.test-example-line-odd)")
  2064. .removeClass("darkhighlight");
  2065. $(this)
  2066. .nextUntil(":not(.test-example-line-odd)")
  2067. .removeClass("darkhighlight");
  2068. }
  2069. );
  2070.  
  2071. // 为偶数行添加悬停效果
  2072. $(".test-example-line-even").hover(
  2073. function () {
  2074. $(this).addClass("darkhighlight");
  2075. $(this)
  2076. .prevUntil(":not(.test-example-line-even)")
  2077. .addClass("darkhighlight");
  2078. $(this)
  2079. .nextUntil(":not(.test-example-line-even)")
  2080. .addClass("darkhighlight");
  2081. },
  2082. function () {
  2083. $(this).removeClass("darkhighlight");
  2084. $(this)
  2085. .prevUntil(":not(.test-example-line-even)")
  2086. .removeClass("darkhighlight");
  2087. $(this)
  2088. .nextUntil(":not(.test-example-line-even)")
  2089. .removeClass("darkhighlight");
  2090. }
  2091. );
  2092. }
  2093.  
  2094. /**
  2095. * 初始化monaco编辑器资源
  2096. */
  2097. async function initMonacoEditor() {
  2098. if (
  2099. OJBetter.monaco.enableOnProblemPage ||
  2100. OJBetter.monaco.beautifyPreBlocks
  2101. ) {
  2102. try {
  2103. // 等待Monaco Editor加载器脚本加载完成
  2104. await OJB_LoadJS(
  2105. "https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/monaco-editor/0.49.0/min/vs/loader.min.js",
  2106. "sha512-ZG31AN9z/CQD1YDDAK4RUAvogwbJHv6bHrumrnMLzdCrVu4HeAqrUX7Jsal/cbUwXGfaMUNmQU04tQ8XXl5Znw=="
  2107. );
  2108.  
  2109. // 配置Monaco Editor
  2110. require.config({
  2111. paths: {
  2112. vs: "https://mirrors.sustech.edu.cn/cdnjs/ajax/libs/monaco-editor/0.49.0/min/vs",
  2113. },
  2114. "vs/nls": { availableLanguages: { "*": "zh-cn" } },
  2115. });
  2116.  
  2117. // 加载Monaco Editor主脚本
  2118. require(["vs/editor/editor.main"], () => {
  2119. OJBetter.monaco.loaderOnload = true;
  2120. });
  2121. } catch (error) {
  2122. console.error("Failed to load Monaco Editor: ", error);
  2123. }
  2124. }
  2125. }
  2126.  
  2127. /**
  2128. * 美化代码块
  2129. */
  2130. async function beautifyPreBlocksWithMonaco() {
  2131. // 判断monacoLoader是否加载完毕
  2132. await OJB_waitUntilTrue(() => OJBetter.monaco.loaderOnload);
  2133.  
  2134. // 用于替换 <pre> 标签为 Monaco 编辑器的函数
  2135. function replacePreWithMonaco(preElement) {
  2136. const pre = $(preElement);
  2137. if (pre.hasClass("source-code-for-copy")) return; // 跳过复制块
  2138. const code = OJB_getCodeFromPre(pre.get(0));
  2139. if (!code) return;
  2140. const language = OJB_codeLangDetect(code);
  2141.  
  2142. // 创建一个用于 Monaco 编辑器的容器
  2143. const container = $("<div></div>");
  2144. const lineCount = code.split("\n").length; // 代码的行数
  2145.  
  2146. // 计算容器的高度
  2147. const calculateContainerHeight = (lineCount) => {
  2148. const lineHeight = 20; // 每行代码的高度
  2149. const minHeight = 100; // 最小高度
  2150. const maxHeight = 1000; // 最大高度
  2151. const dynamicHeight = lineCount * lineHeight;
  2152. return Math.min(Math.max(dynamicHeight, minHeight), maxHeight) + "px";
  2153. };
  2154.  
  2155. // 应用样式
  2156. container.css({
  2157. height: calculateContainerHeight(lineCount),
  2158. width: "100%",
  2159. });
  2160. pre.hide();
  2161. pre.after(container);
  2162.  
  2163. // 初始化 Monaco 编辑器
  2164. const editor = monaco.editor.create(container[0], {
  2165. value: code,
  2166. language: language,
  2167. readOnly: true,
  2168. tabSize: 4,
  2169. theme: OJBetter.common.realDarkMode == "dark" ? "vs-dark" : "vs",
  2170. scrollbar: {
  2171. verticalScrollbarSize: 10,
  2172. horizontalScrollbarSize: 10,
  2173. alwaysConsumeMouseWheel: false,
  2174. },
  2175. automaticLayout: true,
  2176. scrollBeyondLastLine: false,
  2177. });
  2178.  
  2179. // 保存编辑器实例
  2180. OJBetter.monaco.beautifyEditor.push(editor);
  2181.  
  2182. // 替换复制按钮事件
  2183. if (OJBetter.typeOfPage.is_submission) {
  2184. const button = $("div[title=Copy]");
  2185. button.off("click");
  2186. button.on("click", (event) => {
  2187. event.stopPropagation(); // 阻止事件冒泡到 clipboard.min.js
  2188. const text = editor.getValue(); // 获取编辑器内的内容
  2189. GM_setClipboard(text);
  2190. Codeforces.showMessage(
  2191. "The source code has been copied into the clipboard"
  2192. );
  2193. });
  2194. }
  2195. }
  2196. // 全局替换页面上所有的 <pre> 元素
  2197. $("pre").each(function () {
  2198. replacePreWithMonaco(this);
  2199. });
  2200. // 监听页面上的提交状态页面窗口的 <pre> 元素
  2201. if (OJBetter.typeOfPage.is_statePage || OJBetter.typeOfPage.is_submissions) {
  2202. OJB_observeElement({
  2203. selector: "#facebox",
  2204. callback: (node) => {
  2205. // 如果 facebox 中存在 pre 元素,则替换它们
  2206. const preElements = $(node).find("pre");
  2207. preElements.each(function () {
  2208. replacePreWithMonaco(this);
  2209. });
  2210. },
  2211. });
  2212. }
  2213. }
  2214.  
  2215. /**
  2216. * 隐藏题目问题标签
  2217. */
  2218. function hiddenProblemTag() {
  2219. document
  2220. .querySelectorAll(".roundbox.sidebox.borderTopRound .tag-box")
  2221. .forEach((element) => {
  2222. if (!element.textContent.includes("*")) {
  2223. element.classList.add("hover-reveal");
  2224. }
  2225. });
  2226. }
  2227.  
  2228. // 样式
  2229. GM_addStyle(`
  2230. /*动画*/
  2231. @keyframes shake {
  2232. 0% { transform: translateX(-5px); }
  2233. 100% { transform: translateX(5px); }
  2234. }
  2235. @keyframes rotate {
  2236. from {
  2237. transform: rotate(0deg);
  2238. }
  2239.  
  2240. to {
  2241. transform: rotate(360deg);
  2242. }
  2243. }
  2244. @keyframes rippleout {
  2245. 0% {
  2246. box-shadow: 0 0 0 0 rgba(96, 98, 102, 0.2);
  2247. }
  2248.  
  2249. 100% {
  2250. box-shadow: 0 0 0 6px rgba(0, 0, 0, 0);
  2251. }
  2252. }
  2253. @keyframes bounce-in {
  2254. 20%,40%,60%,80%,from,to {
  2255. animation-timing-function: cubic-bezier(.215,.61,.355,1);
  2256. }
  2257.  
  2258. 0% {
  2259. opacity: 0;
  2260. transform: scale3d(.995,.995,.995);
  2261. }
  2262.  
  2263. 20% {
  2264. opacity: 1;
  2265. transform: scale3d(1.005,1.005,1.005);
  2266. }
  2267.  
  2268. 40% {
  2269. transform: scale3d(.998,.998,.998);
  2270. }
  2271.  
  2272. 60% {
  2273. transform: scale3d(1.002,1.002,1.002);
  2274. }
  2275.  
  2276. 80% {
  2277. transform: scale3d(.995,.995,.995);
  2278. }
  2279.  
  2280. to {
  2281. opacity: 1;
  2282. transform: scale3d(1,1,1);
  2283. }
  2284. }
  2285. /*iconfont图标*/
  2286. .iconfont {
  2287. font-family: "iconfont" !important;
  2288. font-size: 16px;
  2289. font-style: normal !important;
  2290. -webkit-font-smoothing: antialiased;
  2291. -moz-osx-font-smoothing: grayscale;
  2292. }
  2293. @font-face {
  2294. font-family: 'iconfont'; /* Project id 4284341 */
  2295. src: url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2') format('woff2'),
  2296. url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2.ttf') format('truetype');
  2297. }
  2298. html {
  2299. scroll-behavior: smooth;
  2300. }
  2301. :root {
  2302. --vp-font-family-base: "Chinese Quotes", "Inter var", "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  2303. }
  2304. span.mdViewContent {
  2305. white-space: pre-wrap;
  2306. }
  2307.  
  2308. /* 特殊处理,加上input-output-copier类, 让convertStatementToText方法忽略该元素 */
  2309. .translateDiv.input-output-copier, .html2md-panel.input-output-copier, #OJBetter_SubmitForm.input-output-copier {
  2310. font-size: initial;
  2311. float: initial;
  2312. color: initial;
  2313. cursor: initial;
  2314. border: none;
  2315. padding: 0px;
  2316. margin: 0px;
  2317. line-height: initial;
  2318. text-transform: none;
  2319. }
  2320. .translateDiv.input-output-copier:hover, .html2md-panel.input-output-copier:hover, #OJBetter_SubmitForm.input-output-copier:hover {
  2321. background-color: #ffffff00;
  2322. }
  2323.  
  2324. /* dialog */
  2325. dialog {
  2326. margin: 0px !important;
  2327. }
  2328. dialog::backdrop {
  2329. background-color: rgba(0, 0, 0, 0.4);
  2330. }
  2331.  
  2332. /*题目页链接栏样式*/
  2333. #problemToolbar {
  2334. display: flex;
  2335. gap: 6px;
  2336. flex-wrap: wrap;
  2337. justify-content: flex-end;
  2338. overflow: auto;
  2339. height: 100%;
  2340. margin: 0.5em;
  2341. }
  2342.  
  2343. /*html2md面板*/
  2344. .html2md-panel {
  2345. display: flex;
  2346. justify-content: flex-end;
  2347. align-items: center;
  2348. gap: 6px;
  2349. padding: 5px 0px !important;
  2350. }
  2351. .html2md-panel a {
  2352. text-decoration: none;
  2353. }
  2354. .html2md-panel.is_simple {
  2355. position: absolute;
  2356. right: 2%;
  2357. }
  2358.  
  2359. /*通用按钮*/
  2360. .ojb_btn {
  2361. display: flex;
  2362. align-items: center;
  2363. justify-content: center;
  2364. cursor: pointer;
  2365. background-color: #ffffff;
  2366. color: #606266;
  2367. width: auto;
  2368. font-size: 13px;
  2369. border-radius: 0.3rem;
  2370. padding: 2px 5px;
  2371. margin: 0px;
  2372. border: 1px solid #dcdfe6;
  2373. }
  2374. .ojb_btn[disabled] {
  2375. cursor: not-allowed !important;
  2376. color: rgb(168, 171, 178) !important;
  2377. border: 1px solid #e4e7ed;
  2378. background-color: #ffffff;
  2379. }
  2380. .ojb_btn:hover {
  2381. color: #409eff;
  2382. border-color: #409eff;
  2383. background-color: #f1f8ff;
  2384. z-index: 150;
  2385. }
  2386. .ojb_btn.primary {
  2387. color: #ffffff;
  2388. border: 1px solid #409eff;
  2389. background-color: #409eff;
  2390. }
  2391. .ojb_btn.primary:hover {
  2392. color: #ffffff;
  2393. border: 1px solid #79bbff;
  2394. background-color: #79bbff;
  2395. }
  2396. .ojb_btn.success {
  2397. color: #4caf50;
  2398. border: 1px solid #C8E6C9;
  2399. background-color: #f0f9eb;
  2400. }
  2401. .ojb_btn.warning {
  2402. color: #e6a23c;
  2403. border: 1px solid #f3d19e;
  2404. background-color: #fdf6ec;
  2405. }
  2406. .ojb_btn.error {
  2407. color: #f56c6c;
  2408. border: 1px solid #fab6b6;
  2409. background-color: #fef0f0;
  2410. }
  2411. .ojb_btn.enabled {
  2412. color: #42A5F5;
  2413. border: 1px solid #90CAF9;
  2414. background-color: #fafbff;
  2415. }
  2416. .ojb_btn.active {
  2417. animation: rippleout 0.5s ease-in-out;
  2418. }
  2419. a.ojb_btn {
  2420. text-decoration: none;
  2421. }
  2422. a.ojb_btn:link {
  2423. color: #606266;
  2424. }
  2425. a.ojb_btn span {
  2426. margin-left: 2px;
  2427. }
  2428. /*按钮图标和popover*/
  2429. .ojb_btn_popover {
  2430. display: flex;
  2431. justify-content: center;
  2432. position: relative;
  2433. outline: none;
  2434. appearance: none;
  2435. }
  2436. .ojb_btn_popover:hover span {
  2437. opacity: 1;
  2438. visibility: visible;
  2439. }
  2440. .ojb_btn_popover i:before {
  2441. position: absolute;
  2442. text-shadow: 1px 1px 0px #ffffff, 1px -1px 0px #ffffff, -1px -1px 0px #ffffff, -1px 1px 0px #ffffff;
  2443. }
  2444. .ojb_btn_popover span {
  2445. cursor: initial;
  2446. position: absolute;
  2447. left: 50%;
  2448. opacity: 0;
  2449. visibility: hidden;
  2450. padding: 4px 8px;
  2451. background-color: rgba(33, 33, 33, 0.8);
  2452. color: rgba(255, 255, 255, 0.9019607843);
  2453. font-size: 12px;
  2454. border-radius: 6px;
  2455. line-height: 1.6;
  2456. text-align: left;
  2457. white-space: nowrap;
  2458. transition: all 0.15s ease-in-out;
  2459. z-index: 999;
  2460. }
  2461. .ojb_btn_popover span:hover {
  2462. opacity: 0;
  2463. visibility: hidden;
  2464. }
  2465. .ojb_btn_popover.top:hover span {
  2466. transform: translate(-50%, 0);
  2467. }
  2468. .ojb_btn_popover.top span {
  2469. bottom: 100%;
  2470. transform: translate(-50%, -20%);
  2471. margin-bottom: 4px;
  2472. }
  2473. .ojb_btn_popover.top span:hover {
  2474. transform: translate(-50%, -20%);
  2475. }
  2476. .ojb_btn_popover.bottom:hover span {
  2477. transform: translate(-50%, 105%);
  2478. }
  2479. .ojb_btn_popover.bottom span {
  2480. bottom: -2%;
  2481. transform: translate(-50%, 100%);
  2482. margin-top: 4px;
  2483. }
  2484. .ojb_btn_popover.bottom span:hover {
  2485. transform: translate(-50%, 50%);
  2486. }
  2487. .ojb_btn_popover.loading i {
  2488. color: rgba(33, 33, 33, 0.1);
  2489. }
  2490. .ojb_btn_popover.loading i:before {
  2491. content: "\\e640";
  2492. color: rgb(168, 171, 178);
  2493. animation: rotate 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
  2494. }
  2495. .ojb_btn_popover.running i {
  2496. color: rgba(33, 33, 33, 0.1);
  2497. }
  2498. .ojb_btn_popover.running i:before {
  2499. content: "\\e600";
  2500. color: rgb(168, 171, 178);
  2501. animation: rotate 1s linear infinite;
  2502. }
  2503. .ojb_btn_popover.warning i {
  2504. color: rgba(230, 162, 61, 0.8);
  2505. }
  2506. .ojb_btn_popover.warning i:before {
  2507. content: "\\e68b";
  2508. font-size: 15px;
  2509. left: 10px;
  2510. bottom: 0%;
  2511. color: #ff9800;
  2512. }
  2513. .ojb_btn_popover.error i {
  2514. color: rgba(245, 108, 108, 0.8);
  2515. }
  2516. .ojb_btn_popover.error i:before {
  2517. content: "\\e651";
  2518. font-size: 15px;
  2519. left: 10px;
  2520. bottom: 0%;
  2521. color: #F44336;
  2522. }
  2523. .ojb_btn_popover.success i {
  2524. color: rgba(76, 175, 80, 0.9);
  2525. }
  2526. .ojb_btn_popover.success i:before {
  2527. content: "\\e61e";
  2528. font-size: 15px;
  2529. left: 10px;
  2530. bottom: 0%;
  2531. color: #4caf50;
  2532. }
  2533. .ojb_btn_popover.enabled i {
  2534. color: rgba(33, 150, 243, 0.6);
  2535. }
  2536. .ojb_btn_popover.enabled i:before {
  2537. content: "\\e6f4";
  2538. font-size: 15px;
  2539. left: 10px;
  2540. bottom: 0%;
  2541. color: #2196F3;
  2542. }
  2543. .ojb_btn_popover.redo i {
  2544. color: rgba(33, 33, 33, 0.1);
  2545. }
  2546. .ojb_btn_popover.redo i:before {
  2547. content: "\\e831";
  2548. color: #616161;
  2549. }
  2550. .ojb_btn_popover.reverse i {
  2551. transform: rotate(180deg);
  2552. }
  2553.  
  2554. /*translateDiv样式*/
  2555. .translateDiv .topText {
  2556. display: flex;
  2557. margin-left: 5px;
  2558. color: #9e9e9e;
  2559. font-size: 13px;
  2560. align-items: center;
  2561. }
  2562. .translateDiv .borderlessButton{
  2563. display: flex;
  2564. align-items: center;
  2565. margin: 2.5px 7px;
  2566. fill: #9E9E9E;
  2567. }
  2568. .translateDiv .borderlessButton:hover{
  2569. cursor: pointer;
  2570. fill: #059669;
  2571. }
  2572. .translateDiv.bounce-in {
  2573. animation: bounce-in 1s forwards;
  2574. }
  2575. html:not([data-theme='dark']) .translateDiv {
  2576. box-shadow: 0px 0px 0.5px 0.5px #defdf378;
  2577. }
  2578. .translate-problem-statement {
  2579. justify-items: start;
  2580. letter-spacing: 1.8px;
  2581. color: #059669;
  2582. background-color: #f9f9fa;
  2583. border: 1px solid #c5ebdf;
  2584. border-radius: 0rem 0rem 0.3rem 0.3rem;
  2585. padding: 5px;
  2586. margin: -5px 0px 6px 0px;
  2587. width: 100%;
  2588. box-sizing: border-box;
  2589. font-size: 13px;
  2590. }
  2591. .translate-problem-statement h2 {
  2592. font-size: 1.6em;
  2593. font-weight: 700;
  2594. }
  2595. .translate-problem-statement h3 {
  2596. font-size: 1.3em;
  2597. font-weight: 700;
  2598. }
  2599. .translate-problem-statement-panel{
  2600. display: flex;
  2601. justify-content: space-between;
  2602. background-color: #f9f9fa;
  2603. border: 1px solid #c5ebdf;
  2604. border-radius: 0.3rem;
  2605. margin: 4px 0px;
  2606. }
  2607. .translate-problem-statement-panel .ojb_btn {
  2608. background: none;
  2609. border: none;
  2610. color: #9e9e9e;
  2611. padding: 5px 8px;
  2612. }
  2613. .translate-problem-statement-panel.error, .translate-problem-statement.error {
  2614. color: red;
  2615. border-color: red;
  2616. }
  2617. .translate-problem-statement a, .translate-problem-statement a:link {
  2618. color: #10b981;
  2619. font-weight: 600;
  2620. background: 0 0;
  2621. text-decoration: none;
  2622. }
  2623. .translate-problem-statement ol, .translate-problem-statement ul {
  2624. display: grid;
  2625. margin-inline-start: 0.8em;
  2626. margin-block-start: 0em;
  2627. margin: 0.5em 0 0 3em;
  2628. padding-inline-start: 0px;
  2629. }
  2630. .translate-problem-statement li {
  2631. display: list-item;
  2632. height: auto;
  2633. word-wrap: break-word;
  2634. }
  2635. .translate-problem-statement ol li {
  2636. list-style-type: auto;
  2637. }
  2638. .translate-problem-statement ul li {
  2639. list-style-type: disc;
  2640. }
  2641. .translate-problem-statement img {
  2642. max-width: 100.0%;
  2643. max-height: 100.0%;
  2644. }
  2645. .ttypography .translate-problem-statement .MathJax {
  2646. color: #059669!important;
  2647. }
  2648. .translate-problem-statement span.math {
  2649. margin: 0px 2.5px !important;
  2650. }
  2651. .translate-problem-statement a:hover {
  2652. background-color: #800;
  2653. color: #fff;
  2654. text-decoration: none;
  2655. }
  2656. .translate-problem-statement table {
  2657. border: 1px #ccc solid !important;
  2658. margin: 1.5em 0 !important;
  2659. color: #059669 !important;
  2660. }
  2661. .translate-problem-statement table thead th {
  2662. border: 1px #ccc solid !important;
  2663. color: #059669 !important;
  2664. }
  2665. .translate-problem-statement table td {
  2666. border-right: 1px solid #ccc;
  2667. border-top: 1px solid #ccc;
  2668. padding: 0.7143em 0.5em;
  2669. }
  2670. .translate-problem-statement table th {
  2671. padding: 0.7143em 0.5em;
  2672. }
  2673. .translate-problem-statement p:not(:first-child) {
  2674. margin: 1.5em 0 0;
  2675. }
  2676. .translate-problem-statement p {
  2677. line-height: 20px !important;
  2678. word-wrap: break-word;
  2679. font-size: 13px !important
  2680. }
  2681. .problem-statement p:last-child {
  2682. margin-bottom: 0px !important;
  2683. }
  2684.  
  2685. /*设置按钮*/
  2686. header .enter-or-register-box, header .languages {
  2687. position: absolute;
  2688. right: 170px;
  2689. }
  2690. .ojb_btn.OJBetter_setting {
  2691. float: right;
  2692. height: 30px;
  2693. background: #60a5fa;
  2694. color: white;
  2695. margin: 10px;
  2696. border: 1px solid #60a5fa;
  2697. }
  2698. .ojb_btn.OJBetter_setting.open {
  2699. background-color: #e6e6e6;
  2700. color: #727378;
  2701. cursor: not-allowed;
  2702. }
  2703.  
  2704. /*设置面板*/
  2705. .OJBetter_setting_menu {
  2706. box-shadow: 0px 0px 0px 4px #ffffff;
  2707. position: fixed;
  2708. top: 50%;
  2709. left: 50%;
  2710. width: 600px;
  2711. min-height: 600px;
  2712. transform: translate(-50%, -50%);
  2713. border-radius: 6px;
  2714. background-color: #f0f4f9;
  2715. border-collapse: collapse;
  2716. border: 1px solid #ffffff;
  2717. color: #697e91;
  2718. font-family: var(--vp-font-family-base);
  2719. padding: 10px 20px 20px 10px;
  2720. box-sizing: content-box;
  2721. }
  2722. .OJBetter_setting_menu h3 {
  2723. margin-top: 10px;
  2724. font-size: 1.4em;
  2725. font-weight: 700;
  2726. }
  2727. .OJBetter_setting_menu h4 {
  2728. margin: 15px 0px 10px 0px;
  2729. }
  2730. .OJBetter_setting_menu h4,.OJBetter_setting_menu h5 {
  2731. font-weight: 600;
  2732. }
  2733. .OJBetter_setting_menu hr {
  2734. border: none;
  2735. height: 1px;
  2736. background-color: #ccc;
  2737. margin: 10px 0;
  2738. }
  2739. .OJBetter_setting_menu details {
  2740. padding: 10px;
  2741. margin-bottom: 5px;
  2742. background-color: #ffffff;
  2743. border-bottom: 1px solid #c9c6c696;
  2744. border-radius: 8px;
  2745. }
  2746. .OJBetter_setting_menu .badge {
  2747. border-radius: 4px;
  2748. border: 1px solid #009688;
  2749. color: #009688;
  2750. background-color: #fff;
  2751. padding: 0.5px 4px;
  2752. margin-left: 5px;
  2753. margin-right: auto;
  2754. line-height: initial;
  2755. font-weight: initial;
  2756. }
  2757. .OJBetter_setting_menu .missing {
  2758. box-shadow: inset 0px 0px 1px 1px red;
  2759. }
  2760. /* 页面切换 */
  2761. .OJBetter_setting_menu .settings-page {
  2762. display: none;
  2763. }
  2764. .OJBetter_setting_menu .settings-page.active {
  2765. display: block;
  2766. }
  2767. .OJBetter_setting_container {
  2768. display: flex;
  2769. }
  2770. .OJBetter_setting_sidebar {
  2771. flex: 0 0 auto;
  2772. min-width: 110px;
  2773. padding: 6px 10px 6px 6px;
  2774. margin: 20px 0px;
  2775. border-right: 1px solid #d4d8e9;
  2776. }
  2777. .OJBetter_setting_content {
  2778. flex-grow: 1;
  2779. margin: 20px 0px 0px 12px;
  2780. padding-right: 10px;
  2781. max-height: 580px;
  2782. overflow-y: auto;
  2783. box-sizing: border-box;
  2784. }
  2785. .OJBetter_setting_sidebar h3 {
  2786. margin-top: 0;
  2787. }
  2788. .OJBetter_setting_sidebar hr {
  2789. margin-top: 10px;
  2790. margin-bottom: 10px;
  2791. border: none;
  2792. border-top: 1px solid #DADCE0;
  2793. }
  2794. .OJBetter_setting_sidebar ul {
  2795. list-style-type: none;
  2796. margin: 0;
  2797. padding: 0;
  2798. }
  2799. .OJBetter_setting_sidebar li {
  2800. margin: 5px 0px;
  2801. background-color: #ffffff;
  2802. border: 1px solid #d4d8e9;
  2803. border-radius: 4px;
  2804. font-size: 16px;
  2805. }
  2806. .OJBetter_setting_sidebar li a {
  2807. text-decoration: none;
  2808. display: flex;
  2809. width: 100%;
  2810. font-size: 16px;
  2811. color: gray;
  2812. background-color: #ffffff;
  2813. border: none;
  2814. letter-spacing: 2px;
  2815. padding: 7px;
  2816. margin: 0px;
  2817. border-radius: 4px;
  2818. align-items: center;
  2819. -webkit-box-sizing: border-box;
  2820. -moz-box-sizing: border-box;
  2821. box-sizing: border-box;
  2822. }
  2823. .OJBetter_setting_sidebar li a.active {
  2824. background-color: #eceff1c7;
  2825. }
  2826. /* 链接样式 */
  2827. .OJBetter_setting_menu a {
  2828. font-size: 13px;
  2829. color: #009688 !important;
  2830. background-color: #E0F2F1;
  2831. border: 1px solid #009688;
  2832. border-radius: 4px;
  2833. padding: 0px 5px;
  2834. margin: 0px 5px;
  2835. text-decoration: none;
  2836. }
  2837. /* 下拉选择框 */
  2838. .OJBetter_setting_menu select {
  2839. appearance: none;
  2840. padding: 5px 10px;
  2841. margin: -5px 0px;
  2842. border-radius: 6px;
  2843. border-style: solid;
  2844. border: 1px solid #ced4da;
  2845. color: #009688;
  2846. background: #ffffff;
  2847. font-size: 15px;
  2848. }
  2849. .OJBetter_setting_menu select:focus-visible {
  2850. outline: none;
  2851. }
  2852. .OJBetter_setting_menu select option:disabled {
  2853. color: #EEEEEE;
  2854. }
  2855. /* 数值输入框 */
  2856. .OJBetter_setting_menu input[type="number"] {
  2857. width: 40px;
  2858. color: #009688;
  2859. font-size: 15px;
  2860. appearance: none;
  2861. padding: 5px 10px;
  2862. margin: -5px 3px;
  2863. border-radius: 6px;
  2864. border-style: solid;
  2865. border: 1px solid #ced4da;
  2866. }
  2867. .OJBetter_setting_menu input[type="number"]:focus-visible {
  2868. outline: none;
  2869. }
  2870. .OJBetter_setting_menu input[type="number"]::-webkit-inner-spin-button,
  2871. .OJBetter_setting_menu input[type="number"]::-webkit-outer-spin-button {
  2872. -webkit-appearance: none;
  2873. margin: 0;
  2874. }
  2875. /*设置面板-滚动条*/
  2876. .OJBetter_setting_menu::-webkit-scrollbar, .OJBetter_setting_content::-webkit-scrollbar,
  2877. .OJBetter_modal .content::-webkit-scrollbar {
  2878. width: 5px;
  2879. height: 7px;
  2880. background-color: #aaa;
  2881. }
  2882. .OJBetter_setting_menu::-webkit-scrollbar-thumb, .OJBetter_setting_content::-webkit-scrollbar-thumb,
  2883. .OJBetter_modal .content::-webkit-scrollbar-thumb {
  2884. background-clip: padding-box;
  2885. background-color: #d7d9e4;
  2886. }
  2887. .OJBetter_setting_menu::-webkit-scrollbar-track, .OJBetter_setting_content::-webkit-scrollbar-track,
  2888. .OJBetter_modal .content::-webkit-scrollbar-track {
  2889. background-color: #f1f1f1;
  2890. }
  2891. /*设置面板-关闭按钮*/
  2892. .OJBetter_setting_menu .tool-box {
  2893. position: absolute;
  2894. width: 20px;
  2895. height: 20px;
  2896. top: 3px;
  2897. right: 3px;
  2898. }
  2899. .OJBetter_setting_menu .btn-close {
  2900. width: 20px;
  2901. height: 20px;
  2902. border-radius: 50%;
  2903. border: none;
  2904. margin: 0px;
  2905. padding: 0px;
  2906. background-color: #ff000080;
  2907. transition: .15s ease all;
  2908. box-sizing: border-box;
  2909. text-align: center;
  2910. color: transparent;
  2911. }
  2912. .OJBetter_setting_menu .iconfont {
  2913. font-size: 10px;
  2914. font-weight: bolder;
  2915. }
  2916. .OJBetter_setting_menu .btn-close:hover {
  2917. color: #ffffff;
  2918. background-color: #ff0000cc;
  2919. box-shadow: 0 5px 5px 0 #00000026;
  2920. }
  2921. .OJBetter_setting_menu .btn-close:active {
  2922. color: #ffffffde;
  2923. background-color: #ff000080;
  2924. }
  2925. /*设置面板-checkbox*/
  2926. .OJBetter_setting_menu input[type=checkbox]:focus {
  2927. outline: 0px;
  2928. }
  2929. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"] {
  2930. margin: 0px;
  2931. appearance: none;
  2932. -webkit-appearance: none;
  2933. width: 40px;
  2934. height: 20px;
  2935. border: 1.5px solid #D7CCC8;
  2936. padding: 0px !important;
  2937. border-radius: 20px;
  2938. background: #efebe978;
  2939. position: relative;
  2940. box-sizing: border-box;
  2941. }
  2942. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::before {
  2943. content: "";
  2944. width: 17px;
  2945. height: 17px;
  2946. background: #D7CCC8;
  2947. border: 1.5px solid #BCAAA4;
  2948. border-radius: 50%;
  2949. position: absolute;
  2950. top: 0;
  2951. left: 0;
  2952. transform: translate(2%, 2%);
  2953. transition: all 0.3s ease-in-out;
  2954. box-sizing: border-box;
  2955. }
  2956. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::after {
  2957. content: url("data:image/svg+xml,%3Csvg xmlns='://www.w3.org/2000/svg' width='23' height='23' viewBox='0 0 23 23' fill='none'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.55021 5.84315L17.1568 16.4498L16.4497 17.1569L5.84311 6.55026L6.55021 5.84315Z' fill='%23EA0707' fill-opacity='0.89'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M17.1567 6.55021L6.55012 17.1568L5.84302 16.4497L16.4496 5.84311L17.1567 6.55021Z' fill='%23EA0707' fill-opacity='0.89'/%3E%3C/svg%3E");
  2958. position: absolute;
  2959. top: 0;
  2960. left: 24px;
  2961. }
  2962. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked {
  2963. border: 1.5px solid #C5CAE9;
  2964. background: #E8EAF6;
  2965. }
  2966. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::before {
  2967. background: #C5CAE9;
  2968. border: 1.5px solid #7986CB;
  2969. transform: translate(122%, 2%);
  2970. transition: all 0.3s ease-in-out;
  2971. }
  2972. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::after {
  2973. content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 15 13' fill='none'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M14.8185 0.114533C15.0314 0.290403 15.0614 0.605559 14.8855 0.818454L5.00187 12.5L0.113036 6.81663C-0.0618274 6.60291 -0.0303263 6.2879 0.183396 6.11304C0.397119 5.93817 0.71213 5.96967 0.886994 6.18339L5.00187 11L14.1145 0.181573C14.2904 -0.0313222 14.6056 -0.0613371 14.8185 0.114533Z' fill='%2303A9F4' fill-opacity='0.9'/%3E%3C/svg%3E");
  2974. position: absolute;
  2975. top: 1.5px;
  2976. left: 4.5px;
  2977. }
  2978. .OJBetter_setting_menu .OJBetter_setting_list button {
  2979. cursor: pointer;
  2980. color: #7986cb;
  2981. background-color: #e8eaf6;
  2982. border: 1px solid #7986cb;
  2983. border-radius: 6px;
  2984. width: 100px;
  2985. margin: -5px 2px;
  2986. padding: 5px 10px;
  2987. }
  2988. .OJBetter_setting_menu .OJBetter_setting_list button:hover {
  2989. color: #e8eaf6;
  2990. background-color: #7986cb;
  2991. border: 1px solid #7986cb;
  2992. }
  2993. .OJBetter_setting_menu label, #darkMode_span, #loaded_span {
  2994. font-size: 16px;
  2995. }
  2996. .OJBetter_setting_list {
  2997. display: flex;
  2998. flex-wrap: wrap;
  2999. align-items: center;
  3000. padding: 10px;
  3001. margin: 5px 0px;
  3002. background-color: #ffffff;
  3003. border: 1px solid #c9c6c642;
  3004. border-bottom-color: #c9c6c696;
  3005. border-radius: 8px;
  3006. justify-content: space-between;
  3007. }
  3008. .OJBetter_setting_list.alert_danger {
  3009. color: #F44336;
  3010. background-color: #FFEBEE;
  3011. border: 1px solid #F44336;
  3012. margin: 10px 0px;
  3013. }
  3014. .OJBetter_setting_list.alert_warn {
  3015. color: #E65100;
  3016. background-color: #FFF3E0;
  3017. border: 1px solid #FF9800;
  3018. margin: 10px 0px;
  3019. }
  3020. .OJBetter_setting_list.alert_tip {
  3021. color: #009688;
  3022. background-color: #E0F2F1;
  3023. border: 1px solid #009688;
  3024. margin: 10px 0px;
  3025. }
  3026. .OJBetter_setting_list.alert_info {
  3027. color: #ffffff;
  3028. background-color: #009688;
  3029. margin: 10px 0px;
  3030. box-shadow: rgba(0, 0, 0, 0.06) 0px 2px 4px 0px inset;
  3031. }
  3032. .OJBetter_setting_list p:not(:last-child) {
  3033. margin-bottom: 10px;
  3034. }
  3035. .OJBetter_setting_list p:not(:first-child) {
  3036. margin-top: 10px;
  3037. }
  3038. /*设置面板-checkboxs*/
  3039. .OJBetter_setting_menu .OJBetter_checkboxs {
  3040. flex-basis: 100%;
  3041. display: flex;
  3042. padding: 8px;
  3043. margin: 10px 0px 0px 0px;
  3044. border-bottom: 1px solid #c9c6c696;
  3045. border-radius: 8px;
  3046. border: 1px solid #c5cae9;
  3047. background-color: #f0f8ff;
  3048. }
  3049. .OJBetter_setting_menu .OJBetter_checkboxs label {
  3050. font-size: 13px;
  3051. margin: 0px 6px 0px 3px;
  3052. }
  3053. .OJBetter_setting_menu .OJBetter_checkboxs input[type=checkbox]:checked+label{
  3054. color: #7986cb;
  3055. }
  3056. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"] {
  3057. border: none;
  3058. width: 16px;
  3059. height: 16px;
  3060. }
  3061. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before{
  3062. background: #ffffff;
  3063. transform: none;
  3064. width: 16px;
  3065. height: 16px;
  3066. }
  3067. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked {
  3068. background: none;
  3069. border: none;
  3070. }
  3071. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before {
  3072. border: 1.5px solid #95a2de;
  3073. background: #e8eaf6;
  3074. transform: none;
  3075. }
  3076. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::after {
  3077. content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='9' viewBox='0 0 15 13' fill='none'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M14.8185 0.114533C15.0314 0.290403 15.0614 0.605559 14.8855 0.818454L5.00187 12.5L0.113036 6.81663C-0.0618274 6.60291 -0.0303263 6.2879 0.183396 6.11304C0.397119 5.93817 0.71213 5.96967 0.886994 6.18339L5.00187 11L14.1145 0.181573C14.2904 -0.0313222 14.6056 -0.0613371 14.8185 0.114533Z' fill='%2303A9F4' fill-opacity='0.9'/%3E%3C/svg%3E");
  3078. top: 0px;
  3079. left: 3.5px;
  3080. }
  3081. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:disabled+label {
  3082. color: #BDBDBD;
  3083. }
  3084. /*设置面板-radio*/
  3085. .OJBetter_setting_menu label {
  3086. display: block;
  3087. font-weight: initial;
  3088. list-style-type: none;
  3089. padding-inline-start: 0px;
  3090. overflow-x: auto;
  3091. max-width: 100%;
  3092. margin: 3px 0px;
  3093. overflow-x: visible;
  3094. }
  3095. .OJBetter_setting_menu_label_text {
  3096. display: flex;
  3097. border: 1px dashed #00aeeccc;
  3098. height: 35px;
  3099. width: 100%;
  3100. color: #6e6e6e;
  3101. font-weight: 300;
  3102. font-size: 14px;
  3103. letter-spacing: 2px;
  3104. padding: 7px;
  3105. margin-bottom: 4px;
  3106. align-items: center;
  3107. -webkit-box-sizing: border-box;
  3108. -moz-box-sizing: border-box;
  3109. box-sizing: border-box;
  3110. }
  3111. input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
  3112. background: #41e49930;
  3113. border: 1px solid green;
  3114. color: green;
  3115. text-shadow: 0px 0px 0.5px green;
  3116. }
  3117. input[type="radio"]:disabled+.OJBetter_setting_menu_label_text {
  3118. background: #fafafa00;
  3119. border: 1px solid #e0e0e07a;
  3120. color: #e0e0e0;
  3121. }
  3122. .OJBetter_setting_menu label input[type="radio"], .OJBetter_contextmenu label input[type="radio"]{
  3123. appearance: none;
  3124. list-style: none;
  3125. padding: 0px !important;
  3126. margin: 0px;
  3127. clip: rect(0 0 0 0);
  3128. -webkit-clip-path: inset(100%);
  3129. clip-path: inset(100%);
  3130. height: 1px;
  3131. overflow: hidden;
  3132. position: absolute;
  3133. white-space: nowrap;
  3134. width: 1px;
  3135. }
  3136. /*设置面板-文本输入框*/
  3137. .OJBetter_setting_menu input[type="text"] {
  3138. display: block;
  3139. height: 25px !important;
  3140. width: 100%;
  3141. background-color: #ffffff;
  3142. color: #727378;
  3143. font-size: 12px;
  3144. border-radius: 0.3rem;
  3145. padding: 1px 5px !important;
  3146. box-sizing: border-box;
  3147. margin: 5px 0px 5px 0px;
  3148. border: 1px solid #00aeeccc;
  3149. box-shadow: 0 0 1px #0000004d;
  3150. }
  3151. .OJBetter_setting_menu .OJBetter_setting_list input[type="text"] {
  3152. margin-left: 5px;
  3153. }
  3154. .OJBetter_setting_menu input[type="text"]:focus-visible{
  3155. border-style: solid;
  3156. border-color: #3f51b5;
  3157. outline: none;
  3158. }
  3159. .OJBetter_setting_menu_config_box {
  3160. width: 100%;
  3161. display: grid;
  3162. margin-top: 5px;
  3163. -webkit-box-sizing: border-box;
  3164. -moz-box-sizing: border-box;
  3165. box-sizing: border-box;
  3166. }
  3167. .OJBetter_setting_menu input::placeholder {
  3168. color: #727378;
  3169. }
  3170. .OJBetter_setting_menu input.no_default::placeholder{
  3171. color: #BDBDBD;
  3172. }
  3173. .OJBetter_setting_menu input.is_null::placeholder{
  3174. color: red;
  3175. border-width: 1.5px;
  3176. }
  3177. .OJBetter_setting_menu input.is_null{
  3178. border-color: red;
  3179. }
  3180. .OJBetter_setting_menu textarea {
  3181. resize: vertical;
  3182. display: block;
  3183. width: 100%;
  3184. height: 60px;
  3185. background-color: #ffffff;
  3186. color: #727378;
  3187. font-size: 12px;
  3188. padding: 1px 5px !important;
  3189. box-sizing: border-box;
  3190. margin: 5px 0px 5px 0px;
  3191. border: 1px solid #00aeeccc;
  3192. box-shadow: 0 0 1px #0000004d;
  3193. }
  3194. .OJBetter_setting_menu textarea:focus-visible{
  3195. border-style: solid;
  3196. border-color: #3f51b5;
  3197. outline: none;
  3198. }
  3199. .OJBetter_setting_menu textarea::placeholder{
  3200. color: #BDBDBD;
  3201. font-size: 14px;
  3202. }
  3203. .OJBetter_setting_menu #tempConfig_save {
  3204. cursor: pointer;
  3205. display: inline-flex;
  3206. padding: 5px;
  3207. background-color: #1aa06d;
  3208. color: #ffffff;
  3209. font-size: 14px;
  3210. line-height: 1.5rem;
  3211. font-weight: 500;
  3212. justify-content: center;
  3213. width: 100%;
  3214. border-radius: 0.375rem;
  3215. border: none;
  3216. box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  3217. margin-top: 20px
  3218. }
  3219. .OJBetter_setting_menu button#debug_button.debug_button {
  3220. width: 18%;
  3221. }
  3222. .OJBetter_setting_menu span.tip {
  3223. color: #999;
  3224. font-size: 12px;
  3225. font-weight: 500;
  3226. padding: 5px 0px;
  3227. }
  3228. /*设置面板-tip*/
  3229. .help_tip {
  3230. margin-right: auto;
  3231. }
  3232. span.input_label {
  3233. font-size: 14px;
  3234. }
  3235. .help_tip .tip_text {
  3236. display: none;
  3237. position: absolute;
  3238. color: #697e91;
  3239. font-weight: 400;
  3240. font-size: 14px;
  3241. letter-spacing: 0px;
  3242. background-color: #ffffff;
  3243. padding: 10px;
  3244. margin: 5px 0px;
  3245. border-radius: 4px;
  3246. border: 1px solid #e4e7ed;
  3247. box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);
  3248. z-index: 100;
  3249. }
  3250. .help_tip .tip_text p {
  3251. margin-bottom: 5px;
  3252. }
  3253. .help_tip .tip_text:before {
  3254. content: "";
  3255. position: absolute;
  3256. top: -20px;
  3257. right: -10px;
  3258. bottom: -10px;
  3259. left: -10px;
  3260. z-index: -1;
  3261. }
  3262. .help-icon {
  3263. cursor: help;
  3264. width: 15px;
  3265. color: #b4b9d4;
  3266. margin-left: 5px;
  3267. margin-top: 3px;
  3268. }
  3269. .OJBetter_setting_menu .OJBetter_setting_menu_label_text .help_tip .help-icon {
  3270. color: #7fbeb2;
  3271. }
  3272. .help_tip .help-icon:hover + .tip_text, .help_tip .tip_text:hover {
  3273. display: block;
  3274. cursor: help;
  3275. width: 250px;
  3276. }
  3277. /* 版本信息 */
  3278. .OJBetter_setting_menu .versionInfo{
  3279. display: grid;
  3280. justify-items: center;
  3281. font-size: 16px;
  3282. padding: 10px;
  3283. }
  3284. .OJBetter_setting_menu .versionInfo>* {
  3285. margin: 10px 0px;
  3286. }
  3287.  
  3288. /* 配置管理面板 */
  3289. .config{
  3290. width: 100%;
  3291. margin: 10px 0px;
  3292. }
  3293. .config li.tempConfig_add_button {
  3294. cursor: pointer;
  3295. height: 40px;
  3296. border: 1px dashed #BDBDBD;
  3297. border-radius: 8px;
  3298. background-color: #fcfbfb36;
  3299. color: #bdbdbd;
  3300. font-size: 14px;
  3301. align-items: center;
  3302. justify-content: center;
  3303. }
  3304. .config li.tempConfig_add_button:hover {
  3305. border: 1px dashed #03A9F4;
  3306. background-color: #d7f0fb8c;
  3307. color: #03A9F4;
  3308. }
  3309. .config .config_bar_list {
  3310. display: flex;
  3311. width: 100%;
  3312. padding-bottom: 2px;
  3313. border: 1px solid #c5cae9;
  3314. background-color: #f0f8ff;
  3315. box-sizing: border-box;
  3316. border-radius: 0px 0px 8px 8px;
  3317. }
  3318. .config .config_bar_list input[type="radio"] {
  3319. appearance: none;
  3320. width: 0;
  3321. height: 0;
  3322. overflow: hidden;
  3323. }
  3324. .config .config_bar_list input[type="radio"] {
  3325. margin: 0px;
  3326. }
  3327. .config .config_bar_list input[type=radio]:focus {
  3328. outline: 0px;
  3329. }
  3330. .config .config_bar_ul_li_text {
  3331. display: flex;
  3332. align-items: center;
  3333. justify-content: center;
  3334. max-width: 100%;
  3335. height: 40px;
  3336. overflow-x: auto;
  3337. font-size: 14px;
  3338. font-weight: 400;
  3339. margin: 0px 4px;
  3340. padding: 3px;
  3341. border: 1px solid #dedede;
  3342. border-radius: 10px;
  3343. box-shadow: 0px 2px 4px 0px rgba(0,0,0,.05);
  3344. box-sizing: border-box;
  3345. }
  3346. .config .config_bar_ul li button {
  3347. background-color: #e6e6e6;
  3348. color: #727378;
  3349. height: 23px;
  3350. font-size: 14px;
  3351. border-radius: 0.3rem;
  3352. padding: 1px 5px;
  3353. margin: 5px;
  3354. border: none;
  3355. box-shadow: 0 0 1px #0000004d;
  3356. }
  3357. .config .config_bar_ul {
  3358. display: flex;
  3359. align-items: center;
  3360. list-style-type: none;
  3361. padding-inline-start: 0px;
  3362. overflow-x: auto;
  3363. max-width: 100%;
  3364. margin: 0px;
  3365. padding: 5px;
  3366. }
  3367. .config .config_bar_ul li {
  3368. width: 80px;
  3369. display: grid;
  3370. margin: 4px 4px;
  3371. min-width: 100px;
  3372. box-sizing: border-box;
  3373. }
  3374. .config .config_bar_ul_li_text:hover {
  3375. background-color: #eae4dc24;
  3376. }
  3377. input[type="radio"]:checked + .config_bar_ul_li_text {
  3378. background: #41b3e430;
  3379. border: 1px solid #5e7ce0;
  3380. color: #5e7ce0;
  3381. }
  3382. .config .config_bar_ul::-webkit-scrollbar {
  3383. width: 5px;
  3384. height: 4px;
  3385. }
  3386. .config .config_bar_ul::-webkit-scrollbar-thumb {
  3387. background-clip: padding-box;
  3388. background-color: #d7d9e4;
  3389. border-radius: 8px;
  3390. }
  3391. .config .config_bar_ul::-webkit-scrollbar-button:start:decrement {
  3392. width: 4px;
  3393. background-color: transparent;
  3394. }
  3395. .config .config_bar_ul::-webkit-scrollbar-button:end:increment {
  3396. width: 4px;
  3397. background-color: transparent;
  3398. }
  3399. .config .config_bar_ul::-webkit-scrollbar-track {
  3400. border-radius: 5px;
  3401. }
  3402. .config .config_bar_ul_li_text::-webkit-scrollbar {
  3403. width: 5px;
  3404. height: 7px;
  3405. background-color: #aaa;
  3406. }
  3407. .config .config_bar_ul_li_text::-webkit-scrollbar-thumb {
  3408. background-clip: padding-box;
  3409. background-color: #d7d9e4;
  3410. }
  3411. .config .config_bar_ul_li_text::-webkit-scrollbar-track {
  3412. background-color: #f1f1f1;
  3413. }
  3414. .config .config_bar_list_add_div {
  3415. display: flex;
  3416. height: 40px;
  3417. margin: 4px 2px;
  3418. }
  3419.  
  3420. /* 修改菜单 */
  3421. #config_bar_menu {
  3422. z-index: 400;
  3423. position: fixed;
  3424. width: 60px;
  3425. background: #ffffff;
  3426. box-shadow: 1px 1px 4px 0px #0000004d;
  3427. border: 0px solid rgba(0,0,0,0.04);
  3428. border-radius: 4px;
  3429. padding: 8px 0;
  3430. }
  3431. .config_bar_menu_item {
  3432. cursor: pointer;
  3433. padding: 2px 6px;
  3434. display: flex;
  3435. justify-content: center;
  3436. align-items: center;
  3437. height: 32px;
  3438. font-size: 14px;
  3439. font-weight: 500;
  3440. box-shadow: inset 0px 0px 0px 0px #8bb2d9;
  3441. }
  3442. #config_bar_menu_edit:hover {
  3443. background-color: #00aeec;
  3444. color: white;
  3445. }
  3446. #config_bar_menu_delete:hover {
  3447. background-color: #FF5722;
  3448. color: white;
  3449. }
  3450.  
  3451. /* 配置编辑页面 */
  3452. #config_edit_menu {
  3453. z-index: 300;
  3454. width: 450px;
  3455. }
  3456.  
  3457. /* 黑暗模式选项按钮 */
  3458. .dark-mode-selection {
  3459. display: flex;
  3460. justify-content: center;
  3461. align-items: center;
  3462. max-width: 350px;
  3463. -webkit-user-select: none;
  3464. -moz-user-select: none;
  3465. -ms-user-select: none;
  3466. user-select: none;
  3467. }
  3468. .dark-mode-selection label {
  3469. margin: 8px 0px 8px 8px;
  3470. }
  3471. .dark-mode-selection > * {
  3472. margin: 6px;
  3473. }
  3474. .dark-mode-selection .OJBetter_setting_menu_label_text {
  3475. border-radius: 8px;
  3476. margin-bottom: 0px;
  3477. }
  3478.  
  3479. /*确认弹窗*/
  3480. .OJBetter_modal {
  3481. z-index: 600;
  3482. display: grid;
  3483. position: fixed;
  3484. top: 50%;
  3485. left: 50%;
  3486. transform: translate(-50%, -50%);
  3487. font-size: 12px;
  3488. font-family: var(--vp-font-family-base);
  3489. width: max-content;
  3490. padding: 10px 20px;
  3491. box-shadow: 0px 0px 0px 4px #ffffff;
  3492. border-radius: 6px;
  3493. background-color: #f0f4f9;
  3494. border-collapse: collapse;
  3495. border: 1px solid #ffffff;
  3496. color: #697e91;
  3497. }
  3498. .OJBetter_modal h2 {
  3499. font-size: 1.6em;
  3500. font-weight: 700;
  3501. }
  3502. .OJBetter_modal .content{
  3503. white-space: nowrap;
  3504. max-height: 500px;
  3505. overflow-y: auto;
  3506. }
  3507. .OJBetter_modal .buttons{
  3508. display: flex;
  3509. padding-top: 15px;
  3510. }
  3511. .OJBetter_modal button {
  3512. display: inline-flex;
  3513. justify-content: center;
  3514. align-items: center;
  3515. line-height: 1;
  3516. white-space: nowrap;
  3517. cursor: pointer;
  3518. text-align: center;
  3519. box-sizing: border-box;
  3520. outline: none;
  3521. transition: .1s;
  3522. user-select: none;
  3523. vertical-align: middle;
  3524. -webkit-appearance: none;
  3525. height: 24px;
  3526. padding: 5px 11px;
  3527. margin-right: 15px;
  3528. font-size: 12px;
  3529. border-radius: 4px;
  3530. color: #ffffff;
  3531. background: #009688;
  3532. border-color: #009688;
  3533. border: none;
  3534. }
  3535. .OJBetter_modal button.secondary{
  3536. background-color:#4DB6AC;
  3537. }
  3538. .OJBetter_modal button:hover{
  3539. background-color:#4DB6AC;
  3540. }
  3541. .OJBetter_modal button.secondary:hover {
  3542. background-color: #80CBC4;
  3543. }
  3544. .OJBetter_modal .help-icon {
  3545. margin: 0px 8px 0px 0px;
  3546. height: 1em;
  3547. width: 1em;
  3548. line-height: 1em;
  3549. display: inline-flex;
  3550. justify-content: center;
  3551. align-items: center;
  3552. position: relative;
  3553. fill: currentColor;
  3554. font-size: inherit;
  3555. }
  3556. .OJBetter_modal p {
  3557. margin: 5px 0px;
  3558. }
  3559.  
  3560. /* 右键菜单 */
  3561. .OJBetter_contextmenu {
  3562. z-index: 500;
  3563. display: grid;
  3564. position: absolute;
  3565. background-color: #f0f4f9;
  3566. border-collapse: collapse;
  3567. color: #697e91;
  3568. font-family: var(--vp-font-family-base);
  3569. overflow: hidden;
  3570. box-sizing: content-box;
  3571. box-shadow: 0px 0px 0px 2px #eddbdb4d;
  3572. }
  3573. .OJBetter_contextmenu label {
  3574. margin: 0px;
  3575. }
  3576. input[type="radio"]:checked+.OJBetter_contextmenu_label_text {
  3577. background: #41e49930;
  3578. border: 1px solid green;
  3579. color: green;
  3580. font-weight: 500;
  3581. }
  3582. .OJBetter_contextmenu_label_text {
  3583. display: flex;
  3584. border: 1px dashed #80cbc4;
  3585. height: 26px;
  3586. width: 100%;
  3587. color: gray;
  3588. font-size: 13px;
  3589. font-weight: initial;
  3590. padding: 4px;
  3591. align-items: center;
  3592. -webkit-box-sizing: border-box;
  3593. -moz-box-sizing: border-box;
  3594. box-sizing: border-box;
  3595. }
  3596. .OJBetter_contextmenu_label_text:hover {
  3597. color: #F44336;
  3598. border: 1px dashed #009688;
  3599. background-color: #ffebcd;
  3600. }
  3601.  
  3602. /* RatingByClist */
  3603. .ratingBadge, html[data-theme=dark] button.ratingBadge{
  3604. display: block;
  3605. font-weight: 700;
  3606. font-size: 11px;
  3607. margin-top: 5px;
  3608. padding: 2px;
  3609. border-radius: 4px;
  3610. color: #B0BEC5;
  3611. border: 1px solid #cccccc66;
  3612. }
  3613.  
  3614. /* 多选翻译 */
  3615. .block_selected{
  3616. box-shadow: 0px 0px 0px 1px #FF9800;
  3617. outline: none;
  3618. }
  3619.  
  3620. /* 悬浮菜单 */
  3621. .OJBetter_MiniTranslateButton {
  3622. z-index: 100;
  3623. display: grid;
  3624. position: absolute;
  3625. border-collapse: collapse;
  3626. fill: #F57C00;
  3627. background-color: #FFF3E0;
  3628. overflow: hidden;
  3629. box-sizing: content-box;
  3630. box-shadow: 0px 0px 0px 2px #FFE0B2;
  3631. border-radius: 100%;
  3632. }
  3633. .OJBetter_MiniTranslateButton:hover {
  3634. cursor: pointer;
  3635. box-shadow: 0px 0px 0px 2px #FFB74D;
  3636. }
  3637.  
  3638. /* acmsguru划分块 */
  3639. .OJBetter_acmsguru {
  3640. margin: 0 0 1em!important;
  3641. }
  3642.  
  3643. /* 代码提交表单 */
  3644. #OJBetter_SubmitForm.input-output-copier:hover {
  3645. background-color: #ffffff00;
  3646. }
  3647. #OJBetter_SubmitForm input[type="number"] {
  3648. width: 40px;
  3649. color: #009688;
  3650. appearance: none;
  3651. border-radius: 6px;
  3652. border-style: solid;
  3653. border: none;
  3654. background-color: #ffffff00;
  3655. }
  3656. #OJBetter_SubmitForm :focus-visible {
  3657. outline: none;
  3658. }
  3659. #OJBetter_SubmitForm .topDiv {
  3660. display: flex;
  3661. align-items: center;
  3662. justify-content: space-between;
  3663. padding: 10px 0px;
  3664. box-sizing: border-box;
  3665. }
  3666. #OJBetter_SubmitForm .topDiv .topRightDiv {
  3667. height: 100%;
  3668. display: flex;
  3669. flex-wrap: wrap;
  3670. gap: 5px;
  3671. }
  3672. #OJBetter_SubmitForm input[type="checkbox"], #OJBetter_SubmitForm label {
  3673. margin: 0px;
  3674. font-weight: initial;
  3675. }
  3676. /* 顶部右侧区域 */
  3677. #OJBetter_SubmitForm .topRightDiv>* {
  3678. height: 30px;
  3679. box-sizing: border-box;
  3680. }
  3681. #OJBetter_SubmitForm .topRightDiv>button{
  3682. padding: 0px 8px;
  3683. }
  3684. #OJBetter_SubmitForm .topRightDiv {
  3685. display: flex;
  3686. flex-wrap: wrap;
  3687. gap: 0px;
  3688. align-items: center;
  3689. }
  3690. #OJBetter_SubmitForm input[type="checkbox"], #OJBetter_SubmitForm label {
  3691. margin: 0px;
  3692. font-weight: initial;
  3693. }
  3694.  
  3695. /* LSP连接Log */
  3696. #LSPLog{
  3697. width: 500px;
  3698. height: 500px;
  3699. position: fixed;
  3700. top: 50%;
  3701. left: 50%;
  3702. padding: 10px;
  3703. transform: translate(-50%, -50%);
  3704. border: 1px solid;
  3705. z-index: 200;
  3706. background-color: #ffffff;
  3707. }
  3708. #LSPLog button{
  3709. position: fixed;
  3710. top: 10px;
  3711. right: 10px;
  3712. z-index: 200;
  3713. }
  3714. #LSPLog #LSPLogList{
  3715. width: 500px;
  3716. height: 500px;
  3717. overflow: auto;
  3718. color: #424242;
  3719. }
  3720. #LSPLog li:nth-child(odd){
  3721. background-color: #f5f5f5;
  3722. }
  3723. #LSPLog details{
  3724. padding: 2px;
  3725. }
  3726.  
  3727. /* 代码编辑器 */
  3728. #OJBetter_editor{
  3729. box-sizing: border-box;
  3730. height: 600px;
  3731. border: 1px solid #d3d3d3;
  3732. width: 100%;
  3733. resize: vertical;
  3734. display: flex;
  3735. flex-direction: column;
  3736. }
  3737. #OJBetter_editor.fullscreen{
  3738. position: fixed;
  3739. top: 0;
  3740. left: 0;
  3741. width: 100%;
  3742. height: 100vh;
  3743. z-index: 2000;
  3744. }
  3745. #OJBetter_editor.bottom{
  3746. position: fixed;
  3747. bottom: 0;
  3748. left: 0;
  3749. width: 100%;
  3750. height: 50vh;
  3751. z-index: 2000;
  3752. }
  3753. .ojb_btn.exit_button_bottom {
  3754. position: fixed;
  3755. bottom: 30px;
  3756. right: 15px;
  3757. z-index: 2000;
  3758. height: 28px;
  3759. }
  3760.  
  3761. /* monaco */
  3762. #OJBetter_monaco {
  3763. flex: 1;
  3764. min-height: 0;
  3765. width: 100%;
  3766. }
  3767. #OJBetter_monaco .highlight {
  3768. border: 1px solid #ffffff00;
  3769. background-color: #ffffff00!important
  3770. }
  3771. .monaco-hover hr {
  3772. margin: 4px -8px 4px !important;
  3773. }
  3774.  
  3775. /* 状态底栏 */
  3776. #OJBetter_statusBar{
  3777. height: 22px;
  3778. font-size: 12px;
  3779. color: #757575;
  3780. border: 1px solid #d3d3d3;
  3781. background-color: #f8f8f8;
  3782. padding: 3px;
  3783. box-sizing: border-box;
  3784. }
  3785.  
  3786. /* 提交 */
  3787. #OJBetter_submitDiv{
  3788. display: flex;
  3789. gap: 5px;
  3790. padding-top: 15px;
  3791. height: 50px;
  3792. box-sizing: border-box;
  3793. }
  3794. #OJBetter_submitDiv >* {
  3795. border-radius: 6px;
  3796. }
  3797. #OJBetter_submitDiv > button {
  3798. height: 100%;
  3799. aspect-ratio: 1 / 1;
  3800. }
  3801. #SubmitButton {
  3802. color: #fff;
  3803. background-color: #209978;
  3804. border-color: #17795E;
  3805. }
  3806. #SubmitButton:hover {
  3807. background-color: #17795e;
  3808. }
  3809. #SubmitButton.disabled {
  3810. background-color: red;
  3811. animation: shake 0.07s infinite alternate;
  3812. }
  3813. .topLeftDiv > select {
  3814. height: 100%;
  3815. padding: 5px 10px;
  3816. border-radius: 6px;
  3817. border-style: solid;
  3818. border: 1px solid #ced4da;
  3819. color: #212529;
  3820. }
  3821.  
  3822. /* 调试 */
  3823. .OJBetter_loding{
  3824. padding: 6px 0px 0px 5px;
  3825. height: 22px;
  3826. }
  3827. #CompilerArgsInput{
  3828. flex-grow: 1;
  3829. width: 100%;
  3830. height: 100%;
  3831. margin-bottom: 10px;
  3832. padding: 5px 10px;
  3833. border-radius: 6px;
  3834. box-sizing: border-box;
  3835. border: 1px solid #ccc;
  3836. box-shadow: inset 0px 1px 1px rgba(0,0,0,.075);
  3837. }
  3838. #CompilerArgsInput[disabled] {
  3839. cursor: not-allowed;
  3840. }
  3841. #CompilerSetting{
  3842. font-size: 14px;
  3843. margin-top: 10px;
  3844. display: none;
  3845. }
  3846. #CompilerSetting select, #CompilerSetting textarea{
  3847. padding: 4px 10px;
  3848. border-radius: 6px;
  3849. border-style: solid;
  3850. border: 1px solid #ced4da;
  3851. color: #212529;
  3852. }
  3853. #CompilerBox{
  3854. display: grid;
  3855. margin-top: 10px;
  3856. border: #d0d7de solid 1px;
  3857. border-radius: 6px;
  3858. }
  3859. #CompilerBox > * {
  3860. margin: 5px;
  3861. }
  3862.  
  3863. /* 自定义样例 */
  3864. #customTestBlock {
  3865. margin-top: 10px;
  3866. font-size: 14px;
  3867. color: #616161;
  3868. border: 1px solid #d3d3d3;
  3869. box-sizing: border-box;
  3870. position: relative;
  3871. }
  3872. #customTestBlock #customTests{
  3873. border-top: 1px solid #d3d3d3;
  3874. margin: 0px 0px 40px 0px;
  3875. }
  3876. #customTestBlock summary {
  3877. cursor: pointer;
  3878. padding: 10px;
  3879. }
  3880. #customTestBlock textarea {
  3881. resize: vertical;
  3882. }
  3883. .sampleDiv {
  3884. color: #727378;
  3885. background-color: #FAFAFA;
  3886. padding: 5px;
  3887. margin-bottom: 10px;
  3888. box-shadow: inset 0 0 1px #0000004d;
  3889. position: relative;
  3890. }
  3891. .dynamicTextarea {
  3892. width: 98%;
  3893. height: 120px;
  3894. margin: 10px 5px;
  3895. border: 1px solid #E0E0E0;
  3896. }
  3897. .deleteCustomTest {
  3898. cursor: pointer;
  3899. position: absolute;
  3900. top: 5px;
  3901. right: 5px;
  3902. display: flex;
  3903. fill: #9E9E9E;
  3904. padding: 2px 2px;
  3905. border-radius: 4px;
  3906. border: 1px solid #ffffff00;
  3907. background-color: #ffffff00;
  3908. align-items: center;
  3909. }
  3910. .deleteCustomTest:hover {
  3911. fill: #EF5350;
  3912. border: 1px solid #ef9a9a;
  3913. background-color: #FFEBEE;
  3914. }
  3915. #addCustomTest {
  3916. cursor: pointer;
  3917. position: absolute;
  3918. bottom: 5px;
  3919. right: 5px;
  3920. padding: 3px 10px;
  3921. color: #795548;
  3922. border: 1px solid #ccc;
  3923. border-radius: 4px;
  3924. background-color: #FAFAFA;
  3925. }
  3926. #addCustomTest:hover {
  3927. background-color: #f5f5f5;
  3928. }
  3929.  
  3930. /* 调试结果 */
  3931. #statePanel{
  3932. display: none;
  3933. padding: 5px;
  3934. margin-top: 10px;
  3935. border: 1px solid #ddd;
  3936. border-radius: 4px;
  3937. }
  3938. .test-case {
  3939. padding: 10px;
  3940. border: 1px solid #ddd;
  3941. border-radius: 4px;
  3942. }
  3943. .test-case:not(:first-child){
  3944. margin-top: 5px;
  3945. }
  3946. .test-case > * {
  3947. margin: 5px 0px;
  3948. }
  3949. .test-case > :first-child {
  3950. margin-top: 0px;
  3951. }
  3952. .test-case > :last-child {
  3953. margin-bottom: 0px;
  3954. }
  3955. .test-case-title, .test-case-status {
  3956. font-size: 16px;
  3957. display: inline;
  3958. }
  3959. .test-case-status{
  3960. margin-left: 5px;
  3961. }
  3962. .test-case-status.error{
  3963. color: red;
  3964. }
  3965. .test-case-status.success{
  3966. color: #449d44;
  3967. }
  3968. .test-case-judge, .judge-checker {
  3969. font-size: 13px;
  3970. }
  3971.  
  3972. /* 差异对比 */
  3973. .output_diff {
  3974. color: #5d4037;
  3975. margin: 5px 0px;
  3976. display: grid;
  3977. border: 1px solid #bcaaa4;
  3978. font-size: 13px;
  3979. font-family: Consolas, "Lucida Console", "Andale Mono", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
  3980. overflow: auto;
  3981. }
  3982. .output_diff .added {
  3983. background-color: #c8f7c5;
  3984. user-select: none;
  3985. }
  3986. .output_diff .removed {
  3987. background-color: #f7c5c5;
  3988. }
  3989. .output_diff .diffLine {
  3990. display: flex;
  3991. }
  3992. .output_diff .diffLine:nth-child(odd) {
  3993. background-color: #f5f5f5;
  3994. }
  3995. .lineNo {
  3996. display: flex;
  3997. align-items: center;
  3998. justify-content: center;
  3999. width: 17px;
  4000. color: #BDBDBD;
  4001. font-size: 10px;
  4002. border-right: 1px solid;
  4003. user-select: none;
  4004. }
  4005. .lineContent {
  4006. display: grid;
  4007. width: 100%;
  4008. }
  4009. .lineContent>span {
  4010. height: 16px;
  4011. padding-left: 3px;
  4012. }
  4013. .output_no_diff {
  4014. padding: 5px;
  4015. border: 1px solid #ddd;
  4016. }
  4017. .diff_note {
  4018. font-size: 10px;
  4019. }
  4020.  
  4021. /* 网站本地化替换规则标记 */
  4022. .markingTextReplaceRule{
  4023. color: #FFF3E0;
  4024. background-color: #FF9800;
  4025. }
  4026.  
  4027. /* SelectPage样式 */
  4028. .sp_input {
  4029. padding: 4px 6px px !important;
  4030. height: 20px !important;
  4031. min-height: 20px !important;
  4032. line-height: 20px !important;
  4033. }
  4034. div.sp_clear_btn {
  4035. padding: 0px !important;
  4036. }
  4037.  
  4038. /* 题目问题标签hover隐藏 */
  4039. .hover-reveal {
  4040. opacity: 0;
  4041. transition: opacity 0.5s;
  4042. }
  4043. .hover-reveal:hover {
  4044. opacity: 1;
  4045. }
  4046.  
  4047.  
  4048. /* 移动设备 */
  4049. @media (max-device-width: 450px) {
  4050. .ojb_btn{
  4051. height: 2em;
  4052. font-size: 1.2em;
  4053. }
  4054. .ojb_btn.OJBetter_setting{
  4055. height: 2.5em;
  4056. font-size: 1em;
  4057. }
  4058. .OJBetter_setting_menu{
  4059. width: 90%;
  4060. }
  4061. .OJBetter_setting_menu label, #darkMode_span, #loaded_span, .OJBetter_setting_menu_label_text,
  4062. .OJBetter_setting_sidebar li{
  4063. font-size: 1em;
  4064. }
  4065. .translate-problem-statement{
  4066. font-size: 1.2em;
  4067. }
  4068. .OJBetter_modal{
  4069. font-size: 1.5em;
  4070. }
  4071. .OJBetter_setting_list, .translate-problem-statement{
  4072. padding: 0.5em;
  4073. }
  4074. .OJBetter_setting_menu_label_text{
  4075. height: 2.5em;
  4076. padding: 0.5em;
  4077. }
  4078. #pagBar #jump-input, #pagBar #items-per-page, .OJBetter_modal button{
  4079. height: 2.5em;
  4080. font-size: 1em;
  4081. }
  4082. .translate-problem-statement p, .translate-problem-statement ul li{
  4083. line-height: 1.5em !important;
  4084. }
  4085. .OJBetter_contextmenu_label_text{
  4086. height: 3em;
  4087. font-size: 1em;
  4088. }
  4089. }
  4090.  
  4091. /* 覆盖网站原本的样式 */
  4092. #footer > div:nth-child(7) {
  4093. left: 0px !important;
  4094. }
  4095. `);
  4096.  
  4097. /**
  4098. * 添加一些依赖库和条件加载的css样式
  4099. */
  4100. function addDependencyStyles() {
  4101. GM_addStyle(GM_getResourceText("xtermcss"));
  4102. GM_addStyle(GM_getResourceText("selectpagecss"));
  4103. GM_addStyle(GM_getResourceText("dialogpolyfillcss"));
  4104. // 自定义图标大小
  4105. GM_addStyle(`
  4106. .iconfont {
  4107. font-size: ${OJBetter.preference.iconButtonSize}px;
  4108. }
  4109. `);
  4110. }
  4111.  
  4112. /**
  4113. * 添加包含i18n内容的css样式
  4114. */
  4115. function addI18nStyles() {
  4116. GM_addStyle(`
  4117. /* 加载鼠标悬浮覆盖层css */
  4118. .ojb-overlay::before {
  4119. content: '';
  4120. position: absolute;
  4121. top: 0;
  4122. left: 0;
  4123. width: 100%;
  4124. height: 100%;
  4125. background: repeating-linear-gradient(135deg, rgb(77 208 225 / 30%), rgb(77 208 225 / 30%) 30px, rgb(77 208 225 / 10%) 0px, rgb(77 208 225 / 10%) 55px);
  4126. z-index: 100;
  4127. }
  4128. .ojb-overlay::after {
  4129. content: '${i18next.t("targetArea", { ns: "common" })}';
  4130. position: absolute;
  4131. top: 50%;
  4132. left: 50%;
  4133. transform: translate(-50%, -50%);
  4134. color: #00695C;
  4135. font-size: 16px;
  4136. font-weight: bold;
  4137. z-index: 100;
  4138. }
  4139.  
  4140. .config::before {
  4141. content: "${i18next.t("common.configManageTitle", { ns: "settings" })}";
  4142. display: block;
  4143. height: 20px;
  4144. background-color: #f0f8ff;
  4145. border: 1px solid #c5cae9;
  4146. border-bottom: 0px;
  4147. line-height: 20px;
  4148. padding: 2px 10px;
  4149. border-radius: 8px 8px 0px 0px;
  4150. box-sizing: content-box;
  4151. }
  4152. .config.missing::before {
  4153. content: "${i18next.t("common.missing.radio", { ns: "settings" })}";
  4154. background-color: #fef0f0;
  4155. color: #f56c6c;
  4156. border: 1px solid #fab6b6;
  4157. }
  4158. `);
  4159. }
  4160.  
  4161. // ------------------------------
  4162. // 一些工具类
  4163. // ------------------------------
  4164.  
  4165. /**
  4166. * 自定义错误类,以区分不同的错误类型
  4167. */
  4168. class OJB_GMError extends Error {
  4169. constructor(type, message, originalError) {
  4170. super(message);
  4171. this.name = "GMError";
  4172. this.type = type;
  4173. this.stack = originalError.stack;
  4174. Object.assign(this, originalError);
  4175. }
  4176. }
  4177.  
  4178. /**
  4179. * 文本块替换/恢复类
  4180. */
  4181. class TextBlockReplacer {
  4182. constructor() {
  4183. /** @type {string[]} 匹配项 */
  4184. this.matches = [];
  4185. /** @type {Map<string, string>} 待还原项 */
  4186. this.replacements = new Map();
  4187. /** @type {Map<string, string>} 暂时未找到的待还原项 */
  4188. this.tempReplacements = new Map();
  4189. /** @type {string} 替换符号 */
  4190. this.replaceSymbol = OJBetter.translation.replaceSymbol;
  4191. }
  4192.  
  4193. /**
  4194. * 替换文本
  4195. * @param {string} text 原文本
  4196. * @param {RegExp} regex 匹配规则
  4197. * @returns {string} 替换后的文本
  4198. */
  4199. replace(text, regex) {
  4200. // 优化序数词翻译
  4201. let isOrdinal = (text) => {
  4202. return Boolean(text.match(/\$\d+?\$[(st)(nd)(rd)(th)]/g));
  4203. };
  4204. let ordinalTranslation = (text) => {
  4205. return "第 " + text.match(/\$\d+?\$/g)[0] + " 个";
  4206. };
  4207.  
  4208. this.matches = text.match(regex) || [];
  4209. try {
  4210. for (let i = 0; i < this.matches.length; i++) {
  4211. const match = this.matches[i];
  4212. const id = OJB_getRandomNumber(8);
  4213. let replacement = "";
  4214. switch (this.replaceSymbol) {
  4215. case "1":
  4216. replacement = `【${id}】`;
  4217. break;
  4218. case "2":
  4219. replacement = `{${id}}`;
  4220. break;
  4221. case "3":
  4222. replacement = `[${id}]`;
  4223. break;
  4224. default:
  4225. replacement = `【${id}】`;
  4226. break;
  4227. }
  4228. text = text.replace(match, replacement);
  4229. if (isOrdinal(match) && OJBetter.translation.targetLang === "zh")
  4230. this.replacements.set(id, ordinalTranslation(match));
  4231. else this.replacements.set(id, match);
  4232. }
  4233. } catch (e) {}
  4234. return text;
  4235. }
  4236.  
  4237. /**
  4238. * 恢复替换的文本
  4239. * @param {string} text 还原前的文本
  4240. * @returns {string} 还原后的文本
  4241. */
  4242. recover(text) {
  4243. let textCopy = text;
  4244.  
  4245. /**
  4246. * 替换回文本
  4247. * @param {string} replacement 替换的文本
  4248. * @param {string} regexPattern 匹配规则
  4249. * @returns {void}
  4250. */
  4251. const replaceText = (replacement, regexPattern) => {
  4252. const latexMatch =
  4253. "(?<latex_block>\\$\\$(\\\\\\$|[^\\$])*?\\$\\$)|(?<latex_inline>\\$(\\\\\\$|[^\\$])*?\\$)|";
  4254. const regex = new RegExp(latexMatch + regexPattern, "g");
  4255. textCopy = textCopy.replace(regex, (match, ...args) => {
  4256. // LaTeX中的不替换
  4257. const groups = args[args.length - 1]; // groups是replace方法的最后一个参数
  4258. if (groups.latex_block || groups.latex_inline) return match;
  4259. // 没有空格则加一个
  4260. const offset = args[args.length - 3]; // offset是replace方法的倒数第三个参数
  4261. let leftSpace = "",
  4262. rightSpace = "";
  4263. if (!/\s/.test(textCopy[offset - 1])) leftSpace = " ";
  4264. if (
  4265. !/\s/.test(textCopy[offset + match.length]) &&
  4266. /[\x20-\x7E]/.test(replacement[replacement.length - 1])
  4267. )
  4268. rightSpace = " ";
  4269. return leftSpace + replacement + rightSpace;
  4270. });
  4271. };
  4272.  
  4273. /**
  4274. * 尝试还原
  4275. * @param {string} replacement 替换的文本
  4276. * @param {string} id 替换的 id
  4277. * @returns {boolean} 是否替换成功
  4278. */
  4279. const tryRecover = (replacement, id) => {
  4280. // 尝试还原,如果还原成功,则从 replacements 中删除
  4281. const originalText = textCopy;
  4282. replaceText(
  4283. replacement,
  4284. `【\\s*${id}\\s*】|\\[\\s*${id}\\s*\\]|{\\s*${id}\\s*}`
  4285. ); // 替换符完整匹配(考虑了多出空格的情况)
  4286. replaceText(
  4287. replacement,
  4288. `【\\s*${id}(?![】\\d])|(?<![【\\d])${id}\\s*】|\\[\\s*${id}(?![\\]\\d])|(?<![\\[\\d])${id}\\s*\\]|{\\s*${id}(?![}\\d])|(?<![{\\d])${id}\\s*}`
  4289. ); // 替换符部分匹配
  4290.  
  4291. if (textCopy === originalText) {
  4292. // 如果文本没有变化,说明没有找到,加入到 tempReplacements
  4293. this.tempReplacements.set(id, replacement);
  4294. return false;
  4295. } else {
  4296. // 如果文本变化了,说明找到并成功替换,则删除
  4297. this.replacements.delete(id);
  4298. this.tempReplacements.delete(id);
  4299. return true;
  4300. }
  4301. };
  4302.  
  4303. // 处理 replacements 中的项
  4304. this.replacements.forEach((replacement, id) => {
  4305. tryRecover(replacement, id);
  4306. });
  4307.  
  4308. // 处理 tempReplacements 中的项
  4309. while (this.tempReplacements.size > 0) {
  4310. let found = false;
  4311. this.tempReplacements.forEach((replacement, id) => {
  4312. found = tryRecover(replacement, id) || found;
  4313. });
  4314. if (!found) break; // 如果这一轮没有找到任何项,终止循环
  4315. }
  4316.  
  4317. // 如果 tempReplacements 还有未找到的项
  4318. if (this.tempReplacements.size > 0) {
  4319. console.warn(
  4320. "There are still some replacements not found:",
  4321. this.tempReplacements
  4322. );
  4323. }
  4324.  
  4325. return textCopy;
  4326. }
  4327. }
  4328.  
  4329. // ------------------------------
  4330. // 一些工具函数
  4331. // ------------------------------
  4332.  
  4333. /**
  4334. * 格式化链接格式
  4335. * @param {string} url 链接字符串
  4336. * @returns {string} 清理后的链接字符串
  4337. */
  4338. function OJB_cleanLink(url) {
  4339. if (url === null || url === undefined) return "";
  4340.  
  4341. // 替换'http://'为'https://'
  4342. let cleanUrl = url.replace(/^http:\/\//i, "https://");
  4343.  
  4344. // 移除末尾的斜杠
  4345. cleanUrl = cleanUrl.replace(/\/$/, "");
  4346.  
  4347. return cleanUrl;
  4348. }
  4349.  
  4350. /**
  4351. * 深度比较两个对象或数组是否完全相等。
  4352. * @param {any} a - 第一个比较对象。
  4353. * @param {any} b - 第二个比较对象。
  4354. * @returns {boolean} - 如果两个对象或数组深度相等,则返回true,否则返回false。
  4355. */
  4356. function OJB_deepEquals(a, b) {
  4357. if (a === b) return true;
  4358. if (
  4359. typeof a !== "object" ||
  4360. a === null ||
  4361. typeof b !== "object" ||
  4362. b === null
  4363. )
  4364. return false;
  4365. const keysA = Object.keys(a);
  4366. const keysB = Object.keys(b);
  4367. if (keysA.length !== keysB.length) return false;
  4368. for (let key of keysA) {
  4369. if (!b.hasOwnProperty(key)) return false;
  4370. if (!OJB_deepEquals(a[key], b[key])) return false;
  4371. }
  4372. return true;
  4373. }
  4374.  
  4375. /**
  4376. * 用于封装需要重试的异步函数
  4377. * @param {Function} task 需要封装的异步函数
  4378. * @param {Object} options 配置项
  4379. * @param {Number} options.maxRetries 重试次数,默认为 5
  4380. * @param {Number} options.retryInterval 重试时间间隔,默认为 0 毫秒
  4381. * @param {Function} options.errorHandler 错误处理函数,默认为抛出错误
  4382. * @param {...any} args task 函数的参数
  4383. * @returns {Promise} 返回 Promise
  4384. */
  4385. async function OJB_promiseRetryWrapper(
  4386. task,
  4387. {
  4388. maxRetries = 5,
  4389. retryInterval = 0,
  4390. errorHandler = (err) => {
  4391. throw err;
  4392. },
  4393. } = {},
  4394. ...args
  4395. ) {
  4396. let attemptsLeft = maxRetries;
  4397. while (attemptsLeft--) {
  4398. try {
  4399. return await task(...args);
  4400. } catch (err) {
  4401. if (attemptsLeft <= 0) {
  4402. return errorHandler(err, maxRetries, attemptsLeft);
  4403. }
  4404. if (retryInterval > 0) {
  4405. await OJB_delay(retryInterval);
  4406. }
  4407. }
  4408. }
  4409. }
  4410.  
  4411. /**
  4412. * GM_xmlhttpRequest 的 Promise 封装
  4413. * @param {Object} options GM_xmlhttpRequest 的参数
  4414. * @param {Boolean} isStream 是否为流式请求
  4415. * @returns {Promise<OJB_GMError>} 返回 Promise
  4416. */
  4417. function OJB_GMRequest(options, isStream = false) {
  4418. return new Promise((resolve, reject) => {
  4419. GM_xmlhttpRequest({
  4420. ...options,
  4421. ...(isStream
  4422. ? {
  4423. onloadstart: resolve,
  4424. }
  4425. : {
  4426. onload: resolve,
  4427. }),
  4428. onerror: (error) =>
  4429. reject(
  4430. new OJB_GMError(
  4431. "error",
  4432. "An error occurred during the request.",
  4433. error
  4434. )
  4435. ),
  4436. ontimeout: (error) =>
  4437. reject(new OJB_GMError("timeout", "The request timed out.", error)),
  4438. onabort: (error) =>
  4439. reject(new OJB_GMError("abort", "The request was aborted.", error)),
  4440. });
  4441. });
  4442. }
  4443.  
  4444. /**
  4445. * 获取cookie
  4446. * @param {string} name cookie名称
  4447. * @returns {string} cookie值
  4448. */
  4449. function OJB_getCookie(name) {
  4450. const cookies = document.cookie.split(";");
  4451. for (let i = 0; i < cookies.length; i++) {
  4452. const cookie = cookies[i].trim();
  4453. const [cookieName, cookieValue] = cookie.split("=");
  4454.  
  4455. if (cookieName === name) {
  4456. return decodeURIComponent(cookieValue);
  4457. }
  4458. }
  4459. return "";
  4460. }
  4461.  
  4462. /**
  4463. * 检查是否仍在同一浏览器会话中
  4464. * @param {string} sessionKey - 会话键名,用于标识会话
  4465. * @returns {boolean} - 如果在当前会话中之前已经设置过这个键,则返回true,否则返回false
  4466. */
  4467. function OJB_isSameBrowserSession(sessionKey) {
  4468. const fullCookieName = `OJB_Session_${sessionKey}`;
  4469. const sessionValue = OJB_getCookie(fullCookieName);
  4470. if (sessionValue === "") {
  4471. document.cookie = `${fullCookieName}=true; path=/`;
  4472. return false;
  4473. }
  4474. return true;
  4475. }
  4476.  
  4477. /**
  4478. * 随机数生成
  4479. * @param {number} numDigits 位数
  4480. * @returns {number} 一个随机数
  4481. */
  4482. function OJB_getRandomNumber(numDigits) {
  4483. let min = Math.pow(10, numDigits - 1);
  4484. let max = Math.pow(10, numDigits) - 1;
  4485. return Math.floor(Math.random() * (max - min + 1)) + min;
  4486. }
  4487.  
  4488. /**
  4489. * 随机数生成-范围内
  4490. * @param {number} min 最小值
  4491. * @param {number} max 最大值
  4492. * @returns {number} 一个随机数
  4493. */
  4494. function OJB_getRandomNumberInRange(min, max) {
  4495. return Math.floor(Math.random() * (max - min + 1)) + min;
  4496. }
  4497.  
  4498. /**
  4499. * 防抖函数
  4500. * @param {Function} callback 回调函数
  4501. * @returns {Function}
  4502. */
  4503. function OJB_debounce(callback) {
  4504. let timer;
  4505. let immediateExecuted = false;
  4506. const delay = 500;
  4507. return function () {
  4508. clearTimeout(timer);
  4509. if (!immediateExecuted) {
  4510. callback.call(this);
  4511. immediateExecuted = true;
  4512. }
  4513. timer = setTimeout(() => {
  4514. immediateExecuted = false;
  4515. }, delay);
  4516. };
  4517. }
  4518.  
  4519. /**
  4520. * 为元素添加鼠标拖拽支持
  4521. * @param {JQuery<HTMLElement>} element 要添加拖拽支持的元素
  4522. * @returns {void}
  4523. */
  4524. function OJB_addDraggable(element) {
  4525. let isDragging = false;
  4526. let x, y, l, t, nl, nt;
  4527. let isSpecialMouseDown = false; // 选取某些元素时不拖动
  4528.  
  4529. element.on("mousedown", function (e) {
  4530. isSpecialMouseDown = $(e.target).is(
  4531. "label, p, input, textarea, span, select, details, summary"
  4532. );
  4533. if (isSpecialMouseDown) return;
  4534.  
  4535. isDragging = true;
  4536. x = e.clientX;
  4537. y = e.clientY;
  4538. l = element.offset().left - $(window).scrollLeft();
  4539. t = element.offset().top - $(window).scrollTop();
  4540.  
  4541. element.css({ left: l + "px", top: t + "px", transform: "none" });
  4542.  
  4543. $(document).on("mousemove", drag);
  4544. $(document).on("mouseup", stopDrag);
  4545. element.css("cursor", "all-scroll");
  4546. });
  4547.  
  4548. const drag = (e) => {
  4549. if (!isDragging) return;
  4550. // 不执行拖动操作
  4551. if (
  4552. $(e.target).is("label, p, input, textarea, span") ||
  4553. (isSpecialMouseDown && !$(e.target).is("input, textarea"))
  4554. )
  4555. return;
  4556. e.preventDefault();
  4557.  
  4558. const nx = e.clientX;
  4559. const ny = e.clientY;
  4560. nl = nx - (x - l);
  4561. nt = ny - (y - t);
  4562. element.css({ transform: `translate(${nx - x}px, ${ny - y}px)` });
  4563. };
  4564.  
  4565. const stopDrag = () => {
  4566. isDragging = false;
  4567. isSpecialMouseDown = false;
  4568. element.css("cursor", "default");
  4569.  
  4570. // 在停止拖拽后,设置元素的left和top,并还原transform
  4571. element.css({ left: nl + "px", top: nt + "px", transform: "none" });
  4572. $(document).off("mousemove", drag);
  4573. $(document).off("mouseup", stopDrag);
  4574. };
  4575. }
  4576.  
  4577. /**
  4578. * 切换元素的折叠/展开过渡动画
  4579. * @param {HTMLElement} element
  4580. */
  4581. function OJB_toggleCollapseExpand(element) {
  4582. // 设置transitionend事件监听器的函数
  4583. const setTransitionListener = (listener) => {
  4584. const listenerName = `transitionEndListener${Date.now()}`;
  4585. window[listenerName] = listener;
  4586. element.addEventListener("transitionend", listener);
  4587. element.setAttribute("data-transition-end-listener", listenerName);
  4588. };
  4589.  
  4590. // 移除事件监听器的函数
  4591. const removeTransitionListener = () => {
  4592. const transitionEndListenerName = element.getAttribute(
  4593. "data-transition-end-listener"
  4594. );
  4595. if (transitionEndListenerName) {
  4596. element.removeEventListener(
  4597. "transitionend",
  4598. window[transitionEndListenerName]
  4599. );
  4600. element.removeAttribute("data-transition-end-listener");
  4601. }
  4602. };
  4603.  
  4604. const collapsed = element.getAttribute("data-collapsed") === "true";
  4605. const sectionHeight = element.scrollHeight;
  4606.  
  4607. // 移除事件监听器
  4608. removeTransitionListener();
  4609.  
  4610. // 设置初始样式
  4611. element.style.overflow = "hidden";
  4612. element.style.transition = "height 0.3s ease-out 0s";
  4613. element.style.height = collapsed ? `0px` : `${sectionHeight}px`;
  4614. element.style.opacity = collapsed ? "" : "1";
  4615.  
  4616. // 需要立即开始动画
  4617. requestAnimationFrame(() => {
  4618. // 设置结束样式
  4619. element.style.height = collapsed ? `${sectionHeight}px` : `0px`;
  4620. });
  4621.  
  4622. const transitionEndListener = (event) => {
  4623. if (event.propertyName === "height") {
  4624. if (collapsed) {
  4625. // 展开后的设置
  4626. element.style.height = "";
  4627. element.style.overflow = "";
  4628. } else {
  4629. // 折叠后的设置
  4630. element.style.opacity = "0";
  4631. }
  4632. removeTransitionListener();
  4633. }
  4634. };
  4635.  
  4636. setTransitionListener(transitionEndListener);
  4637.  
  4638. // 更新data-collapsed属性
  4639. element.setAttribute("data-collapsed", collapsed ? "false" : "true");
  4640. }
  4641.  
  4642. /**
  4643. * 获取外部JSON并转换为Object
  4644. * @param {string} url JSON Url
  4645. * @param {boolean} [nacache=true] 是否不使用缓存
  4646. * @returns {Promise<Object>} JSON Object
  4647. */
  4648. async function OJB_getExternalJSON(url, nacache = true) {
  4649. const response = await OJB_GMRequest({
  4650. method: "GET",
  4651. url: url,
  4652. nocache: nacache,
  4653. });
  4654. try {
  4655. return JSON.parse(response.responseText);
  4656. } catch (e) {
  4657. throw new Error(`JSON parse error\n${e}`);
  4658. }
  4659. }
  4660.  
  4661. /**
  4662. * 创建确认对话框dialog
  4663. * @param {string} title 标题
  4664. * @param {string} content 内容
  4665. * @param {string[]} buttons 按钮 (取消 确定) 可以为null
  4666. * @param {boolean} renderMarkdown 是否使用markdown渲染文本
  4667. * @returns {Promise<boolean>} 用户点击了确定按钮返回true, 否则返回false
  4668. */
  4669. function OJB_createDialog(title, content, buttons, renderMarkdown = false) {
  4670. return new Promise((resolve) => {
  4671. let contentHtml = content;
  4672.  
  4673. if (renderMarkdown) {
  4674. const md = window.markdownit();
  4675. contentHtml = md.render(content);
  4676. }
  4677.  
  4678. const dialog = OJB_safeCreateJQElement(`
  4679. <dialog class="OJBetter_modal">
  4680. <h2>${title}</h2>
  4681. <div class="content">${contentHtml}</div>
  4682. </dialog>
  4683. `);
  4684. const buttonbox = OJB_safeCreateJQElement(`<div class="buttons"></div>`);
  4685. const cancelButton = OJB_safeCreateJQElement(
  4686. `<button class="cancelButton">${buttons[0]}</button>`
  4687. ).addClass("secondary");
  4688. const continueButton = OJB_safeCreateJQElement(
  4689. `<button class="continueButton">${buttons[1]}</button>`
  4690. );
  4691. if (buttons[0] !== null) buttonbox.append(cancelButton);
  4692. if (buttons[1] !== null) buttonbox.append(continueButton);
  4693. dialog.append(buttonbox);
  4694. $("body").before(dialog);
  4695.  
  4696. OJB_showModal(dialog);
  4697. OJB_addDraggable(dialog);
  4698.  
  4699. continueButton.click(function () {
  4700. OJB_closeAndRemoveModal(dialog);
  4701. resolve(true);
  4702. });
  4703.  
  4704. cancelButton.click(function () {
  4705. OJB_closeAndRemoveModal(dialog);
  4706. resolve(false);
  4707. });
  4708. });
  4709. }
  4710.  
  4711. /**
  4712. * 显示模态对话框并阻止页面滚动,同时考虑滚动条宽度变化和原始marginRight
  4713. * @param {JQuery<HTMLElement>} element
  4714. */
  4715. function OJB_showModal(element) {
  4716. const dialog = element.get(0);
  4717. dialogPolyfill.registerDialog(dialog);
  4718. dialog.showModal();
  4719. OJBetter.state.openDialogCount++;
  4720.  
  4721. if (OJBetter.state.openDialogCount === 1) {
  4722. const scrollbarWidth =
  4723. window.innerWidth - document.documentElement.clientWidth;
  4724. // 获取原始的html marginRight,考虑到可能的非数字值,比如auto
  4725. const originalMarginRight = window.getComputedStyle(
  4726. document.documentElement
  4727. ).marginRight;
  4728. const marginRightValue = parseFloat(originalMarginRight) || 0; // 将非数字值转换为0
  4729.  
  4730. if (scrollbarWidth > 0) {
  4731. // 保存原始的marginRight,并设置新的值以补偿滚动条宽度
  4732. document.documentElement.style.setProperty(
  4733. "--original-margin-right",
  4734. originalMarginRight
  4735. );
  4736. document.documentElement.style.marginRight = `${
  4737. marginRightValue + scrollbarWidth
  4738. }px`;
  4739. }
  4740.  
  4741. // 保存原始的overflow样式
  4742. document.documentElement.setAttribute(
  4743. "data-original-overflow",
  4744. document.documentElement.style.overflow
  4745. );
  4746. document.documentElement.style.overflow = "hidden";
  4747. }
  4748.  
  4749. const allowScrollIfNeeded = () => {
  4750. OJBetter.state.openDialogCount--;
  4751. if (OJBetter.state.openDialogCount === 0) {
  4752. // 恢复原始的html marginRight和overflow样式
  4753. const originalMarginRight =
  4754. document.documentElement.style.getPropertyValue(
  4755. "--original-margin-right"
  4756. );
  4757. document.documentElement.style.marginRight = originalMarginRight;
  4758. document.documentElement.style.removeProperty("--original-margin-right");
  4759.  
  4760. const originalOverflow = document.documentElement.getAttribute(
  4761. "data-original-overflow"
  4762. );
  4763. document.documentElement.style.overflow = originalOverflow;
  4764. document.documentElement.removeAttribute("data-original-overflow");
  4765. }
  4766. };
  4767.  
  4768. dialog.addEventListener("close", allowScrollIfNeeded);
  4769. }
  4770.  
  4771. /**
  4772. * 关闭并移除模态对话框
  4773. * @param {JQuery<HTMLElement>} element
  4774. */
  4775. function OJB_closeAndRemoveModal(element) {
  4776. const dialog = element.get(0);
  4777. dialog.close();
  4778. dialog.remove();
  4779. }
  4780.  
  4781. /**
  4782. * 关闭并移除模态对话框
  4783. * @param {JQuery<HTMLElement>} element
  4784. */
  4785. function OJB_closeModal(element) {
  4786. const dialog = element.get(0);
  4787. dialog.close();
  4788. }
  4789.  
  4790. /**
  4791. * 清除i18next的缓存数据并刷新
  4792. */
  4793. function clearI18nextCache() {
  4794. Object.keys(localStorage)
  4795. .filter((key) => key.startsWith("i18next_res_"))
  4796. .forEach((key) => localStorage.removeItem(key));
  4797. window.location.reload();
  4798. }
  4799.  
  4800. /**
  4801. * 清除网站本地化数据
  4802. */
  4803. async function clearWebsiteL10nData() {
  4804. OJBetter.common.database.localizeSubsData
  4805. .clear()
  4806. .then(() => {
  4807. console.log("localizeSubsData table has been cleared");
  4808. window.location.reload();
  4809. })
  4810. .catch((error) => {
  4811. console.error("Failed to clear localizeSubsData table:", error);
  4812. });
  4813. }
  4814.  
  4815. /**
  4816. * 从Pre代码块中获取原始代码
  4817. * @param {HTMLElement} element pre代码块元素
  4818. * @returns {string|null} 代码文本
  4819. */
  4820. function OJB_getCodeFromPre(element) {
  4821. /**
  4822. * 从Ace格式化的代码块中获取原始代码
  4823. * @param {HTMLElement} element pre代码块元素
  4824. * @returns {string} 代码文本
  4825. */
  4826. const getCodeFromAcePre = function (element) {
  4827. const editor = ace.edit(element);
  4828. return editor.getValue();
  4829. };
  4830.  
  4831. /**
  4832. * 从Pretty格式化的代码块中获取原始代码-1
  4833. * 代码直接存放在 pre 元素中
  4834. * @param {HTMLElement} element pre代码块元素
  4835. * @returns {string} 代码文本
  4836. */
  4837. const getCodeFromPrettyPre = function (element) {
  4838. return Array.from(element.querySelectorAll("li"))
  4839. .map(function (li) {
  4840. return li.textContent;
  4841. })
  4842. .join("\n");
  4843. };
  4844.  
  4845. /**
  4846. * 从Pretty格式化的代码块中获取原始代码-2
  4847. * 代码存放在子元素 code 中
  4848. * @param {HTMLElement} element pre代码块元素
  4849. * @returns {string} 代码文本
  4850. */
  4851. const getCodeFromPreChild = function (element) {
  4852. const code = element.querySelector("code.prettyprint");
  4853. if (code.classList.contains("linenums")) {
  4854. return getCodeFromPrettyPre(element);
  4855. } else {
  4856. return element.querySelector("code.prettyprint").textContent;
  4857. }
  4858. };
  4859.  
  4860. let result;
  4861. if (element.id === "submission-code") {
  4862. result = getCodeFromAcePre(element);
  4863. } else if (element.classList.contains("prettyprint")) {
  4864. result = getCodeFromPrettyPre(element);
  4865. } else if (element.querySelector("code.prettyprint")) {
  4866. result = getCodeFromPreChild(element);
  4867. } else {
  4868. result = "";
  4869. }
  4870. result = result.replace(/\u00A0/g, ""); // 过滤文本中的U+00a0字符(由&nbsp;造成的)
  4871. return result;
  4872. }
  4873.  
  4874. /**
  4875. * 判断代码的语言
  4876. * @param {string} code 代码文本
  4877. * @returns {string} 可能的语言
  4878. */
  4879. function OJB_codeLangDetect(code) {
  4880. result = hljs.highlightAuto(code);
  4881. return result.language;
  4882. }
  4883.  
  4884. /**
  4885. * 获取指定命名空间下的所有i18n翻译键值对。
  4886. *
  4887. * @param {string} namespace - 要获取键值对的i18next命名空间。
  4888. * @returns {Map<string, string>} 一个包含命名空间下所有键值对的Map对象。
  4889. */
  4890. function OJB_getAllI18nKeysForNamespace(namespace) {
  4891. const language = i18next.language; // 获取当前语言
  4892. const resources = i18next.store.data[language]; // 获取当前语言的所有资源
  4893. const nsResources = resources[namespace]; // 获取特定命名空间的资源
  4894. const resultMap = new Map();
  4895.  
  4896. if (nsResources) {
  4897. // 遍历命名空间下的所有键值对,并添加到Map中
  4898. Object.keys(nsResources).forEach((key) => {
  4899. resultMap.set(key, nsResources[key]);
  4900. });
  4901. } else {
  4902. console.log(`No resources found for namespace "${namespace}"`);
  4903. }
  4904.  
  4905. return resultMap;
  4906. }
  4907.  
  4908. /**
  4909. * 更新检查
  4910. */
  4911. async function checkScriptVersion() {
  4912. try {
  4913. const versionResponse = await OJB_GMRequest({
  4914. method: "GET",
  4915. url: "https://aowuucdn.oss-accelerate.aliyuncs.com/script/versions.json",
  4916. timeout: 10 * 1e3,
  4917. nocache: true,
  4918. });
  4919. const versionData = JSON.parse(versionResponse.responseText);
  4920. const {
  4921. [OJBetter.state.formatName]: {
  4922. dev: version_dev,
  4923. release: version_release,
  4924. },
  4925. } = versionData;
  4926. const baseUrls = {
  4927. greasyfork:
  4928. "https://update.greasyfork.org/scripts/465777/Codeforces%20Better%21.user.js",
  4929. github: `https://github.com/beijixiaohu/OJBetter/raw/main/script/${OJBetter.about.updateChannel}/${OJBetter.state.formatName}.user.js`,
  4930. aliyunoss: `https://aowuucdn.oss-accelerate.aliyuncs.com/script/${OJBetter.about.updateChannel}/${OJBetter.state.formatName}.user.js`,
  4931. };
  4932. /** @type {string} 更新跳转url */
  4933. const updateUrl = baseUrls[OJBetter.about.updateSource];
  4934. /** @type {string} 是否暂时跳过cookie */
  4935. const skipUpdate = OJB_getCookie("skipUpdate");
  4936. /** @type {string} 当前更新频道的最新版本 */
  4937. const version =
  4938. OJBetter.about.updateChannel == "release" ? version_release : version_dev;
  4939. if (
  4940. OJB_compareVersions(version, OJBetter.state.version) === 1 &&
  4941. skipUpdate !== "true"
  4942. ) {
  4943. const updateConfirmed = await OJB_createDialog(
  4944. i18next.t("update.title", {
  4945. ns: "dialog",
  4946. scriptName: OJBetter.state.name,
  4947. }),
  4948. i18next.t("update.content", {
  4949. ns: "dialog",
  4950. oldVersion: OJBetter.state.version,
  4951. newVersion: version,
  4952. }),
  4953. [
  4954. i18next.t("update.buttons.0", { ns: "dialog" }),
  4955. i18next.t("update.buttons.1", { ns: "dialog" }),
  4956. ],
  4957. true
  4958. );
  4959.  
  4960. if (updateConfirmed) {
  4961. window.location.href = updateUrl;
  4962. } else {
  4963. document.cookie = "skipUpdate=true; path=/";
  4964. }
  4965. }
  4966. } catch (error) {
  4967. console.error("Update check failed: ", error);
  4968. }
  4969. }
  4970.  
  4971. /**
  4972. * 公告
  4973. */
  4974. async function showAnnounce() {
  4975. /** @type {string} 最新公告版本*/
  4976. const lastAnnounceVer = i18next.t("lastVersion", { ns: "announce" });
  4977. if (
  4978. OJB_compareVersions(
  4979. OJBetter.state.version,
  4980. OJBetter.state.lastAnnounceVer
  4981. ) === 1
  4982. ) {
  4983. const title = `🎉${i18next.t("announce.title", { ns: "dialog" })} ${
  4984. OJBetter.state.version
  4985. }`;
  4986. /** @type {Boolean} 是否是新的公告 */
  4987. const isNewAnnounceVer =
  4988. OJB_compareVersions(
  4989. lastAnnounceVer,
  4990. OJBetter.state.lastReadAnnounceVer
  4991. ) === 1;
  4992. /** @type {Boolean} 是否展示新的公告(高于当前版本的测试公告不展示) */
  4993. const showNewAnnounceVer =
  4994. OJB_compareVersions(lastAnnounceVer, OJBetter.state.version) !== 1;
  4995. /**
  4996. * 获取公告的内容
  4997. * @returns {string} 公告内容
  4998. */
  4999. const getAnnounceContent = function () {
  5000. // 获取公告
  5001. const announceMap = OJB_getAllI18nKeysForNamespace("announce");
  5002. // 移除 'lastVersion' 键
  5003. announceMap.delete("lastVersion");
  5004. // 将 Map 转换为数组并根据版本号排序
  5005. const sortedVersions = [...announceMap.keys()]
  5006. .sort(OJB_compareVersions)
  5007. .reverse();
  5008. let content = "";
  5009. sortedVersions.forEach((version) => {
  5010. content += `### ${version}\n\n`; // 使用版本号作为标题
  5011. content += announceMap.get(version); // 添加对应版本的公告内容
  5012. content += "\n\n";
  5013. });
  5014.  
  5015. return content;
  5016. };
  5017.  
  5018. const content = (() => {
  5019. if (isNewAnnounceVer && showNewAnnounceVer) {
  5020. return `${i18next.t("announce.prefix", {
  5021. ns: "dialog",
  5022. })}\n\n${getAnnounceContent()}`;
  5023. } else {
  5024. return i18next.t("announce.divContent", { ns: "dialog" });
  5025. }
  5026. })();
  5027. const ok = await OJB_createDialog(
  5028. title,
  5029. content,
  5030. [null, i18next.t("announce.buttons.0", { ns: "dialog" })],
  5031. true
  5032. ); //跳过折叠块确认
  5033. if (ok) {
  5034. if (isNewAnnounceVer && showNewAnnounceVer) {
  5035. GM_setValue("lastReadAnnounceVer", lastAnnounceVer);
  5036. }
  5037. GM_setValue("lastAnnounceVer", OJBetter.state.version);
  5038. }
  5039. }
  5040. }
  5041.  
  5042. /**
  5043. * 页面顶部提示信息alert类
  5044. */
  5045. class LoadingMessage {
  5046. constructor() {
  5047. this._statusElement = null;
  5048. this._isDisplayed = false;
  5049. this.init();
  5050. }
  5051.  
  5052. /**
  5053. * 初始化加载提示信息
  5054. */
  5055. init() {
  5056. this._statusElement = this.createStatusElement();
  5057. this.insertStatusElement();
  5058. }
  5059.  
  5060. /**
  5061. * 创建提示信息元素
  5062. */
  5063. createStatusElement() {
  5064. const statusElement = $("<div></div>")
  5065. .addClass("alert OJBetter_alert")
  5066. .css({
  5067. margin: "1em",
  5068. "text-align": "center",
  5069. position: "relative",
  5070. })
  5071. .hide();
  5072. return statusElement;
  5073. }
  5074.  
  5075. /**
  5076. * 插入提示信息
  5077. * @returns {void}
  5078. */
  5079. insertStatusElement() {
  5080. (OJBetter.typeOfPage.is_mSite
  5081. ? $("header")
  5082. : $(".menu-box:first").next()
  5083. ).after(this._statusElement);
  5084. }
  5085.  
  5086. /**
  5087. * 显示提示信息
  5088. */
  5089. showStatus() {
  5090. this._statusElement.show();
  5091. this._isDisplayed = true;
  5092. }
  5093.  
  5094. /**
  5095. * 隐藏提示信息
  5096. */
  5097. hideStatus() {
  5098. this._statusElement.fadeOut(500);
  5099. this._isDisplayed = false;
  5100. }
  5101.  
  5102. /**
  5103. * 移除提示信息
  5104. */
  5105. removeStatus() {
  5106. this._statusElement.remove();
  5107. this._isDisplayed = false;
  5108. }
  5109.  
  5110. /**
  5111. * 更新提示信息
  5112. * @param {string} text 提示信息文本
  5113. * @param {string} type 提示信息类型,可选值:info, success, warning, error
  5114. * @param {number} timeout 提示信息显示的持续时间(毫秒), 默认为无限长
  5115. */
  5116. updateStatus(text, type = "info", timeout = Infinity, isMarkdown = false) {
  5117. if (isMarkdown) {
  5118. let md = window.markdownit({
  5119. html: !is_escapeHTML,
  5120. });
  5121. text = md.render(text);
  5122. }
  5123. this._statusElement
  5124. .html(text)
  5125. .removeClass("alert-info alert-success alert-warning alert-error")
  5126. .addClass(`alert-${type}`);
  5127. if (!this._isDisplayed) {
  5128. this.showStatus();
  5129. }
  5130. if (timeout !== Infinity) {
  5131. setTimeout(() => {
  5132. this.hideStatus();
  5133. }, timeout);
  5134. }
  5135. }
  5136. }
  5137.  
  5138. /**
  5139. * 获取网站本地化的数据
  5140. * @param {*} localizationLanguage 本地化语言
  5141. * @returns {Promise<Object>} 本地化数据
  5142. */
  5143. async function getLocalizeWebsiteJson(localizationLanguage) {
  5144. let data = await OJBetter.common.database.localizeSubsData.get(
  5145. localizationLanguage
  5146. );
  5147. let url =
  5148. localizationLanguage === "zh"
  5149. ? `https://aowuucdn.oss-accelerate.aliyuncs.com/resources/subs/${OJBetter.state.formatName}.json`
  5150. : `https://aowuucdn.oss-accelerate.aliyuncs.com/i18n/${localizationLanguage}/resources/subs/${OJBetter.state.formatName}.json`;
  5151. if (data) data = data.data;
  5152. if (!data) {
  5153. // 如果本地没有数据,从远端获取并保存
  5154. data = await OJB_getExternalJSON(url);
  5155. await OJBetter.common.database.localizeSubsData.put({
  5156. lang: localizationLanguage,
  5157. data: data,
  5158. });
  5159. } else {
  5160. // 如果本地有数据,检查是否已经在当前会话中尝试过更新
  5161. const sessionKey = `ojb_updateL10nWebsiteJson_${localizationLanguage}`;
  5162. if (!OJB_isSameBrowserSession(sessionKey)) {
  5163. // 如果尚未更新,则在后台更新
  5164. (async () => {
  5165. try {
  5166. const newData = await OJB_getExternalJSON(url);
  5167. await OJBetter.common.database.localizeSubsData.put({
  5168. lang: localizationLanguage,
  5169. data: newData,
  5170. });
  5171. console.log("Website local data has been refreshed!");
  5172. } catch (error) {
  5173. console.error("Failed to update localization data:", error);
  5174. }
  5175. })();
  5176. }
  5177. }
  5178. return data;
  5179. }
  5180.  
  5181. /**
  5182. * 网站本地化替换
  5183. * @returns
  5184. */
  5185. async function localizeWebsite() {
  5186. if (OJBetter.localization.websiteLang === "initial") return;
  5187.  
  5188. // 设置网页语言
  5189. var htmlTag = document.getElementsByTagName("html")[0];
  5190. htmlTag.setAttribute("lang", OJBetter.localization.websiteLang);
  5191.  
  5192. // 获取网站本地化的数据
  5193. var subs = await getLocalizeWebsiteJson(OJBetter.localization.websiteLang);
  5194.  
  5195. /**
  5196. * 文本节点遍历替换
  5197. * @param {JQuery} $nodes jQuery对象
  5198. * @param {Object} textReplaceRules 文本替换规则对象
  5199. * @param {string} key 应用的规则集的名字
  5200. */
  5201. const traverseTextNodes = ($nodes, textReplaceRules, key) => {
  5202. if (!$nodes) return;
  5203.  
  5204. $nodes.each((_, node) => {
  5205. if (node.nodeType === Node.TEXT_NODE) {
  5206. Object.entries(textReplaceRules).forEach(([match, replace]) => {
  5207. try {
  5208. const regex = new RegExp(match, "g");
  5209. const beforeText = node.textContent;
  5210. node.textContent = node.textContent.replace(regex, replace);
  5211. if (
  5212. node.textContent !== beforeText &&
  5213. OJBetter.dev.isRuleMarkingEnabled
  5214. ) {
  5215. $(node).after(
  5216. `<span class="markingTextReplaceRule">${key}</span>`
  5217. );
  5218. }
  5219. } catch (error) {
  5220. console.error(
  5221. `Error processing text replacement for match: ${match}`,
  5222. error
  5223. );
  5224. }
  5225. });
  5226. } else if (
  5227. node.nodeType === Node.ELEMENT_NODE &&
  5228. node.tagName.toLowerCase() !== "iframe"
  5229. ) {
  5230. $(node)
  5231. .contents()
  5232. .each((_, childNode) => {
  5233. traverseTextNodes($(childNode), textReplaceRules, key);
  5234. });
  5235. }
  5236. });
  5237. };
  5238.  
  5239. /**
  5240. * value替换
  5241. * @param {JQuery} $nodes jQuery对象
  5242. * @param {Object} valueReplaceRules 值替换规则对象
  5243. * @param {string} key 应用的规则集的名字
  5244. */
  5245. function traverseValueNodes($nodes, valueReplaceRules, key) {
  5246. if (!$nodes) return;
  5247.  
  5248. $nodes.each(function () {
  5249. let $node = $(this);
  5250. if ($node.is("[value]")) {
  5251. Object.keys(valueReplaceRules).forEach((match) => {
  5252. const replace = valueReplaceRules[match];
  5253. const regex = new RegExp(match, "g");
  5254. let currentValue = $node.val();
  5255. let newValue = currentValue.replace(regex, replace);
  5256. $node.val(newValue);
  5257. if (OJBetter.dev.isRuleMarkingEnabled) {
  5258. if (newValue !== currentValue)
  5259. $($node).after(
  5260. `<span class="markingTextReplaceRule">${key}</span>`
  5261. );
  5262. }
  5263. });
  5264. } else {
  5265. $node.children().each(function () {
  5266. traverseValueNodes($(this), valueReplaceRules, key);
  5267. });
  5268. }
  5269. });
  5270. }
  5271.  
  5272. /**
  5273. * 严格的文本节点遍历替换
  5274. * 要求被替换文本严格与规则文本一致
  5275. * @param {JQuery} $nodes jQuery对象
  5276. * @param {Object} textReplaceRules 文本替换规则对象
  5277. * @param {string} key 应用的规则集的名字
  5278. */
  5279. const strictTraverseTextNodes = ($nodes, textReplaceRules, key) => {
  5280. if (!$nodes) return;
  5281.  
  5282. $nodes.each((_, node) => {
  5283. if (node.nodeType === Node.TEXT_NODE) {
  5284. const trimmedNodeText = node.textContent.trim();
  5285. for (const [match, replacement] of Object.entries(textReplaceRules)) {
  5286. if (trimmedNodeText === match) {
  5287. const beforeText = node.textContent;
  5288. node.textContent = replacement;
  5289. if (
  5290. node.textContent !== beforeText &&
  5291. OJBetter.dev.isRuleMarkingEnabled
  5292. ) {
  5293. $(node).after(
  5294. `<span class="markingTextReplaceRule">${key}</span>`
  5295. );
  5296. }
  5297. }
  5298. }
  5299. } else if (
  5300. node.nodeType === Node.ELEMENT_NODE &&
  5301. node.tagName.toLowerCase() !== "iframe"
  5302. ) {
  5303. $(node)
  5304. .contents()
  5305. .each((_, childNode) => {
  5306. strictTraverseTextNodes($(childNode), textReplaceRules, key);
  5307. });
  5308. }
  5309. });
  5310. };
  5311.  
  5312. /**
  5313. * 应用文本替换
  5314. */
  5315. let commonReplacements = subs.commonReplacements;
  5316. Object.entries(commonReplacements).forEach(([key, value]) => {
  5317. const classSelectors = Array.isArray(value.class)
  5318. ? value.class
  5319. : [value.class]; // 兼容,class的值可以为数组或者字符串
  5320. classSelectors.forEach((classSelector) => {
  5321. if (value.isStrict) {
  5322. strictTraverseTextNodes(
  5323. OJB_safeCreateJQElement(`${classSelector}`),
  5324. value.rules,
  5325. key
  5326. );
  5327. } else {
  5328. traverseTextNodes(
  5329. OJB_safeCreateJQElement(`${classSelector}`),
  5330. value.rules,
  5331. key
  5332. );
  5333. }
  5334. });
  5335. });
  5336.  
  5337. /**
  5338. * 应用value替换
  5339. */
  5340. let InputValueReplacements = subs.InputValueReplacements;
  5341. Object.entries(InputValueReplacements).forEach(([key, value]) => {
  5342. const classSelectors = Array.isArray(value.class)
  5343. ? value.class
  5344. : [value.class];
  5345. classSelectors.forEach((classSelector) => {
  5346. traverseValueNodes(
  5347. OJB_safeCreateJQElement(`${classSelector}`),
  5348. value.rules,
  5349. key
  5350. );
  5351. });
  5352. });
  5353.  
  5354. /**
  5355. * 动态添加的文本的替换
  5356. */
  5357. let dynamicReplacements = subs.dynamicReplacements;
  5358. Object.entries(dynamicReplacements).forEach(([key, value]) => {
  5359. const classSelectors = Array.isArray(value.class)
  5360. ? value.class
  5361. : [value.class]; // 兼容,class的值可以为数组或者字符串
  5362. classSelectors.forEach((classSelector) => {
  5363. OJB_observeElement({
  5364. selector: classSelector,
  5365. callback: (node) => {
  5366. // let popupContent = node.textContent.replace(/^×/, ''); // 去除开头多余的 '×' 字符
  5367. if (value.isStrict) {
  5368. strictTraverseTextNodes(
  5369. OJB_safeCreateJQElement(`${classSelector}`),
  5370. value.rules,
  5371. key
  5372. );
  5373. } else {
  5374. traverseTextNodes(
  5375. OJB_safeCreateJQElement(`${classSelector}`),
  5376. value.rules,
  5377. key
  5378. );
  5379. }
  5380. },
  5381. });
  5382. });
  5383. });
  5384.  
  5385. // 杂项
  5386. (function () {
  5387. // 选项汉化input[type="radio"]
  5388. var translations = {
  5389. "as individual participant": "个人",
  5390. "as a team member": "作为一个团队成员",
  5391. };
  5392. $('input[type="radio"]').each(function () {
  5393. var tag = $(this)
  5394. .parent()
  5395. .contents()
  5396. .filter(function () {
  5397. return this.nodeType === Node.TEXT_NODE;
  5398. });
  5399. for (var i = 0; i < tag.length; i++) {
  5400. var text = tag[i].textContent.trim();
  5401. if (translations.hasOwnProperty(text)) {
  5402. $(this).addClass(text);
  5403. tag[i].replaceWith(translations[text]);
  5404. break;
  5405. }
  5406. }
  5407. });
  5408. })();
  5409. (function () {
  5410. var translations = {
  5411. "(standard input/output)": "标准输入/输出",
  5412. };
  5413. $("div.notice").each(function () {
  5414. var tag = $(this).children().eq(0).text();
  5415. for (var property in translations) {
  5416. if (tag.match(property)) {
  5417. $(this).children().eq(0).text(translations[property]);
  5418. break;
  5419. }
  5420. }
  5421. });
  5422. })();
  5423.  
  5424. // 轻量站特殊
  5425. if (OJBetter.typeOfPage.is_mSite) {
  5426. traverseTextNodes(
  5427. $("nav"),
  5428. commonReplacements[".second-level-menu"]["rules"]
  5429. );
  5430. }
  5431. if (OJBetter.typeOfPage.is_mSite) {
  5432. (function () {
  5433. var translations = {
  5434. Announcements: "公告",
  5435. Submissions: "提交记录",
  5436. Contests: "比赛",
  5437. };
  5438. $(".caption").each(function () {
  5439. var optionValue = $(this).text();
  5440. if (translations[optionValue]) {
  5441. $(this).text(translations[optionValue]);
  5442. }
  5443. });
  5444. })();
  5445. }
  5446. }
  5447.  
  5448. /**
  5449. * i18next初始化
  5450. */
  5451. async function initI18next() {
  5452. return new Promise((resolve, reject) => {
  5453. i18next.use(i18nextChainedBackend).init(
  5454. {
  5455. lng: OJBetter.localization.scriptLang,
  5456. ns: [
  5457. "common",
  5458. "settings",
  5459. "config",
  5460. "dialog",
  5461. "alert",
  5462. "translator",
  5463. "button",
  5464. "codeEditor",
  5465. "comments",
  5466. "announce",
  5467. "logMessage",
  5468. ], // 命名空间列表
  5469. defaultNS: "settings",
  5470. fallbackLng: ["zh", OJBetter.translation.targetLang],
  5471. load: "currentOnly",
  5472. debug: false,
  5473. backend: {
  5474. backends: [i18nextLocalStorageBackend, i18nextHttpBackend],
  5475. backendOptions: [
  5476. {
  5477. prefix: "i18next_res_",
  5478. expirationTime: 7 * 24 * 60 * 60 * 1000,
  5479. defaultVersion: `v${OJBetter.state.version}`,
  5480. store: typeof window !== "undefined" ? window.localStorage : null,
  5481. },
  5482. {
  5483. /* options for secondary backend */
  5484. loadPath: (lng, ns) => {
  5485. if (lng[0] === "zh" || lng[0] === "zh-Hans") {
  5486. return `https://aowuucdn.oss-accelerate.aliyuncs.com/resources/locales/${OJBetter.state.formatName}/${ns}.json`;
  5487. }
  5488. return `https://aowuucdn.oss-accelerate.aliyuncs.com/i18n/${lng}/resources/locales/${OJBetter.state.formatName}/${ns}.json`;
  5489. },
  5490. },
  5491. ],
  5492. },
  5493. },
  5494. (err, t) => {
  5495. if (err) {
  5496. reject(err);
  5497. } else {
  5498. jqueryI18next.init(i18next, $, {
  5499. useOptionsAttr: true,
  5500. });
  5501. resolve(t);
  5502. }
  5503. }
  5504. );
  5505. });
  5506. }
  5507.  
  5508. /**
  5509. * 抽象命令类
  5510. */
  5511. class Command {
  5512. execute() {}
  5513. undo() {}
  5514. }
  5515.  
  5516. /**
  5517. * 命令调用者
  5518. */
  5519. class CommandInvoker {
  5520. constructor() {
  5521. this.history = [];
  5522. }
  5523.  
  5524. /**
  5525. * 执行命令
  5526. * @param {Command} command 命令对象
  5527. */
  5528. execute(command) {
  5529. this.history.push(command);
  5530. command.execute();
  5531. }
  5532.  
  5533. /**
  5534. * 撤销命令
  5535. */
  5536. undo() {
  5537. const command = this.history.pop();
  5538. if (command) {
  5539. command.undo();
  5540. }
  5541. }
  5542. }
  5543.  
  5544. /**
  5545. * 接收者
  5546. */
  5547. class DOMContainer {
  5548. /**
  5549. * @param {JQueryObject} element 容器对象
  5550. */
  5551. constructor(element) {
  5552. this.containerElement = element;
  5553. }
  5554.  
  5555. /**
  5556. * 添加元素
  5557. * @param {JQueryObject} element 元素对象
  5558. * @returns {JQueryObject} 添加的元素对象
  5559. */
  5560. add(element) {
  5561. this.containerElement.append(element);
  5562. return this.containerElement.children().last();
  5563. }
  5564.  
  5565. /**
  5566. * 删除元素
  5567. * @param {JQueryObject} element 元素对象
  5568. */
  5569. remove(element) {
  5570. $(element).remove();
  5571. }
  5572. }
  5573.  
  5574. /**
  5575. * 具体命令类:添加元素
  5576. */
  5577. class AddElementCommand extends Command {
  5578. /**
  5579. * @param {DOMContainer} receiver 接收者
  5580. * @param {JQueryObject} element 元素对象
  5581. */
  5582. constructor(receiver, element) {
  5583. super();
  5584. this.receiver = receiver;
  5585. this.element = element;
  5586. this.addedElement = null;
  5587. }
  5588.  
  5589. execute() {
  5590. this.addedElement = this.receiver.add(this.element);
  5591. }
  5592.  
  5593. undo() {
  5594. if (this.addedElement) {
  5595. this.receiver.remove(this.addedElement);
  5596. }
  5597. }
  5598. }
  5599.  
  5600. /**
  5601. * 具体命令类:删除元素
  5602. */
  5603. class RemoveElementCommand extends Command {
  5604. /**
  5605. * @param {DOMContainer} receiver 接收者
  5606. * @param {JQueryObject} element 元素对象
  5607. */
  5608. constructor(receiver, element) {
  5609. super();
  5610. this.receiver = receiver;
  5611. this.element = element;
  5612. this.parent = $(element).parent();
  5613. this.nextSibling = $(element).next();
  5614. }
  5615.  
  5616. execute() {
  5617. this.receiver.remove(this.element);
  5618. }
  5619.  
  5620. undo() {
  5621. if (this.nextSibling.length > 0) {
  5622. $(this.element).insertBefore(this.nextSibling);
  5623. } else {
  5624. this.parent.append(this.element);
  5625. }
  5626. }
  5627. }
  5628.  
  5629. /**
  5630. * 验证器
  5631. */
  5632. class Validator {
  5633. /**
  5634. * 表单必填项空值校验
  5635. */
  5636. static required(structure) {
  5637. let config = {};
  5638. let allFieldsValid = true;
  5639. for (const key in structure) {
  5640. let value =
  5641. key.type == "checkbox" ? $(key).prop("checked") : $(key).val();
  5642.  
  5643. config[structure[key].value] = value;
  5644.  
  5645. if (value || structure[key].require === false) {
  5646. $(key).removeClass("is_null");
  5647. } else {
  5648. $(key).addClass("is_null");
  5649. allFieldsValid = false;
  5650. }
  5651. }
  5652. return {
  5653. valid: allFieldsValid,
  5654. config: config,
  5655. };
  5656. }
  5657.  
  5658. /**
  5659. * 表单合法性校验
  5660. */
  5661. static checkKeyValuePairs(structure, config) {
  5662. let errorKeys = [];
  5663. let allFieldsValid = true;
  5664.  
  5665. for (const key in structure) {
  5666. const { check, value } = structure[key];
  5667. const fieldValue = config[value];
  5668.  
  5669. // 如果字段没有值或校验类型不匹配,则跳过当前迭代
  5670. if (!fieldValue) continue;
  5671.  
  5672. let isValid = true;
  5673. switch (check) {
  5674. case "keyValuePairs":
  5675. isValid = Validator.keyValuePairs(fieldValue);
  5676. break;
  5677. case "dotSeparatedPath":
  5678. isValid = Validator.validateDotSeparatedPath(fieldValue);
  5679. break;
  5680. default:
  5681. // 没有匹配的校验类型
  5682. continue;
  5683. }
  5684.  
  5685. Validator.toggleErrorDisplay(key, isValid);
  5686. if (!isValid) {
  5687. allFieldsValid = false;
  5688. errorKeys.push(key);
  5689. }
  5690. }
  5691.  
  5692. return {
  5693. valid: allFieldsValid,
  5694. errorKeys: errorKeys,
  5695. };
  5696. }
  5697.  
  5698. /**
  5699. * 切换错误信息的显示和隐藏
  5700. * @param {string} key - 字段的键
  5701. * @param {boolean} isValid - 字段值是否有效
  5702. */
  5703. static toggleErrorDisplay(key, isValid) {
  5704. const errorMessage = i18next.t("common.unValid", { ns: "settings" });
  5705. const $errorSpan = $(key).prev("span.text-error");
  5706. if (!isValid) {
  5707. if (!$errorSpan.length) {
  5708. $(key).before(
  5709. `<span class="text-error" style="color: red;">${errorMessage}</span>`
  5710. );
  5711. }
  5712. } else {
  5713. $errorSpan.remove();
  5714. }
  5715. }
  5716.  
  5717. /**
  5718. * 键值对合法性校验
  5719. * @param {string} value
  5720. * @returns {boolean}
  5721. */
  5722. static keyValuePairs(value) {
  5723. const keyValuePairs = value.split("\n");
  5724. // 允许值中包含空格和冒号
  5725. const regex = /^[a-zA-Z0-9_-]+\s*:\s*.+$/;
  5726. return keyValuePairs.every((pair) => regex.test(pair));
  5727. }
  5728.  
  5729. /**
  5730. * 点分隔符路径格式校验,允许加减运算
  5731. * @param {string} path
  5732. * @returns {boolean}
  5733. */
  5734. static validateDotSeparatedPath(path) {
  5735. // 正则表达式允许标识符之间有点号,标识符可以包含加减运算
  5736. const regex =
  5737. /^([a-zA-Z0-9_-]+(\s*[\+\-]\s*[a-zA-Z0-9_-]+)*\.)*([a-zA-Z0-9_-]+(\s*[\+\-]\s*[a-zA-Z0-9_-]+)*)$/;
  5738. return regex.test(path);
  5739. }
  5740. }
  5741.  
  5742. /**
  5743. * 配置管理
  5744. */
  5745. class ConfigManager {
  5746. /**
  5747. * @param {HTMLElement} element - 挂载容器
  5748. * @param {string} prefix - 前缀
  5749. * @param {object} tempConfig - 配置内容
  5750. * @param {object} structure - 配置结构
  5751. * @param {object} configHTML - 配置编辑页面HTML
  5752. * @param {boolean} allowChoice - 是否允许选择列表项
  5753. */
  5754. constructor(
  5755. element,
  5756. prefix,
  5757. tempConfig,
  5758. structure,
  5759. configHTML,
  5760. allowChoice = true
  5761. ) {
  5762. /** @param 设置面板DIV */
  5763. this.settingMenuDiv = $("#OJBetter_setting_menu");
  5764. this.element = $(element);
  5765. this.prefix = prefix;
  5766. this.tempConfig = tempConfig;
  5767. this.structure = structure;
  5768. this.configHTML = configHTML;
  5769. this.allowChoice = allowChoice;
  5770.  
  5771. this.controlTip = null;
  5772. this.config_bar_list = null;
  5773. this.config_bar_ul = null;
  5774. this.config_add_button = null;
  5775. this.menu = null;
  5776. this.editItem = null;
  5777. this.deleteItem = null;
  5778.  
  5779. // 绑定方法
  5780. this.onAdd = this.onAdd.bind(this);
  5781. this.onEdit = this.onEdit.bind(this);
  5782. this.onDelete = this.onDelete.bind(this);
  5783. this.createListItemElement = this.createListItemElement.bind(this);
  5784.  
  5785. this.lastItemId = 0; // 列表中当前最后一个元素的id号
  5786. this.init();
  5787. }
  5788.  
  5789. init() {
  5790. this.createControlBar();
  5791. this.createContextMenu();
  5792. this.renderList();
  5793. }
  5794.  
  5795. /**
  5796. * 创建控制栏
  5797. */
  5798. createControlBar() {
  5799. this.controlTip = OJB_safeCreateJQElement(
  5800. `<div id='${this.prefix}configControlTip' style='color:red;'></div>`
  5801. );
  5802. this.config_bar_list = OJB_safeCreateJQElement(
  5803. `<div class='config_bar_list' id='${this.prefix}config_bar_list'></div>`
  5804. );
  5805. this.config_bar_ul = OJB_safeCreateJQElement(
  5806. `<ul class='config_bar_ul' id='${this.prefix}config_bar_ul'></ul>`
  5807. );
  5808. this.element.append(this.controlTip);
  5809. this.element.append(this.config_bar_list);
  5810. this.config_bar_list.append(this.config_bar_ul);
  5811. }
  5812.  
  5813. /**
  5814. * 创建右键菜单
  5815. */
  5816. createContextMenu() {
  5817. const menu = OJB_safeCreateJQElement(
  5818. `<div id='config_bar_menu' style='display: none;'></div>`
  5819. );
  5820. const editItem = OJB_safeCreateJQElement(`
  5821. <div class='config_bar_menu_item' id='config_bar_menu_edit'>
  5822. ${i18next.t("contextMenu.edit", { ns: "translator" })}
  5823. </div>`);
  5824. const deleteItem = OJB_safeCreateJQElement(`
  5825. <div class='config_bar_menu_item' id='config_bar_menu_delete'>
  5826. ${i18next.t("contextMenu.delete", { ns: "translator" })}
  5827. </div>`);
  5828. menu.append(editItem);
  5829. menu.append(deleteItem);
  5830. this.editItem = editItem;
  5831. this.deleteItem = deleteItem;
  5832. this.menu = menu;
  5833. this.settingMenuDiv.append(menu);
  5834. }
  5835.  
  5836. /**
  5837. * 关闭右键菜单
  5838. */
  5839. closeContextMenu() {
  5840. this.menu.css({ display: "none" });
  5841. }
  5842.  
  5843. /**
  5844. * 创建列表项
  5845. * @param {string} text - 列表项文本
  5846. * @returns {HTMLElement} - 列表项
  5847. */
  5848. createListItemElement(text) {
  5849. const id = OJB_getRandomNumber(4);
  5850. const li = $("<li></li>");
  5851. const radio = OJB_safeCreateJQElement(
  5852. `<input type='radio' name='${this.prefix}config_item'></input>`
  5853. )
  5854. .attr("value", text)
  5855. .attr("id", id)
  5856. .attr("prev_id", this.lastItemId)
  5857. .appendTo(li);
  5858. if (!this.allowChoice) {
  5859. radio.prop("disabled", true);
  5860. }
  5861. const label = OJB_safeCreateJQElement(
  5862. `<label for='${id}' class='config_bar_ul_li_text'>${text}</label>`
  5863. ).appendTo(li);
  5864.  
  5865. this.lastItemId = id;
  5866.  
  5867. // 添加右键菜单
  5868. li.on("contextmenu", (event) => {
  5869. event.preventDefault();
  5870. this.menu.css({
  5871. display: "block",
  5872. left: event.pageX,
  5873. top: event.pageY,
  5874. });
  5875.  
  5876. const deleteItem = this.deleteItem;
  5877. const editItem = this.editItem;
  5878.  
  5879. // 移除旧事件
  5880. deleteItem.off("click");
  5881. editItem.off("click");
  5882.  
  5883. // 获取 li 在 ul 中的索引
  5884. const index = li.index();
  5885.  
  5886. deleteItem.on("click", () => this.onDelete(index, li));
  5887. editItem.on("click", () => this.onEdit(index, li));
  5888.  
  5889. $(document).one("click", (event) => {
  5890. if (!this.menu.get(0).contains(event.target)) {
  5891. this.closeContextMenu();
  5892. deleteItem.off("click", () => this.onDelete);
  5893. editItem.off("click", () => this.onEdit);
  5894. }
  5895. });
  5896. });
  5897.  
  5898. return li;
  5899. }
  5900.  
  5901. /**
  5902. * 渲染配置列表
  5903. */
  5904. renderList() {
  5905. const list = this.config_bar_ul;
  5906. list.empty(); // 清空
  5907. this.tempConfig.configurations.forEach((item) => {
  5908. list.append(this.createListItemElement(item["name"]));
  5909. });
  5910.  
  5911. // 添加按钮
  5912. let addButton = OJB_safeCreateJQElement(`<li id='${
  5913. this.prefix
  5914. }add_button' class="tempConfig_add_button">
  5915. <span>+ ${i18next.t("add", { ns: "common" })}</span>
  5916. </li>`);
  5917. this.config_add_button = addButton;
  5918. list.append(addButton);
  5919. addButton.on("click", this.onAdd);
  5920. }
  5921.  
  5922. /**
  5923. * 添加配置项
  5924. */
  5925. onAdd() {
  5926. const configMenu = this.createConfigHTML();
  5927. const structure = this.structure;
  5928.  
  5929. configMenu.on("click", "#tempConfig_save", () => {
  5930. // 检查必填字段
  5931. const { valid, config } = Validator.required(structure);
  5932. if (!valid) return;
  5933.  
  5934. // 检查键值对
  5935. const { valid: checkOk, errorKey } = Validator.checkKeyValuePairs(
  5936. structure,
  5937. config
  5938. );
  5939. if (!checkOk) return;
  5940.  
  5941. this.tempConfig.configurations.push(config);
  5942.  
  5943. this.createListItemElement(config.name).insertBefore(
  5944. this.config_add_button
  5945. );
  5946.  
  5947. configMenu.remove();
  5948. });
  5949.  
  5950. configMenu.on("click", ".btn-close", () => {
  5951. configMenu.remove();
  5952. });
  5953. }
  5954.  
  5955. /**
  5956. * 修改配置项
  5957. * @param {number} index - 配置项索引
  5958. * @param {HTMLElement} li - 配置项
  5959. * @returns {void}
  5960. */
  5961. onEdit(index, li) {
  5962. const configMenu = this.createConfigHTML();
  5963. const structure = this.structure;
  5964.  
  5965. this.closeContextMenu();
  5966.  
  5967. // 填充表单
  5968. for (const [key, { value, type }] of Object.entries(this.structure)) {
  5969. const configValue = this.tempConfig.configurations[index][value];
  5970. const $element = $(key);
  5971. if (type === "checkbox") {
  5972. $element.prop("checked", configValue);
  5973. } else {
  5974. $element.val(configValue);
  5975. }
  5976. }
  5977.  
  5978. configMenu.on("click", "#tempConfig_save", () => {
  5979. // 检查必填字段
  5980. const { valid, config } = Validator.required(structure);
  5981. if (!valid) return;
  5982.  
  5983. // 检查键值对
  5984. const { valid: checkOk, errorKey } = Validator.checkKeyValuePairs(
  5985. structure,
  5986. config
  5987. );
  5988. if (!checkOk) return;
  5989.  
  5990. // 更新配置
  5991. this.tempConfig.configurations[index] = config;
  5992. li.find("label").text(config.name);
  5993.  
  5994. OJB_closeAndRemoveModal(configMenu);
  5995. });
  5996.  
  5997. configMenu.on("click", ".btn-close", () => {
  5998. OJB_closeAndRemoveModal(configMenu);
  5999. });
  6000. }
  6001.  
  6002. /**
  6003. * 删除配置项
  6004. * @param {number} index - 配置项索引
  6005. * @param {HTMLElement} li - 配置项
  6006. * @returns {void}
  6007. */
  6008. onDelete(index, li) {
  6009. this.closeContextMenu();
  6010. this.tempConfig.configurations.splice(index, 1);
  6011. li.remove();
  6012. }
  6013.  
  6014. /**
  6015. * 创建配置编辑页面
  6016. * @returns {JQuery<HTMLElement>} 返回配置编辑页面
  6017. */
  6018. createConfigHTML() {
  6019. const configMenu = OJB_safeCreateJQElement(this.configHTML);
  6020. this.settingMenuDiv.after(configMenu);
  6021. OJB_showModal(configMenu);
  6022. OJB_addDraggable(configMenu);
  6023. elementLocalize(configMenu);
  6024. return configMenu;
  6025. }
  6026.  
  6027. /**
  6028. * 获取配置内容
  6029. * @returns {object} - 配置内容
  6030. */
  6031. getTempConfig() {
  6032. return this.tempConfig;
  6033. }
  6034.  
  6035. /**
  6036. * 注册列表项选中改变监听
  6037. */
  6038. registerChoiceChange() {
  6039. this.config_bar_ul.on("change", "input[type='radio']", (event) => {
  6040. const value = event.target.value;
  6041. this.tempConfig.choice = value;
  6042. });
  6043. }
  6044. }
  6045.  
  6046. const OJBetter_setting_sidebar_HTML = `
  6047. <div class="OJBetter_setting_sidebar">
  6048. <ul>
  6049. <li><a href="#basic-settings" id="sidebar-basic-settings" class="active" data-i18n="settings:sidebar.basic"></a></li>
  6050. <li><a href="#preference-settings" id="sidebar-preference-settings" data-i18n="settings:sidebar.preference"></a></li>
  6051. <li><a href="#translation-settings" id="sidebar-translation-settings" data-i18n="settings:sidebar.translation"></a></li>
  6052. <li><a href="#clist_rating-settings" id="sidebar-clist_rating-settings" data-i18n="settings:sidebar.clist"></a></li>
  6053. <li><a href="#code_editor-settings" id="sidebar-code_editor-settings" data-i18n="settings:sidebar.monaco"></a></li>
  6054. <li><a href="#dev-settings" id="sidebar-dev-settings" data-i18n="settings:sidebar.dev"></a></li>
  6055. <li><a href="#about-settings" id="sidebar-about-settings" data-i18n="settings:sidebar.about"></a></li>
  6056. </ul>
  6057. </div>
  6058. `;
  6059.  
  6060. const basic_settings_HTML = `
  6061. <div id="basic-settings" class="settings-page active">
  6062. <h3 data-i18n="settings:basic.title"></h3>
  6063. <hr>
  6064. <div class='OJBetter_setting_list' style="padding: 0px 10px;">
  6065. <span id="darkMode_span" data-i18n="settings:basic.darkMode.name"></span>
  6066. <div class="dark-mode-selection">
  6067. <label>
  6068. <input class="radio-input" type="radio" name="darkMode" value="dark" />
  6069. <span class="OJBetter_setting_menu_label_text"
  6070. data-i18n="settings:basic.darkMode.options.dark"></span>
  6071. <span class="radio-icon"> </span>
  6072. </label>
  6073. <label>
  6074. <input checked="" class="radio-input" type="radio" name="darkMode" value="light" />
  6075. <span class="OJBetter_setting_menu_label_text"
  6076. data-i18n="settings:basic.darkMode.options.light"></span>
  6077. <span class="radio-icon"> </span>
  6078. </label>
  6079. <label>
  6080. <input class="radio-input" type="radio" name="darkMode" value="follow" />
  6081. <span class="OJBetter_setting_menu_label_text"
  6082. data-i18n="settings:basic.darkMode.options.system"></span>
  6083. <span class="radio-icon"> </span>
  6084. </label>
  6085. </div>
  6086. </div>
  6087. <div class='OJBetter_setting_list'>
  6088. <label for="scriptL10nLanguage" style="display: flex;" data-i18n="settings:localization.scriptLanguageLabel"></label>
  6089. <select id="scriptL10nLanguage" name="scriptL10nLanguage">
  6090. <option value="zh">简体中文</option>
  6091. <option value="zh-Hant">繁體中文</option>
  6092. <option value="en">English</option>
  6093. <option value="de">Deutsch</option>
  6094. <option value="fr">Français</option>
  6095. <option value="ko">한국어</option>
  6096. <option value="pt">Português</option>
  6097. <option value="ja">日本語</option>
  6098. <option value="es">Español</option>
  6099. <option value="it">Italiano</option>
  6100. <option value="hi">हिन्दी</option>
  6101. </select>
  6102. </div>
  6103. <div class='OJBetter_setting_list'>
  6104. <label for="localizationLanguage" style="display: flex;" data-i18n="settings:localization.websiteLanguageLabel"></label>
  6105. <select id="localizationLanguage" name="localizationLanguage">
  6106. <option value="initial">——</option>
  6107. <option value="zh">简体中文</option>
  6108. <option value="zh-Hant">繁體中文</option>
  6109. <option value="de">Deutsch</option>
  6110. <option value="fr">Français</option>
  6111. <option value="ko">한국어</option>
  6112. <option value="pt">Português</option>
  6113. <option value="ja">日本語</option>
  6114. <option value="es">Español</option>
  6115. <option value="it">Italiano</option>
  6116. <option value="hi">हिन्दी</option>
  6117. </select>
  6118. </div>
  6119. <div class='OJBetter_setting_list alert_tip'>
  6120. <div data-i18n="[html]settings:localization.notice.1"></div>
  6121. </div>
  6122. <div class='OJBetter_setting_list alert_tip'>
  6123. <div data-i18n="[html]settings:localization.notice.2"></div>
  6124. </div>
  6125. </div>
  6126. `;
  6127.  
  6128. const translation_settings_HTML = `
  6129. <div id="translation-settings" class="settings-page">
  6130. <h3 data-i18n="settings:translation.title"></h3>
  6131. <hr>
  6132. <h4 data-i18n="settings:translation.options.title"></h4>
  6133. <div class='OJBetter_setting_list'>
  6134. <label for="transTargetLang" style="display: flex;" data-i18n="settings:translation.preference.target.title"></label>
  6135. <div class="help_tip">
  6136. ${helpCircleHTML}
  6137. <div class="tip_text" data-i18n="[html]settings:translation.preference.target.helpText"></div>
  6138. </div>
  6139. <select id="transTargetLang" name="transTargetLang">
  6140. <option value="zh">简体中文</option>
  6141. <option value="zh-Hant">繁體中文</option>
  6142. <option value="de">Deutsch</option>
  6143. <option value="fr">Français</option>
  6144. <option value="ko">한국어</option>
  6145. <option value="pt">Português</option>
  6146. <option value="ja">日本語</option>
  6147. <option value="es">Español</option>
  6148. <option value="it">Italiano</option>
  6149. <option value="hi">हिन्दी</option>
  6150. </select>
  6151. </div>
  6152. <div id="translationServices">
  6153. <label>
  6154. <input type='radio' name='translation' value='deepl'>
  6155. <span class='OJBetter_setting_menu_label_text'
  6156. data-i18n="settings:translation.options.services.deepl"></span>
  6157. </label>
  6158. <label>
  6159. <input type='radio' name='translation' value='iflyrec'>
  6160. <span class='OJBetter_setting_menu_label_text'
  6161. data-i18n="settings:translation.options.services.iflyrec"></span>
  6162. </label>
  6163. <label>
  6164. <input type='radio' name='translation' value='youdao'>
  6165. <span class='OJBetter_setting_menu_label_text'
  6166. data-i18n="settings:translation.options.services.youdao"></span>
  6167. </label>
  6168. <label>
  6169. <input type='radio' name='translation' value='google'>
  6170. <span class='OJBetter_setting_menu_label_text'
  6171. data-i18n="settings:translation.options.services.google"></span>
  6172. </label>
  6173. <label>
  6174. <input type='radio' name='translation' value='caiyun'>
  6175. <span class='OJBetter_setting_menu_label_text'
  6176. data-i18n="settings:translation.options.services.caiyun"></span>
  6177. </label>
  6178. <label>
  6179. <input type='radio' name='translation' value='openai'>
  6180. <span class='OJBetter_setting_menu_label_text'
  6181. data-i18n="settings:translation.options.services.openai.name">
  6182. <div class="help_tip">
  6183. ${helpCircleHTML}
  6184. <div class="tip_text"
  6185. data-i18n="[html]settings:translation.options.services.openai.helpText"></div>
  6186. </div>
  6187. </span>
  6188. </label>
  6189. </div>
  6190. <hr>
  6191. <h4>DeepL</h4>
  6192. <div class='OJBetter_setting_list'>
  6193. <label for="deepl_type" style="display: flex;" data-i18n="settings:translation.deepl.mode.title"></label>
  6194. <div class="help_tip">
  6195. ${helpCircleHTML}
  6196. <div class="tip_text" data-i18n="[html]settings:translation.deepl.mode.helpText"></div>
  6197. </div>
  6198. <select id="deepl_type" name="deepl_type">
  6199. <option value="free" data-i18n="settings:translation.deepl.mode.select.free"></option>
  6200. <option value="api" data-i18n="settings:translation.deepl.mode.select.api"></option>
  6201. </select>
  6202. </div>
  6203. <div id="deepl_config" class="config"></div>
  6204. <div class='OJBetter_setting_list'>
  6205. <label for="enableEmphasisProtection" data-i18n="settings:translation.deepl.enableEmphasisProtection.title"></label>
  6206. <div class="help_tip" style="margin-right: initial;">
  6207. ${helpCircleHTML}
  6208. <div class="tip_text" data-i18n="[html]settings:translation.deepl.enableEmphasisProtection.helpText"></div>
  6209. </div>
  6210. <div class="badge">Official API Only</div>
  6211. <input type="checkbox" id="enableEmphasisProtection" name="enableEmphasisProtection">
  6212. </div>
  6213. <div class='OJBetter_setting_list'>
  6214. <label for="enableLinkProtection" data-i18n="settings:translation.deepl.enableLinkProtection.title"></label>
  6215. <div class="help_tip" style="margin-right: initial;">
  6216. ${helpCircleHTML}
  6217. <div class="tip_text" data-i18n="[html]settings:translation.deepl.enableLinkProtection.helpText"></div>
  6218. </div>
  6219. <div class="badge">Official API Only</div>
  6220. <input type="checkbox" id="enableLinkProtection" name="enableLinkProtection">
  6221. </div>
  6222. <hr>
  6223. <h4>ChatGPT</h4>
  6224. <div id="chatgpt_config" class="config"></div>
  6225. <div class='OJBetter_setting_list'>
  6226. <label for="openai_isStream" data-i18n="settings:translation.chatgpt.isStream.name"></label>
  6227. <div class="help_tip">
  6228. ${helpCircleHTML}
  6229. <div class="tip_text" data-i18n="[html]settings:translation.chatgpt.isStream.helpText"></div>
  6230. </div>
  6231. <input type="checkbox" id="openai_isStream" name="openai_isStream">
  6232. </div>
  6233. <div class='OJBetter_setting_list'>
  6234. <label for="openai_asSystemPrompt" data-i18n="settings:translation.chatgpt.asSystemPrompt.name"></label>
  6235. <div class="help_tip">
  6236. ${helpCircleHTML}
  6237. <div class="tip_text" data-i18n="[html]settings:translation.chatgpt.asSystemPrompt.helpText"></div>
  6238. </div>
  6239. <input type="checkbox" id="openai_asSystemPrompt" name="openai_asSystemPrompt">
  6240. </div>
  6241. <div class="OJBetter_setting_list">
  6242. <label for='openai_customPrompt'>
  6243. <div style="display: flex;align-items: center;">
  6244. <span class="input_label" data-i18n="settings:translation.chatgpt.customPrompt.name"></span>
  6245. <div class="help_tip">
  6246. ${helpCircleHTML}
  6247. <div class="tip_text" data-i18n="[html]settings:translation.chatgpt.customPrompt.helpText"></div>
  6248. </div>
  6249. </div>
  6250. </label>
  6251. <textarea id="openai_customPrompt" placeholder='' require = false data-i18n="[placeholder]settings:translation.chatgpt.customPrompt.placeholder"></textarea>
  6252. </div>
  6253. <hr>
  6254. <h4 data-i18n="settings:translation.preference.title"></h4>
  6255. <div class='OJBetter_setting_list'>
  6256. <label for="comment_translation_choice" style="display: flex;"
  6257. data-i18n="settings:translation.preference.comment_translation_choice.title">
  6258. </label>
  6259. <select id="comment_translation_choice" name="comment_translation_choice">
  6260. <option value="0" data-i18n="settings:translation.preference.comment_translation_choice.services.follow"></option>
  6261. <option value="deepl" data-i18n="settings:translation.preference.comment_translation_choice.services.deepl"></option>
  6262. <option value="iflyrec" data-i18n="settings:translation.preference.comment_translation_choice.services.iflyrec"></option>
  6263. <option value="youdao" data-i18n="settings:translation.preference.comment_translation_choice.services.youdao"></option>
  6264. <option value="google" data-i18n="settings:translation.preference.comment_translation_choice.services.google"></option>
  6265. <option value="caiyun" data-i18n="settings:translation.preference.comment_translation_choice.services.caiyun"></option>
  6266. <option value="openai" data-i18n="settings:translation.preference.comment_translation_choice.services.openai"></option>
  6267. </select>
  6268. </div>
  6269. <hr>
  6270. <h4 data-i18n="settings:translation.autoTranslation.title"></h4>
  6271. <div class='OJBetter_setting_list'>
  6272. <label for="autoTranslation" data-i18n="settings:translation.autoTranslation.enable"></label>
  6273. <div class="help_tip">
  6274. ${helpCircleHTML}
  6275. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.helpText"></div>
  6276. </div>
  6277. <input type="checkbox" id="autoTranslation" name="autoTranslation">
  6278. </div>
  6279. <div class='OJBetter_setting_list'>
  6280. <label for='shortTextLength'>
  6281. <div style="display: flex;align-items: center;"
  6282. data-i18n="settings:translation.autoTranslation.shortTextLength.name"></div>
  6283. </label>
  6284. <div class="help_tip">
  6285. ${helpCircleHTML}
  6286. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.shortTextLength.helpText">
  6287. </div>
  6288. </div>
  6289. <input type='number' id='shortTextLength' class='no_default' require=true data-i18n="[placeholder]settings:translation.autoTranslation.shortTextLength.placeholder">
  6290. <span data-i18n="settings:translation.autoTranslation.shortTextLength.end"></span>
  6291. </div>
  6292. <div class='OJBetter_setting_list'>
  6293. <label for="allowMixTrans" data-i18n="settings:translation.autoTranslation.allowMixTrans.name"></label>
  6294. <div class="help_tip">
  6295. ${helpCircleHTML}
  6296. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.allowMixTrans.helpText">
  6297. </div>
  6298. </div>
  6299. <input type="checkbox" id="allowMixTrans" name="allowMixTrans">
  6300. <div class='OJBetter_checkboxs'>
  6301. <input type="checkbox" id="deepl" name="mixedTranslation" value="deepl">
  6302. <label for="deepl" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.deepl"></label>
  6303. <input type="checkbox" id="iflyrec" name="mixedTranslation" value="iflyrec">
  6304. <label for="iflyrec" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.iflyrec"></label>
  6305. <input type="checkbox" id="youdao" name="mixedTranslation" value="youdao">
  6306. <label for="youdao" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.youdao"></label>
  6307. <input type="checkbox" id="google" name="mixedTranslation" value="google">
  6308. <label for="google" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.google">Google</label>
  6309. <input type="checkbox" id="caiyun" name="mixedTranslation" value="caiyun">
  6310. <label for="caiyun" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.caiyun"></label>
  6311. </div>
  6312. </div>
  6313. <hr>
  6314. <h4 data-i18n="settings:translation.advanced.name"></h4>
  6315. <div class='OJBetter_setting_list'>
  6316. <label for="comment_translation_mode" style="display: flex;" data-i18n="settings:translation.advanced.mode.name"></label>
  6317. <div class="help_tip">
  6318. ${helpCircleHTML}
  6319. <div class="tip_text" data-i18n="[html]settings:translation.advanced.mode.helpText"></div>
  6320. </div>
  6321. <select id="comment_translation_mode" name="comment_translation_mode">
  6322. <option value="0" data-i18n="settings:translation.advanced.mode.options.0"></option>
  6323. <option value="1" data-i18n="settings:translation.advanced.mode.options.1"></option>
  6324. <option value="2" data-i18n="settings:translation.advanced.mode.options.2"></option>
  6325. </select>
  6326. </div>
  6327. <div class='OJBetter_setting_list'>
  6328. <label for="memoryTranslateHistory" data-i18n="settings:translation.advanced.memory.name"></label>
  6329. <div class="help_tip">
  6330. ${helpCircleHTML}
  6331. <div class="tip_text" data-i18n="[html]settings:translation.advanced.memory.helpText"></div>
  6332. </div>
  6333. <input type="checkbox" id="memoryTranslateHistory" name="memoryTranslateHistory">
  6334. </div>
  6335. <div class='OJBetter_setting_list'>
  6336. <label for="translation_retransAction" style="display: flex;" data-i18n="settings:translation.advanced.retrans.name"></label>
  6337. <div class="help_tip">
  6338. ${helpCircleHTML}
  6339. <div class="tip_text" data-i18n="[html]settings:translation.advanced.retrans.helpText"></div>
  6340. </div>
  6341. <select id="translation_retransAction" name="translation_retransAction">
  6342. <option value=0 data-i18n="settings:translation.advanced.retrans.options.0"></option>
  6343. <option value=1 data-i18n="settings:translation.advanced.retrans.options.1"></option>
  6344. </select>
  6345. </div>
  6346. <div class='OJBetter_setting_list'>
  6347. <label for='transWaitTime'>
  6348. <div style="display: flex;align-items: center;" data-i18n="settings:translation.advanced.transWaitTime.name"></div>
  6349. </label>
  6350. <div class="help_tip">
  6351. ${helpCircleHTML}
  6352. <div class="tip_text" data-i18n="[html]settings:translation.advanced.transWaitTime.helpText"></div>
  6353. </div>
  6354. <input type='number' id='transWaitTime' class='no_default' require=true data-i18n="[placeholder]settings:translation.advanced.transWaitTime.placeholder">
  6355. <span data-i18n="settings:translation.advanced.transWaitTime.end"></span>
  6356. </div>
  6357. <div class='OJBetter_setting_list'>
  6358. <label for="translation_replaceSymbol" style="display: flex;" data-i18n="settings:translation.advanced.replaceSymbol.name"></label>
  6359. <div class="help_tip">
  6360. ${helpCircleHTML}
  6361. <div class="tip_text" data-i18n="[html]settings:translation.advanced.replaceSymbol.helpText"></div>
  6362. </div>
  6363. <select id="translation_replaceSymbol" name="translation_replaceSymbol">
  6364. <option value=2 data-i18n="settings:translation.advanced.replaceSymbol.options.2"></option>
  6365. <option value=1 data-i18n="settings:translation.advanced.replaceSymbol.options.1"></option>
  6366. <option value=3 data-i18n="settings:translation.advanced.replaceSymbol.options.3"></option>
  6367. </select>
  6368. </div>
  6369. <div class='OJBetter_setting_list'>
  6370. <label for="filterTextWithoutEmphasis" data-i18n="settings:translation.advanced.filterTextWithoutEmphasis.name"></label>
  6371. <div class="help_tip">
  6372. ${helpCircleHTML}
  6373. <div class="tip_text" data-i18n="[html]settings:translation.advanced.filterTextWithoutEmphasis.helpText"></div>
  6374. </div>
  6375. <input type="checkbox" id="filterTextWithoutEmphasis" name="filterTextWithoutEmphasis">
  6376. </div>
  6377. <div class='OJBetter_setting_list'>
  6378. <label for="forceTurndownConversion" data-i18n="settings:translation.advanced.forceTurndownConversion.name"></label>
  6379. <div class="help_tip">
  6380. ${helpCircleHTML}
  6381. <div class="tip_text" data-i18n="[html]settings:translation.advanced.forceTurndownConversion.helpText"></div>
  6382. </div>
  6383. <input type="checkbox" id="forceTurndownConversion" name="forceTurndownConversion">
  6384. </div>
  6385. </div>
  6386. `;
  6387.  
  6388. const clist_rating_settings_HTML = `
  6389. <div id="clist_rating-settings" class="settings-page">
  6390. <h3 data-i18n="settings:clist.title"></h3>
  6391. <hr>
  6392. <h4 data-i18n="settings:clist.basics.name"></h4>
  6393. <div class='OJBetter_setting_list alert_tip'>
  6394. <div>
  6395. <p data-i18n="[html]settings:clist.basics.notice"></p>
  6396. </div>
  6397. </div>
  6398. <div class='OJBetter_setting_list'>
  6399. <label for='clist_Authorization'>
  6400. <div style="display: flex;align-items: center;">
  6401. <span class="input_label" data-i18n="settings:clist.basics.key.title"></span>
  6402. </div>
  6403. </label>
  6404. <div class="help_tip">
  6405. ${helpCircleHTML}
  6406. <div class="tip_text" data-i18n="[html]settings:clist.basics.key.helpText"></div>
  6407. </div>
  6408. <input type='text' id='clist_Authorization' class='no_default' required="true"
  6409. data-i18n="[placeholder]settings:clist.basics.key.keyPlaceholder">
  6410. </div>
  6411. <hr>
  6412. <h4 data-i18n="settings:clist.displayRating.title"></h4>
  6413. <div class='OJBetter_setting_list'>
  6414. <label for="showClistRating_contest"><span data-i18n="settings:clist.displayRating.contest.name"></span></label>
  6415. <input type="checkbox" id="showClistRating_contest" name="showClistRating_contest">
  6416. </div>
  6417. <div class='OJBetter_setting_list'>
  6418. <label for="showClistRating_problem"><span data-i18n="settings:clist.displayRating.problem.name"></span></label>
  6419. <input type="checkbox" id="showClistRating_problem" name="showClistRating_problem">
  6420. </div>
  6421. <div class='OJBetter_setting_list'>
  6422. <label for="showClistRating_problemset"><span data-i18n="settings:clist.displayRating.problemset.name"></span></label>
  6423. <input type="checkbox" id="showClistRating_problemset" name="showClistRating_problemset">
  6424. </div>
  6425. <hr>
  6426. <div class='OJBetter_setting_list'>
  6427. <label for="RatingHidden"><span data-i18n="settings:clist.spoilerProtection.title"></span></label>
  6428. <div class="help_tip">
  6429. ${helpCircleHTML}
  6430. <div class="tip_text" data-i18n="[html]settings:clist.spoilerProtection.helpText"></div>
  6431. </div>
  6432. <input type="checkbox" id="RatingHidden" name="RatingHidden">
  6433. </div>
  6434. </div>
  6435. `;
  6436.  
  6437. const code_editor_settings_HTML = `
  6438. <div id="code_editor-settings" class="settings-page">
  6439. <h3 data-i18n="settings:codeEditor.title"></h3>
  6440. <hr>
  6441. <h4 data-i18n="settings:codeEditor.basics"></h4>
  6442. <div class='OJBetter_setting_list'>
  6443. <label for="problemPageCodeEditor"><span
  6444. data-i18n="settings:codeEditor.problemPageCodeEditor.label"></span></label>
  6445. <div class="help_tip">
  6446. ${helpCircleHTML}
  6447. <div class="tip_text" data-i18n="settings:codeEditor.problemPageCodeEditor.helpText"></div>
  6448. </div>
  6449. <input type="checkbox" id="problemPageCodeEditor" name="problemPageCodeEditor">
  6450. </div>
  6451. <div class='OJBetter_setting_list'>
  6452. <label for="beautifyPreBlocks"><span
  6453. data-i18n="settings:codeEditor.beautifyPreBlocks.label"></span></label>
  6454. <div class="help_tip">
  6455. ${helpCircleHTML}
  6456. <div class="tip_text" data-i18n="settings:codeEditor.beautifyPreBlocks.helpText"></div>
  6457. </div>
  6458. <input type="checkbox" id="beautifyPreBlocks" name="beautifyPreBlocks">
  6459. </div>
  6460. <hr>
  6461. <h4 data-i18n="settings:codeEditor.preferences.title"></h4>
  6462. <div class='OJBetter_setting_list'>
  6463. <label for="isCodeSubmitConfirm"><span
  6464. data-i18n="settings:codeEditor.preferences.isCodeSubmitConfirm.label"></span></label>
  6465. <div class="help_tip">
  6466. ${helpCircleHTML}
  6467. <div class="tip_text" data-i18n="settings:codeEditor.preferences.isCodeSubmitConfirm.helpText"></div>
  6468. </div>
  6469. <input type="checkbox" id="isCodeSubmitConfirm" name="isCodeSubmitConfirm">
  6470. </div>
  6471. <div class='OJBetter_setting_list'>
  6472. <label for="autoSubmitAfterPass"><span
  6473. data-i18n="settings:codeEditor.preferences.autoSubmitAfterPass.label"></span></label>
  6474. <div class="help_tip">
  6475. ${helpCircleHTML}
  6476. <div class="tip_text" data-i18n="settings:codeEditor.preferences.autoSubmitAfterPass.helpText"></div>
  6477. </div>
  6478. <input type="checkbox" id="autoSubmitAfterPass" name="autoSubmitAfterPass">
  6479. </div>
  6480. <div class='OJBetter_setting_list'>
  6481. <label for="alwaysConsumeMouseWheel"><span
  6482. data-i18n="settings:codeEditor.preferences.alwaysConsumeMouseWheel.label"></span></label>
  6483. <div class="help_tip">
  6484. ${helpCircleHTML}
  6485. <div class="tip_text" data-i18n="settings:codeEditor.preferences.alwaysConsumeMouseWheel.helpText"></div>
  6486. </div>
  6487. <input type="checkbox" id="alwaysConsumeMouseWheel" name="alwaysConsumeMouseWheel">
  6488. </div>
  6489. <div class='OJBetter_setting_list'>
  6490. <label for="autoMemoryCode"><span
  6491. data-i18n="settings:codeEditor.preferences.autoMemoryCode.label"></span></label>
  6492. <div class="help_tip">
  6493. ${helpCircleHTML}
  6494. <div class="tip_text" data-i18n="settings:codeEditor.preferences.autoMemoryCode.helpText"></div>
  6495. </div>
  6496. <input type="checkbox" id="autoMemoryCode" name="autoMemoryCode">
  6497. </div>
  6498. <div class='OJBetter_setting_list'>
  6499. <label for="submitButtonPosition"><span
  6500. data-i18n="settings:codeEditor.preferences.submitButtonPosition.label"></span></label>
  6501. <div class="help_tip">
  6502. ${helpCircleHTML}
  6503. <div class="tip_text" data-i18n="settings:codeEditor.preferences.submitButtonPosition.helpText"></div>
  6504. </div>
  6505. <select id="submitButtonPosition" name="submitButtonPosition">
  6506. <option value="bottom" data-i18n="settings:codeEditor.preferences.submitButtonPosition.options.bottom"></option>
  6507. <option value="top" data-i18n="settings:codeEditor.preferences.submitButtonPosition.options.top"></option>
  6508. </select>
  6509. </div>
  6510. <hr>
  6511. <h4 data-i18n="settings:codeEditor.onlineCodeExecution.title"></h4>
  6512. <label>
  6513. <input type='radio' name='compiler' value='official'>
  6514. <span class='OJBetter_setting_menu_label_text'
  6515. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.codeforces"></span>
  6516. </label>
  6517. <label>
  6518. <input type='radio' name='compiler' value='wandbox'>
  6519. <span class='OJBetter_setting_menu_label_text'
  6520. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.wandbox"></span>
  6521. </label>
  6522. <label>
  6523. <input type='radio' name='compiler' value='rextester'>
  6524. <span class='OJBetter_setting_menu_label_text'
  6525. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.rextester"></span>
  6526. </label>
  6527. <hr>
  6528. <h4 data-i18n="settings:codeEditor.lsp.title"></h4>
  6529. <div class='OJBetter_setting_list'>
  6530. <label for="useLSP"><span data-i18n="settings:codeEditor.lsp.useLSP.label"></span></label>
  6531. <div class="help_tip">
  6532. ${helpCircleHTML}
  6533. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.useLSP.helpText"></div>
  6534. </div>
  6535. <input type="checkbox" id="useLSP" name="useLSP">
  6536. </div>
  6537. <div class='OJBetter_setting_list'>
  6538. <label for='OJBetter_Bridge_WorkUri'>
  6539. <div style="display: flex;align-items: center;">
  6540. <span class="input_label" data-i18n="settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.label"></span>
  6541. </div>
  6542. </label>
  6543. <div class="help_tip">
  6544. ${helpCircleHTML}
  6545. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.helpText"></div>
  6546. </div>
  6547. <input type='text' id='OJBetter_Bridge_WorkUri' class='no_default'
  6548. require=true data-i18n="[placeholder]settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.placeholder">
  6549. </div>
  6550. <div class='OJBetter_setting_list'>
  6551. <label for='OJBetter_Bridge_SocketUrl'>
  6552. <div style="display: flex;align-items: center;">
  6553. <span class="input_label"
  6554. data-i18n="settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.label"></span>
  6555. </div>
  6556. </label>
  6557. <div class="help_tip">
  6558. ${helpCircleHTML}
  6559. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.helpText"></div>
  6560. </div>
  6561. <input type='text' id='OJBetter_Bridge_SocketUrl' class='no_default'
  6562. require=true data-i18n="[placeholder]settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.placeholder">
  6563. </div>
  6564. <hr>
  6565. <h4 data-i18n="settings:codeEditor.staticCompletionEnhancement.title"></h4>
  6566. <div class='OJBetter_setting_list'>
  6567. <label for="cppCodeTemplateComplete"><span
  6568. data-i18n="settings:codeEditor.staticCompletionEnhancement.cppCodeTemplateComplete.label"></span></label>
  6569. <div class="help_tip">
  6570. ${helpCircleHTML}
  6571. <div class="tip_text" data-i18n="[html]settings:codeEditor.staticCompletionEnhancement.cppCodeTemplateComplete.helpText"></div>
  6572. </div>
  6573. <input type="checkbox" id="cppCodeTemplateComplete" name="cppCodeTemplateComplete">
  6574. </div>
  6575. <hr>
  6576. <h5 data-i18n="settings:codeEditor.staticCompletionEnhancement.customization"></h5>
  6577. <div class='OJBetter_setting_list alert_warn'>
  6578. <div>
  6579. <p data-i18n="settings:codeEditor.staticCompletionEnhancement.performanceWarning"></p>
  6580. </div>
  6581. </div>
  6582. <div id="Complet_config" class="config"></div>
  6583. </div>
  6584. `;
  6585.  
  6586. const preference_settings_HTML = `
  6587. <div id="preference-settings" class="settings-page">
  6588. <h3 data-i18n="settings:preference.title"></h3>
  6589. <hr>
  6590. <div class='OJBetter_setting_list'>
  6591. <label for="expandFoldingblocks" data-i18n="settings:basic.expandBlocks"></label>
  6592. <input type="checkbox" id="expandFoldingblocks" name="expandFoldingblocks">
  6593. </div>
  6594. <div class='OJBetter_setting_list'>
  6595. <label for="renderPerfOpt" data-i18n="settings:basic.renderOptimization.label"></label>
  6596. <div class="help_tip">
  6597. ${helpCircleHTML}
  6598. <div class="tip_text" data-i18n="[html]settings:basic.renderOptimization.helpText"></div>
  6599. </div>
  6600. <input type="checkbox" id="renderPerfOpt" name="renderPerfOpt">
  6601. </div>
  6602. <div class='OJBetter_setting_list'>
  6603. <label for="selectElementPerfOpt" data-i18n="settings:basic.selectElementOptimization.label"></label>
  6604. <div class="help_tip">
  6605. ${helpCircleHTML}
  6606. <div class="tip_text" data-i18n="[html]settings:basic.selectElementOptimization.helpText"></div>
  6607. </div>
  6608. <input type="checkbox" id="selectElementPerfOpt" name="selectElementPerfOpt">
  6609. </div>
  6610. <div class='OJBetter_setting_list'>
  6611. <label for="commentPaging" data-i18n="settings:basic.paging.label"></label>
  6612. <div class="help_tip">
  6613. ${helpCircleHTML}
  6614. <div class="tip_text" data-i18n="[html]settings:basic.paging.helpText"></div>
  6615. </div>
  6616. <input type="checkbox" id="commentPaging" name="commentPaging">
  6617. </div>
  6618. <div class='OJBetter_setting_list'>
  6619. <label for="showJumpToLuogu" data-i18n="settings:basic.luoguJump.label"></label>
  6620. <div class="help_tip">
  6621. ${helpCircleHTML}
  6622. <div class="tip_text" data-i18n="[html]settings:basic.luoguJump.helpText"></div>
  6623. </div>
  6624. <input type="checkbox" id="showJumpToLuogu" name="showJumpToLuogu">
  6625. </div>
  6626. <div class='OJBetter_setting_list'>
  6627. <label for="showCF2vjudge" data-i18n="settings:basic.vjudgeJump.label"></label>
  6628. <div class="help_tip">
  6629. ${helpCircleHTML}
  6630. <div class="tip_text" data-i18n="[html]settings:basic.vjudgeJump.helpText"></div>
  6631. </div>
  6632. <input type="checkbox" id="showCF2vjudge" name="showCF2vjudge">
  6633. </div>
  6634. <div class='OJBetter_setting_list'>
  6635. <label for="standingsRecolor" data-i18n="settings:basic.recolor.label"></label>
  6636. <div class="help_tip">
  6637. ${helpCircleHTML}
  6638. <div class="tip_text" data-i18n="[html]settings:basic.recolor.helpText"></div>
  6639. </div>
  6640. <input type="checkbox" id="standingsRecolor" name="standingsRecolor">
  6641. </div>
  6642. <div class='OJBetter_setting_list'>
  6643. <label for="hiddenProblemTag" data-i18n="settings:basic.hiddenProblemTag.label"></label>
  6644. <div class="help_tip">
  6645. ${helpCircleHTML}
  6646. <div class="tip_text" data-i18n="[html]settings:basic.hiddenProblemTag.helpText"></div>
  6647. </div>
  6648. <input type="checkbox" id="hiddenProblemTag" name="hiddenProblemTag">
  6649. </div>
  6650. <div class='OJBetter_setting_list'>
  6651. <label for="showLoading" data-i18n="settings:preference.loadingInfo.label"></label>
  6652. <div class="help_tip">
  6653. ${helpCircleHTML}
  6654. <div class="tip_text" data-i18n="[html]settings:preference.loadingInfo.helpText" data-i18n-options='{ "scriptName": "${OJBetter.state.name}" }'></div>
  6655. </div>
  6656. <input type="checkbox" id="showLoading" name="showLoading">
  6657. </div>
  6658. <div class='OJBetter_setting_list'>
  6659. <label for="hoverTargetAreaDisplay" data-i18n="settings:preference.targetArea.label"></label>
  6660. <div class="help_tip">
  6661. ${helpCircleHTML}
  6662. <div class="tip_text" data-i18n="[html]settings:preference.targetArea.helpText"></div>
  6663. </div>
  6664. <input type="checkbox" id="hoverTargetAreaDisplay" name="hoverTargetAreaDisplay">
  6665. </div>
  6666. <div class='OJBetter_setting_list'>
  6667. <label for='iconButtonSize'>
  6668. <div style="display: flex;align-items: center;" data-i18n="settings:preference.iconButtonSize.title"></div>
  6669. </label>
  6670. <div class="help_tip">
  6671. ${helpCircleHTML}
  6672. <div class="tip_text" data-i18n="[html]settings:preference.iconButtonSize.helpText"></div>
  6673. </div>
  6674. <input type='number' id='iconButtonSize' class='no_default' require=true data-i18n="[placeholder]settings:preference.iconButtonSize.placeholder">
  6675. <span>px</span>
  6676. </div>
  6677. <div class='OJBetter_setting_list'>
  6678. <label for='judgeStatusReplaceText'>
  6679. <div style="display: flex;align-items: center;" data-i18n="settings:preference.judgeStatusReplaceText.title"></div>
  6680. </label>
  6681. <div class="help_tip">
  6682. ${helpCircleHTML}
  6683. <div class="tip_text" data-i18n="[html]settings:preference.judgeStatusReplaceText.helpText"></div>
  6684. </div>
  6685. <textarea type="text" id='judgeStatusReplaceText' class='no_default' data-i18n="[placeholder]settings:preference.judgeStatusReplaceText.placeholder"></textarea>
  6686. </div>
  6687. </div>
  6688. `;
  6689.  
  6690. const dev_settings_HTML = `
  6691. <div id="dev-settings" class="settings-page">
  6692. <h3 data-i18n="settings:dev.title"></h3>
  6693. <hr>
  6694. <div class='OJBetter_setting_list alert_danger'>
  6695. <div>
  6696. <p data-i18n="[html]settings:dev.notice"></p>
  6697. </div>
  6698. </div>
  6699. <hr>
  6700. <h5 data-i18n="settings:dev.load.title"></h5>
  6701. <div class='OJBetter_setting_list'>
  6702. <label for="notWaiteLoaded"><span data-i18n="settings:dev.load.notWaiteLoaded.label"></span></label>
  6703. <div class="help_tip">
  6704. ${helpCircleHTML}
  6705. <div class="tip_text" data-i18n="[html]settings:dev.load.notWaiteLoaded.helpText"></div>
  6706. </div>
  6707. <input type="checkbox" id="notWaiteLoaded" name="notWaiteLoaded">
  6708. </div>
  6709. <hr>
  6710. <h5 data-i18n="settings:dev.l10n.title"></h5>
  6711. <div class='OJBetter_setting_list'>
  6712. <label><span data-i18n="settings:dev.l10n.refreshScrpitCache.label"></span></label>
  6713. <div class="help_tip">
  6714. ${helpCircleHTML}
  6715. <div class="tip_text" data-i18n="[html]settings:dev.l10n.refreshScrpitCache.helpText"></div>
  6716. </div>
  6717. <button type="button" id="l10n_refreshScrpitCacheButton" name="l10n_refreshScrpitCacheButton" data-i18n="settings:dev.l10n.refreshScrpitCache.button"></button>
  6718. </div>
  6719. <hr>
  6720. <h5 data-i18n="settings:dev.l10n_web.title"></h5>
  6721. <div class='OJBetter_setting_list'>
  6722. <label><span data-i18n="settings:dev.l10n_web.refreshScrpitCache.label"></span></label>
  6723. <div class="help_tip">
  6724. ${helpCircleHTML}
  6725. <div class="tip_text" data-i18n="[html]settings:dev.l10n_web.refreshScrpitCache.helpText"></div>
  6726. </div>
  6727. <button type="button" id="l10n_web_refreshScrpitCacheButton" name="l10n_web_refreshScrpitCacheButton" data-i18n="settings:dev.l10n_web.refreshScrpitCache.button"></button>
  6728. </div>
  6729. <div class='OJBetter_setting_list'>
  6730. <label for="isRuleMarkingEnabled"><span data-i18n="settings:dev.l10n_web.isRuleMarkingEnabled.label"></span></label>
  6731. <div class="help_tip">
  6732. ${helpCircleHTML}
  6733. <div class="tip_text" data-i18n="[html]settings:dev.l10n_web.isRuleMarkingEnabled.helpText"></div>
  6734. </div>
  6735. <input type="checkbox" id="isRuleMarkingEnabled" name="isRuleMarkingEnabled">
  6736. </div>
  6737. <hr>
  6738. <h5 data-i18n="settings:dev.indexedDB.title"></h5>
  6739. <div class='OJBetter_setting_list'>
  6740. <label><span data-i18n="settings:dev.indexedDB.clear.label"></span></label>
  6741. <div class="help_tip">
  6742. ${helpCircleHTML}
  6743. <div class="tip_text" data-i18n="[html]settings:dev.indexedDB.clear.helpText"></div>
  6744. </div>
  6745. <button type="button" id="indexedDB_clearButton" name="indexedDB_clearButton" data-i18n="settings:dev.indexedDB.clear.button"></button>
  6746. </div>
  6747. <div class='OJBetter_setting_list'>
  6748. <label><span data-i18n="settings:dev.indexedDB.inputOrExport.label"></span></label>
  6749. <div class="help_tip">
  6750. ${helpCircleHTML}
  6751. <div class="tip_text" data-i18n="[html]settings:dev.indexedDB.inputOrExport.helpText"></div>
  6752. </div>
  6753. <button type="button" id="indexedDB_exportButton" name="indexedDB_exportButton" data-i18n="settings:dev.indexedDB.inputOrExport.export"></button>
  6754. <button type="button" id="indexedDB_importButton" name="indexedDB_importButton" data-i18n="settings:dev.indexedDB.inputOrExport.import"></button>
  6755. </div>
  6756. <hr>
  6757. <h5 data-i18n="settings:dev.configuration.title"></h5>
  6758. <div class='OJBetter_setting_list'>
  6759. <label><span data-i18n="settings:dev.configuration.clear.label"></span></label>
  6760. <div class="help_tip">
  6761. ${helpCircleHTML}
  6762. <div class="tip_text" data-i18n="[html]settings:dev.configuration.clear.helpText"></div>
  6763. </div>
  6764. <button type="button" id="configuration_clearButton" name="configuration_clearButton" data-i18n="settings:dev.configuration.clear.button"></button>
  6765. </div>
  6766. <div class='OJBetter_setting_list'>
  6767. <label><span data-i18n="settings:dev.configuration.inputOrExport.label"></span></label>
  6768. <div class="help_tip">
  6769. ${helpCircleHTML}
  6770. <div class="tip_text" data-i18n="[html]settings:dev.configuration.inputOrExport.helpText"></div>
  6771. </div>
  6772. <button type="button" id="configuration_exportButton" name="configuration_exportButton" data-i18n="settings:dev.configuration.inputOrExport.export"></button>
  6773. <button type="button" id="configuration_importButton" name="configuration_importButton" data-i18n="settings:dev.configuration.inputOrExport.import"></button>
  6774. </div>
  6775. </div>
  6776. `;
  6777.  
  6778. const about_settings_HTML = `
  6779. <div id="about-settings" class="settings-page">
  6780. <h3 data-i18n="settings:about.title"></h3>
  6781. <hr>
  6782. <div class='versionInfo'>
  6783. <p>${OJBetter.state.name}</p>
  6784. <p><span data-i18n="settings:about.version"></span><span id="nowVersion">${OJBetter.state.version}</span></p>
  6785. <p> @北极小狐 <a target="_blank" href="https://github.com/beijixiaohu/OJBetter">Github</a>
  6786. <a target="_blank" href="https://greasyfork.org/zh-CN/scripts/465777">GreasyFork</a></p>
  6787. </div>
  6788. <hr>
  6789. <h5 data-i18n="settings:about.update.title"></h5>
  6790. <div id="thanksforDevChannelNotice" class='OJBetter_setting_list alert_info'>
  6791. <div data-i18n="[html]settings:about.update.thanksforDevChannelNotice"} data-i18n-options='{ "scriptName": "${OJBetter.state.name}" }' ></div>
  6792. </div>
  6793. <div class='OJBetter_setting_list'>
  6794. <label for="updateChannel"><span data-i18n="settings:about.update.channel.label"></span></label>
  6795. <div class="help_tip">
  6796. ${helpCircleHTML}
  6797. <div class="tip_text" data-i18n="[html]settings:about.update.channel.helpText"></div>
  6798. </div>
  6799. <select id="updateChannel" name="updateChannel">
  6800. <option value="release" data-i18n="settings:about.update.channel.options.release"></option>
  6801. <option value="dev" data-i18n="settings:about.update.channel.options.dev"></option>
  6802. </select>
  6803. </div>
  6804. <div class='OJBetter_setting_list'>
  6805. <label for="updateSource"><span data-i18n="settings:about.update.source.label"></span></label>
  6806. <div class="help_tip">
  6807. ${helpCircleHTML}
  6808. <div class="tip_text" data-i18n="[html]settings:about.update.source.helpText"></div>
  6809. </div>
  6810. <select id="updateSource" name="updateSource">
  6811. <option value="greasyfork" data-i18n="settings:about.update.source.options.greasyfork"></option>
  6812. <option value="github" data-i18n="settings:about.update.source.options.github"></option>
  6813. <option value="aliyunoss" data-i18n="settings:about.update.source.options.aliyunoss"></option>
  6814. </select>
  6815. </div>
  6816. </div>
  6817. `;
  6818.  
  6819. const OJBetter_setting_content_HTML = `
  6820. <div class="OJBetter_setting_content">
  6821. ${basic_settings_HTML}
  6822. ${translation_settings_HTML}
  6823. ${clist_rating_settings_HTML}
  6824. ${code_editor_settings_HTML}
  6825. ${preference_settings_HTML}
  6826. ${dev_settings_HTML}
  6827. ${about_settings_HTML}
  6828. </div>
  6829. `;
  6830.  
  6831. // 设置界面HTML
  6832. const OJBetterSettingMenu_HTML = `
  6833. <dialog class='OJBetter_setting_menu' id='OJBetter_setting_menu'>
  6834. <div class="tool-box">
  6835. <button class='ojb_btn ojb_btn_popover top btn-close' type="button">
  6836. <i class="iconfont">&#xe614;</i>
  6837. </button>
  6838. </div>
  6839. <div class="OJBetter_setting_container">
  6840. ${OJBetter_setting_sidebar_HTML}
  6841. ${OJBetter_setting_content_HTML}
  6842. </div>
  6843. </dialog>
  6844. `;
  6845.  
  6846. const apiCustomConfigHTML = (prefix) => {
  6847. return `
  6848. <div class="OJBetter_setting_list">
  6849. <label for='${prefix}_header'>
  6850. <div style="display: flex;align-items: center;">
  6851. <span class="input_label" data-i18n="config:common.advanced.header.label"></span>
  6852. <div class="help_tip">
  6853. ${helpCircleHTML}
  6854. <div class="tip_text" data-i18n="[html]config:common.advanced.header.tipText"></div>
  6855. </div>
  6856. </div>
  6857. </label>
  6858. <textarea id="${prefix}_header" placeholder='' require = false data-i18n="[placeholder]config:common.advanced.header.placeholder"></textarea>
  6859. </div>
  6860. <div class="OJBetter_setting_list">
  6861. <label for='${prefix}_data'>
  6862. <div style="display: flex;align-items: center;">
  6863. <span class="input_label" data-i18n="config:common.advanced.data.label"></span>
  6864. <div class="help_tip">
  6865. ${helpCircleHTML}
  6866. <div class="tip_text" data-i18n="[html]config:common.advanced.data.tipText"></div>
  6867. </div>
  6868. </div>
  6869. </label>
  6870. <textarea id="${prefix}_data" placeholder='' require = false data-i18n="[placeholder]config:common.advanced.data.placeholder"></textarea>
  6871. </div>
  6872. `;
  6873. };
  6874.  
  6875. const apiQuotaConfigHTML = (prefix) => {
  6876. return `
  6877. <div class="OJBetter_setting_list">
  6878. <label for='${prefix}_quota_url'>
  6879. <div style="display: flex;align-items: center;">
  6880. <span class="input_label" data-i18n="config:common.quota.url.label"></span>
  6881. <div class="help_tip">
  6882. ${helpCircleHTML}
  6883. <div class="tip_text" data-i18n="[html]config:common.quota.url.tipText"></div>
  6884. </div>
  6885. </div>
  6886. </label>
  6887. <input type='text' id='${prefix}_quota_url' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:common.quota.url.placeholder">
  6888. </div>
  6889. <div class="OJBetter_setting_list">
  6890. <label for="${prefix}_quota_method" style="display: flex;" data-i18n="config:common.quota.method.label"></label>
  6891. <div class="help_tip">
  6892. ${helpCircleHTML}
  6893. <div class="tip_text" data-i18n="[html]config:common.quota.method.tipText"></div>
  6894. </div>
  6895. <select id="${prefix}_quota_method" name="${prefix}_quota_method">
  6896. <option value="get">GET</option>
  6897. <option value="post">POST</option>
  6898. </select>
  6899. </div>
  6900. <div class="OJBetter_setting_list">
  6901. <label for='${prefix}_quota_header'>
  6902. <div style="display: flex;align-items: center;">
  6903. <span class="input_label" data-i18n="config:common.quota.header.label"></span>
  6904. <div class="help_tip">
  6905. ${helpCircleHTML}
  6906. <div class="tip_text" data-i18n="[html]config:common.quota.header.tipText"></div>
  6907. </div>
  6908. </div>
  6909. </label>
  6910. <textarea id="${prefix}_quota_header" placeholder='' require = false data-i18n="[placeholder]config:common.quota.header.placeholder"></textarea>
  6911. </div>
  6912. <div class="OJBetter_setting_list">
  6913. <label for='${prefix}_quota_data'>
  6914. <div style="display: flex;align-items: center;">
  6915. <span class="input_label" data-i18n="config:common.quota.data.label"></span>
  6916. <div class="help_tip">
  6917. ${helpCircleHTML}
  6918. <div class="tip_text" data-i18n="[html]config:common.quota.data.tipText"></div>
  6919. </div>
  6920. </div>
  6921. </label>
  6922. <textarea id="${prefix}_quota_data" placeholder='' require = false data-i18n="[placeholder]config:common.quota.data.placeholder"></textarea>
  6923. </div>
  6924. <div class="OJBetter_setting_list">
  6925. <div style="display: flex;align-items: center;">
  6926. <span class="input_label" data-i18n="config:common.quota.surplus.label"></span>
  6927. <div class="help_tip">
  6928. ${helpCircleHTML}
  6929. <div class="tip_text" data-i18n="[html]config:common.quota.surplus.tipText"></div>
  6930. </div>
  6931. </div>
  6932. </label>
  6933. <input type='text' id='${prefix}_quota_surplus' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:common.quota.surplus.placeholder">
  6934. </div>
  6935. `;
  6936. };
  6937.  
  6938. const deeplConfigEditHTML = `
  6939. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  6940. <div class='OJBetter_setting_content'>
  6941. <div class="tool-box">
  6942. <button class='ojb_btn ojb_btn_popover top btn-close' type="button">
  6943. <i class="iconfont">&#xe614;</i>
  6944. </button>
  6945. </div>
  6946. <h4 data-i18n="config:deepl.title"></h4>
  6947. <h5 data-i18n="config:deepl.basic.title"></h5>
  6948. <hr>
  6949. <div class="OJBetter_setting_list">
  6950. <label for='name'>
  6951. <span class="input_label" data-i18n="config:deepl.basic.name.label"></span>
  6952. </label>
  6953. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:deepl.basic.name.placeholder">
  6954. </div>
  6955. <div class='OJBetter_setting_list'>
  6956. <label for="deepl_apiGenre" style="display: flex;" data-i18n="config:deepl.genre.label"></label>
  6957. <div class="help_tip">
  6958. ${helpCircleHTML}
  6959. <div class="tip_text" data-i18n="[html]config:deepl.genre.tipText"></div>
  6960. </div>
  6961. <select id="deepl_apiGenre" name="deepl_apiGenre">
  6962. <option value="api-free">api-free</option>
  6963. <option value="api-pro">api-pro</option>
  6964. <option value="deeplx">deeplx</option>
  6965. </select>
  6966. </div>
  6967. <div class="OJBetter_setting_list">
  6968. <label for='deepl_key'>
  6969. <div style="display: flex;align-items: center;">
  6970. <span class="input_label" data-i18n="config:deepl.basic.key.label"></span>
  6971. <div class="help_tip">
  6972. ${helpCircleHTML}
  6973. <div class="tip_text" data-i18n="[html]config:deepl.basic.key.tipText"></div>
  6974. </div>
  6975. </div>
  6976. </label>
  6977. <input type='text' id='deepl_key' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:deepl.basic.key.placeholder">
  6978. </div>
  6979. <div class="OJBetter_setting_list">
  6980. <label for='deepl_proxy'>
  6981. <div style="display: flex;align-items: center;">
  6982. <span class="input_label" data-i18n="config:deepl.basic.proxy.label">Proxy API:</span>
  6983. <div class="help_tip">
  6984. ${helpCircleHTML}
  6985. <div class="tip_text" data-i18n="[html]config:deepl.basic.proxy.tipText"></div>
  6986. </div>
  6987. </div>
  6988. </label>
  6989. <input type='text' id='deepl_proxy' placeholder='' require = false>
  6990. </div>
  6991. <hr>
  6992. <details>
  6993. <summary data-i18n="config:common.advanced.title"></summary>
  6994. ${apiCustomConfigHTML("deepl")}
  6995. </details>
  6996. <details>
  6997. <summary data-i18n="config:common.quota.title"></summary>
  6998. ${apiQuotaConfigHTML("deepl")}
  6999. </details>
  7000. <button id='tempConfig_save' data-i18n="common:save"></button>
  7001. </div>
  7002. </dialog>
  7003. `;
  7004.  
  7005. const chatgptConfigEditHTML = `
  7006. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  7007. <div class='OJBetter_setting_content'>
  7008. <div class="tool-box">
  7009. <button class='ojb_btn ojb_btn_popover top btn-close' type="button">
  7010. <i class="iconfont">&#xe614;</i>
  7011. </button>
  7012. </div>
  7013. <h4 data-i18n="config:chatgpt.title"></h4>
  7014. <h5 data-i18n="config:chatgpt.basic.title"></h5>
  7015. <hr>
  7016. <div class="OJBetter_setting_list">
  7017. <label for='name'>
  7018. <span class="input_label" data-i18n="config:chatgpt.basic.name.label"></span>
  7019. </label>
  7020. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:chatgpt.basic.name.placeholder">
  7021. </div>
  7022. <div class="OJBetter_setting_list">
  7023. <label for='chatgpt_model'>
  7024. <div style="display: flex;align-items: center;">
  7025. <span class="input_label" data-i18n="[html]config:chatgpt.basic.model.label"></span>
  7026. <div class="help_tip">
  7027. ${helpCircleHTML}
  7028. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.model.tipText"></div>
  7029. </div>
  7030. </div>
  7031. </label>
  7032. <input type='text' id='chatgpt_model' placeholder='gpt-3.5-turbo' require = false>
  7033. </div>
  7034. <div class="OJBetter_setting_list">
  7035. <label for='chatgpt_key'>
  7036. <div style="display: flex;align-items: center;">
  7037. <span class="input_label" data-i18n="config:chatgpt.basic.key.label"></span>
  7038. <div class="help_tip">
  7039. ${helpCircleHTML}
  7040. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.key.tipText"></div>
  7041. </div>
  7042. </div>
  7043. </label>
  7044. <input type='text' id='chatgpt_key' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:chatgpt.basic.key.placeholder">
  7045. </div>
  7046. <div class="OJBetter_setting_list">
  7047. <label for='chatgpt_proxy'>
  7048. <div style="display: flex;align-items: center;">
  7049. <span class="input_label" data-i18n="config:chatgpt.basic.proxy.label">Proxy API:</span>
  7050. <div class="help_tip">
  7051. ${helpCircleHTML}
  7052. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.proxy.tipText"></div>
  7053. </div>
  7054. </div>
  7055. </label>
  7056. <input type='text' id='chatgpt_proxy' placeholder='https://api.openai.com/v1/chat/completions' require = false>
  7057. </div>
  7058. <hr>
  7059. <details>
  7060. <summary data-i18n="config:common.advanced.title"></summary>
  7061. ${apiCustomConfigHTML("chatgpt")}
  7062. </details>
  7063. <details>
  7064. <summary data-i18n="config:common.quota.title"></summary>
  7065. ${apiQuotaConfigHTML("chatgpt")}
  7066. </details>
  7067. <button id='tempConfig_save' data-i18n="common:save"></button>
  7068. </div>
  7069. </dialog>
  7070. `;
  7071.  
  7072. const CompletConfigEditHTML = `
  7073. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  7074. <div class='OJBetter_setting_content'>
  7075. <div class="tool-box">
  7076. <button class='ojb_btn ojb_btn_popover top btn-close' type="button">
  7077. <i class="iconfont">&#xe614;</i>
  7078. </button>
  7079. </div>
  7080. <h4 data-i18n="config:complet.title"></h4>
  7081. <hr>
  7082. <div class="OJBetter_setting_list">
  7083. <label for='name'>
  7084. <span class="input_label" data-i18n="config:complet.name.label"></span>
  7085. </label>
  7086. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:complet.name.placeholder">
  7087. </div>
  7088. <div class='OJBetter_setting_list'>
  7089. <label for="complet_isChoose"><span id="loaded_span" data-i18n="config:complet.choose.label"></span></label>
  7090. <input type="checkbox" id="complet_isChoose" name="complet_isChoose" require = false>
  7091. </div>
  7092. <div class='OJBetter_setting_list'>
  7093. <label for="complet_genre" style="display: flex;" data-i18n="config:complet.genre.label"></label>
  7094. <div class="help_tip">
  7095. ${helpCircleHTML}
  7096. <div class="tip_text" data-i18n="[html]config:complet.genre.tipText"></div>
  7097. </div>
  7098. <select id="complet_genre" name="complet_genre">
  7099. <option value="monaco">monaco</option>
  7100. <option value="ace">ace</option>
  7101. </select>
  7102. </div>
  7103. <div class='OJBetter_setting_list'>
  7104. <label for="complet_language" style="display: flex;" data-i18n="config:complet.language.label"></label>
  7105. <select id="complet_language" name="complet_language">
  7106. <option value="cpp">cpp</option>
  7107. <option value="python">python</option>
  7108. <option value="java">java</option>
  7109. <option value="c">c</option>
  7110. </select>
  7111. </div>
  7112. <div class="OJBetter_setting_list">
  7113. <label for='complet_jsonUrl'>
  7114. <div style="display: flex;align-items: center;">
  7115. <span class="input_label">JSON URL:</span>
  7116. <div class="help_tip">
  7117. ${helpCircleHTML}
  7118. <div class="tip_text" data-i18n="[html]config:complet.jsonurl.tipText"></div>
  7119. </div>
  7120. </div>
  7121. </label>
  7122. <div class='OJBetter_setting_list alert_warn' data-i18n="[html]config:complet.jsonurl.alert"></div>
  7123. <input type='text' id='complet_jsonUrl' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:complet.jsonurl.placeholder">
  7124. </div>
  7125. <button id='tempConfig_save' data-i18n="common:save"></button>
  7126. </div>
  7127. </dialog>
  7128. `;
  7129.  
  7130. /**
  7131. * 加载设置按钮面板
  7132. */
  7133. async function initSettingsPanel() {
  7134. /**
  7135. * 添加右上角设置按钮
  7136. * @param {string} location 位置选择器
  7137. * @param {string} method 插入方法
  7138. */
  7139. function insertOJBetterSettingButton(location, method) {
  7140. $(location)[method](`<button class='ojb_btn OJBetter_setting'>
  7141. ${OJBetter.state.name} ${i18next.t("settings", {
  7142. ns: "common",
  7143. })}</button>`);
  7144. }
  7145.  
  7146. /**
  7147. * ============================================
  7148. * 该网站插入设置按钮的位置和方式
  7149. */
  7150. insertOJBetterSettingButton(".lang-chooser", "before");
  7151. insertOJBetterSettingButton(".enter-or-register-box", "after");
  7152. if (OJBetter.typeOfPage.is_completeProblemset)
  7153. insertOJBetterSettingButton(".lang", "before");
  7154. /**
  7155. * ============================================
  7156. */
  7157.  
  7158. const $settingBtns = $(".OJBetter_setting");
  7159. $settingBtns.click(() => {
  7160. $settingBtns.prop("disabled", true).addClass("open");
  7161.  
  7162. // 设置面板div
  7163. const settingMenu = OJB_safeCreateJQElement(OJBetterSettingMenu_HTML);
  7164. $("body").append(settingMenu);
  7165.  
  7166. elementLocalize(settingMenu); // 加载i18n
  7167. OJB_showModal(settingMenu);
  7168. OJB_addDraggable($("#OJBetter_setting_menu")); // 窗口支持拖拽
  7169.  
  7170. // help帮助悬浮窗位置更新
  7171. $(document).on("mouseenter", ".help-icon", function (event) {
  7172. var menuOffset = $(".OJBetter_setting_menu:last").offset();
  7173. var mouseX = event.pageX - menuOffset.left;
  7174. var mouseY = event.pageY - menuOffset.top;
  7175.  
  7176. $(".tip_text").css({
  7177. top: mouseY + "px",
  7178. left: mouseX + "px",
  7179. });
  7180. });
  7181.  
  7182. // 选项卡切换
  7183. $(".OJBetter_setting_sidebar a").click(function (event) {
  7184. event.preventDefault();
  7185. $(".OJBetter_setting_sidebar a").removeClass("active");
  7186. $(".settings-page").removeClass("active");
  7187. $(this).addClass("active");
  7188. const targetPageId = $(this).attr("href").substring(1);
  7189. $("#" + targetPageId).addClass("active");
  7190. });
  7191.  
  7192. /**
  7193. * 更新单选按钮组的可用状态
  7194. * @param {string} selector 单选按钮组的选择器
  7195. * @param {string} targetLanguage 目标语言
  7196. * @param {Object} translationSupport 翻译支持的语言对应表
  7197. */
  7198. const updateRadioButtonsAvailability = (selector, targetLanguage) => {
  7199. Object.entries(OJBetter.supportList.translationSupport).forEach(
  7200. ([service, languages]) => {
  7201. const radioButton = $(selector).find(`input[value="${service}"]`);
  7202. const isEnabled = languages[targetLanguage];
  7203. $(radioButton).prop("disabled", !isEnabled);
  7204. if (!isEnabled) {
  7205. $(radioButton).prop("checked", false);
  7206. }
  7207. }
  7208. );
  7209. };
  7210.  
  7211. /**
  7212. * 检查下拉框选中项是否有效,若无效则清空
  7213. * @param {string} selector 下拉框的选择器
  7214. */
  7215. const validateSelectOption = (selector) => {
  7216. const selectedValue = $(selector).val();
  7217. if (!selectedValue) {
  7218. $(selector).val("");
  7219. }
  7220. };
  7221.  
  7222. /**
  7223. * 更新翻译目标语言下拉框的可用状态
  7224. * @param {string} selector 下拉框的选择器
  7225. * @param {string} targetLanguage 目标语言
  7226. * @param {Object} translationSupport 翻译支持的语言对应表
  7227. */
  7228. const updateSelectOptionsAvailability = (selector, targetLanguage) => {
  7229. $(selector)
  7230. .children("option")
  7231. .each(function () {
  7232. const optionValue = $(this).val();
  7233. const isEnabled = OJBetter.supportList.translationSupport[optionValue]
  7234. ? OJBetter.supportList.translationSupport[optionValue][
  7235. targetLanguage
  7236. ]
  7237. : true;
  7238. $(this).prop("disabled", !isEnabled);
  7239. });
  7240. validateSelectOption(selector);
  7241. };
  7242.  
  7243. /**
  7244. * 更新翻译服务复选框的可用状态
  7245. * @param {string} selector 复选框的选择器
  7246. * @param {string} targetLanguage 目标语言
  7247. * @param {Object} translationSupport 翻译支持的语言对应表
  7248. */
  7249. const updateCheckboxesAvailability = (selector, targetLanguage) => {
  7250. $(selector)
  7251. .children("input")
  7252. .each(function () {
  7253. const checkboxValue = $(this).val();
  7254. const isEnabled =
  7255. OJBetter.supportList.translationSupport[checkboxValue][
  7256. targetLanguage
  7257. ];
  7258. $(this).prop("disabled", !isEnabled);
  7259. if (!isEnabled) {
  7260. $(this).prop("checked", false);
  7261. }
  7262. });
  7263. };
  7264.  
  7265. /**
  7266. * 更新更新源下拉框的可用状态
  7267. * @param {string} selector 下拉框的选择器
  7268. * @param {string} targetLanguage 目标语言
  7269. * @param {Object} translationSupport 翻译支持的语言对应表
  7270. */
  7271. const updateUpdateSourceSelectOptionsAvailability = (
  7272. selector,
  7273. updateChannel
  7274. ) => {
  7275. $(selector)
  7276. .children("option")
  7277. .each(function () {
  7278. const optionValue = $(this).val();
  7279. const isEnabled =
  7280. OJBetter.supportList.updateSourceSupportList[optionValue][
  7281. updateChannel
  7282. ];
  7283. $(this).prop("disabled", !isEnabled);
  7284. });
  7285. validateSelectOption(selector);
  7286. };
  7287.  
  7288. /**
  7289. * 创建配置结构
  7290. * @param {string} type - 该字段的在表单中的类型
  7291. * @param {string} value - 在配置中的键值
  7292. * @param {boolean} require - 是否是表单的必填项
  7293. * @param {string} [check=""] check - 调用的合法性检查
  7294. */
  7295. function createStructure(type, value, require, check = "") {
  7296. return { type, value, require, check };
  7297. }
  7298.  
  7299. // deepl配置
  7300. const deeplStructure = {
  7301. "#name": createStructure("text", "name", true),
  7302. "#deepl_apiGenre": createStructure("text", "apiGenre", true),
  7303. "#deepl_key": createStructure("text", "key", false),
  7304. "#deepl_proxy": createStructure("text", "proxy", false),
  7305. "#deepl_header": createStructure(
  7306. "text",
  7307. "_header",
  7308. false,
  7309. "keyValuePairs"
  7310. ),
  7311. "#deepl_data": createStructure("text", "_data", false, "keyValuePairs"),
  7312. "#deepl_quota_url": createStructure("text", "quota_url", false),
  7313. "#deepl_quota_method": createStructure("text", "quota_method", false),
  7314. "#deepl_quota_header": createStructure(
  7315. "text",
  7316. "quota_header",
  7317. false,
  7318. "keyValuePairs"
  7319. ),
  7320. "#deepl_quota_data": createStructure(
  7321. "text",
  7322. "quota_data",
  7323. false,
  7324. "keyValuePairs"
  7325. ),
  7326. "#deepl_quota_surplus": createStructure(
  7327. "text",
  7328. "quota_surplus",
  7329. false,
  7330. "dotSeparatedPath"
  7331. ),
  7332. };
  7333. let tempConfig_deepl = GM_getValue("deepl_config"); // 获取配置信息
  7334. const configManager_deepl = new ConfigManager(
  7335. "#deepl_config",
  7336. "deepl_config_",
  7337. tempConfig_deepl,
  7338. deeplStructure,
  7339. deeplConfigEditHTML
  7340. );
  7341. configManager_deepl.registerChoiceChange();
  7342.  
  7343. // chatgpt配置
  7344. const chatgptStructure = {
  7345. "#name": createStructure("text", "name", true),
  7346. "#chatgpt_model": createStructure("text", "model", false),
  7347. "#chatgpt_key": createStructure("text", "key", true),
  7348. "#chatgpt_proxy": createStructure("text", "proxy", false),
  7349. "#chatgpt_header": createStructure(
  7350. "text",
  7351. "_header",
  7352. false,
  7353. "keyValuePairs"
  7354. ),
  7355. "#chatgpt_data": createStructure("text", "_data", false, "keyValuePairs"),
  7356. "#chatgpt_quota_url": createStructure("text", "quota_url", false),
  7357. "#chatgpt_quota_header": createStructure(
  7358. "text",
  7359. "quota_header",
  7360. false,
  7361. "keyValuePairs"
  7362. ),
  7363. "#chatgpt_quota_data": createStructure(
  7364. "text",
  7365. "quota_data",
  7366. false,
  7367. "keyValuePairs"
  7368. ),
  7369. "#chatgpt_quota_surplus": createStructure(
  7370. "text",
  7371. "quota_surplus",
  7372. false,
  7373. "dotSeparatedPath"
  7374. ),
  7375. "#chatgpt_quota_method": createStructure("text", "quota_method", false),
  7376. };
  7377. let tempConfig_chatgpt = GM_getValue("chatgpt_config"); // 获取配置信息
  7378. const configManager_chatgpt = new ConfigManager(
  7379. "#chatgpt_config",
  7380. "chatgpt_config_",
  7381. tempConfig_chatgpt,
  7382. chatgptStructure,
  7383. chatgptConfigEditHTML
  7384. );
  7385. configManager_chatgpt.registerChoiceChange();
  7386.  
  7387. // Complet配置
  7388. const CompletStructure = {
  7389. "#name": createStructure("text", "name", true),
  7390. "#complet_isChoose": createStructure("checkbox", "isChoose", true),
  7391. "#complet_genre": createStructure("text", "genre", true),
  7392. "#complet_language": createStructure("text", "language", true),
  7393. "#complet_jsonUrl": createStructure("text", "jsonUrl", true),
  7394. };
  7395. let tempConfig_Complet = GM_getValue("Complet_config"); // 获取配置信息
  7396. const configManager_complet = new ConfigManager(
  7397. "#Complet_config",
  7398. "complet_config_",
  7399. tempConfig_Complet,
  7400. CompletStructure,
  7401. CompletConfigEditHTML,
  7402. false
  7403. );
  7404.  
  7405. // 状态更新
  7406. $("input[name='darkMode'][value='" + OJBetter.basic.darkMode + "']").prop(
  7407. "checked",
  7408. true
  7409. );
  7410. $("#showLoading").prop("checked", GM_getValue("showLoading") === true);
  7411. $("#expandFoldingblocks").prop(
  7412. "checked",
  7413. GM_getValue("expandFoldingblocks") === true
  7414. );
  7415. $("#renderPerfOpt").prop("checked", GM_getValue("renderPerfOpt") === true);
  7416. $("#selectElementPerfOpt").prop(
  7417. "checked",
  7418. GM_getValue("selectElementPerfOpt") === true
  7419. );
  7420. $("#commentPaging").prop("checked", GM_getValue("commentPaging") === true);
  7421. $("#standingsRecolor").prop(
  7422. "checked",
  7423. GM_getValue("standingsRecolor") === true
  7424. );
  7425. $("#hiddenProblemTag").prop(
  7426. "checked",
  7427. GM_getValue("hiddenProblemTag") === true
  7428. );
  7429. $("#showJumpToLuogu").prop(
  7430. "checked",
  7431. GM_getValue("showJumpToLuogu") === true
  7432. );
  7433. $("#showCF2vjudge").prop("checked", GM_getValue("showCF2vjudge") === true);
  7434. $("#hoverTargetAreaDisplay").prop(
  7435. "checked",
  7436. GM_getValue("hoverTargetAreaDisplay") === true
  7437. );
  7438. $("#showClistRating_contest").prop(
  7439. "checked",
  7440. GM_getValue("showClistRating_contest") === true
  7441. );
  7442. $("#showClistRating_problemset").prop(
  7443. "checked",
  7444. GM_getValue("showClistRating_problemset") === true
  7445. );
  7446. $("#showClistRating_problem").prop(
  7447. "checked",
  7448. GM_getValue("showClistRating_problem") === true
  7449. );
  7450. $("#RatingHidden").prop("checked", GM_getValue("RatingHidden") === true);
  7451. $("#scriptL10nLanguage").val(GM_getValue("scriptL10nLanguage"));
  7452. $("#localizationLanguage").val(GM_getValue("localizationLanguage"));
  7453. $(
  7454. "input[name='translation'][value='" + OJBetter.translation.choice + "']"
  7455. ).prop("checked", true);
  7456. $("input[name='translation']").css("color", "gray");
  7457. $("#deepl_type").val(GM_getValue("deepl_type"));
  7458. $("#deepl_config_config_bar_ul")
  7459. .find(
  7460. `input[name='deepl_config_config_item'][value='${tempConfig_deepl.choice}']`
  7461. )
  7462. .prop("checked", true);
  7463. $("#enableEmphasisProtection").prop(
  7464. "checked",
  7465. GM_getValue("enableEmphasisProtection") === true
  7466. );
  7467. $("#enableLinkProtection").prop(
  7468. "checked",
  7469. GM_getValue("enableLinkProtection") === true
  7470. );
  7471. $("#chatgpt_config_config_bar_ul")
  7472. .find(
  7473. `input[name='chatgpt_config_config_item'][value='${tempConfig_chatgpt.choice}']`
  7474. )
  7475. .prop("checked", true);
  7476. $("#openai_isStream").prop(
  7477. "checked",
  7478. GM_getValue("openai_isStream") === true
  7479. );
  7480. $("#openai_asSystemPrompt").prop(
  7481. "checked",
  7482. GM_getValue("openai_asSystemPrompt") === true
  7483. );
  7484. $("#openai_customPrompt").val(GM_getValue("openai_customPrompt"));
  7485. $("#comment_translation_choice").val(
  7486. GM_getValue("commentTranslationChoice")
  7487. );
  7488. $("#iconButtonSize").val(GM_getValue("iconButtonSize"));
  7489. $("#judgeStatusReplaceText").val(GM_getValue("judgeStatusReplaceText"));
  7490. $("#autoTranslation").prop(
  7491. "checked",
  7492. GM_getValue("autoTranslation") === true
  7493. );
  7494. $("#shortTextLength").val(GM_getValue("shortTextLength"));
  7495. $("#allowMixTrans").prop("checked", GM_getValue("allowMixTrans") === true);
  7496. $(".OJBetter_checkboxs")
  7497. .find('input[type="checkbox"][name="mixedTranslation"]')
  7498. .each(function () {
  7499. if (
  7500. OJBetter.translation.auto.mixTrans.servers.indexOf($(this).val()) > -1
  7501. ) {
  7502. $(this).prop("checked", true);
  7503. }
  7504. });
  7505. // 翻译目标语言下拉框
  7506. $("#transTargetLang").change(function () {
  7507. var selectedLang = $(this).val();
  7508. updateRadioButtonsAvailability("#translationServices", selectedLang);
  7509. updateSelectOptionsAvailability(
  7510. "#comment_translation_choice",
  7511. selectedLang
  7512. );
  7513. updateCheckboxesAvailability(".OJBetter_checkboxs", selectedLang);
  7514. });
  7515. $("#transTargetLang").val(GM_getValue("transTargetLang"));
  7516. $("#transTargetLang").change();
  7517. //
  7518. $("#comment_translation_mode").val(GM_getValue("commentTranslationMode"));
  7519. $("#memoryTranslateHistory").prop(
  7520. "checked",
  7521. GM_getValue("memoryTranslateHistory") === true
  7522. );
  7523. $("#transWaitTime").val(GM_getValue("transWaitTime"));
  7524. $("#translation_replaceSymbol").val(GM_getValue("replaceSymbol"));
  7525. $("#filterTextWithoutEmphasis").prop(
  7526. "checked",
  7527. GM_getValue("filterTextWithoutEmphasis") === true
  7528. );
  7529. $("#forceTurndownConversion").prop(
  7530. "checked",
  7531. GM_getValue("forceTurndownConversion") === true
  7532. );
  7533. $("#translation_retransAction").val(GM_getValue("retransAction"));
  7534. $("#clist_Authorization").val(GM_getValue("clist_Authorization"));
  7535. $("#problemPageCodeEditor").prop(
  7536. "checked",
  7537. GM_getValue("problemPageCodeEditor") === true
  7538. );
  7539. $("#beautifyPreBlocks").prop(
  7540. "checked",
  7541. GM_getValue("beautifyPreBlocks") === true
  7542. );
  7543. $("#isCodeSubmitConfirm").prop(
  7544. "checked",
  7545. GM_getValue("isCodeSubmitConfirm") === true
  7546. );
  7547. $("#autoSubmitAfterPass").prop(
  7548. "checked",
  7549. GM_getValue("autoSubmitAfterPass") === true
  7550. );
  7551. $("#alwaysConsumeMouseWheel").prop(
  7552. "checked",
  7553. GM_getValue("alwaysConsumeMouseWheel") === true
  7554. );
  7555. $("#autoMemoryCode").prop(
  7556. "checked",
  7557. GM_getValue("autoMemoryCode") === true
  7558. );
  7559. $("#submitButtonPosition").val(GM_getValue("submitButtonPosition"));
  7560. $("#cppCodeTemplateComplete").prop(
  7561. "checked",
  7562. GM_getValue("cppCodeTemplateComplete") === true
  7563. );
  7564. $("#useLSP").prop("checked", GM_getValue("useLSP") === true);
  7565. $("#OJBetter_Bridge_WorkUri").val(GM_getValue("OJBetter_Bridge_WorkUri"));
  7566. $("#OJBetter_Bridge_SocketUrl").val(
  7567. GM_getValue("OJBetter_Bridge_SocketUrl")
  7568. );
  7569. $(
  7570. "input[name='compiler'][value='" +
  7571. OJBetter.monaco.onlineCompilerChoice +
  7572. "']"
  7573. ).prop("checked", true);
  7574. $("input[name='compiler']").css("color", "gray");
  7575. // 调试
  7576. $("#notWaiteLoaded").prop(
  7577. "checked",
  7578. GM_getValue("notWaiteLoaded") === true
  7579. );
  7580. $("#l10n_refreshScrpitCacheButton").click(clearI18nextCache);
  7581. $("#l10n_web_refreshScrpitCacheButton").click(clearWebsiteL10nData);
  7582. $("#isRuleMarkingEnabled").prop(
  7583. "checked",
  7584. GM_getValue("isRuleMarkingEnabled") === true
  7585. );
  7586. $("#indexedDB_clearButton").click(async () => {
  7587. await clearDatabase();
  7588. });
  7589. $("#indexedDB_exportButton").click(async () => {
  7590. downloadDataAsFile(await exportDatabase(), "database_export.json");
  7591. });
  7592. $("#indexedDB_importButton").click(() => {
  7593. readFileInput(async (fileContent) => {
  7594. await importDatabase(fileContent);
  7595. });
  7596. });
  7597. $("#configuration_clearButton").click(deleteAllConfigSettings);
  7598. $("#configuration_exportButton").click(() => {
  7599. downloadDataAsFile(exportSettingsToJSON(), "configuration_export.json");
  7600. });
  7601. $("#configuration_importButton").click(() => {
  7602. readFileInput((fileContent) => {
  7603. importSettingsFromJSON(fileContent);
  7604. });
  7605. });
  7606. // 关于
  7607. $("#updateChannel").val(GM_getValue("updateChannel"));
  7608. $("#updateSource").val(GM_getValue("updateSource"));
  7609. $("#updateChannel").change(function () {
  7610. var selectedLang = $(this).val();
  7611. updateUpdateSourceSelectOptionsAvailability(
  7612. "#updateSource",
  7613. selectedLang
  7614. );
  7615. if (selectedLang == "dev") $("#thanksforDevChannelNotice").show();
  7616. else $("#thanksforDevChannelNotice").hide();
  7617. });
  7618. $("#updateChannel").change();
  7619.  
  7620. // 关闭
  7621. settingMenu.on("click", ".btn-close", async () => {
  7622. // 设置的数据
  7623. const settings = {
  7624. darkMode: $("input[name='darkMode']:checked").val(),
  7625. showLoading: $("#showLoading").prop("checked"),
  7626. hoverTargetAreaDisplay: $("#hoverTargetAreaDisplay").prop("checked"),
  7627. expandFoldingblocks: $("#expandFoldingblocks").prop("checked"),
  7628. renderPerfOpt: $("#renderPerfOpt").prop("checked"),
  7629. selectElementPerfOpt: $("#selectElementPerfOpt").prop("checked"),
  7630. commentPaging: $("#commentPaging").prop("checked"),
  7631. standingsRecolor: $("#standingsRecolor").prop("checked"),
  7632. hiddenProblemTag: $("#hiddenProblemTag").prop("checked"),
  7633. showJumpToLuogu: $("#showJumpToLuogu").prop("checked"),
  7634. showCF2vjudge: $("#showCF2vjudge").prop("checked"),
  7635. scriptL10nLanguage: $("#scriptL10nLanguage").val(),
  7636. localizationLanguage: $("#localizationLanguage").val(),
  7637. transTargetLang: $("#transTargetLang").val(),
  7638. translation: $("input[name='translation']:checked").val(),
  7639. deepl_type: $("#deepl_type").val(),
  7640. enableEmphasisProtection: $("#enableEmphasisProtection").prop(
  7641. "checked"
  7642. ),
  7643. enableLinkProtection: $("#enableLinkProtection").prop("checked"),
  7644. openai_isStream: $("#openai_isStream").prop("checked"),
  7645. openai_asSystemPrompt: $("#openai_asSystemPrompt").prop("checked"),
  7646. openai_customPrompt: $("#openai_customPrompt").val(),
  7647. commentTranslationChoice: $("#comment_translation_choice").val(),
  7648. iconButtonSize: $("#iconButtonSize").val(),
  7649. judgeStatusReplaceText: $("#judgeStatusReplaceText").val(),
  7650. autoTranslation: $("#autoTranslation").prop("checked"),
  7651. shortTextLength: $("#shortTextLength").val(),
  7652. allowMixTrans: $("#allowMixTrans").prop("checked"),
  7653. mixedTranslation: (() => {
  7654. let mixedTranslation = [];
  7655. $(".OJBetter_checkboxs")
  7656. .find('input[type="checkbox"][name="mixedTranslation"]')
  7657. .each(function () {
  7658. if ($(this).is(":checked")) {
  7659. mixedTranslation.push($(this).val());
  7660. }
  7661. });
  7662. return mixedTranslation;
  7663. })(),
  7664. commentTranslationMode: $("#comment_translation_mode").val(),
  7665. memoryTranslateHistory: $("#memoryTranslateHistory").prop("checked"),
  7666. transWaitTime: $("#transWaitTime").val(),
  7667. replaceSymbol: $("#translation_replaceSymbol").val(),
  7668. filterTextWithoutEmphasis: $("#filterTextWithoutEmphasis").prop(
  7669. "checked"
  7670. ),
  7671. forceTurndownConversion: $("#forceTurndownConversion").prop("checked"),
  7672. retransAction: $("#translation_retransAction").val(),
  7673. showClistRating_contest: $("#showClistRating_contest").prop("checked"),
  7674. showClistRating_problemset: $("#showClistRating_problemset").prop(
  7675. "checked"
  7676. ),
  7677. showClistRating_problem: $("#showClistRating_problem").prop("checked"),
  7678. RatingHidden: $("#RatingHidden").prop("checked"),
  7679. clist_Authorization: $("#clist_Authorization").val(),
  7680. problemPageCodeEditor: $("#problemPageCodeEditor").prop("checked"),
  7681. beautifyPreBlocks: $("#beautifyPreBlocks").prop("checked"),
  7682. isCodeSubmitConfirm: $("#isCodeSubmitConfirm").prop("checked"),
  7683. autoSubmitAfterPass: $("#autoSubmitAfterPass").prop("checked"),
  7684. alwaysConsumeMouseWheel: $("#alwaysConsumeMouseWheel").prop("checked"),
  7685. autoMemoryCode: $("#autoMemoryCode").prop("checked"),
  7686. submitButtonPosition: $("#submitButtonPosition").val(),
  7687. cppCodeTemplateComplete: $("#cppCodeTemplateComplete").prop("checked"),
  7688. useLSP: $("#useLSP").prop("checked"),
  7689. OJBetter_Bridge_WorkUri: $("#OJBetter_Bridge_WorkUri")
  7690. .val()
  7691. .replace(/\\/g, "/")
  7692. .replace(/\/$/, ""),
  7693. OJBetter_Bridge_SocketUrl: $("#OJBetter_Bridge_SocketUrl").val(),
  7694. onlineCompilerChoice: $("input[name='compiler']:checked").val(),
  7695. notWaiteLoaded: $("#notWaiteLoaded").prop("checked"),
  7696. isRuleMarkingEnabled: $("#isRuleMarkingEnabled").prop("checked"),
  7697. updateChannel: $("#updateChannel").val(),
  7698. updateSource: $("#updateSource").val(),
  7699. };
  7700. // tempConfigs的数据
  7701. const tempConfigs = {
  7702. deepl_config: configManager_deepl.getTempConfig(),
  7703. chatgpt_config: configManager_chatgpt.getTempConfig(),
  7704. Complet_config: configManager_complet.getTempConfig(),
  7705. };
  7706.  
  7707. // 判断是否改变
  7708. let changes = {};
  7709. const combinedConfigs = Object.assign({}, settings, tempConfigs); // 合并settings和tempConfigs对象
  7710. for (const [key, value] of Object.entries(combinedConfigs)) {
  7711. const storedValue = GM_getValue(key);
  7712. if (!OJB_deepEquals(value, storedValue)) {
  7713. changes[key] = { oldValue: storedValue, newValue: value };
  7714. }
  7715. }
  7716.  
  7717. // 如果changes对象不为空,则有变化
  7718. if (Object.keys(changes).length > 0) {
  7719. console.log("Changes detected:", changes);
  7720. const shouldSave = await OJB_createDialog(
  7721. i18next.t("saveSetting.title", { ns: "dialog" }),
  7722. i18next.t("saveSetting.content", { ns: "dialog" }),
  7723. [
  7724. i18next.t("saveSetting.buttons.0", { ns: "dialog" }),
  7725. i18next.t("saveSetting.buttons.1", { ns: "dialog" }),
  7726. ]
  7727. ); // 配置改变保存确认
  7728. if (shouldSave) {
  7729. // 数据校验
  7730. // TODO
  7731. if (settings.deepl_type !== "free") {
  7732. let selectedIndex =
  7733. $('input[name="deepl_config_config_item"]:checked').length > 0;
  7734. if (!selectedIndex) {
  7735. $(".deepl_config a").removeClass("active");
  7736. $(".settings-page").removeClass("active");
  7737. $("#sidebar-translation-settings").addClass("active");
  7738. $("#translation-settings").addClass("active");
  7739.  
  7740. $("#deepl_config").addClass("missing");
  7741. return;
  7742. } else {
  7743. $("#deepl_config").removeClass("missing");
  7744. }
  7745. }
  7746. if (settings.translation === "openai") {
  7747. let selectedIndex =
  7748. $('input[name="chatgpt_config_config_item"]:checked').length > 0;
  7749. if (!selectedIndex) {
  7750. $(".chatgpt_config a").removeClass("active");
  7751. $(".settings-page").removeClass("active");
  7752. $("#sidebar-translation-settings").addClass("active");
  7753. $("#translation-settings").addClass("active");
  7754.  
  7755. $("#chatgpt_config").addClass("missing");
  7756. return;
  7757. } else {
  7758. $("#chatgpt_config").removeClass("missing");
  7759. }
  7760. }
  7761. {
  7762. let selectedIndex =
  7763. $('input[name="translation"]:checked').length > 0;
  7764. if (!selectedIndex) {
  7765. $(".OJBetter_setting_sidebar a").removeClass("active");
  7766. $(".settings-page").removeClass("active");
  7767. $("#sidebar-translation-settings").addClass("active");
  7768. $("#translation-settings").addClass("active");
  7769.  
  7770. $("#translationServices").addClass("missing");
  7771. return;
  7772. } else {
  7773. $("#translationServices").removeClass("missing");
  7774. }
  7775. }
  7776.  
  7777. // 保存数据
  7778. let refreshPage = false; // 是否需要刷新页面
  7779. for (const [key, value] of Object.entries(settings)) {
  7780. if (
  7781. !refreshPage &&
  7782. !(
  7783. key == "translation" ||
  7784. key == "darkMode" ||
  7785. key == "commentTranslationChoice"
  7786. )
  7787. ) {
  7788. if (GM_getValue(key) != value) refreshPage = true;
  7789. }
  7790. GM_setValue(key, value);
  7791. }
  7792. for (const [key, value] of Object.entries(tempConfigs)) {
  7793. if (
  7794. !refreshPage &&
  7795. JSON.stringify(GM_getValue(key)) != JSON.stringify(value)
  7796. )
  7797. refreshPage = true;
  7798. GM_setValue(key, value);
  7799. }
  7800.  
  7801. if (refreshPage) location.reload();
  7802. else {
  7803. // 切换黑暗模式
  7804. if (OJBetter.basic.darkMode != settings.darkMode) {
  7805. OJBetter.basic.darkMode = settings.darkMode;
  7806. // 移除旧的事件监听器
  7807. changeEventListeners.forEach((listener) => {
  7808. mediaQueryList.removeEventListener("change", listener);
  7809. });
  7810.  
  7811. if (OJBetter.basic.darkMode == "follow") {
  7812. changeEventListeners.push(handleColorSchemeChange);
  7813. mediaQueryList.addEventListener(
  7814. "change",
  7815. handleColorSchemeChange
  7816. );
  7817. $("html").removeAttr("data-theme");
  7818. } else if (OJBetter.basic.darkMode == "dark") {
  7819. $("html").attr("data-theme", "dark");
  7820. if (OJBetter.monaco.editor) {
  7821. monaco.editor.setTheme("vs-dark");
  7822. }
  7823. } else {
  7824. $("html").attr("data-theme", "light");
  7825. if (OJBetter.monaco.editor) {
  7826. monaco.editor.setTheme("vs");
  7827. }
  7828. // 移除旧的事件监听器
  7829. const mediaQueryList = window.matchMedia(
  7830. "(prefers-color-scheme: dark)"
  7831. );
  7832. window.matchMedia("(prefers-color-scheme: dark)");
  7833. }
  7834. }
  7835. // 更新配置信息
  7836. OJBetter.translation.choice = settings.translation;
  7837. OJBetter.translation.comment.choice =
  7838. settings.commentTranslationChoice;
  7839. }
  7840. }
  7841. }
  7842. OJB_closeAndRemoveModal(settingMenu);
  7843. $settingBtns.prop("disabled", false).removeClass("open");
  7844. });
  7845. });
  7846. }
  7847.  
  7848. /**
  7849. * 初始化html2markdown转换器
  7850. */
  7851. async function initHTML2MarkDown() {
  7852. OJBetter.common.turndownService = new TurndownService({
  7853. bulletListMarker: "-",
  7854. });
  7855.  
  7856. // 保留原始
  7857. OJBetter.common.turndownService.keep(["del"]);
  7858.  
  7859. // 丢弃
  7860. OJBetter.common.turndownService.addRule("remove-by-class", {
  7861. filter: function (node) {
  7862. return (
  7863. node.classList.contains("sample-tests") ||
  7864. node.classList.contains("header") ||
  7865. node.classList.contains("ojb-overlay") ||
  7866. node.classList.contains("html2md-panel") ||
  7867. node.classList.contains("likeForm") ||
  7868. node.classList.contains("monaco-editor") ||
  7869. node.nodeName === "SCRIPT"
  7870. );
  7871. },
  7872. replacement: function (content, node) {
  7873. return "";
  7874. },
  7875. });
  7876. OJBetter.common.turndownService.addRule("remove-script", {
  7877. filter: function (node, options) {
  7878. return (
  7879. node.tagName.toLowerCase() == "script" &&
  7880. node.type.startsWith("math/tex")
  7881. );
  7882. },
  7883. replacement: function (content, node) {
  7884. return "";
  7885. },
  7886. });
  7887.  
  7888. // inline math
  7889. OJBetter.common.turndownService.addRule("inline-math", {
  7890. filter: function (node, options) {
  7891. return (
  7892. node.tagName.toLowerCase() == "span" && node.className == "MathJax"
  7893. );
  7894. },
  7895. replacement: function (content, node) {
  7896. var latex = $(node).next().text();
  7897. latex = latex.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  7898. return "$" + latex + "$";
  7899. },
  7900. });
  7901.  
  7902. // block math
  7903. OJBetter.common.turndownService.addRule("block-math", {
  7904. filter: function (node, options) {
  7905. return (
  7906. node.tagName.toLowerCase() == "div" &&
  7907. node.className == "MathJax_Display"
  7908. );
  7909. },
  7910. replacement: function (content, node) {
  7911. var latex = $(node).next().text();
  7912. latex = latex.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  7913. return "\n$$\n" + latex + "\n$$\n";
  7914. },
  7915. });
  7916.  
  7917. // texFontStyle
  7918. OJBetter.common.turndownService.addRule("texFontStyle", {
  7919. filter: function (node) {
  7920. return (
  7921. node.nodeName === "SPAN" && node.classList.contains("tex-font-style-bf")
  7922. );
  7923. },
  7924. replacement: function (content) {
  7925. return "**" + content + "**";
  7926. },
  7927. });
  7928.  
  7929. // sectionTitle
  7930. OJBetter.common.turndownService.addRule("sectionTitle", {
  7931. filter: function (node) {
  7932. return (
  7933. node.nodeName === "DIV" && node.classList.contains("section-title")
  7934. );
  7935. },
  7936. replacement: function (content) {
  7937. return "**" + content + "**";
  7938. },
  7939. });
  7940.  
  7941. // property-title
  7942. OJBetter.common.turndownService.addRule("property-title", {
  7943. filter: function (node) {
  7944. return (
  7945. node.nodeName === "DIV" && node.classList.contains("property-title")
  7946. );
  7947. },
  7948. replacement: function (content) {
  7949. return content + ": ";
  7950. },
  7951. });
  7952.  
  7953. // pre
  7954. OJBetter.common.turndownService.addRule("pre", {
  7955. filter: function (node, options) {
  7956. return node.tagName.toLowerCase() == "pre";
  7957. },
  7958. replacement: function (content, node) {
  7959. if (!!node.querySelector("code.prettyprint")) {
  7960. return "";
  7961. } else {
  7962. return "```\n" + content + "```\n";
  7963. }
  7964. },
  7965. });
  7966.  
  7967. // bordertable
  7968. OJBetter.common.turndownService.addRule("bordertable", {
  7969. filter: "table",
  7970. replacement: function (content, node) {
  7971. if (node.classList.contains("bordertable")) {
  7972. var output = [],
  7973. thead = "",
  7974. trs = node.querySelectorAll("tr");
  7975. if (trs.length > 0) {
  7976. var ths = trs[0].querySelectorAll("td,th");
  7977. if (ths.length > 0) {
  7978. thead =
  7979. "| " +
  7980. Array.from(ths)
  7981. .map((th) =>
  7982. OJBetter.common.turndownService.turndown(th.innerHTML.trim())
  7983. )
  7984. .join(" | ") +
  7985. " |\n" +
  7986. "| " +
  7987. Array.from(ths)
  7988. .map(() => " --- ")
  7989. .join("|") +
  7990. " |\n";
  7991. }
  7992. }
  7993. var rows = node.querySelectorAll("tr");
  7994. Array.from(rows).forEach(function (row, i) {
  7995. if (i > 0) {
  7996. var cells = row.querySelectorAll("td,th");
  7997. var trow =
  7998. "| " +
  7999. Array.from(cells)
  8000. .map((cell) =>
  8001. OJBetter.common.turndownService.turndown(
  8002. cell.innerHTML.trim()
  8003. )
  8004. )
  8005. .join(" | ") +
  8006. " |";
  8007. output.push(trow);
  8008. }
  8009. });
  8010. return thead + output.join("\n");
  8011. } else {
  8012. return content;
  8013. }
  8014. },
  8015. });
  8016. }
  8017.  
  8018. /**
  8019. * 任务队列
  8020. */
  8021. class TaskQueue {
  8022. constructor() {
  8023. this.taskQueues = {};
  8024. this.timers = {}; // 定时器
  8025. this.delays = {}; // 等待时间(毫秒)
  8026. this.isProcessing = {}; // 处理状态
  8027. }
  8028.  
  8029. getDelay(type) {
  8030. return type === "openai" ? 0 : OJBetter.translation.waitTime;
  8031. }
  8032.  
  8033. /**
  8034. * 添加任务
  8035. * @param {string} type 任务类型
  8036. * @param {function} fn 任务函数
  8037. * @param {boolean} isNonQueueTask 是否为非队列任务
  8038. * @returns {Promise<*>} 任务执行的结果
  8039. */
  8040. addTask(type, fn, isNonQueueTask = false) {
  8041. if (isNonQueueTask) return fn();
  8042.  
  8043. if (!this.taskQueues[type]) {
  8044. this.taskQueues[type] = [];
  8045. this.isProcessing[type] = false;
  8046. }
  8047.  
  8048. return new Promise((resolve, reject) => {
  8049. this.taskQueues[type].push({ fn, resolve, reject });
  8050. if (!this.isProcessing[type]) {
  8051. this.processNextTask(type);
  8052. }
  8053. });
  8054. }
  8055.  
  8056. async processNextTask(type) {
  8057. if (!this.taskQueues[type] || this.taskQueues[type].length === 0) {
  8058. this.isProcessing[type] = false;
  8059. return;
  8060. }
  8061.  
  8062. this.isProcessing[type] = true;
  8063. const { fn, resolve, reject } = this.taskQueues[type].shift();
  8064.  
  8065. try {
  8066. const result = await fn();
  8067. resolve(result);
  8068. } catch (error) {
  8069. reject(error);
  8070. }
  8071.  
  8072. const delay = this.getDelay(type);
  8073. if (delay > 0) {
  8074. await new Promise((resolve) => setTimeout(resolve, delay));
  8075. }
  8076.  
  8077. this.processNextTask(type);
  8078. }
  8079. }
  8080.  
  8081. /**
  8082. * 检测为空文本
  8083. * @param {string} text 待检测的文本
  8084. * @returns {boolean} 是否为空文本
  8085. */
  8086. const isEmptyText = (text) => text.trim() === "";
  8087.  
  8088. /**
  8089. * 加载按钮相关函数
  8090. */
  8091. async function initButtonFunc() {
  8092. // 鼠标悬浮时为目标元素区域添加一个覆盖层
  8093. $.fn.addHoverOverlay = function (target) {
  8094. let position = $(target).css("position");
  8095. let display = $(target).css("display");
  8096.  
  8097. this.hover(
  8098. () => {
  8099. $(target).addClass("ojb-overlay").css("position", "relative");
  8100. if (display == "inline" || display == "contents") {
  8101. $(target).css("display", "block");
  8102. }
  8103. },
  8104. () => {
  8105. $(target).removeClass("ojb-overlay").css("position", position);
  8106. if (display == "inline" || display == "contents") {
  8107. $(target).css("display", display);
  8108. }
  8109. }
  8110. );
  8111. };
  8112.  
  8113. /**
  8114. * 为按钮设置图标
  8115. * @param {string} icon 图标
  8116. * @returns {JQuery<HTMLElement>} 按钮
  8117. */
  8118. $.fn.setButtonIcon = function (icon) {
  8119. let i = this.find("i");
  8120. if (i.length != 0 && i.hasClass("iconfont")) {
  8121. i.html(icon);
  8122. } else {
  8123. i = OJB_safeCreateJQElement(`<i>${icon}</i>`);
  8124. this.prepend(i);
  8125. }
  8126. return this;
  8127. };
  8128.  
  8129. /**
  8130. * 设置按钮为加载等待状态
  8131. */
  8132. $.fn.setButtonLoading = function () {
  8133. this.addClass("loading");
  8134. this.prop("disabled", true);
  8135. return this;
  8136. };
  8137.  
  8138. /**
  8139. * 解除按钮的加载等待状态
  8140. */
  8141. $.fn.setButtonLoaded = function () {
  8142. this.removeClass("loading");
  8143. this.prop("disabled", false);
  8144. return this;
  8145. };
  8146.  
  8147. /**
  8148. * 为按钮设置popover提示文本
  8149. * @param {string} text 文本
  8150. * @returns {JQuery<HTMLElement>} 按钮
  8151. */
  8152. $.fn.setButtonPopover = function (text) {
  8153. // find if has popover_content class element
  8154. let popover_content = this.find(".popover_content");
  8155. if (popover_content.length != 0) {
  8156. popover_content.text(text);
  8157. } else {
  8158. popover_content = OJB_safeCreateJQElement(
  8159. `<span class="popover_content">${text}</span>`
  8160. );
  8161. this.append(popover_content);
  8162. }
  8163. return this;
  8164. };
  8165.  
  8166. /**
  8167. * 获取MarkDown
  8168. * @returns {string} MarkDown
  8169. */
  8170. $.fn.getMarkdown = function () {
  8171. const markdown = this.data("markdown");
  8172. if (markdown === undefined) {
  8173. const htmlContent = this.html();
  8174. const newMarkdown = OJBetter.common.turndownService.turndown(htmlContent);
  8175. this.data("markdown", newMarkdown);
  8176. return newMarkdown;
  8177. }
  8178. return markdown;
  8179. };
  8180.  
  8181. // 设置按钮状态
  8182. $.fn.setButtonState = function (state, popoverText = null, disabled = false) {
  8183. this.data("buttonState", state)
  8184. .prop("disabled", disabled)
  8185. .css("cursor", disabled ? "not-allowed" : "pointer")
  8186. .removeClass("running success enabled error loading redo");
  8187. if (popoverText) this.setButtonPopover(popoverText);
  8188.  
  8189. if (state !== "initial") this.addClass(state);
  8190. return this;
  8191. };
  8192.  
  8193. // 为按钮添加鼠标悬浮重试
  8194. $.fn.setHoverRedo = function () {
  8195. this.hover(
  8196. () => {
  8197. prevState = this.getButtonState();
  8198. if (prevState !== "normal" && prevState !== "running") {
  8199. this.setButtonState("redo");
  8200. }
  8201. },
  8202. () => {
  8203. const currentState = this.getButtonState();
  8204. if (
  8205. prevState !== "normal" &&
  8206. ["normal", "redo"].includes(currentState)
  8207. ) {
  8208. this.setButtonState(prevState);
  8209. prevState = null;
  8210. }
  8211. }
  8212. );
  8213. };
  8214.  
  8215. // 获取按钮状态
  8216. $.fn.getButtonState = function () {
  8217. return this.data("buttonState") || "normal";
  8218. };
  8219.  
  8220. // 设置翻译按钮状态
  8221. $.fn.setTransButtonState = function (state, text = null) {
  8222. const popoverText = text || i18next.t(`trans.${state}`, { ns: "button" });
  8223. const disabled = state === "running" || state === "loading";
  8224. this.setButtonState(state, popoverText, disabled);
  8225. return this;
  8226. };
  8227.  
  8228. // 存翻译结果
  8229. $.fn.pushResultToTransButton = function (result) {
  8230. let resultStack = this.data("resultStack");
  8231. if (!resultStack) resultStack = [];
  8232. resultStack.push(result);
  8233. this.data("resultStack", resultStack);
  8234. };
  8235.  
  8236. // 获取翻译结果
  8237. $.fn.getResultFromTransButton = function () {
  8238. return this.data("resultStack");
  8239. };
  8240.  
  8241. // 标记为不自动翻译
  8242. $.fn.setNotAutoTranslate = function () {
  8243. this.data("notAutoTranslate", true);
  8244. };
  8245.  
  8246. // 获取是否为不自动翻译
  8247. $.fn.getNotAutoTranslate = function () {
  8248. return this.data("notAutoTranslate");
  8249. };
  8250.  
  8251. // 判断是否已经翻译
  8252. $.fn.IsTranslated = function () {
  8253. if (this.hasAttr("translated")) {
  8254. return true;
  8255. } else {
  8256. return false;
  8257. }
  8258. };
  8259.  
  8260. // 判断是否为评论区按钮
  8261. $.fn.IsCommentButton = function () {
  8262. let isCommentButton = this.data("isCommentButton");
  8263. if (isCommentButton == undefined) {
  8264. this.parents(".comments").length > 0
  8265. ? (isCommentButton = true)
  8266. : (isCommentButton = false);
  8267. this.data("isCommentButton", isCommentButton);
  8268. }
  8269. return isCommentButton;
  8270. };
  8271.  
  8272. // 按钮点击效果
  8273. $(document).on("mousedown", ".ojb_btn", function () {
  8274. $(this)
  8275. .addClass("active")
  8276. .on("animationend", () => $(this).removeClass("active"));
  8277. });
  8278. }
  8279.  
  8280. /**
  8281. * 添加题目markdown转换/复制/翻译按钮面板
  8282. * @param {HTMLElement} element 需要添加按钮面板的元素
  8283. * @param {string} suffix 按钮面板id后缀
  8284. * @param {string} type 按钮面板添加位置
  8285. * @param {boolean} is_simple 是否是简单模式
  8286. * @returns {object} 返回按钮面板元素
  8287. */
  8288. function addButtonPanel(element, suffix, type, is_simple = false) {
  8289. let text;
  8290. if (OJBetter.translation.comment.transMode == "1")
  8291. text = i18next.t("trans.segment", { ns: "button" });
  8292. else if (OJBetter.translation.comment.transMode == "2")
  8293. text = i18next.t("trans.select", { ns: "button" });
  8294. else text = i18next.t("trans.normal", { ns: "button" });
  8295.  
  8296. let panel = OJB_safeCreateJQElement(
  8297. `<div class='html2md-panel input-output-copier ${
  8298. is_simple ? "is_simple" : ""
  8299. }'></div>`
  8300. );
  8301. let viewButton = OJB_safeCreateJQElement(`
  8302. <button class='ojb_btn ojb_btn_popover top' id='html2md-view${suffix}'>
  8303. <i class="iconfont">&#xe7e5;</i>
  8304. <span class="popover_content">${i18next.t("md.normal", {
  8305. ns: "button",
  8306. })}</span>
  8307. </button>`);
  8308. let copyButton = OJB_safeCreateJQElement(`
  8309. <button class='ojb_btn ojb_btn_popover top' id='html2md-cb${suffix}'>
  8310. <i class="iconfont">&#xe608;</i>
  8311. <span class="popover_content">${i18next.t("copy.normal", {
  8312. ns: "button",
  8313. })}</span>
  8314. </button>`);
  8315. let translateButton = OJB_safeCreateJQElement(`
  8316. <button class='ojb_btn translateButton ojb_btn_popover top' id='translateButton${suffix}'>
  8317. <i class="iconfont">&#xe6be;</i>
  8318. <span class="popover_content">${text}</span>
  8319. </button>`);
  8320. if (!is_simple) panel.append(viewButton);
  8321. if (!is_simple) panel.append(copyButton);
  8322. panel.append(translateButton);
  8323. if (type === "this_level") {
  8324. $(element).before(panel);
  8325. } else if (type === "child_level") {
  8326. $(element).prepend(panel);
  8327. }
  8328.  
  8329. return {
  8330. panel: panel,
  8331. viewButton: viewButton,
  8332. copyButton: copyButton,
  8333. translateButton: translateButton,
  8334. };
  8335. }
  8336.  
  8337. /**
  8338. * 添加MD视图按钮
  8339. * @param {JQuery<HTMLElement>} button 按钮
  8340. * @param {JQuery<HTMLElement>} element 目标元素
  8341. * @param {string} suffix id后缀
  8342. * @param {string} type 类型
  8343. * @returns {void}
  8344. */
  8345. async function addButtonWithHTML2MD(button, element, suffix, type) {
  8346. /**
  8347. * 改变按钮状态
  8348. * @param {string} state 状态
  8349. */
  8350. const changeButtonState = (state) => {
  8351. if (state == "loading") {
  8352. button.setButtonLoading();
  8353. button.setButtonPopover(i18next.t("state.waitMathJax", { ns: "button" }));
  8354. } else if (state == "loaded") {
  8355. button.setButtonLoaded();
  8356. button.setButtonPopover(i18next.t("md.normal", { ns: "button" }));
  8357. } else if (state == "normal") {
  8358. button.removeClass("enabled");
  8359. button.setButtonPopover(i18next.t("md.normal", { ns: "button" }));
  8360. } else if (state == "mdView") {
  8361. button.addClass("enabled");
  8362. button.setButtonPopover(i18next.t("md.reduction", { ns: "button" }));
  8363. } else if (state == "disabled") {
  8364. button.prop("disabled", true);
  8365. button.setButtonPopover(i18next.t("md.disabled", { ns: "button" }));
  8366. }
  8367. };
  8368.  
  8369. /**
  8370. * 存放目标元素的 JQueryObject
  8371. */
  8372. const target = (() => {
  8373. if ((type = "child_level")) {
  8374. return $(element).children(":not(.html2md-panel)");
  8375. } else {
  8376. return $(element);
  8377. }
  8378. })();
  8379.  
  8380. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  8381. changeButtonState("disabled");
  8382. return;
  8383. } else {
  8384. changeButtonState("loading");
  8385. await waitForMathJaxIdle();
  8386. changeButtonState("loaded");
  8387. }
  8388.  
  8389. button.click(
  8390. OJB_debounce(function () {
  8391. /**
  8392. * 检查是否是MarkDown视图
  8393. * @returns {boolean} 是否是MarkDown视图
  8394. */
  8395. function checkViewmd() {
  8396. if ($(element).attr("viewmd") === "true") {
  8397. return true;
  8398. } else {
  8399. return false;
  8400. }
  8401. }
  8402.  
  8403. /**
  8404. * 设置是否是MarkDown视图
  8405. * @param {boolean} value 是否是MarkDown视图
  8406. * @returns {void}
  8407. */
  8408. function setViewmd(value) {
  8409. $(element).attr("viewmd", value);
  8410. if (value) {
  8411. changeButtonState("mdView");
  8412. } else {
  8413. changeButtonState("normal");
  8414. }
  8415. }
  8416.  
  8417. if (checkViewmd()) {
  8418. setViewmd(false);
  8419. target.last().next(".mdViewContent").remove();
  8420. target.show();
  8421. } else {
  8422. setViewmd(true);
  8423. const markdown = $(element).getMarkdown();
  8424. const mdViewContent = OJB_safeCreateJQElement(
  8425. `<span class="mdViewContent" style="width:auto; height:auto;">${markdown}</span>`
  8426. );
  8427. target.last().after(mdViewContent);
  8428. target.hide();
  8429. }
  8430. })
  8431. );
  8432.  
  8433. if (
  8434. OJBetter.preference.hoverTargetAreaDisplay &&
  8435. !OJBetter.typeOfPage.is_oldLatex &&
  8436. !OJBetter.typeOfPage.is_acmsguru
  8437. ) {
  8438. button.addHoverOverlay($(element));
  8439. }
  8440. }
  8441.  
  8442. /**
  8443. * 添加复制按钮
  8444. * @param {JQuery<HTMLElement>} button 按钮
  8445. * @param {JQuery<HTMLElement>} element 目标元素
  8446. * @param {string} suffix 后缀
  8447. * @param {string} type 类型
  8448. */
  8449. async function addButtonWithCopy(button, element, suffix, type) {
  8450. /**
  8451. * 改变按钮状态
  8452. * @param {string} state 状态
  8453. */
  8454. function changeButtonState(state) {
  8455. if (state == "loading") {
  8456. button.setButtonLoading();
  8457. button.setButtonPopover(i18next.t("state.waitMathJax", { ns: "button" }));
  8458. } else if (state == "loaded") {
  8459. button.setButtonLoaded();
  8460. button.setButtonPopover(i18next.t("copy.normal", { ns: "button" }));
  8461. } else if (state == "normal") {
  8462. button.setButtonPopover(i18next.t("copy.normal", { ns: "button" }));
  8463. } else if (state == "copied") {
  8464. button.setButtonPopover(i18next.t("copy.copied", { ns: "button" }));
  8465. } else if (state == "disabled") {
  8466. button.prop("disabled", true);
  8467. button.setButtonPopover(i18next.t("copy.disabled", { ns: "button" }));
  8468. }
  8469. }
  8470.  
  8471. // 等待MathJax队列完成
  8472. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  8473. changeButtonState("disabled");
  8474. return;
  8475. } else {
  8476. changeButtonState("loading");
  8477. await waitForMathJaxIdle();
  8478. changeButtonState("loaded");
  8479. }
  8480.  
  8481. button.click(
  8482. OJB_debounce(function () {
  8483. var target = $(element).get(0);
  8484.  
  8485. var markdown = $(element).getMarkdown();
  8486.  
  8487. GM_setClipboard(markdown);
  8488.  
  8489. $(this).addClass("success");
  8490. changeButtonState("copied");
  8491.  
  8492. // 更新复制按钮文本
  8493. setTimeout(() => {
  8494. $(this).removeClass("success");
  8495. changeButtonState("normal");
  8496. }, 2000);
  8497. })
  8498. );
  8499.  
  8500. if (
  8501. OJBetter.preference.hoverTargetAreaDisplay &&
  8502. !OJBetter.typeOfPage.is_oldLatex &&
  8503. !OJBetter.typeOfPage.is_acmsguru
  8504. ) {
  8505. button.addHoverOverlay($(element));
  8506. }
  8507. }
  8508.  
  8509. /**
  8510. * 添加翻译按钮
  8511. * @param {JQuery<HTMLElement>} button 按钮
  8512. * @param {JQuery<HTMLElement>} element 目标元素
  8513. * @param {string} suffix 后缀
  8514. * @param {string} type 类型
  8515. * @param {boolean} is_comment 是否是评论
  8516. */
  8517. async function addButtonWithTranslation(
  8518. button,
  8519. element,
  8520. suffix,
  8521. type,
  8522. is_comment = false
  8523. ) {
  8524. /**
  8525. * 添加可指定翻译服务的方法调用
  8526. * @param {string} translation 翻译服务
  8527. */
  8528. button.data("translatedItBy", function (translation) {
  8529. button.setTransButtonState(
  8530. "running",
  8531. i18next.t("trans.wait", { ns: "button" })
  8532. );
  8533. executeTranslation(button, element, type, is_comment, translation);
  8534. });
  8535.  
  8536. // 等待MathJax队列完成
  8537. button.setButtonLoading();
  8538. await waitForMathJaxIdle();
  8539. button.setButtonLoaded();
  8540.  
  8541. // 标记目标文本区域不自动翻译
  8542. {
  8543. let text;
  8544. if (
  8545. (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) &&
  8546. !OJBetter.translation.forceTurndownConversion
  8547. ) {
  8548. text = $(element).html();
  8549. } else {
  8550. text = $(element).getMarkdown();
  8551. }
  8552. let length = text.length;
  8553. if (
  8554. length > OJBetter.translation.auto.shortTextLength ||
  8555. isEmptyText(text) ||
  8556. $(element).find(".spoiler").length > 0
  8557. ) {
  8558. button.setNotAutoTranslate();
  8559. }
  8560. // button.after(`<span>${length}</span>`); // 显示字符数
  8561. }
  8562.  
  8563. button.click(
  8564. OJB_debounce(async function () {
  8565. // 重新翻译
  8566. let resultStack = $(this).getResultFromTransButton();
  8567. if (resultStack) {
  8568. let pElements = $(element).find(
  8569. "p.block_selected:not(li p), li.block_selected"
  8570. );
  8571. for (let item of resultStack) {
  8572. if (OJBetter.translation.retransAction == "0") {
  8573. // 选段翻译不直接移除旧结果
  8574. if (OJBetter.translation.comment.transMode == "2") {
  8575. // 只移除即将要翻译的段的结果
  8576. if (pElements.is(item.translateDiv.getDiv().prev())) {
  8577. item.translateDiv.close();
  8578. }
  8579. } else {
  8580. item.translateDiv.close();
  8581. $($(element))
  8582. .find(
  8583. ".translate-problem-statement, .translate-problem-statement-panel"
  8584. )
  8585. .remove();
  8586. }
  8587. } else {
  8588. item.translateDiv.fold();
  8589. }
  8590. }
  8591. }
  8592.  
  8593. // 翻译
  8594. button.setTransButtonState(
  8595. "running",
  8596. i18next.t("trans.wait", { ns: "button" })
  8597. );
  8598.  
  8599. executeTranslation(button, element, type, is_comment);
  8600. })
  8601. );
  8602.  
  8603. // 重新翻译提示
  8604. let prevState;
  8605. button.hover(
  8606. () => {
  8607. prevState = button.getButtonState();
  8608. if (prevState !== "normal" && prevState !== "running") {
  8609. button.setTransButtonState("redo");
  8610. }
  8611. },
  8612. () => {
  8613. const currentState = button.getButtonState();
  8614. if (prevState !== "normal" && ["normal", "redo"].includes(currentState)) {
  8615. button.setTransButtonState(prevState);
  8616. prevState = null;
  8617. }
  8618. }
  8619. );
  8620.  
  8621. // 目标区域指示
  8622. if (OJBetter.preference.hoverTargetAreaDisplay) {
  8623. button.addHoverOverlay($(element));
  8624. }
  8625.  
  8626. // 翻译右键切换菜单
  8627. $(document).on("contextmenu", "#translateButton" + suffix, function (e) {
  8628. e.preventDefault();
  8629.  
  8630. // 是否为评论的翻译
  8631. let is_comment = button.IsCommentButton();
  8632.  
  8633. // 移除旧的
  8634. if (!$(e.target).closest(".OJBetter_contextmenu").length) {
  8635. $(".OJBetter_contextmenu").remove();
  8636. }
  8637.  
  8638. var menu = $('<div class="OJBetter_contextmenu"></div>');
  8639. var translations = [
  8640. {
  8641. value: "deepl",
  8642. name: i18next.t("translation.options.services.deepl", {
  8643. ns: "settings",
  8644. }),
  8645. },
  8646. {
  8647. value: "iflyrec",
  8648. name: i18next.t("translation.options.services.iflyrec", {
  8649. ns: "settings",
  8650. }),
  8651. },
  8652. {
  8653. value: "youdao",
  8654. name: i18next.t("translation.options.services.youdao", {
  8655. ns: "settings",
  8656. }),
  8657. },
  8658. {
  8659. value: "google",
  8660. name: i18next.t("translation.options.services.google", {
  8661. ns: "settings",
  8662. }),
  8663. },
  8664. {
  8665. value: "caiyun",
  8666. name: i18next.t("translation.options.services.caiyun", {
  8667. ns: "settings",
  8668. }),
  8669. },
  8670. {
  8671. value: "openai",
  8672. name: i18next.t("translation.options.services.openai.name", {
  8673. ns: "settings",
  8674. }),
  8675. },
  8676. ];
  8677.  
  8678. // Function to check if the service supports the target language
  8679. function supportsTargetLanguage(service, targetLang) {
  8680. return (
  8681. OJBetter.supportList.translationSupport[service] &&
  8682. OJBetter.supportList.translationSupport[service][targetLang] !==
  8683. undefined
  8684. );
  8685. }
  8686.  
  8687. if (is_comment) {
  8688. var label =
  8689. OJB_safeCreateJQElement(`<label><input type="radio" name="translation" value="0">
  8690. <span class="OJBetter_contextmenu_label_text">
  8691. ${i18next.t(
  8692. "translation.preference.comment_translation_choice.services.follow",
  8693. { ns: "settings" }
  8694. )}
  8695. </span></label>`);
  8696. menu.append(label);
  8697. }
  8698. translations.forEach(function (translation) {
  8699. if (
  8700. supportsTargetLanguage(
  8701. translation.value,
  8702. OJBetter.translation.targetLang
  8703. )
  8704. ) {
  8705. var label =
  8706. OJB_safeCreateJQElement(`<label><input type="radio" name="translation" value="${translation.value}">
  8707. <span class="OJBetter_contextmenu_label_text">${translation.name}</span></label>`);
  8708. menu.append(label);
  8709. }
  8710. });
  8711.  
  8712. // 初始化
  8713. if (is_comment) {
  8714. menu
  8715. .find(
  8716. `input[name="translation"][value="${OJBetter.translation.comment.choice}"]`
  8717. )
  8718. .prop("checked", true);
  8719. } else {
  8720. menu
  8721. .find(
  8722. `input[name="translation"][value="${OJBetter.translation.choice}"]`
  8723. )
  8724. .prop("checked", true);
  8725. }
  8726. menu
  8727. .css({
  8728. top: e.pageY + "px",
  8729. left: e.pageX + "px",
  8730. })
  8731. .appendTo("body");
  8732.  
  8733. $(document).one("change", 'input[name="translation"]', function () {
  8734. if (is_comment) {
  8735. OJBetter.translation.comment.choice = $(
  8736. 'input[name="translation"]:checked'
  8737. ).val();
  8738. GM_setValue(
  8739. "commentTranslationChoice",
  8740. OJBetter.translation.comment.choice
  8741. );
  8742. } else {
  8743. OJBetter.translation.choice = $(
  8744. 'input[name="translation"]:checked'
  8745. ).val();
  8746. GM_setValue("translation", OJBetter.translation.choice);
  8747. }
  8748. $(".OJBetter_contextmenu").remove();
  8749. });
  8750.  
  8751. // 点击区域外关闭菜单
  8752. function handleClick(event) {
  8753. if (!$(event.target).closest(".OJBetter_contextmenu").length) {
  8754. $(".OJBetter_contextmenu").remove();
  8755. $(document).off("change", 'input[name="translation"]');
  8756. } else {
  8757. $(document).one("click", handleClick);
  8758. }
  8759. }
  8760. $(document).one("click", handleClick);
  8761. });
  8762. }
  8763.  
  8764. /**
  8765. * 处理按钮的翻译事件
  8766. * @param {JQuery<HTMLElement>} button 按钮
  8767. * @param {HTMLElement} element 目标元素
  8768. * @param {string} type 类型
  8769. * @param {boolean} is_comment 是否是评论
  8770. * @param {string} overrideTrans 覆盖全局翻译服务设定
  8771. */
  8772. async function executeTranslation(button, element, type, is_comment, overrideTrans) {
  8773. /** @type {HTMLElement} 目标元素 */
  8774. let target;
  8775. /**
  8776. * 错误计数数据结构
  8777. * @typedef {Object} count
  8778. * @property {number} errerNum 错误数量
  8779. * @property {number} skipNum 跳过数量
  8780. */
  8781. const count = {
  8782. errerNum: 0,
  8783. skipNum: 0,
  8784. };
  8785. if (OJBetter.translation.comment.transMode == "1") {
  8786. // 分段翻译
  8787. let pElements = $(element).find(
  8788. "p:not(:scope > li p), li, .OJBetter_acmsguru"
  8789. );
  8790. for (let i = 0; i < pElements.length; i++) {
  8791. target = $(pElements[i]).eq(0).clone();
  8792. element_node = pElements[i];
  8793. await process(
  8794. button,
  8795. target,
  8796. element_node,
  8797. type,
  8798. is_comment,
  8799. count,
  8800. overrideTrans
  8801. );
  8802. }
  8803. } else if (OJBetter.translation.comment.transMode == "2") {
  8804. // 选段翻译
  8805. let pElements = $(element).find(
  8806. "p.block_selected:not(li p), li.block_selected, .OJBetter_acmsguru"
  8807. );
  8808. for (let i = 0; i < pElements.length; i++) {
  8809. target = $(pElements[i]).eq(0).clone();
  8810. element_node = pElements[i];
  8811. await process(
  8812. button,
  8813. target,
  8814. element_node,
  8815. type,
  8816. is_comment,
  8817. count,
  8818. overrideTrans
  8819. );
  8820. }
  8821. $(element)
  8822. .find("p.block_selected:not(li p), li.block_selected")
  8823. .removeClass("block_selected");
  8824. } else {
  8825. // 普通翻译
  8826. target = $(element).eq(0).clone();
  8827. if (type === "child_level") $(target).children(":first").remove();
  8828. element_node = $($(element)).get(0);
  8829. await process(
  8830. button,
  8831. target,
  8832. element_node,
  8833. type,
  8834. is_comment,
  8835. count,
  8836. overrideTrans
  8837. );
  8838. }
  8839.  
  8840. // 翻译完成
  8841. if (!count.errerNum && !count.skipNum) {
  8842. button.setTransButtonState("success");
  8843. }
  8844. }
  8845.  
  8846. /**
  8847. * 翻译处理
  8848. * @param {JQuery<HTMLElement>} button 按钮
  8849. * @param {HTMLElement} target 目标元素
  8850. * @param {HTMLElement} element_node 目标节点
  8851. * @param {string} type 类型
  8852. * @param {boolean} is_comment 是否是评论
  8853. * @param {string} overrideTrans 覆盖全局翻译服务设定
  8854. */
  8855. async function process(
  8856. button,
  8857. target,
  8858. element_node,
  8859. type,
  8860. is_comment,
  8861. count,
  8862. overrideTrans
  8863. ) {
  8864. if (type === "child_level") {
  8865. let div = $("<div>");
  8866. $(element_node).append(div);
  8867. element_node = div.get(0);
  8868. }
  8869.  
  8870. //是否跳过折叠块
  8871. if ($(target).find(".spoiler").length > 0) {
  8872. const shouldSkip = await OJB_createDialog(
  8873. i18next.t("skipFold.title", { ns: "dialog" }),
  8874. i18next.t("skipFold.content", { ns: "dialog" }),
  8875. [
  8876. i18next.t("skipFold.buttons.0", { ns: "dialog" }),
  8877. i18next.t("skipFold.buttons.1", { ns: "dialog" }),
  8878. ],
  8879. true
  8880. ); //跳过折叠块确认
  8881. if (shouldSkip) {
  8882. $(target).find(".spoiler").remove();
  8883. } else {
  8884. $(target).find(".html2md-panel").remove();
  8885. }
  8886. }
  8887.  
  8888. // 等待并获取结果
  8889. button.setTransButtonState("running");
  8890. const result = await blockProcessing(
  8891. button,
  8892. target,
  8893. element_node,
  8894. is_comment,
  8895. overrideTrans
  8896. );
  8897. button.pushResultToTransButton(result);
  8898.  
  8899. if (result.status == "error") count.errerNum += 1;
  8900. else if (result.status == "skip") count.skipNum += 1;
  8901. $(target).remove();
  8902. }
  8903.  
  8904. /**
  8905. * 块处理
  8906. * @param {JQuery<HTMLElement>} button
  8907. * @param {HTMLElement} target 目标元素
  8908. * @param {HTMLElement} element_node 目标节点
  8909. * @param {boolean} is_comment 是否是评论
  8910. * @param {string} overrideTrans 覆盖全局翻译服务设定
  8911. * @returns {TranslateResult} 翻译结果对象
  8912. */
  8913. async function blockProcessing(
  8914. button,
  8915. target,
  8916. element_node,
  8917. is_comment,
  8918. overrideTrans
  8919. ) {
  8920. if (
  8921. (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) &&
  8922. !OJBetter.translation.forceTurndownConversion
  8923. ) {
  8924. target.markdown = $(target).html();
  8925. } else if (!target.markdown) {
  8926. target.markdown = OJBetter.common.turndownService.turndown(
  8927. $(target).html()
  8928. );
  8929. }
  8930.  
  8931. const result = await OJBetter.common.taskQueue.addTask(
  8932. OJBetter.translation.choice,
  8933. () =>
  8934. translateMain(
  8935. target.markdown,
  8936. element_node,
  8937. is_comment,
  8938. overrideTrans
  8939. ),
  8940. OJBetter.translation.choice == "openai"
  8941. );
  8942. if (result.status == "skip") {
  8943. button.setTransButtonState(
  8944. "error",
  8945. i18next.t("trans.tooLong", { ns: "button" })
  8946. );
  8947. result.translateDiv.close();
  8948. } else if (result.status == "error" || !result.rawData.done) {
  8949. result.translateDiv.setError();
  8950. result.translateDiv.setRawData(result.rawData);
  8951. result.translateDiv.showDebugButton();
  8952. button.setTransButtonState(
  8953. "error",
  8954. i18next.t("trans.error", { ns: "button" })
  8955. );
  8956. $(target).remove();
  8957. }
  8958. return result;
  8959. }
  8960.  
  8961. /**
  8962. * 选段翻译支持
  8963. */
  8964. async function multiChoiceTranslation() {
  8965. GM_addStyle(`
  8966. .topic .content .ttypography {
  8967. overflow: initial;
  8968. }
  8969. `);
  8970.  
  8971. $(document).on(
  8972. "click",
  8973. "p, li:not(:has(.comment)), .OJBetter_acmsguru",
  8974. function (e) {
  8975. let $this = $(this);
  8976. e.stopPropagation();
  8977. if ($this.hasClass("block_selected")) {
  8978. $this.removeClass("block_selected");
  8979. // 移除对应的按钮
  8980. $(".OJBetter_MiniTranslateButton").remove(
  8981. "#translateButton_selected_" + $this.attr("OJBetter_p_id")
  8982. );
  8983. } else {
  8984. let id = OJB_getRandomNumber(8);
  8985. $this.attr("OJBetter_p_id", id);
  8986. $this.addClass("block_selected");
  8987. // 添加按钮
  8988. let menu = OJB_safeCreateJQElement(
  8989. `<div class="OJBetter_MiniTranslateButton" id='translateButton_selected_${id}'>${translateIcon}</div>`
  8990. ).css({
  8991. left:
  8992. $($this).outerWidth(true) + $($this).position().left + 10 + "px",
  8993. });
  8994. $this.before(menu);
  8995.  
  8996. $("#translateButton_selected_" + id).click(async function () {
  8997. // 处理旧的结果
  8998. if ($this.attr("translated")) {
  8999. let result = $this.data("resultData");
  9000. if (OJBetter.translation.retransAction == "0") {
  9001. result.translateDiv.close();
  9002. } else {
  9003. result.translateDiv.fold();
  9004. }
  9005. }
  9006. // 翻译
  9007. let target = $this.eq(0).clone();
  9008. let result = await blockProcessing(
  9009. OJBetter.translation.choice,
  9010. target,
  9011. $this.eq(0),
  9012. $("#translateButton_selected_" + id),
  9013. false
  9014. );
  9015. $this.data("resultData", result);
  9016. $this.removeClass("block_selected");
  9017. // 移除对应的按钮
  9018. $(".OJBetter_MiniTranslateButton").remove(
  9019. "#translateButton_selected_" + id
  9020. );
  9021. $this.attr("translated", "1"); // 标记已翻译
  9022. });
  9023. }
  9024. }
  9025. );
  9026. }
  9027.  
  9028. /**
  9029. * 为acmsguru题面重新划分div
  9030. */
  9031. async function acmsguruReblock() {
  9032. if (OJBetter.translation.comment.transMode == "0") {
  9033. // 普通模式下的划分方式
  9034. var html = $(".ttypography").children().html();
  9035. var separator =
  9036. /(<div align="left" style="margin-top: 1\.0em;"><b>.*?<\/b><\/div>)/g;
  9037. var result = html.split(separator); // 分割代码
  9038. var outputHtml = "";
  9039. var header = "";
  9040. for (var i = 0; i < result.length; i++) {
  9041. if (separator.test(result[i])) {
  9042. header = result[i];
  9043. continue;
  9044. }
  9045. outputHtml += '<div class="ttypography">' + header + result[i] + "</div>";
  9046. header = "";
  9047. }
  9048. $(".ttypography").html(outputHtml);
  9049. } else {
  9050. // 分段/选段模式下的划分方式
  9051. $(".ttypography")
  9052. .children()
  9053. .each(function () {
  9054. var html = $(this).html();
  9055. var replacedHtml = html.replace(
  9056. /(?:<\/div>|<br><br>)(?<text>[\s\S]+?)(?=<br><br>)/g,
  9057. '<div align="left" class="OJBetter_acmsguru" >$<text></div>'
  9058. );
  9059. $(this).html(replacedHtml);
  9060. });
  9061. }
  9062. }
  9063.  
  9064. /**
  9065. * 添加MD/复制/翻译按钮
  9066. */
  9067. async function addConversionButton() {
  9068. let promises = []; // 用于收集所有的 Promise
  9069.  
  9070. // 题目页添加按钮
  9071. if (OJBetter.typeOfPage.is_problem) {
  9072. let exContentsPageClasses = ["sample-tests"];
  9073. $(".problem-statement")
  9074. .children("div")
  9075. .each((i, e) => {
  9076. var className = $(e).attr("class");
  9077. if (!exContentsPageClasses.includes(className)) {
  9078. var id = "_problem_" + OJB_getRandomNumber(8);
  9079. let panel = addButtonPanel(e, id, "this_level");
  9080. promises.push(
  9081. addButtonWithHTML2MD(panel.viewButton, e, id, "this_level")
  9082. );
  9083. promises.push(
  9084. addButtonWithCopy(panel.copyButton, e, id, "this_level")
  9085. );
  9086. promises.push(
  9087. addButtonWithTranslation(panel.translateButton, e, id, "this_level")
  9088. );
  9089. if (i == 0) panel.translateButton.setNotAutoTranslate(); // 题目标题块跳过,不自动翻译
  9090. }
  9091. });
  9092. }
  9093. // 添加按钮到ttypography部分
  9094. $(".ttypography").each((i, e) => {
  9095. // 是否为评论
  9096. let is_comment = false;
  9097. if ($(e).parents(".comments").length > 0) is_comment = true;
  9098. // 题目页不添加
  9099. if (!OJBetter.typeOfPage.is_problem || OJBetter.typeOfPage.is_acmsguru) {
  9100. let id = "_ttypography_" + OJB_getRandomNumber(8);
  9101. let panel = addButtonPanel(e, id, "this_level");
  9102. promises.push(
  9103. addButtonWithHTML2MD(panel.viewButton, e, id, "this_level")
  9104. );
  9105. promises.push(addButtonWithCopy(panel.copyButton, e, id, "this_level"));
  9106. promises.push(
  9107. addButtonWithTranslation(
  9108. panel.translateButton,
  9109. e,
  9110. id,
  9111. "this_level",
  9112. is_comment
  9113. )
  9114. );
  9115. }
  9116. });
  9117.  
  9118. // 完整题目集页特殊处理
  9119. if (OJBetter.typeOfPage.is_completeProblemset) {
  9120. let exContentsPageClasses = ["sample-tests"];
  9121. $(".problem-statement").each(function () {
  9122. $(this)
  9123. .children("div")
  9124. .each((i, e) => {
  9125. var className = $(e).attr("class");
  9126. if (!exContentsPageClasses.includes(className)) {
  9127. var id = "_problem_" + OJB_getRandomNumber(8);
  9128. let panel = addButtonPanel(e, id, "this_level");
  9129. promises.push(
  9130. addButtonWithHTML2MD(panel.viewButton, e, id, "this_level")
  9131. );
  9132. promises.push(
  9133. addButtonWithCopy(panel.copyButton, e, id, "this_level")
  9134. );
  9135. promises.push(
  9136. addButtonWithTranslation(
  9137. panel.translateButton,
  9138. e,
  9139. id,
  9140. "this_level"
  9141. )
  9142. );
  9143. if (i == 0) panel.translateButton.setNotAutoTranslate(); // 题目标题块跳过,不自动翻译
  9144. }
  9145. });
  9146. });
  9147. }
  9148.  
  9149. // 添加按钮到spoiler部分
  9150. $(".spoiler-content").each((i, e) => {
  9151. if ($(e).find(".html2md-panel").length === 0) {
  9152. const id = "_spoiler_" + OJB_getRandomNumber(8);
  9153. const panel = addButtonPanel(e, id, "child_level");
  9154. promises.push(
  9155. addButtonWithHTML2MD(panel.viewButton, e, id, "child_level")
  9156. );
  9157. promises.push(addButtonWithCopy(panel.copyButton, e, id, "child_level"));
  9158. promises.push(
  9159. addButtonWithTranslation(panel.translateButton, e, id, "child_level")
  9160. );
  9161. }
  9162. });
  9163.  
  9164. // 添加按钮到比赛QA部分
  9165. $(".question-response").each((i, e) => {
  9166. let id = "_question_" + OJB_getRandomNumber(8);
  9167. let panel = addButtonPanel(e, id, "this_level", true);
  9168. promises.push(
  9169. addButtonWithTranslation(panel.translateButton, e, id, "this_level")
  9170. );
  9171. });
  9172. if (OJBetter.typeOfPage.is_mSite) {
  9173. $("div._ProblemsPage_announcements table tbody tr:gt(0)").each((i, e) => {
  9174. var $nextDiv = $(e).find("td:first");
  9175. let id = "_question_" + OJB_getRandomNumber(8);
  9176. let panel = addButtonPanel($nextDiv, id, "this_level", true);
  9177. promises.push(
  9178. addButtonWithTranslation(
  9179. panel.translateButton,
  9180. $nextDiv,
  9181. id,
  9182. "this_level"
  9183. )
  9184. );
  9185. });
  9186. }
  9187.  
  9188. // 添加按钮到弹窗confirm-proto部分
  9189. $(".confirm-proto").each((i, e) => {
  9190. let id = "_titled_" + OJB_getRandomNumber(8);
  9191. var $nextDiv = $(e).children().get(0);
  9192. let panel = addButtonPanel($nextDiv, id, "this_level", true);
  9193. promises.push(
  9194. addButtonWithTranslation(
  9195. panel.translateButton,
  9196. $nextDiv,
  9197. id,
  9198. "this_level"
  9199. )
  9200. );
  9201. });
  9202.  
  9203. // 添加按钮到_CatalogHistorySidebarFrame_item部分
  9204. $("._CatalogHistorySidebarFrame_item").each((i, e) => {
  9205. let id = "_history_sidebar_" + OJB_getRandomNumber(8);
  9206. let panel = addButtonPanel(e, id, "this_level", true);
  9207. promises.push(
  9208. addButtonWithTranslation(panel.translateButton, e, id, "this_level")
  9209. );
  9210. });
  9211.  
  9212. $(".problem-lock-link").on("click", function () {
  9213. $(".popup .content div").each((i, e) => {
  9214. let id = "_popup_" + OJB_getRandomNumber(8);
  9215. let panel = addButtonPanel(e, id, "this_level", true);
  9216. promises.push(
  9217. addButtonWithTranslation(panel.translateButton, e, id, "this_level")
  9218. );
  9219. });
  9220. });
  9221.  
  9222. // 添加按钮到弹窗alert部分
  9223. $(".alert:not(.OJBetter_alert)").each((i, e) => {
  9224. let id = "_alert_" + OJB_getRandomNumber(8);
  9225. let panel = addButtonPanel(e, id, "child_level", true);
  9226. promises.push(
  9227. addButtonWithTranslation(panel.translateButton, e, id, "child_level")
  9228. );
  9229. });
  9230.  
  9231. // 添加按钮到talk-text部分
  9232. $(".talk-text").each((i, e) => {
  9233. let id = "_talk-text_" + OJB_getRandomNumber(8);
  9234. let panel = addButtonPanel(e, id, "child_level", true);
  9235. promises.push(
  9236. addButtonWithTranslation(panel.translateButton, e, id, "child_level")
  9237. );
  9238. });
  9239.  
  9240. return Promise.all(promises).catch((error) => {
  9241. console.error("One or more of the Add Button operations failed: ", error);
  9242. });
  9243. }
  9244.  
  9245. /**
  9246. * 等待LaTeX渲染队列全部完成
  9247. * @returns {Promise} 完成渲染
  9248. */
  9249. function waitForMathJaxIdle() {
  9250. return new Promise((resolve, reject) => {
  9251. // 检查MathJax对象是否存在
  9252. const checkMathJaxExists = () => {
  9253. if (typeof MathJax === "undefined") {
  9254. // 如果MathJax不存在,稍后再次检查
  9255. OJB_delay(100).then(checkMathJaxExists);
  9256. } else {
  9257. // MathJax存在,开始监视渲染队列
  9258. startMonitoringQueue();
  9259. }
  9260. };
  9261.  
  9262. // 开始监视MathJax渲染队列
  9263. const startMonitoringQueue = () => {
  9264. const intervalId = setInterval(() => {
  9265. const queue = MathJax.Hub.queue;
  9266. if (queue.pending === 0 && queue.running === 0) {
  9267. clearInterval(intervalId);
  9268. resolve();
  9269. }
  9270. }, 100);
  9271. };
  9272.  
  9273. // 开始检查MathJax对象
  9274. checkMathJaxExists();
  9275. });
  9276. }
  9277.  
  9278. /**
  9279. * 翻译结果面板
  9280. */
  9281. class TranslateDiv {
  9282. /**
  9283. * 构造函数
  9284. * @param {string} id 指定翻译框的id
  9285. */
  9286. constructor(id) {
  9287. this.id = id;
  9288. this.div = $("<div>").attr("id", id).addClass("translateDiv bounce-in");
  9289. if (!OJBetter.typeOfPage.is_completeProblemset) {
  9290. this.div.addClass("input-output-copier");
  9291. }
  9292. this.panelDiv = $("<div>").addClass("translate-problem-statement-panel");
  9293. this.div.append(this.panelDiv);
  9294.  
  9295. // 主要信息
  9296. this.mainDiv = $("<div>").addClass("translate-problem-statement");
  9297. this.span = $("<span>");
  9298. this.mainDiv.append(this.span);
  9299. this.div.append(this.mainDiv);
  9300. this.mainDivState = {
  9301. current: "transHTML",
  9302. transHTML: "",
  9303. rawDataHTML: "",
  9304. };
  9305.  
  9306. // 顶栏信息
  9307. this.topText = $("<div>").addClass("topText");
  9308. this.panelDiv.append(this.topText);
  9309.  
  9310. // 右侧
  9311. this.rightDiv = $("<div>").css("display", "flex");
  9312. this.panelDiv.append(this.rightDiv);
  9313. this.debugButton = OJB_safeCreateJQElement(`
  9314. <button class='ojb_btn ojb_btn_popover top'>
  9315. <i class="iconfont">&#xe641;</i>
  9316. <span class="popover_content">${i18next.t("rawData.normal", {
  9317. ns: "button",
  9318. })}</span>
  9319. </button>`).hide();
  9320. this.rightDiv.append(this.debugButton);
  9321. this.queryBalanceButton = OJB_safeCreateJQElement(`
  9322. <button class='ojb_btn ojb_btn_popover top'>
  9323. <i class="iconfont">&#xe6ae;</i>
  9324. <span class="popover_content">${i18next.t("queryBalance.normal", {
  9325. ns: "button",
  9326. })}</span>
  9327. </button>`).hide();
  9328. this.rightDiv.append(this.queryBalanceButton);
  9329. this.copyButton = OJB_safeCreateJQElement(`
  9330. <button class='ojb_btn ojb_btn_popover top'>
  9331. <i class="iconfont">&#xe608;</i>
  9332. <span class="popover_content">${i18next.t("copy.normal", {
  9333. ns: "button",
  9334. })}</span>
  9335. </button>`);
  9336. this.rightDiv.append(this.copyButton);
  9337. this.upButton = OJB_safeCreateJQElement(`
  9338. <button class='ojb_btn ojb_btn_popover top'>
  9339. <i class="iconfont">&#xe601;</i>
  9340. <span class="popover_content">${i18next.t("fold.normal", {
  9341. ns: "button",
  9342. })}</span>
  9343. </button>`);
  9344. this.rightDiv.append(this.upButton);
  9345. this.closeButton = OJB_safeCreateJQElement(`
  9346. <button class='ojb_btn ojb_btn_popover top'>
  9347. <i class="iconfont">&#xe614;</i>
  9348. <span class="popover_content">${i18next.t("close.normal", {
  9349. ns: "button",
  9350. })}</span>
  9351. </button>`);
  9352. this.rightDiv.append(this.closeButton);
  9353. }
  9354.  
  9355. /**
  9356. * 获取翻译框
  9357. * @returns {JQuery<HTMLElement>} 返回翻译框
  9358. */
  9359. getDiv() {
  9360. return this.div;
  9361. }
  9362.  
  9363. /**
  9364. * 设置翻译框顶部的文本
  9365. * @param {string} text 翻译框顶部的文本
  9366. */
  9367. setTopText(text) {
  9368. this.div.attr("data-topText", text);
  9369. this.topText.text(text);
  9370. }
  9371.  
  9372. /**
  9373. * 获取翻译框顶部的文本
  9374. * @returns {string} 返回翻译框顶部的文本
  9375. */
  9376. getTopText() {
  9377. return this.topText.text();
  9378. }
  9379.  
  9380. /**
  9381. * 渲染一个元素内的LaTeX公式
  9382. * @param {HTMLElement} element 元素
  9383. */
  9384. renderLaTeX(element) {
  9385. MathJax.Hub.Queue(["Typeset", MathJax.Hub, element]);
  9386. }
  9387.  
  9388. /**
  9389. * 更新翻译框内容
  9390. * @param {string} text 文本内容
  9391. * @param {boolean} is_escapeHTML 是否转义HTML标签,为true则HTML标签将作为普通文本处理,默认为true
  9392. * @param {boolean} is_renderLaTeX 是否渲染LaTeX,为true则会渲染LaTeX,默认为true
  9393. */
  9394. updateTranslateDiv(text, is_escapeHTML = true, is_renderLaTeX = true) {
  9395. // 渲染MarkDown
  9396. let md = window.markdownit({
  9397. html: !is_escapeHTML,
  9398. });
  9399. if (!text) text = "";
  9400. let html = md.render(text);
  9401. this.mainDiv.html(html);
  9402. // 渲染Latex
  9403. if (is_renderLaTeX) {
  9404. // MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.mainDiv.get(0)]);
  9405. this.renderLaTeX(this.mainDiv.get(0));
  9406. }
  9407. // // 渲染代码块中的公式 (AtCoder)
  9408. // this.mainDiv.find('pre code').each((index, element) => {
  9409. // const codeText = $(element).text();
  9410. // const latexPattern = /\$\$(\\.|[^\$])*?\$\$|\$(\\.|[^\$])*?\$/;
  9411. // if (latexPattern.test(codeText)) {
  9412. // this.renderLaTeX(element);
  9413. // }
  9414. // });
  9415. }
  9416.  
  9417. /**
  9418. * 关闭元素
  9419. */
  9420. close() {
  9421. this.closeButton.click();
  9422. }
  9423.  
  9424. /**
  9425. * 收起元素
  9426. */
  9427. fold() {
  9428. if (!this.upButton.hasClass("reverse")) {
  9429. this.upButton.click();
  9430. }
  9431. }
  9432.  
  9433. /**
  9434. * 注册收起按钮事件
  9435. */
  9436. registerUpButtonEvent() {
  9437. this.upButton.on("click", () => {
  9438. // 如果没有reverse类,说明是展开状态
  9439. if (!this.upButton.hasClass("reverse")) {
  9440. // 执行收起操作
  9441. this.upButton.addClass("reverse");
  9442. this.upButton.setButtonState(
  9443. "initial",
  9444. i18next.t("fold.unfold", { ns: "button" })
  9445. );
  9446. OJB_toggleCollapseExpand(this.mainDiv.get(0));
  9447. } else {
  9448. // 执行展开操作
  9449. this.upButton.removeClass("reverse");
  9450. this.upButton.setButtonState(
  9451. "initial",
  9452. i18next.t("fold.normal", { ns: "button" })
  9453. );
  9454. OJB_toggleCollapseExpand(this.mainDiv.get(0));
  9455. }
  9456. });
  9457. }
  9458.  
  9459. /**
  9460. * 注册关闭按钮事件
  9461. */
  9462. registerCloseButtonEvent() {
  9463. this.closeButton.on("click", () => {
  9464. $(this.div).remove();
  9465. $(this.panelDiv).remove();
  9466. if (
  9467. OJBetter.typeOfPage.is_problem &&
  9468. OJBetter.translation.memory.enabled
  9469. ) {
  9470. OJBetter.translation.memory.ttTree.rmTransResultMap(this.id); // 移除ttTree中的数据
  9471. OJBetter.translation.memory.ttTree.refreshNode(".ttypography");
  9472. updateTransDBData(
  9473. OJBetter.translation.memory.ttTree.getNodeData(),
  9474. OJBetter.translation.memory.ttTree.getTransResultMap()
  9475. ); // 更新DB中的数据
  9476. }
  9477. });
  9478. }
  9479.  
  9480. /**
  9481. * 注册复制按钮事件
  9482. * @param {string} text 复制的文本
  9483. */
  9484. registerCopyButtonEvent(text) {
  9485. this.copyButton.on("click", () => {
  9486. GM_setClipboard(text);
  9487. this.copyButton.setButtonState(
  9488. "success",
  9489. i18next.t("copy.copied", { ns: "button" })
  9490. );
  9491. // 复制提示
  9492. setTimeout(() => {
  9493. this.copyButton.setButtonState(
  9494. "initial",
  9495. i18next.t("copy.normal", { ns: "button" })
  9496. );
  9497. }, 2000);
  9498. });
  9499. }
  9500.  
  9501. /**
  9502. * 禁用复制按钮
  9503. */
  9504. disableCopyButton() {
  9505. this.copyButton.css({ fill: "#ccc" });
  9506. this.copyButton.off("click");
  9507. }
  9508.  
  9509. /**
  9510. * 设置面板为error状态
  9511. */
  9512. setError() {
  9513. this.div.addClass("error");
  9514. this.panelDiv.addClass("error");
  9515. this.mainDiv.addClass("error");
  9516. }
  9517.  
  9518. /**
  9519. * 设置原始数据数据
  9520. * @param {Object} Object 原始数据
  9521. */
  9522. setRawData(Object) {
  9523. this.mainDivState.rawDataHTML = $("<pre>")
  9524. .text(JSON.stringify(Object, null, 4))
  9525. .get(0);
  9526. if (this.mainDivState.current === "rawDataHTML") {
  9527. this.renderMainDiv();
  9528. }
  9529. }
  9530.  
  9531. /**
  9532. * 切换结果面板与原始数据面板
  9533. */
  9534. switchMainDiv() {
  9535. // 在切换之前,保存当前内容的状态
  9536. this.mainDivState[this.mainDivState.current] = this.mainDiv.html();
  9537. // 切换当前状态
  9538. this.debugButton.setButtonState(
  9539. this.mainDivState.current === "transHTML" ? "enabled" : "initial"
  9540. );
  9541. this.mainDivState.current =
  9542. this.mainDivState.current === "transHTML" ? "rawDataHTML" : "transHTML";
  9543. // 渲染新的当前状态
  9544. this.renderMainDiv();
  9545. }
  9546.  
  9547. // 渲染当前内容到 mainDiv
  9548. renderMainDiv() {
  9549. requestAnimationFrame(() => {
  9550. this.mainDiv.html(this.mainDivState[this.mainDivState.current]);
  9551. });
  9552. }
  9553.  
  9554. /**
  9555. * 注册debug按钮事件
  9556. */
  9557. registerDebugButtonEvent() {
  9558. this.debugButton.on("click", () => {
  9559. this.switchMainDiv();
  9560. });
  9561. }
  9562.  
  9563. /**
  9564. * 显示debug按钮
  9565. */
  9566. showDebugButton() {
  9567. this.debugButton.show();
  9568. this.registerDebugButtonEvent();
  9569. }
  9570.  
  9571. /**
  9572. * 注册查询余额按钮事件
  9573. * @param {function} callback 查询回调函数
  9574. */
  9575. registerQueryBalanceButtonEvent(callback) {
  9576. this.queryBalanceButton.on("click", async () => {
  9577. this.queryBalanceButton.setButtonState(
  9578. "loading",
  9579. i18next.t("queryBalance.loading", { ns: "button" })
  9580. );
  9581. try {
  9582. const balance = await callback();
  9583. this.queryBalanceButton.setButtonState(
  9584. "success",
  9585. `${i18next.t("queryBalance.success", { ns: "button" })} ${balance}`
  9586. );
  9587. } catch (error) {
  9588. this.queryBalanceButton.setButtonState(
  9589. "error",
  9590. `${i18next.t("queryBalance.error", { ns: "button" })} ${
  9591. error.message
  9592. }`
  9593. );
  9594. }
  9595. });
  9596. }
  9597.  
  9598. /**
  9599. * 显示余额查询按钮
  9600. * @param {string} server 服务名称
  9601. */
  9602. showQueryBalanceButton(server) {
  9603. if (server == "deepl") {
  9604. const quotaConfig = OJBetter.deepl.config.quota;
  9605. if (quotaConfig.url && quotaConfig.surplus && quotaConfig.header) {
  9606. this.queryBalanceButton.show();
  9607. this.registerQueryBalanceButtonEvent(() => {
  9608. return queryServerBalance(OJBetter.deepl.config.quota);
  9609. });
  9610. }
  9611. } else if (server == "openai") {
  9612. const quotaConfig = OJBetter.chatgpt.config.quota;
  9613. if (quotaConfig.url && quotaConfig.surplus && quotaConfig.header) {
  9614. this.queryBalanceButton.show();
  9615. this.registerQueryBalanceButtonEvent(() => {
  9616. return queryServerBalance(OJBetter.chatgpt.config.quota);
  9617. });
  9618. }
  9619. }
  9620. }
  9621. }
  9622.  
  9623. // 元素关系树
  9624. class ElementsTree {
  9625. constructor(elements) {
  9626. this.node = [];
  9627. this.transResultMap = {};
  9628. this.index = 0;
  9629. this.tagNames = ["DIV", "P", "UL", "LI"];
  9630. this.init($(elements));
  9631. }
  9632.  
  9633. // Iterate through all elements, because there may be multiple ttypography
  9634. init(elements) {
  9635. elements.each((i, e) => {
  9636. this.node.push({}); // add one element
  9637. this.index = 0; // reset index
  9638. this.create(i, $(e));
  9639. });
  9640. }
  9641.  
  9642. // 刷新关系树
  9643. refreshNode(elements) {
  9644. this.node = [];
  9645. this.index = 0;
  9646. this.init($(elements));
  9647. }
  9648.  
  9649. // 创建节点间的关系树
  9650. create(i_, element) {
  9651. var prev = null;
  9652. var node = this.node[i_];
  9653. element.children().each((i, e) => {
  9654. // only add element with tagNames
  9655. if (this.tagNames.includes($(e).prop("tagName"))) {
  9656. prev = this.addNode(i_, prev, e);
  9657. }
  9658. // recursively child element
  9659. if ($(e).children().length > 0 && prev !== null) {
  9660. node[prev].firstChild = this.index;
  9661. this.create(i_, $(e));
  9662. }
  9663. });
  9664. }
  9665.  
  9666. // 向树中添加一个节点
  9667. addNode(i_, prev, e) {
  9668. let node = this.node[i_];
  9669. node[this.index] = {
  9670. prev: prev,
  9671. next: null,
  9672. firstChild: null,
  9673. type: $(e).prop("tagName"),
  9674. isTranslateDiv: $(e).hasClass("translateDiv"),
  9675. topText: $(e).attr("data-topText"),
  9676. id: $(e).attr("id"),
  9677. };
  9678.  
  9679. if (prev !== null) {
  9680. node[prev].next = this.index;
  9681. }
  9682.  
  9683. prev = this.index;
  9684.  
  9685. this.index++;
  9686. return prev;
  9687. }
  9688.  
  9689. getNodeData() {
  9690. return this.node;
  9691. }
  9692.  
  9693. setNodeData(node) {
  9694. this.node = node;
  9695. }
  9696.  
  9697. getTransResultMap() {
  9698. return this.transResultMap;
  9699. }
  9700.  
  9701. setTransResultMap(transResultMap) {
  9702. this.transResultMap = transResultMap;
  9703. }
  9704.  
  9705. rmTransResultMap(id) {
  9706. delete this.transResultMap[id];
  9707. }
  9708.  
  9709. addTransResultMap(id, text) {
  9710. this.transResultMap[id] = text;
  9711. }
  9712.  
  9713. getTranslateDivNum(ttTree) {
  9714. var num = 0;
  9715. for (var i in ttTree) {
  9716. if (ttTree[i].isTranslateDiv) {
  9717. num++;
  9718. }
  9719. }
  9720. return num;
  9721. }
  9722.  
  9723. // 恢复目标元素中的translateDiv
  9724. recover(elements) {
  9725. elements.each((i, e) => {
  9726. var ttTreeNode = this.node[i];
  9727. var missingTranslateDivs = this.getTranslateDivNum(ttTreeNode);
  9728. if (missingTranslateDivs > 0) {
  9729. this.recoverOneElement($(e), ttTreeNode);
  9730. }
  9731. });
  9732. }
  9733.  
  9734. recoverOneElement(element, ttTreeNode) {
  9735. this.recoverOneFork(element.children().eq(0), ttTreeNode, 0);
  9736. }
  9737.  
  9738. // 恢复一个分支
  9739. recoverOneFork(pElement, ttTreeNode, index) {
  9740. do {
  9741. // only recover element with tagNames
  9742. if (!this.tagNames.includes(pElement.prop("tagName"))) {
  9743. if (pElement.next().length > 0) {
  9744. pElement = pElement.next();
  9745. } else {
  9746. return;
  9747. }
  9748. }
  9749. if (
  9750. !ttTreeNode[index] ||
  9751. pElement.prop("tagName") !== ttTreeNode[index].type
  9752. ) {
  9753. // console.warn(`元素不存在或类型不同, 元素结构可能已经发生了变化: \nindex: ${index}`, pElement);
  9754. return;
  9755. } else {
  9756. // recursively child element
  9757. var node = ttTreeNode[index];
  9758. if (node.firstChild !== null) {
  9759. this.recoverOneFork(
  9760. pElement.children().eq(0),
  9761. ttTreeNode,
  9762. node.firstChild
  9763. );
  9764. }
  9765. // check if next node is translateDiv
  9766. if (node.next !== null) {
  9767. index = node.next;
  9768.  
  9769. var ne_node = ttTreeNode[index];
  9770. if (ne_node.isTranslateDiv) {
  9771. var id = ne_node.id;
  9772. var topText = ne_node.topText;
  9773. var text = this.transResultMap[id];
  9774. // create element after pElement
  9775. this.reCreateTransDiv(
  9776. pElement,
  9777. id,
  9778. text,
  9779. topText,
  9780. node.isTranslateDiv
  9781. ); // 如果前面一个也是翻译结果,则该结果折叠
  9782. }
  9783. pElement = pElement.next(); // go to next element
  9784. }
  9785. }
  9786. } while (node.next !== null);
  9787. }
  9788.  
  9789. /**
  9790. * 重新创建translateDiv
  9791. * @param {*} pElement
  9792. * @param {*} id
  9793. * @param {*} translatedText
  9794. * @param {*} topText
  9795. * @param {Boolean} isFold 是否折叠
  9796. */
  9797. reCreateTransDiv(pElement, id, translatedText, topText, isFold) {
  9798. const translateDiv = new TranslateDiv(id);
  9799. pElement.after(translateDiv.getDiv());
  9800. translateDiv.setTopText(topText);
  9801. translateDiv.registerUpButtonEvent();
  9802. translateDiv.registerCloseButtonEvent();
  9803. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  9804. translateDiv.registerCopyButtonEvent(translatedText);
  9805. } else {
  9806. translateDiv.disableCopyButton();
  9807. }
  9808. translateDiv.updateTranslateDiv(
  9809. translatedText,
  9810. !(
  9811. (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) &&
  9812. !OJBetter.translation.forceTurndownConversion
  9813. )
  9814. );
  9815. // 标记已翻译并添加到翻译按钮的结果栈中
  9816. let transButton = pElement.prev(".html2md-panel").find(".translateButton");
  9817. if (transButton.length == 0) {
  9818. // 如果没有找到,则应该是得在父元素中找到
  9819. transButton = pElement
  9820. .parent()
  9821. .prev(".html2md-panel")
  9822. .find(".translateButton");
  9823. }
  9824. if (isFold) translateDiv.fold(); // 是否折叠该翻译
  9825. transButton.pushResultToTransButton({
  9826. translateDiv: translateDiv,
  9827. status: 0,
  9828. });
  9829. transButton.setTransButtonState("success");
  9830. }
  9831. }
  9832.  
  9833. // 更新TransDB中的翻译数据
  9834. async function updateTransDBData(nodeDate, transResultMap) {
  9835. var url = window.location.href.replace(/#/, "");
  9836. try {
  9837. await OJBetter.common.database.translateData.put({
  9838. url,
  9839. transResultMap,
  9840. nodeDate,
  9841. });
  9842. return "translateData saved successfully";
  9843. } catch (error) {
  9844. throw new Error(`Failed to save translateData: ${error}`);
  9845. }
  9846. }
  9847.  
  9848. // 获取TransDB中保存的翻译数据
  9849. async function getTransDBData() {
  9850. var url = window.location.href.replace(/#/, "");
  9851. try {
  9852. const result = await OJBetter.common.database.translateData.get(url);
  9853. return result;
  9854. } catch (error) {
  9855. throw new Error(`Failed to get translateData: ${error}`);
  9856. }
  9857. }
  9858.  
  9859. /**
  9860. * 翻译结果恢复功能初始化
  9861. * @returns
  9862. */
  9863. async function initTransResultsRecover() {
  9864. OJBetter.translation.memory.ttTree = new ElementsTree(".ttypography"); // 初始化当前页面.ttypography元素的结构树
  9865. let result = await getTransDBData();
  9866. if (!result) return;
  9867. OJBetter.translation.memory.ttTree.setNodeData(result.nodeDate);
  9868. OJBetter.translation.memory.ttTree.setTransResultMap(result.transResultMap);
  9869. OJBetter.translation.memory.ttTree.recover($(".ttypography"));
  9870. }
  9871.  
  9872. /**
  9873. * 自动翻译
  9874. */
  9875. async function initTransWhenViewable() {
  9876. await waitForMathJaxIdle();
  9877.  
  9878. const elements = $(".ttypography, .comments").find(".translateButton");
  9879. const observers = [];
  9880.  
  9881. // Use a single Intersection Observer for all elements
  9882. const observer = new IntersectionObserver((entries, obs) => {
  9883. entries.forEach((entry) => {
  9884. if (entry.isIntersecting) {
  9885. const button = $(entry.target);
  9886. const state = button.getButtonState();
  9887. const notAutoTranslate = button.getNotAutoTranslate();
  9888. // Check if the button meets the criteria
  9889. if (state === "normal" && !notAutoTranslate) {
  9890. let trans = OJBetter.translation.choice;
  9891.  
  9892. if (
  9893. OJBetter.translation.auto.mixTrans.enabled &&
  9894. button.IsCommentButton() &&
  9895. OJBetter.translation.auto.mixTrans.servers.length > 0
  9896. ) {
  9897. const randomIndex = Math.floor(
  9898. Math.random() * OJBetter.translation.auto.mixTrans.servers.length
  9899. );
  9900. trans = OJBetter.translation.auto.mixTrans.servers[randomIndex];
  9901. }
  9902. button.data("translatedItBy")(trans);
  9903. }
  9904.  
  9905. // Stop observing the element
  9906. obs.unobserve(entry.target);
  9907. }
  9908. });
  9909. });
  9910.  
  9911. // Observe each element
  9912. elements.each((i, e) => {
  9913. observer.observe(e);
  9914. });
  9915.  
  9916. // Store the observer in case you need to disconnect it later
  9917. observers.push(observer);
  9918. }
  9919.  
  9920. /**
  9921. * 翻译返回结果结构体
  9922. * @typedef {Object} TranslateResult
  9923. * @property {string} status 翻译状态
  9924. * @property {TranslateDiv} translateDiv 翻译结果面板
  9925. * @property {TransRawData} rawData 原始翻译数据
  9926. */
  9927.  
  9928. /**
  9929. * 翻译主方法
  9930. * @param {string} text 待翻译文本
  9931. * @param {HTMLElement} element_node 元素节点
  9932. * @param {Boolean} is_comment 是否为评论区文本
  9933. * @param {string} overrideTrans 覆盖全局翻译服务设定
  9934. * @returns {TranslateResult} 翻译结果对象
  9935. */
  9936. async function translateMain(
  9937. text,
  9938. element_node,
  9939. is_comment,
  9940. overrideTrans
  9941. ) {
  9942. /** @type {number} 翻译结果的ID*/
  9943. const id = OJB_getRandomNumber(8);
  9944. /** @type {TextBlockReplacer} 文本块替换/恢复实例*/
  9945. const textBlockReplacer = new TextBlockReplacer();
  9946. /** @type {string} 翻译结果文本*/
  9947. let translatedText = "";
  9948.  
  9949. /** @type {string} 当前实际应用的翻译服务 */
  9950. const realTransServer =
  9951. overrideTrans ||
  9952. (is_comment && OJBetter.translation.comment.choice != "0"
  9953. ? OJBetter.translation.comment.choice
  9954. : OJBetter.translation.choice);
  9955.  
  9956. /** @type {TranslateResult} 翻译结果对象 */
  9957. const translateResult = {
  9958. status: "ok",
  9959. rawData: {
  9960. done: false,
  9961. },
  9962. };
  9963.  
  9964. /**
  9965. * LaTeX替换
  9966. * @param {string} text 待翻译文本
  9967. * @returns {string} 处理后的文本
  9968. */
  9969. const replaceLatex = function (text) {
  9970. if (OJBetter.typeOfPage.is_oldLatex) {
  9971. const regex = /<span\s+class="tex-span">.*?<\/span>/gi;
  9972. text = textBlockReplacer.replace(text, regex);
  9973. text = text.replace(/<p>(.*?)<\/p>/g, "$1\n\n"); // <p/>标签换为换行
  9974. } else if (OJBetter.typeOfPage.is_acmsguru) {
  9975. const regex =
  9976. /<i>.*?<\/i>|<sub>.*?<\/sub>|<sup>.*?<\/sup>|<pre>.*?<\/pre>/gi;
  9977. text = textBlockReplacer.replace(text, regex);
  9978. } else if (realTransServer != "openai") {
  9979. // 使用GPT翻译时不必替换latex公式
  9980. let regex = /\$\$([^]*?)\$\$|\$(\\\$|[^\$])*?\$/g;
  9981. // 目标语言是中文时,匹配行内公式时对序数词特殊判断以优化翻译
  9982. if (OJBetter.translation.targetLang === "zh")
  9983. regex = /\$\$([^]*?)\$\$|\$(\\\$|[^\$])*?\$(st|nd|rd|th)?/g;
  9984. text = textBlockReplacer.replace(text, regex);
  9985.  
  9986. // 替换行间代码块```
  9987. const regex2 = /```[\s\S]*?```/g;
  9988. text = textBlockReplacer.replace(text, regex2);
  9989. }
  9990. return text;
  9991. };
  9992.  
  9993. /**
  9994. * LaTeX恢复
  9995. * @param {string} text 已翻译的文本
  9996. * @returns {string} 恢复后的文本
  9997. */
  9998. const recoverLatex = function (text) {
  9999. // 两个公式之间加个空格,防止有些LaTeX解析器解析错误
  10000. let resultText = text
  10001. .replace(/】【/g, "】 【")
  10002. .replace(/\]\[/g, "] [")
  10003. .replace(/\}\{/g, "} {");
  10004.  
  10005. if (OJBetter.typeOfPage.is_oldLatex) {
  10006. resultText = resultText.replace(/(.+?)(\n\n|$)/g, "<p>$1</p>"); // 换行符还原为<p/>标签
  10007. resultText = textBlockReplacer.recover(resultText);
  10008. } else if (OJBetter.typeOfPage.is_acmsguru) {
  10009. resultText = textBlockReplacer.recover(resultText);
  10010. } else if (realTransServer != "openai") {
  10011. resultText = textBlockReplacer.recover(resultText);
  10012. }
  10013. return resultText;
  10014. };
  10015.  
  10016. /**
  10017. * 格式化翻译结果
  10018. * @param {string} text
  10019. * @returns {string} 处理后的翻译结果
  10020. */
  10021. const formatText = function (text) {
  10022. // 转义LaTex中的特殊符号
  10023. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  10024. // 先替换掉行间代码块
  10025. const replacer = new TextBlockReplacer();
  10026. text = replacer.replace(text, /```[\s\S]*?```/g);
  10027.  
  10028. // 处理LaTeX公式
  10029. const escapeRules = [
  10030. { pattern: /(?<!\\)>(?!\s)/g, replacement: " &gt; " }, // >符号
  10031. { pattern: /(?<!\\)</g, replacement: " &lt; " }, // <符号
  10032. { pattern: /(?<!\\)\*/g, replacement: " &#42; " }, // *符号
  10033. { pattern: /(?<!\\)_/g, replacement: " &#95; " }, // _符号
  10034. { pattern: /(?<!\\)\\\\(?=\s)/g, replacement: "\\\\\\\\" }, // \\符号
  10035. { pattern: /(?<!\\)\\(?![\\a-zA-Z0-9])/g, replacement: "\\\\" }, // \符号
  10036. ];
  10037.  
  10038. let latexMatches = [
  10039. ...text.matchAll(/\$\$([\s\S]*?)\$\$|\$(.*?)\$|\$([\s\S]*?)\$/g),
  10040. ];
  10041. for (const match of latexMatches) {
  10042. const matchedText = match[0];
  10043. let escapedText = matchedText;
  10044.  
  10045. for (const rule of escapeRules) {
  10046. escapedText = escapedText.replaceAll(rule.pattern, rule.replacement);
  10047. }
  10048. escapedText = escapedText.replace(/\$\$/g, "$$$$$$$$"); // $$符号(因为后面需要作为replacement,双倍消耗)
  10049. text = text.replace(matchedText, escapedText);
  10050. }
  10051.  
  10052. // 恢复行间代码块
  10053. text = replacer.recover(text);
  10054. }
  10055.  
  10056. // 使符合mathjx的转换语法
  10057. const mathjaxRuleMap = [
  10058. { pattern: /\$/g, replacement: "$$$$$$" }, // $$ 行间
  10059. ];
  10060. mathjaxRuleMap.forEach(({ pattern, replacement }) => {
  10061. text = text.replace(pattern, replacement);
  10062. });
  10063.  
  10064. // markdown修正
  10065. const mdRuleMap = [
  10066. {
  10067. pattern: /(\s_[\u4e00-\u9fa5]+_)([\u4e00-\u9fa5]+)/g,
  10068. replacement: "$1 $2",
  10069. }, // 斜体
  10070. {
  10071. pattern: /(_[\u4e00-\u9fa5]+_\s)([\u4e00-\u9fa5]+)/g,
  10072. replacement: " $1$2",
  10073. },
  10074. {
  10075. pattern: /(_[\u4e00-\u9fa5]+_)([\u4e00-\u9fa5]+)/g,
  10076. replacement: " $1 $2",
  10077. },
  10078. { pattern: /(([\s\S]*?))/g, replacement: "($1)" }, // 中文()
  10079. // { pattern: /:/g, replacement: ":" }, // 中文:
  10080. { pattern: /\*\* (.*?) \*\*/g, replacement: "**$1**" }, // 加粗
  10081. { pattern: /\* \*(.*?)\* \*/g, replacement: "**$1**" }, // 加粗
  10082. ];
  10083. mdRuleMap.forEach(({ pattern, replacement }) => {
  10084. text = text.replace(pattern, replacement);
  10085. });
  10086.  
  10087. return text;
  10088. };
  10089.  
  10090. // 创建翻译结果元素并放在element_node的后面
  10091. translateResult.translateDiv = new TranslateDiv(id);
  10092. $(element_node).after(translateResult.translateDiv.getDiv());
  10093.  
  10094. // 顶栏左侧信息
  10095. translateResult.translateDiv.setTopText(
  10096. i18next.t("servers." + realTransServer, { ns: "translator" }) +
  10097. i18next.t("translateDiv.topTextSuffix", { ns: "translator" })
  10098. );
  10099.  
  10100. // 注册按钮
  10101. translateResult.translateDiv.registerUpButtonEvent();
  10102. translateResult.translateDiv.registerCloseButtonEvent();
  10103. if (
  10104. OJBetter.translation.choice == "openai" ||
  10105. OJBetter.translation.choice == "deepl"
  10106. ) {
  10107. translateResult.translateDiv.showQueryBalanceButton(
  10108. OJBetter.translation.choice
  10109. ); // 显示额度查询
  10110. }
  10111.  
  10112. // 翻译内容是否为空文本
  10113. if (isEmptyText(text)) {
  10114. const shouldContinue = await OJB_createDialog(
  10115. i18next.t("isEmptyText.title", { ns: "dialog" }),
  10116. i18next.t("isEmptyText.content", { ns: "dialog" }),
  10117. [
  10118. i18next.t("isEmptyText.buttons.0", { ns: "dialog" }),
  10119. i18next.t("isEmptyText.buttons.1", { ns: "dialog" }),
  10120. ],
  10121. true
  10122. );
  10123. if (shouldContinue) {
  10124. translateResult.status = "skip";
  10125. return translateResult;
  10126. }
  10127. }
  10128.  
  10129. // 替换latex公式
  10130. text = replaceLatex(text);
  10131.  
  10132. // 过滤**号
  10133. if (
  10134. OJBetter.translation.filterTextWithoutEmphasis &&
  10135. GM_getValue("translation") !== "openai"
  10136. ) {
  10137. // TODO
  10138. text = text.replace(/\*\*/g, "");
  10139. }
  10140.  
  10141. // 字符数上限
  10142. const translationLimits = {
  10143. deepl: 5000,
  10144. iflyrec: 2000,
  10145. youdao: 5000,
  10146. google: 5000,
  10147. caiyun: 5000,
  10148. };
  10149. if (
  10150. translationLimits.hasOwnProperty(realTransServer) &&
  10151. text.length > translationLimits[realTransServer]
  10152. ) {
  10153. let textLength = translationLimits[realTransServer];
  10154. let realTextLength = text.length;
  10155. const shouldContinue = await OJB_createDialog(
  10156. i18next.t("transTextLimits.title", { ns: "dialog" }),
  10157. i18next.t("transTextLimits.content", {
  10158. ns: "dialog",
  10159. textLength: textLength,
  10160. realTextLength: realTextLength,
  10161. }),
  10162. [
  10163. i18next.t("transTextLimits.buttons.0", { ns: "dialog" }),
  10164. i18next.t("transTextLimits.buttons.1", { ns: "dialog" }),
  10165. ],
  10166. true
  10167. ); // 字数超限确认
  10168. if (shouldContinue) {
  10169. translateResult.status = "skip";
  10170. return translateResult;
  10171. }
  10172. }
  10173.  
  10174. /**
  10175. * 调用各个翻译服务
  10176. * @param {string} transServer 翻译服务
  10177. * @returns {TransRawData} 原始翻译数据
  10178. */
  10179. async function translate(transServer) {
  10180. const is_renderLaTeX = !(
  10181. (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) &&
  10182. !OJBetter.translation.forceTurndownConversion
  10183. );
  10184. const servername = i18next.t("servers." + realTransServer, {
  10185. ns: "translator",
  10186. });
  10187. /** @type {TransRawData} 原始翻译数据*/
  10188. let rawData = {};
  10189. try {
  10190. if (transServer == "deepl") {
  10191. if (OJBetter.deepl.config.type == "free") {
  10192. translateResult.translateDiv.updateTranslateDiv(
  10193. `${i18next.t("transingTip.basic", {
  10194. ns: "translator",
  10195. server: servername,
  10196. })}`,
  10197. is_renderLaTeX
  10198. );
  10199. rawData = await translate_deepl(text);
  10200. } else if (OJBetter.deepl.config.type == "api") {
  10201. translateResult.translateDiv.updateTranslateDiv(
  10202. `${i18next.t("transingTip.deeplApi", {
  10203. ns: "translator",
  10204. deepl_configName: OJBetter.deepl.config.name,
  10205. })}`,
  10206. is_renderLaTeX
  10207. );
  10208. if (OJBetter.deepl.config.apiGenre == "deeplx") {
  10209. rawData = await translate_deeplx(text);
  10210. } else {
  10211. if (OJBetter.deepl.enableEmphasisProtection)
  10212. text = convertBoldMarkdownToHTML(text);
  10213. if (OJBetter.deepl.enableLinkProtection)
  10214. text = convertLinksMarkdownToHTML(text);
  10215. if (OJBetter.deepl.config.apiGenre == "api-free") {
  10216. rawData = await translate_deepl_api_free(text);
  10217. } else if (OJBetter.deepl.config.apiGenre == "api-pro") {
  10218. rawData = await translate_deepl_api_pro(text);
  10219. }
  10220. if (OJBetter.deepl.enableEmphasisProtection)
  10221. rawData.text = convertBoldHTMLToMarkdown(rawData.text);
  10222. if (OJBetter.deepl.enableLinkProtection)
  10223. rawData.text = convertLinksHTMLToMarkdown(rawData.text);
  10224. }
  10225. }
  10226. } else if (transServer == "iflyrec") {
  10227. translateResult.translateDiv.updateTranslateDiv(
  10228. `${i18next.t("transingTip.basic", {
  10229. ns: "translator",
  10230. server: servername,
  10231. })}`,
  10232. is_renderLaTeX
  10233. );
  10234. rawData = await translate_iflyrec(text);
  10235. } else if (transServer == "youdao") {
  10236. translateResult.translateDiv.updateTranslateDiv(
  10237. `${i18next.t("transingTip.basic", {
  10238. ns: "translator",
  10239. server: servername,
  10240. })}`,
  10241. is_renderLaTeX
  10242. );
  10243. rawData = await translate_youdao_web(text);
  10244. } else if (transServer == "google") {
  10245. translateResult.translateDiv.updateTranslateDiv(
  10246. `${i18next.t("transingTip.basic", {
  10247. ns: "translator",
  10248. server: servername,
  10249. })}`,
  10250. is_renderLaTeX
  10251. );
  10252. rawData = await translate_gg(text);
  10253. } else if (transServer == "caiyun") {
  10254. translateResult.translateDiv.updateTranslateDiv(
  10255. `${i18next.t("transingTip.basic", {
  10256. ns: "translator",
  10257. server: servername,
  10258. })}`,
  10259. is_renderLaTeX
  10260. );
  10261. rawData = await translate_caiyun(text);
  10262. } else if (transServer == "openai") {
  10263. translateResult.translateDiv.updateTranslateDiv(
  10264. `${i18next.t("transingTip.openai", {
  10265. ns: "translator",
  10266. openai_name: OJBetter.chatgpt.config.name,
  10267. })}${
  10268. !OJBetter.chatgpt.isStream
  10269. ? i18next.t("transingTip.openai_isStream", { ns: "translator" })
  10270. : ""
  10271. }`,
  10272. is_renderLaTeX
  10273. );
  10274. if (OJBetter.chatgpt.isStream) {
  10275. // 流式传输
  10276. rawData = await translate_openai_stream(
  10277. text,
  10278. translateResult.translateDiv
  10279. );
  10280. } else {
  10281. // 普通模式
  10282. rawData = await translate_openai(text);
  10283. }
  10284. }
  10285. translatedText = rawData.text;
  10286. if (!rawData.done) {
  10287. translateResult.status = "error";
  10288. }
  10289. } catch (e) {
  10290. translateResult.status = "error";
  10291. rawData.message = i18next.t("error.unexpected", { ns: "translator" });
  10292. console.warn(e);
  10293. }
  10294. return rawData;
  10295. }
  10296. translateResult.rawData = await translate(realTransServer);
  10297.  
  10298. if (translateResult.status == "error") {
  10299. translateResult.translateDiv.updateTranslateDiv(
  10300. translateResult.rawData.message
  10301. );
  10302. return translateResult;
  10303. }
  10304.  
  10305. // 还原latex公式
  10306. translatedText = recoverLatex(translatedText);
  10307.  
  10308. // 注册结果复制按钮
  10309. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  10310. translateResult.translateDiv.registerCopyButtonEvent(translatedText);
  10311. } else {
  10312. translateResult.translateDiv.disableCopyButton();
  10313. }
  10314.  
  10315. // 翻译结果格式化
  10316. translatedText = formatText(translatedText);
  10317.  
  10318. // 保存翻译结果
  10319. if (
  10320. (OJBetter.typeOfPage.is_problem ||
  10321. OJBetter.typeOfPage.is_completeProblemset) &&
  10322. OJBetter.translation.memory.enabled
  10323. ) {
  10324. OJBetter.translation.memory.ttTree.refreshNode(".ttypography"); // 刷新当前页面.ttypography元素的结构树实例
  10325. OJBetter.translation.memory.ttTree.addTransResultMap(id, translatedText);
  10326. updateTransDBData(
  10327. OJBetter.translation.memory.ttTree.getNodeData(),
  10328. OJBetter.translation.memory.ttTree.getTransResultMap()
  10329. ); // 更新翻译结果到transDB
  10330. }
  10331.  
  10332. // 翻译结果面板更新
  10333. translateResult.translateDiv.updateTranslateDiv(
  10334. translatedText,
  10335. !(
  10336. (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) &&
  10337. !OJBetter.translation.forceTurndownConversion
  10338. )
  10339. );
  10340.  
  10341. return translateResult;
  10342. }
  10343.  
  10344. //弹窗翻译
  10345. function alertZh() {
  10346. // var _alert = window.alert;
  10347. // window.alert = async function (msg) {
  10348. // _alert(msg + "\n=========翻译=========\n" + await translate_deepl(msg));
  10349. // return true;
  10350. // }
  10351. }
  10352.  
  10353. /**
  10354. * 折叠块展开
  10355. */
  10356. function ExpandFoldingblocks() {
  10357. $(".spoiler").addClass("spoiler-open");
  10358. $(".spoiler-content").attr("style", "");
  10359. }
  10360.  
  10361. /**
  10362. * 折叠块渲染优化
  10363. */
  10364. function RenderPerfOpt() {
  10365. GM_addStyle(`
  10366. .spoiler-content {
  10367. contain: layout style;
  10368. }
  10369. `);
  10370. }
  10371.  
  10372. /**
  10373. * 下拉选择框性能优化
  10374. */
  10375. async function SelectElementPerfOpt() {
  10376. // TODO 10
  10377. // 加载库资源
  10378. await OJB_LoadJS(
  10379. "https://aowuucdn.oss-accelerate.aliyuncs.com/js/selectpage.min.js",
  10380. "sha512-HhBheWc9nbTuTG0oVYtY9c3nkJAAiuk899lycOtB8NALvp20CNOjlYdTAYbRy9/0zXnLl0LZpiwhfLZurvK1XQ=="
  10381. );
  10382. /**
  10383. * 将一个<select>元素转换为SelectPage控件
  10384. * @param {HTMLElement|string} selector - 要转换的<select>元素或其选择器
  10385. */
  10386. const OJB_transformSelectToSelectPage = (selector) => {
  10387. const $select = $(selector);
  10388. if ($select.length === 0 || !$select.is("select")) {
  10389. console.error("Invalid select element provided.");
  10390. return;
  10391. }
  10392.  
  10393. // 隐藏原生的<select>元素
  10394. $select.hide();
  10395.  
  10396. // 创建一个新的<input>元素用于SelectPage控件
  10397. const $inputForSelectPage = $("<input>", {
  10398. type: "text",
  10399. class: "selectpage-input",
  10400. autocomplete: "off",
  10401. });
  10402. $select.after($inputForSelectPage);
  10403.  
  10404. // 准备SelectPage所需的数据格式
  10405. const data = $select
  10406. .find("option")
  10407. .map((_, option) => ({
  10408. id: option.value,
  10409. text: option.text,
  10410. }))
  10411. .get();
  10412.  
  10413. // 初始化SelectPage
  10414. $inputForSelectPage.selectPage({
  10415. showField: "text",
  10416. keyField: "id",
  10417. data,
  10418. lang: "en",
  10419. // 当选中一个选项时,更新隐藏的<select>元素的值
  10420. eSelect: (data) => {
  10421. $select.val(data.id).trigger("change");
  10422. },
  10423. // 初始化时根据<select>的当前值设置SelectPage
  10424. initRecord: $select.val(),
  10425. });
  10426. };
  10427.  
  10428. // 遍历页面上的所有select
  10429. $("select").each((_, select) => {
  10430. // 选项大于500才优化
  10431. if ($(select).find("option").length > 500) {
  10432. OJB_transformSelectToSelectPage(select);
  10433. }
  10434. });
  10435. }
  10436.  
  10437. /**
  10438. * 评论区分页
  10439. */
  10440. function CommentPagination() {
  10441. GM_addStyle(`
  10442. .comments > .comment {
  10443. display: none;
  10444. }
  10445. #next-page-btn, #prev-page-btn {
  10446. display: none;
  10447. }
  10448. #jump-input, #items-per-page{
  10449. height: 22px;
  10450. border: 1px solid #dcdfe6;
  10451. border-radius: 0.3rem;
  10452. }
  10453. #jump-input:focus-visible{
  10454. border-style: solid;
  10455. border-color: #3f51b5;
  10456. outline: none;
  10457. }
  10458. `);
  10459. $(".comments").after(`
  10460. <div id="pagBar" style="display: flex; align-items: center; justify-content: center; color: #606266;">
  10461. <label for="items-per-page">${i18next.t("perpage", {
  10462. ns: "comments",
  10463. })}</label>
  10464. <select id="items-per-page" style="margin-right: 15px;">
  10465. <option value="5">5</option>
  10466. <option value="10">10</option>
  10467. <option value="20">20</option>
  10468. </select>
  10469. <div class="paging" style="margin-right: 15px;">
  10470. <span id="current-page">1</span> / <span id="total-pages"></span>
  10471. </div>
  10472. <input type="text" id="jump-input" placeholder="${i18next.t(
  10473. "jumpTo",
  10474. { ns: "comments" }
  10475. )}">
  10476. <button type="button" id="jump-btn" class="ojb_btn">${i18next.t(
  10477. "jump",
  10478. { ns: "comments" }
  10479. )}</button>
  10480. <button id="prev-page-btn" class="ojb_btn">${i18next.t("prev", {
  10481. ns: "comments",
  10482. })}</button>
  10483. <button id="next-page-btn" class="ojb_btn">${i18next.t("next", {
  10484. ns: "comments",
  10485. })}</button>
  10486. </div>
  10487. `);
  10488.  
  10489. let batchSize = 5;
  10490. let elements = $(".comments > .comment:not(.comment-reply-prototype)");
  10491. let start = 0;
  10492. let end = batchSize;
  10493. let currentPage = 1;
  10494. let displayedIndexes = []; // 存储已显示元素的索引
  10495.  
  10496. function showBatch(start, end) {
  10497. // 隐藏上一页
  10498. for (var i = 0; i < displayedIndexes.length; i++) {
  10499. elements.eq(displayedIndexes[i]).hide();
  10500. }
  10501.  
  10502. displayedIndexes = [];
  10503.  
  10504. // 显示当前页
  10505. elements.slice(start, end).each(function (index) {
  10506. $(this).show();
  10507. displayedIndexes.push(start + index);
  10508. });
  10509.  
  10510. // 更新页码和翻页按钮
  10511. $("#current-page").text(currentPage);
  10512. $("#total-pages").text(Math.ceil(elements.length / batchSize));
  10513.  
  10514. if (currentPage === 1) $("#prev-page-btn").hide();
  10515. else $("#prev-page-btn").show();
  10516.  
  10517. if (end >= elements.length) $("#next-page-btn").hide();
  10518. else $("#next-page-btn").show();
  10519. }
  10520.  
  10521. // 初始化
  10522. var commentID = null;
  10523. var pageURL = window.location.href;
  10524. if (pageURL.includes("#comment-")) {
  10525. // 带评论区锚点的链接
  10526. var startIndex = pageURL.lastIndexOf("#comment-") + 9;
  10527. commentID = pageURL.substring(startIndex);
  10528. var indexInComments = null;
  10529. $(".comments > .comment").each(function (index) {
  10530. $(this)
  10531. .find(".comment-table")
  10532. .each(function () {
  10533. var tableCommentID = $(this).attr("commentid");
  10534. if (tableCommentID === commentID) {
  10535. indexInComments = index;
  10536. return false;
  10537. }
  10538. });
  10539. });
  10540. let page = Math.ceil((indexInComments + 1) / batchSize);
  10541. currentPage = !page ? 1 : page;
  10542. showBatch((currentPage - 1) * batchSize, currentPage * batchSize);
  10543. setTimeout(function () {
  10544. window.location.href = pageURL;
  10545. }, 1000);
  10546. } else {
  10547. showBatch(0, batchSize);
  10548. }
  10549.  
  10550. $("#prev-page-btn").on("click", function () {
  10551. var itemsPerPage = parseInt($("#items-per-page").val());
  10552. start = (currentPage - 2) * itemsPerPage;
  10553. end = (currentPage - 1) * itemsPerPage;
  10554. currentPage--;
  10555. showBatch(start, end);
  10556. });
  10557.  
  10558. $("#next-page-btn").on("click", function () {
  10559. var itemsPerPage = parseInt($("#items-per-page").val());
  10560. start = currentPage * itemsPerPage;
  10561. end = (currentPage + 1) * itemsPerPage;
  10562. currentPage++;
  10563. showBatch(start, end);
  10564. });
  10565.  
  10566. $("#jump-btn").on("click", function () {
  10567. var inputPage = parseInt($("#jump-input").val());
  10568.  
  10569. if (
  10570. inputPage >= 1 &&
  10571. inputPage <=
  10572. Math.ceil(elements.length / parseInt($("#items-per-page").val()))
  10573. ) {
  10574. var itemsPerPage = parseInt($("#items-per-page").val());
  10575. start = (inputPage - 1) * itemsPerPage;
  10576. end = inputPage * itemsPerPage;
  10577. currentPage = inputPage; // 更新当前页码
  10578. showBatch(start, end);
  10579. }
  10580. });
  10581.  
  10582. $("#items-per-page").on("change", function () {
  10583. batchSize = parseInt($(this).val());
  10584. let page = Math.ceil(end / batchSize);
  10585. currentPage = !page ? 1 : page;
  10586. let maxPage = Math.ceil(elements.length / batchSize);
  10587. if (currentPage > maxPage) currentPage = maxPage;
  10588. showBatch((currentPage - 1) * batchSize, currentPage * batchSize);
  10589. });
  10590. }
  10591.  
  10592. /**
  10593. * 题目页相关链接栏
  10594. */
  10595. class ProblemPageLinkbar {
  10596. constructor() {
  10597. this.containerElement = this.createToolbar();
  10598. this.commandInvoker = new CommandInvoker();
  10599. }
  10600.  
  10601. /**
  10602. * 创建工具栏
  10603. */
  10604. createToolbar() {
  10605. const toolbarElement = $("<div>")
  10606. .attr("id", "problemToolbar")
  10607. .insertBefore($(".problemindexholder"));
  10608. return new DOMContainer(toolbarElement);
  10609. }
  10610.  
  10611. /**
  10612. * 添加按钮
  10613. * @param {string} id 按钮id
  10614. * @param {string} url 按钮链接
  10615. * @param {string} text 按钮文字
  10616. * @param {JQueryObject} icon 按钮图标
  10617. * @param {string} iconHeight 图标高度
  10618. * @returns {object} 按钮对象
  10619. */
  10620. addLinkButton(id, url, text, icon = $("<div>"), iconHeight = "22px") {
  10621. const linkElement = $("<a>")
  10622. .attr("href", url)
  10623. .attr("target", "_blank")
  10624. .addClass("ojb_btn")
  10625. .attr("id", id);
  10626.  
  10627. linkElement.append(icon);
  10628. icon.css("height", iconHeight);
  10629.  
  10630. const textSpan = $("<span>").html(text);
  10631. linkElement.append(textSpan);
  10632.  
  10633. this.commandInvoker.execute(
  10634. new AddElementCommand(this.containerElement, linkElement)
  10635. );
  10636. return {
  10637. element: linkElement,
  10638. text: textSpan,
  10639. icon: icon,
  10640. };
  10641. }
  10642.  
  10643. /**
  10644. * 更新链接
  10645. * @param {object} button 按钮对象
  10646. * @param {string} url 按钮链接
  10647. */
  10648. updateUrl(button, url) {
  10649. button.element.attr("href", url);
  10650. }
  10651.  
  10652. /**
  10653. * 更新文字
  10654. * @param {object} button 按钮对象
  10655. * @param {string} text 按钮文字
  10656. */
  10657. updateText(button, text) {
  10658. button.text.html(text);
  10659. }
  10660.  
  10661. /**
  10662. * 设置文字为粗体
  10663. * @param {object} button 按钮对象
  10664. */
  10665. setBold(button) {
  10666. button.text.css("font-weight", "bold");
  10667. }
  10668.  
  10669. /**
  10670. * 更新图标
  10671. * @param {object} button 按钮对象
  10672. * @param {JQueryObject} icon 按钮图标
  10673. * @param {string} iconHeight 图标高度
  10674. */
  10675. updateIcon(button, icon, iconHeight = "16px") {
  10676. button.icon.remove();
  10677. button.text.prepend(icon);
  10678. icon.css("height", iconHeight);
  10679. button.icon = icon;
  10680. }
  10681.  
  10682. /**
  10683. * 添加类
  10684. * @param {object} button 按钮对象
  10685. * @param {string} className 类名
  10686. */
  10687. addClass(button, className) {
  10688. button.element.addClass(className);
  10689. }
  10690.  
  10691. /**
  10692. * 禁用链接按钮
  10693. * @param {object} button 按钮对象
  10694. */
  10695. disableButton(button) {
  10696. button.element.addClass("disabled");
  10697. }
  10698.  
  10699. /**
  10700. * 启用链接按钮
  10701. * @param {object} button 按钮对象
  10702. */
  10703. enableButton(button) {
  10704. button.element.removeClass("disabled");
  10705. }
  10706. }
  10707.  
  10708. /**
  10709. * 获取题目的id
  10710. * @param {String} url 题目的链接
  10711. * @returns 题目的id,形如2000A
  10712. */
  10713. function getProblemId(url) {
  10714. const regexMap = new Map([
  10715. ["/contest/", /\/contest\/(\d+)\/problem\/([A-Za-z\d]+)/],
  10716. ["/problemset/", /\/problemset\/problem\/(\d+)\/([A-Za-z\d]+)/],
  10717. ["/gym/", /\/gym\/(\d+)\/problem\/([A-Za-z\d]+)/],
  10718. ]);
  10719.  
  10720. let regex = null;
  10721. for (let [key, value] of regexMap) {
  10722. if (url.includes(key)) {
  10723. regex = value;
  10724. break;
  10725. }
  10726. }
  10727.  
  10728. if (!regex) return "";
  10729.  
  10730. const matchResult = url.match(regex);
  10731. return matchResult && matchResult.length >= 3
  10732. ? `${matchResult[1]}${matchResult[2]}`
  10733. : "";
  10734. }
  10735.  
  10736. /**
  10737. * 跳转到洛谷
  10738. * @param {ProblemPageLinkbar} problemToolbar
  10739. */
  10740. async function CF2luogu(problemToolbar) {
  10741. const url = window.location.href;
  10742. const problemId = getProblemId(url);
  10743. const luoguButton = problemToolbar.addLinkButton(
  10744. "luoguButton",
  10745. "https://www.luogu.com.cn/",
  10746. i18next.t("state.loading", { ns: "button" }),
  10747. $("<img>").attr("src", "https://cdn.luogu.com.cn/fe/logo.png")
  10748. );
  10749.  
  10750. const checkLinkExistence = async (url) => {
  10751. return OJB_promiseRetryWrapper(
  10752. async () => {
  10753. const response = await OJB_GMRequest({
  10754. method: "GET",
  10755. url,
  10756. });
  10757. return !response.responseText.match(/出错了/g);
  10758. },
  10759. {
  10760. maxRetries: 3,
  10761. retryInterval: 1000,
  10762. }
  10763. );
  10764. };
  10765.  
  10766. const LuoguUrl = `https://www.luogu.com.cn/problem/CF${problemId}`;
  10767. try {
  10768. const result = await checkLinkExistence(LuoguUrl);
  10769. if (problemId && result) {
  10770. problemToolbar.updateText(luoguButton, "");
  10771. problemToolbar.updateUrl(luoguButton, LuoguUrl);
  10772. } else {
  10773. problemToolbar.updateText(
  10774. luoguButton,
  10775. i18next.t("state.404", { ns: "button" })
  10776. );
  10777. problemToolbar.disableButton(luoguButton);
  10778. }
  10779. } catch (error) {
  10780. if (error instanceof OJB_GMError && error.type == "error") {
  10781. problemToolbar.updateText(
  10782. luoguButton,
  10783. i18next.t("state.netError", { ns: "button" })
  10784. );
  10785. problemToolbar.disableButton(luoguButton);
  10786. }
  10787. }
  10788. }
  10789.  
  10790. /**
  10791. * 跳转到 Virtual Judge
  10792. * @param {ProblemPageLinkbar} problemToolbar
  10793. */
  10794. async function CF2vjudge(problemToolbar) {
  10795. const url = window.location.href;
  10796. const problemId = getProblemId(url);
  10797. const vjudgeButton = problemToolbar.addLinkButton(
  10798. "vjudgeButton",
  10799. "https://vjudge.net/",
  10800. i18next.t("state.loading", { ns: "button" }),
  10801. $("<img>").attr(
  10802. "src",
  10803. "https://aowuucdn.oss-accelerate.aliyuncs.com/vjudge.ico"
  10804. )
  10805. );
  10806.  
  10807. const checkLinkExistence = async (url) => {
  10808. return OJB_promiseRetryWrapper(
  10809. async () => {
  10810. const response = await OJB_GMRequest({
  10811. method: "HEAD",
  10812. url: url,
  10813. });
  10814. if (response.status >= 200 && response.status < 300) return true;
  10815. else if (response.status == 404) return false;
  10816. else
  10817. throw new OJB_GMError(
  10818. "network",
  10819. "An unknown network error occurred!",
  10820. response
  10821. );
  10822. },
  10823. {
  10824. maxRetries: 3,
  10825. retryInterval: 1000,
  10826. }
  10827. );
  10828. };
  10829.  
  10830. const VjudgeUrl = OJBetter.typeOfPage.is_gym
  10831. ? `https://vjudge.net/problem/Gym-${problemId}`
  10832. : `https://vjudge.net/problem/CodeForces-${problemId}`;
  10833. try {
  10834. const result = await checkLinkExistence(VjudgeUrl);
  10835. if (problemId && result) {
  10836. problemToolbar.updateText(vjudgeButton, "VJudge");
  10837. problemToolbar.updateUrl(vjudgeButton, VjudgeUrl);
  10838. } else {
  10839. problemToolbar.updateText(
  10840. vjudgeButton,
  10841. i18next.t("state.404", { ns: "button" })
  10842. );
  10843. problemToolbar.disableButton(vjudgeButton);
  10844. }
  10845. } catch (error) {
  10846. if (error instanceof OJB_GMError && error.type == "error") {
  10847. problemToolbar.updateText(
  10848. vjudgeButton,
  10849. i18next.t("state.netError", { ns: "button" })
  10850. );
  10851. problemToolbar.disableButton(vjudgeButton);
  10852. }
  10853. }
  10854. }
  10855.  
  10856. // RatingClass
  10857. const ratingClassMap = {
  10858. NaN: "rating_by_clist_colorNaN",
  10859. 0: "rating_by_clist_color0",
  10860. 1200: "rating_by_clist_color1",
  10861. 1400: "rating_by_clist_color2",
  10862. 1600: "rating_by_clist_color3",
  10863. 1900: "rating_by_clist_color4",
  10864. 2100: "rating_by_clist_color5",
  10865. 2300: "rating_by_clist_color6",
  10866. 2400: "rating_by_clist_color7",
  10867. 2600: "rating_by_clist_color8",
  10868. 3000: "rating_by_clist_color9",
  10869. };
  10870. const cssMap = {
  10871. rating_by_clist_colorNaN: "#cccccc",
  10872. rating_by_clist_color0: "#808080",
  10873. rating_by_clist_color1: "#73e473",
  10874. rating_by_clist_color2: "#77ddbb",
  10875. rating_by_clist_color3: "#aaaaff",
  10876. rating_by_clist_color4: "#ff88ff",
  10877. rating_by_clist_color5: "#ffcc88",
  10878. rating_by_clist_color6: "#ffbb55",
  10879. rating_by_clist_color7: "#ff7777",
  10880. rating_by_clist_color8: "#ff3333",
  10881. rating_by_clist_color9: "#aa0000",
  10882. };
  10883. // TODO 7
  10884. /**
  10885. * clist 访问有效性检查
  10886. * @param {boolean} onlyCookie 是否只检查Cookie
  10887. * @returns {Promise<boolean>} 是否有效
  10888. */
  10889. async function validateClistConnection(onlyCookie = false) {
  10890. const clistApiUrl =
  10891. "https://clist.by:443/api/v4/contest/?limit=1&resource_id=1";
  10892. const requestOptions = {
  10893. method: "GET",
  10894. url: clistApiUrl,
  10895. timeout: 5000,
  10896. };
  10897.  
  10898. // 尝试发送请求
  10899. async function tryRequest(options) {
  10900. try {
  10901. const response = await OJB_GMRequest(options);
  10902. if (response.status === 200) {
  10903. return { ok: true };
  10904. } else if (response.status === 401) {
  10905. throw new Error("unauthorized");
  10906. } else if (response.status === 404) {
  10907. throw new Error("not_found");
  10908. } else {
  10909. throw new Error("other_error");
  10910. }
  10911. } catch (error) {
  10912. console.warn(`Error accessing clist.by: ${error.message}`);
  10913. return { ok: false, error: error.message };
  10914. }
  10915. }
  10916.  
  10917. // 尝试携带Key发送请求
  10918. let result = await tryRequest(requestOptions);
  10919. if (!onlyCookie && !result.ok) {
  10920. requestOptions.headers = { Authorization: OJBetter.clist.authorization };
  10921. result = await tryRequest(requestOptions);
  10922. }
  10923.  
  10924. // 根据结果显示错误信息
  10925. if (!result.ok) {
  10926. let errorType = result.error;
  10927. const loadingMessage = new LoadingMessage();
  10928. let state;
  10929. if (errorType === "not_found") {
  10930. state = i18next.t("error.clist.404", { ns: "alert" });
  10931. } else if (errorType === "unauthorized") {
  10932. state = i18next.t("error.clist.cookie", { ns: "alert" });
  10933. } else {
  10934. state = i18next.t("error.clist.other", { ns: "alert" });
  10935. }
  10936. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${state}`, "error");
  10937. }
  10938. return result.ok;
  10939. }
  10940.  
  10941. /**
  10942. * 创建Rating相关css
  10943. * @param {boolean} [hasBorder=true] 是否有边框
  10944. */
  10945. function creatRatingCss(hasBorder = true) {
  10946. const defaultBorderColor = "#dcdfe6";
  10947. let dynamicCss = "";
  10948. let hoverSelector = OJBetter.clist.ratingHidden ? ":hover" : "";
  10949. for (let cssClass in cssMap) {
  10950. dynamicCss += `a.${cssClass}${hoverSelector}, a.${cssClass}${hoverSelector}:link {\n`;
  10951. let borderColor = hasBorder ? cssMap[cssClass] : defaultBorderColor;
  10952. dynamicCss += ` color: ${cssMap[cssClass]} ${
  10953. OJBetter.clist.ratingHidden ? "!important" : ""
  10954. };\n`;
  10955. dynamicCss += `}\n`;
  10956. }
  10957. GM_addStyle(dynamicCss);
  10958. if (OJBetter.clist.ratingHidden) {
  10959. GM_addStyle(`
  10960. #clistButton {
  10961. color: #ffffff00;
  10962. }
  10963. `);
  10964. }
  10965. }
  10966.  
  10967. /**
  10968. * 模拟clist网页访问获取rating
  10969. * @param {string} problem 题目名称
  10970. * @param {string} problem_url 题目链接
  10971. * @param {string} contest 比赛名称
  10972. * @returns {Promise<{rating: number, problem: string}>} 题目难度
  10973. */
  10974. async function getRatingFromHTML(problem, problem_url, contest = null) {
  10975. // 去除题目名称中的括号,以及首尾的空白符
  10976. problem = problem.replace(/\([\s\S]*?\)/g, "").trim();
  10977.  
  10978. return OJB_promiseRetryWrapper(
  10979. async () => {
  10980. const queryString = `search=${encodeURIComponent(problem)}&resource=1`;
  10981. const response = await OJB_GMRequest({
  10982. method: "GET",
  10983. url: `https://clist.by/problems/?${queryString}`,
  10984. });
  10985.  
  10986. if (!response.responseText)
  10987. throw new OJB_GMError(
  10988. "network",
  10989. "An unknown network error occurred!",
  10990. response
  10991. );
  10992. const html = response.responseText;
  10993. const cleanedHtml = html.replace(/src=(.|\s)*?"/g, "");
  10994. const parser = new DOMParser();
  10995. const doc = parser.parseFromString(cleanedHtml, "text/html");
  10996. const trs = doc.querySelectorAll("table tbody tr");
  10997.  
  10998. for (let tr of trs) {
  10999. const rating = tr
  11000. .querySelector(".problem-rating-column")
  11001. .textContent.trim();
  11002. const link = OJB_cleanLink(
  11003. tr.querySelector(".problem-name-column a:nth-of-type(2)")?.href
  11004. );
  11005.  
  11006. if (link === problem_url || link === problem_url + "/") {
  11007. return {
  11008. rating: parseInt(rating),
  11009. problem: problem,
  11010. };
  11011. } else if (contest !== null) {
  11012. const contestTitles = [
  11013. ...tr.querySelectorAll(
  11014. ".problem-name-column .pull-right a[title], .problem-name-column .pull-right span[title]"
  11015. ),
  11016. ].map((el) => el.title);
  11017. if (contestTitles.includes(contest)) {
  11018. return {
  11019. rating: parseInt(rating),
  11020. problem: problem,
  11021. };
  11022. }
  11023. }
  11024. }
  11025. console.warn(`No data found for the question: ${problem}`);
  11026. },
  11027. {
  11028. maxRetries: 3,
  11029. retryInterval: 500,
  11030. }
  11031. );
  11032. }
  11033.  
  11034. /**
  11035. * 从clist API获取题目的rating
  11036. * @param {string} problem_name 题目名
  11037. * @param {string} problem_url 题目链接
  11038. * @returns {Promise<number>} 题目rating
  11039. *
  11040. * 使用两个Map对象来存储和快速访问题目信息:
  11041. * - problemsMap: 通过题目的URL作为键来存储题目信息。
  11042. * - nameMap: 通过题目的名称作为键来存储题目信息。
  11043. *
  11044. * 每个题目信息是一个对象,包含以下属性:
  11045. * @typedef {Object} ProblemInfo
  11046. * @property {string} name 题目名称
  11047. * @property {string} url 题目URL
  11048. * @property {number} rating 题目评分,如果没有评分信息则为NaN
  11049. */
  11050. async function getRatingFromApi_problem(problem_name, problem_url) {
  11051. return OJB_promiseRetryWrapper(
  11052. async () => {
  11053. const response = await OJB_GMRequest({
  11054. method: "GET",
  11055. url: `https://clist.by:443/api/v4/problem/?name=${encodeURIComponent(
  11056. problem_name
  11057. )}&resource__regex=codeforces`,
  11058. headers: { Authorization: OJBetter.clist.authorization },
  11059. });
  11060.  
  11061. if (!response.responseText)
  11062. throw new OJB_GMError(
  11063. "network",
  11064. "An unknown network error occurred!",
  11065. response
  11066. );
  11067. let data = JSON.parse(response.responseText);
  11068. /**
  11069. * 使用题目的URL作为键来存储题目信息。
  11070. * @type {Map<string, ProblemInfo>}
  11071. */
  11072. let problemsMap = new Map();
  11073.  
  11074. /**
  11075. * 使用题目的名称作为键来存储题目信息。
  11076. * @type {Map<string, ProblemInfo>}
  11077. */
  11078. let nameMap = new Map();
  11079.  
  11080. data.objects.forEach((problem) => {
  11081. /** @type {ProblemInfo} 题目信息*/
  11082. let problemInfo = {
  11083. name: problem.name,
  11084. url: problem.url,
  11085. rating: problem.rating ? problem.rating : NaN,
  11086. };
  11087. problemsMap.set(OJB_cleanLink(problem.url), problemInfo);
  11088. nameMap.set(problem.name, problemInfo);
  11089. });
  11090.  
  11091. if (problemsMap.has(problem_url)) {
  11092. return problemsMap.get(problem_url).rating;
  11093. } else if (nameMap.has(problem_name)) {
  11094. return nameMap.get(problem_name).rating;
  11095. } else {
  11096. console.warn("Problem not found in the response");
  11097. }
  11098. },
  11099. {
  11100. maxRetries: 5,
  11101. retryInterval: 1000,
  11102. }
  11103. );
  11104. }
  11105.  
  11106. /**
  11107. * 获取字符串中的关键词列表
  11108. * @param {string} text 字符串文本
  11109. * @returns {array<string>} 返回关键词列表
  11110. */
  11111. function getKeywords(text) {
  11112. // 定义要过滤掉的高频词
  11113. const highFrequencyWords = ["Educational", "Codeforces", "Round", "Div"];
  11114.  
  11115. // 使用正则表达式替换掉特殊符号(保留空格以便分词)
  11116. const sanitizedText = text.replace(/[^\w\s]|_/g, "").replace(/\s+/g, " ");
  11117.  
  11118. // 将字符串拆分为单词数组
  11119. const words = sanitizedText.split(" ");
  11120.  
  11121. // 过滤掉高频词和空字符串
  11122. const filteredWords = words.filter((word) => {
  11123. return word && highFrequencyWords.indexOf(word) === -1;
  11124. });
  11125.  
  11126. // 返回关键词列表
  11127. return filteredWords;
  11128. }
  11129.  
  11130. /**
  11131. * 根据关键词从 Clist API 中获取实际比赛名称
  11132. * @param {string} contestName 比赛名
  11133. * @param {string} contestUrl 比赛链接
  11134. * @returns {string|null} 该比赛在Clist中的实际名字
  11135. */
  11136. async function getContestNameFromApi(contestName, contestUrl) {
  11137. return OJB_promiseRetryWrapper(
  11138. async () => {
  11139. const options = {
  11140. method: "GET",
  11141. url: `https://clist.by:443/api/v4/contest/?resource_id=1&event__regex=${encodeURIComponent(
  11142. contestName
  11143. )}`,
  11144. headers: {
  11145. Authorization: OJBetter.clist.authorization,
  11146. },
  11147. };
  11148.  
  11149. let response = await OJB_GMRequest(options);
  11150.  
  11151. if (!response.responseText)
  11152. throw new OJB_GMError(
  11153. "network",
  11154. "An unknown network error occurred!",
  11155. response
  11156. );
  11157.  
  11158. let data = JSON.parse(response.responseText);
  11159. let objects = data.objects;
  11160.  
  11161. if (objects.length > 0) {
  11162. for (const contest of objects) {
  11163. const href = contest.href.replace(/\/contests\//i, "/contest/"); // 链接可能是contests而不是contest,换回来
  11164. if (OJB_cleanLink(href) == contestUrl) {
  11165. return contest.event;
  11166. }
  11167. }
  11168. }
  11169. return null;
  11170. },
  11171. {
  11172. maxRetries: 5,
  11173. retryInterval: 1000,
  11174. }
  11175. );
  11176. }
  11177.  
  11178. /**
  11179. * 获取在clist中的实际比赛名称
  11180. * @param {string} contestName 待搜索的比赛名称
  11181. * @param {string} contestUrl 比赛的url
  11182. * @returns {Promise<string|null>} 在clist中的实际比赛名称,如果没有找到,则返回null
  11183. */
  11184. async function getActualContestName(contestName, contestUrl) {
  11185. // 首先尝试使用完整的比赛名称进行搜索
  11186. let actualContestName = await getContestNameFromApi(contestName, contestUrl);
  11187. if (actualContestName) {
  11188. return actualContestName;
  11189. }
  11190.  
  11191. // 如果使用完整名称没有找到,则尝试使用关键词进行搜索
  11192. const keywords = getKeywords(contestName);
  11193. const maxKeywordAttempts = 1; // 最多尝试到第几个关键词(因为Clist API有频率限制)
  11194. for (let i = 0; i < Math.min(keywords.length, maxKeywordAttempts); i++) {
  11195. actualContestName = await getContestNameFromApi(keywords[i], contestUrl);
  11196. if (actualContestName) {
  11197. return actualContestName;
  11198. }
  11199. }
  11200.  
  11201. // 如果全部尝试后仍没有找到,返回null
  11202. return null;
  11203. }
  11204.  
  11205. /**
  11206. * 从clist API获取比赛题目集的rating
  11207. * @param {string} contestName 比赛名
  11208. * @returns {Promise<Map<string, number>>} 题目rating
  11209. */
  11210. async function getRatingFromApi_contest(contestName, contestUrl) {
  11211. const actualContestName = await getActualContestName(contestName, contestUrl);
  11212. return OJB_promiseRetryWrapper(
  11213. async () => {
  11214. const options = {
  11215. method: "GET",
  11216. url: `https://clist.by:443/api/v4/contest/?resource_id=1&with_problems=true&event=${encodeURIComponent(
  11217. actualContestName
  11218. )}`,
  11219. headers: {
  11220. Authorization: OJBetter.clist.authorization,
  11221. },
  11222. };
  11223.  
  11224. let response = await OJB_GMRequest(options);
  11225.  
  11226. if (!response.responseText)
  11227. throw new OJB_GMError(
  11228. "network",
  11229. "An unknown network error occurred!",
  11230. response
  11231. );
  11232.  
  11233. let data = JSON.parse(response.responseText);
  11234. let objects = data.objects;
  11235. let problemsMap = new Map();
  11236.  
  11237. if (objects.length > 0 && objects[0].problems) {
  11238. objects[0].problems.forEach((problem) => {
  11239. problemsMap.set(
  11240. OJB_cleanLink(problem.url),
  11241. problem.rating ? problem.rating : NaN
  11242. );
  11243. });
  11244. }
  11245.  
  11246. return problemsMap;
  11247. },
  11248. {
  11249. maxRetries: 5,
  11250. retryInterval: 1000,
  11251. }
  11252. );
  11253. }
  11254.  
  11255. /**
  11256. * 根据rating获取对应的颜色class名
  11257. * @param {number} rating 题目rating
  11258. * @returns {string} 颜色class名
  11259. */
  11260. function getClassNameByRating(rating) {
  11261. let className = "rating_by_clist_color9";
  11262. if (Number.isNaN(rating)) {
  11263. className = "rating_by_clist_colorNaN";
  11264. } else {
  11265. let keys = Object.keys(ratingClassMap);
  11266. for (let i = 0; i < keys.length; i++) {
  11267. if (rating < keys[i]) {
  11268. className = ratingClassMap[keys[i - 1]];
  11269. break;
  11270. }
  11271. }
  11272. }
  11273. return className;
  11274. }
  11275.  
  11276. /**
  11277. * problem题目页显示Rating
  11278. * @param {ProblemPageLinkbar} problemToolbar
  11279. * @returns {Promise<void>}
  11280. */
  11281. async function showRatingByClist_problem(problemToolbar) {
  11282. // 题目名
  11283. let problem = $(".header .title")
  11284. .eq(0)
  11285. .text()
  11286. .replace(/[\s\S]*?. /, "");
  11287. if (OJBetter.typeOfPage.is_acmsguru)
  11288. problem = $("h4")
  11289. .eq(0)
  11290. .text()
  11291. .replace(/[\s\S]*?. /, "");
  11292.  
  11293. // 创建Rating按钮元素
  11294. creatRatingCss(false);
  11295. // TODO
  11296. const clistButton = problemToolbar.addLinkButton(
  11297. "clistButton",
  11298. `https://clist.by/problems/?search=${problem}&resource=1&resource=64`,
  11299. i18next.t("state.wait", { ns: "button" }),
  11300. $("<img>").attr("src", "https://clist.by/static/img/logo-48.png"),
  11301. "15px"
  11302. );
  11303.  
  11304. // 检测clist连接
  11305. if (!(await validateClistConnection())) {
  11306. problemToolbar.updateText(
  11307. clistButton,
  11308. i18next.t("state.netError", { ns: "button" })
  11309. );
  11310. return;
  11311. }
  11312.  
  11313. // 题目链接
  11314. let problem_url = window.location.href;
  11315. if (problem_url.includes("/contest/")) {
  11316. problem_url = problem_url.replace(
  11317. /\/contest\/(\d+)\/problem\/(\w+)[^\w]*/,
  11318. "/contest/$1/problem/$2"
  11319. );
  11320. } else {
  11321. problem_url = problem_url.replace(
  11322. /\/problemset\/problem\/(\d+)\/(\w+)/,
  11323. "/contest/$1/problem/$2"
  11324. );
  11325. }
  11326. if (OJBetter.typeOfPage.is_mSite)
  11327. problem_url = problem_url.replace(
  11328. /\/\/(\w+).codeforces.com/,
  11329. "//codeforces.com"
  11330. ); // 轻量站
  11331.  
  11332. // 比赛名
  11333. // let contest = $('#sidebar').children().first().find('.rtable th').first().text();
  11334.  
  11335. // rating
  11336. problemToolbar.updateText(
  11337. clistButton,
  11338. i18next.t("state.loading", { ns: "button" })
  11339. );
  11340. let rating = await getRatingFromApi_problem(problem, problem_url);
  11341. if (rating) {
  11342. let className = getClassNameByRating(rating);
  11343. problemToolbar.updateText(clistButton, rating);
  11344. problemToolbar.setBold(clistButton);
  11345. problemToolbar.addClass(clistButton, className);
  11346. } else {
  11347. problemToolbar.updateText(
  11348. clistButton,
  11349. i18next.t("state.404", { ns: "button" })
  11350. );
  11351. problemToolbar.disableButton(clistButton);
  11352. }
  11353. }
  11354.  
  11355. /**
  11356. * contest页显示Rating
  11357. * @returns {Promise<void>}
  11358. */
  11359. async function showRatingByClist_contest() {
  11360. // 创建Rating显示框
  11361. creatRatingCss();
  11362. let ratingBadges = {};
  11363. $(".datatable .id.left").each(function () {
  11364. let href = "https://codeforces.com" + $(this).find("a").attr("href");
  11365. let badge = OJB_safeCreateJQElement(
  11366. `<a id="clistButton" class="ratingBadge">${i18next.t("state.wait", {
  11367. ns: "button",
  11368. })}</a>`
  11369. );
  11370. $(this).find("a").after(badge);
  11371. ratingBadges[href] = badge;
  11372. });
  11373.  
  11374. // 检测clist连接
  11375. if (!(await validateClistConnection())) {
  11376. for (let href in ratingBadges) {
  11377. ratingBadges[href].text("error").addClass("ratingBadge_error");
  11378. }
  11379. return;
  11380. }
  11381.  
  11382. // 显示loading
  11383. for (let href in ratingBadges) {
  11384. ratingBadges[href]
  11385. .text(i18next.t("state.loading", { ns: "button" }))
  11386. .addClass("ratingBadge_loading");
  11387. }
  11388.  
  11389. // 获取Rating
  11390. let contestName = $("#sidebar")
  11391. .children()
  11392. .first()
  11393. .find(".rtable th")
  11394. .first()
  11395. .text();
  11396. let contestUrl = OJB_cleanLink(window.location.href);
  11397. try {
  11398. let problemsMap = await getRatingFromApi_contest(contestName, contestUrl);
  11399.  
  11400. // 填充数据
  11401. for (let href in ratingBadges) {
  11402. if (problemsMap.has(href)) {
  11403. let rating = problemsMap.get(href);
  11404. let className = getClassNameByRating(rating);
  11405. ratingBadges[href].text(rating).addClass(className);
  11406. } else {
  11407. ratingBadges[href]
  11408. .text(i18next.t("state.404", { ns: "button" }))
  11409. .addClass("ratingBadge_no");
  11410. }
  11411. }
  11412. } catch (error) {
  11413. // 填充数据
  11414. for (let href in ratingBadges) {
  11415. ratingBadges[href]
  11416. .text(i18next.t("state.netError", { ns: "button" }))
  11417. .addClass("ratingBadge_no");
  11418. }
  11419. console.warn(error);
  11420. }
  11421. }
  11422.  
  11423. /**
  11424. * problemset页显示Rating
  11425. * @returns {Promise<void>}
  11426. */
  11427. async function showRatingByClist_problemset() {
  11428. creatRatingCss();
  11429. let ratingBadges = [];
  11430. const $problems = $(".problems");
  11431. const $trs = $problems.find("tbody tr:gt(0)");
  11432.  
  11433. // 先创建Rating显示框,并将关系存进数组ratingBadges
  11434. for (let i = 0; i < $trs.length; i++) {
  11435. const $tds = $($trs[i]).find("td");
  11436. const $firstDiv = $($tds[1]).find("div:first");
  11437. let problem = $firstDiv.text();
  11438. let problem_url = $firstDiv.find("a").attr("href");
  11439. problem_url = problem_url.replace(
  11440. /^\/problemset\/problem\/(\d+)\/(\w+)/,
  11441. "https://codeforces.com/contest/$1/problem/$2"
  11442. );
  11443.  
  11444. const ratingBadge = OJB_safeCreateJQElement(
  11445. `<a id="clistButton" class="ratingBadge"></a>`
  11446. );
  11447. const rating = OJB_safeCreateJQElement(
  11448. `<span class="rating">${i18next.t("state.wait", { ns: "button" })}</span>`
  11449. );
  11450. ratingBadge.append(rating);
  11451. $($tds[0]).find("a").after(ratingBadge);
  11452. ratingBadges.push({ ratingBadge, rating, problem, problem_url });
  11453. }
  11454.  
  11455. // 检测clist连接
  11456. if (!(await validateClistConnection())) {
  11457. for (let i = 0; i < ratingBadges.length; i++) {
  11458. ratingBadges[i].rating.text(
  11459. i18next.t("state.netError", { ns: "button" })
  11460. );
  11461. }
  11462. return;
  11463. }
  11464.  
  11465. // 每次只获取3个rating
  11466. for (let i = 0; i < ratingBadges.length; i += 3) {
  11467. const promises = [];
  11468. const endIndex = Math.min(i + 3, ratingBadges.length);
  11469.  
  11470. for (let j = i; j < endIndex; j++) {
  11471. const ratingBadge = ratingBadges[j];
  11472. // 显示请求中
  11473. ratingBadge.rating.text(i18next.t("state.loading", { ns: "button" }));
  11474. promises.push(
  11475. getRatingFromHTML(ratingBadge.problem, ratingBadge.problem_url).catch(
  11476. (error) => console.warn(error)
  11477. )
  11478. );
  11479. }
  11480.  
  11481. const results = await Promise.all(promises);
  11482.  
  11483. for (let j = i; j < endIndex; j++) {
  11484. const result = results[j - i];
  11485. const ratingBadge = ratingBadges[j];
  11486. if (result) {
  11487. let className = getClassNameByRating(result.rating);
  11488. ratingBadge.ratingBadge.addClass(className);
  11489. ratingBadge.rating.text(result.rating);
  11490. } else {
  11491. ratingBadge.rating.text(i18next.t("state.404", { ns: "button" }));
  11492. }
  11493. }
  11494. }
  11495. }
  11496.  
  11497. /**
  11498. * cf赛制榜单重新着色
  11499. */
  11500. async function recolorStandings() {
  11501. function getColorValue(value) {
  11502. value = Math.max(0, Math.min(1, value));
  11503.  
  11504. const scale = chroma
  11505. .scale(["#b71c1c", "#ff9800", "#ffc107", "#00aa00"])
  11506. .mode("lch")
  11507. .domain([0, 0.45, 0.7, 1]);
  11508. return scale(value).hex();
  11509. }
  11510. var maxScores = $(".standings tr:first th:nth-child(n+5)")
  11511. .map(function () {
  11512. return $(this).find("span").text();
  11513. })
  11514. .get();
  11515. $(".standings tr:not(:first):not(:last)").each(function () {
  11516. var thElements = $(this).find("td:nth-child(n+5)");
  11517. thElements.each(function (index) {
  11518. var spanElement = $(this).find("span:first");
  11519. var value = parseInt(spanElement.text());
  11520. if (value <= 0 || /[a-zA-Z]/.test(maxScores[index])) return;
  11521. var colorValue = getColorValue(value / maxScores[index]);
  11522. spanElement.css("color", colorValue);
  11523. });
  11524. });
  11525. }
  11526.  
  11527. /**
  11528. * 评测结果的简写
  11529. */
  11530. const StatusAcronyms = {
  11531. Accepted: "AC",
  11532. "Happy New Year!": "AC",
  11533. "Wrong answer": "WA",
  11534. "Time limit exceeded": "TLE",
  11535. "Memory limit exceeded": "MLE",
  11536. "Runtime error": "RE",
  11537. "Compilation error": "CE",
  11538. Hacked: "Hacked",
  11539. Skipped: "Skipped",
  11540. "Idleness limit exceeded": "ILE",
  11541. "Perfect result:": "AC",
  11542. "Partial result:": "PC",
  11543. Running: "PENDING",
  11544. "In queue": "INQUEUE",
  11545. "Pretests Passed": "PREPASS",
  11546. };
  11547.  
  11548. /**
  11549. * 评测结果的颜色
  11550. */
  11551. const StatusColors = {
  11552. AC: "#0a0",
  11553. PREPASS: "#0a0",
  11554. WA: "#e74c3c",
  11555. PENDING: "#808080",
  11556. INQUEUE: "#808080",
  11557. };
  11558.  
  11559. /**
  11560. * 替换评测结果
  11561. */
  11562. async function judgeStatusReplace() {
  11563. /**
  11564. * 获取评测状态的名称和编号
  11565. *
  11566. * @param {string} text 评测状态
  11567. * @returns {object} 评测状态的名称和编号
  11568. */
  11569. const getStatusName = (text) => {
  11570. const words = text.split(" ");
  11571. const number_of_words = words.length;
  11572. let status_name = "",
  11573. number = "";
  11574. let status_name_is_over = false;
  11575. for (let i = 0; i < number_of_words; i++) {
  11576. if (words[i] === "on" || words[i] === "(") {
  11577. status_name_is_over = true;
  11578. }
  11579. if (!status_name_is_over) {
  11580. status_name += words[i] + " ";
  11581. } else if (parseInt(words[i]).toString() !== "NaN") {
  11582. number = words[i];
  11583. }
  11584. if (words[i] === "result:") {
  11585. status_name_is_over = true;
  11586. }
  11587. }
  11588. status_name = status_name.trim();
  11589. return {
  11590. status_name: status_name,
  11591. number: number,
  11592. };
  11593. };
  11594.  
  11595. /**
  11596. * 获取当前评测状态的缩写
  11597. *
  11598. * @param {string} status_name 评测状态
  11599. * @returns {string} 评测状态的缩写
  11600. */
  11601. const getStatusAcronym = (status_name) => {
  11602. return StatusAcronyms[status_name];
  11603. };
  11604.  
  11605. /**
  11606. * 根据评测状态的缩写获取颜色
  11607. *
  11608. * @param {string} statusAcronym 评测状态的缩写
  11609. * @returns {string} 颜色值
  11610. */
  11611. const getStatusColor = (statusAcronym) => {
  11612. let color = StatusColors[statusAcronym];
  11613. return color ? color : StatusColors["WA"];
  11614. };
  11615.  
  11616. /**
  11617. * 解析模板
  11618. *
  11619. * @param {string} template 模板
  11620. * @param {object} display 各个前置标识符的bool值,决定了是否显示这一部分
  11621. * @returns {string} 解析后的文本
  11622. *
  11623. * @example
  11624. * parseTemplate("{ac:!}{wa:呜呜}{pending:别急}", {ac: false, wa: true, pending: false}) => "呜呜"
  11625. */
  11626. const parseTemplate = (template, display) => {
  11627. const regex = /{(\w+):([^}]*)}/g;
  11628. let result = "";
  11629. let lastIndex = 0;
  11630.  
  11631. template.replace(regex, (match, key, text, offset) => {
  11632. // 添加模板前的普通文本部分
  11633. result += template.slice(lastIndex, offset);
  11634.  
  11635. // 根据布尔值决定是否添加模板部分
  11636. if (display[key]) {
  11637. result += text;
  11638. }
  11639. lastIndex = offset + match.length;
  11640. });
  11641.  
  11642. // 添加最后一段普通文本部分
  11643. result += template.slice(lastIndex);
  11644.  
  11645. return result;
  11646. };
  11647.  
  11648. /**
  11649. * 根据替换规则获取替换后的文本
  11650. *
  11651. * @param {string} status_name 评测状态
  11652. * @param {string} number 评测编号
  11653. * @returns {string} 替换后的文本
  11654. */
  11655. const getReplaceText = (status_name, number) => {
  11656. const statusAcronym = getStatusAcronym(status_name);
  11657.  
  11658. let result = OJBetter.preference.judgeStatusReplaceText;
  11659. result = result.replace(/{Status}/g, status_name);
  11660. result = result.replace(/{Stat}/g, statusAcronym);
  11661. result = result.replace(/{Number}/g, number);
  11662.  
  11663. const statusMap = {
  11664. ac: statusAcronym === "AC",
  11665. wa: statusAcronym === "WA",
  11666. tle: statusAcronym === "TLE",
  11667. mle: statusAcronym === "MLE",
  11668. re: statusAcronym === "RE",
  11669. ce: statusAcronym === "CE",
  11670. hacked: statusAcronym === "Hacked",
  11671. skipped: statusAcronym === "Skipped",
  11672. ile: statusAcronym === "ILE",
  11673. pc: statusAcronym === "PC",
  11674. pending: statusAcronym === "PENDING",
  11675. inqueue: statusAcronym === "INQUEUE",
  11676. prepass: statusAcronym === "PREPASS",
  11677. };
  11678. const final_result = parseTemplate(result, statusMap);
  11679. return final_result;
  11680. };
  11681.  
  11682. /**
  11683. * 处理评测结果
  11684. * @param {string} text 待替换的文本
  11685. * @returns {object} 处理后的文本和颜色
  11686. */
  11687. const process = (text) => {
  11688. /**
  11689. * 当前评测状态
  11690. */
  11691. const { status_name, number } = getStatusName(text);
  11692.  
  11693. /**
  11694. * 当前评测状态的缩写
  11695. */
  11696. const statusAcronym = getStatusAcronym(status_name);
  11697.  
  11698. return {
  11699. text: statusAcronym ? getReplaceText(status_name, number) : text,
  11700. color: getStatusColor(statusAcronym),
  11701. };
  11702. };
  11703.  
  11704. OJB_observeElement({
  11705. selector: ".datatable",
  11706. callback: (node) => {
  11707. /**
  11708. * 更新元素
  11709. * @param {HTMLElement} element - 要更新的元素
  11710. */
  11711. const updateElement = (element) => {
  11712. /**
  11713. * 替换文本并着色
  11714. * @param {HTMLElement} element - 要处理的元素
  11715. */
  11716. const replaceAndColor = (element) => {
  11717. const { text, color } = process(element.textContent);
  11718. element.textContent = text;
  11719. element.style.color = color;
  11720. element.style.fontWeight = "700";
  11721. };
  11722.  
  11723. /**
  11724. * 获取最小目标元素
  11725. * @param {HTMLElement} element - 要检查的元素
  11726. * @returns {HTMLElement} - 最小目标元素
  11727. */
  11728. const getMinElement = (element) => {
  11729. // 先获取一次text值
  11730. const { text: originalText } = process(element.textContent);
  11731.  
  11732. /**
  11733. * 深搜
  11734. * @param {HTMLElement} el - 当前遍历的元素
  11735. * @returns {HTMLElement} - 能维持text不变的最小子元素
  11736. */
  11737. const findMinElement = (el) => {
  11738. // 遍历子元素
  11739. for (let child of el.children) {
  11740. // 获取子元素的处理后的text值
  11741. const { text: childText } = process(child.textContent);
  11742.  
  11743. // 如果子元素的text与原始text相同,递归检查子元素
  11744. if (childText === originalText) {
  11745. return findMinElement(child);
  11746. }
  11747. }
  11748.  
  11749. // 如果没有子元素能维持text不变,返回当前元素
  11750. return el;
  11751. };
  11752.  
  11753. // 从初始元素开始递归
  11754. return findMinElement(element);
  11755. };
  11756.  
  11757. // 获取最小目标元素
  11758. const minElement = getMinElement(element);
  11759. // 替换文本并着色
  11760. replaceAndColor(minElement);
  11761. };
  11762.  
  11763. /**
  11764. * 判断该元素是否在 .status-verdict-cell 内,
  11765. * 检查直到确认是或者遇到了 .datatable 或没有上层元素
  11766. *
  11767. * @param {Element} element - 要检查的元素
  11768. * @returns {Element|null} - 如果是,则返回对应的 .status-verdict-cell 元素,否则返回 null
  11769. * @throws {TypeError} - 如果提供的值不是有效的 DOM 元素
  11770. */
  11771. const isDescendantOfStatusVerdictCell = (element) => {
  11772. if (!(element instanceof Element)) {
  11773. throw new TypeError("The provided value is not a valid DOM element.");
  11774. }
  11775.  
  11776. let currentElement = element;
  11777.  
  11778. while (currentElement && !currentElement.matches(".datatable")) {
  11779. if (currentElement.matches(".status-verdict-cell")) {
  11780. return currentElement;
  11781. }
  11782. currentElement = currentElement.parentElement;
  11783. }
  11784.  
  11785. return null;
  11786. };
  11787.  
  11788. // 检查变动元素是否在 .status-verdict-cell 内
  11789. const statusVerdictCell = isDescendantOfStatusVerdictCell(node);
  11790. if (statusVerdictCell) {
  11791. updateElement(statusVerdictCell);
  11792. }
  11793.  
  11794. // 检查node内部所有的 .status-verdict-cell 元素
  11795. node.querySelectorAll(".status-verdict-cell").forEach((element) => {
  11796. updateElement(element);
  11797. });
  11798. },
  11799. });
  11800. }
  11801.  
  11802. /**
  11803. * 存放编辑器语言select的值与Monaco语言对应关系的map.
  11804. * @type {Object.<string, string>}
  11805. */
  11806. const value_monacoLanguageMap = {
  11807. 4: "pascal",
  11808. 6: "php",
  11809. 7: "python",
  11810. 9: "csharp",
  11811. 13: "perl",
  11812. 20: "scala",
  11813. 31: "python",
  11814. 32: "go",
  11815. 34: "javascript",
  11816. 36: "java",
  11817. 40: "python",
  11818. 41: "python",
  11819. 43: "cpp",
  11820. 50: "cpp",
  11821. 51: "pascal",
  11822. 52: "cpp",
  11823. 54: "cpp",
  11824. 55: "javascript",
  11825. 59: "cpp",
  11826. 60: "java",
  11827. 61: "cpp",
  11828. 65: "csharp",
  11829. 67: "ruby",
  11830. 70: "python",
  11831. 73: "cpp",
  11832. 74: "java",
  11833. 75: "rust",
  11834. 77: "kotlin",
  11835. 79: "csharp",
  11836. 80: "cpp",
  11837. 83: "kotlin",
  11838. 87: "java",
  11839. 89: "cpp",
  11840. };
  11841.  
  11842. /**
  11843. * 更新代码提交页的HTML
  11844. * @param {string} submitUrl 提交页面的URL
  11845. * @param {string} cacheKey 本地缓存的键名
  11846. * @returns {Promise<jQuery<HTMLElement>>} 返回 jQuery 包装的 HTML 元素
  11847. */
  11848. async function CloneOriginalHTML(submitUrl, cacheKey) {
  11849. return OJB_promiseRetryWrapper(
  11850. async () => {
  11851. const response = await OJB_GMRequest({
  11852. method: "GET",
  11853. url: submitUrl,
  11854. });
  11855. const html = response.responseText;
  11856. const parser = new DOMParser();
  11857. const doc = parser.parseFromString(html, "text/html");
  11858. const cloneHTML = $(doc.body).html();
  11859. localStorage.setItem(cacheKey, html);
  11860. return $(cloneHTML);
  11861. },
  11862. {
  11863. maxRetries: 5,
  11864. retryInterval: 1000,
  11865. errorHandler: (err) => {
  11866. console.error(
  11867. "A network error occurred while retrieving the HTML for the code submission page.",
  11868. submitUrl
  11869. );
  11870. },
  11871. }
  11872. );
  11873. }
  11874.  
  11875. /**
  11876. * 获取代码提交页的HTML元素
  11877. * @param {string} submitUrl
  11878. * @returns {Promise<jQuery>}
  11879. */
  11880. async function getSubmitHTML(submitUrl) {
  11881. const cacheKey = "OJBetter_CloneOriginalHTML";
  11882. const cookieKey = "OJBetter_CloneOriginalHTML_time";
  11883. if (OJB_getCookie(cookieKey) === "1") {
  11884. // 存在缓存
  11885. CloneOriginalHTML(submitUrl, cacheKey);
  11886. // 校验
  11887. let cloneHTML = $(localStorage.getItem(cacheKey));
  11888. if (cloneHTML.find("form.submit-form").length > 0) {
  11889. return cloneHTML;
  11890. } else {
  11891. // 存在错误,更新缓存
  11892. console.warn(
  11893. `Cache error detected!\nattempting to update, cache destination submitUrl:\n${submitUrl}`
  11894. );
  11895. return await CloneOriginalHTML(submitUrl, cacheKey);
  11896. }
  11897. } else {
  11898. // 没有缓存,更新
  11899. document.cookie = `${cookieKey}=1; path=/`;
  11900. return await CloneOriginalHTML(submitUrl, cacheKey);
  11901. }
  11902. }
  11903.  
  11904. // 代码自动保存
  11905. async function saveCode(url, code) {
  11906. try {
  11907. await OJBetter.common.database.editorCode.put({ url, code });
  11908. return "Code saved successfully";
  11909. } catch (error) {
  11910. throw new Error("Failed to save code");
  11911. }
  11912. }
  11913.  
  11914. async function getCode(url) {
  11915. try {
  11916. const result = await OJBetter.common.database.editorCode.get(url);
  11917. return result ? result.code : null;
  11918. } catch (error) {
  11919. throw new Error("Failed to get code");
  11920. }
  11921. }
  11922.  
  11923. // 创建代码编辑调试表单元素
  11924. async function createCodeEditorForm(submitUrl, cloneHTML) {
  11925. // 表单
  11926. let formDiv = $(
  11927. '<form method="post" id="OJBetter_SubmitForm" class="input-output-copier"></form>'
  11928. );
  11929. $(".ttypography").after(formDiv);
  11930. formDiv.attr(
  11931. "action",
  11932. submitUrl + "?csrf_token=" + OJBetter.common.cf_csrf_token
  11933. );
  11934.  
  11935. // 顶部区域
  11936. let topDiv = OJB_safeCreateJQElement(`<div class="topDiv"></div>`);
  11937.  
  11938. // 顶部左侧区域
  11939. let topLeftDiv = OJB_safeCreateJQElement(`<div class="topLeftDiv"></div>`);
  11940. topDiv.append(topLeftDiv);
  11941. // 语言选择
  11942. let selectLang = cloneHTML.find('select[name="programTypeId"]');
  11943. selectLang.attr("id", "programTypeId");
  11944. topLeftDiv.append(selectLang);
  11945.  
  11946. // 顶部右侧区域
  11947. let topRightDiv = OJB_safeCreateJQElement(`<div class="topRightDiv"></div>`);
  11948. topDiv.append(topRightDiv);
  11949. formDiv.append(topDiv);
  11950.  
  11951. // 问题选择/编号
  11952. let selectProblem = $(
  11953. '<input name="submittedProblemIndex" style="display:none;"></input>'
  11954. );
  11955. let problemCode;
  11956. if (OJBetter.typeOfPage.is_acmsguru) {
  11957. problemCode = $("h4").eq(0).text();
  11958. let matchResult = problemCode.match(/([A-Z0-9]+)/);
  11959. problemCode = matchResult[0];
  11960. } else if (OJBetter.typeOfPage.is_problemset_problem) {
  11961. let match = window.location.href.match(
  11962. /\/problem\/([0-9]+?)\/([A-Za-z0-9]+)/
  11963. );
  11964. problemCode = match[1] + match[2];
  11965. selectProblem.attr("name", "submittedProblemCode");
  11966. } else {
  11967. problemCode = $(".header .title").eq(0).text();
  11968. let matchResult = problemCode.match(/([A-Z0-9]+)/);
  11969. problemCode = matchResult[0];
  11970. }
  11971. selectProblem.val(problemCode);
  11972. formDiv.append(selectProblem);
  11973.  
  11974. // 隐藏的代码记录
  11975. let sourceDiv = $(
  11976. '<textarea id="sourceCodeTextarea" name="source" style="display: none;"></textarea>'
  11977. );
  11978. formDiv.append(sourceDiv);
  11979.  
  11980. // 代码编辑器
  11981. let editorDiv = $('<div id="OJBetter_editor"></div>');
  11982. formDiv.append(editorDiv);
  11983.  
  11984. // monaco
  11985. let monaco = $('<div id="OJBetter_monaco"></div>');
  11986. editorDiv.append(monaco);
  11987.  
  11988. // 自定义调试
  11989. let customTestDiv = OJB_safeCreateJQElement(`
  11990. <details id="customTestBlock">
  11991. <summary >${i18next.t("customTestBlock.title", {
  11992. ns: "codeEditor",
  11993. })}</summary>
  11994. <div id="customTests" style="min-height: 30px;"></div>
  11995. <div id="control" style="display:flex;">
  11996. <div style="display: flex;margin: 5px;">
  11997. <input type="checkbox" id="onlyCustomTest"}><label for="onlyCustomTest">
  11998. ${i18next.t("customTestBlock.onlyCustom", {
  11999. ns: "codeEditor",
  12000. })}
  12001. </label>
  12002. </div>
  12003. <div style="display: flex;margin: 5px;">
  12004. <input type="checkbox" id="DontShowDiff"}>
  12005. <label for="DontShowDiff">
  12006. ${i18next.t("customTestBlock.DontShowDiff", {
  12007. ns: "codeEditor",
  12008. })}
  12009. </label>
  12010. </div>
  12011. <button type="button" id="addCustomTest">${i18next.t(
  12012. "customTestBlock.add",
  12013. { ns: "codeEditor" }
  12014. )}</button>
  12015. </div>
  12016. </details>
  12017. `);
  12018. formDiv.append(customTestDiv);
  12019.  
  12020. // 调试/提交
  12021. let submitDiv = $('<div id="OJBetter_submitDiv"></div>');
  12022. let CompilerArgsInput = $('<input type="text" id="CompilerArgsInput">');
  12023. submitDiv.append(CompilerArgsInput);
  12024.  
  12025. let runButton = OJB_safeCreateJQElement(`
  12026. <button type="button" id="RunTestButton" class="ojb_btn ojb_btn_popover top">
  12027. <i class="iconfont">&#xe6c1;</i>
  12028. <span class="popover_content">${i18next.t("runTestButton.initial", {
  12029. ns: "codeEditor",
  12030. })}</span>
  12031. </button>
  12032. `);
  12033. let submitButton = OJB_safeCreateJQElement(`
  12034. <button id="SubmitButton" class="ojb_btn ojb_btn_popover top" type="submit">
  12035. <i class="iconfont">&#xe633;</i>
  12036. <span class="popover_content">${i18next.t("submitButton", {
  12037. ns: "codeEditor",
  12038. })}</span>
  12039. </button>
  12040. `);
  12041. if (OJBetter.monaco.setting.submitButtonPosition == "bottom") {
  12042. // 添加测试/提交按钮到底部
  12043. submitDiv.append(runButton);
  12044. submitDiv.append(submitButton);
  12045. }
  12046.  
  12047. formDiv.append(submitDiv);
  12048. let CompilerSetting = OJB_safeCreateJQElement(`
  12049. <div id="CompilerSetting"></div>
  12050. `);
  12051. formDiv.append(CompilerSetting);
  12052. let statePanel = OJB_safeCreateJQElement(`
  12053. <div id="statePanel"></div>
  12054. `);
  12055. formDiv.append(statePanel);
  12056.  
  12057. let from = {
  12058. formDiv: formDiv,
  12059. selectLang: selectLang,
  12060. topRightDiv: topRightDiv,
  12061. sourceDiv: sourceDiv,
  12062. editorDiv: editorDiv,
  12063. monaco: monaco,
  12064. runButton: runButton,
  12065. submitButton: submitButton,
  12066. submitDiv: submitDiv,
  12067. CompilerSetting: CompilerSetting,
  12068. statePanel: statePanel,
  12069. };
  12070. return from;
  12071. }
  12072.  
  12073. // 解析ace格式的补全规则(acwing)
  12074. function parseAceCompleter(rules, range) {
  12075. const suggestions = [];
  12076. if (rules && rules.templates && rules.templates.items) {
  12077. const items = rules.templates.items;
  12078. for (let i = 0; i < items.length; i++) {
  12079. const item = items[i];
  12080. const parts = item.caption.split(" ");
  12081. for (let i = 0; i < parts.length; i++) {
  12082. if (item.value.startsWith(parts[i])) {
  12083. item.value = item.value.replace(
  12084. parts[i],
  12085. parts.slice(0, i + 1).join(" ")
  12086. );
  12087. break;
  12088. }
  12089. }
  12090. const completionItem = {
  12091. label: item.caption,
  12092. kind: monaco.languages.CompletionItemKind.Function,
  12093. insertText: item.value,
  12094. range: range,
  12095. };
  12096. suggestions.push(completionItem);
  12097. }
  12098. }
  12099. return { suggestions };
  12100. }
  12101.  
  12102. // 解析monaco格式的补全规则
  12103. function parseMonacoCompleter(rules, range) {
  12104. const suggestions_ = [];
  12105. if (rules && rules.suggestions) {
  12106. const suggestion = rules.suggestions;
  12107. for (let i = 0; i < rules.suggestions.length; i++) {
  12108. const item = suggestion[i];
  12109. const completionItem = {
  12110. ...item,
  12111. range: range,
  12112. };
  12113. suggestions_.push(completionItem);
  12114. }
  12115. }
  12116. return { suggestions: suggestions_ };
  12117. }
  12118.  
  12119. /**
  12120. * 创建monaco编辑器的一个实例
  12121. */
  12122. async function createMonacoEditor(language, form, support) {
  12123. // 判断monacoLoader是否加载完毕
  12124. await OJB_waitUntilTrue(() => OJBetter.monaco.loaderOnload);
  12125.  
  12126. /**
  12127. * 通用参数
  12128. */
  12129. var id = 0; // 协议中的id标识
  12130. var workspace = language + "_workspace";
  12131. var rootUri = OJBetter.monaco.lsp.workUri + "/" + workspace;
  12132. // 文件名
  12133. var InstanceID = OJB_getRandomNumber(8).toString();
  12134. var filename = language == "java" ? "hello/src/" + InstanceID : InstanceID;
  12135. // 后缀名
  12136. var fileExtension =
  12137. language === "cpp"
  12138. ? ".cpp"
  12139. : language === "python"
  12140. ? ".py"
  12141. : language === "java"
  12142. ? ".java"
  12143. : "";
  12144. var uri = rootUri + "/" + filename + fileExtension;
  12145. var initialized = false; // 是否已初始化
  12146. var serverInfo; // 服务器返回的支持信息
  12147. var model; // model
  12148. var OJBetter_monaco = {};
  12149. window.OJBetter_monaco = OJBetter_monaco; // 全局方法
  12150.  
  12151. /**
  12152. * 一些工具函数
  12153. */
  12154. // 将lsp格式的rang转换为Monaco格式
  12155. OJBetter_monaco.lspRangeToMonacoRange = function (range) {
  12156. const { start, end } = range;
  12157. return new monaco.Range(
  12158. start.line + 1,
  12159. start.character + 1,
  12160. end.line + 1,
  12161. end.character + 1
  12162. );
  12163. };
  12164. // 将Monaco格式的rang转为lsp格式
  12165. OJBetter_monaco.MonacoRangeTolspRange = function (range) {
  12166. return {
  12167. start: {
  12168. line: range.startLineNumber - 1,
  12169. character: range.startColumn - 1,
  12170. },
  12171. end: {
  12172. line: range.endLineNumber - 1,
  12173. character: range.endColumn - 1,
  12174. },
  12175. };
  12176. };
  12177. // 将Monaco格式的position转为lsp格式的
  12178. OJBetter_monaco.MonacoPositionTolspPosition = function (position) {
  12179. return {
  12180. line: position.lineNumber - 1,
  12181. character: position.column - 1,
  12182. };
  12183. };
  12184. // 将Monaco格式的severity转为lsp格式的
  12185. OJBetter_monaco.MonacoSeverityTolspSeverity = function (severity) {
  12186. switch (severity) {
  12187. case 8:
  12188. return 1;
  12189. case 1:
  12190. return 4;
  12191. case 2:
  12192. return 3;
  12193. case 4:
  12194. return 2;
  12195. default:
  12196. return severity;
  12197. }
  12198. };
  12199. // 将lsp格式的severity转为Monaco格式的
  12200. OJBetter_monaco.lspSeverityToMonacoSeverity = function (severity) {
  12201. switch (severity) {
  12202. case 1:
  12203. return 8;
  12204. case 4:
  12205. return 1;
  12206. case 3:
  12207. return 2;
  12208. case 2:
  12209. return 4;
  12210. default:
  12211. return severity;
  12212. }
  12213. };
  12214. // 收集Monaco数据中的rang数据
  12215. OJBetter_monaco.CollectRange = function (item) {
  12216. return {
  12217. startLineNumber: item.startLineNumber,
  12218. startColumn: item.startColumn,
  12219. endLineNumber: item.endLineNumber,
  12220. endColumn: item.endColumn,
  12221. };
  12222. };
  12223. // 收集Monaco position数据中的rang数据
  12224. OJBetter_monaco.CollectRangeByPosition = function (item) {
  12225. var word = model.getWordUntilPosition(item);
  12226. return {
  12227. startLineNumber: item.lineNumber,
  12228. endLineNumber: item.lineNumber,
  12229. startColumn: word.startColumn,
  12230. endColumn: word.endColumn,
  12231. };
  12232. };
  12233. // 将lsp格式的Edit转换为Monaco格式
  12234. OJBetter_monaco.lspEditToMonacoEdit = function (edit) {
  12235. const edits = [];
  12236.  
  12237. if (language == "python") {
  12238. for (const item1 of edit.documentChanges) {
  12239. for (const item2 of item1.edits) {
  12240. const newElement = {
  12241. textEdit: {
  12242. range: OJBetter_monaco.lspRangeToMonacoRange(item2.range),
  12243. text: item2.newText,
  12244. },
  12245. resource: monaco.Uri.parse(item1.textDocument.uri),
  12246. versionId: model.getVersionId(),
  12247. };
  12248. edits.push(newElement);
  12249. }
  12250. }
  12251. } else if (language == "java") {
  12252. for (const item1 in edit.changes) {
  12253. edit.changes[item1].forEach((item2) => {
  12254. const newElement = {
  12255. textEdit: {
  12256. range: OJBetter_monaco.lspRangeToMonacoRange(item2.range),
  12257. text: item2.newText,
  12258. },
  12259. resource: uri,
  12260. versionId: model.getVersionId(),
  12261. };
  12262. edits.push(newElement);
  12263. });
  12264. }
  12265. } else {
  12266. for (const key in edit.changes) {
  12267. const arr = edit.changes[key];
  12268. for (const item of arr) {
  12269. const newElement = {
  12270. textEdit: {
  12271. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  12272. text: item.newText,
  12273. },
  12274. resource: monaco.Uri.parse(key),
  12275. versionId: model.getVersionId(),
  12276. };
  12277. edits.push(newElement);
  12278. }
  12279. }
  12280. }
  12281. return { edits: edits };
  12282. };
  12283.  
  12284. /**
  12285. * 实例化一个editor
  12286. */
  12287. uri = monaco.Uri.file(uri);
  12288. model = monaco.editor.createModel("", language, uri);
  12289. OJBetter.monaco.editor = monaco.editor.create(
  12290. document.getElementById("OJBetter_monaco"),
  12291. {
  12292. model: model,
  12293. rootUri: rootUri,
  12294. fontSize: 15,
  12295. tabSize: 4,
  12296. theme: OJBetter.common.realDarkMode == "dark" ? "vs-dark" : "vs",
  12297. bracketPairColorization: {
  12298. enabled: true,
  12299. independentColorPoolPerBracketType: true,
  12300. },
  12301. automaticLayout: true,
  12302. lineNumbersMinChars: 3,
  12303. matchOnWordStartOnly: false,
  12304. wordWrap: "on",
  12305. wrappingIndent: "same",
  12306. glyphMargin: true,
  12307. formatOnType: true,
  12308. scrollbar: {
  12309. verticalScrollbarSize: 10,
  12310. horizontalScrollbarSize: 10,
  12311. alwaysConsumeMouseWheel:
  12312. OJBetter.monaco.setting.alwaysConsumeMouseWheel,
  12313. },
  12314. suggest: {
  12315. selectionMode: "never", // 代码建议不自动选择
  12316. },
  12317. }
  12318. );
  12319.  
  12320. /**
  12321. * 添加快捷功能
  12322. */
  12323. (OJBetter_monaco.addShortCuts = async () => {
  12324. // 从配置信息更新字体大小
  12325. OJBetter.monaco.editor.updateOptions({
  12326. fontSize: parseInt(OJBetter.monaco.setting.fontsize),
  12327. });
  12328.  
  12329. // 更多设置按钮
  12330. let moreSetting = OJB_safeCreateJQElement(`
  12331. <div class="ojb_btn ojb_btn_popover top">
  12332. <i class="iconfont">&#xe643;</i>
  12333. <span class="popover_content">${i18next.t("moreSettings.title", {
  12334. ns: "codeEditor",
  12335. })}</span>
  12336. </div>`);
  12337. form.topRightDiv.append(moreSetting);
  12338.  
  12339. // 设置弹窗页面
  12340. let moreSettingPopover = OJB_safeCreateJQElement(`
  12341. <dialog id="moreSettingPopover" class="OJBetter_setting_menu">
  12342. <div class="tool-box">
  12343. <button class='ojb_btn ojb_btn_popover top btn-close' type="button">
  12344. <i class="iconfont">&#xe614;</i>
  12345. </button>
  12346. </div>
  12347. <h2>${i18next.t("moreSettings.title", { ns: "codeEditor" })}</h2>
  12348. <div class='OJBetter_setting_list alert_tip'>
  12349. <p>${i18next.t("moreSettings.tip", { ns: "codeEditor" })}</p>
  12350. </div>
  12351. </dialog>`);
  12352. OJB_addDraggable(moreSettingPopover);
  12353. $("body").append(moreSettingPopover);
  12354.  
  12355. // 设置弹窗的关闭按钮
  12356. moreSettingPopover.find(".btn-close").on("click", function () {
  12357. moreSettingPopover[0].close();
  12358. });
  12359.  
  12360. // 调整字体大小
  12361. let changeSize = OJB_safeCreateJQElement(`
  12362. <div class='OJBetter_setting_list'>
  12363. <label for='fontSizeInput'>
  12364. <div style="display: flex;align-items: center;">${i18next.t(
  12365. "moreSettings.fontSizeInput.label",
  12366. { ns: "codeEditor" }
  12367. )}</div>
  12368. </label>
  12369. <div class="help_tip">
  12370. ${helpCircleHTML}
  12371. <div class="tip_text">${i18next.t(
  12372. "moreSettings.fontSizeInput.helpText",
  12373. { ns: "codeEditor" }
  12374. )}</div>
  12375. </div>
  12376. <input type='number' id='fontSizeInput' class='no_default'
  12377. require=true
  12378. placeholder="${i18next.t(
  12379. "moreSettings.fontSizeInput.placeholder",
  12380. { ns: "codeEditor" }
  12381. )}"
  12382. value="${OJBetter.monaco.setting.fontsize}">
  12383. <span>px</span>
  12384. </div>`);
  12385. moreSettingPopover.append(changeSize);
  12386. changeSize.find("input#fontSizeInput").on("input", function () {
  12387. var size = $(this).val();
  12388. OJBetter.monaco.editor.updateOptions({ fontSize: parseInt(size) });
  12389. GM_setValue("editorFontSize", size);
  12390. });
  12391.  
  12392. // 测试检查器选择
  12393. let selectValidator = OJB_safeCreateJQElement(`
  12394. <div class='OJBetter_setting_list'>
  12395. <label for="judgeResultValidator">
  12396. <span>${i18next.t("moreSettings.validator.label", {
  12397. ns: "codeEditor",
  12398. })}</span>
  12399. </label>
  12400. <div class="help_tip">
  12401. ${helpCircleHTML}
  12402. <div class="tip_text">${i18next.t(
  12403. "moreSettings.validator.helpText",
  12404. { ns: "codeEditor" }
  12405. )}</div>
  12406. </div>
  12407. <select id="judgeResultValidator" name="judgeResultValidator">
  12408. <option value="ignoreWhitespace">${i18next.t(
  12409. "moreSettings.validator.options.ignoreWhitespace",
  12410. { ns: "codeEditor" }
  12411. )}</option>
  12412. <option value="strict">${i18next.t(
  12413. "moreSettings.validator.options.strict",
  12414. { ns: "codeEditor" }
  12415. )}</option>
  12416. <option value="ncmp">${i18next.t(
  12417. "moreSettings.validator.options.ncmp",
  12418. { ns: "codeEditor" }
  12419. )}</option>
  12420. <option value="rcmp4">${i18next.t(
  12421. "moreSettings.validator.options.rcmp4",
  12422. { ns: "codeEditor" }
  12423. )}</option>
  12424. <option value="rcmp6">${i18next.t(
  12425. "moreSettings.validator.options.rcmp6",
  12426. { ns: "codeEditor" }
  12427. )}</option>
  12428. <option value="rcmp9">${i18next.t(
  12429. "moreSettings.validator.options.rcmp9",
  12430. { ns: "codeEditor" }
  12431. )}</option>
  12432. <option value="wcmp">${i18next.t(
  12433. "moreSettings.validator.options.wcmp",
  12434. { ns: "codeEditor" }
  12435. )}</option>
  12436. <option value="nyesno">${i18next.t(
  12437. "moreSettings.validator.options.nyesno",
  12438. { ns: "codeEditor" }
  12439. )}</option>
  12440. </select>
  12441. </div>`);
  12442. // 选择默认检查器
  12443. const defaultValidator = OJB_getGMValue(
  12444. "judgeResultCheckMode",
  12445. "ignoreWhitespace"
  12446. );
  12447. selectValidator.find("select").val(defaultValidator);
  12448. moreSettingPopover.append(selectValidator);
  12449. // 注册检查器更改事件
  12450. selectValidator.find("select").on("change", function () {
  12451. GM_setValue("judgeResultCheckMode", $(this).val());
  12452. });
  12453.  
  12454. // 全屏按钮
  12455. let fullscreenButton = OJB_safeCreateJQElement(`
  12456. <button type="button" class="ojb_btn ojb_btn_popover top">
  12457. <i class="iconfont">&#xe606;</i>
  12458. <span class="popover_content">${i18next.t("fullscreenButton", {
  12459. ns: "codeEditor",
  12460. })}</span>
  12461. </button>
  12462. `);
  12463. form.topRightDiv.append(fullscreenButton);
  12464. fullscreenButton.on("click", enterFullscreen);
  12465.  
  12466. // 固定到底部按钮
  12467. let fixToBottomButton = OJB_safeCreateJQElement(`
  12468. <button type="button" class="ojb_btn ojb_btn_popover top">
  12469. <i class="iconfont">&#xe607;</i>
  12470. <span class="popover_content">${i18next.t("fixToBottomButton", {
  12471. ns: "codeEditor",
  12472. })}</span>
  12473. </button>
  12474. `);
  12475. form.topRightDiv.append(fixToBottomButton);
  12476. fixToBottomButton.on("click", fixToBottom);
  12477.  
  12478. // 固定到右侧按钮
  12479. let fixToRightButton = OJB_safeCreateJQElement(`
  12480. <button type="button" class="ojb_btn ojb_btn_popover top">
  12481. <i class="iconfont">&#xe605;</i>
  12482. <span class="popover_content">${i18next.t("fixToRightButton", {
  12483. ns: "codeEditor",
  12484. })}</span>
  12485. </button>
  12486. `);
  12487. form.topRightDiv.append(fixToRightButton);
  12488. fixToRightButton.on("click", fixToRight);
  12489.  
  12490. // 添加测试/提交按钮到顶部
  12491. if (OJBetter.monaco.setting.submitButtonPosition == "top") {
  12492. form.topRightDiv.append(form.runButton);
  12493. form.topRightDiv.append(form.submitButton);
  12494. }
  12495.  
  12496. // 选择记忆
  12497. if (!OJBetter.monaco.setting.position_initialized) {
  12498. OJBetter.monaco.setting.position_initialized = true; // 标记是否已经初始化过
  12499. if (OJBetter.monaco.setting.position == "full") {
  12500. fullscreenButton.click();
  12501. } else if (OJBetter.monaco.setting.position == "bottom") {
  12502. fixToBottomButton.click();
  12503. } else if (OJBetter.monaco.setting.position == "right") {
  12504. fixToRightButton.click();
  12505. }
  12506. }
  12507.  
  12508. // 禁用按钮
  12509. function disableButtons() {
  12510. fullscreenButton.prop("disabled", true);
  12511. fixToBottomButton.prop("disabled", true);
  12512. fixToRightButton.prop("disabled", true);
  12513. }
  12514.  
  12515. // 启用按钮
  12516. function enableButtons() {
  12517. fullscreenButton.prop("disabled", false);
  12518. fixToBottomButton.prop("disabled", false);
  12519. fixToRightButton.prop("disabled", false);
  12520. }
  12521.  
  12522. // 打开更多设置弹窗
  12523. moreSetting.on("click", () => {
  12524. OJB_showModal(moreSettingPopover);
  12525. });
  12526.  
  12527. // 进入全屏
  12528. function enterFullscreen() {
  12529. let editor = $("#OJBetter_editor");
  12530. editor.addClass("fullscreen");
  12531.  
  12532. // 取消按钮
  12533. let cancelButton = OJB_safeCreateJQElement(`
  12534. <button type="button" class="ojb_btn ojb_btn_popover top primary exit_button_bottom">
  12535. <i class="iconfont">&#xe60b;</i>
  12536. <span class="popover_content">${i18next.t(
  12537. "exitFullscreenButton",
  12538. { ns: "codeEditor" }
  12539. )}</span>
  12540. </button>
  12541. `).on("click", () => exitFullscreen(cancelButton));
  12542. $("body").append(cancelButton);
  12543.  
  12544. disableButtons();
  12545. GM_setValue("monacoEditor_position", "full");
  12546. }
  12547.  
  12548. // 退出全屏
  12549. const exitFullscreen = (cancelButton) => {
  12550. let editor = $("#OJBetter_editor");
  12551. editor.removeClass("fullscreen");
  12552. cancelButton.remove();
  12553. enableButtons();
  12554. GM_setValue("monacoEditor_position", "initial");
  12555. };
  12556.  
  12557. // 固定到底部
  12558. function fixToBottom() {
  12559. let editor = $("#OJBetter_editor");
  12560. editor.addClass("bottom");
  12561.  
  12562. let halfHeight = $(window).height() * 0.5;
  12563. let blankSpace = $("<div>", {
  12564. class: "blank-space",
  12565. style: "height: " + (halfHeight + 30) + "px;",
  12566. });
  12567. $("body").append(blankSpace);
  12568.  
  12569. let cancelButton = OJB_safeCreateJQElement(`
  12570. <button type="button" class="ojb_btn ojb_btn_popover top enabled exit_button_bottom">
  12571. <i class="iconfont">&#xe625;</i>
  12572. <span class="popover_content">${i18next.t(
  12573. "cancelFixButton",
  12574. { ns: "codeEditor" }
  12575. )}</span>
  12576. </button>
  12577. `).on("click", () =>
  12578. cancelFixingToBottom(cancelButton, blankSpace)
  12579. );
  12580. $("body").append(cancelButton);
  12581.  
  12582. disableButtons();
  12583. GM_setValue("monacoEditor_position", "bottom");
  12584. }
  12585.  
  12586. // 取消固定到底部
  12587. const cancelFixingToBottom = (cancelButton, blankSpace) => {
  12588. let editor = $("#OJBetter_editor");
  12589. editor.removeClass("bottom");
  12590. cancelButton.remove();
  12591. blankSpace.remove();
  12592. enableButtons();
  12593. GM_setValue("monacoEditor_position", "initial");
  12594. };
  12595.  
  12596. // 固定到右侧边栏
  12597. function fixToRight() {
  12598. const sidebar = $("#sidebar").hide();
  12599.  
  12600. // 添加样式
  12601. const styleElement = GM_addStyle(`
  12602. #body {
  12603. min-width: 50vw;
  12604. max-width: 50vw;
  12605. max-height: 100vh;
  12606. overflow-x: hidden;
  12607. overflow-y: auto;
  12608. padding: 1rem;
  12609. box-sizing: border-box;
  12610. }
  12611. body {
  12612. margin: 0px;
  12613. }
  12614. .content-with-sidebar {
  12615. margin-right: 0px !important;
  12616. }
  12617. .menu-list li {
  12618. margin-right: 0.5em;
  12619. }
  12620. .menu-list li a {
  12621. font-size: 1.4rem;
  12622. }
  12623. #OJBetter_editor{
  12624. height: 100vh;
  12625. width: 50vw;
  12626. }
  12627. `);
  12628.  
  12629. // 包装一层div
  12630. $("#body").wrap(
  12631. '<div id="right-side-wrapper" style="display:flex; max-width: 100vw; overflow: hidden;"></div>'
  12632. );
  12633. const blankSpace = $("<div>").appendTo("#right-side-wrapper");
  12634.  
  12635. // 移动编辑器
  12636. const editor = $("#OJBetter_editor")
  12637. .prependTo(blankSpace)
  12638. .addClass("right-side");
  12639.  
  12640. // 取消按钮
  12641. const cancelButton = OJB_safeCreateJQElement(`
  12642. <button type="button" class="ojb_btn ojb_btn_popover top enabled exit_button_bottom">
  12643. <i class="iconfont">&#xe625;</i>
  12644. <span class="popover_content">${i18next.t(
  12645. "cancelFixButton",
  12646. { ns: "codeEditor" }
  12647. )}</span>
  12648. </button>
  12649. `)
  12650. .on("click", () =>
  12651. cancelFixingToRight(sidebar, styleElement, editor, cancelButton)
  12652. )
  12653. .appendTo("body");
  12654.  
  12655. disableButtons();
  12656. GM_setValue("monacoEditor_position", "right");
  12657.  
  12658. // 补丁:修复固定到右侧导致的样例元素.sample-test相关代码重复执行的问题(具体原因未查)
  12659. $(".sample-test")
  12660. .find(".title")
  12661. .each((i, e) => {
  12662. if ($(e).find(".input-output-copier").length > 1) {
  12663. $(e).find(".input-output-copier").first().remove();
  12664. }
  12665. });
  12666. darkModeStyleAdjustment();
  12667. }
  12668.  
  12669. const cancelFixingToRight = (
  12670. sidebar,
  12671. styleElement,
  12672. editor,
  12673. cancelButton
  12674. ) => {
  12675. sidebar.show();
  12676. // 移回来
  12677. editor.insertAfter(form.sourceDiv).removeClass("right-side");
  12678.  
  12679. // 移除包装
  12680. $("#body").unwrap();
  12681. cancelButton.remove();
  12682. styleElement.remove(); // 移除添加的样式
  12683.  
  12684. enableButtons();
  12685. GM_setValue("monacoEditor_position", "initial");
  12686. };
  12687.  
  12688. // 代码同步与保存
  12689. if (OJBetter.monaco.setting.autoMemoryCode) {
  12690. let nowUrl = window.location.href;
  12691. nowUrl = nowUrl.replace(/#/, ""); // 当页面存在更改时url会多出一个#,去掉
  12692. const code = await getCode(nowUrl);
  12693. if (code) {
  12694. OJBetter.monaco.editor.setValue(code); // 恢复代码
  12695. form.sourceDiv.val(code);
  12696. }
  12697. OJBetter.monaco.editor.onDidChangeModelContent(async () => {
  12698. // 将monaco editor的内容同步到sourceDiv
  12699. const code = OJBetter.monaco.editor.getValue();
  12700. form.sourceDiv.val(code);
  12701. await saveCode(nowUrl, code);
  12702. });
  12703. }
  12704. })();
  12705.  
  12706. /**
  12707. * 注册本地自动补全
  12708. */
  12709. (OJBetter_monaco.RegisterLocalComplet = async () => {
  12710. // 补全器注册函数
  12711. function registMyCompletionItemProvider(language, genre, rule) {
  12712. if (genre == "monaco") {
  12713. monaco.languages.registerCompletionItemProvider(language, {
  12714. provideCompletionItems: function (model, position) {
  12715. return parseMonacoCompleter(
  12716. rule,
  12717. OJBetter_monaco.CollectRangeByPosition(position)
  12718. );
  12719. },
  12720. });
  12721. } else if (genre == "ace") {
  12722. monaco.languages.registerCompletionItemProvider(language, {
  12723. provideCompletionItems: function (model, position) {
  12724. return parseAceCompleter(
  12725. rule,
  12726. OJBetter_monaco.CollectRangeByPosition(position)
  12727. );
  12728. },
  12729. });
  12730. }
  12731. }
  12732.  
  12733. // 注册acwing cpp 模板
  12734. if (language == "cpp" && OJBetter.monaco.complet.cppCodeTemplate) {
  12735. try {
  12736. var acwing_cpp_code_completer = JSON.parse(
  12737. GM_getResourceText("acwing_cpp_code_completer")
  12738. );
  12739. registMyCompletionItemProvider("cpp", "ace", acwing_cpp_code_completer);
  12740. } catch (error) {
  12741. console.error("Error registering acwing cpp template:", error);
  12742. }
  12743. }
  12744.  
  12745. // 注册自定义的补全
  12746. let complet_length =
  12747. OJBetter.monaco.complet.customConfig.configurations.length;
  12748. if (complet_length > 0) {
  12749. for (let i = 0; i < complet_length; i++) {
  12750. let item = OJBetter.monaco.complet.customConfig.configurations[i];
  12751. if (item.isChoose && item.language == language) {
  12752. try {
  12753. let rule = await OJB_getExternalJSON(item.jsonUrl);
  12754. registMyCompletionItemProvider(item.language, item.genre, rule);
  12755. } catch (error) {
  12756. console.error(
  12757. `Error registering custom completer for ${item.language}:`,
  12758. error
  12759. );
  12760. }
  12761. }
  12762. }
  12763. }
  12764. })();
  12765.  
  12766. if (!support || !OJBetter.monaco.lsp.enabled) {
  12767. return;
  12768. } // 如果不支持lsp,则到此为止
  12769.  
  12770. /**
  12771. * LSP连接状态指示
  12772. */
  12773. const lspStateButton = OJB_safeCreateJQElement(`
  12774. <div id="lspStateDiv" class="ojb_btn ojb_btn_popover top loading">
  12775. <i class="iconfont">&#xe658;</i>
  12776. <span class="popover_content">${i18next.t("lsp.connect", {
  12777. ns: "codeEditor",
  12778. })}</span>
  12779. </div>
  12780. `).on("click", () => {
  12781. OJB_showModal(LSPLogDiv);
  12782. LSPLogDiv.show();
  12783. });
  12784. form.topRightDiv.prepend(lspStateButton);
  12785.  
  12786. const LSPLogDiv = OJB_safeCreateJQElement(`
  12787. <dialog id="LSPLog" style="display: none;">
  12788. <button class="ojb_btn">${i18next.t("close", { ns: "common" })}</button>
  12789. <div id="LSPLogList" style="overflow: auto;"></div>
  12790. <dialog>`);
  12791. $("body").append(LSPLogDiv);
  12792.  
  12793. const LSPLogList = $("<ul></ul>");
  12794. $("#LSPLogList").append(LSPLogList);
  12795.  
  12796. const closeButton = LSPLogDiv.find("button");
  12797. closeButton.on("click", function () {
  12798. OJB_closeModal(LSPLogDiv);
  12799. });
  12800.  
  12801. /**
  12802. * 推送新的消息到LSP日志中
  12803. * @param {'error' | 'warn' | 'info'} status
  12804. * @param {string} msg
  12805. * @param {boolean} data
  12806. */
  12807. function pushLSPLogMessage(status, msg, data) {
  12808. var li = $("<li>").text("[" + new Date().toLocaleString() + "] " + msg);
  12809. if (status === "error") {
  12810. li.attr("style", "color: #f44336;");
  12811. } else if (status === "warn") {
  12812. li.attr("style", "color: #ff9800;");
  12813. } else if (status === "info") {
  12814. li.attr("style", "color: #616161;");
  12815. }
  12816. if (data) {
  12817. var jsonText = JSON.stringify(data, null, 2);
  12818. var details = $("<details>");
  12819. var summary = $("<summary>").text("Data");
  12820. var pre = $("<pre>").text(jsonText);
  12821. details.append(summary, pre);
  12822. li.append(details);
  12823. }
  12824. LSPLogList.append(li);
  12825. }
  12826.  
  12827. /**
  12828. * 添加状态底栏
  12829. */
  12830. var statusBar = $('<div id="OJBetter_statusBar">');
  12831. form.editorDiv.append(statusBar);
  12832.  
  12833. /**
  12834. * languageSocket
  12835. */
  12836. var url = OJBetter.monaco.lsp.socketUrl;
  12837. var languageSocket = new WebSocket(url + language);
  12838. OJBetter.monaco.lsp.socket.push(languageSocket);
  12839. var languageSocketState = false;
  12840. var responseHandlers = new Map(); // 映射表,需要等待返回数据的请求 -> 对应的事件触发函数
  12841.  
  12842. languageSocket.onopen = () => {
  12843. languageSocketState = true;
  12844. lspStateButton.setButtonPopover(
  12845. i18next.t("lsp.waitingAnswer", { ns: "codeEditor" })
  12846. );
  12847. pushLSPLogMessage(
  12848. "info",
  12849. `languageSocket ${i18next.t("lsp.socket.open", { ns: "logMessage" })}`
  12850. );
  12851. };
  12852. languageSocket.onmessage = (event) => {
  12853. const message = JSON.parse(event.data);
  12854. if (message.id === 0 && message.result) {
  12855. // 初始化完成
  12856. lspStateButton.setButtonState(
  12857. "success",
  12858. i18next.t("lsp.connected", { ns: "codeEditor" })
  12859. );
  12860. pushLSPLogMessage(
  12861. "info",
  12862. `Initialization ${i18next.t("lsp.state.finished", {
  12863. ns: "logMessage",
  12864. })}`
  12865. );
  12866. serverInfo = message.result; // 存下服务器支持信息
  12867. OJBetter_monaco.openDocRequest(); // 打开文档
  12868. if (!OJBetter.monaco.setting.language.includes(language)) {
  12869. OJBetter.monaco.setting.language.push(language);
  12870. OJBetter_monaco.RegistrationAfterInit(); // 注册语言及功能
  12871. } else {
  12872. location.reload(); // 这里有问题,先贴个补丁
  12873. }
  12874. OJBetter_monaco.PassiveReceiveHandler(); // 注册被动接收函数
  12875. } else if (message.id === 0 && message.error) {
  12876. pushLSPLogMessage(
  12877. "warn",
  12878. `Initialization ${i18next.t("lsp.state.error", { ns: "logMessage" })}`
  12879. );
  12880. } else if (message.id !== undefined && responseHandlers.has(message.id)) {
  12881. // 如果收到带有id字段的消息,则回传给对应的事件触发函数
  12882. const handler = responseHandlers.get(message.id);
  12883. if (handler) {
  12884. handler(message);
  12885. responseHandlers.delete(message.id); // 删除已处理的事件触发函数
  12886. }
  12887. } else if (message.method == "textDocument/publishDiagnostics") {
  12888. // 接收代码诊断推送
  12889. OJBetter_monaco.updateMarkers(message);
  12890. } else if (message.method == "workspace/applyEdit") {
  12891. // 应用服务器推送的更改
  12892. OJBetter_monaco.applyEdit(message);
  12893. }
  12894. };
  12895. languageSocket.onerror = (error) => {
  12896. pushLSPLogMessage(
  12897. "error",
  12898. `languageSocket ${i18next.t("lsp.state.error", { ns: "logMessage" })}`,
  12899. error
  12900. );
  12901. console.warn(`Error connecting to languageSocket: ${error}`);
  12902. };
  12903. languageSocket.onclose = (event) => {
  12904. languageSocketState = false;
  12905. lspStateButton.setButtonState(
  12906. "error",
  12907. i18next.t("lsp.error", { ns: "codeEditor" })
  12908. );
  12909. pushLSPLogMessage(
  12910. "warn",
  12911. `languageSocket ${i18next.t("lsp.socket.close", { ns: "logMessage" })}`
  12912. );
  12913. };
  12914.  
  12915. /**
  12916. * 等待LanguageSocketState
  12917. */
  12918. async function waitForLanguageSocketState() {
  12919. return new Promise((resolve) => {
  12920. const checkInitialized = () => {
  12921. if (languageSocketState) {
  12922. resolve();
  12923. } else {
  12924. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  12925. }
  12926. };
  12927. checkInitialized();
  12928. });
  12929. }
  12930.  
  12931. // 等待lsp响应初始化结果
  12932. async function waitForInitialized() {
  12933. return new Promise((resolve) => {
  12934. const checkInitialized = () => {
  12935. if (initialized) {
  12936. resolve();
  12937. } else {
  12938. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  12939. }
  12940. };
  12941. checkInitialized();
  12942. });
  12943. }
  12944.  
  12945. /**
  12946. * 与languageSocket通信的包装方法
  12947. */
  12948. async function sendMessage(data, requiresResponse, callback) {
  12949. if (!initialized) {
  12950. await waitForInitialized(); // 等待initialized为真
  12951. }
  12952. if (requiresResponse) {
  12953. responseHandlers.set(data.id, callback); // 将事件触发函数与id关联起来
  12954. }
  12955. if (!languageSocketState) await waitForLanguageSocketState();
  12956. languageSocket.send(JSON.stringify(data));
  12957. }
  12958. // 发送消息并等待返回结果
  12959. function fetchData(params, callback) {
  12960. sendMessage(params, true, callback);
  12961. }
  12962. // 发送消息,不需要等待返回结果
  12963. function sendData(data) {
  12964. sendMessage(data, false);
  12965. }
  12966.  
  12967. /**
  12968. * 代码文件更新fileWebSocket
  12969. */
  12970. var fileWebSocket = new WebSocket(url + "file");
  12971. var fileWebSocketState = false;
  12972. OJBetter.monaco.lsp.socket.push(fileWebSocket);
  12973. fileWebSocket.onopen = () => {
  12974. fileWebSocketState = true;
  12975. pushLSPLogMessage(
  12976. "info",
  12977. `fileWebSocket ${i18next.t("lsp.socket.open", { ns: "logMessage" })}`
  12978. );
  12979. };
  12980. fileWebSocket.onclose = (ev) => {
  12981. fileWebSocketState = false;
  12982. pushLSPLogMessage(
  12983. "warn",
  12984. `fileWebSocket ${i18next.t("lsp.socket.close", { ns: "logMessage" })}`,
  12985. ev
  12986. );
  12987. };
  12988. fileWebSocket.onmessage = (ev) => {
  12989. let message = JSON.parse(ev.data);
  12990. if (message.result !== "ok")
  12991. pushLSPLogMessage("error", `update file failed: ${ev}`);
  12992. };
  12993. fileWebSocket.onerror = (error) => {
  12994. console.warn(`Error connecting to fileWebSocket: ${error}`);
  12995. };
  12996. async function updateFile(workspace, filename, fileExtension, code) {
  12997. async function waitForfileWebSocketState() {
  12998. return new Promise((resolve) => {
  12999. const checkInitialized = () => {
  13000. if (fileWebSocketState) {
  13001. resolve();
  13002. } else {
  13003. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  13004. }
  13005. };
  13006. checkInitialized();
  13007. });
  13008. }
  13009. if (!fileWebSocketState) await waitForfileWebSocketState();
  13010. fileWebSocket.send(
  13011. JSON.stringify({
  13012. type: "update",
  13013. workspace,
  13014. filename,
  13015. fileExtension,
  13016. code,
  13017. })
  13018. );
  13019. }
  13020.  
  13021. /**
  13022. * 发送初始化请求
  13023. */
  13024. OJBetter_monaco.Initialize = () => {
  13025. //初始化initialize
  13026. const capabilities = {
  13027. workspace: {
  13028. applyEdit: true,
  13029. },
  13030. textDocument: {
  13031. publishDiagnostics: {
  13032. relatedInformation: true,
  13033. versionSupport: false,
  13034. tagSupport: {
  13035. valueSet: [1, 2],
  13036. },
  13037. codeDescriptionSupport: true,
  13038. },
  13039. completion: {
  13040. contextSupport: true,
  13041. completionItem: {
  13042. snippetSupport: true,
  13043. commitCharactersSupport: true,
  13044. documentationFormat: ["markdown", "plaintext"],
  13045. deprecatedSupport: true,
  13046. preselectSupport: true,
  13047. tagSupport: {
  13048. valueSet: [1],
  13049. },
  13050. insertReplaceSupport: true,
  13051. resolveSupport: {
  13052. properties: ["documentation", "detail", "additionalTextEdits"],
  13053. },
  13054. insertTextModeSupport: {
  13055. valueSet: [1, 2],
  13056. },
  13057. },
  13058. },
  13059. hover: {
  13060. dynamicRegistration: true,
  13061. contentFormat: ["markdown", "plaintext"],
  13062. },
  13063. signatureHelp: {
  13064. signatureInformation: {
  13065. documentationFormat: ["markdown", "plaintext"],
  13066. parameterInformation: {
  13067. labelOffsetSupport: true,
  13068. },
  13069. activeParameterSupport: true,
  13070. },
  13071. contextSupport: true,
  13072. },
  13073. definition: {
  13074. dynamicRegistration: true,
  13075. linkSupport: true,
  13076. },
  13077. references: {
  13078. dynamicRegistration: true,
  13079. },
  13080. documentHighlight: {
  13081. dynamicRegistration: true,
  13082. },
  13083. codeAction: {
  13084. codeActionLiteralSupport: {
  13085. codeActionKind: {
  13086. valueSet:
  13087. language == "java"
  13088. ? []
  13089. : [
  13090. "",
  13091. "quickfix",
  13092. "refactor",
  13093. "refactor.extract",
  13094. "refactor.inline",
  13095. "refactor.rewrite",
  13096. "source",
  13097. "source.organizeImports",
  13098. ],
  13099. },
  13100. },
  13101. },
  13102. rename: {
  13103. dynamicRegistration: true,
  13104. prepareSupport: true,
  13105. prepareSupportDefaultBehavior: 1,
  13106. honorsChangeAnnotations: true,
  13107. },
  13108. documentLink: {
  13109. tooltipSupport: true,
  13110. },
  13111. typeDefinition: {
  13112. dynamicRegistration: true,
  13113. linkSupport: true,
  13114. },
  13115. implementation: {
  13116. dynamicRegistration: true,
  13117. linkSupport: true,
  13118. },
  13119. colorProvider: {
  13120. dynamicRegistration: true,
  13121. },
  13122. foldingRange: {
  13123. dynamicRegistration: true,
  13124. rangeLimit: 5000,
  13125. lineFoldingOnly: true,
  13126. },
  13127. declaration: {
  13128. dynamicRegistration: true,
  13129. linkSupport: true,
  13130. },
  13131. semanticTokens: {
  13132. dynamicRegistration: true,
  13133. tokenTypes: [
  13134. "namespace",
  13135. "type",
  13136. "class",
  13137. "enum",
  13138. "interface",
  13139. "struct",
  13140. "typeParameter",
  13141. "parameter",
  13142. "variable",
  13143. "property",
  13144. "enumMember",
  13145. "event",
  13146. "function",
  13147. "method",
  13148. "macro",
  13149. "keyword",
  13150. "modifier",
  13151. "comment",
  13152. "string",
  13153. "number",
  13154. "regexp",
  13155. "operator",
  13156. ],
  13157. tokenModifiers: [
  13158. "declaration",
  13159. "definition",
  13160. "readonly",
  13161. "static",
  13162. "deprecated",
  13163. "abstract",
  13164. "async",
  13165. "modification",
  13166. "documentation",
  13167. "defaultLibrary",
  13168. ],
  13169. formats: ["relative"],
  13170. requests: {
  13171. range: true,
  13172. full: {
  13173. delta: true,
  13174. },
  13175. },
  13176. multilineTokenSupport: false,
  13177. overlappingTokenSupport: false,
  13178. },
  13179. callHierarchy: {
  13180. dynamicRegistration: true,
  13181. },
  13182. },
  13183. window: {
  13184. showMessage: {
  13185. messageActionItem: {
  13186. additionalPropertiesSupport: true,
  13187. },
  13188. },
  13189. showDocument: {
  13190. support: true,
  13191. },
  13192. workDoneProgress: true,
  13193. },
  13194. general: {
  13195. regularExpressions: {
  13196. engine: "ECMAScript",
  13197. version: "ES2020",
  13198. },
  13199. markdown: {
  13200. parser: "marked",
  13201. version: "1.1.0",
  13202. },
  13203. },
  13204. };
  13205.  
  13206. const initializeRequest = {
  13207. id: id++,
  13208. jsonrpc: "2.0",
  13209. method: "initialize",
  13210. params: {
  13211. processId: null,
  13212. clientInfo: {
  13213. name: "CFMonaco" + InstanceID,
  13214. },
  13215. locale: "zh-CN",
  13216. rootPath: null,
  13217. rootUri: null,
  13218. capabilities: capabilities,
  13219. trace: "off",
  13220. workspaceFolders: [
  13221. {
  13222. uri: "file:///" + OJBetter.monaco.lsp.workUri + workspace,
  13223. name: "file:///" + OJBetter.monaco.lsp.workUri + workspace,
  13224. },
  13225. ],
  13226. },
  13227. };
  13228. languageSocket.send(JSON.stringify(initializeRequest));
  13229.  
  13230. // 打开文档函数
  13231. OJBetter_monaco.openDocRequest = function () {
  13232. const initializ = {
  13233. jsonrpc: "2.0",
  13234. method: "initialized",
  13235. params: {},
  13236. };
  13237. languageSocket.send(JSON.stringify(initializ));
  13238. const openDocRequest = {
  13239. jsonrpc: "2.0",
  13240. method: "textDocument/didOpen",
  13241. params: {
  13242. textDocument: {
  13243. uri: model.uri.toString(),
  13244. languageId: language,
  13245. version: model.getVersionId(),
  13246. text: model.getValue(),
  13247. },
  13248. },
  13249. };
  13250. languageSocket.send(JSON.stringify(openDocRequest));
  13251. initialized = true; // 初始化完成,这里确认逻辑待完善
  13252. };
  13253.  
  13254. // 初始化更新文件
  13255. updateFile(workspace, filename, fileExtension, model.getValue());
  13256. };
  13257.  
  13258. /**
  13259. * 注册语言及功能
  13260. */
  13261. OJBetter_monaco.RegistrationAfterInit = () => {
  13262. // 注册语言
  13263. monaco.languages.register({ id: language });
  13264.  
  13265. // 注册"Command"
  13266. (function registerCommand() {
  13267. serverInfo.capabilities.executeCommandProvider.commands.forEach(
  13268. (item) => {
  13269. pushLSPLogMessage(
  13270. "info",
  13271. `${i18next.t("lsp.server.regist", { ns: "logMessage" })}`,
  13272. item
  13273. );
  13274. monaco.editor.registerCommand(item, (accessor, ...args) => {
  13275. sendData({
  13276. jsonrpc: "2.0",
  13277. id: id++,
  13278. method: "workspace/executeCommand",
  13279. params: {
  13280. command: item,
  13281. arguments: args,
  13282. },
  13283. });
  13284. });
  13285. }
  13286. );
  13287. })();
  13288.  
  13289. // 注册"增量更新"
  13290. model.onDidChangeContent((event) => {
  13291. updateFile(workspace, filename, fileExtension, model.getValue()); // 更新文件
  13292. const changeDocRequest = {
  13293. jsonrpc: "2.0",
  13294. method: "textDocument/didChange",
  13295. params: {
  13296. textDocument: {
  13297. uri: model.uri.toString(),
  13298. version: model.getVersionId(),
  13299. },
  13300. contentChanges: event.changes.map((change) => ({
  13301. range: OJBetter_monaco.MonacoRangeTolspRange(change.range),
  13302. rangeLength: change.rangeLength,
  13303. text: change.text,
  13304. })),
  13305. },
  13306. };
  13307. sendData(changeDocRequest);
  13308. });
  13309.  
  13310. //注册"自动补全"
  13311. monaco.languages.registerCompletionItemProvider(language, {
  13312. provideCompletionItems: (model, position, context) => {
  13313. const request = {
  13314. jsonrpc: "2.0",
  13315. id: id++,
  13316. method: "textDocument/completion",
  13317. params: {
  13318. textDocument: {
  13319. uri: model.uri.toString(),
  13320. },
  13321. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  13322. context: {
  13323. triggerKind: context.triggerKind + 1, // 这里要+1,两边的定义不一样。。。
  13324. triggerCharacter: context.triggerCharacter,
  13325. },
  13326. },
  13327. };
  13328. return new Promise((resolve, reject) => {
  13329. fetchData(request, (response) => {
  13330. const result = response.result;
  13331. pushLSPLogMessage(
  13332. "info",
  13333. `completion ${i18next.t("lsp.server.receive", {
  13334. ns: "logMessage",
  13335. })}`,
  13336. response
  13337. );
  13338. if (!result) return resolve(null);
  13339. const CompletionItems = {
  13340. suggestions: result.items.map(
  13341. ({
  13342. label,
  13343. kind,
  13344. filterText,
  13345. insertText,
  13346. insertTextFormat,
  13347. sortText,
  13348. textEdit,
  13349. documentation,
  13350. additionalTextEdits,
  13351. }) => ({
  13352. additionalTextEdits: additionalTextEdits
  13353. ? additionalTextEdits.map(({ newText, range }) => ({
  13354. text: newText,
  13355. range: OJBetter_monaco.lspRangeToMonacoRange(range),
  13356. }))
  13357. : [],
  13358. documentation: documentation ? documentation.value : "",
  13359. filterText,
  13360. insertText: insertText ? insertText : textEdit.newText,
  13361. insertTextRules:
  13362. insertTextFormat === 2
  13363. ? monaco.languages.CompletionItemInsertTextRule
  13364. .InsertAsSnippet
  13365. : monaco.languages.CompletionItemInsertTextRule
  13366. .KeepWhitespace,
  13367. kind,
  13368. label,
  13369. sortText,
  13370. range: textEdit
  13371. ? textEdit.range
  13372. ? OJBetter_monaco.lspRangeToMonacoRange(textEdit.range)
  13373. : OJBetter_monaco.lspRangeToMonacoRange(textEdit.insert)
  13374. : null,
  13375. })
  13376. ),
  13377. };
  13378. pushLSPLogMessage(
  13379. "info",
  13380. `completion ${i18next.t("lsp.server.transmit", {
  13381. ns: "logMessage",
  13382. })}`,
  13383. CompletionItems
  13384. );
  13385. resolve(CompletionItems);
  13386. });
  13387. });
  13388. },
  13389. });
  13390.  
  13391. // 注册"代码修复"
  13392. monaco.languages.registerCodeActionProvider(language, {
  13393. provideCodeActions: (model, range, context) => {
  13394. const request = {
  13395. id: id++,
  13396. jsonrpc: "2.0",
  13397. method: "textDocument/codeAction",
  13398. params: {
  13399. textDocument: {
  13400. uri: model.uri.toString(),
  13401. },
  13402. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  13403. context: {
  13404. diagnostics: context.markers.map((item) => ({
  13405. range: OJBetter_monaco.MonacoRangeTolspRange({
  13406. startLineNumber: item.startLineNumber,
  13407. startColumn: item.startColumn,
  13408. endLineNumber: item.endLineNumber,
  13409. endColumn: item.endColumn,
  13410. }),
  13411. severity: OJBetter_monaco.MonacoSeverityTolspSeverity(
  13412. item.severity
  13413. ),
  13414. code: item.code,
  13415. source: item.source,
  13416. message: item.message,
  13417. tags: item.tags,
  13418. relatedInformation: item.relatedInformation
  13419. ? item.relatedInformation.map((item) => ({
  13420. location: {
  13421. uri: item.resource.toString(),
  13422. range: OJBetter_monaco.MonacoRangeTolspRange({
  13423. startLineNumber: item.startLineNumber,
  13424. startColumn: item.startColumn,
  13425. endLineNumber: item.endLineNumber,
  13426. endColumn: item.endColumn,
  13427. }),
  13428. },
  13429. message: item.message,
  13430. }))
  13431. : null,
  13432. })),
  13433. only: context.only ? [context.only] : [],
  13434. triggerKind: context.trigger,
  13435. },
  13436. },
  13437. };
  13438.  
  13439. return new Promise((resolve, reject) => {
  13440. fetchData(request, (response) => {
  13441. const result = response.result;
  13442. pushLSPLogMessage(
  13443. "info",
  13444. `codeAction ${i18next.t("lsp.server.receive", {
  13445. ns: "logMessage",
  13446. })}`,
  13447. response
  13448. );
  13449. if (!result) return resolve(null);
  13450. const codeAction = {
  13451. actions: result.map((item) => ({
  13452. title: item.title,
  13453. kind: item.kind ? item.kind : "quickfix",
  13454. command: item.command
  13455. ? item.command.command
  13456. ? {
  13457. id: item.command.command,
  13458. arguments: item.command.arguments,
  13459. title: item.command.title,
  13460. }
  13461. : null
  13462. : null,
  13463. diagnostics: item.diagnostics
  13464. ? item.diagnostics.map((item) => ({
  13465. code: item.code,
  13466. message: item.message,
  13467. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  13468. severity: OJBetter_monaco.lspSeverityToMonacoSeverity(
  13469. item.severity
  13470. ),
  13471. source: item.source,
  13472. }))
  13473. : null,
  13474. edit: item.edit
  13475. ? OJBetter_monaco.lspEditToMonacoEdit(item.edit)
  13476. : item.arguments
  13477. ? {
  13478. edits: item.arguments.flatMap(
  13479. (item1) =>
  13480. OJBetter_monaco.lspEditToMonacoEdit(item1).edits
  13481. ),
  13482. }
  13483. : null,
  13484. })),
  13485. dispose: () => {},
  13486. };
  13487. pushLSPLogMessage(
  13488. "info",
  13489. `codeAction ${i18next.t("lsp.server.transmit", {
  13490. ns: "logMessage",
  13491. })}`,
  13492. codeAction
  13493. );
  13494.  
  13495. resolve(codeAction);
  13496. });
  13497. });
  13498. },
  13499. });
  13500.  
  13501. // 注册"hover提示"
  13502. monaco.languages.registerHoverProvider(language, {
  13503. provideHover: (model, position) => {
  13504. const request = {
  13505. jsonrpc: "2.0",
  13506. id: id++,
  13507. method: "textDocument/hover",
  13508. params: {
  13509. textDocument: {
  13510. uri: model.uri.toString(),
  13511. },
  13512. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  13513. },
  13514. };
  13515.  
  13516. return new Promise((resolve, reject) => {
  13517. fetchData(request, (response) => {
  13518. pushLSPLogMessage(
  13519. "info",
  13520. `Hover ${i18next.t("lsp.server.receive", { ns: "logMessage" })}`,
  13521. response
  13522. );
  13523. const result = response.result;
  13524.  
  13525. if (!result) return resolve(null);
  13526. const Hover = {
  13527. range: result.range
  13528. ? OJBetter_monaco.lspRangeToMonacoRange(result.range)
  13529. : new monaco.Range(
  13530. position.lineNumber,
  13531. position.column,
  13532. position.lineNumber,
  13533. position.column
  13534. ),
  13535. contents: Array.isArray(result.contents)
  13536. ? result.contents.map((item) => ({
  13537. value: item.value ? item.value : item,
  13538. }))
  13539. : [
  13540. {
  13541. value: result.contents.value,
  13542. },
  13543. ],
  13544. };
  13545. pushLSPLogMessage(
  13546. "info",
  13547. `Hover ${i18next.t("lsp.server.transmit", { ns: "logMessage" })}`,
  13548. Hover
  13549. );
  13550. resolve(Hover);
  13551. });
  13552. });
  13553. },
  13554. });
  13555.  
  13556. // 注册"inlay提示"
  13557. if (language == "cpp" || language == "java")
  13558. monaco.languages.registerInlayHintsProvider(language, {
  13559. provideInlayHints: (model, range, token) => {
  13560. return new Promise((resolve, reject) => {
  13561. const request = {
  13562. jsonrpc: "2.0",
  13563. id: id++,
  13564. method: "textDocument/inlayHint",
  13565. params: {
  13566. textDocument: {
  13567. uri: model.uri.toString(),
  13568. },
  13569. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  13570. },
  13571. };
  13572.  
  13573. fetchData(request, (response) => {
  13574. const result = response.result;
  13575. pushLSPLogMessage(
  13576. "info",
  13577. `Inlay Hints ${i18next.t("lsp.server.receive", {
  13578. ns: "logMessage",
  13579. })}`,
  13580. response
  13581. );
  13582.  
  13583. if (!result) return resolve(null);
  13584.  
  13585. const inlayHints = {
  13586. hints: result.map((item) => {
  13587. return {
  13588. kind: item.kind,
  13589. label: item.label,
  13590. paddingLeft: item.paddingLeft,
  13591. paddingRight: item.paddingRight,
  13592. position: {
  13593. lineNumber: item.position.line + 1,
  13594. column: item.position.character + 1,
  13595. },
  13596. };
  13597. }),
  13598. };
  13599. pushLSPLogMessage(
  13600. "info",
  13601. `Inlay Hints ${i18next.t("lsp.server.transmit", {
  13602. ns: "logMessage",
  13603. })}`,
  13604. inlayHints
  13605. );
  13606.  
  13607. resolve(inlayHints);
  13608. });
  13609. });
  13610. },
  13611. });
  13612.  
  13613. // 注册"转到定义"
  13614. monaco.languages.registerDefinitionProvider(language, {
  13615. provideDefinition: (model, position) => {
  13616. const request = {
  13617. jsonrpc: "2.0",
  13618. id: id++,
  13619. method: "textDocument/definition",
  13620. params: {
  13621. textDocument: {
  13622. uri: model.uri.toString(),
  13623. },
  13624. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  13625. },
  13626. };
  13627.  
  13628. return new Promise((resolve, reject) => {
  13629. fetchData(request, (response) => {
  13630. const result = response.result;
  13631. pushLSPLogMessage(
  13632. "info",
  13633. `definition ${i18next.t("lsp.server.receive", {
  13634. ns: "logMessage",
  13635. })}`,
  13636. response
  13637. );
  13638.  
  13639. if (result.length == 0) return resolve(null);
  13640. const definition = result.map((item) => ({
  13641. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  13642. uri: monaco.Uri.parse(item.uri), //
  13643. }));
  13644. pushLSPLogMessage(
  13645. "info",
  13646. `definition ${i18next.t("lsp.server.transmit", {
  13647. ns: "logMessage",
  13648. })}`,
  13649. definition
  13650. );
  13651.  
  13652. resolve(definition);
  13653. });
  13654. });
  13655. },
  13656. });
  13657.  
  13658. // 注册"转到引用"
  13659. monaco.languages.registerReferenceProvider(language, {
  13660. provideReferences: (model, position, context) => {
  13661. const request = {
  13662. jsonrpc: "2.0",
  13663. id: id++,
  13664. method: "textDocument/references",
  13665. params: {
  13666. context: context,
  13667. textDocument: {
  13668. uri: model.uri.toString(),
  13669. },
  13670. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  13671. },
  13672. };
  13673.  
  13674. return new Promise((resolve, reject) => {
  13675. fetchData(request, (response) => {
  13676. const result = response.result;
  13677. pushLSPLogMessage(
  13678. "info",
  13679. `references ${i18next.t("lsp.server.receive", {
  13680. ns: "logMessage",
  13681. })}`,
  13682. response
  13683. );
  13684.  
  13685. if (result.length == 0) return resolve([]);
  13686.  
  13687. const references = result.map((item) => ({
  13688. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  13689. uri: monaco.Uri.parse(item.uri), //
  13690. }));
  13691. pushLSPLogMessage(
  13692. "info",
  13693. `references ${i18next.t("lsp.server.transmit", {
  13694. ns: "logMessage",
  13695. })}`,
  13696. references
  13697. );
  13698. resolve(references);
  13699. });
  13700. });
  13701. },
  13702. });
  13703.  
  13704. // 注册"符号引用点击高亮"
  13705. monaco.languages.registerDocumentHighlightProvider(language, {
  13706. provideDocumentHighlights: (model, position) => {
  13707. const request = {
  13708. jsonrpc: "2.0",
  13709. id: id++,
  13710. method: "textDocument/documentHighlight",
  13711. params: {
  13712. textDocument: {
  13713. uri: model.uri.toString(),
  13714. },
  13715. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  13716. },
  13717. };
  13718.  
  13719. return new Promise((resolve, reject) => {
  13720. fetchData(request, (response) => {
  13721. const result = response.result;
  13722. pushLSPLogMessage(
  13723. "info",
  13724. `documentHighlight ${i18next.t("lsp.server.receive", {
  13725. ns: "logMessage",
  13726. })}`,
  13727. response
  13728. );
  13729.  
  13730. if (!result || result.length == 0) return resolve([]);
  13731. const highlights = result.map((item) => ({
  13732. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  13733. kind: item.kind,
  13734. }));
  13735. pushLSPLogMessage(
  13736. "info",
  13737. `documentHighlight ${i18next.t("lsp.server.transmit", {
  13738. ns: "logMessage",
  13739. })}`,
  13740. highlights
  13741. );
  13742.  
  13743. resolve(highlights);
  13744. });
  13745. });
  13746. },
  13747. });
  13748.  
  13749. // 注册"文件链接"
  13750. if (language == "cpp" || language == "java")
  13751. monaco.languages.registerLinkProvider(language, {
  13752. provideLinks: (model) => {
  13753. const request = {
  13754. jsonrpc: "2.0",
  13755. id: id++,
  13756. method: "textDocument/documentLink",
  13757. params: {
  13758. textDocument: {
  13759. uri: model.uri.toString(),
  13760. },
  13761. },
  13762. };
  13763.  
  13764. return new Promise((resolve, reject) => {
  13765. fetchData(request, (response) => {
  13766. const result = response.result;
  13767. pushLSPLogMessage(
  13768. "info",
  13769. `DocumentLink ${i18next.t("lsp.server.receive", {
  13770. ns: "logMessage",
  13771. })}`,
  13772. response
  13773. );
  13774.  
  13775. if (!result) return resolve(null);
  13776. const links = {
  13777. links: result.map((item) => ({
  13778. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  13779. url: item.target.toString(),
  13780. tooltip: item.tooltip ? item.tooltip : null,
  13781. })),
  13782. };
  13783. pushLSPLogMessage(
  13784. "info",
  13785. `DocumentLink ${i18next.t("lsp.server.transmit", {
  13786. ns: "logMessage",
  13787. })}`,
  13788. links
  13789. );
  13790. resolve(links);
  13791. });
  13792. });
  13793. },
  13794. });
  13795.  
  13796. // 注册"格式化"
  13797. monaco.languages.registerDocumentFormattingEditProvider(language, {
  13798. provideDocumentFormattingEdits: (model, options, token) => {
  13799. const request = {
  13800. jsonrpc: "2.0",
  13801. id: id++,
  13802. method: "textDocument/formatting",
  13803. params: {
  13804. textDocument: {
  13805. uri: model.uri.toString(),
  13806. },
  13807. options: options,
  13808. },
  13809. };
  13810.  
  13811. return new Promise((resolve, reject) => {
  13812. fetchData(request, (response) => {
  13813. const result = response.result;
  13814. pushLSPLogMessage(
  13815. "info",
  13816. `formatting ${i18next.t("lsp.server.receive", {
  13817. ns: "logMessage",
  13818. })}`,
  13819. response
  13820. );
  13821.  
  13822. const TextEdit = result.map((edit) => ({
  13823. range: OJBetter_monaco.lspRangeToMonacoRange(edit.range),
  13824. text: edit.newText,
  13825. }));
  13826. pushLSPLogMessage(
  13827. "info",
  13828. `formatting ${i18next.t("lsp.server.transmit", {
  13829. ns: "logMessage",
  13830. })}`,
  13831. TextEdit
  13832. );
  13833. resolve(TextEdit);
  13834. });
  13835. });
  13836. },
  13837. });
  13838.  
  13839. // 注册"部分格式化"
  13840. monaco.languages.registerDocumentRangeFormattingEditProvider(language, {
  13841. provideDocumentRangeFormattingEdits: (model, range, options) => {
  13842. const request = {
  13843. jsonrpc: "2.0",
  13844. id: id++,
  13845. method: "textDocument/rangeFormatting",
  13846. params: {
  13847. textDocument: {
  13848. uri: model.uri.toString(),
  13849. },
  13850. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  13851. options,
  13852. },
  13853. };
  13854.  
  13855. return new Promise((resolve, reject) => {
  13856. fetchData(request, (response) => {
  13857. const result = response.result;
  13858. pushLSPLogMessage(
  13859. "info",
  13860. `rangeFormatting ${i18next.t("lsp.server.receive", {
  13861. ns: "logMessage",
  13862. })}`,
  13863. response
  13864. );
  13865.  
  13866. if (!result || result.length == 0) return resolve([]);
  13867. const edits = result.map((item) => ({
  13868. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  13869. text: item.newText,
  13870. }));
  13871. pushLSPLogMessage(
  13872. "info",
  13873. `rangeFormatting ${i18next.t("lsp.server.transmit", {
  13874. ns: "logMessage",
  13875. })}`,
  13876. edits
  13877. );
  13878. resolve(edits);
  13879. });
  13880. });
  13881. },
  13882. });
  13883.  
  13884. // 注册"重命名"
  13885. monaco.languages.registerRenameProvider(language, {
  13886. provideRenameEdits: (model, position, newName, token) => {
  13887. const request = {
  13888. jsonrpc: "2.0",
  13889. id: id++,
  13890. method: "textDocument/rename",
  13891. params: {
  13892. textDocument: {
  13893. uri: model.uri.toString(),
  13894. },
  13895. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  13896. newName: newName,
  13897. },
  13898. };
  13899.  
  13900. return new Promise((resolve, reject) => {
  13901. fetchData(request, (response) => {
  13902. const result = response.result;
  13903. pushLSPLogMessage(
  13904. "info",
  13905. `rename ${i18next.t("lsp.server.receive", { ns: "logMessage" })}`,
  13906. response
  13907. );
  13908.  
  13909. const rename = OJBetter_monaco.lspEditToMonacoEdit(result);
  13910. pushLSPLogMessage(
  13911. "info",
  13912. `rename ${i18next.t("lsp.server.transmit", {
  13913. ns: "logMessage",
  13914. })}`,
  13915. rename
  13916. );
  13917. resolve(rename);
  13918. });
  13919. });
  13920. },
  13921. });
  13922.  
  13923. // 注册"折叠范围分析"
  13924. monaco.languages.registerFoldingRangeProvider(language, {
  13925. provideFoldingRanges: (model) => {
  13926. const request = {
  13927. jsonrpc: "2.0",
  13928. id: id++,
  13929. method: "textDocument/foldingRange",
  13930. params: {
  13931. textDocument: {
  13932. uri: model.uri.toString(),
  13933. },
  13934. },
  13935. };
  13936.  
  13937. return new Promise((resolve, reject) => {
  13938. fetchData(request, (response) => {
  13939. const result = response.result;
  13940. pushLSPLogMessage(
  13941. "info",
  13942. `FoldingRange ${i18next.t("lsp.server.receive", {
  13943. ns: "logMessage",
  13944. })}`,
  13945. response
  13946. );
  13947.  
  13948. if (!result) return resolve([]);
  13949. const foldingRanges = result.map((item) => ({
  13950. start: item.startLine + 1,
  13951. end: item.endLine + 1,
  13952. kind: monaco.languages.FoldingRangeKind.fromValue(item.kind),
  13953. }));
  13954. pushLSPLogMessage(
  13955. "info",
  13956. `FoldingRange ${i18next.t("lsp.server.transmit", {
  13957. ns: "logMessage",
  13958. })}`,
  13959. foldingRanges
  13960. );
  13961. resolve(foldingRanges);
  13962. });
  13963. });
  13964. },
  13965. });
  13966.  
  13967. // 注册"方法签名提示"
  13968. monaco.languages.registerSignatureHelpProvider(language, {
  13969. signatureHelpTriggerCharacters:
  13970. serverInfo.capabilities.signatureHelpProvider.triggerCharacters,
  13971. provideSignatureHelp: (model, position, token, context) => {
  13972. const request = {
  13973. jsonrpc: "2.0",
  13974. id: id++,
  13975. method: "textDocument/signatureHelp",
  13976. params: {
  13977. textDocument: {
  13978. uri: model.uri.toString(),
  13979. },
  13980. position: {
  13981. line: position.lineNumber - 1,
  13982. character: position.column - 1,
  13983. },
  13984. context: {
  13985. triggerKind: context.triggerKind,
  13986. triggerCharacter: context.triggerCharacter,
  13987. isRetrigger: context.isRetrigger,
  13988. activeSignatureHelp: context.activeSignatureHelp,
  13989. },
  13990. },
  13991. };
  13992.  
  13993. return new Promise((resolve, reject) => {
  13994. fetchData(request, (response) => {
  13995. const result = response.result;
  13996.  
  13997. pushLSPLogMessage(
  13998. "info",
  13999. `signatureHelp ${i18next.t("lsp.server.receive", {
  14000. ns: "logMessage",
  14001. })}`,
  14002. response
  14003. );
  14004.  
  14005. if (!result) return resolve(null);
  14006. const SignatureHelpResult = {
  14007. value: {
  14008. activeParameter: result.activeParameter,
  14009. activeSignature: result.activeSignature,
  14010. signatures: result.signatures,
  14011. },
  14012. dispose: () => {},
  14013. };
  14014.  
  14015. pushLSPLogMessage(
  14016. "info",
  14017. `signatureHelp ${i18next.t("lsp.server.transmit", {
  14018. ns: "logMessage",
  14019. })}`,
  14020. SignatureHelpResult
  14021. );
  14022. resolve(SignatureHelpResult);
  14023. });
  14024. });
  14025. },
  14026. });
  14027.  
  14028. // 注册"渐进式自动格式化" 如果server有这个
  14029. if (serverInfo.capabilities.documentOnTypeFormattingProvider)
  14030. monaco.languages.registerOnTypeFormattingEditProvider(language, {
  14031. autoFormatTriggerCharacters: [
  14032. serverInfo.capabilities.documentOnTypeFormattingProvider
  14033. .firstTriggerCharacter,
  14034. ],
  14035. provideOnTypeFormattingEdits: (model, position, ch, options) => {
  14036. const request = {
  14037. jsonrpc: "2.0",
  14038. id: id++,
  14039. method: "textDocument/onTypeFormatting",
  14040. params: {
  14041. textDocument: {
  14042. uri: model.uri.toString(),
  14043. },
  14044. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  14045. ch,
  14046. options,
  14047. },
  14048. };
  14049.  
  14050. return new Promise((resolve, reject) => {
  14051. fetchData(request, (response) => {
  14052. const result = response.result;
  14053. pushLSPLogMessage(
  14054. "info",
  14055. `onTypeFormatting ${i18next.t("lsp.server.receive", {
  14056. ns: "logMessage",
  14057. })}`,
  14058. response
  14059. );
  14060.  
  14061. if (!result || result.length == 0) return resolve([]);
  14062.  
  14063. const edits = result.map((item) => ({
  14064. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  14065. text: item.newText,
  14066. }));
  14067. pushLSPLogMessage(
  14068. "info",
  14069. `onTypeFormatting ${i18next.t("lsp.server.transmit", {
  14070. ns: "logMessage",
  14071. })}`,
  14072. edits
  14073. );
  14074. resolve(edits);
  14075. });
  14076. });
  14077. },
  14078. });
  14079. };
  14080.  
  14081. /**
  14082. * 被动式接收处理
  14083. */
  14084. OJBetter_monaco.PassiveReceiveHandler = () => {
  14085. // "实时代码诊断"
  14086. OJBetter_monaco.updateMarkers = function (message) {
  14087. const params = message.params;
  14088. pushLSPLogMessage(
  14089. "info",
  14090. `Markers ${i18next.t("lsp.server.receive", { ns: "logMessage" })}`,
  14091. message
  14092. );
  14093.  
  14094. if (!params) return;
  14095. const markers = params.diagnostics.map((item1) => ({
  14096. code: item1.code,
  14097. message: item1.message,
  14098. ...OJBetter_monaco.lspRangeToMonacoRange(item1.range),
  14099. relatedInformation: item1.relatedInformation
  14100. ? item1.relatedInformation.map((item2) => ({
  14101. ...(item2.location.range
  14102. ? OJBetter_monaco.lspRangeToMonacoRange(item2.location.range)
  14103. : OJBetter_monaco.lspRangeToMonacoRange(item2.location)),
  14104. message: item2.message,
  14105. resource: monaco.Uri.parse(item2.location.uri),
  14106. }))
  14107. : null,
  14108. severity: OJBetter_monaco.lspSeverityToMonacoSeverity(item1.severity),
  14109. source: item1.source,
  14110. }));
  14111.  
  14112. pushLSPLogMessage(
  14113. "info",
  14114. `Markers ${i18next.t("lsp.server.transmit", { ns: "logMessage" })}`,
  14115. markers
  14116. );
  14117. monaco.editor.setModelMarkers(model, "eslint", markers);
  14118.  
  14119. // 更新状态底栏信息
  14120. const nowMarks = monaco.editor.getModelMarkers();
  14121. warningCount = 0;
  14122. errorCount = 0;
  14123. for (const marker of nowMarks) {
  14124. if (marker.severity === monaco.MarkerSeverity.Warning) {
  14125. warningCount++;
  14126. } else if (marker.severity === monaco.MarkerSeverity.Error) {
  14127. errorCount++;
  14128. }
  14129. }
  14130. $("#OJBetter_statusBar").text(
  14131. `Warnings: ${warningCount}, Errors: ${errorCount}`
  14132. );
  14133. };
  14134.  
  14135. // "应用服务器推送的更改"(代码修复)
  14136. OJBetter_monaco.applyEdit = function (message) {
  14137. const params = message.params;
  14138. pushLSPLogMessage(
  14139. "info",
  14140. `applyEdit ${i18next.t("lsp.server.receive", { ns: "logMessage" })}`,
  14141. message
  14142. );
  14143.  
  14144. if (!params) return;
  14145. const operations = Object.values(params.edit.changes)
  14146. .flat()
  14147. .map((item) => ({
  14148. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  14149. text: item.newText,
  14150. }));
  14151.  
  14152. pushLSPLogMessage(
  14153. "info",
  14154. `applyEdit ${i18next.t("lsp.server.transmit", { ns: "logMessage" })}`,
  14155. operations
  14156. );
  14157. model.pushEditOperations([], operations, () => null); // 入栈编辑操作
  14158. };
  14159. };
  14160.  
  14161. if (!languageSocketState) await waitForLanguageSocketState();
  14162. OJBetter_monaco.Initialize();
  14163. }
  14164.  
  14165. // 语言更改
  14166. function changeMonacoLanguage(form) {
  14167. let nowSelect = form.selectLang.val();
  14168. // 记忆更改
  14169. GM_setValue("compilerSelection", nowSelect);
  14170. // 销毁旧的编辑器
  14171. try {
  14172. if (OJBetter.monaco.editor) OJBetter.monaco.editor.dispose();
  14173. } catch (error) {
  14174. console.warn(
  14175. "Encountered an error while attempting to delete the old editor, but it should not affect your regular usage.",
  14176. error
  14177. );
  14178. }
  14179. // 关闭旧的socket
  14180. OJBetter.monaco.lsp.socket.forEach((socket) => {
  14181. socket.close();
  14182. });
  14183. // 移除相关元素
  14184. form.topRightDiv.empty();
  14185. $("#LSPLog").remove();
  14186. $("#OJBetter_statusBar").remove();
  14187. // 创建新的编辑器
  14188. if (nowSelect in value_monacoLanguageMap) {
  14189. let language = value_monacoLanguageMap[nowSelect];
  14190. if (language == "python" || language == "cpp") {
  14191. createMonacoEditor(language, form, true);
  14192. } else {
  14193. createMonacoEditor(language, form, false);
  14194. }
  14195. } else {
  14196. createMonacoEditor(null, form, false);
  14197. }
  14198. // 更新在线编译器参数
  14199. changeCompilerArgs(nowSelect);
  14200. }
  14201.  
  14202. // 收集样例数据
  14203. function getTestData() {
  14204. let testData = {};
  14205.  
  14206. /**
  14207. * 从pre中获取文本信息
  14208. * @param {JQuery<HTMLElement>} node 元素
  14209. * @returns {string} 文本信息
  14210. */
  14211. function getTextFromPre(node) {
  14212. let text;
  14213. if (node.find("br").length > 0) {
  14214. text = node.html().replace(/<br>/g, "\n"); // <br>作换行符的情况
  14215. } else {
  14216. text = node.text();
  14217. }
  14218. return text;
  14219. }
  14220.  
  14221. $(".input").each(function (index) {
  14222. var inputText = "";
  14223. if ($(this).find("pre").find("div").length > 0) {
  14224. $(this)
  14225. .find("pre")
  14226. .find("div")
  14227. .each(function () {
  14228. inputText += getTextFromPre($(this)) + "\n";
  14229. });
  14230. } else {
  14231. inputText = getTextFromPre($(this).find("pre"));
  14232. }
  14233. var outputText = "";
  14234. if ($(".output").eq(index).find("pre").find("div").length > 0) {
  14235. $(".output")
  14236. .eq(index)
  14237. .find("pre")
  14238. .find("div")
  14239. .each(function () {
  14240. inputText += getTextFromPre($(this)) + "\n";
  14241. });
  14242. } else {
  14243. outputText = getTextFromPre($(".output").eq(index).find("pre"));
  14244. }
  14245.  
  14246. testData[index + 1] = {
  14247. input: inputText.trim(),
  14248. output: outputText.trim(),
  14249. };
  14250. });
  14251. return testData;
  14252. }
  14253.  
  14254. // 初始化自定义测试数据面板
  14255. function CustomTestInit() {
  14256. const url = window.location.href;
  14257.  
  14258. restoreText();
  14259.  
  14260. // 添加
  14261. $("#addCustomTest").click(function () {
  14262. var sampleDiv = $('<div class="sampleDiv">');
  14263. var inputTextarea = $(
  14264. '<p style="padding: 0px 5px;">input</p><textarea class="dynamicTextarea inputTextarea"></textarea>'
  14265. );
  14266. var outputTextarea = $(
  14267. '<p style="padding: 0px 5px;">output</p><textarea class="dynamicTextarea outputTextarea"></textarea>'
  14268. );
  14269. var deleteCustomTest = OJB_safeCreateJQElement(
  14270. `<button type="button" class="deleteCustomTest">${closeIcon}</button>`
  14271. );
  14272. sampleDiv.append(deleteCustomTest);
  14273. sampleDiv.append(inputTextarea);
  14274. sampleDiv.append(outputTextarea);
  14275. $("#customTests").append(sampleDiv);
  14276. });
  14277.  
  14278. // 实时保存文本内容到 IndexedDB 中
  14279. $(document).on("input", ".inputTextarea, .outputTextarea", function () {
  14280. OJBetter.common.database.transaction(
  14281. "rw",
  14282. OJBetter.common.database.samplesData,
  14283. function () {
  14284. var objectStore = OJBetter.common.database.samplesData;
  14285. var samples = {
  14286. url: url,
  14287. samples: [],
  14288. };
  14289. var index = 0;
  14290. $(".sampleDiv").each(function () {
  14291. var $sampleDiv = $(this);
  14292. var inputTextarea = $sampleDiv.find(".inputTextarea");
  14293. var outputTextarea = $sampleDiv.find(".outputTextarea");
  14294. $sampleDiv.attr("data-index", index);
  14295. inputTextarea.attr("id", "input" + index);
  14296. outputTextarea.attr("id", "output" + index);
  14297. var sample = {
  14298. id: index,
  14299. input: inputTextarea.val(),
  14300. output: outputTextarea.val(),
  14301. };
  14302. samples.samples.push(sample);
  14303. index++;
  14304. });
  14305. objectStore.put(samples);
  14306. }
  14307. );
  14308. });
  14309.  
  14310. // 删除
  14311. $(document).on("click", ".deleteCustomTest", function () {
  14312. var $sampleDiv = $(this).closest(".sampleDiv");
  14313. OJBetter.common.database.transaction(
  14314. "rw",
  14315. OJBetter.common.database.samplesData,
  14316. function () {
  14317. var objectStore = OJBetter.common.database.samplesData;
  14318. var index = parseInt($sampleDiv.attr("data-index"));
  14319. if (!isNaN(index)) {
  14320. objectStore.get(url).then((row) => {
  14321. let samples = row.samples;
  14322. samples.splice(index, 1); // 移除第index个元素
  14323. objectStore.put({
  14324. url: url,
  14325. samples: samples,
  14326. });
  14327. });
  14328. }
  14329. $sampleDiv.remove();
  14330. }
  14331. );
  14332. });
  14333.  
  14334. // 恢复保存的内容
  14335. function restoreText() {
  14336. OJBetter.common.database
  14337. .transaction("r", OJBetter.common.database.samplesData, function () {
  14338. return OJBetter.common.database.samplesData.get(url);
  14339. })
  14340. .then(function (data) {
  14341. if (data.samples && data.samples.length > 0) {
  14342. data.samples.forEach(function (item, index) {
  14343. var sampleDiv = $('<div class="sampleDiv">');
  14344. var inputTextarea = OJB_safeCreateJQElement(
  14345. `<p style="padding: 0px 5px;">input</p><textarea id="input${index}" class="dynamicTextarea inputTextarea"></textarea>`
  14346. );
  14347. var outputTextarea = OJB_safeCreateJQElement(
  14348. `<p style="padding: 0px 5px;">output</p><textarea id="output${index}" class="dynamicTextarea outputTextarea"></textarea>`
  14349. );
  14350. var deleteCustomTest = OJB_safeCreateJQElement(
  14351. `<button type="button" class="deleteCustomTest">${closeIcon}</button>`
  14352. );
  14353.  
  14354. inputTextarea.val(item.input);
  14355. outputTextarea.val(item.output);
  14356.  
  14357. sampleDiv.append(deleteCustomTest);
  14358. sampleDiv.append(inputTextarea);
  14359. sampleDiv.append(outputTextarea);
  14360. sampleDiv.attr("data-index", index);
  14361. $("#customTests").append(sampleDiv);
  14362. });
  14363. }
  14364. });
  14365. }
  14366. }
  14367.  
  14368. // 获取自定义测试数据
  14369. function getCustomTestData() {
  14370. const url = window.location.href;
  14371.  
  14372. return new Promise(function (resolve) {
  14373. var customTestData = {};
  14374. OJBetter.common.database
  14375. .transaction("r", OJBetter.common.database.samplesData, function () {
  14376. return OJBetter.common.database.samplesData.get(url);
  14377. })
  14378. .then(function (data) {
  14379. if (!data) resolve(customTestData);
  14380. if (data.samples && data.samples.length > 0) {
  14381. data.samples.forEach(function (item, index) {
  14382. customTestData[index + 1] = {
  14383. input: item.input,
  14384. output: item.output,
  14385. };
  14386. });
  14387. }
  14388. resolve(customTestData);
  14389. });
  14390. });
  14391. }
  14392.  
  14393. // codeforces编译器参数列表
  14394. let officialLanguage = "";
  14395. function officialCompilerArgsChange(nowSelect) {
  14396. officialLanguage = nowSelect;
  14397. $("#CompilerArgsInput").prop("disabled", true);
  14398. }
  14399.  
  14400. // codeforces编译器通信
  14401. async function officialCompiler(code, input) {
  14402. const data = new FormData();
  14403. data.append("csrf_token", OJBetter.common.cf_csrf_token);
  14404. data.append("source", code);
  14405. data.append("tabSize", "4");
  14406. data.append("programTypeId", officialLanguage);
  14407. data.append("input", input);
  14408. data.append("output", "");
  14409. data.append("communityCode", "");
  14410. data.append("action", "submitSourceCode");
  14411. data.append("programTypeId", officialLanguage);
  14412. data.append("sourceCode", code);
  14413.  
  14414. const requestOptions = {
  14415. method: "POST",
  14416. url: `${OJBetter.common.hostAddress}/data/customtest`,
  14417. data: data,
  14418. headers: {
  14419. "X-Csrf-Token": OJBetter.common.cf_csrf_token,
  14420. },
  14421. };
  14422.  
  14423. const result = {
  14424. Errors: "",
  14425. Result: "",
  14426. Stats: "",
  14427. };
  14428.  
  14429. try {
  14430. const submitResponse = await OJB_GMRequest(requestOptions);
  14431. if (submitResponse.status !== 200 || !submitResponse.response) {
  14432. result.Errors = `${i18next.t("compiler.official.pushError", {
  14433. ns: "codeEditor",
  14434. })}`;
  14435. return result;
  14436. }
  14437.  
  14438. const submitResult = JSON.parse(submitResponse.response);
  14439. const customTestSubmitId = submitResult.customTestSubmitId;
  14440.  
  14441. const verdictResponse = await OJB_promiseRetryWrapper(
  14442. getOfficialCompilerVerdict,
  14443. {
  14444. maxRetries: 10,
  14445. retryInterval: 500,
  14446. },
  14447. customTestSubmitId
  14448. );
  14449. return verdictResponse;
  14450. } catch (error) {
  14451. result.Errors = error.message;
  14452. return result;
  14453. }
  14454. }
  14455.  
  14456. // 获取codeforces编译器的执行结果
  14457. async function getOfficialCompilerVerdict(customTestSubmitId) {
  14458. const newdata = new FormData();
  14459. newdata.append("csrf_token", OJBetter.common.cf_csrf_token);
  14460. newdata.append("action", "getVerdict");
  14461. newdata.append("customTestSubmitId", customTestSubmitId);
  14462.  
  14463. const requestOptions = {
  14464. method: "POST",
  14465. url: `${OJBetter.common.hostAddress}/data/customtest`,
  14466. data: newdata,
  14467. headers: {
  14468. "X-Csrf-Token": OJBetter.common.cf_csrf_token,
  14469. },
  14470. };
  14471.  
  14472. const responseDetails = await OJB_GMRequest(requestOptions);
  14473. if (responseDetails.status !== 200 || !responseDetails.response) {
  14474. throw new Error(
  14475. `${i18next.t("compiler.official.getResultError", { ns: "codeEditor" })}`
  14476. );
  14477. }
  14478.  
  14479. const response = JSON.parse(responseDetails.response);
  14480. if (!response.stat) {
  14481. throw new Error("Verdict not ready, retrying...");
  14482. }
  14483.  
  14484. return {
  14485. Errors:
  14486. response.verdict === "OK"
  14487. ? null
  14488. : response.verdict + "<br>" + response.output,
  14489. Result: response.output.replace(/\r\n/g, "\n"),
  14490. Stats: `Status: ${response.stat}`,
  14491. };
  14492. }
  14493.  
  14494. // rextester编译器参数列表
  14495. let rextesterLanguage = "";
  14496. function rextesterCompilerArgsChange(nowSelect) {
  14497. let LanguageChoiceList = {
  14498. 4: "9",
  14499. 6: "8",
  14500. 7: "5",
  14501. 9: "1",
  14502. 13: "13",
  14503. 19: "42",
  14504. 20: "21",
  14505. 28: "30",
  14506. 31: "24",
  14507. 32: "20",
  14508. 34: "17",
  14509. 36: "4",
  14510. 43: "6",
  14511. 45: "7",
  14512. 46: "4",
  14513. 50: "7",
  14514. 51: "9",
  14515. 52: "27",
  14516. 54: "7",
  14517. 55: "23",
  14518. 60: "4",
  14519. 61: "7",
  14520. 65: "1",
  14521. 67: "12",
  14522. 70: "5",
  14523. 73: "7",
  14524. 74: "4",
  14525. 75: "46",
  14526. 77: "43",
  14527. 79: "1",
  14528. 80: "27",
  14529. 83: "43",
  14530. 87: "4",
  14531. };
  14532. let CompilerArgsList = {
  14533. 6: "-Wall -std=gnu99 -O2 -o a.out source_file.c",
  14534. 7: "-Wall -std=c++14 -O2 -o a.out source_file.cpp",
  14535. 20: "-o a.out source_file.go",
  14536. 27: "-Wall -std=c++14 -stdlib=libc++ -O2 -o a.out source_file.cpp",
  14537. 30: "source_file.d -ofa.out",
  14538. };
  14539. if (nowSelect in LanguageChoiceList) {
  14540. $("#RunTestButton").prop("disabled", false);
  14541. rextesterLanguage = LanguageChoiceList[nowSelect];
  14542. } else {
  14543. $("#RunTestButton").prop("disabled", true);
  14544. }
  14545. if (rextesterLanguage in CompilerArgsList) {
  14546. const rextesterCompilerArgs = CompilerArgsList[rextesterLanguage];
  14547. $("#CompilerArgsInput").val(rextesterCompilerArgs);
  14548. } else {
  14549. $("#CompilerArgsInput").val("");
  14550. }
  14551. }
  14552.  
  14553. // rextester编译器通信
  14554. async function rextesterCompiler(code, input) {
  14555. try {
  14556. const result = await OJB_promiseRetryWrapper(
  14557. rextesterCompilerRequest,
  14558. {
  14559. maxRetries: 5,
  14560. retryInterval: 500,
  14561. errorHandler: (err) => ({ Errors: err.message, Result: "", Stats: "" }),
  14562. },
  14563. code,
  14564. input
  14565. );
  14566. return result;
  14567. } catch (error) {
  14568. return { Errors: error.message, Result: "", Stats: "" };
  14569. }
  14570. }
  14571.  
  14572. // rextester编译器请求方法
  14573. async function rextesterCompilerRequest(code, input) {
  14574. const data = new FormData();
  14575. data.append("LanguageChoiceWrapper", rextesterLanguage);
  14576. data.append("EditorChoiceWrapper", "1");
  14577. data.append("LayoutChoiceWrapper", "1");
  14578. data.append("Program", code);
  14579. data.append("CompilerArgs", $("#CompilerArgsInput").val());
  14580. data.append("Input", input);
  14581. data.append("ShowWarnings", "false");
  14582. data.append("IsInEditMode", "false");
  14583. data.append("IsLive", "false");
  14584.  
  14585. const responseDetails = await OJB_GMRequest({
  14586. method: "POST",
  14587. url: "https://rextester.com/rundotnet/Run",
  14588. data: data,
  14589. });
  14590.  
  14591. if (responseDetails.status !== 200 || !responseDetails.response) {
  14592. throw new Error(
  14593. `${i18next.t("compiler.common.error", { ns: "codeEditor" })}`
  14594. );
  14595. }
  14596.  
  14597. const response = JSON.parse(responseDetails.response);
  14598. return {
  14599. Errors: response.Errors || "",
  14600. Result: response.Result || "",
  14601. Stats: response.Stats || "",
  14602. };
  14603. }
  14604.  
  14605. // wandbox编译器参数列表
  14606. function wandboxCompilerArgsChange(nowSelect) {
  14607. let LanguageChoiceList = {
  14608. 6: "PHP",
  14609. 7: "Python",
  14610. 9: "C#",
  14611. 12: "Haskell",
  14612. 13: "Perl",
  14613. 19: "OCaml",
  14614. 20: "Scala",
  14615. 28: "D",
  14616. 31: "Python",
  14617. 32: "Go",
  14618. 34: "JavaScript",
  14619. 36: "Java",
  14620. 40: "Python",
  14621. 41: "Python",
  14622. 43: "C++",
  14623. 50: "C++",
  14624. 51: "Pascal",
  14625. 52: "C++",
  14626. 54: "C++",
  14627. 60: "Java",
  14628. 61: "C++",
  14629. 65: "C#",
  14630. 67: "Ruby",
  14631. 70: "Python",
  14632. 73: "C++",
  14633. 74: "Java",
  14634. 75: "Rust",
  14635. 79: "C#",
  14636. 80: "C++",
  14637. 87: "Java",
  14638. };
  14639.  
  14640. // 移除旧的
  14641. $("#CompilerChange").remove();
  14642.  
  14643. if (nowSelect in LanguageChoiceList) {
  14644. $("#RunTestButton").prop("disabled", false);
  14645. const wandboxlist = JSON.parse(GM_getResourceText("wandboxlist"));
  14646. const Languagefiltered = wandboxlist.filter(
  14647. (obj) => obj.language === LanguageChoiceList[nowSelect]
  14648. );
  14649.  
  14650. // 创建编译器下拉框
  14651. let CompilerChange = $(
  14652. '<select id="CompilerChange" style="width: 100%;"></select>'
  14653. );
  14654.  
  14655. $("#CompilerSetting").show().append(CompilerChange);
  14656. for (let i = 0; i < Languagefiltered.length; i++) {
  14657. let Compiler = Languagefiltered[i];
  14658. let op = $("<option></option>")
  14659. .val(Compiler.name)
  14660. .text(Compiler["display-name"] + " " + Compiler.version);
  14661. $("#CompilerChange").append(op);
  14662. }
  14663.  
  14664. // 编译器参数刷新
  14665. function refreshCompilerArgs() {
  14666. var flags = "";
  14667. $("#CompilerBox")
  14668. .find("*")
  14669. .each(function () {
  14670. if ($(this).is("input[type='checkbox']")) {
  14671. let flag = $(this).prop("checked") ? $(this).val() : "";
  14672. flags += flag + (flag ? " " : "");
  14673. } else if (
  14674. $(this).is("select") ||
  14675. $(this).is("input") ||
  14676. $(this).is("textarea")
  14677. ) {
  14678. let flag = $(this).val();
  14679. flags += flag + (flag ? " " : "");
  14680. }
  14681. });
  14682. $("#CompilerArgsInput").val(flags);
  14683. $("#CompilerArgsInput").prop("readonly", true); // 只读
  14684. }
  14685.  
  14686. // 编译器切换监听
  14687. CompilerChange.change(function () {
  14688. let selectedName = CompilerChange.val();
  14689. let Compiler = Languagefiltered.find((obj) => obj.name === selectedName);
  14690.  
  14691. $("#CompilerArgsInput").val(); // 初始化编译器输入框
  14692.  
  14693. $("#CompilerBox").remove();
  14694. let div = $("<div id='CompilerBox'></div>");
  14695.  
  14696. let display_compile_command = OJB_safeCreateJQElement(
  14697. `<input id='${Compiler.name}' value='${Compiler["display-compile-command"]}' style="display:none;"}></input>`
  14698. );
  14699. div.append(display_compile_command);
  14700.  
  14701. let switches = Compiler.switches;
  14702. for (let i = 0; i < switches.length; i++) {
  14703. let switche = switches[i];
  14704.  
  14705. if (switche.type == "single") {
  14706. let single = OJB_safeCreateJQElement(`
  14707. <div>
  14708. <input type='checkbox' id='${switche.name}' value='${
  14709. switche["display-flags"]
  14710. }' ${switche.default ? "checked" : ""}></input>
  14711. <label for='${switche.name}'>${
  14712. switche["display-name"]
  14713. }</label>
  14714. </div>
  14715. `);
  14716. div.append(single);
  14717. single.find("input").change(function () {
  14718. refreshCompilerArgs();
  14719. });
  14720. } else if (switche.type == "select") {
  14721. let select = OJB_safeCreateJQElement(
  14722. `<select id='${switche.name}'></select>`
  14723. );
  14724. select.data("previousValue", switche.options[0]["display-flags"]);
  14725. div.append(select);
  14726. for (let i = 0; i < switche.options.length; i++) {
  14727. let option = switche.options[i];
  14728. let op = $("<option></option>")
  14729. .val(option["display-flags"])
  14730. .text(option["display-name"]);
  14731. select.append(op);
  14732. }
  14733. select.change(function () {
  14734. refreshCompilerArgs();
  14735. });
  14736. }
  14737. }
  14738.  
  14739. if (Compiler["compiler-option-raw"] == true) {
  14740. let textarea = OJB_safeCreateJQElement(
  14741. `<textarea id="compiler_option_raw" placeholder="Raw compiler options" style="resize: vertical;"></textarea>`
  14742. );
  14743. div.append(textarea);
  14744. textarea.on("input", function () {
  14745. refreshCompilerArgs();
  14746. });
  14747. }
  14748. if (Compiler["runtime-option-raw"] == true) {
  14749. let textarea = OJB_safeCreateJQElement(
  14750. `<textarea id="runtime_option_raw" placeholder="Raw runtime options" style="resize: vertical;"></textarea>`
  14751. );
  14752. div.append(textarea);
  14753. textarea.on("input", function () {
  14754. refreshCompilerArgs();
  14755. });
  14756. }
  14757. $("#CompilerSetting").append(div);
  14758.  
  14759. refreshCompilerArgs(); // 初始化
  14760. });
  14761.  
  14762. CompilerChange.trigger("change"); // 初始化
  14763. } else {
  14764. $("#RunTestButton").prop("disabled", true);
  14765. }
  14766. }
  14767.  
  14768. // wandbox编译器通信
  14769. async function wandboxCompiler(code, input) {
  14770. try {
  14771. const result = await OJB_promiseRetryWrapper(
  14772. wandboxCompilerRequest,
  14773. {
  14774. maxRetries: 5,
  14775. retryInterval: 500,
  14776. errorHandler: (err) => ({ Errors: err.message, Result: "", Stats: "" }),
  14777. },
  14778. code,
  14779. input
  14780. );
  14781. return result;
  14782. } catch (error) {
  14783. return { Errors: error.message, Result: "", Stats: "" };
  14784. }
  14785. }
  14786.  
  14787. // wandbox编译器请求方法
  14788. async function wandboxCompilerRequest(code, input) {
  14789. const data = {
  14790. code: code,
  14791. codes: [],
  14792. compiler: $("#CompilerChange")
  14793. .val()
  14794. .replace($("#compiler_option_raw").val(), "")
  14795. .replace($("#runtime_option_raw").val(), ""),
  14796. "compiler-option-raw": $("#compiler_option_raw").val(),
  14797. "runtime-option-raw": $("#runtime_option_raw").val(),
  14798. options: $("#CompilerArgsInput").val(),
  14799. description: "",
  14800. stdin: input,
  14801. title: "",
  14802. };
  14803.  
  14804. const responseDetails = await OJB_GMRequest({
  14805. method: "POST",
  14806. url: "https://wandbox.org/api/compile.json",
  14807. data: JSON.stringify(data),
  14808. headers: {
  14809. "Content-Type": "application/json",
  14810. },
  14811. });
  14812.  
  14813. if (responseDetails.status !== 200 || !responseDetails.response) {
  14814. throw new Error(
  14815. `${i18next.t("compiler.common.error", { ns: "codeEditor" })}`
  14816. );
  14817. }
  14818.  
  14819. const response = JSON.parse(responseDetails.response);
  14820. return {
  14821. Errors:
  14822. response.compiler_error === ""
  14823. ? response.signal
  14824. : response.compiler_error,
  14825. Result: response.program_output || "",
  14826. Stats: response.status === "0" ? "Finish" : "Error",
  14827. };
  14828. }
  14829.  
  14830. // 更改编译器参数
  14831. function changeCompilerArgs(nowSelect) {
  14832. if (OJBetter.monaco.onlineCompilerChoice == "official") {
  14833. officialCompilerArgsChange(nowSelect);
  14834. } else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
  14835. rextesterCompilerArgsChange(nowSelect);
  14836. } else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
  14837. wandboxCompilerArgsChange(nowSelect);
  14838. }
  14839. }
  14840.  
  14841. // 在线编译器通信
  14842. async function onlineCompilerConnect(code, input) {
  14843. if (OJBetter.monaco.onlineCompilerChoice == "official") {
  14844. return await officialCompiler(code, input);
  14845. } else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
  14846. return await rextesterCompiler(code, input);
  14847. } else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
  14848. return await wandboxCompiler(code, input);
  14849. }
  14850. }
  14851.  
  14852. // 差异对比
  14853. function codeDiff(expectedText, actualText) {
  14854. // 将文本按行拆分
  14855. const expectedLines = expectedText ? expectedText.split("\n") : [];
  14856. const actualLines = actualText ? actualText.split("\n") : [];
  14857.  
  14858. const output = document.createElement("div");
  14859.  
  14860. const createLineElement = (lineNo, contentElement) => {
  14861. const lineDiv = document.createElement("div");
  14862. lineDiv.className = "diffLine";
  14863.  
  14864. const lineNoDiv = document.createElement("div");
  14865. lineNoDiv.className = "lineNo";
  14866. lineNoDiv.textContent = lineNo;
  14867.  
  14868. lineDiv.appendChild(lineNoDiv);
  14869. lineDiv.appendChild(contentElement);
  14870.  
  14871. return lineDiv;
  14872. };
  14873.  
  14874. const createContentElement = (
  14875. isEquals = true,
  14876. expected = null,
  14877. removed = null
  14878. ) => {
  14879. const contentDiv = document.createElement("div");
  14880. contentDiv.className = "lineContent";
  14881.  
  14882. if (isEquals) {
  14883. const span = document.createElement("span");
  14884. span.textContent = expected;
  14885. contentDiv.appendChild(span);
  14886. } else {
  14887. if (removed != null) {
  14888. const removedSpan = document.createElement("span");
  14889. removedSpan.className = "removed";
  14890. removedSpan.textContent = removed;
  14891. contentDiv.appendChild(removedSpan);
  14892. }
  14893. if (expected != null) {
  14894. const addedSpan = document.createElement("span");
  14895. addedSpan.className = "added";
  14896. addedSpan.textContent = expected;
  14897. contentDiv.appendChild(addedSpan);
  14898. }
  14899. }
  14900.  
  14901. return contentDiv;
  14902. };
  14903.  
  14904. let index = 1;
  14905.  
  14906. expectedLines.forEach((expectedLine, i) => {
  14907. const actualLine = actualLines[i];
  14908. if (actualLine === undefined) {
  14909. output.appendChild(
  14910. createLineElement(index++, createContentElement(false, expectedLine))
  14911. );
  14912. } else if (expectedLine === actualLine) {
  14913. output.appendChild(
  14914. createLineElement(index++, createContentElement(true, expectedLine))
  14915. );
  14916. } else {
  14917. output.appendChild(
  14918. createLineElement(
  14919. index++,
  14920. createContentElement(false, expectedLine, actualLine)
  14921. )
  14922. );
  14923. }
  14924. });
  14925.  
  14926. // 处理多余的 actualLines
  14927. for (let i = expectedLines.length; i < actualLines.length; i++) {
  14928. output.appendChild(
  14929. createLineElement(
  14930. index++,
  14931. createContentElement(false, null, actualLines[i])
  14932. )
  14933. );
  14934. }
  14935.  
  14936. return output.innerHTML;
  14937. }
  14938.  
  14939. // 内容类型常量
  14940. const TestCaseContentType = {
  14941. TERMINAL: "terminal",
  14942. DIFF: "diff",
  14943. NO_DIFF: "no_diff",
  14944. SUCCESS: "success",
  14945. };
  14946.  
  14947. // 样例测试状态类
  14948. class TestCaseStatus {
  14949. constructor(item, prefix) {
  14950. this.testCase = OJB_safeCreateJQElement(`<div class="test-case"></div>`);
  14951. this.item = item;
  14952. this.prefix = prefix;
  14953. this.titleElement = OJB_safeCreateJQElement(
  14954. `<div class="test-case-title">${this.prefix} ${this.item}</div>`
  14955. );
  14956. this.statusElement = OJB_safeCreateJQElement(
  14957. `<div class="test-case-status"></div>`
  14958. );
  14959. this.contentElement = OJB_safeCreateJQElement(
  14960. `<div class="test-case-content"></div>`
  14961. );
  14962. this.judgeElement = OJB_safeCreateJQElement(
  14963. `<div class="test-case-judge"></div>`
  14964. );
  14965. this.testCase.append(
  14966. this.titleElement,
  14967. this.statusElement,
  14968. this.contentElement,
  14969. this.judgeElement
  14970. );
  14971. $("#statePanel").append(this.testCase);
  14972. this.setStatus("Queued", "queued");
  14973. }
  14974.  
  14975. /**
  14976. * 设置标题
  14977. *
  14978. * @param {string} title 标题
  14979. */
  14980. setTitle(title) {
  14981. this.titleElement.text(title);
  14982. }
  14983.  
  14984. /**
  14985. * 设置状态
  14986. *
  14987. * @param {string} text 状态文本
  14988. * @param {string} status 状态类名
  14989. */
  14990. setStatus(text, status) {
  14991. this.statusElement
  14992. .text(text)
  14993. .removeClass("queued running success error")
  14994. .addClass(status);
  14995. }
  14996.  
  14997. /**
  14998. * 设置内容
  14999. *
  15000. * @param {string} content 内容
  15001. * @param {string} type 内容类型
  15002. */
  15003. setContent(content, type) {
  15004. // 如果内容类型为SUCCESS,隐藏内容元素并提前返回
  15005. if (type === TestCaseContentType.SUCCESS) {
  15006. this.contentElement.hide();
  15007. return;
  15008. }
  15009.  
  15010. // 根据内容类型创建内容元素
  15011. const createContentElementByType = (content, type) => {
  15012. let contentElement;
  15013. switch (type) {
  15014. case TestCaseContentType.TERMINAL:
  15015. // 为TERMINAL类型创建一个新的终端容器
  15016. contentElement = OJB_safeCreateJQElement(
  15017. `<div class="terminal-container" style="overflow: auto;"></div>`
  15018. );
  15019. break;
  15020. case TestCaseContentType.DIFF:
  15021. case TestCaseContentType.NO_DIFF:
  15022. // 为DIFF和NO_DIFF类型创建相应的内容元素,并添加差异说明
  15023. const className =
  15024. type === TestCaseContentType.DIFF
  15025. ? "output_diff"
  15026. : "output_no_diff";
  15027. contentElement = OJB_safeCreateJQElement(
  15028. `<pre class="${className}">${content}</pre>`
  15029. );
  15030. appendDiffNote();
  15031. break;
  15032. default:
  15033. throw new Error("Unsupported content type.");
  15034. }
  15035. return contentElement;
  15036. };
  15037.  
  15038. // 初始化终端
  15039. const initializeTerminal = (content, contentElement) => {
  15040. const term = new Terminal({ rows: 10, cols: 150 });
  15041. term.setOption("theme", { background: "#2d2e2c" });
  15042. term.setOption("convertEol", true); // 将换行符\n转换为\r\n
  15043. term.write(content);
  15044. term.open(contentElement.get(0));
  15045. };
  15046.  
  15047. // 添加差异说明
  15048. const appendDiffNote = () => {
  15049. const diffNote = OJB_safeCreateJQElement(
  15050. `<div class="diff_note">${i18next.t("resultBlock.diffNote", {
  15051. ns: "codeEditor",
  15052. })}</div>`
  15053. );
  15054. this.testCase.append(diffNote);
  15055. };
  15056.  
  15057. // 创建并追加内容元素
  15058. const contentElement = createContentElementByType(content, type);
  15059. this.contentElement.append(contentElement);
  15060.  
  15061. // 如果内容类型为TERMINAL,初始化并打开终端
  15062. if (type === TestCaseContentType.TERMINAL) {
  15063. initializeTerminal(content, contentElement);
  15064. }
  15065. }
  15066.  
  15067. // 设置checker的评测结果
  15068. setJudgeChecker(message) {
  15069. function createJudgeCheckerElement(message) {
  15070. const judgeCheckerElement = OJB_safeCreateJQElement(
  15071. `<div class="judge-checker">${i18next.t(
  15072. "moreSettings.validator.messagePrefix",
  15073. { ns: "codeEditor" }
  15074. )}${message}</div>`
  15075. );
  15076. return judgeCheckerElement;
  15077. }
  15078. const judgeCheckerElement = createJudgeCheckerElement(message);
  15079. this.contentElement.before(judgeCheckerElement);
  15080. }
  15081.  
  15082. setJudge(judge) {
  15083. this.judgeElement.text(judge);
  15084. }
  15085. }
  15086.  
  15087. /**
  15088. * 评测结果检查器基类,所有检查器类应继承此类
  15089. */
  15090. class judgeResultValidator {
  15091. /**
  15092. * 检查方法,子类应覆盖此方法
  15093. * @param {string} expected - 预期输出
  15094. * @param {string} actual - 实际输出
  15095. * @returns {Object} 检查结果对象 { passed: boolean, message: string }
  15096. */
  15097. validate(expected, actual) {
  15098. throw new Error("This method should be overridden by subclasses");
  15099. }
  15100. }
  15101.  
  15102. /**
  15103. * 忽略行末空格和末尾换行的检查器
  15104. */
  15105. class IgnoreWhitespaceValidator extends judgeResultValidator {
  15106. validate(expected, actual) {
  15107. // 去掉字符串末尾的空格和换行符
  15108. expected = expected.trim().replace(/\s+$/gm, "");
  15109. actual = actual.trim().replace(/\s+$/gm, "");
  15110. const passed = expected === actual;
  15111. return {
  15112. passed: passed,
  15113. message: passed
  15114. ? i18next.t("moreSettings.checkMessage.ignoreWhitespace.correct", {
  15115. ns: "codeEditor",
  15116. })
  15117. : i18next.t("moreSettings.checkMessage.ignoreWhitespace.mismatch", {
  15118. ns: "codeEditor",
  15119. }),
  15120. };
  15121. }
  15122. }
  15123.  
  15124. /**
  15125. * 严格检查器
  15126. */
  15127. class StrictValidator extends judgeResultValidator {
  15128. validate(expected, actual) {
  15129. const passed = expected === actual;
  15130. return {
  15131. passed: passed,
  15132. message: passed
  15133. ? i18next.t("moreSettings.checkMessage.strict.correct", {
  15134. ns: "codeEditor",
  15135. })
  15136. : i18next.t("moreSettings.checkMessage.strict.mismatch", {
  15137. ns: "codeEditor",
  15138. }),
  15139. };
  15140. }
  15141. }
  15142.  
  15143. /**
  15144. * 整数检查器
  15145. */
  15146. class NcmpValidator extends judgeResultValidator {
  15147. validate(expected, actual) {
  15148. const expectedInts = expected.split(/\s+/).filter(Boolean).map(Number);
  15149. const actualInts = actual.split(/\s+/).filter(Boolean).map(Number);
  15150.  
  15151. const minLength = Math.min(expectedInts.length, actualInts.length);
  15152. let firstElems = [];
  15153.  
  15154. for (let i = 0; i < minLength; i++) {
  15155. if (expectedInts[i] !== actualInts[i]) {
  15156. return {
  15157. passed: false,
  15158. message: i18next.t("moreSettings.checkMessage.ncmp.differ", {
  15159. ns: "codeEditor",
  15160. count: i + 1,
  15161. expected: expectedInts[i],
  15162. actual: actualInts[i],
  15163. }),
  15164. };
  15165. } else if (i < 5) {
  15166. firstElems.push(expectedInts[i]);
  15167. }
  15168. }
  15169.  
  15170. if (expectedInts.length > actualInts.length) {
  15171. return {
  15172. passed: false,
  15173. message: i18next.t("moreSettings.checkMessage.ncmp.longerExpected", {
  15174. ns: "codeEditor",
  15175. expectedLength: expectedInts.length,
  15176. actualLength: actualInts.length,
  15177. }),
  15178. };
  15179. }
  15180.  
  15181. if (actualInts.length > expectedInts.length) {
  15182. return {
  15183. passed: false,
  15184. message: i18next.t("moreSettings.checkMessage.ncmp.longerActual", {
  15185. ns: "codeEditor",
  15186. actualLength: actualInts.length,
  15187. expectedLength: expectedInts.length,
  15188. }),
  15189. };
  15190. }
  15191.  
  15192. return {
  15193. passed: true,
  15194. message:
  15195. firstElems.length <= 5
  15196. ? i18next.t("moreSettings.checkMessage.ncmp.correctFew", {
  15197. ns: "codeEditor",
  15198. count: firstElems.length,
  15199. numbers: firstElems.join(" "),
  15200. })
  15201. : i18next.t("moreSettings.checkMessage.ncmp.correctMany", {
  15202. ns: "codeEditor",
  15203. count: expectedInts.length,
  15204. }),
  15205. };
  15206. }
  15207. }
  15208.  
  15209. /**
  15210. * 浮点数检查器
  15211. */
  15212. class RcmpValidator extends judgeResultValidator {
  15213. constructor(epsilon) {
  15214. super();
  15215. this.epsilon = epsilon;
  15216. }
  15217.  
  15218. validate(expected, actual) {
  15219. const expectedFloats = expected.split(/\s+/).filter(Boolean).map(Number);
  15220. const actualFloats = actual.split(/\s+/).filter(Boolean).map(Number);
  15221.  
  15222. if (expectedFloats.length !== actualFloats.length) {
  15223. return {
  15224. passed: false,
  15225. message: i18next.t("moreSettings.checkMessage.rcmp.lengthMismatch", {
  15226. ns: "codeEditor",
  15227. expectedLength: expectedFloats.length,
  15228. actualLength: actualFloats.length,
  15229. }),
  15230. };
  15231. }
  15232.  
  15233. for (let i = 0; i < expectedFloats.length; i++) {
  15234. if (isNaN(expectedFloats[i]) || isNaN(actualFloats[i])) {
  15235. return {
  15236. passed: false,
  15237. message: i18next.t("moreSettings.checkMessage.rcmp.invalidNumber", {
  15238. ns: "codeEditor",
  15239. index: i + 1,
  15240. expected: expected.split(/\s+/)[i],
  15241. actual: actual.split(/\s+/)[i],
  15242. }),
  15243. };
  15244. }
  15245.  
  15246. const error = Math.abs(expectedFloats[i] - actualFloats[i]);
  15247. if (error > this.epsilon) {
  15248. return {
  15249. passed: false,
  15250. message: i18next.t("moreSettings.checkMessage.rcmp.differ", {
  15251. ns: "codeEditor",
  15252. n: i + 1,
  15253. expected: expectedFloats[i].toFixed(7),
  15254. actual: actualFloats[i].toFixed(7),
  15255. error: error.toFixed(7),
  15256. }),
  15257. };
  15258. }
  15259. }
  15260.  
  15261. if (expectedFloats.length === 1) {
  15262. const error = Math.abs(expectedFloats[0] - actualFloats[0]);
  15263. return {
  15264. passed: true,
  15265. message: i18next.t("moreSettings.checkMessage.rcmp.single", {
  15266. ns: "codeEditor",
  15267. expected: expectedFloats[0].toFixed(7),
  15268. actual: actualFloats[0].toFixed(7),
  15269. error: error.toFixed(7),
  15270. }),
  15271. };
  15272. }
  15273.  
  15274. return {
  15275. passed: true,
  15276. message: i18next.t("moreSettings.checkMessage.rcmp.total", {
  15277. ns: "codeEditor",
  15278. count: expectedFloats.length,
  15279. }),
  15280. };
  15281. }
  15282. }
  15283.  
  15284. /**
  15285. * 字符串检查器
  15286. */
  15287. class WcmpValidator extends judgeResultValidator {
  15288. validate(expected, actual) {
  15289. const expectedWords = expected.split(/\s+/);
  15290. const actualWords = actual.split(/\s+/);
  15291.  
  15292. const minLength = Math.min(expectedWords.length, actualWords.length);
  15293.  
  15294. for (let i = 0; i < minLength; i++) {
  15295. if (expectedWords[i] !== actualWords[i]) {
  15296. return {
  15297. passed: false,
  15298. message: i18next.t("moreSettings.checkMessage.wcmp.wordsDiffer", {
  15299. ns: "codeEditor",
  15300. count: i + 1,
  15301. expected: expectedWords[i],
  15302. actual: actualWords[i],
  15303. }),
  15304. };
  15305. }
  15306. }
  15307.  
  15308. if (expectedWords.length !== actualWords.length) {
  15309. return {
  15310. passed: false,
  15311. message:
  15312. expectedWords.length > actualWords.length
  15313. ? i18next.t(
  15314. "moreSettings.checkMessage.wcmp.extraTokensInParticipant",
  15315. { ns: "codeEditor" }
  15316. )
  15317. : i18next.t("moreSettings.checkMessage.wcmp.unexpectedEOF", {
  15318. ns: "codeEditor",
  15319. }),
  15320. };
  15321. }
  15322.  
  15323. return {
  15324. passed: true,
  15325. message:
  15326. minLength === 1
  15327. ? i18next.t("moreSettings.checkMessage.wcmp.singleToken", {
  15328. ns: "codeEditor",
  15329. token: expectedWords[0],
  15330. })
  15331. : i18next.t("moreSettings.checkMessage.wcmp.tokenCount", {
  15332. ns: "codeEditor",
  15333. count: minLength,
  15334. }),
  15335. };
  15336. }
  15337. }
  15338.  
  15339. /**
  15340. * YES NO大小写不敏感检查器
  15341. */
  15342. class NyesnoValidator extends judgeResultValidator {
  15343. validate(expected, actual) {
  15344. const YES = "yes";
  15345. const NO = "no";
  15346.  
  15347. const expectedTokens = expected.trim().toLowerCase().split(/\s+/);
  15348. const actualTokens = actual.trim().toLowerCase().split(/\s+/);
  15349.  
  15350. let index = 0;
  15351. let yesCount = 0;
  15352. let noCount = 0;
  15353.  
  15354. while (index < expectedTokens.length && index < actualTokens.length) {
  15355. const expectedToken = expectedTokens[index];
  15356. const actualToken = actualTokens[index];
  15357. index++;
  15358.  
  15359. if (expectedToken !== YES && expectedToken !== NO) {
  15360. return {
  15361. passed: false,
  15362. message: i18next.t(
  15363. "moreSettings.checkMessage.nyesno.expectedInAnswer",
  15364. {
  15365. ns: "codeEditor",
  15366. YES,
  15367. NO,
  15368. token: expectedToken,
  15369. index,
  15370. ending: this.englishEnding(index),
  15371. }
  15372. ),
  15373. };
  15374. }
  15375.  
  15376. if (actualToken === YES) {
  15377. yesCount++;
  15378. } else if (actualToken === NO) {
  15379. noCount++;
  15380. } else {
  15381. return {
  15382. passed: false,
  15383. message: i18next.t(
  15384. "moreSettings.checkMessage.nyesno.expectedInOutput",
  15385. {
  15386. ns: "codeEditor",
  15387. YES,
  15388. NO,
  15389. token: actualToken,
  15390. index,
  15391. ending: this.englishEnding(index),
  15392. }
  15393. ),
  15394. };
  15395. }
  15396.  
  15397. if (expectedToken !== actualToken) {
  15398. return {
  15399. passed: false,
  15400. message: i18next.t("moreSettings.checkMessage.nyesno.mismatch", {
  15401. ns: "codeEditor",
  15402. expected: expectedToken,
  15403. actual: actualToken,
  15404. index,
  15405. ending: this.englishEnding(index),
  15406. }),
  15407. };
  15408. }
  15409. }
  15410.  
  15411. if (index < expectedTokens.length) {
  15412. return {
  15413. passed: false,
  15414. message: i18next.t("moreSettings.checkMessage.nyesno.longerInAnswer", {
  15415. ns: "codeEditor",
  15416. expectedLength: expectedTokens.length,
  15417. actualLength: index,
  15418. }),
  15419. };
  15420. }
  15421.  
  15422. if (index < actualTokens.length) {
  15423. return {
  15424. passed: false,
  15425. message: i18next.t("moreSettings.checkMessage.nyesno.longerInOutput", {
  15426. ns: "codeEditor",
  15427. actualLength: actualTokens.length,
  15428. expectedLength: index,
  15429. }),
  15430. };
  15431. }
  15432.  
  15433. if (index === 0) {
  15434. return {
  15435. passed: true,
  15436. message: i18next.t("moreSettings.checkMessage.nyesno.emptyOutput", {
  15437. ns: "codeEditor",
  15438. }),
  15439. };
  15440. } else if (index === 1) {
  15441. return {
  15442. passed: true,
  15443. message: `${actualTokens[0]}`,
  15444. };
  15445. } else {
  15446. return {
  15447. passed: true,
  15448. message: i18next.t("moreSettings.checkMessage.nyesno.summary", {
  15449. ns: "codeEditor",
  15450. index,
  15451. yesCount,
  15452. noCount,
  15453. }),
  15454. };
  15455. }
  15456. }
  15457.  
  15458. englishEnding(number) {
  15459. if (number % 10 === 1 && number % 100 !== 11) {
  15460. return "st";
  15461. } else if (number % 10 === 2 && number % 100 !== 12) {
  15462. return "nd";
  15463. } else if (number % 10 === 3 && number % 100 !== 13) {
  15464. return "rd";
  15465. } else {
  15466. return "th";
  15467. }
  15468. }
  15469. }
  15470.  
  15471. // 创建检查器实例映射
  15472. const judgeResultValidators = {
  15473. ignoreWhitespace: new IgnoreWhitespaceValidator(),
  15474. strict: new StrictValidator(),
  15475. ncmp: new NcmpValidator(),
  15476. rcmp4: new RcmpValidator(1e-4),
  15477. rcmp6: new RcmpValidator(1e-6),
  15478. rcmp9: new RcmpValidator(1e-9),
  15479. wcmp: new WcmpValidator(),
  15480. nyesno: new NyesnoValidator(),
  15481. };
  15482.  
  15483. /**
  15484. * 检查入口函数
  15485. *
  15486. * @param {string} 原始输出
  15487. * @param {string} 实际输出
  15488. * @return {Object} 检查结果对象 { passed: boolean, message: string }
  15489. */
  15490. function judgeResultValidate(expected, actual) {
  15491. const judgeResultCheckMode = OJB_getGMValue(
  15492. "judgeResultCheckMode",
  15493. "ignoreWhitespace"
  15494. );
  15495. const validator = judgeResultValidators[judgeResultCheckMode];
  15496. if (!validator) {
  15497. throw new Error("Unsupported validator");
  15498. }
  15499. const result = validator.validate(expected, actual);
  15500. // message前面加上检查器的英文简写名字
  15501. result.message = `[${judgeResultCheckMode}] ${result.message}`;
  15502. return result;
  15503. }
  15504.  
  15505. // 样例测试函数
  15506. async function runCode(event, runButton, sourceDiv) {
  15507. event.preventDefault();
  15508. const statePanel = $("#statePanel").show().empty();
  15509. const testData = getTestData();
  15510. const customTestData = await getCustomTestData();
  15511. const totalTests =
  15512. Object.keys(customTestData).length + Object.keys(testData).length;
  15513.  
  15514. let passedTests = 0;
  15515. let failedTests = 0;
  15516. let hasError = false;
  15517.  
  15518. // 定义一个对象队列,包括创建的样例块实例和对应的样例数据
  15519. const queue = [];
  15520.  
  15521. // 先生成各个样例的块,并显示排队中,将创建的各个对象存到队列中,以便后面更新
  15522. for (const [item, data] of Object.entries(customTestData)) {
  15523. const testCase = new TestCaseStatus(
  15524. item,
  15525. i18next.t("resultBlock.title.custom", { ns: "codeEditor" })
  15526. );
  15527. queue.push({ testCase, data });
  15528. }
  15529.  
  15530. if (!$("#onlyCustomTest").prop("checked")) {
  15531. for (const [item, data] of Object.entries(testData)) {
  15532. const testCase = new TestCaseStatus(
  15533. item,
  15534. i18next.t("resultBlock.title.sample", { ns: "codeEditor" })
  15535. );
  15536. queue.push({ testCase, data });
  15537. }
  15538. }
  15539.  
  15540. // 测试函数
  15541. const runTest = async (testCase, data, index) => {
  15542. runButton.setButtonState("running", `${index}/${totalTests}`);
  15543.  
  15544. testCase.setStatus("Running", "running");
  15545. const result = await onlineCompilerConnect(sourceDiv.val(), data.input);
  15546.  
  15547. if (result.Errors) {
  15548. testCase.setStatus("Compilation error or Time limit", "error");
  15549. testCase.setContent(result.Errors, TestCaseContentType.TERMINAL);
  15550. hasError = true;
  15551. } else {
  15552. const resultCheck = judgeResultValidate(data.output, result.Result);
  15553. testCase.setJudgeChecker(resultCheck.message);
  15554. // 根据检查结果设置样例测试状态
  15555. if (resultCheck.passed) {
  15556. testCase.setStatus("Accepted", "success");
  15557. testCase.setContent(
  15558. "The output is correct.",
  15559. TestCaseContentType.SUCCESS
  15560. );
  15561. passedTests++;
  15562. } else {
  15563. testCase.setStatus("Wrong Answer", "error");
  15564. const judgeResultCheckMode = OJB_getGMValue(
  15565. "judgeResultCheckMode",
  15566. "ignoreWhitespace"
  15567. );
  15568. // 如果检查模式为ignoreWhitespace,去掉字符串末尾的空格和换行符
  15569. if (judgeResultCheckMode === "ignoreWhitespace") {
  15570. data.output = data.output.trim().replace(/\s+$/gm, "");
  15571. result.Result = result.Result.trim().replace(/\s+$/gm, "");
  15572. }
  15573. const diffContent = $("#DontShowDiff").prop("checked")
  15574. ? result.Result
  15575. : codeDiff(data.output, result.Result);
  15576. const contentType = $("#DontShowDiff").prop("checked")
  15577. ? TestCaseContentType.NO_DIFF
  15578. : TestCaseContentType.DIFF;
  15579. testCase.setContent(diffContent, contentType);
  15580. failedTests++;
  15581. }
  15582. }
  15583.  
  15584. const judgeStats = `${i18next.t("resultBlock.state", {
  15585. ns: "codeEditor",
  15586. })}${result.Stats}`;
  15587. testCase.setJudge(judgeStats);
  15588.  
  15589. await OJB_delay(500); // 等待500毫秒
  15590. };
  15591.  
  15592. // 对队列中的对象进行测试
  15593. for (let i = 0; i < queue.length; i++) {
  15594. const { testCase, data } = queue[i];
  15595. await runTest(testCase, data, i + 1);
  15596. }
  15597.  
  15598. // 测试完成后更新按钮状态
  15599. if (hasError) {
  15600. runButton.setButtonState(
  15601. "error",
  15602. i18next.t("runTestButton.error", { ns: "codeEditor" })
  15603. );
  15604. } else if (failedTests > 0) {
  15605. runButton.setButtonState(
  15606. "error",
  15607. `${passedTests}/${totalTests} ` +
  15608. i18next.t("runTestButton.partial", { ns: "codeEditor" })
  15609. );
  15610. } else {
  15611. runButton.setButtonState(
  15612. "success",
  15613. i18next.t("runTestButton.success", { ns: "codeEditor" })
  15614. );
  15615. if (OJBetter.monaco.setting.autoSubmitAfterPass) {
  15616. $("#OJBetter_SubmitForm").submit(); // 自动提交
  15617. }
  15618. }
  15619. }
  15620.  
  15621. /**
  15622. * 添加题目页代码编辑器
  15623. * @returns
  15624. */
  15625. async function addProblemPageCodeEditor() {
  15626. if (typeof ace === "undefined") {
  15627. const loadingMessage = new LoadingMessage();
  15628. loadingMessage.updateStatus(
  15629. `${OJBetter.state.name} —— ${i18next.t("error.codeEditor.load", {
  15630. ns: "alert",
  15631. })}`,
  15632. "error"
  15633. );
  15634. return; // 因为Codeforces设定的是未登录时不能访问提交页,也不会加载ace库
  15635. }
  15636.  
  15637. // 获取提交页链接
  15638. const href = window.location.href;
  15639. let submitUrl;
  15640. if (/\/problemset\//.test(href)) {
  15641. // problemset
  15642. submitUrl = OJBetter.common.hostAddress + "/problemset/submit";
  15643. } else if (/\/gym\//.test(href)) {
  15644. // gym 题目
  15645. submitUrl =
  15646. OJBetter.common.hostAddress +
  15647. "/gym/" +
  15648. ((href) => {
  15649. const regex = /\/gym\/(?<num>[0-9a-zA-Z]*?)\/problem\//;
  15650. const match = href.match(regex);
  15651. return match && match.groups.num;
  15652. })(href) +
  15653. "/submit";
  15654. } else if (OJBetter.typeOfPage.is_acmsguru) {
  15655. // acmsguru 题目
  15656. submitUrl = href.replace(
  15657. /\/problemsets[A-Za-z0-9\/#]*/,
  15658. "/problemsets/acmsguru/submit"
  15659. );
  15660. } else {
  15661. submitUrl = href.replace(/\/problem[A-Za-z0-9\/#]*/, "/submit");
  15662. }
  15663.  
  15664. // 获取提交页HTML
  15665. let cloneHTML = await getSubmitHTML(submitUrl);
  15666.  
  15667. // 创建
  15668. let form = await createCodeEditorForm(submitUrl, cloneHTML);
  15669. let selectLang = form.selectLang;
  15670. let submitButton = form.submitButton;
  15671. let runButton = form.runButton;
  15672.  
  15673. // 初始化
  15674. CustomTestInit(); // 自定义测试数据面板
  15675. selectLang.val(OJBetter.monaco.compilerSelection);
  15676.  
  15677. // 设置语言选择change事件监听器
  15678. selectLang.on("change", () => {
  15679. changeMonacoLanguage(form); // 编辑器语言切换监听
  15680. });
  15681. changeMonacoLanguage(form);
  15682.  
  15683. // 样例测试
  15684. runButton
  15685. .on("click", (event) =>
  15686. runCode(event, runButton, form.sourceDiv, form.submitDiv)
  15687. )
  15688. .setHoverRedo();
  15689.  
  15690. // 提交
  15691. submitButton.on("click", async function (event) {
  15692. event.preventDefault();
  15693. if (OJBetter.monaco.setting.isCodeSubmitDoubleConfirm) {
  15694. // 获取题目名
  15695. const questionTitle = (() => {
  15696. if (OJBetter.typeOfPage.is_acmsguru) {
  15697. return $("h4").eq(0).text();
  15698. } else {
  15699. return $(".header .title").eq(0).text();
  15700. }
  15701. })();
  15702. const submit = await OJB_createDialog(
  15703. i18next.t("submitCode.title", { ns: "dialog" }),
  15704. i18next.t("submitCode.content", {
  15705. ns: "dialog",
  15706. questionTitle: questionTitle,
  15707. }),
  15708. [
  15709. i18next.t("submitCode.buttons.0", { ns: "dialog" }),
  15710. i18next.t("submitCode.buttons.1", { ns: "dialog" }),
  15711. ],
  15712. true
  15713. ); //提交确认
  15714. if (submit) {
  15715. submitButton.after(
  15716. `<img class="OJBetter_loding" src="//codeforces.org/s/84141/images/ajax-loading-24x24.gif">`
  15717. );
  15718. $("#OJBetter_SubmitForm").submit();
  15719. } else {
  15720. submitButton.addClass("disabled");
  15721. setTimeout(function () {
  15722. submitButton.removeClass("disabled");
  15723. }, 300);
  15724. }
  15725. } else {
  15726. $("#OJBetter_SubmitForm").submit();
  15727. }
  15728. });
  15729. }
  15730.  
  15731. /**
  15732. * 获取翻译服务目标语言的对应代码
  15733. * @param {string} serverName 服务名称
  15734. * @returns {string} 目标语言,如果没有对应代码则返回中文
  15735. */
  15736. function getTargetLanguage(serverName) {
  15737. let targetLanguage =
  15738. OJBetter.supportList.translationSupport[serverName][
  15739. OJBetter.translation.targetLang
  15740. ];
  15741. if (targetLanguage) return targetLanguage;
  15742. else return OJBetter.supportList.translationSupport[serverName]["zh"];
  15743. }
  15744.  
  15745. /**
  15746. * 将文本中Markdown格式的加粗**转换成HTML格式。
  15747. * @param {string} text 文本
  15748. * @returns {string} 替换后的字符串
  15749. */
  15750. function convertBoldMarkdownToHTML(text) {
  15751. return text.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
  15752. }
  15753.  
  15754. /**
  15755. * 将文本中Markdown格式的链接文本转换成HTML格式。
  15756. * @param {string} text 文本
  15757. * @returns {string} 替换后的字符串
  15758. */
  15759. function convertLinksMarkdownToHTML(text) {
  15760. return text.replace(
  15761. /(?<!!)\[(.*?)\]\(([^"]*?)("(.*?)")*\)/g,
  15762. '<a href="$2" title="$4">$1</a>'
  15763. );
  15764. }
  15765.  
  15766. /**
  15767. * 将HTML格式的加粗文本转换回Markdown格式。
  15768. * @param {string} text 文本
  15769. * @returns {string} 替换后的字符串
  15770. */
  15771. function convertBoldHTMLToMarkdown(text) {
  15772. return text.replace(/<strong>(.*?)<\/strong>/g, "**$1**");
  15773. }
  15774.  
  15775. /**
  15776. * 将HTML格式的链接文本转换回Markdown格式。
  15777. * @param {string} html - 包含HTML链接标签<a>的字符串。
  15778. * @returns {string} 转换后的字符串,其中HTML链接标签被替换为Markdown的链接语法。
  15779. */
  15780. function convertLinksHTMLToMarkdown(html) {
  15781. return html.replace(
  15782. /<a href="([^"]*)"( title="([^"]*)")*>([^<]+)<\/a>/g,
  15783. '[$4]($1 "$3")'
  15784. );
  15785. }
  15786.  
  15787. /**
  15788. * DeepL翻译
  15789. * @param {string} raw 原文
  15790. * @returns {Promise<TransRawData>} 翻译结果对象
  15791. */
  15792. async function translate_deepl(raw) {
  15793. const id = (Math.floor(Math.random() * 99999) + 100000) * 1000;
  15794. const data = {
  15795. jsonrpc: "2.0",
  15796. method: "LMT_handle_texts",
  15797. id,
  15798. params: {
  15799. splitting: "newlines",
  15800. lang: {
  15801. source_lang_user_selected: "auto",
  15802. target_lang: getTargetLanguage("deepl"),
  15803. },
  15804. texts: [
  15805. {
  15806. text: raw,
  15807. requestAlternatives: 3,
  15808. },
  15809. ],
  15810. timestamp: getTimeStamp(raw.split("i").length - 1),
  15811. },
  15812. };
  15813. let postData = JSON.stringify(data);
  15814. if ((id + 5) % 29 === 0 || (id + 3) % 13 === 0) {
  15815. postData = postData.replace('"method":"', '"method" : "');
  15816. } else {
  15817. postData = postData.replace('"method":"', '"method": "');
  15818. }
  15819. const options = {
  15820. method: "POST",
  15821. url: "https://www2.deepl.com/jsonrpc",
  15822. data: postData,
  15823. headers: {
  15824. "Content-Type": "application/json",
  15825. Host: "www2.deepl.com",
  15826. Origin: "https://www.deepl.com",
  15827. Referer: "https://www.deepl.com/",
  15828. },
  15829. anonymous: true,
  15830. nocache: true,
  15831. };
  15832.  
  15833. return await BaseTranslate(
  15834. options,
  15835. (res) => JSON.parse(res)?.result?.texts?.[0]?.text || res,
  15836. (res) => {
  15837. const resObj = {
  15838. status: true,
  15839. message: "ok",
  15840. };
  15841. if (res.includes('"message":"Too many requests"')) {
  15842. resObj.status = false;
  15843. resObj.message = i18next.t("error.deepl429", { ns: "translator" }); // Too many requests 提示
  15844. return resObj;
  15845. }
  15846. return resObj;
  15847. }
  15848. );
  15849. }
  15850.  
  15851. /**
  15852. * 使用 DeepL Free API 进行翻译
  15853. * @param {string} raw 原文
  15854. * @returns {Promise<TransRawData>} 翻译结果对象
  15855. */
  15856. async function translate_deepl_api_free(raw) {
  15857. const data = JSON.stringify({
  15858. text: [raw],
  15859. target_lang: getTargetLanguage("deepl"),
  15860. split_sentences: "1",
  15861. ...(OJBetter.deepl.enableEmphasisProtection ||
  15862. OJBetter.deepl.enableLinkProtection
  15863. ? { tag_handling: "html" }
  15864. : {}),
  15865. ...Object.assign({}, ...OJBetter.deepl.config.data),
  15866. });
  15867.  
  15868. const options = {
  15869. method: "POST",
  15870. url:
  15871. OJBetter.deepl.config.proxy || "https://api-free.deepl.com/v2/translate",
  15872. headers: {
  15873. Authorization: `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
  15874. "Content-Type": "application/json",
  15875. ...Object.assign({}, ...OJBetter.deepl.config.header),
  15876. },
  15877. data: data,
  15878. onload: (response) => response.responseText,
  15879. onerror: (error) => console.error(error),
  15880. };
  15881.  
  15882. return await BaseTranslate(
  15883. options,
  15884. (res) => JSON.parse(res).translations[0].text
  15885. );
  15886. }
  15887.  
  15888. /**
  15889. * 使用 DeepL Pro API 进行翻译
  15890. * @param {string} raw 原文
  15891. * @returns {Promise<TransRawData>} 翻译结果对象
  15892. */
  15893. async function translate_deepl_api_pro(raw) {
  15894. const data = JSON.stringify({
  15895. text: [raw],
  15896. target_lang: getTargetLanguage("deepl"),
  15897. split_sentences: "1",
  15898. ...(OJBetter.deepl.enableEmphasisProtection ||
  15899. OJBetter.deepl.enableLinkProtection
  15900. ? { tag_handling: "html" }
  15901. : {}),
  15902. ...Object.assign({}, ...OJBetter.deepl.config.data),
  15903. });
  15904.  
  15905. const options = {
  15906. method: "POST",
  15907. url: OJBetter.deepl.config.proxy || "https://api.deepl.com/v2/translate",
  15908. headers: {
  15909. Authorization: `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
  15910. "Content-Type": "application/json",
  15911. ...Object.assign({}, ...OJBetter.deepl.config.header),
  15912. },
  15913. data: data,
  15914. onload: (response) => response.responseText,
  15915. onerror: (error) => console.error(error),
  15916. };
  15917.  
  15918. return await BaseTranslate(
  15919. options,
  15920. (res) => JSON.parse(res).translations[0].text
  15921. );
  15922. }
  15923.  
  15924. /**
  15925. * 使用 DeepLX 进行翻译
  15926. * @param {String} text 原文
  15927. * @returns {Promise<TransRawData>} 翻译结果对象
  15928. */
  15929. async function translate_deeplx(text) {
  15930. const options = {
  15931. method: "POST",
  15932. url: OJBetter.deepl.config.proxy || "https://api.deeplx.org/translate",
  15933. data: JSON.stringify({
  15934. text: text,
  15935. source_lang: "EN",
  15936. target_lang: getTargetLanguage("deepl"),
  15937. }),
  15938. headers: {
  15939. "Content-Type": "application/json",
  15940. ...(OJBetter.deepl.config.key
  15941. ? { Authorization: `Bearer ${OJBetter.deepl.config.key}` }
  15942. : {}),
  15943. },
  15944. responseType: "json",
  15945. };
  15946.  
  15947. return await BaseTranslate(options, (res) => {
  15948. const parsedResponse = JSON.parse(res);
  15949. if (parsedResponse.code === 200 && parsedResponse.data) {
  15950. return parsedResponse.data;
  15951. } else {
  15952. throw new Error("Translation failed or invalid response format.");
  15953. }
  15954. });
  15955. }
  15956.  
  15957. function getTimeStamp(iCount) {
  15958. const ts = Date.now();
  15959. if (iCount !== 0) {
  15960. iCount = iCount + 1;
  15961. return ts - (ts % iCount) + iCount;
  15962. } else {
  15963. return ts;
  15964. }
  15965. }
  15966.  
  15967. /**
  15968. * 讯飞听见翻译
  15969. * @param {String} text 要翻译的文本
  15970. * @returns {Promise<TransRawData>} 翻译结果对象
  15971. */
  15972. async function translate_iflyrec(text) {
  15973. const options = {
  15974. method: "POST",
  15975. url: "https://www.iflyrec.com/TranslationService/v1/textTranslation",
  15976. data: JSON.stringify({
  15977. from: "2",
  15978. to: getTargetLanguage("iflyrec"),
  15979. contents: [
  15980. {
  15981. text: text,
  15982. frontBlankLine: 0,
  15983. },
  15984. ],
  15985. }),
  15986. anonymous: true,
  15987. headers: {
  15988. "Content-Type": "application/json",
  15989. Origin: "https://www.iflyrec.com",
  15990. },
  15991. responseType: "json",
  15992. };
  15993. return await BaseTranslate(options, (res) =>
  15994. JSON.parse(res).biz[0].translateResult.replace(/\\n/g, "\n\n")
  15995. );
  15996. }
  15997.  
  15998. /**
  15999. * 有道翻译
  16000. * @param {string} raw 原文
  16001. * @returns {Promise<TransRawData>} 翻译结果对象
  16002. */
  16003. async function translate_youdao_web(raw) {
  16004. /**
  16005. * 生成cookie
  16006. */
  16007. const getcookie = (() => {
  16008. // 生成IP地址
  16009. const generateIP = () => {
  16010. return `${OJB_getRandomNumberInRange(
  16011. 1,
  16012. 255
  16013. )}.${OJB_getRandomNumberInRange(1, 255)}.${OJB_getRandomNumberInRange(
  16014. 1,
  16015. 255
  16016. )}.${OJB_getRandomNumberInRange(1, 255)}`;
  16017. };
  16018. // 生成OUTFOX_SEARCH_USER_ID_NCOO的值
  16019. const OUTFOX_SEARCH_USER_ID_NCOO = `${OJB_getRandomNumberInRange(
  16020. 100000000,
  16021. 999999999
  16022. )}.${OJB_getRandomNumberInRange(100000000, 999999999)}`;
  16023. // 生成OUTFOX_SEARCH_USER_ID的值
  16024. const OUTFOX_SEARCH_USER_ID = `${OJB_getRandomNumberInRange(
  16025. 100000000,
  16026. 999999999
  16027. )}@${generateIP()}`;
  16028. return `OUTFOX_SEARCH_USER_ID_NCOO=${OUTFOX_SEARCH_USER_ID_NCOO}; OUTFOX_SEARCH_USER_ID=${OUTFOX_SEARCH_USER_ID}`;
  16029. })();
  16030.  
  16031. /**
  16032. * 生成sign
  16033. */
  16034. const getsign = (e, t) => {
  16035. const d = "fanyideskweb";
  16036. const u = "webfanyi";
  16037.  
  16038. function A(e) {
  16039. return CryptoJS.MD5(e.toString()).toString(CryptoJS.enc.Hex);
  16040. }
  16041. return A(`client=${d}&mysticTime=${e}&product=${u}&key=${t}`);
  16042. };
  16043.  
  16044. // 获取key
  16045. const getKey = async (sign, time) => {
  16046. const urlData = {
  16047. "keyid": "webfanyi-key-getter",
  16048. "sign": sign,
  16049. "client": "fanyideskweb",
  16050. "product": "webfanyi",
  16051. "appVersion": "1.0.0",
  16052. "vendor": "web",
  16053. "pointParam": "client,mysticTime,product",
  16054. "mysticTime": time,
  16055. "keyfrom": "fanyi.web",
  16056. "mid": "1",
  16057. "screen": "1",
  16058. "model": "1",
  16059. "network": "wifi",
  16060. "abtest": "0",
  16061. "yduuid": "abcdefg"
  16062. }
  16063. const options = {
  16064. method: "GET",
  16065. url: `https://dict.youdao.com/webtranslate/key?${new URLSearchParams(urlData)}`,
  16066. headers: {
  16067. Accept: "application/json, text/plain, */*",
  16068. Origin: "https://fanyi.youdao.com",
  16069. Referer: "https://fanyi.youdao.com/",
  16070. },
  16071. };
  16072. const response = await OJB_GMRequest(options);
  16073. if (!response.responseText)
  16074. throw new OJB_GMError(
  16075. "network",
  16076. "An unknown network error occurred!",
  16077. response
  16078. );
  16079. const {data} = JSON.parse(response.responseText);
  16080. return data;
  16081. }
  16082.  
  16083. /**
  16084. * 解码方法
  16085. * @param {string} src 待解码的字符串
  16086. * @returns {Object} 解码后的数据
  16087. */
  16088. const decode = function (src, key, iv) {
  16089. // 解码URL安全的Base64
  16090. const decodeUrlSafeBase64 = (str) => {
  16091. let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
  16092. return base64;
  16093. };
  16094.  
  16095. const keyHash = CryptoJS.MD5(key).toString();
  16096. const ivHash = CryptoJS.MD5(iv).toString();
  16097.  
  16098. const keyForAES = keyHash.substring(0, 32);
  16099. const ivForAES = ivHash.substring(0, 32);
  16100.  
  16101. // 解码
  16102. const decodedBase64 = decodeUrlSafeBase64(src);
  16103.  
  16104. // 解密
  16105. const decrypted = CryptoJS.AES.decrypt(
  16106. {
  16107. ciphertext: CryptoJS.enc.Base64.parse(decodedBase64),
  16108. },
  16109. CryptoJS.enc.Hex.parse(keyForAES),
  16110. {
  16111. iv: CryptoJS.enc.Hex.parse(ivForAES),
  16112. mode: CryptoJS.mode.CBC,
  16113. padding: CryptoJS.pad.Pkcs7,
  16114. }
  16115. );
  16116.  
  16117. // 将解密结果转换为Utf8字符串
  16118. const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
  16119.  
  16120. // 处理JSON字符串并解析
  16121. const jsonStr = decryptedStr.substring(
  16122. 0,
  16123. decryptedStr.lastIndexOf("}") + 1
  16124. );
  16125. return JSON.parse(jsonStr);
  16126. };
  16127. // 整理数据
  16128. const organizeTranslation = (data) => {
  16129. // 提取translateResult数组
  16130. const { translateResult } = data;
  16131.  
  16132. // 整理tgt字段
  16133. return translateResult
  16134. .flat()
  16135. .map((item) => item.tgt)
  16136. .join("");
  16137. };
  16138.  
  16139. /**
  16140. * 生成随机时间戳
  16141. */
  16142. const time = new Date().getTime();
  16143. const t = "asdjnjfenknafdfsdfsd";
  16144. const sign = getsign(time, t);
  16145. const {secretKey, aesKey, aesIv} = await getKey(sign, time);
  16146. // 表单数据
  16147. const data = {
  16148. i: raw,
  16149. from: "auto",
  16150. to: getTargetLanguage("youdao"),
  16151. useTerm: "false",
  16152. domain: "0",
  16153. dictResult: "true",
  16154. keyid: "webfanyi",
  16155. sign: getsign(time, secretKey),
  16156. client: "fanyideskweb",
  16157. product: "webfanyi",
  16158. appVersion: "1.0.0",
  16159. vendor: "web",
  16160. pointParam: "client,mysticTime,product",
  16161. mysticTime: time,
  16162. keyfrom: "fanyi.web",
  16163. mid: "1",
  16164. screen: "1",
  16165. model: "1",
  16166. network: "wifi",
  16167. abtest: "0",
  16168. yduuid: "abcdefg",
  16169. };
  16170. const options = {
  16171. method: "POST",
  16172. url: "https://dict.youdao.com/webtranslate",
  16173. data: new URLSearchParams(data),
  16174. anonymous: true,
  16175. cookie: getcookie,
  16176. headers: {
  16177. "Accept": "application/json, text/plain, */*",
  16178. 'Sec-Fetch-Site': 'same-site',
  16179. "Content-Type": "application/x-www-form-urlencoded",
  16180. Referer: "https://fanyi.youdao.com/",
  16181. },
  16182. };
  16183. return await BaseTranslate(options, (res) => {
  16184. const decodeData = decode(res, aesKey, aesIv);
  16185. const result = organizeTranslation(decodeData);
  16186. return result.replace(/\n/g, "\n\n");
  16187. });
  16188. }
  16189.  
  16190. /**
  16191. * google翻译
  16192. * @param {string} raw 原文
  16193. * @returns {Promise<TransRawData>} 翻译结果对象
  16194. */
  16195. async function translate_gg(raw) {
  16196. const params = `tl=${getTargetLanguage("google")}&q=${encodeURIComponent(
  16197. raw
  16198. )}`;
  16199. const options = {
  16200. method: "GET",
  16201. url: `https://translate.google.com/m?${params}`,
  16202. };
  16203. return await BaseTranslate(
  16204. options,
  16205. (res) =>
  16206. $(res).filter(".result-container").text() ||
  16207. $(res).find(".result-container").text()
  16208. );
  16209. }
  16210.  
  16211. /**
  16212. * 彩云翻译
  16213. * @param {string} raw 原文
  16214. * @returns {Promise<TransRawData>} 翻译结果对象
  16215. */
  16216. async function translate_caiyun(raw) {
  16217. const source = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";
  16218. const dic = [
  16219. ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
  16220. ].reduce((dic, current, index) => {
  16221. dic[current] = source[index];
  16222. return dic;
  16223. }, {});
  16224. const browser_id = CryptoJS.MD5(Math.random().toString()).toString();
  16225. const caiyun_jwt = await (async () => {
  16226. const options = {
  16227. method: "POST",
  16228. url: "https://api.interpreter.caiyunai.com/v1/user/jwt/generate",
  16229. headers: {
  16230. "content-type": "application/json",
  16231. "x-authorization": "token:qgemv4jr1y38jyq6vhvi",
  16232. origin: "https://fanyi.caiyunapp.com",
  16233. },
  16234. data: JSON.stringify({ browser_id }),
  16235. };
  16236. const res = await OJB_GMRequest(options);
  16237. return JSON.parse(res.responseText).jwt;
  16238. })();
  16239.  
  16240. // 解码
  16241. const decodeUnicode = (str) => {
  16242. const decoder = new TextDecoder();
  16243. const data = Uint8Array.from(atob(str), (c) => c.charCodeAt(0));
  16244. return decoder.decode(data);
  16245. };
  16246. const decoder = (line) =>
  16247. decodeUnicode([...line].map((i) => dic[i] || i).join(""));
  16248.  
  16249. const options = {
  16250. method: "POST",
  16251. url: "https://api.interpreter.caiyunai.com/v1/translator",
  16252. data: JSON.stringify({
  16253. source: raw.split("\n"),
  16254. browser_id: browser_id,
  16255. trans_type: getTargetLanguage("caiyun"),
  16256. request_id: "web_fanyi",
  16257. media: "text",
  16258. os_type: "web",
  16259. dict: true,
  16260. cached: true,
  16261. replaced: true,
  16262. style: "formal",
  16263. model: "",
  16264. detect: true,
  16265. }),
  16266. headers: {
  16267. "content-type": "application/json;charset=UTF-8",
  16268. "x-authorization": "token:qgemv4jr1y38jyq6vhvi",
  16269. "t-authorization": caiyun_jwt,
  16270. },
  16271. };
  16272. return await BaseTranslate(options, (res) =>
  16273. JSON.parse(res).target.map(decoder).join("\n")
  16274. );
  16275. }
  16276.  
  16277. /**
  16278. * ChatGPT
  16279. * @param {string} raw 原文
  16280. * @returns {Promise<TransRawData>} 翻译结果对象
  16281. */
  16282. async function translate_openai(raw) {
  16283. const modelDefault = "gpt-3.5-turbo";
  16284. const lang = getTargetLanguage("openai");
  16285. let prompt = "";
  16286. if (OJBetter.chatgpt.customPrompt) {
  16287. prompt = `\n${OJBetter.chatgpt.customPrompt}`;
  16288. if (!OJBetter.chatgpt.asSystemPrompt) {
  16289. prompt += `\n${raw}`;
  16290. }
  16291. } else {
  16292. prompt = `
  16293. As a professional English translator, your task is to accurately translate a segment of an algorithm programming competition question into ${lang}.
  16294. The translation should use professional terms and maintain the text format, including ${
  16295. OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru
  16296. ? "keeping the LaTeX equations unchanged."
  16297. : "keeping the brackets【】, HTML tags, and their content unchanged."
  16298. }
  16299. After translation, please ensure that the ${lang} version conforms to normal expression habits.
  16300. What I need is a carefully polished ${lang} translation of my question segment. ${
  16301. OJBetter.chatgpt.asSystemPrompt
  16302. ? ""
  16303. : `The segment to be translated is as follows: "
  16304. ${raw}
  16305. "`
  16306. }`;
  16307. }
  16308. const data = {
  16309. model: OJBetter.chatgpt.config.model || modelDefault,
  16310. messages: OJBetter.chatgpt.asSystemPrompt
  16311. ? [
  16312. {
  16313. role: "system",
  16314. content: prompt,
  16315. },
  16316. {
  16317. role: "user",
  16318. content: raw,
  16319. },
  16320. ]
  16321. : [
  16322. {
  16323. role: "user",
  16324. content: prompt,
  16325. },
  16326. ],
  16327. temperature: 0.7,
  16328. ...Object.assign({}, ...OJBetter.chatgpt.config.data),
  16329. };
  16330. const options = {
  16331. method: "POST",
  16332. url:
  16333. OJBetter.chatgpt.config.proxy ||
  16334. "https://api.openai.com/v1/chat/completions",
  16335. data: JSON.stringify(data),
  16336. responseType: "json",
  16337. headers: {
  16338. "Content-Type": "application/json",
  16339. Authorization: "Bearer " + OJBetter.chatgpt.config.key,
  16340. ...Object.assign({}, ...OJBetter.chatgpt.config.header),
  16341. },
  16342. };
  16343. return await BaseTranslate(
  16344. options,
  16345. (res) => res,
  16346. undefined,
  16347. (response) => response.response.choices[0].message.content
  16348. );
  16349. }
  16350.  
  16351. /**
  16352. * ChatGPT 流式传输
  16353. * @param {string} raw 原文
  16354. * @param {TranslateDiv} translateDiv 翻译结果面板
  16355. * @returns {Promise<TransRawData>} 翻译结果对象
  16356. */
  16357. async function translate_openai_stream(raw, translateDiv) {
  16358. const result = {
  16359. done: true,
  16360. checkPassed: null,
  16361. response: null,
  16362. responseText: null,
  16363. text: "",
  16364. error: null,
  16365. message: null,
  16366. };
  16367. const helpText = i18next.t("error.basic", { ns: "translator" }); // 基本帮助提示信息
  16368. try {
  16369. for await (const delta of openai_stream(raw)) {
  16370. result.text += delta;
  16371. // 翻译结果面板更新
  16372. translateDiv.updateTranslateDiv(
  16373. result.text,
  16374. !(
  16375. (OJBetter.typeOfPage.is_oldLatex ||
  16376. OJBetter.typeOfPage.is_acmsguru) &&
  16377. !OJBetter.translation.forceTurndownConversion
  16378. ),
  16379. false
  16380. );
  16381. }
  16382. return result;
  16383. } catch (err) {
  16384. console.warn(err);
  16385. result.error = {
  16386. message: err.message || null,
  16387. stack: err.stack
  16388. ? err.stack.replace(/\n/g, "<br>").replace(/\s/g, "&nbsp;")
  16389. : null,
  16390. enumerable: err,
  16391. source: "openai_stream",
  16392. };
  16393. result.message = `${i18next.t("error.GMRequest", {
  16394. ns: "translator",
  16395. })}${helpText}`;
  16396. }
  16397.  
  16398. return result;
  16399. }
  16400.  
  16401. /**
  16402. * 流式传输
  16403. * @param {string} raw 原文
  16404. * @returns {AsyncGenerator<string>} 返回 AsyncGenerator
  16405. */
  16406. async function* openai_stream(raw) {
  16407. const modelDefault = "gpt-3.5-turbo";
  16408. const lang = getTargetLanguage("openai");
  16409. let prompt = "";
  16410. if (OJBetter.chatgpt.customPrompt) {
  16411. prompt = `\n${OJBetter.chatgpt.customPrompt}`;
  16412. if (!OJBetter.chatgpt.asSystemPrompt) {
  16413. prompt += `\n${raw}`;
  16414. }
  16415. } else {
  16416. prompt = `
  16417. As a professional English translator, your task is to accurately translate a segment of an algorithm programming competition question into ${lang}.
  16418. The translation should use professional terms and maintain the text format, including ${
  16419. OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru
  16420. ? "keeping the LaTeX equations unchanged."
  16421. : "keeping the brackets【】, HTML tags, and their content unchanged."
  16422. }
  16423. After translation, please ensure that the ${lang} version conforms to normal expression habits.
  16424. What I need is a carefully polished ${lang} translation of my question segment. ${
  16425. OJBetter.chatgpt.asSystemPrompt
  16426. ? ""
  16427. : `The segment to be translated is as follows: "
  16428. ${raw}
  16429. "`
  16430. }`;
  16431. }
  16432. const data = {
  16433. model: OJBetter.chatgpt.config.model || modelDefault,
  16434. messages: OJBetter.chatgpt.asSystemPrompt
  16435. ? [
  16436. {
  16437. role: "system",
  16438. content: prompt,
  16439. },
  16440. {
  16441. role: "user",
  16442. content: raw,
  16443. },
  16444. ]
  16445. : [
  16446. {
  16447. role: "user",
  16448. content: prompt,
  16449. },
  16450. ],
  16451. temperature: 0.7,
  16452. stream: true,
  16453. ...Object.assign({}, ...OJBetter.chatgpt.config.data),
  16454. };
  16455. const options = {
  16456. method: "POST",
  16457. url:
  16458. OJBetter.chatgpt.config.proxy ||
  16459. "https://api.openai.com/v1/chat/completions",
  16460. data: JSON.stringify(data),
  16461. responseType: "stream",
  16462. headers: {
  16463. "Content-Type": "application/json",
  16464. Authorization: "Bearer " + OJBetter.chatgpt.config.key,
  16465. ...Object.assign({}, ...OJBetter.chatgpt.config.header),
  16466. },
  16467. };
  16468. const response = await OJB_GMRequest(options, true);
  16469. const reader = response.response.getReader();
  16470. const decoder = new TextDecoder();
  16471. let buffer = ""; // 用于累积数据片段的缓冲区
  16472.  
  16473. while (true) {
  16474. const { done, value } = await reader.read();
  16475. if (done) break;
  16476. buffer += decoder.decode(value, { stream: true }); // 将新的数据片段追加到缓冲区
  16477. let lines = buffer.split("\n\n"); // 处理累积的数据
  16478.  
  16479. // 缓冲区的最后一行可能还未完整接收,保留在缓冲区中,-1
  16480. for (let i = 0; i < lines.length - 1; i++) {
  16481. let line = lines[i];
  16482. line = line.substring(5); // 移除 'data:' 前缀
  16483. if (line.includes("[DONE]")) {
  16484. return; // End
  16485. }
  16486. try {
  16487. let data = JSON.parse(line);
  16488. let delta = data["choices"][0]["delta"];
  16489. let content = delta["content"] ? delta["content"] : "";
  16490. yield content; // 传递数据给调用者
  16491. } catch (error) {
  16492. console.warn(`Error parsing JSON: ${error}\n\nError data: ${line}`);
  16493. }
  16494. }
  16495.  
  16496. // 保留最后一行在缓冲区中
  16497. buffer = lines.slice(-1);
  16498. }
  16499.  
  16500. return buffer;
  16501. }
  16502.  
  16503. /**
  16504. * @typedef {Object} CheckResponseResult
  16505. * @property {boolean} status 检查是否通过
  16506. * @property {string} message 检查失败时的消息
  16507. */
  16508.  
  16509. /**
  16510. * @typedef {Object} ErrorResponse
  16511. * @property {Object} message 错误消息
  16512. * @property {Object} stack 错误堆栈
  16513. * @property {Object} enumerable 可枚举的错误属性
  16514. * @property {string} source 错误来源
  16515. */
  16516.  
  16517. /**
  16518. * @typedef {Object} TransRawData
  16519. * @property {boolean} done 操作是否完成
  16520. * @property {CheckResponseResult|null} checkPassed 检查是否通过的结果
  16521. * @property {Object|null} response 响应对象
  16522. * @property {string|null} text 处理后的文本
  16523. * @property {ErrorResponse} error 错误列表
  16524. * @property {string|null} message 可能的消息
  16525. */
  16526.  
  16527. /**
  16528. * 通用翻译函数
  16529. * @param {Object} options GM_xmlhttpRequest 的参数
  16530. * @param {Function} processer 响应再处理函数,它接收响应文本,并应返回处理后的文本。
  16531. * @param {Function} checkResponse 检查文本是否符合预期的函数,它接收文本,并返回一个Object,包含状态和信息。默认为返回 { status: true, message: 'ok' }
  16532. * @param {Function} getResponseText 重写响应文本获取函数,它接收response,并返回响应文本。 默认为 response.responseText
  16533. * @returns {Promise<TransRawData>} 返回 Promise,其解析值为翻译结果对象
  16534. */
  16535. async function BaseTranslate(
  16536. options,
  16537. processer,
  16538. checkResponse = () => {
  16539. return { status: true, message: "ok" };
  16540. },
  16541. getResponseText = (response) => response.responseText
  16542. ) {
  16543. const result = {
  16544. done: false,
  16545. checkPassed: null,
  16546. response: null,
  16547. responseText: null,
  16548. text: "",
  16549. error: null,
  16550. message: null,
  16551. };
  16552. const helpText = i18next.t("error.basic", { ns: "translator" }); // 基本帮助提示信息
  16553. const toDo = async () => {
  16554. try {
  16555. result.response = await OJB_GMRequest(options);
  16556. result.responseText = result.response.responseText;
  16557. result.text = getResponseText(result.response);
  16558. } catch (err) {
  16559. console.warn(err);
  16560. result.error = {
  16561. message: err.message || null,
  16562. stack: err.stack
  16563. ? err.stack.replace(/\n/g, "<br>").replace(/\s/g, "&nbsp;")
  16564. : null,
  16565. enumerable: err,
  16566. source: "GMRequest",
  16567. };
  16568. result.message = `${i18next.t("error.GMRequest", {
  16569. ns: "translator",
  16570. })}${helpText}`;
  16571. throw result;
  16572. }
  16573. try {
  16574. result.text = processer(result.text);
  16575. } catch (err) {
  16576. console.warn(err);
  16577. result.error = {
  16578. message: err.message || null,
  16579. stack: err.stack
  16580. ? err.stack.replace(/\n/g, "<br>").replace(/\s/g, "&nbsp;")
  16581. : null,
  16582. enumerable: err,
  16583. source: "processer",
  16584. };
  16585. result.message = `${i18next.t("error.processer", {
  16586. ns: "translator",
  16587. })}${helpText}`;
  16588. throw result;
  16589. }
  16590. try {
  16591. result.checkPassed = checkResponse(result.text);
  16592. if (result.checkPassed.status) result.done = true;
  16593. else result.message = result.checkPassed.message;
  16594. return result;
  16595. } catch (err) {
  16596. console.warn(err);
  16597. result.error = {
  16598. message: err.message || null,
  16599. stack: err.stack
  16600. ? err.stack.replace(/\n/g, "<br>").replace(/\s/g, "&nbsp;")
  16601. : null,
  16602. enumerable: err,
  16603. source: "checkResponse",
  16604. };
  16605. result.message = `${i18next.t("error.checkResponse", {
  16606. ns: "translator",
  16607. })}${helpText}`;
  16608. throw result;
  16609. }
  16610. };
  16611.  
  16612. return await OJB_promiseRetryWrapper(toDo, {
  16613. maxRetries: 3,
  16614. errorHandler: (err, maxRetries, attemptsLeft) => {
  16615. const detailedError = {
  16616. maxRetries: maxRetries,
  16617. attemptsLeft: attemptsLeft,
  16618. ...err,
  16619. };
  16620. return detailedError;
  16621. },
  16622. });
  16623. }
  16624.  
  16625. /**
  16626. * 查询服务余额
  16627. * @param {Object} quotaConfig - 配额配置对象
  16628. * @returns {Promise} 返回包含余额信息的 Promise
  16629. */
  16630. async function queryServerBalance(quotaConfig) {
  16631. // 确保传入了有效的配置对象
  16632. if (!quotaConfig || !quotaConfig.url) {
  16633. return Promise.reject(new Error("Quota configuration is missing."));
  16634. }
  16635.  
  16636. // 准备请求选项
  16637. const requestOptions = {
  16638. method: quotaConfig.method || "GET",
  16639. url: quotaConfig.url,
  16640. headers: {
  16641. ...Object.assign({}, ...quotaConfig.header),
  16642. },
  16643. };
  16644.  
  16645. // 只有在 method 不是 'GET' 时才添加 data
  16646. if (requestOptions.method.toUpperCase() !== 'GET' && quotaConfig.data) {
  16647. requestOptions.data = JSON.stringify({ ...Object.assign({}, ...quotaConfig.data) });
  16648. }
  16649.  
  16650. // 发送请求并返回 Promise
  16651. return OJB_GMRequest(requestOptions)
  16652. .then((response) => {
  16653. try {
  16654. const responseData = JSON.parse(response.responseText);
  16655. // 从响应数据中提取余额
  16656. const surplusPath = quotaConfig.surplus;
  16657. const surplusValue = OJB_evaluatePathOrExpression(
  16658. responseData,
  16659. surplusPath
  16660. );
  16661. return surplusValue;
  16662. } catch (error) {
  16663. return Promise.reject(new Error("Failed to parse balance response."));
  16664. }
  16665. })
  16666. .catch((error) => {
  16667. console.warn("Error querying balance:", error);
  16668. return Promise.reject(error);
  16669. });
  16670. }
  16671.  
  16672. /**
  16673. * 确认 jQuery 已加载
  16674. * @param {number} retryDelay 重试延迟(毫秒)
  16675. * @returns {Promise<void>}
  16676. */
  16677. async function ensureJQueryIsLoaded(retryDelay = 50) {
  16678. while (typeof jQuery === "undefined") {
  16679. console.warn(`JQuery is not loaded. Retry after ${retryDelay} ms.`);
  16680. await OJB_delay(retryDelay);
  16681. retryDelay = Math.min(retryDelay * 2, 2000);
  16682. }
  16683. }
  16684.  
  16685. /**
  16686. * 加载必须的函数
  16687. * @returns {Promise} 加载提示信息
  16688. */
  16689. async function loadRequiredFunctions() {
  16690. await initVar(); // 初始化全局变量
  16691. return Promise.allSettled([
  16692. initDB(), // 连接数据库
  16693. initI18next(), // i18next初始化
  16694. initButtonFunc(), // 加载按钮相关函数
  16695. initHTML2MarkDown(), // 初始化html2markdown转换器
  16696. checkScriptVersion(), // 更新检查
  16697. ...(OJBetter.typeOfPage.is_acmsguru ? [acmsguruReblock()] : []), // 为acmsguru题面重新划分div
  16698. ]);
  16699. }
  16700.  
  16701. /**
  16702. * DOM加载后即可执行
  16703. */
  16704. function initOnDOMReady() {
  16705. showAnnounce(); // 显示公告
  16706. showWarnMessage(); // 显示警告消息
  16707. initSettingsPanel(); // 加载设置按钮面板
  16708. initMonacoEditor(); // 初始化monaco编辑器资源
  16709. localizeWebsite(); // 网站本地化替换
  16710. addDependencyStyles(); // 添加一些依赖库的样式
  16711. addI18nStyles(); // 添加包含i18n内容的样式
  16712. if (OJBetter.basic.expandFoldingblocks) ExpandFoldingblocks(); // 折叠块展开
  16713. if (OJBetter.basic.renderPerfOpt) RenderPerfOpt(); // 折叠块渲染优化
  16714. if (OJBetter.basic.selectElementPerfOpt) SelectElementPerfOpt(); // 下拉选择框性能优化
  16715. if (OJBetter.typeOfPage.is_problem) {
  16716. const problemPageLinkbar = new ProblemPageLinkbar(); // 创建题目页相关链接栏
  16717. if (OJBetter.basic.showCF2vjudge) CF2vjudge(problemPageLinkbar); // 跳转到Vjudge按钮
  16718. if (OJBetter.basic.showJumpToLuogu) CF2luogu(problemPageLinkbar); // 跳转到洛谷按钮
  16719. if (OJBetter.clist.enabled.problem)
  16720. showRatingByClist_problem(problemPageLinkbar); // problem页显示Rating
  16721. }
  16722. if (OJBetter.typeOfPage.is_contest) {
  16723. if (OJBetter.clist.enabled.contest) showRatingByClist_contest(); // contest页显示Rating
  16724. }
  16725. if (OJBetter.typeOfPage.is_problemset) {
  16726. if (OJBetter.clist.enabled.problemset) showRatingByClist_problemset(); // problemset页显示Rating
  16727. }
  16728. if (OJBetter.typeOfPage.is_problem && OJBetter.monaco.enableOnProblemPage) {
  16729. addProblemPageCodeEditor(); // 添加题目页代码编辑器
  16730. }
  16731. if (
  16732. OJBetter.preference.judgeStatusReplaceText &&
  16733. (OJBetter.typeOfPage.is_submissions || OJBetter.typeOfPage.is_statePage)
  16734. ) {
  16735. judgeStatusReplace(); // 评测结果替换
  16736. }
  16737. }
  16738.  
  16739. /**
  16740. * 需要在页面资源完全加载后执行的函数
  16741. */
  16742. function onResourcesReady(loadingMessage) {
  16743. if (OJBetter.preference.showLoading)
  16744. loadingMessage.updateStatus(
  16745. `${OJBetter.state.name} —— ${i18next.t("loadFunc", { ns: "alert" })}`
  16746. );
  16747. initializeInParallel(loadingMessage);
  16748. initializeSequentially(loadingMessage);
  16749. }
  16750.  
  16751. /**
  16752. * 可以异步并行的函数
  16753. */
  16754. function initializeInParallel(loadingMessage) {
  16755. if (OJBetter.basic.darkMode == "dark") darkModeStyleAdjustment(); // 黑暗模式额外的处理事件
  16756. if (OJBetter.basic.commentPaging) CommentPagination(); // 评论区分页
  16757. if (OJBetter.translation.comment.transMode == "2") multiChoiceTranslation(); // 选段翻译支持
  16758. if (OJBetter.monaco.beautifyPreBlocks) beautifyPreBlocksWithMonaco(); // 美化Pre代码块
  16759. if (OJBetter.basic.hiddenProblemTag) hiddenProblemTag(); // 隐藏题目问题标签
  16760. }
  16761.  
  16762. /**
  16763. * 必须按序执行的函数
  16764. */
  16765. async function initializeSequentially(loadingMessage) {
  16766. await addConversionButton(); // 添加MD/复制/翻译按钮
  16767. if (
  16768. (OJBetter.typeOfPage.is_problem ||
  16769. OJBetter.typeOfPage.is_completeProblemset) &&
  16770. OJBetter.translation.memory.enabled
  16771. ) {
  16772. await initTransResultsRecover(); // 翻译结果恢复功能初始化
  16773. }
  16774. if (OJBetter.translation.auto.enabled) {
  16775. await initTransWhenViewable(); // 自动翻译
  16776. }
  16777. if (OJBetter.basic.standingsRecolor && OJBetter.typeOfPage.is_cfStandings) {
  16778. await recolorStandings(); // cf赛制榜单重新着色
  16779. }
  16780. if (OJBetter.preference.showLoading) {
  16781. loadingMessage.updateStatus(
  16782. `${OJBetter.state.name} —— ${i18next.t("loadSuccess", { ns: "alert" })}`,
  16783. "success",
  16784. 3000
  16785. );
  16786. }
  16787. }
  16788.  
  16789. /**
  16790. * 主方法
  16791. */
  16792. async function main() {
  16793. await ensureJQueryIsLoaded(); // 等待jQuery加载
  16794. const loadingMessage = new LoadingMessage();
  16795. await loadRequiredFunctions(); // 加载必须的函数
  16796. initOnDOMReady(); // DOM加载后即可执行的函数
  16797. if (OJBetter.preference.showLoading)
  16798. loadingMessage.updateStatus(
  16799. `${OJBetter.state.name} —— ${i18next.t("onload", { ns: "alert" })}`
  16800. );
  16801.  
  16802. // 检查页面资源是否已经完全加载
  16803. if (OJBetter.state.notWaiteLoaded) {
  16804. onResourcesReady(loadingMessage);
  16805. } else {
  16806. if (document.readyState === "complete") {
  16807. onResourcesReady(loadingMessage);
  16808. } else {
  16809. window.addEventListener("load", () => onResourcesReady(loadingMessage));
  16810. }
  16811. }
  16812. }
  16813.  
  16814. // ------------------------------
  16815. // 脚本加载入口
  16816. if (document.readyState === "loading") {
  16817. document.addEventListener("DOMContentLoaded", main);
  16818. } else {
  16819. main(); // 如果DOMContentLoaded已经触发,立即执行
  16820. }
  16821. // ------------------------------
  16822.  
  16823. // ------------------------------
  16824. // 配置自动迁移代码(将在10个小版本后移除-1.83)
  16825. // ------------------------------
  16826.  
  16827. {
  16828. let bottomZh_CN = GM_getValue("bottomZh_CN");
  16829. if (bottomZh_CN !== undefined) {
  16830. if (bottomZh_CN == true) {
  16831. GM_setValue("localizationLanguage", "zh");
  16832. } else {
  16833. GM_setValue("localizationLanguage", "initial");
  16834. }
  16835. GM_deleteValue("bottomZh_CN");
  16836. location.reload();
  16837. }
  16838. }
  16839. {
  16840. let config = GM_getValue("chatgpt-config");
  16841. if (config && config !== undefined) {
  16842. let index = parseInt(config.choice, 10);
  16843. if (index == -1) config.choice = "";
  16844. else config.choice = config.configurations[index].note;
  16845. config.configurations.forEach(function (item) {
  16846. item.name = item.note;
  16847. delete item.note;
  16848. });
  16849. GM_deleteValue("chatgpt-config");
  16850. GM_setValue("chatgpt_config", config);
  16851. location.reload();
  16852. }
  16853. }
  16854. {
  16855. let config = GM_getValue("Complet_config");
  16856. if (config && config.changed === undefined) {
  16857. config.changed = true; // 设置一个迁移标志
  16858. config.configurations.forEach(function (item) {
  16859. if (item.note !== undefined) {
  16860. item.name = item.note;
  16861. delete item.note;
  16862. }
  16863. });
  16864. GM_setValue("Complet_config", config);
  16865. location.reload();
  16866. }
  16867. }