Atcoder Better!

A Tampermonkey script for AtCoder that enhances functionality and interface.

As of 2024-06-05. See the latest version.

  1. // ==UserScript==
  2. // @name Atcoder Better!
  3. // @namespace https://greatest.deepsurf.us/users/747162
  4. // @version 1.16.0
  5. // @description 一个适用于 AtCoder 的 Tampermonkey 脚本,增强功能与界面。
  6. // @author 北极小狐
  7. // @match *://atcoder.jp/*
  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 greatest.deepsurf.us
  23. // @connect staticfile.net
  24. // @connect aowuucdn.oss-cn-beijing.aliyuncs.com
  25. // @connect aowuucdn.oss-accelerate.aliyuncs.com
  26. // @connect 127.0.0.1
  27. // @connect *
  28. // @grant GM_xmlhttpRequest
  29. // @grant GM_info
  30. // @grant GM_setValue
  31. // @grant GM_getValue
  32. // @grant GM_listValues
  33. // @grant GM_deleteValue
  34. // @grant GM_addStyle
  35. // @grant GM_setClipboard
  36. // @grant GM_getResourceText
  37. // @icon https://aowuucdn.oss-accelerate.aliyuncs.com/atcoder.png
  38. // @require https://cdn.staticfile.net/turndown/7.1.2/turndown.min.js
  39. // @require https://cdn.staticfile.net/markdown-it/13.0.1/markdown-it.min.js
  40. // @require https://cdn.bootcdn.net/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
  41. // @require https://cdn.staticfile.net/chroma-js/2.4.2/chroma.min.js
  42. // @require https://cdn.staticfile.net/xterm/3.9.2/xterm.min.js
  43. // @require https://cdn.staticfile.net/dexie/3.2.4/dexie.min.js
  44. // @require https://cdn.staticfile.net/i18next/23.5.1/i18next.min.js
  45. // @require https://cdn.staticfile.net/i18next-http-backend/2.2.2/i18nextHttpBackend.min.js
  46. // @require https://cdn.staticfile.net/jquery-i18next/1.2.1/jquery-i18next.min.js
  47. // @require https://cdn.staticfile.net/highlight.js/11.3.1/highlight.min.js
  48. // @require https://cdn.staticfile.net/dialog-polyfill/0.5.6/dialog-polyfill.min.js
  49. // @require https://update.greatest.deepsurf.us/scripts/484742/1311040/i18nextChainedBackendjs.js
  50. // @require https://update.greatest.deepsurf.us/scripts/484743/1311041/i18next-localstorage-backendjs.js
  51. // @resource acwing_cpp_code_completer https://aowuucdn.oss-accelerate.aliyuncs.com/acwing_cpp_code_completer-0.0.11.json
  52. // @resource wandboxlist https://wandbox.org/api/list.json
  53. // @resource xtermcss https://cdn.staticfile.net/xterm/3.9.2/xterm.min.css
  54. // @resource selectpagecss https://aowuucdn.oss-accelerate.aliyuncs.com/css/selectpage.css
  55. // @resource dialogpolyfillcss https://cdn.staticfile.net/dialog-polyfill/0.5.6/dialog-polyfill.min.css
  56. // @license GPL3
  57. // @compatible Chrome
  58. // @compatible Firefox
  59. // @compatible Edge
  60. // @incompatible safari
  61. // @supportURL https://github.com/beijixiaohu/OJBetter/issues
  62. // @name:zh-TW Atcoder Better!
  63. // @name:en Atcoder Better!
  64. // @name:de Atcoder Better!
  65. // @name:fr Atcoder Better!
  66. // @name:ko Atcoder Better!
  67. // @name:pt Atcoder Better!
  68. // @name:ja Atcoder Better!
  69. // @name:es Atcoder Better!
  70. // @name:it Atcoder Better!
  71. // @name:hi Atcoder Better!
  72. // @description 一个适用于 AtCoder 的 Tampermonkey 脚本,增强功能与界面。
  73. // @description:zh-TW 一個適用於 AtCoder 的 Tampermonkey 腳本,增強功能與界面。
  74. // @description:en A Tampermonkey script for AtCoder that enhances functionality and interface.
  75. // @description:de Ein Tampermonkey-Skript für AtCoder, das Funktionalität und Benutzeroberfläche verbessert.
  76. // @description:fr Un script Tampermonkey pour AtCoder qui améliore les fonctionnalités et l'interface.
  77. // @description:ko AtCoder를 위한 Tampermonkey 스크립트로 기능과 인터페이스를 개선합니다.
  78. // @description:pt Um script Tampermonkey para AtCoder que aprimora a funcionalidade e a interface.
  79. // @description:ja AtCoder用のTampermonkeyスクリプトで機能とインターフェースを強化します。
  80. // @description:es Un script Tampermonkey para AtCoder que mejora la funcionalidad y la interfaz.
  81. // @description:it Uno script Tampermonkey per AtCoder che migliora la funzionalità e l'interfaccia.
  82. // @description:hi AtCoder के लिए एक Tampermonkey स्क्रिप्ट जो कार्यक्षमता और इंटरफ़ेस को बेहतर बनाता है।
  83. // ==/UserScript==
  84.  
  85. /**
  86. * @namespace OJBetter
  87. * @desc 主命名空间
  88. */
  89. const OJBetter = {};
  90.  
  91. /**
  92. * @namespace state
  93. * @desc 描述脚本的当前状态。
  94. * @memberof OJBetter
  95. */
  96. OJBetter.state = {
  97. /** @type {string} 脚本名*/
  98. name: GM_info.script.name,
  99. /** @type {string} 格式化后的脚本名*/
  100. formatName: undefined,
  101. /** @type {string} 版本号*/
  102. version: GM_info.script.version,
  103. /** @type {boolean?} 是否跳过页面加载等待 */
  104. notWaiteLoaded: undefined,
  105. /** @type {string} 最后公告版本,用于标识版本更新完成提示 */
  106. lastAnnounceVer: undefined,
  107. /** @type {string} 最后读取的有效公告版本 */
  108. lastReadAnnounceVer: undefined,
  109. /** @type {number} 当前已打开的模态对话框数量*/
  110. openDialogCount: 0
  111. };
  112.  
  113. /**
  114. * @namespace common
  115. * @desc 通用设置和属性。
  116. * @memberof OJBetter
  117. */
  118. OJBetter.common = {
  119. /** @type {string} 网站的主机地址 */
  120. hostAddress: location.origin,
  121. /** @type {string?} Atcoder的CSRF令牌 */
  122. at_csrf_token: undefined,
  123. /** @type {Array?} 任务队列 */
  124. taskQueue: undefined,
  125. /** @type {object} OJBetter数据库连接实例*/
  126. database: undefined,
  127. /** @type {object} turndownService实例*/
  128. turndownService: undefined,
  129. };
  130.  
  131. /**
  132. * @namespace basic
  133. * @desc 基本的用户界面设置。
  134. * @memberof OJBetter
  135. */
  136. OJBetter.basic = {
  137. /** @type {string} 黑暗模式设置 */
  138. darkMode: undefined,
  139. /** @type {boolean?} 是否展开折叠块 */
  140. expandFoldingblocks: undefined,
  141. /** @type {boolean?} 是否开启折叠块渲染性能优化 */
  142. renderPerfOpt: undefined,
  143. /** @type {boolean?} 是否开启下拉选择框性能优化 */
  144. selectElementPerfOpt: undefined,
  145. /** @type {boolean?} 评论区分页 */
  146. commentPaging: undefined,
  147. /** @type {boolean?} 显示跳转到Luogu按钮 */
  148. showJumpToLuogu: undefined,
  149. /** @type {boolean?} 显示跳转到Virtual Judge按钮 */
  150. showCF2vjudge: undefined,
  151. /** @type {boolean?} 比赛排行榜重新着色 */
  152. standingsRecolor: undefined
  153. };
  154.  
  155. /**
  156. * @namespace typeOfPage
  157. * @desc 页面类型判断。
  158. * @memberof OJBetter
  159. */
  160. OJBetter.typeOfPage = {
  161. /** @type {boolean?} 是否是轻量站 */
  162. is_mSite: false,
  163. /** @type {boolean?} 是否是acmsguru页面 */
  164. is_acmsguru: false,
  165. /** @type {boolean?} 是否是旧版LaTeX页面 */
  166. is_oldLatex: false,
  167. /** @type {boolean?} 是否是题目集页面 */
  168. is_contest: undefined,
  169. /** @type {boolean?} 是否是题目页面 */
  170. is_problem: undefined,
  171. /** @type {boolean?} 是否是完整的问题集页面 */
  172. is_completeProblemset: false,
  173. /** @type {boolean?} 是否是问题集中的问题页面 */
  174. is_problemset_problem: false,
  175. /** @type {boolean?} 是否是问题集页面 */
  176. is_problemset: false,
  177. /** @type {boolean?} 是否是Codeforces排名页面 */
  178. is_cfStandings: false,
  179. /** @type {boolean?} 是否是提交页面 */
  180. is_submitPage: false,
  181. /** @type {boolean?} 是否是代码状态页面 */
  182. is_statePage: false,
  183. /** @type {boolean?} 是否是提交记录页面 */
  184. is_submissions: false,
  185. /** @type {boolean?} 是否是主页 */
  186. is_homepage: undefined,
  187. /** @type {boolean?} 是否选择的是英语页面 */
  188. isEnglishLanguage: undefined,
  189. /** @type {boolean?} 是否是题解页面 */
  190. isEditorial: undefined,
  191. };
  192.  
  193. /**
  194. * @namespace localization
  195. * @desc 本地化设置。
  196. * @memberof OJBetter
  197. */
  198. OJBetter.localization = {
  199. /** @type {string?} 网站语言 */
  200. websiteLang: undefined,
  201. /** @type {string?} 脚本语言 */
  202. scriptLang: undefined
  203. };
  204.  
  205. /**
  206. * @namespace translation
  207. * @desc 翻译设置。
  208. * @memberof OJBetter
  209. */
  210. OJBetter.translation = {
  211. /** @type {string?} 翻译服务选择 */
  212. choice: undefined,
  213. /** @type {string?} 目标语言 */
  214. targetLang: undefined,
  215. comment: {
  216. /** @type {string?} 评论翻译服务选择 */
  217. choice: undefined,
  218. /** @type {string?} 评论翻译模式 */
  219. transMode: undefined
  220. },
  221. auto: {
  222. /** @type {boolean?} 自动翻译开关 */
  223. enabled: undefined,
  224. /** @type {number?} 短文本长度限制 */
  225. shortTextLength: undefined,
  226. mixTrans: {
  227. /** @type {boolean?} 混合翻译开关 */
  228. enabled: undefined,
  229. /** @type {Array?} 混合翻译服务列表 */
  230. servers: undefined
  231. }
  232. },
  233. memory: {
  234. /** @type {boolean?} 翻译记忆开关 */
  235. enabled: undefined,
  236. /** @type {Object?} 翻译记忆树 */
  237. ttTree: undefined
  238. },
  239. /** @type {string?} 重翻译时的行为 */
  240. retransAction: undefined,
  241. /** @type {number?} 等待时间 */
  242. waitTime: undefined,
  243. /** @type {boolean?} 替换符 */
  244. replaceSymbol: undefined,
  245. /** @type {boolean?} 过滤文本中的*号 */
  246. filterTextWithoutEmphasis: undefined
  247. };
  248.  
  249. /**
  250. * @namespace clist
  251. * @desc Clist相关设置。
  252. * @memberof OJBetter
  253. */
  254. OJBetter.clist = {
  255. enabled: {
  256. /** @type {boolean?} 比赛页面开关 */
  257. contest: undefined,
  258. /** @type {boolean?} 问题页面开关 */
  259. problem: undefined,
  260. /** @type {boolean?} 问题集页面开关 */
  261. problemset: undefined
  262. },
  263. /** @type {boolean?} Rating数据防剧透 */
  264. ratingHidden: undefined,
  265. /** @type {string?} Clist key */
  266. authorization: undefined
  267. };
  268.  
  269. /**
  270. * @namespace monaco
  271. * @desc Monaco编辑器配置。
  272. * @memberof OJBetter
  273. */
  274. OJBetter.monaco = {
  275. /** @type {boolean?} 在问题页面上启用Monaco编辑器 */
  276. enableOnProblemPage: undefined,
  277. /** @type {boolean?} 美化pre代码块 */
  278. beautifyPreBlocks: undefined,
  279. /** @type {boolean} Monaco编辑器加载完成标志 */
  280. loaderOnload: false,
  281. lsp: {
  282. /** @type {Array?} LSP套接字数组 */
  283. socket: [],
  284. /** @type {boolean?} 是否启用LSP */
  285. enabled: undefined,
  286. /** @type {string?} 工作路径 */
  287. workUri: undefined,
  288. /** @type {string?} 套接字URL */
  289. socketUrl: undefined
  290. },
  291. complet: {
  292. /** @type {boolean?} 是否启用C++代码补全模板 */
  293. cppCodeTemplate: undefined,
  294. /** @type {Object?} 自定义配置 */
  295. customConfig: undefined
  296. },
  297. /** @type {Object?} Monaco编辑器实例 */
  298. editor: null,
  299. /** @type {string?} 在线编译器选择 */
  300. onlineCompilerChoice: undefined,
  301. /** @type {string?} 记忆编译器语言选择 */
  302. compilerSelection: undefined,
  303. /** @type {string?} 当前选择的语言 */
  304. nowLangSelect: undefined,
  305. setting: {
  306. /** @type {Array?} 语言设置数组 */
  307. language: [],
  308. /** @type {string?} 位置 */
  309. position: undefined,
  310. /** @type {boolean} 位置初始化标志 */
  311. position_initialized: false,
  312. /** @type {number?} 字体大小 */
  313. fontsize: undefined,
  314. /** @type {boolean?} 鼠标滚动锁定 */
  315. alwaysConsumeMouseWheel: undefined,
  316. /** @type {boolean?} 提交代码二次确认 */
  317. isCodeSubmitDoubleConfirm: undefined,
  318. /** @type {boolean?} 测试通过后自动提交 */
  319. autoSubmitAfterPass: undefined,
  320. /** @type {string?} 提交按钮位置 */
  321. submitButtonPosition: undefined,
  322. /** @type {boolean?} 自动保存代码 */
  323. autoMemoryCode: undefined
  324. }
  325. };
  326.  
  327. /**
  328. * @namespace deepl
  329. * @desc DeepL翻译服务配置。
  330. * @memberof OJBetter
  331. */
  332. OJBetter.deepl = {
  333. /** @type {Object?} DeepL配置对象 */
  334. configs: undefined,
  335. config: {
  336. /** @type {string?} 类型 */
  337. type: undefined,
  338. /** @type {string?} 名称 */
  339. name: undefined,
  340. /** @type {string?} API类型 */
  341. apiGenre: undefined,
  342. /** @type {string?} API密钥 */
  343. key: undefined,
  344. /** @type {string?} 代理 */
  345. proxy: undefined,
  346. /** @type {Object?} 额外请求头 */
  347. header: undefined,
  348. /** @type {Object?} 额外请求数据 */
  349. data: undefined,
  350. quota: {
  351. /** @type {string?} 余额URL */
  352. url: undefined,
  353. /** @type {string?} 余额请求方法 */
  354. method: undefined,
  355. /** @type {Object?} 余额请求头 */
  356. header: undefined,
  357. /** @type {Object?} 余额请求数据 */
  358. data: undefined,
  359. /** @type {number?} 剩余配额 */
  360. surplus: undefined
  361. }
  362. },
  363. /** @type {boolean?} 启用重点保护 */
  364. enableEmphasisProtection: undefined,
  365. /** @type {boolean?} 启用链接保护 */
  366. enableLinkProtection: undefined
  367. };
  368.  
  369. /**
  370. * @namespace chatgpt
  371. * @desc ChatGPT服务配置。
  372. * @memberof OJBetter
  373. */
  374. OJBetter.chatgpt = {
  375. /** @type {Object?} ChatGPT配置对象 */
  376. configs: undefined,
  377. config: {
  378. /** @type {string?} 名称 */
  379. name: undefined,
  380. /** @type {string?} 模型 */
  381. model: undefined,
  382. /** @type {string?} API密钥 */
  383. key: undefined,
  384. /** @type {string?} 代理 */
  385. proxy: undefined,
  386. /** @type {Object?} 额外请求头 */
  387. header: undefined,
  388. /** @type {Object?} 额外请求数据 */
  389. data: undefined,
  390. quota: {
  391. /** @type {string?} 余额URL */
  392. url: undefined,
  393. /** @type {string?} 余额请求方法 */
  394. method: undefined,
  395. /** @type {Object?} 余额请求头 */
  396. header: undefined,
  397. /** @type {Object?} 余额请求数据 */
  398. data: undefined,
  399. /** @type {number?} 剩余配额 */
  400. surplus: undefined
  401. }
  402. },
  403. /** @type {boolean?} 是否为流式传输 */
  404. isStream: undefined,
  405. /** @type {string?} 是否使用自定义Prompt */
  406. customPrompt: undefined,
  407. /** @type {boolean?} 是否作为系统Prompt */
  408. asSystemPrompt: undefined
  409. };
  410.  
  411. /**
  412. * @namespace preference
  413. * @desc 偏好设置
  414. * @memberof OJBetter
  415. */
  416. OJBetter.preference = {
  417. /** @type {boolean?} 是否显示加载动画 */
  418. showLoading: undefined,
  419. /** @type {boolean?} 是否显示悬停目标区域 */
  420. hoverTargetAreaDisplay: undefined,
  421. /** @type {string?} 按钮图标大小 */
  422. iconButtonSize: undefined,
  423. };
  424.  
  425. /**
  426. * @namespace dev
  427. * @desc 维护
  428. * @memberof OJBetter
  429. */
  430. OJBetter.dev = {
  431. /** @type {boolean?} 是否显示规则标记 */
  432. isRuleMarkingEnabled: undefined,
  433. };
  434.  
  435. /**
  436. * @namespace about
  437. * @desc 关于页信息
  438. * @memberof OJBetter
  439. */
  440. OJBetter.about = {
  441. /** @type {string?} 更新通道 */
  442. updateChannel: undefined,
  443. /** @type {string?} 更新源 */
  444. updateSource: undefined
  445. };
  446.  
  447. /**
  448. * @namespace supportList
  449. * @desc 支持列表
  450. * @memberof OJBetter
  451. */
  452. OJBetter.supportList = {
  453. /** @type {object} 翻译支持列表和对应语言代码*/
  454. translationSupport: {
  455. 'deepl': { 'zh': 'ZH', 'de': 'DE', 'fr': 'FR', 'ko': 'KO', 'pt': 'PT', 'ja': 'JA', 'es': 'ES', 'it': 'IT' },
  456. 'iflyrec': { 'zh': '1' },
  457. 'youdao': { 'zh': 'zh-CHS', 'zh-Hant': 'zh-CHT', 'de': 'de', 'fr': 'fr', 'ko': 'ko', 'pt': 'pt', 'ja': 'ja', 'es': 'es', 'it': 'it', 'hi': 'hi' },
  458. 'google': { 'zh': 'zh-CN', 'zh-Hant': 'zh-TW', 'de': 'de', 'fr': 'fr', 'ko': 'ko', 'pt': 'pt', 'ja': 'ja', 'es': 'es', 'it': 'it', 'hi': 'hi' },
  459. 'caiyun': { 'zh': 'auto2zh', 'ja': 'auto2ja', 'ko': 'auto2ko', 'es': 'auto2es', 'fr': 'auto2fr' },
  460. 'openai': { 'zh': 'Chinese', 'zh-Hant': 'Traditional Chinese', 'de': 'German', 'fr': 'French', 'ko': 'Korean', 'pt': 'Portuguese', 'ja': 'Japanese', 'es': 'Spanish', 'it': 'Italian', 'hi': 'Hindi' }
  461. },
  462. /** @type {object} 更新源支持列表*/
  463. updateSourceSupportList: {
  464. 'greasyfork': {
  465. 'release': true,
  466. 'dev': false
  467. },
  468. 'github': {
  469. 'release': true,
  470. 'dev': true
  471. },
  472. 'aliyunoss': {
  473. 'release': true,
  474. 'dev': true
  475. }
  476. }
  477. }
  478.  
  479. // ------------------------------
  480. // 一些工具函数
  481. // ------------------------------
  482.  
  483. /**
  484. * 延迟函数
  485. * @param {number} ms 延迟时间(毫秒)
  486. * @returns {Promise<void>}
  487. */
  488. function OJB_delay(ms) {
  489. return new Promise(resolve => setTimeout(resolve, ms));
  490. }
  491.  
  492. /**
  493. * 等待直到指定的条件函数返回true。
  494. *
  495. * @param {() => boolean} conditionCheck 一个无参数的函数,用于检查条件是否满足。当函数返回true时,表示条件已满足。
  496. * @param {number} [interval=100] 检查条件的间隔时间,单位为毫秒。默认为100毫秒。
  497. * @returns {Promise<void>} 返回一个Promise,在条件满足时解决。
  498. */
  499. async function OJB_waitUntilTrue(conditionCheck, interval = 100) {
  500. return new Promise((resolve) => {
  501. const checkCondition = async () => {
  502. if (conditionCheck()) {
  503. resolve();
  504. } else {
  505. await OJB_delay(interval);
  506. checkCondition();
  507. }
  508. };
  509. checkCondition();
  510. });
  511. }
  512.  
  513. /**
  514. * 动态加载JavaScript库并返回一个Promise,该Promise在脚本加载完成后解决。
  515. *
  516. * @param {string} url - 要加载的JavaScript库的URL地址。
  517. * @returns {Promise<void>} 一个Promise,它在脚本加载并执行完成后解决。
  518. */
  519. function OJB_LoadJS(url) {
  520. return new Promise((resolve, reject) => {
  521. let scriptElement = document.createElement("script");
  522. scriptElement.src = url;
  523. document.head.prepend(scriptElement);
  524. scriptElement.onload = resolve;
  525. scriptElement.onerror = reject;
  526. });
  527. }
  528.  
  529. /**
  530. * 安全地创建JQuery对象
  531. * @description 通过字符串创建JQuery对象时,如果字符串以空格开头,在某些Jquery版本中会发生错误,过滤空格以安全的创建元素。
  532. * @param {string} string - 字符串。
  533. * @returns {JQuery} JQuery对象
  534. */
  535. const OJB_safeCreateJQElement = function (string) {
  536. return $(string.replace(/^\s+/, ""));
  537. }
  538.  
  539.  
  540. /**
  541. * 将数字或者字符串解析为数字。
  542. * @memberof OJBetter.common
  543. * @param {string} val 要解析的字符串
  544. * @param {boolean} [strict=false] 是否进行严格类型检查
  545. * @returns {number} 解析结果
  546. * @throws {Error} 如果解析失败,则抛出错误
  547. */
  548. const OJB_parseNumber = (val, strict = false) => {
  549. const num = Number(val);
  550. if (isNaN(num) || (strict && val.toString() !== num.toString())) {
  551. throw new Error('Invalid number');
  552. }
  553. return num;
  554. };
  555.  
  556. /**
  557. * 将字符串解析为布尔值
  558. * @param {string} val - 要解析的字符串
  559. * @param {boolean} strict - 是否进行严格类型检查
  560. * @returns {boolean} - 解析结果
  561. * @throws {Error} - 如果解析失败,则抛出错误
  562. */
  563. const OJB_parseBoolean = (val, strict) => {
  564. if (strict) {
  565. if (val === true || val === false) return val;
  566. throw new Error('Invalid boolean');
  567. }
  568. return val === 'true' ? true : val === 'false' ? false : val;
  569. };
  570.  
  571. /**
  572. * 将字符串解析为对象
  573. * @param {string} val - 要解析的字符串
  574. * @returns {Object} - 解析结果
  575. * @throws {Error} - 如果解析失败,则抛出错误
  576. */
  577. const OJB_parseObject = val => {
  578. try {
  579. return JSON.parse(val);
  580. } catch {
  581. throw new Error('Invalid JSON');
  582. }
  583. };
  584.  
  585. /**
  586. * 将字符串解析为键值对数组
  587. * @param {string} val - 要解析的字符串
  588. * @returns {Object[]} - 解析结果
  589. * @throws {Error} - 如果解析失败,则抛出错误
  590. */
  591. const OJB_parseLinePairArray = val => {
  592. if (typeof val !== 'string' || val.trim() === '') return [];
  593. return val.split("\n").filter(line => line.trim() !== '').map(line => {
  594. const indexOfFirstColon = line.indexOf(":");
  595. if (indexOfFirstColon === -1) throw new Error('Invalid LinePairArray format: ":" is missing');
  596. const key = line.substring(0, indexOfFirstColon).trim();
  597. const value = line.substring(indexOfFirstColon + 1).trim();
  598. return { [key]: value };
  599. });
  600. };
  601.  
  602. /**
  603. * 移除文本中的HTML标签
  604. * @param {string} text - 包含HTML标签的文本
  605. * @returns {string} - 移除HTML标签后的文本
  606. */
  607. const OJB_removeHTMLTags = function (text) {
  608. return text.replace(/<\/?[a-zA-Z]+("[^"]*"|'[^']*'|[^'">])*>/g, '');
  609. }
  610.  
  611. /**
  612. * 获取对象中指定路径表达式的值
  613. * @param {Object} obj - 要计算的对象
  614. * @param {string} pathOrExpression - 要计算的路径表达式
  615. * @returns {any} - 计算结果
  616. * @example
  617. * const obj = {
  618. * "a": {
  619. * "b": 1
  620. * },
  621. * "c": 2
  622. * };
  623. * OJB_evaluatePathOrExpression(obj, "a.b"); // 1
  624. * OJB_evaluatePathOrExpression(obj, "a.b + c"); // 3
  625. * OJB_evaluatePathOrExpression(obj, "a.b + a.c"); // 1
  626. */
  627. function OJB_evaluatePathOrExpression(obj, pathOrExpression) {
  628. const hasOperator = /[\+\-\*\/]/.test(pathOrExpression);
  629. const getPathValue = (obj, path) => {
  630. return path.split('.').reduce((acc, part) => {
  631. return acc !== undefined && acc !== null && acc.hasOwnProperty(part) ? acc[part] : undefined;
  632. }, obj);
  633. };
  634. const evaluateExpression = (obj, expression) => {
  635. const tokens = expression.split(/([\+\-\*\/])/).map(token => token.trim());
  636. const values = tokens.map(token => {
  637. if (/[\+\-\*\/]/.test(token)) {
  638. return token;
  639. } else {
  640. const value = getPathValue(obj, token);
  641. return value !== undefined ? value : 0;
  642. }
  643. });
  644. const evaluatedExpression = values.join(' ');
  645. try {
  646. return Function(`'use strict'; return (${evaluatedExpression});`)();
  647. } catch (e) {
  648. console.error('Expression evaluation error:', e);
  649. return undefined;
  650. }
  651. };
  652. return hasOperator ? evaluateExpression(obj, pathOrExpression) : getPathValue(obj, pathOrExpression);
  653. }
  654.  
  655. /**
  656. * 获取 GM 存储的值并根据类型进行处理
  657. * @param {string} key - 要检索的值的键。
  658. * @param {any} defaultValue - 如果值未找到,则返回的默认值。
  659. * @param {Object} [options={}] - 配置选项对象。
  660. * @param {string} [options.type='string'] - 期望的值的类型。可选值:'string', 'number', 'boolean', 'object', 'array', 'linePairArray'。
  661. * @param {boolean} [options.strict=false] - 用于数字和布尔类型,表示是否进行严格类型检查。
  662. * @param {string} [options.pathOrExpression=''] - 用于对象或数组类型,表示路径表达式或获取元素的索引。
  663. * @returns {any} - 检索到的值。
  664. */
  665. const OJB_getGMValue = (key, defaultValue, { type = 'string', strict = false, pathOrExpression = '' } = {}) => {
  666. let value = GM_getValue(key);
  667. if (value === undefined || value === null || value === "") {
  668. GM_setValue?.(key, defaultValue);
  669. return defaultValue;
  670. }
  671.  
  672. const parsers = {
  673. string: val => val,
  674. number: (val) => OJB_parseNumber(val, strict),
  675. boolean: (val) => OJB_parseBoolean(val, strict),
  676. object: OJB_parseObject,
  677. array: OJB_parseObject,
  678. linePairArray: OJB_parseLinePairArray
  679. };
  680.  
  681. if (!(type in parsers)) {
  682. console.error(`Unsupported type: ${type}`);
  683. return defaultValue;
  684. }
  685.  
  686. try {
  687. value = parsers[type](value);
  688. } catch (e) {
  689. console.error('Error:', e.message);
  690. return defaultValue;
  691. }
  692.  
  693. // The pathOrExpression processing is not applicable to linePairArray type
  694. if ((type === 'object' || type === 'array') && pathOrExpression) {
  695. const evaluated = OJB_evaluatePathOrExpression(value, pathOrExpression);
  696. if (evaluated === undefined) {
  697. console.error('Path or expression evaluation returned undefined');
  698. return defaultValue;
  699. }
  700. value = evaluated;
  701. }
  702.  
  703. return value;
  704. };
  705.  
  706. /**
  707. * 版本号比较方法
  708. * @param {string} version1 版本号1
  709. * @param {string} version2 版本号2
  710. * @returns {number} -1: version1 < version2, 0: version1 = version2, 1: version1 > version2
  711. */
  712. const OJB_compareVersions = function (version1 = "0", version2 = "0") {
  713. const v1Array = version1.split(".").map(Number);
  714. const v2Array = version2.split(".").map(Number);
  715. const length = Math.max(v1Array.length, v2Array.length);
  716. for (let i = 0; i < length; i++) {
  717. const diff = (v1Array[i] || 0) - (v2Array[i] || 0);
  718. if (diff) return Math.sign(diff);
  719. }
  720. return 0;
  721. }
  722.  
  723. /**
  724. * 获取上一个主版本号
  725. * @param {string} currentVersion 当前版本号
  726. * @returns {string} 上一个主版本号
  727. */
  728. const OJB_getPreviousVersion = function (currentVersion) {
  729. const versionArray = currentVersion.split(".").map(Number);
  730. let lastNonZeroIndex = versionArray.length - 1;
  731. while (lastNonZeroIndex >= 0 && versionArray[lastNonZeroIndex] === 0) {
  732. lastNonZeroIndex--;
  733. }
  734. if (lastNonZeroIndex >= 0) {
  735. versionArray[lastNonZeroIndex]--;
  736. for (let i = lastNonZeroIndex + 1; i < versionArray.length; i++) {
  737. versionArray[i] = 0;
  738. }
  739. }
  740. return versionArray.join(".");
  741. };
  742.  
  743. /**
  744. * 在指定根节点下观察指定选择器的元素,当元素存在时,执行回调函数
  745. * @param {Object} options - 配置对象
  746. * @param {string} options.selector - CSS选择器文本
  747. * @param {Function} options.callback - 回调函数,接收变动的节点作为参数
  748. * @param {Boolean} [options.triggerOnExist=true] - 如果为true,元素已存在时立即触发一次回调
  749. * @param {Element} [options.root=document.body] - 在哪个根节点下监听变化
  750. * @param {Boolean} [options.subtree=false] - 是否监听子树变化(即非直接子元素)
  751. */
  752. function OJB_observeElement({
  753. selector,
  754. callback,
  755. triggerOnExist = true,
  756. root = document.body,
  757. subtree = false
  758. }) {
  759. // 尝试获取选择器指定的元素
  760. const targetNode = root.querySelector(selector);
  761.  
  762. if (targetNode) {
  763. // 如果元素已存在,直接开始观察
  764. observeAndReport(targetNode, callback);
  765. // 如果triggerOnExist为true,则立即触发一次回调
  766. if (triggerOnExist) {
  767. callback(targetNode);
  768. }
  769. } else {
  770. // 如果元素不存在,监听DOM变化直到该元素被添加
  771. const observer = new MutationObserver((mutations) => {
  772. mutations.forEach((mutation) => {
  773. mutation.addedNodes.forEach((node) => {
  774. if (node.nodeType === Node.ELEMENT_NODE && node.matches(selector)) {
  775. observeAndReport(node, callback);
  776. if (triggerOnExist) {
  777. callback(node);
  778. }
  779. observer.disconnect(); // 停止监听
  780. }
  781. });
  782. });
  783. });
  784.  
  785. observer.observe(root, { childList: true, subtree, attributes: false });
  786. }
  787.  
  788. function observeAndReport(node, callback) {
  789. const childObserver = new MutationObserver((mutations) => {
  790. mutations.forEach((mutation) => {
  791. mutation.addedNodes.forEach((addedNode) => {
  792. if (addedNode.nodeType === Node.ELEMENT_NODE) {
  793. callback(addedNode); // 执行回调函数
  794. }
  795. });
  796. });
  797. });
  798.  
  799. childObserver.observe(node, { childList: true, subtree: true, attributes: false });
  800. }
  801. }
  802.  
  803. /**
  804. * 初始化全局变量
  805. */
  806. async function initVar() {
  807. const { hostname, href } = window.location;
  808. OJBetter.state.formatName = (() => OJBetter.state.name
  809. .toLowerCase()
  810. .replace(/\s+/g, '-')
  811. .replace(/[^a-z0-9-]/g, ''))();
  812. OJBetter.state.lastAnnounceVer = OJB_getGMValue("lastAnnounceVer", "0");
  813. OJBetter.state.lastReadAnnounceVer = OJB_getGMValue("lastReadAnnounceVer", "0");
  814. OJBetter.typeOfPage.is_contest = /\/contests\/[^\/]+\/tasks\/?$/.test(href);
  815. OJBetter.typeOfPage.is_problem = href.includes('/tasks/');
  816. OJBetter.typeOfPage.is_homepage = (href === 'https://atcoder.jp/' || href === 'https://atcoder.jp/?lang=ja');
  817. OJBetter.typeOfPage.isEnglishLanguage = $('meta[http-equiv="Content-Language"]').attr('content') === 'en';
  818. OJBetter.typeOfPage.isEditorial = href.includes("editorial");
  819. OJBetter.localization.websiteLang = OJB_getGMValue("localizationLanguage", "zh");
  820. OJBetter.localization.scriptLang = OJB_getGMValue("scriptL10nLanguage", "zh");
  821. OJBetter.basic.renderPerfOpt = OJB_getGMValue("renderPerfOpt", false);
  822. OJBetter.basic.selectElementPerfOpt = OJB_getGMValue("selectElementPerfOpt", true);
  823. OJBetter.basic.commentPaging = OJB_getGMValue("commentPaging", true);
  824. OJBetter.basic.showJumpToLuogu = OJB_getGMValue("showJumpToLuogu", true);
  825. OJBetter.basic.showCF2vjudge = OJB_getGMValue("showCF2vjudge", true);
  826. OJBetter.basic.standingsRecolor = OJB_getGMValue("standingsRecolor", true);
  827. OJBetter.state.notWaiteLoaded = OJB_getGMValue("notWaiteLoaded", false);
  828. OJBetter.translation.targetLang = OJB_getGMValue("transTargetLang", "zh");
  829. OJBetter.translation.choice = OJB_getGMValue("translation", "deepl");
  830. OJBetter.translation.comment.transMode = OJB_getGMValue("commentTranslationMode", "0");
  831. OJBetter.translation.comment.choice = OJB_getGMValue("commentTranslationChoice", "0");
  832. OJBetter.translation.memory.enabled = OJB_getGMValue("memoryTranslateHistory", true);
  833. OJBetter.translation.auto.enabled = OJB_getGMValue("autoTranslation", false);
  834. OJBetter.translation.auto.shortTextLength = OJB_getGMValue("shortTextLength", "2000");
  835. OJBetter.translation.retransAction = OJB_getGMValue("retransAction", "0");
  836. OJBetter.translation.waitTime = OJB_getGMValue("transWaitTime", "200");
  837. OJBetter.translation.auto.mixTrans.enabled = OJB_getGMValue("allowMixTrans", true);
  838. OJBetter.translation.auto.mixTrans.servers = OJB_getGMValue("mixedTranslation", ['deepl', 'iflyrec', 'youdao', 'caiyun']);
  839. OJBetter.common.taskQueue = new TaskQueue();
  840. OJBetter.translation.replaceSymbol = OJB_getGMValue("replaceSymbol", "2");
  841. OJBetter.translation.filterTextWithoutEmphasis = OJB_getGMValue("filterTextWithoutEmphasis", false);
  842. OJBetter.clist.enabled.contest = OJB_getGMValue("showClistRating_contest", false);
  843. OJBetter.clist.enabled.problem = OJB_getGMValue("showClistRating_problem", false);
  844. OJBetter.clist.enabled.problemset = OJB_getGMValue("showClistRating_problemset", false);
  845. OJBetter.clist.ratingHidden = OJB_getGMValue("RatingHidden", false);
  846. OJBetter.clist.authorization = OJB_getGMValue("clist_Authorization", "");
  847. //deepl
  848. OJBetter.deepl.config.type = OJB_getGMValue("deepl_type", "free");
  849. OJBetter.deepl.configs = OJB_getGMValue("deepl_config", {
  850. "choice": "",
  851. "configurations": []
  852. });
  853. if (OJBetter.deepl.configs.choice !== "" && OJBetter.deepl.configs.configurations.length !== 0) {
  854. const choice = OJBetter.deepl.configs.choice;
  855. const configuration = OJBetter.deepl.configs.configurations.find(obj => obj.name === choice);;
  856. if (configuration == undefined) {
  857. let existingConfig = GM_getValue('deepl_config');
  858. existingConfig.choice = "";
  859. GM_setValue('deepl_config', existingConfig);
  860. location.reload();
  861. }
  862. OJBetter.deepl.config.name = configuration.name;
  863. OJBetter.deepl.config.apiGenre = configuration.apiGenre;
  864. OJBetter.deepl.config.key = configuration.key;
  865. OJBetter.deepl.config.proxy = configuration.proxy;
  866. OJBetter.deepl.config.header = OJB_parseLinePairArray(configuration._header);
  867. OJBetter.deepl.config.data = OJB_parseLinePairArray(configuration._data);
  868. OJBetter.deepl.config.quota.url = configuration.quota_url;
  869. OJBetter.deepl.config.quota.method = configuration.quota_method;
  870. OJBetter.deepl.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
  871. OJBetter.deepl.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
  872. OJBetter.deepl.config.quota.surplus = configuration.quota_surplus;
  873. }
  874. OJBetter.deepl.enableEmphasisProtection = OJB_getGMValue("enableEmphasisProtection", true);
  875. OJBetter.deepl.enableLinkProtection = OJB_getGMValue("enableLinkProtection", true);
  876. //openai
  877. OJBetter.chatgpt.isStream = OJB_getGMValue("openai_isStream", true);
  878. OJBetter.chatgpt.customPrompt = OJB_getGMValue("openai_customPrompt", '');
  879. OJBetter.chatgpt.asSystemPrompt = OJB_getGMValue("openai_asSystemPrompt", false);
  880. OJBetter.chatgpt.configs = OJB_getGMValue("chatgpt_config", {
  881. "choice": "",
  882. "configurations": []
  883. });
  884. if (OJBetter.chatgpt.configs.choice !== "" && OJBetter.chatgpt.configs.configurations.length !== 0) {
  885. const choice = OJBetter.chatgpt.configs.choice;
  886. const configuration = OJBetter.chatgpt.configs.configurations.find(obj => obj.name === choice);;
  887. if (configuration == undefined) {
  888. let existingConfig = GM_getValue('chatgpt_config');
  889. existingConfig.choice = "";
  890. GM_setValue('chatgpt_config', existingConfig);
  891. location.reload();
  892. }
  893. OJBetter.chatgpt.config.name = configuration.name;
  894. OJBetter.chatgpt.config.model = configuration.model;
  895. OJBetter.chatgpt.config.key = configuration.key;
  896. OJBetter.chatgpt.config.proxy = configuration.proxy;
  897. OJBetter.chatgpt.config.header = OJB_parseLinePairArray(configuration._header);
  898. OJBetter.chatgpt.config.data = OJB_parseLinePairArray(configuration._data);
  899. OJBetter.chatgpt.config.quota.url = configuration.quota_url;
  900. OJBetter.chatgpt.config.quota.method = configuration.quota_method;
  901. OJBetter.chatgpt.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
  902. OJBetter.chatgpt.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
  903. OJBetter.chatgpt.config.quota.surplus = configuration.quota_surplus;
  904. }
  905. // 编辑器
  906. // if (!OJBetter.typeOfPage.is_mSite) OJBetter.common.cf_csrf_token = Codeforces.getCsrfToken();
  907. // else OJBetter.common.cf_csrf_token = "";
  908. OJBetter.common.at_csrf_token = csrfToken;
  909. // OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "61");
  910. OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "5001");
  911. OJBetter.monaco.setting.fontsize = OJB_getGMValue("editorFontSize", "15");
  912. OJBetter.monaco.enableOnProblemPage = OJB_getGMValue("problemPageCodeEditor", true);
  913. OJBetter.monaco.beautifyPreBlocks = OJB_getGMValue("beautifyPreBlocks", true);
  914. OJBetter.monaco.complet.cppCodeTemplate = OJB_getGMValue("cppCodeTemplateComplete", true);
  915. OJBetter.monaco.onlineCompilerChoice = OJB_getGMValue("onlineCompilerChoice", "official");
  916. OJBetter.monaco.setting.isCodeSubmitDoubleConfirm = OJB_getGMValue("isCodeSubmitConfirm", true);
  917. OJBetter.monaco.setting.autoSubmitAfterPass = OJB_getGMValue("autoSubmitAfterPass", false);
  918. OJBetter.monaco.setting.alwaysConsumeMouseWheel = OJB_getGMValue("alwaysConsumeMouseWheel", true);
  919. OJBetter.monaco.setting.submitButtonPosition = OJB_getGMValue("submitButtonPosition", "bottom");
  920. OJBetter.monaco.setting.autoMemoryCode = OJB_getGMValue("autoMemoryCode", true);
  921. //自定义补全
  922. OJBetter.monaco.complet.customConfig = OJB_getGMValue("Complet_config", {
  923. "choice": -1,
  924. "configurations": []
  925. });
  926. // monaco
  927. OJBetter.monaco.lsp.enabled = OJB_getGMValue("useLSP", false);
  928. OJBetter.monaco.setting.position = OJB_getGMValue("monacoEditor_position", "initial");
  929. OJBetter.monaco.lsp.workUri = OJB_getGMValue("OJBetter_Bridge_WorkUri", "C:/OJBetter_Bridge");
  930. OJBetter.monaco.lsp.socketUrl = OJB_getGMValue("OJBetter_Bridge_SocketUrl", "ws://127.0.0.1:2323/");
  931. OJBetter.preference.showLoading = OJB_getGMValue("showLoading", true);
  932. OJBetter.preference.hoverTargetAreaDisplay = OJB_getGMValue("hoverTargetAreaDisplay", false);
  933. OJBetter.basic.expandFoldingblocks = OJB_getGMValue("expandFoldingblocks", true);
  934. OJBetter.preference.iconButtonSize = OJB_getGMValue("iconButtonSize", "16");
  935. OJBetter.dev.isRuleMarkingEnabled = OJB_getGMValue("isRuleMarkingEnabled", false);
  936. OJBetter.about.updateChannel = OJB_getGMValue("updateChannel", "release");
  937. OJBetter.about.updateSource = OJB_getGMValue("updateSource", "greasyfork");
  938. }
  939.  
  940. /**
  941. * 显示警告消息
  942. */
  943. function showWarnMessage() {
  944. if (OJBetter.typeOfPage.is_oldLatex) {
  945. const loadingMessage = new LoadingMessage();
  946. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_oldLatex', { ns: 'alert' })}`, 'warning');
  947. }
  948. if (OJBetter.typeOfPage.is_acmsguru) {
  949. const loadingMessage = new LoadingMessage();
  950. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_acmsguru', { ns: 'alert' })}`, 'warning');
  951. }
  952. if (OJBetter.translation.comment.transMode == "1") {
  953. const loadingMessage = new LoadingMessage();
  954. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_segment', { ns: 'alert' })}`, 'warning');
  955. }
  956. if (OJBetter.translation.comment.transMode == "2") {
  957. const loadingMessage = new LoadingMessage();
  958. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_select', { ns: 'alert' })}`, 'warning');
  959. }
  960. if (OJBetter.typeOfPage.is_submitPage && OJBetter.monaco.enableOnProblemPage) {
  961. const loadingMessage = new LoadingMessage();
  962. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_submitPage', { ns: 'alert' })}`, 'warning');
  963. }
  964. }
  965.  
  966. // 常量
  967. const helpCircleHTML = '<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>';
  968. 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>`;
  969. 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
  970. 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>`;
  971. 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" />
  972. <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" />
  973. <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>`;
  974.  
  975. /**
  976. * 连接数据库
  977. */
  978. async function initDB() {
  979. OJBetter.common.database = new Dexie('OJBetterDB');
  980. OJBetter.common.database.version(3).stores({
  981. samplesData: '&url',
  982. editorCode: '&url',
  983. translateData: '&url',
  984. localizeSubsData: '&lang'
  985. });
  986.  
  987. // 等待数据库打开
  988. await OJBetter.common.database.open();
  989. }
  990.  
  991. /**
  992. * 清空数据库
  993. */
  994. async function clearDatabase() {
  995. const isConfirmed = await OJB_createDialog(
  996. i18next.t('isClearDatabase.title', { ns: 'dialog' }),
  997. i18next.t('isClearDatabase.content', { ns: 'dialog' }),
  998. [
  999. i18next.t('isClearDatabase.buttons.0', { ns: 'dialog' }),
  1000. i18next.t('isClearDatabase.buttons.1', { ns: 'dialog' })
  1001. ]
  1002. );
  1003. if (!isConfirmed) {
  1004. try {
  1005. // 开启一个读写事务,包含数据库中的所有表
  1006. await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
  1007. // 遍历所有表
  1008. for (const table of OJBetter.common.database.tables) {
  1009. // 清空当前表
  1010. await table.clear();
  1011. }
  1012. });
  1013. console.log("All tables in the database have been cleared.");
  1014. alert("All tables in the database have been cleared.");
  1015. } catch (error) {
  1016. console.error("Error clearing the database:", error);
  1017. }
  1018. }
  1019. }
  1020.  
  1021. /**
  1022. * 导出数据库
  1023. * @returns {Promise<string>} 数据库的JSON字符串
  1024. */
  1025. async function exportDatabase() {
  1026. try {
  1027. // 创建一个存储数据的对象
  1028. const exportData = {};
  1029. // 获取数据库中所有表的名称
  1030. const tableNames = OJBetter.common.database.tables.map(table => table.name);
  1031.  
  1032. // 遍历每一个表,获取数据
  1033. for (const tableName of tableNames) {
  1034. const tableData = await OJBetter.common.database.table(tableName).toArray();
  1035. exportData[tableName] = tableData;
  1036. }
  1037.  
  1038. // 将数据对象转换为JSON字符串
  1039. const jsonData = JSON.stringify(exportData, null, 4);
  1040. return jsonData;
  1041. } catch (error) {
  1042. console.error("Error exporting database:", error);
  1043. }
  1044. }
  1045.  
  1046. /**
  1047. * 导入数据库
  1048. * @param {string} jsonData 数据库的JSON字符串
  1049. */
  1050. async function importDatabase(jsonData) {
  1051. const isConfirmed = await OJB_createDialog(
  1052. i18next.t('isImportDatabase.title', { ns: 'dialog' }),
  1053. i18next.t('isImportDatabase.content', { ns: 'dialog' }),
  1054. [
  1055. i18next.t('isImportDatabase.buttons.0', { ns: 'dialog' }),
  1056. i18next.t('isImportDatabase.buttons.1', { ns: 'dialog' })
  1057. ]
  1058. );
  1059. if (!isConfirmed) {
  1060. try {
  1061. // 将JSON字符串解析为对象
  1062. const importData = JSON.parse(jsonData);
  1063.  
  1064. // 开启一个事务,并清空现有数据
  1065. await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
  1066. // 清空所有表的数据
  1067. for (const tableName of OJBetter.common.database.tables.map(table => table.name)) {
  1068. await OJBetter.common.database.table(tableName).clear();
  1069. }
  1070.  
  1071. // 插入新数据
  1072. for (const [tableName, rows] of Object.entries(importData)) {
  1073. await OJBetter.common.database.table(tableName).bulkAdd(rows);
  1074. }
  1075. });
  1076. alert("Data imported successfully");
  1077. } catch (error) {
  1078. console.error("Error importing database:", error);
  1079. }
  1080. }
  1081. }
  1082.  
  1083. /**
  1084. * 将数据下载为文件
  1085. * @param {string} data 数据
  1086. * @param {string} filename 文件名,默认为'export.json'
  1087. * @param {string} fileType 文件MIME类型,默认为'application/json'
  1088. * @returns {void}
  1089. */
  1090. function downloadDataAsFile(data, filename = 'export.json', fileType = 'application/json') {
  1091. // 创建一个blob对象,指定文件类型
  1092. const blob = new Blob([data], { type: fileType });
  1093. const url = URL.createObjectURL(blob);
  1094.  
  1095. // 创建一个隐藏的a标签,模拟点击进行下载
  1096. const a = document.createElement('a');
  1097. a.href = url;
  1098. a.download = filename;
  1099. document.body.appendChild(a);
  1100. a.click();
  1101.  
  1102. // 清理
  1103. document.body.removeChild(a);
  1104. URL.revokeObjectURL(url);
  1105. }
  1106.  
  1107.  
  1108. /**
  1109. * 从文件中读取数据
  1110. * @param {Function} callback 回调函数
  1111. * @returns {void}
  1112. */
  1113. function readFileInput(callback) {
  1114. const fileInput = document.createElement('input');
  1115. fileInput.type = 'file';
  1116. fileInput.accept = '.json';
  1117. fileInput.style.display = 'none'; // 隐藏input元素
  1118.  
  1119. fileInput.onchange = (e) => {
  1120. const file = e.target.files[0];
  1121. if (file) {
  1122. const reader = new FileReader();
  1123. reader.onload = (e) => {
  1124. const fileContent = e.target.result;
  1125. if (callback && typeof callback === 'function') {
  1126. callback(fileContent); // 调用回调函数,传入文件内容
  1127. }
  1128. };
  1129. reader.readAsText(file);
  1130. }
  1131. };
  1132.  
  1133. document.body.appendChild(fileInput);
  1134. fileInput.click();
  1135. document.body.removeChild(fileInput);
  1136. }
  1137.  
  1138. /**
  1139. * 清除所有设置
  1140. */
  1141. async function deleteAllConfigSettings() {
  1142. const isConfirmed = await OJB_createDialog(
  1143. i18next.t('isDeleteAllConfigSettings.title', { ns: 'dialog' }),
  1144. i18next.t('isDeleteAllConfigSettings.content', { ns: 'dialog' }),
  1145. [
  1146. i18next.t('isDeleteAllConfigSettings.buttons.0', { ns: 'dialog' }),
  1147. i18next.t('isDeleteAllConfigSettings.buttons.1', { ns: 'dialog' })
  1148. ]
  1149. );
  1150. if (!isConfirmed) {
  1151. const keys = GM_listValues();
  1152.  
  1153. keys.forEach(key => {
  1154. GM_deleteValue(key);
  1155. });
  1156.  
  1157. alert('All settings have been deleted.');
  1158. window.location.reload();
  1159. }
  1160. }
  1161.  
  1162. /**
  1163. * 导出设置到JSON
  1164. * @returns {string} JSON字符串
  1165. */
  1166. function exportSettingsToJSON() {
  1167. const keys = GM_listValues();
  1168. let settings = {};
  1169.  
  1170. keys.forEach(key => {
  1171. settings[key] = GM_getValue(key);
  1172. });
  1173.  
  1174. return JSON.stringify(settings, null, 4);
  1175. }
  1176.  
  1177. /**
  1178. * 从JSON导入设置
  1179. * @param {string} jsonData JSON字符串
  1180. * @returns {void}
  1181. */
  1182. async function importSettingsFromJSON(jsonData) {
  1183. const isConfirmed = await OJB_createDialog(
  1184. i18next.t('isImportSettings.title', { ns: 'dialog' }),
  1185. i18next.t('isImportSettings.content', { ns: 'dialog' }),
  1186. [
  1187. i18next.t('isImportSettings.buttons.0', { ns: 'dialog' }),
  1188. i18next.t('isImportSettings.buttons.1', { ns: 'dialog' })
  1189. ]
  1190. );
  1191. if (!isConfirmed) {
  1192. let settings;
  1193. try {
  1194. settings = JSON.parse(jsonData);
  1195. } catch (e) {
  1196. console.error('JSON parsing error:', e);
  1197. return;
  1198. }
  1199.  
  1200. Object.keys(settings).forEach(key => {
  1201. GM_setValue(key, settings[key]);
  1202. });
  1203.  
  1204. alert('Settings imported successfully!');
  1205. window.location.reload();
  1206. }
  1207. }
  1208.  
  1209. /**
  1210. * 加载元素本地化语言数据
  1211. * @param {JQuery} element jQuery元素
  1212. * @param {number} [retries=10] 重试次数
  1213. * @param {number} [interval=50] 重试间隔
  1214. */
  1215. function elementLocalize(element, retries = 10, interval = 50) {
  1216. if ($.isFunction(element.localize)) {
  1217. element.localize();
  1218. } else if (retries > 0) {
  1219. setTimeout(elementLocalize, interval, element, retries - 1, interval);
  1220. } else {
  1221. console.error('Unable to localize', element);
  1222. }
  1223. }
  1224.  
  1225. // 切换系统黑暗监听
  1226. const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
  1227. const changeEventListeners = [];
  1228. function handleColorSchemeChange(event) {
  1229. event.matches ? $('html').attr('data-theme', 'dark') : $('html').attr('data-theme', 'light');
  1230. if (!event.matches) {
  1231. var originalColor = $(this).data("original-color");
  1232. $(this).css("background-color", originalColor);
  1233. const intervalId = setinterval(() => {
  1234. if (OJBetter.monaco && OJBetter.monaco.editor) {
  1235. monaco.editor.setTheme('vs');
  1236. clearInterval(intervalId);
  1237. }
  1238. }, 100);
  1239. } else {
  1240. const intervalId = setInterval(() => {
  1241. if (OJBetter.monaco && OJBetter.monaco.editor) {
  1242. monaco.editor.setTheme('vs-dark');
  1243. clearInterval(intervalId);
  1244. }
  1245. }, 100);
  1246. }
  1247. }
  1248.  
  1249. // 黑暗模式
  1250. (function setDark() {
  1251. // 初始化
  1252. function setDarkTheme() {
  1253. const htmlElement = document.querySelector('html');
  1254. if (htmlElement) {
  1255. htmlElement.setAttribute('data-theme', 'dark');
  1256. const intervalId = setInterval(() => {
  1257. if (OJBetter.monaco && OJBetter.monaco.editor) {
  1258. monaco.editor.setTheme('vs-dark');
  1259. clearInterval(intervalId);
  1260. }
  1261. }, 100);
  1262. } else {
  1263. setTimeout(setDarkTheme, 100);
  1264. }
  1265. }
  1266. OJBetter.basic.darkMode = OJB_getGMValue("darkMode", "follow")
  1267. if (OJBetter.basic.darkMode == "dark") {
  1268. setDarkTheme();
  1269. } else if (OJBetter.basic.darkMode == "follow") {
  1270. // 添加事件监听器
  1271. changeEventListeners.push(handleColorSchemeChange);
  1272. mediaQueryList.addEventListener('change', handleColorSchemeChange);
  1273.  
  1274. if (window.matchMedia('(prefers-color-scheme: dark)').matches) setDarkTheme();
  1275. }
  1276.  
  1277. // 定义全局变量
  1278. GM_addStyle(`
  1279. /* 黑暗支持 */
  1280. html[data-theme=dark]:root {
  1281. color-scheme: light dark;
  1282. }
  1283. /* 颜色 */
  1284. :root {
  1285. /* 文字颜色 */
  1286. --ojb-color-text-primary: #a0adb9; /* 主要文字颜色 */
  1287. --ojb-color-text-secondary: #9AA4B1; /* 次要文字颜色 */
  1288. --ojb-color-text-tertiary: #9BA5B2; /* 第三级文字颜色 */
  1289. --ojb-color-text-success: #43A047; /* 成功状态文字颜色 */
  1290. --ojb-color-text-highlight: #cbd6e2; /* 高亮文字颜色 */
  1291. --ojb-color-text-disabled: #506778; /* 禁用状态文字颜色 */
  1292. --ojb-color-text-icon-success: #2e7d32; /* 成功状态图标颜色 */
  1293. --ojb-color-text-link: #4b8eda; /* 链接颜色 */
  1294.  
  1295. /* 背景颜色 */
  1296. --ojb-color-bg-primary: #22272e; /* 主背景颜色 */
  1297. --ojb-color-bg-secondary: #2d333b; /* 次级背景颜色 */
  1298. --ojb-color-bg-disabled: #24292e; /* 禁用元素背景颜色 */
  1299.  
  1300. /* 边框颜色 */
  1301. --ojb-color-border-primary: #48535F; /* 主要边框颜色 */
  1302. --ojb-color-border-disabled: #404950; /* 禁用状态边框颜色 */
  1303. --ojb-color-border-dashed-hover: #03A9F4; /* 虚线边框悬浮颜色 */
  1304. --ojb-color-border-radio-checked: #326154; /* 选中的单选框边框颜色 */
  1305.  
  1306. /* 阴影颜色 */
  1307. --ojb-shadow-standard: 0px 0px 0.5px 0.5px #3A4048; /* 标准阴影 */
  1308. --ojb-shadow-menu-modal: 0px 0px 0px 4px #2d333b; /* 菜单和模态框阴影 */
  1309.  
  1310. /* 区域遮罩颜色 */
  1311. --ojb-overlay-background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px); /* 区域遮罩背景 */
  1312.  
  1313. /* 文字阴影 */
  1314. --ojb-text-shadow-icon: 1px 1px 0px #2d333b, 1px -1px 0px #2d333b, -1px -1px 0px #2d333b, -1px 1px 0px #2d333b; /* 图标文字阴影 */
  1315. }
  1316. /* 边框样式 */
  1317. :root {
  1318. /* 边框样式 */
  1319. --ojb-border-width: 1px; /* 边框宽度 */
  1320. --ojb-border-style-solid: solid; /* 实线样式 */
  1321. --ojb-border-style-dashed: dashed; /* 虚线样式 */
  1322. --ojb-border-radius-small: 4px; /* 小圆角 */
  1323. --ojb-border-radius-medium: 8px; /* 中圆角 */
  1324. --ojb-border-radius-large: 12px; /* 大圆角 */
  1325. /* 组合边框样式 */
  1326. --ojb-border-solid-primary: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-primary); /* 主要实线边框 */
  1327. --ojb-border-dashed: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-primary); /* 主要虚线边框 */
  1328. --ojb-border-dashed-hover: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-dashed-hover); /* 悬浮虚线边框 */
  1329. --ojb-border-solid-disabled: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-disabled); /* 禁用状态实线边框 */
  1330. }
  1331. `);
  1332.  
  1333. // OJBetter界面样式
  1334. GM_addStyle(`
  1335. /* 主要文字颜色 */
  1336. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1337. html[data-theme=dark] .alert-warning, html[data-theme=dark] .markItUpEditor,
  1338. html[data-theme=dark] .translate-problem-statement, html[data-theme=dark] .OJBetter_setting_menu,
  1339. html[data-theme=dark] .help_tip .tip_text,
  1340. html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
  1341. html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
  1342. html[data-theme=dark] #items-per-page, html[data-theme=dark] #pagBar,
  1343. html[data-theme=dark] .OJBetter_setting_sidebar li a:link,
  1344. html[data-theme=dark] .popup .content{
  1345. color: var(--ojb-color-text-primary);
  1346. }
  1347. /* 次要文字颜色 */
  1348. html[data-theme=dark] .ojb_btn:hover, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_statusBar,
  1349. html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #addCustomTest,
  1350. html[data-theme=dark] #customTestBlock, html[data-theme=dark] .OJBetter_setting_list.alert_info{
  1351. color: var(--ojb-color-text-secondary);
  1352. }
  1353. /* 文字颜色3 */
  1354. html[data-theme=dark] .ojb_btn{
  1355. color: var(--ojb-color-text-tertiary);
  1356. }
  1357. /* 文字颜色 浅绿 */
  1358. html[data-theme=dark] #SubmitButton{
  1359. color: var(--ojb-color-text-success);
  1360. }
  1361. /* 禁止文字颜色 */
  1362. html[data-theme=dark] .ojb_btn[disabled]{
  1363. color: var(--ojb-color-text-disabled);
  1364. }
  1365. /* 主要背景层次 */
  1366. html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] li#add_button:hover,
  1367. html[data-theme=dark] .ojb_btn:hover,
  1368. html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
  1369. html[data-theme=dark] #OJBetter_SubmitForm input,
  1370. html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
  1371. html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
  1372. html[data-theme=dark] .OJBetter_setting_sidebar li a.active, html[data-theme=dark] .OJBetter_setting_sidebar li,
  1373. html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-track, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-track,
  1374. html[data-theme=dark] .OJBetter_modal, html[data-theme=dark] .OJBetter_modal button:hover,
  1375. html[data-theme=dark] .popup .content,
  1376. html[data-theme=dark] .config_bar_list, html[data-theme=dark] #LSPLog,
  1377. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
  1378. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before,
  1379. html[data-theme=dark] .OJBetter_setting_menu a, html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button:hover,
  1380. html[data-theme=dark] .OJBetter_setting_menu select{
  1381. background-color: var(--ojb-color-bg-primary);
  1382. background-image: none;
  1383. }
  1384. /* 次要背景层次 */
  1385. html[data-theme=dark] .ojb_btn,
  1386. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1387. html[data-theme=dark] .alert-warning, html[data-theme=dark] .SumoSelect>.optWrapper>.options li.opt:hover,
  1388. html[data-theme=dark] .translate-problem-statement-panel,
  1389. html[data-theme=dark] .translate-problem-statement,
  1390. html[data-theme=dark] .OJBetter_setting_list,
  1391. html[data-theme=dark] .OJBetter_setting_menu hr,
  1392. html[data-theme=dark] .OJBetter_setting_sidebar li a,
  1393. html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-thumb, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-thumb,
  1394. html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] .test-for-popup pre,
  1395. html[data-theme=dark] .popup .content pre, html[data-theme=dark] .popup .content pre code,
  1396. html[data-theme=dark] ul.config_bar_ul::-webkit-scrollbar-thumb, html[data-theme=dark] #OJBetter_statusBar,
  1397. html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] .sampleDiv,
  1398. html[data-theme=dark] #addCustomTest, html[data-theme=dark] #LSPLog li:nth-child(odd),
  1399. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before,
  1400. html[data-theme=dark] .config::before, html[data-theme=dark] .config li.tempConfig_add_button:hover,
  1401. html[data-theme=dark] .OJBetter_setting_menu details, html[data-theme=dark] #config_bar_menu,
  1402. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button,
  1403. html[data-theme=dark] .OJBetter_setting_menu .badge, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton{
  1404. background-color: var(--ojb-color-bg-secondary);
  1405. }
  1406. /* 禁止背景层次 */
  1407. html[data-theme=dark] .ojb_btn[disabled]{
  1408. background-color: var(--ojb-color-bg-disabled);
  1409. }
  1410. /* 实线边框颜色-圆角 */
  1411. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1412. html[data-theme=dark] .alert-warning, html[data-theme=dark] .translate-problem-statement{
  1413. border: var(--ojb-border-solid-primary);
  1414. border-radius: 2px;
  1415. }
  1416. /* 实线边框颜色-无圆角 */
  1417. html[data-theme=dark] .ojb_btn,
  1418. html[data-theme=dark] .OJBetter_setting_list, html[data-theme=dark] .config_bar_list,
  1419. html[data-theme=dark] label.config_bar_ul_li_text,
  1420. html[data-theme=dark] .OJBetter_setting_sidebar li, html[data-theme=dark] .OJBetter_setting_menu select,
  1421. html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_SubmitForm select,
  1422. html[data-theme=dark] #OJBetter_editor, html[data-theme=dark] #OJBetter_statusBar,
  1423. html[data-theme=dark] #OJBetter_SubmitForm #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #customTestBlock,
  1424. html[data-theme=dark] #OJBetter_SubmitForm #addCustomTest, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton,
  1425. html[data-theme=dark] .OJBetter_setting_menu input,
  1426. html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
  1427. html[data-theme=dark] .OJBetter_setting_menu textarea,
  1428. html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea,
  1429. html[data-theme=dark] #CompilerSetting select, html[data-theme=dark] #CompilerSetting textarea, html[data-theme=dark] #CompilerBox,
  1430. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
  1431. html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] .config::before,
  1432. html[data-theme=dark] #statePanel, html[data-theme=dark] .test-case, html[data-theme=dark] .OJBetter_setting_menu .badge{
  1433. border: var(--ojb-border-solid-primary);
  1434. }
  1435. html[data-theme=dark] #customTestBlock #customTests{
  1436. border-top: var(--ojb-border-solid-primary);
  1437. }
  1438. html[data-theme=dark] .OJBetter_setting_sidebar {
  1439. border-right: var(--ojb-border-solid-primary);
  1440. }
  1441. /* 实线边框-禁止 */
  1442. html[data-theme=dark] .ojb_btn[disabled]{
  1443. border: var(--ojb-border-solid-disabled);
  1444. }
  1445. /* 虚线边框 */
  1446. html[data-theme=dark] li#add_button,
  1447. html[data-theme=dark] .OJBetter_setting_menu_label_text{
  1448. border: var(--ojb-border-dashed);
  1449. }
  1450. /* 虚线边框-悬浮 */
  1451. html[data-theme=dark] li#add_button:hover{
  1452. border: var(--ojb-border-dashed-hover);
  1453. background-color: var(--ojb-color-bg-secondary);
  1454. color: var(--ojb-color-border-dashed-hover);
  1455. }
  1456. /* 无边框 */
  1457. html[data-theme=dark] .translate-problem-statement-panel .ojb_btn{
  1458. border: none;
  1459. }
  1460. /* 区域遮罩 */
  1461. html[data-theme=dark] .overlay::before {
  1462. background: var(--ojb-overlay-background);
  1463. color: var(--ojb-color-text-secondary);
  1464. text-shadow: 0px 0px 2px #000000;
  1465. }
  1466. /* 阴影 */
  1467. html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .translate-problem-statement{
  1468. box-shadow: var(--ojb-shadow-standard);
  1469. }
  1470. /* 图标按钮状态样式 */
  1471. html[data-theme=dark] .ojb_btn_popover.success i:before, html[data-theme=dark] .ojb_btn_popover.success i {
  1472. color: var(--ojb-color-text-icon-success);
  1473. }
  1474. html[data-theme=dark] .ojb_btn_popover i:before {
  1475. text-shadow: var(--ojb-text-shadow-icon);
  1476. }
  1477. /* 其他样式 */
  1478. html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .OJBetter_modal{
  1479. box-shadow: var(--ojb-shadow-menu-modal);
  1480. border: 1px solid var(--ojb-color-bg-secondary);
  1481. }
  1482. html[data-theme=dark] input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
  1483. color: var(--ojb-color-text-primary);
  1484. border: 1px solid var(--ojb-color-border-radio-checked);
  1485. }
  1486. html[data-theme=dark] .alert{
  1487. text-shadow: none;
  1488. }
  1489. `);
  1490.  
  1491. // 网站界面样式
  1492. GM_addStyle(`
  1493. /* 文字颜色1 */
  1494. html[data-theme=dark] body, html[data-theme=dark] .float-container>#main-container,
  1495. html[data-theme=dark] .panel-default>.panel-heading, html[data-theme=dark] #header a,
  1496. html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span, html[data-theme=dark] .dropdown-menu,
  1497. html[data-theme=dark] .select2-container--bootstrap .select2-selection--single .select2-selection__rendered,
  1498. html[data-theme=dark] .ace-tm .ace_gutter, html[data-theme=dark] .translate-problem-statement-panel,
  1499. html[data-theme=dark] .select2-container--bootstrap .select2-results__option--highlighted[aria-selected],
  1500. html[data-theme=dark] .nav-pills>li.active>a, html[data-theme=dark] .user-unrated, html[data-theme=dark] #header .header-page li.is-active a,
  1501. html[data-theme=dark] .m-box_inner, html[data-theme=dark] .m-list-job_item, html[data-theme=dark] .a-btn_arrow,
  1502. html[data-theme=dark] #header, html[data-theme=dark] #header .header-sub_page li a,
  1503. html[data-theme=dark] .select2-container--default .select2-selection--single .select2-selection__rendered, html[data-theme=dark] .select2-results{
  1504. color: var(--ojb-color-text-primary) !important;
  1505. }
  1506. /* 文字颜色2 */
  1507. html[data-theme=dark] pre, html[data-theme=dark] .html2mdButton, html[data-theme=dark] .btn-default, html[data-theme=dark] .btn-pre,
  1508. html[data-theme=dark] small.contest-duration, html[data-theme=dark] .select2-container--bootstrap .select2-results__option,
  1509. html[data-theme=dark] #ace_settingsmenu, #kbshortcutmenu, html[data-theme=dark] code{
  1510. color: var(--ojb-color-text-secondary) !important;
  1511. }
  1512. /* 文字颜色3 */
  1513. html[data-theme=dark] input, html[data-theme=dark] #header .header-page li a:hover{
  1514. color: var(--ojb-color-text-secondary);
  1515. }
  1516. /* 文字颜色4 */
  1517. html[data-theme=dark] .katex{
  1518. color: var(--ojb-color-text-highlight) !important;
  1519. }
  1520. /* 链接颜色 */
  1521. html[data-theme=dark] a:link {
  1522. color: var(--ojb-color-text-link);
  1523. }
  1524. html[data-theme=dark] a:visited {
  1525. color: var(--ojb-color-text-secondary);
  1526. }
  1527. /* 按钮 */
  1528. html[data-theme=dark] input:hover, html[data-theme=dark] .btn-default:hover{
  1529. background-color: var(--ojb-color-bg-primary) !important;
  1530. }
  1531. /* 背景层次1 */
  1532. html[data-theme=dark] body, html[data-theme=dark] #main-div.float-container, html[data-theme=dark] pre,
  1533. html[data-theme=dark] .html2mdButton:hover, html[data-theme=dark] .pagination>.active>a, html[data-theme=dark] .ace-tm,
  1534. html[data-theme=dark] .dropdown-menu>li>a:hover, html[data-theme=dark] .dropdown-menu>li>a:focus,
  1535. html[data-theme=dark] .dropdown-menu .divider, html[data-theme=dark] .select2-container--bootstrap .select2-selection,
  1536. html[data-theme=dark] .ace-tm .ace_gutter-active-line, html[data-theme=dark] .select2-dropdown,
  1537. html[data-theme=dark] input, html[data-theme=dark] button, html[data-theme=dark] select, html[data-theme=dark] textarea,
  1538. html[data-theme=dark] code, html[data-theme=dark] #keyvisual .keyvisual-inner:before, html[data-theme=dark] .m-box_inner,
  1539. html[data-theme=dark] .m-list-job_item, html[data-theme=dark] .select2-container--default .select2-selection--single,
  1540. html[data-theme=dark] ol.linenums, html[data-theme=dark] li.L0, html[data-theme=dark] li.L1, html[data-theme=dark] li.L2,
  1541. html[data-theme=dark] li.L3, html[data-theme=dark] li.L4, html[data-theme=dark] li.L5, html[data-theme=dark] li.L6,
  1542. html[data-theme=dark] li.L7, html[data-theme=dark] li.L8, html[data-theme=dark] li.L9{
  1543. background-color: var(--ojb-color-bg-primary) !important;
  1544. }
  1545. /* 背景层次2 */
  1546. html[data-theme=dark] .float-container>#main-container, html[data-theme=dark] #contest-nav-tabs,
  1547. html[data-theme=dark] .btn-default, html[data-theme=dark] .html2mdButton,
  1548. html[data-theme=dark] .nav-tabs>li.active>a, html[data-theme=dark] .nav-tabs>li.active>a:hover, html[data-theme=dark] .nav-tabs>li.active>a:focus,
  1549. html[data-theme=dark] .nav>li>a:hover, html[data-theme=dark] .nav>li>a:focus, html[data-theme=dark] .panel,
  1550. html[data-theme=dark] .table-striped>tbody>tr:nth-of-type(odd), html[data-theme=dark] .insert-participant-box,
  1551. html[data-theme=dark] .btn-pre, html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-danger,
  1552. html[data-theme=dark] .alert-warning, html[data-theme=dark] .panel-default>.panel-heading,
  1553. html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span, html[data-theme=dark] .dropdown-menu,
  1554. html[data-theme=dark] .ace-tm .ace_gutter, html[data-theme=dark] .select2-container--bootstrap .select2-results__option[aria-selected=true],
  1555. html[data-theme=dark] #ace_settingsmenu, #kbshortcutmenu, html[data-theme=dark] #header .header-inner,
  1556. html[data-theme=dark] ul#config_bar_ul::-webkit-scrollbar-thumb, html[data-theme=dark] .panel-info>.panel-heading,
  1557. html[data-theme=dark] .post-footer, html[data-theme=dark] .a-btn_arrow:before,
  1558. html[data-theme=dark] .table-hover>tbody>tr:hover,
  1559. html[data-theme=dark] li.L1, html[data-theme=dark] li.L3, html[data-theme=dark] li.L5, html[data-theme=dark] li.L7,
  1560. html[data-theme=dark] li.L9{
  1561. background-color: var(--ojb-color-bg-secondary) !important;
  1562. }
  1563. /* 实线边框颜色-圆角 */
  1564. html[data-theme=dark] input{
  1565. border: var(--ojb-border-solid-primary) !important;
  1566. border-radius: 2px;
  1567. }
  1568. /* 实线边框颜色-无圆角 */
  1569. html[data-theme=dark] .btn-default, html[data-theme=dark] .html2mdButton, html[data-theme=dark] .nav-tabs>li>a:hover,
  1570. html[data-theme=dark] .nav-tabs>li.active>a, html[data-theme=dark] .nav-tabs>li.active>a:hover,
  1571. html[data-theme=dark] .nav-tabs>li.active>a:focus, html[data-theme=dark] .btn-pre, html[data-theme=dark] .btn-pre:hover,
  1572. html[data-theme=dark] pre, html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span,
  1573. html[data-theme=dark] .table-bordered>thead>tr>th, html[data-theme=dark] .table-bordered>tbody>tr>th, html[data-theme=dark] .table-bordered>tfoot>tr>th,
  1574. html[data-theme=dark] .table-bordered>thead>tr>td, html[data-theme=dark] .table-bordered>tbody>tr>td, html[data-theme=dark] .table-bordered>tfoot>tr>td,
  1575. html[data-theme=dark] .panel, html[data-theme=dark] #editor, html[data-theme=dark] div#config_bar_list, html[data-theme=dark] label.config_bar_ul_li_text,
  1576. html[data-theme=dark] .select2-container--bootstrap .select2-selection, html[data-theme=dark] .select2-container--default .select2-selection--single{
  1577. border: var(--ojb-border-solid-primary) !important;
  1578. }
  1579. html[data-theme=dark] hr, html[data-theme=dark] .panel-footer,
  1580. html[data-theme=dark] .table>thead>tr>th, html[data-theme=dark] .table>tbody>tr>th, html[data-theme=dark] .table>tfoot>tr>th,
  1581. html[data-theme=dark] .table>thead>tr>td, html[data-theme=dark] .table>tbody>tr>td, html[data-theme=dark] .table>tfoot>tr>td{
  1582. border-top: var(--ojb-border-solid-primary) !important;
  1583. }
  1584. html[data-theme=dark] .nav-tabs, html[data-theme=dark] .panel-info>.panel-heading, html[data-theme=dark] .panel-default>.panel-heading,
  1585. html[data-theme=dark] .a-btn_arrow{
  1586. border-bottom: var(--ojb-border-solid-primary) !important;
  1587. }
  1588. html[data-theme=dark] .table>thead>tr>th{
  1589. border-bottom: 2px solid var(--ojb-color-border-primary) !important;
  1590. }
  1591. /* 双实线边框 */
  1592. html[data-theme=dark] #header .header-inner{
  1593. border-bottom: 5px double var(--ojb-color-border-primary) !important;
  1594. }
  1595. /* 阴影 */
  1596. html[data-theme=dark] .float-container>#main-container{
  1597. box-shadow: 0px 0px 10px 5px #fff0;
  1598. }
  1599. /* 图片-亮度 */
  1600. html[data-theme=dark] img{
  1601. opacity: .75;
  1602. }
  1603. /* 反转 */
  1604. html[data-theme=dark] .ace_content, html[data-theme=dark] #header .header-logo img, html[data-theme=dark] pre code{
  1605. filter: invert(1) hue-rotate(.5turn);
  1606. }
  1607. /* 区域遮罩 */
  1608. html[data-theme=dark] .overlay {
  1609. background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px);
  1610. color: #9099a3;
  1611. text-shadow: 0px 0px 2px #000000;
  1612. }
  1613. /* 其他样式 */
  1614. html[data-theme=dark] .nav-tabs>li.active>a, html[data-theme=dark] .nav-tabs>li.active>a:hover, html[data-theme=dark] .nav-tabs>li.active>a:focus{
  1615. border-bottom-color: transparent !important;
  1616. }
  1617. html[data-theme=dark] .collapsible-topic.collapsed .content .collapsible-topic-options:before{
  1618. background-image: linear-gradient(#22272e00, #22272e);
  1619. }
  1620. html[data-theme=dark] .alert{
  1621. text-shadow: none;
  1622. }
  1623. html[data-theme=dark] .m-box-news_post:before{
  1624. background: linear-gradient(0deg, #22272e 50%, rgba(255,255,255,0) 100%);
  1625. }
  1626. html[data-theme=dark] #header .header-sub_page li a:before, html[data-theme=dark] #header .header-page li a:before{
  1627. background-color: #9e9e9e !important;
  1628. }
  1629. html[data-theme=dark] .standings-score{
  1630. color: #2196f3;
  1631. }
  1632. html[data-theme=dark] pre code{
  1633. background-color: transparent !important;
  1634. }
  1635. html[data-theme=dark] #fixed-server-timer {
  1636. color: #000;
  1637. }
  1638. `);
  1639. })()
  1640.  
  1641. /**
  1642. * 黑暗模式额外的处理事件
  1643. */
  1644. function darkModeStyleAdjustment() {
  1645.  
  1646. }
  1647.  
  1648. /**
  1649. * 初始化monaco编辑器资源
  1650. */
  1651. async function initMonacoEditor() {
  1652. if (OJBetter.monaco.enableOnProblemPage || OJBetter.monaco.beautifyPreBlocks) {
  1653. try {
  1654. // 等待Monaco Editor加载器脚本加载完成
  1655. await OJB_LoadJS("https://cdn.staticfile.net/monaco-editor/0.44.0/min/vs/loader.min.js");
  1656.  
  1657. // 配置Monaco Editor
  1658. require.config({
  1659. paths: { vs: "https://cdn.staticfile.net/monaco-editor/0.44.0/min/vs" },
  1660. "vs/nls": { availableLanguages: { "*": "zh-cn" } },
  1661. });
  1662.  
  1663. // 加载Monaco Editor主脚本
  1664. require(["vs/editor/editor.main"], () => {
  1665. OJBetter.monaco.loaderOnload = true;
  1666. });
  1667. } catch (error) {
  1668. console.error("Failed to load Monaco Editor: ", error);
  1669. }
  1670. }
  1671. }
  1672.  
  1673. /**
  1674. * 美化代码块
  1675. */
  1676. async function beautifyPreBlocksWithMonaco() {
  1677. // 判断monacoLoader是否加载完毕
  1678. await OJB_waitUntilTrue(() => OJBetter.monaco.loaderOnload);
  1679.  
  1680. // 用于替换 <pre> 标签为 Monaco 编辑器的函数
  1681. function replacePreWithMonaco(preElement) {
  1682. const pre = $(preElement);
  1683. if (pre.hasClass('source-code-for-copy')) return; // 跳过复制块
  1684. const code = OJB_getCodeFromPre(pre.get(0));
  1685. if (!code) return;
  1686. const language = OJB_codeLangDetect(code);
  1687.  
  1688. // 创建一个用于 Monaco 编辑器的容器
  1689. const container = $('<div></div>');
  1690. const lineCount = code.split('\n').length; // 代码的行数
  1691.  
  1692. // 计算容器的高度
  1693. const calculateContainerHeight = (lineCount) => {
  1694. const lineHeight = 20; // 每行代码的高度
  1695. const minHeight = 100; // 最小高度
  1696. const maxHeight = 1000; // 最大高度
  1697. const dynamicHeight = lineCount * lineHeight;
  1698. return Math.min(Math.max(dynamicHeight, minHeight), maxHeight) + 'px';
  1699. };
  1700.  
  1701. // 应用样式
  1702. container.css({
  1703. height: calculateContainerHeight(lineCount),
  1704. width: '100%'
  1705. });
  1706. pre.hide();
  1707. pre.after(container);
  1708.  
  1709. // 初始化 Monaco 编辑器
  1710. monaco.editor.create(container[0], {
  1711. value: code,
  1712. language: language,
  1713. readOnly: true,
  1714. tabSize: 4,
  1715. theme: OJBetter.basic.darkMode == "dark" ? "vs-dark" : "vs",
  1716. scrollbar: {
  1717. verticalScrollbarSize: 10,
  1718. horizontalScrollbarSize: 10,
  1719. alwaysConsumeMouseWheel: false
  1720. },
  1721. automaticLayout: true,
  1722. scrollBeyondLastLine: false
  1723. });
  1724. }
  1725. // 全局替换页面上所有的 <pre> 元素
  1726. $('pre').each(function () {
  1727. replacePreWithMonaco(this);
  1728. });
  1729. // 监听页面上的提交状态页面窗口的 <pre> 元素
  1730. if (OJBetter.typeOfPage.is_statePage || OJBetter.typeOfPage.is_submissions) {
  1731. OJB_observeElement({
  1732. selector: '#facebox',
  1733. callback: (node) => {
  1734. // 如果 facebox 中存在 pre 元素,则替换它们
  1735. const preElements = $(node).find('pre');
  1736. preElements.each(function () {
  1737. replacePreWithMonaco(this);
  1738. });
  1739. }
  1740. });
  1741. }
  1742. }
  1743.  
  1744. // 样式
  1745. GM_addStyle(`
  1746. /*动画*/
  1747. @keyframes shake {
  1748. 0% { transform: translateX(-5px); }
  1749. 100% { transform: translateX(5px); }
  1750. }
  1751. @keyframes rotate {
  1752. from {
  1753. transform: rotate(0deg);
  1754. }
  1755.  
  1756. to {
  1757. transform: rotate(360deg);
  1758. }
  1759. }
  1760. @keyframes rippleout {
  1761. 0% {
  1762. box-shadow: 0 0 0 0 rgba(96, 98, 102, 0.2);
  1763. }
  1764.  
  1765. 100% {
  1766. box-shadow: 0 0 0 6px rgba(0, 0, 0, 0);
  1767. }
  1768. }
  1769. @keyframes bounce-in {
  1770. 20%,40%,60%,80%,from,to {
  1771. animation-timing-function: cubic-bezier(.215,.61,.355,1);
  1772. }
  1773.  
  1774. 0% {
  1775. opacity: 0;
  1776. transform: scale3d(.995,.995,.995);
  1777. }
  1778.  
  1779. 20% {
  1780. opacity: 1;
  1781. transform: scale3d(1.005,1.005,1.005);
  1782. }
  1783.  
  1784. 40% {
  1785. transform: scale3d(.998,.998,.998);
  1786. }
  1787.  
  1788. 60% {
  1789. transform: scale3d(1.002,1.002,1.002);
  1790. }
  1791.  
  1792. 80% {
  1793. transform: scale3d(.995,.995,.995);
  1794. }
  1795.  
  1796. to {
  1797. opacity: 1;
  1798. transform: scale3d(1,1,1);
  1799. }
  1800. }
  1801. /*iconfont图标*/
  1802. .iconfont {
  1803. font-family: "iconfont" !important;
  1804. font-size: 16px;
  1805. font-style: normal !important;
  1806. -webkit-font-smoothing: antialiased;
  1807. -moz-osx-font-smoothing: grayscale;
  1808. }
  1809. @font-face {
  1810. font-family: 'iconfont'; /* Project id 4284341 */
  1811. src: url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2') format('woff2'),
  1812. url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2.ttf') format('truetype');
  1813. }
  1814. html {
  1815. scroll-behavior: smooth;
  1816. }
  1817. :root {
  1818. --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";
  1819. }
  1820. span.mdViewContent {
  1821. white-space: pre-wrap;
  1822. }
  1823.  
  1824. /* dialog */
  1825. dialog {
  1826. margin: 0px !important;
  1827. }
  1828. dialog::backdrop {
  1829. background-color: rgba(0, 0, 0, 0.4);
  1830. }
  1831.  
  1832. /*题目页链接栏样式*/
  1833. #problemToolbar {
  1834. display: flex;
  1835. flex-wrap: wrap;
  1836. justify-content: flex-end;
  1837. overflow: auto;
  1838. height: 100%;
  1839. margin: 0.5em;
  1840. }
  1841.  
  1842. /*html2md面板*/
  1843. .html2md-panel {
  1844. display: flex;
  1845. justify-content: flex-end;
  1846. align-items: center;
  1847. }
  1848. .html2md-panel a {
  1849. text-decoration: none;
  1850. }
  1851. .html2md-panel > button {
  1852. margin: 5px;
  1853. }
  1854. .html2md-panel.is_simple {
  1855. position: absolute;
  1856. right: 2%;
  1857. }
  1858.  
  1859. /*通用按钮*/
  1860. .ojb_btn {
  1861. display: flex;
  1862. align-items: center;
  1863. justify-content: center;
  1864. cursor: pointer;
  1865. background-color: #ffffff;
  1866. color: #606266;
  1867. width: auto;
  1868. font-size: 13px;
  1869. border-radius: 0.3rem;
  1870. padding: 2px 5px;
  1871. margin: 0px 5px;
  1872. border: 1px solid #dcdfe6;
  1873. }
  1874. .ojb_btn[disabled] {
  1875. cursor: not-allowed !important;
  1876. color: rgb(168, 171, 178) !important;
  1877. border: 1px solid #e4e7ed;
  1878. background-color: #ffffff;
  1879. }
  1880. .ojb_btn:hover {
  1881. color: #409eff;
  1882. border-color: #409eff;
  1883. background-color: #f1f8ff;
  1884. z-index: 150;
  1885. }
  1886. .ojb_btn.primary {
  1887. color: #ffffff;
  1888. border: 1px solid #409eff;
  1889. background-color: #409eff;
  1890. }
  1891. .ojb_btn.primary:hover {
  1892. color: #ffffff;
  1893. border: 1px solid #79bbff;
  1894. background-color: #79bbff;
  1895. }
  1896. .ojb_btn.success {
  1897. color: #4caf50;
  1898. border: 1px solid #C8E6C9;
  1899. background-color: #f0f9eb;
  1900. }
  1901. .ojb_btn.warning {
  1902. color: #e6a23c;
  1903. border: 1px solid #f3d19e;
  1904. background-color: #fdf6ec;
  1905. }
  1906. .ojb_btn.error {
  1907. color: #f56c6c;
  1908. border: 1px solid #fab6b6;
  1909. background-color: #fef0f0;
  1910. }
  1911. .ojb_btn.enabled {
  1912. color: #42A5F5;
  1913. border: 1px solid #90CAF9;
  1914. background-color: #fafbff;
  1915. }
  1916. .ojb_btn.active {
  1917. animation: rippleout 0.5s ease-in-out;
  1918. }
  1919. a.ojb_btn {
  1920. text-decoration: none;
  1921. }
  1922. a.ojb_btn:link {
  1923. color: #606266;
  1924. }
  1925. a.ojb_btn span {
  1926. margin-left: 2px;
  1927. }
  1928. /*按钮图标和popover*/
  1929. .ojb_btn_popover {
  1930. display: flex;
  1931. justify-content: center;
  1932. position: relative;
  1933. outline: none;
  1934. appearance: none;
  1935. }
  1936. .ojb_btn_popover:hover span {
  1937. opacity: 1;
  1938. visibility: visible;
  1939. }
  1940. .ojb_btn_popover i:before {
  1941. position: absolute;
  1942. text-shadow: 1px 1px 0px #ffffff, 1px -1px 0px #ffffff, -1px -1px 0px #ffffff, -1px 1px 0px #ffffff;
  1943. }
  1944. .ojb_btn_popover span {
  1945. cursor: initial;
  1946. position: absolute;
  1947. left: 50%;
  1948. opacity: 0;
  1949. visibility: hidden;
  1950. padding: 4px 8px;
  1951. background-color: rgba(33, 33, 33, 0.8);
  1952. color: rgba(255, 255, 255, 0.9019607843);
  1953. font-size: 12px;
  1954. border-radius: 6px;
  1955. line-height: 1.6;
  1956. text-align: left;
  1957. white-space: nowrap;
  1958. transition: all 0.15s ease-in-out;
  1959. z-index: 999;
  1960. }
  1961. .ojb_btn_popover span:hover {
  1962. opacity: 0;
  1963. visibility: hidden;
  1964. }
  1965. .ojb_btn_popover.top:hover span {
  1966. transform: translate(-50%, 0);
  1967. }
  1968. .ojb_btn_popover.top span {
  1969. bottom: 100%;
  1970. transform: translate(-50%, -20%);
  1971. margin-bottom: 4px;
  1972. }
  1973. .ojb_btn_popover.top span:hover {
  1974. transform: translate(-50%, -20%);
  1975. }
  1976. .ojb_btn_popover.bottom:hover span {
  1977. transform: translate(-50%, 105%);
  1978. }
  1979. .ojb_btn_popover.bottom span {
  1980. bottom: -2%;
  1981. transform: translate(-50%, 100%);
  1982. margin-top: 4px;
  1983. }
  1984. .ojb_btn_popover.bottom span:hover {
  1985. transform: translate(-50%, 50%);
  1986. }
  1987. .ojb_btn_popover.loading i {
  1988. color: rgba(33, 33, 33, 0.1);
  1989. }
  1990. .ojb_btn_popover.loading i:before {
  1991. content: "\\e640";
  1992. color: rgb(168, 171, 178);
  1993. animation: rotate 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
  1994. }
  1995. .ojb_btn_popover.running i {
  1996. color: rgba(33, 33, 33, 0.1);
  1997. }
  1998. .ojb_btn_popover.running i:before {
  1999. content: "\\e600";
  2000. color: rgb(168, 171, 178);
  2001. animation: rotate 1s linear infinite;
  2002. }
  2003. .ojb_btn_popover.warning i {
  2004. color: rgba(230, 162, 61, 0.8);
  2005. }
  2006. .ojb_btn_popover.warning i:before {
  2007. content: "\\e68b";
  2008. font-size: 15px;
  2009. left: 10px;
  2010. bottom: 0%;
  2011. color: #ff9800;
  2012. }
  2013. .ojb_btn_popover.error i {
  2014. color: rgba(245, 108, 108, 0.8);
  2015. }
  2016. .ojb_btn_popover.error i:before {
  2017. content: "\\e651";
  2018. font-size: 15px;
  2019. left: 10px;
  2020. bottom: 0%;
  2021. color: #F44336;
  2022. }
  2023. .ojb_btn_popover.success i {
  2024. color: rgba(76, 175, 80, 0.9);
  2025. }
  2026. .ojb_btn_popover.success i:before {
  2027. content: "\\e61e";
  2028. font-size: 15px;
  2029. left: 10px;
  2030. bottom: 0%;
  2031. color: #4caf50;
  2032. }
  2033. .ojb_btn_popover.enabled i {
  2034. color: rgba(33, 150, 243, 0.6);
  2035. }
  2036. .ojb_btn_popover.enabled i:before {
  2037. content: "\\e6f4";
  2038. font-size: 15px;
  2039. left: 10px;
  2040. bottom: 0%;
  2041. color: #2196F3;
  2042. }
  2043. .ojb_btn_popover.redo i {
  2044. color: rgba(33, 33, 33, 0.1);
  2045. }
  2046. .ojb_btn_popover.redo i:before {
  2047. content: "\\e831";
  2048. color: #616161;
  2049. }
  2050. .ojb_btn_popover.reverse i {
  2051. transform: rotate(180deg);
  2052. }
  2053.  
  2054. /*translateDiv样式*/
  2055. .translateDiv .topText {
  2056. display: flex;
  2057. margin-left: 5px;
  2058. color: #9e9e9e;
  2059. font-size: 13px;
  2060. align-items: center;
  2061. }
  2062. .translateDiv .borderlessButton{
  2063. display: flex;
  2064. align-items: center;
  2065. margin: 2.5px 7px;
  2066. fill: #9E9E9E;
  2067. }
  2068. .translateDiv .borderlessButton:hover{
  2069. cursor: pointer;
  2070. fill: #059669;
  2071. }
  2072. .translateDiv.bounce-in {
  2073. animation: bounce-in 1s forwards;
  2074. }
  2075. html:not([data-theme='dark']) .translateDiv {
  2076. box-shadow: 0px 0px 0.5px 0.5px #defdf378;
  2077. }
  2078. .translate-problem-statement {
  2079. justify-items: start;
  2080. letter-spacing: 1.8px;
  2081. color: #059669;
  2082. background-color: #f9f9fa;
  2083. border: 1px solid #c5ebdf;
  2084. border-radius: 0rem 0rem 0.3rem 0.3rem;
  2085. padding: 5px;
  2086. margin: -5px 0px 6px 0px;
  2087. width: 100%;
  2088. box-sizing: border-box;
  2089. font-size: 13px;
  2090. }
  2091. .translate-problem-statement h2 {
  2092. font-size: 1.6em;
  2093. font-weight: 700;
  2094. }
  2095. .translate-problem-statement h3 {
  2096. font-size: 1.3em;
  2097. font-weight: 700;
  2098. }
  2099. .translate-problem-statement-panel{
  2100. display: flex;
  2101. justify-content: space-between;
  2102. background-color: #f9f9fa;
  2103. border: 1px solid #c5ebdf;
  2104. border-radius: 0.3rem;
  2105. margin: 4px 0px;
  2106. }
  2107. .translate-problem-statement-panel .ojb_btn {
  2108. background: none;
  2109. border: none;
  2110. color: #9e9e9e;
  2111. }
  2112. .translate-problem-statement-panel.error, .translate-problem-statement.error {
  2113. color: red;
  2114. border-color: red;
  2115. }
  2116. .translate-problem-statement a, .translate-problem-statement a:link {
  2117. color: #10b981;
  2118. font-weight: 600;
  2119. background: 0 0;
  2120. text-decoration: none;
  2121. }
  2122. .translate-problem-statement ol, .translate-problem-statement ul {
  2123. display: grid;
  2124. margin-inline-start: 0.8em;
  2125. margin-block-start: 0em;
  2126. margin: 0.5em 0 0 3em;
  2127. padding-inline-start: 0px;
  2128. }
  2129. .translate-problem-statement li {
  2130. display: list-item;
  2131. height: auto;
  2132. word-wrap: break-word;
  2133. }
  2134. .translate-problem-statement ol li {
  2135. list-style-type: auto;
  2136. }
  2137. .translate-problem-statement ul li {
  2138. list-style-type: disc;
  2139. }
  2140. .translate-problem-statement img {
  2141. max-width: 100.0%;
  2142. max-height: 100.0%;
  2143. }
  2144. #task-statement .translate-problem-statement .MathJax {
  2145. color: #059669!important;
  2146. }
  2147. .translate-problem-statement span.math {
  2148. margin: 0px 2.5px !important;
  2149. }
  2150. .translate-problem-statement a:hover {
  2151. background-color: #800;
  2152. color: #fff;
  2153. text-decoration: none;
  2154. }
  2155. .translate-problem-statement table {
  2156. border: 1px #ccc solid !important;
  2157. margin: 1.5em 0 !important;
  2158. color: #059669 !important;
  2159. }
  2160. .translate-problem-statement table thead th {
  2161. border: 1px #ccc solid !important;
  2162. color: #059669 !important;
  2163. }
  2164. .translate-problem-statement table td {
  2165. border-right: 1px solid #ccc;
  2166. border-top: 1px solid #ccc;
  2167. padding: 0.7143em 0.5em;
  2168. }
  2169. .translate-problem-statement table th {
  2170. padding: 0.7143em 0.5em;
  2171. }
  2172. .translate-problem-statement p:not(:first-child) {
  2173. margin: 1.5em 0 0;
  2174. }
  2175. .translate-problem-statement p {
  2176. line-height: 20px !important;
  2177. word-wrap: break-word;
  2178. font-size: 13px !important
  2179. }
  2180. .problem-statement p:last-child {
  2181. margin-bottom: 0px !important;
  2182. }
  2183.  
  2184. /*设置按钮*/
  2185. header .enter-or-register-box, header .languages {
  2186. position: absolute;
  2187. right: 170px;
  2188. }
  2189. .ojb_btn.OJBetter_setting {
  2190. float: right;
  2191. height: 30px;
  2192. background: #60a5fa;
  2193. color: white;
  2194. margin: 10px;
  2195. border: 1px solid #60a5fa;
  2196. }
  2197. .ojb_btn.OJBetter_setting.open {
  2198. background-color: #e6e6e6;
  2199. color: #727378;
  2200. cursor: not-allowed;
  2201. }
  2202.  
  2203. /*设置面板*/
  2204. .OJBetter_setting_menu {
  2205. box-shadow: 0px 0px 0px 4px #ffffff;
  2206. position: fixed;
  2207. top: 50%;
  2208. left: 50%;
  2209. width: 600px;
  2210. min-height: 600px;
  2211. transform: translate(-50%, -50%);
  2212. border-radius: 6px;
  2213. background-color: #f0f4f9;
  2214. border-collapse: collapse;
  2215. border: 1px solid #ffffff;
  2216. color: #697e91;
  2217. font-family: var(--vp-font-family-base);
  2218. padding: 10px 20px 20px 10px;
  2219. box-sizing: content-box;
  2220. }
  2221. .OJBetter_setting_menu h3 {
  2222. margin-top: 10px;
  2223. font-size: 1.4em;
  2224. font-weight: 700;
  2225. }
  2226. .OJBetter_setting_menu h4 {
  2227. margin: 15px 0px 10px 0px;
  2228. }
  2229. .OJBetter_setting_menu h4,.OJBetter_setting_menu h5 {
  2230. font-weight: 600;
  2231. }
  2232. .OJBetter_setting_menu hr {
  2233. border: none;
  2234. height: 1px;
  2235. background-color: #ccc;
  2236. margin: 10px 0;
  2237. }
  2238. .OJBetter_setting_menu details {
  2239. padding: 10px;
  2240. margin-bottom: 5px;
  2241. background-color: #ffffff;
  2242. border-bottom: 1px solid #c9c6c696;
  2243. border-radius: 8px;
  2244. }
  2245. .OJBetter_setting_menu .badge {
  2246. border-radius: 4px;
  2247. border: 1px solid #009688;
  2248. color: #009688;
  2249. background-color: #fff;
  2250. padding: 0.5px 4px;
  2251. margin-left: 5px;
  2252. margin-right: auto;
  2253. line-height: initial;
  2254. font-weight: initial;
  2255. }
  2256. .OJBetter_setting_menu .missing {
  2257. box-shadow: inset 0px 0px 1px 1px red;
  2258. }
  2259. /* 页面切换 */
  2260. .OJBetter_setting_menu .settings-page {
  2261. display: none;
  2262. }
  2263. .OJBetter_setting_menu .settings-page.active {
  2264. display: block;
  2265. }
  2266. .OJBetter_setting_container {
  2267. display: flex;
  2268. }
  2269. .OJBetter_setting_sidebar {
  2270. flex: 0 0 auto;
  2271. min-width: 110px;
  2272. padding: 6px 10px 6px 6px;
  2273. margin: 20px 0px;
  2274. border-right: 1px solid #d4d8e9;
  2275. }
  2276. .OJBetter_setting_content {
  2277. flex-grow: 1;
  2278. margin: 20px 0px 0px 12px;
  2279. padding-right: 10px;
  2280. max-height: 580px;
  2281. overflow-y: auto;
  2282. box-sizing: border-box;
  2283. }
  2284. .OJBetter_setting_sidebar h3 {
  2285. margin-top: 0;
  2286. }
  2287. .OJBetter_setting_sidebar hr {
  2288. margin-top: 10px;
  2289. margin-bottom: 10px;
  2290. border: none;
  2291. border-top: 1px solid #DADCE0;
  2292. }
  2293. .OJBetter_setting_sidebar ul {
  2294. list-style-type: none;
  2295. margin: 0;
  2296. padding: 0;
  2297. }
  2298. .OJBetter_setting_sidebar li {
  2299. margin: 5px 0px;
  2300. background-color: #ffffff;
  2301. border: 1px solid #d4d8e9;
  2302. border-radius: 4px;
  2303. font-size: 16px;
  2304. }
  2305. .OJBetter_setting_sidebar li a {
  2306. text-decoration: none;
  2307. display: flex;
  2308. width: 100%;
  2309. font-size: 16px;
  2310. color: gray;
  2311. background-color: #ffffff;
  2312. border: none;
  2313. letter-spacing: 2px;
  2314. padding: 7px;
  2315. margin: 0px;
  2316. border-radius: 4px;
  2317. align-items: center;
  2318. -webkit-box-sizing: border-box;
  2319. -moz-box-sizing: border-box;
  2320. box-sizing: border-box;
  2321. }
  2322. .OJBetter_setting_sidebar li a.active {
  2323. background-color: #eceff1c7;
  2324. }
  2325. /* 链接样式 */
  2326. .OJBetter_setting_menu a {
  2327. font-size: 13px;
  2328. color: #009688;
  2329. background-color: #E0F2F1;
  2330. border: 1px solid #009688;
  2331. border-radius: 4px;
  2332. padding: 0px 5px;
  2333. margin: 0px 5px;
  2334. text-decoration: none;
  2335. }
  2336. /* 下拉选择框 */
  2337. .OJBetter_setting_menu select {
  2338. appearance: none;
  2339. padding: 5px 10px;
  2340. margin: -5px 0px;
  2341. border-radius: 6px;
  2342. border-style: solid;
  2343. border: 1px solid #ced4da;
  2344. color: #009688;
  2345. background: #ffffff;
  2346. font-size: 15px;
  2347. }
  2348. .OJBetter_setting_menu select:focus-visible {
  2349. outline: none;
  2350. }
  2351. .OJBetter_setting_menu select option:disabled {
  2352. color: #EEEEEE;
  2353. }
  2354. /* 数值输入框 */
  2355. .OJBetter_setting_menu input[type="number"] {
  2356. width: 70px;
  2357. color: #009688;
  2358. font-size: 15px;
  2359. appearance: none;
  2360. padding: 5px 10px;
  2361. margin: -5px 3px;
  2362. border-radius: 6px;
  2363. border-style: solid;
  2364. border: 1px solid #ced4da;
  2365. }
  2366. .OJBetter_setting_menu input[type="number"]:focus-visible {
  2367. outline: none;
  2368. }
  2369. .OJBetter_setting_menu input[type="number"]::-webkit-inner-spin-button,
  2370. .OJBetter_setting_menu input[type="number"]::-webkit-outer-spin-button {
  2371. -webkit-appearance: none;
  2372. margin: 0;
  2373. }
  2374. /*设置面板-滚动条*/
  2375. .OJBetter_setting_menu::-webkit-scrollbar, .OJBetter_setting_content::-webkit-scrollbar,
  2376. .OJBetter_modal .content::-webkit-scrollbar {
  2377. width: 5px;
  2378. height: 7px;
  2379. background-color: #aaa;
  2380. }
  2381. .OJBetter_setting_menu::-webkit-scrollbar-thumb, .OJBetter_setting_content::-webkit-scrollbar-thumb,
  2382. .OJBetter_modal .content::-webkit-scrollbar-thumb {
  2383. background-clip: padding-box;
  2384. background-color: #d7d9e4;
  2385. }
  2386. .OJBetter_setting_menu::-webkit-scrollbar-track, .OJBetter_setting_content::-webkit-scrollbar-track,
  2387. .OJBetter_modal .content::-webkit-scrollbar-track {
  2388. background-color: #f1f1f1;
  2389. }
  2390. /*设置面板-关闭按钮*/
  2391. .OJBetter_setting_menu .tool-box {
  2392. position: absolute;
  2393. width: 20px;
  2394. height: 20px;
  2395. top: 3px;
  2396. right: 3px;
  2397. }
  2398. .OJBetter_setting_menu .btn-close {
  2399. width: 20px;
  2400. height: 20px;
  2401. border-radius: 50%;
  2402. border: none;
  2403. margin: 0px;
  2404. padding: 0px;
  2405. background-color: #ff000080;
  2406. transition: .15s ease all;
  2407. box-sizing: border-box;
  2408. text-align: center;
  2409. color: transparent;
  2410. }
  2411. .OJBetter_setting_menu .iconfont {
  2412. font-size: 10px;
  2413. font-weight: bolder;
  2414. }
  2415. .OJBetter_setting_menu .btn-close:hover {
  2416. color: #ffffff;
  2417. background-color: #ff0000cc;
  2418. box-shadow: 0 5px 5px 0 #00000026;
  2419. }
  2420. .OJBetter_setting_menu .btn-close:active {
  2421. color: #ffffffde;
  2422. background-color: #ff000080;
  2423. }
  2424. /*设置面板-checkbox*/
  2425. .OJBetter_setting_menu input[type=checkbox]:focus {
  2426. outline: 0px;
  2427. }
  2428. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"] {
  2429. margin: 0px;
  2430. appearance: none;
  2431. -webkit-appearance: none;
  2432. width: 40px;
  2433. height: 20px;
  2434. border: 1.5px solid #D7CCC8;
  2435. padding: 0px !important;
  2436. border-radius: 20px;
  2437. background: #efebe978;
  2438. position: relative;
  2439. box-sizing: border-box;
  2440. }
  2441. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::before {
  2442. content: "";
  2443. width: 17px;
  2444. height: 17px;
  2445. background: #D7CCC8;
  2446. border: 1.5px solid #BCAAA4;
  2447. border-radius: 50%;
  2448. position: absolute;
  2449. top: 0;
  2450. left: 0;
  2451. transform: translate(2%, 2%);
  2452. transition: all 0.3s ease-in-out;
  2453. box-sizing: border-box;
  2454. }
  2455. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::after {
  2456. 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");
  2457. position: absolute;
  2458. top: 0;
  2459. left: 24px;
  2460. }
  2461. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked {
  2462. border: 1.5px solid #C5CAE9;
  2463. background: #E8EAF6;
  2464. }
  2465. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::before {
  2466. background: #C5CAE9;
  2467. border: 1.5px solid #7986CB;
  2468. transform: translate(122%, 2%);
  2469. transition: all 0.3s ease-in-out;
  2470. }
  2471. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::after {
  2472. 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");
  2473. position: absolute;
  2474. top: 1.5px;
  2475. left: 4.5px;
  2476. }
  2477. .OJBetter_setting_menu .OJBetter_setting_list button {
  2478. cursor: pointer;
  2479. color: #7986cb;
  2480. background-color: #e8eaf6;
  2481. border: 1px solid #7986cb;
  2482. border-radius: 6px;
  2483. width: 100px;
  2484. margin: -5px 2px;
  2485. padding: 5px 10px;
  2486. }
  2487. .OJBetter_setting_menu .OJBetter_setting_list button:hover {
  2488. color: #e8eaf6;
  2489. background-color: #7986cb;
  2490. border: 1px solid #7986cb;
  2491. }
  2492. .OJBetter_setting_menu label, #darkMode_span, #loaded_span {
  2493. font-size: 16px;
  2494. }
  2495. .OJBetter_setting_list {
  2496. display: flex;
  2497. flex-wrap: wrap;
  2498. align-items: center;
  2499. padding: 10px;
  2500. margin: 5px 0px;
  2501. background-color: #ffffff;
  2502. border: 1px solid #c9c6c642;
  2503. border-bottom-color: #c9c6c696;
  2504. border-radius: 8px;
  2505. justify-content: space-between;
  2506. }
  2507. .OJBetter_setting_list.alert_danger {
  2508. color: #F44336;
  2509. background-color: #FFEBEE;
  2510. border: 1px solid #F44336;
  2511. margin: 10px 0px;
  2512. }
  2513. .OJBetter_setting_list.alert_warn {
  2514. color: #E65100;
  2515. background-color: #FFF3E0;
  2516. border: 1px solid #FF9800;
  2517. margin: 10px 0px;
  2518. }
  2519. .OJBetter_setting_list.alert_tip {
  2520. color: #009688;
  2521. background-color: #E0F2F1;
  2522. border: 1px solid #009688;
  2523. margin: 10px 0px;
  2524. }
  2525. .OJBetter_setting_list.alert_info {
  2526. color: #ffffff;
  2527. background-color: #009688;
  2528. margin: 10px 0px;
  2529. box-shadow: rgba(0, 0, 0, 0.06) 0px 2px 4px 0px inset;
  2530. }
  2531. .OJBetter_setting_list p:not(:last-child) {
  2532. margin-bottom: 10px;
  2533. }
  2534. .OJBetter_setting_list p:not(:first-child) {
  2535. margin-top: 10px;
  2536. }
  2537. /*设置面板-checkboxs*/
  2538. .OJBetter_setting_menu .OJBetter_checkboxs {
  2539. flex-basis: 100%;
  2540. display: flex;
  2541. padding: 8px;
  2542. margin: 10px 0px 0px 0px;
  2543. border-bottom: 1px solid #c9c6c696;
  2544. border-radius: 8px;
  2545. border: 1px solid #c5cae9;
  2546. background-color: #f0f8ff;
  2547. }
  2548. .OJBetter_setting_menu .OJBetter_checkboxs label {
  2549. font-size: 13px;
  2550. margin: 0px 6px 0px 3px;
  2551. }
  2552. .OJBetter_setting_menu .OJBetter_checkboxs input[type=checkbox]:checked+label{
  2553. color: #7986cb;
  2554. }
  2555. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"] {
  2556. border: none;
  2557. width: 16px;
  2558. height: 16px;
  2559. }
  2560. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before{
  2561. background: #ffffff;
  2562. transform: none;
  2563. width: 16px;
  2564. height: 16px;
  2565. }
  2566. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked {
  2567. background: none;
  2568. border: none;
  2569. }
  2570. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before {
  2571. border: 1.5px solid #95a2de;
  2572. background: #e8eaf6;
  2573. transform: none;
  2574. }
  2575. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::after {
  2576. 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");
  2577. top: 0px;
  2578. left: 3.5px;
  2579. }
  2580. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:disabled+label {
  2581. color: #BDBDBD;
  2582. }
  2583. /*设置面板-radio*/
  2584. .OJBetter_setting_menu label {
  2585. display: block;
  2586. font-weight: initial;
  2587. list-style-type: none;
  2588. padding-inline-start: 0px;
  2589. overflow-x: auto;
  2590. max-width: 100%;
  2591. margin: 3px 0px;
  2592. overflow-x: visible;
  2593. }
  2594. .OJBetter_setting_menu_label_text {
  2595. display: flex;
  2596. border: 1px dashed #00aeeccc;
  2597. height: 35px;
  2598. width: 100%;
  2599. color: #6e6e6e;
  2600. font-weight: 300;
  2601. font-size: 14px;
  2602. letter-spacing: 2px;
  2603. padding: 7px;
  2604. margin-bottom: 4px;
  2605. align-items: center;
  2606. -webkit-box-sizing: border-box;
  2607. -moz-box-sizing: border-box;
  2608. box-sizing: border-box;
  2609. }
  2610. input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
  2611. background: #41e49930;
  2612. border: 1px solid green;
  2613. color: green;
  2614. text-shadow: 0px 0px 0.5px green;
  2615. }
  2616. input[type="radio"]:disabled+.OJBetter_setting_menu_label_text {
  2617. background: #fafafa00;
  2618. border: 1px solid #e0e0e07a;
  2619. color: #e0e0e0;
  2620. }
  2621. .OJBetter_setting_menu label input[type="radio"], .OJBetter_contextmenu label input[type="radio"]{
  2622. appearance: none;
  2623. list-style: none;
  2624. padding: 0px !important;
  2625. margin: 0px;
  2626. clip: rect(0 0 0 0);
  2627. -webkit-clip-path: inset(100%);
  2628. clip-path: inset(100%);
  2629. height: 1px;
  2630. overflow: hidden;
  2631. position: absolute;
  2632. white-space: nowrap;
  2633. width: 1px;
  2634. }
  2635. /*设置面板-文本输入框*/
  2636. .OJBetter_setting_menu input[type="text"] {
  2637. display: block;
  2638. height: 25px !important;
  2639. width: 100%;
  2640. background-color: #ffffff;
  2641. color: #727378;
  2642. font-size: 12px;
  2643. border-radius: 0.3rem;
  2644. padding: 1px 5px !important;
  2645. box-sizing: border-box;
  2646. margin: 5px 0px 5px 0px;
  2647. border: 1px solid #00aeeccc;
  2648. box-shadow: 0 0 1px #0000004d;
  2649. }
  2650. .OJBetter_setting_menu .OJBetter_setting_list input[type="text"] {
  2651. margin-left: 5px;
  2652. }
  2653. .OJBetter_setting_menu input[type="text"]:focus-visible{
  2654. border-style: solid;
  2655. border-color: #3f51b5;
  2656. outline: none;
  2657. }
  2658. .OJBetter_setting_menu_config_box {
  2659. width: 100%;
  2660. display: grid;
  2661. margin-top: 5px;
  2662. -webkit-box-sizing: border-box;
  2663. -moz-box-sizing: border-box;
  2664. box-sizing: border-box;
  2665. }
  2666. .OJBetter_setting_menu input::placeholder {
  2667. color: #727378;
  2668. }
  2669. .OJBetter_setting_menu input.no_default::placeholder{
  2670. color: #BDBDBD;
  2671. }
  2672. .OJBetter_setting_menu input.is_null::placeholder{
  2673. color: red;
  2674. border-width: 1.5px;
  2675. }
  2676. .OJBetter_setting_menu input.is_null{
  2677. border-color: red;
  2678. }
  2679. .OJBetter_setting_menu textarea {
  2680. resize: vertical;
  2681. display: block;
  2682. width: 100%;
  2683. height: 60px;
  2684. background-color: #ffffff;
  2685. color: #727378;
  2686. font-size: 12px;
  2687. padding: 1px 5px !important;
  2688. box-sizing: border-box;
  2689. margin: 5px 0px 5px 0px;
  2690. border: 1px solid #00aeeccc;
  2691. box-shadow: 0 0 1px #0000004d;
  2692. }
  2693. .OJBetter_setting_menu textarea:focus-visible{
  2694. border-style: solid;
  2695. border-color: #3f51b5;
  2696. outline: none;
  2697. }
  2698. .OJBetter_setting_menu textarea::placeholder{
  2699. color: #BDBDBD;
  2700. font-size: 14px;
  2701. }
  2702. .OJBetter_setting_menu #tempConfig_save {
  2703. cursor: pointer;
  2704. display: inline-flex;
  2705. padding: 5px;
  2706. background-color: #1aa06d;
  2707. color: #ffffff;
  2708. font-size: 14px;
  2709. line-height: 1.5rem;
  2710. font-weight: 500;
  2711. justify-content: center;
  2712. width: 100%;
  2713. border-radius: 0.375rem;
  2714. border: none;
  2715. box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  2716. margin-top: 20px
  2717. }
  2718. .OJBetter_setting_menu button#debug_button.debug_button {
  2719. width: 18%;
  2720. }
  2721. .OJBetter_setting_menu span.tip {
  2722. color: #999;
  2723. font-size: 12px;
  2724. font-weight: 500;
  2725. padding: 5px 0px;
  2726. }
  2727. /*设置面板-tip*/
  2728. .help_tip {
  2729. margin-right: auto;
  2730. }
  2731. span.input_label {
  2732. font-size: 14px;
  2733. }
  2734. .help_tip .tip_text {
  2735. display: none;
  2736. position: absolute;
  2737. color: #697e91;
  2738. font-weight: 400;
  2739. font-size: 14px;
  2740. letter-spacing: 0px;
  2741. background-color: #ffffff;
  2742. padding: 10px;
  2743. margin: 5px 0px;
  2744. border-radius: 4px;
  2745. border: 1px solid #e4e7ed;
  2746. box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);
  2747. z-index: 100;
  2748. }
  2749. .help_tip .tip_text p {
  2750. margin-bottom: 5px;
  2751. }
  2752. .help_tip .tip_text:before {
  2753. content: "";
  2754. position: absolute;
  2755. top: -20px;
  2756. right: -10px;
  2757. bottom: -10px;
  2758. left: -10px;
  2759. z-index: -1;
  2760. }
  2761. .help-icon {
  2762. cursor: help;
  2763. width: 15px;
  2764. color: #b4b9d4;
  2765. margin-left: 5px;
  2766. margin-top: 3px;
  2767. }
  2768. .OJBetter_setting_menu .OJBetter_setting_menu_label_text .help_tip .help-icon {
  2769. color: #7fbeb2;
  2770. }
  2771. .help_tip .help-icon:hover + .tip_text, .help_tip .tip_text:hover {
  2772. display: block;
  2773. cursor: help;
  2774. width: 250px;
  2775. }
  2776. /* 版本信息 */
  2777. .OJBetter_setting_menu .versionInfo{
  2778. display: grid;
  2779. justify-items: center;
  2780. font-size: 16px;
  2781. padding: 10px;
  2782. }
  2783. .OJBetter_setting_menu .versionInfo>* {
  2784. margin: 10px 0px;
  2785. }
  2786.  
  2787. /* 配置管理面板 */
  2788. .config{
  2789. width: 100%;
  2790. margin: 10px 0px;
  2791. }
  2792. .config li.tempConfig_add_button {
  2793. cursor: pointer;
  2794. height: 40px;
  2795. border: 1px dashed #BDBDBD;
  2796. border-radius: 8px;
  2797. background-color: #fcfbfb36;
  2798. color: #bdbdbd;
  2799. font-size: 14px;
  2800. align-items: center;
  2801. justify-content: center;
  2802. }
  2803. .config li.tempConfig_add_button:hover {
  2804. border: 1px dashed #03A9F4;
  2805. background-color: #d7f0fb8c;
  2806. color: #03A9F4;
  2807. }
  2808. .config .config_bar_list {
  2809. display: flex;
  2810. width: 100%;
  2811. padding-bottom: 2px;
  2812. border: 1px solid #c5cae9;
  2813. background-color: #f0f8ff;
  2814. box-sizing: border-box;
  2815. border-radius: 0px 0px 8px 8px;
  2816. }
  2817. .config .config_bar_list input[type="radio"] {
  2818. appearance: none;
  2819. width: 0;
  2820. height: 0;
  2821. overflow: hidden;
  2822. }
  2823. .config .config_bar_list input[type="radio"] {
  2824. margin: 0px;
  2825. }
  2826. .config .config_bar_list input[type=radio]:focus {
  2827. outline: 0px;
  2828. }
  2829. .config .config_bar_ul_li_text {
  2830. display: flex;
  2831. align-items: center;
  2832. justify-content: center;
  2833. max-width: 100%;
  2834. height: 40px;
  2835. overflow-x: auto;
  2836. font-size: 14px;
  2837. font-weight: 400;
  2838. margin: 0px 4px;
  2839. padding: 3px;
  2840. border: 1px solid #dedede;
  2841. border-radius: 10px;
  2842. box-shadow: 0px 2px 4px 0px rgba(0,0,0,.05);
  2843. box-sizing: border-box;
  2844. }
  2845. .config .config_bar_ul li button {
  2846. background-color: #e6e6e6;
  2847. color: #727378;
  2848. height: 23px;
  2849. font-size: 14px;
  2850. border-radius: 0.3rem;
  2851. padding: 1px 5px;
  2852. margin: 5px;
  2853. border: none;
  2854. box-shadow: 0 0 1px #0000004d;
  2855. }
  2856. .config .config_bar_ul {
  2857. display: flex;
  2858. align-items: center;
  2859. list-style-type: none;
  2860. padding-inline-start: 0px;
  2861. overflow-x: auto;
  2862. max-width: 100%;
  2863. margin: 0px;
  2864. padding: 5px;
  2865. }
  2866. .config .config_bar_ul li {
  2867. width: 80px;
  2868. display: grid;
  2869. margin: 4px 4px;
  2870. min-width: 100px;
  2871. box-sizing: border-box;
  2872. }
  2873. .config .config_bar_ul_li_text:hover {
  2874. background-color: #eae4dc24;
  2875. }
  2876. input[type="radio"]:checked + .config_bar_ul_li_text {
  2877. background: #41b3e430;
  2878. border: 1px solid #5e7ce0;
  2879. color: #5e7ce0;
  2880. }
  2881. .config .config_bar_ul::-webkit-scrollbar {
  2882. width: 5px;
  2883. height: 4px;
  2884. }
  2885. .config .config_bar_ul::-webkit-scrollbar-thumb {
  2886. background-clip: padding-box;
  2887. background-color: #d7d9e4;
  2888. border-radius: 8px;
  2889. }
  2890. .config .config_bar_ul::-webkit-scrollbar-button:start:decrement {
  2891. width: 4px;
  2892. background-color: transparent;
  2893. }
  2894. .config .config_bar_ul::-webkit-scrollbar-button:end:increment {
  2895. width: 4px;
  2896. background-color: transparent;
  2897. }
  2898. .config .config_bar_ul::-webkit-scrollbar-track {
  2899. border-radius: 5px;
  2900. }
  2901. .config .config_bar_ul_li_text::-webkit-scrollbar {
  2902. width: 5px;
  2903. height: 7px;
  2904. background-color: #aaa;
  2905. }
  2906. .config .config_bar_ul_li_text::-webkit-scrollbar-thumb {
  2907. background-clip: padding-box;
  2908. background-color: #d7d9e4;
  2909. }
  2910. .config .config_bar_ul_li_text::-webkit-scrollbar-track {
  2911. background-color: #f1f1f1;
  2912. }
  2913. .config .config_bar_list_add_div {
  2914. display: flex;
  2915. height: 40px;
  2916. margin: 4px 2px;
  2917. }
  2918.  
  2919. /* 修改菜单 */
  2920. #config_bar_menu {
  2921. z-index: 400;
  2922. position: fixed;
  2923. width: 60px;
  2924. background: #ffffff;
  2925. box-shadow: 1px 1px 4px 0px #0000004d;
  2926. border: 0px solid rgba(0,0,0,0.04);
  2927. border-radius: 4px;
  2928. padding: 8px 0;
  2929. }
  2930. .config_bar_menu_item {
  2931. cursor: pointer;
  2932. padding: 2px 6px;
  2933. display: flex;
  2934. justify-content: center;
  2935. align-items: center;
  2936. height: 32px;
  2937. font-size: 14px;
  2938. font-weight: 500;
  2939. box-shadow: inset 0px 0px 0px 0px #8bb2d9;
  2940. }
  2941. #config_bar_menu_edit:hover {
  2942. background-color: #00aeec;
  2943. color: white;
  2944. }
  2945. #config_bar_menu_delete:hover {
  2946. background-color: #FF5722;
  2947. color: white;
  2948. }
  2949.  
  2950. /* 配置编辑页面 */
  2951. #config_edit_menu {
  2952. z-index: 300;
  2953. width: 450px;
  2954. }
  2955.  
  2956. /* 黑暗模式选项按钮 */
  2957. .dark-mode-selection {
  2958. display: flex;
  2959. justify-content: center;
  2960. align-items: center;
  2961. max-width: 350px;
  2962. -webkit-user-select: none;
  2963. -moz-user-select: none;
  2964. -ms-user-select: none;
  2965. user-select: none;
  2966. }
  2967. .dark-mode-selection label {
  2968. margin: 8px 0px 8px 8px;
  2969. }
  2970. .dark-mode-selection > * {
  2971. margin: 6px;
  2972. }
  2973. .dark-mode-selection .OJBetter_setting_menu_label_text {
  2974. border-radius: 8px;
  2975. margin-bottom: 0px;
  2976. }
  2977.  
  2978. /*确认弹窗*/
  2979. .OJBetter_modal {
  2980. z-index: 600;
  2981. display: grid;
  2982. position: fixed;
  2983. top: 50%;
  2984. left: 50%;
  2985. transform: translate(-50%, -50%);
  2986. font-size: 12px;
  2987. font-family: var(--vp-font-family-base);
  2988. width: max-content;
  2989. padding: 10px 20px;
  2990. box-shadow: 0px 0px 0px 4px #ffffff;
  2991. border-radius: 6px;
  2992. background-color: #f0f4f9;
  2993. border-collapse: collapse;
  2994. border: 1px solid #ffffff;
  2995. color: #697e91;
  2996. }
  2997. .OJBetter_modal h2 {
  2998. font-size: 1.6em;
  2999. font-weight: 700;
  3000. }
  3001. .OJBetter_modal .content{
  3002. white-space: nowrap;
  3003. max-height: 500px;
  3004. overflow-y: auto;
  3005. }
  3006. .OJBetter_modal .buttons{
  3007. display: flex;
  3008. padding-top: 15px;
  3009. }
  3010. .OJBetter_modal button {
  3011. display: inline-flex;
  3012. justify-content: center;
  3013. align-items: center;
  3014. line-height: 1;
  3015. white-space: nowrap;
  3016. cursor: pointer;
  3017. text-align: center;
  3018. box-sizing: border-box;
  3019. outline: none;
  3020. transition: .1s;
  3021. user-select: none;
  3022. vertical-align: middle;
  3023. -webkit-appearance: none;
  3024. height: 24px;
  3025. padding: 5px 11px;
  3026. margin-right: 15px;
  3027. font-size: 12px;
  3028. border-radius: 4px;
  3029. color: #ffffff;
  3030. background: #009688;
  3031. border-color: #009688;
  3032. border: none;
  3033. }
  3034. .OJBetter_modal button.secondary{
  3035. background-color:#4DB6AC;
  3036. }
  3037. .OJBetter_modal button:hover{
  3038. background-color:#4DB6AC;
  3039. }
  3040. .OJBetter_modal button.secondary:hover {
  3041. background-color: #80CBC4;
  3042. }
  3043. .OJBetter_modal .help-icon {
  3044. margin: 0px 8px 0px 0px;
  3045. height: 1em;
  3046. width: 1em;
  3047. line-height: 1em;
  3048. display: inline-flex;
  3049. justify-content: center;
  3050. align-items: center;
  3051. position: relative;
  3052. fill: currentColor;
  3053. font-size: inherit;
  3054. }
  3055. .OJBetter_modal p {
  3056. margin: 5px 0px;
  3057. }
  3058.  
  3059. /* 右键菜单 */
  3060. .OJBetter_contextmenu {
  3061. z-index: 500;
  3062. display: grid;
  3063. position: absolute;
  3064. background-color: #f0f4f9;
  3065. border-collapse: collapse;
  3066. color: #697e91;
  3067. font-family: var(--vp-font-family-base);
  3068. overflow: hidden;
  3069. box-sizing: content-box;
  3070. box-shadow: 0px 0px 0px 2px #eddbdb4d;
  3071. }
  3072. .OJBetter_contextmenu label {
  3073. margin: 0px;
  3074. }
  3075. input[type="radio"]:checked+.OJBetter_contextmenu_label_text {
  3076. background: #41e49930;
  3077. border: 1px solid green;
  3078. color: green;
  3079. font-weight: 500;
  3080. }
  3081. .OJBetter_contextmenu_label_text {
  3082. display: flex;
  3083. border: 1px dashed #80cbc4;
  3084. height: 26px;
  3085. width: 100%;
  3086. color: gray;
  3087. font-size: 13px;
  3088. font-weight: initial;
  3089. padding: 4px;
  3090. align-items: center;
  3091. -webkit-box-sizing: border-box;
  3092. -moz-box-sizing: border-box;
  3093. box-sizing: border-box;
  3094. }
  3095. .OJBetter_contextmenu_label_text:hover {
  3096. color: #F44336;
  3097. border: 1px dashed #009688;
  3098. background-color: #ffebcd;
  3099. }
  3100.  
  3101. /* RatingByClist */
  3102. .ratingBadge, html[data-theme=dark] button.ratingBadge{
  3103. display: block;
  3104. font-weight: 700;
  3105. font-size: 11px;
  3106. margin-top: 5px;
  3107. padding: 2px;
  3108. border-radius: 4px;
  3109. color: #B0BEC5;
  3110. border: 1px solid #cccccc66;
  3111. }
  3112.  
  3113. /* 多选翻译 */
  3114. .block_selected{
  3115. box-shadow: 0px 0px 0px 1px #FF9800;
  3116. outline: none;
  3117. }
  3118.  
  3119. /* 悬浮菜单 */
  3120. .OJBetter_MiniTranslateButton {
  3121. z-index: 100;
  3122. display: grid;
  3123. position: absolute;
  3124. border-collapse: collapse;
  3125. fill: #F57C00;
  3126. background-color: #FFF3E0;
  3127. overflow: hidden;
  3128. box-sizing: content-box;
  3129. box-shadow: 0px 0px 0px 2px #FFE0B2;
  3130. border-radius: 100%;
  3131. }
  3132. .OJBetter_MiniTranslateButton:hover {
  3133. cursor: pointer;
  3134. box-shadow: 0px 0px 0px 2px #FFB74D;
  3135. }
  3136.  
  3137. /* acmsguru划分块 */
  3138. .OJBetter_acmsguru {
  3139. margin: 0 0 1em!important;
  3140. }
  3141.  
  3142. /* 代码提交表单 */
  3143. #OJBetter_SubmitForm.input-output-copier:hover {
  3144. background-color: #ffffff00;
  3145. }
  3146. #OJBetter_SubmitForm input[type="number"] {
  3147. width: 40px;
  3148. color: #009688;
  3149. appearance: none;
  3150. border-radius: 6px;
  3151. border-style: solid;
  3152. border: none;
  3153. background-color: #ffffff00;
  3154. }
  3155. #OJBetter_SubmitForm :focus-visible {
  3156. outline: none;
  3157. }
  3158. #OJBetter_SubmitForm .topDiv {
  3159. height: 50px;
  3160. display: flex;
  3161. align-items: center;
  3162. justify-content: space-between;
  3163. padding: 10px 0px;
  3164. box-sizing: border-box;
  3165. }
  3166. #OJBetter_SubmitForm .topDiv .topRightDiv {
  3167. height: 100%;
  3168. display: flex;
  3169. flex-wrap: wrap;
  3170. gap: 0px;
  3171. }
  3172. #OJBetter_SubmitForm input[type="checkbox"], #OJBetter_SubmitForm label {
  3173. margin: 0px;
  3174. font-weight: initial;
  3175. }
  3176. #OJBetter_SubmitForm #fontSizeInput {
  3177. border: none;
  3178. background-color: #ffffff00;
  3179. }
  3180.  
  3181. /* 顶部区域 */
  3182. #OJBetter_SubmitForm .topRightDiv>* {
  3183. height: 100%;
  3184. box-sizing: border-box;
  3185. }
  3186. #OJBetter_SubmitForm .topRightDiv>button{
  3187. padding: 0px 8px;
  3188. }
  3189. #OJBetter_SubmitForm .topRightDiv {
  3190. display: flex;
  3191. flex-wrap: wrap;
  3192. gap: 0px;
  3193. align-items: center;
  3194. }
  3195.  
  3196. /* LSP连接Log */
  3197. #LSPLog{
  3198. width: 500px;
  3199. height: 500px;
  3200. position: fixed;
  3201. top: 50%;
  3202. left: 50%;
  3203. padding: 10px;
  3204. transform: translate(-50%, -50%);
  3205. border: 1px solid;
  3206. z-index: 200;
  3207. background-color: #ffffff;
  3208. }
  3209. #LSPLog button{
  3210. position: fixed;
  3211. top: 10px;
  3212. right: 10px;
  3213. z-index: 200;
  3214. }
  3215. #LSPLog #LSPLogList{
  3216. width: 500px;
  3217. height: 500px;
  3218. overflow: auto;
  3219. color: #424242;
  3220. }
  3221. #LSPLog li:nth-child(odd){
  3222. background-color: #f5f5f5;
  3223. }
  3224. #LSPLog details{
  3225. padding: 2px;
  3226. }
  3227.  
  3228. /* 代码编辑器 */
  3229. #OJBetter_editor{
  3230. box-sizing: border-box;
  3231. height: 600px;
  3232. border: 1px solid #d3d3d3;
  3233. width: 100%;
  3234. resize: vertical;
  3235. display: flex;
  3236. flex-direction: column;
  3237. }
  3238. #OJBetter_editor.fullscreen{
  3239. position: fixed;
  3240. top: 0;
  3241. left: 0;
  3242. width: 100%;
  3243. height: 100vh;
  3244. z-index: 2000;
  3245. }
  3246. #OJBetter_editor.bottom{
  3247. position: fixed;
  3248. bottom: 0;
  3249. left: 0;
  3250. width: 100%;
  3251. height: 50vh;
  3252. z-index: 2000;
  3253. }
  3254. .ojb_btn.exit_button_bottom {
  3255. position: fixed;
  3256. bottom: 30px;
  3257. right: 15px;
  3258. z-index: 2000;
  3259. height: 28px;
  3260. }
  3261.  
  3262. /* monaco */
  3263. #OJBetter_monaco {
  3264. flex: 1;
  3265. min-height: 0;
  3266. width: 100%;
  3267. }
  3268. #OJBetter_monaco .highlight {
  3269. border: 1px solid #ffffff00;
  3270. background-color: #ffffff00!important
  3271. }
  3272. .monaco-hover hr {
  3273. margin: 4px -8px 4px !important;
  3274. }
  3275.  
  3276. /* 状态底栏 */
  3277. #OJBetter_statusBar{
  3278. height: 22px;
  3279. font-size: 12px;
  3280. color: #757575;
  3281. border: 1px solid #d3d3d3;
  3282. background-color: #f8f8f8;
  3283. padding: 3px;
  3284. box-sizing: border-box;
  3285. }
  3286.  
  3287. /* 提交 */
  3288. #OJBetter_submitDiv{
  3289. display: flex;
  3290. padding-top: 15px;
  3291. height: 50px;
  3292. box-sizing: border-box;
  3293. }
  3294. #OJBetter_submitDiv >* {
  3295. border-radius: 6px;
  3296. }
  3297. #OJBetter_submitDiv > button {
  3298. height: 100%;
  3299. aspect-ratio: 1 / 1;
  3300. }
  3301. #SubmitButton {
  3302. color: #fff;
  3303. background-color: #209978;
  3304. border-color: #17795E;
  3305. }
  3306. #SubmitButton:hover {
  3307. background-color: #17795e;
  3308. }
  3309. #SubmitButton.disabled {
  3310. background-color: red;
  3311. animation: shake 0.07s infinite alternate;
  3312. }
  3313. #programTypeId{
  3314. height: 100%;
  3315. padding: 5px 10px;
  3316. border-radius: 6px;
  3317. border-style: solid;
  3318. border: 1px solid #ced4da;
  3319. color: #212529;
  3320. }
  3321.  
  3322. /* 调试 */
  3323. .OJBetter_loding{
  3324. padding: 6px 0px 0px 5px;
  3325. height: 22px;
  3326. }
  3327. #CompilerArgsInput{
  3328. flex-grow: 1;
  3329. width: 100%;
  3330. height: 100%;
  3331. margin-bottom: 10px;
  3332. padding: 5px 10px;
  3333. border-radius: 6px;
  3334. box-sizing: border-box;
  3335. border: 1px solid #ccc;
  3336. box-shadow: inset 0px 1px 1px rgba(0,0,0,.075);
  3337. }
  3338. #CompilerArgsInput[disabled] {
  3339. cursor: not-allowed;
  3340. }
  3341. #CompilerSetting{
  3342. font-size: 14px;
  3343. margin-top: 10px;
  3344. display: none;
  3345. }
  3346. #CompilerSetting select, #CompilerSetting textarea{
  3347. padding: 4px 10px;
  3348. border-radius: 6px;
  3349. border-style: solid;
  3350. border: 1px solid #ced4da;
  3351. color: #212529;
  3352. }
  3353. #CompilerBox{
  3354. display: grid;
  3355. margin-top: 10px;
  3356. border: #d0d7de solid 1px;
  3357. border-radius: 6px;
  3358. }
  3359. #CompilerBox > * {
  3360. margin: 5px;
  3361. }
  3362.  
  3363. /* 自定义样例 */
  3364. #customTestBlock {
  3365. margin-top: 10px;
  3366. font-size: 14px;
  3367. color: #616161;
  3368. border: 1px solid #d3d3d3;
  3369. box-sizing: border-box;
  3370. position: relative;
  3371. }
  3372. #customTestBlock #customTests{
  3373. border-top: 1px solid #d3d3d3;
  3374. margin: 0px 0px 40px 0px;
  3375. }
  3376. #customTestBlock summary {
  3377. cursor: pointer;
  3378. padding: 10px;
  3379. }
  3380. #customTestBlock textarea {
  3381. resize: vertical;
  3382. }
  3383. .sampleDiv {
  3384. color: #727378;
  3385. background-color: #FAFAFA;
  3386. padding: 5px;
  3387. margin-bottom: 10px;
  3388. box-shadow: inset 0 0 1px #0000004d;
  3389. position: relative;
  3390. }
  3391. .dynamicTextarea {
  3392. width: 98%;
  3393. height: 120px;
  3394. margin: 10px 5px;
  3395. border: 1px solid #E0E0E0;
  3396. }
  3397. .deleteCustomTest {
  3398. cursor: pointer;
  3399. position: absolute;
  3400. top: 5px;
  3401. right: 5px;
  3402. display: flex;
  3403. fill: #9E9E9E;
  3404. padding: 2px 2px;
  3405. border-radius: 4px;
  3406. border: 1px solid #ffffff00;
  3407. background-color: #ffffff00;
  3408. align-items: center;
  3409. }
  3410. .deleteCustomTest:hover {
  3411. fill: #EF5350;
  3412. border: 1px solid #ef9a9a;
  3413. background-color: #FFEBEE;
  3414. }
  3415. #addCustomTest {
  3416. cursor: pointer;
  3417. position: absolute;
  3418. bottom: 5px;
  3419. right: 5px;
  3420. padding: 3px 10px;
  3421. color: #795548;
  3422. border: 1px solid #ccc;
  3423. border-radius: 4px;
  3424. background-color: #FAFAFA;
  3425. }
  3426. #addCustomTest:hover {
  3427. background-color: #f5f5f5;
  3428. }
  3429.  
  3430. /* 调试结果 */
  3431. #statePanel{
  3432. display: none;
  3433. padding: 5px;
  3434. margin-top: 10px;
  3435. border: 1px solid #ddd;
  3436. border-radius: 4px;
  3437. }
  3438. .test-case {
  3439. padding: 10px;
  3440. border: 1px solid #ddd;
  3441. border-radius: 4px;
  3442. }
  3443. .test-case:not(:first-child){
  3444. margin-top: 5px;
  3445. }
  3446. .test-case > * {
  3447. margin: 5px 0px;
  3448. }
  3449. .test-case > :first-child {
  3450. margin-top: 0px;
  3451. }
  3452. .test-case > :last-child {
  3453. margin-bottom: 0px;
  3454. }
  3455. .test-case-title, .test-case-status {
  3456. font-size: 16px;
  3457. display: inline;
  3458. }
  3459. .test-case-status{
  3460. margin-left: 5px;
  3461. }
  3462. .test-case-status.error{
  3463. color: red;
  3464. }
  3465. .test-case-status.success{
  3466. color: #449d44;
  3467. }
  3468. .test-case-judge {
  3469. font-size: 13px;
  3470. }
  3471.  
  3472. /* 差异对比 */
  3473. .output_diff {
  3474. color: #5d4037;
  3475. margin: 5px 0px;
  3476. display: grid;
  3477. border: 1px solid #bcaaa4;
  3478. font-size: 13px;
  3479. font-family: Consolas, "Lucida Console", "Andale Mono", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
  3480. overflow: auto;
  3481. }
  3482. .output_diff .added {
  3483. background-color: #c8f7c5;
  3484. user-select: none;
  3485. }
  3486. .output_diff .removed {
  3487. background-color: #f7c5c5;
  3488. }
  3489. .output_diff .diffLine {
  3490. display: flex;
  3491. }
  3492. .output_diff .diffLine:nth-child(odd) {
  3493. background-color: #f5f5f5;
  3494. }
  3495. .lineNo {
  3496. display: flex;
  3497. align-items: center;
  3498. justify-content: center;
  3499. width: 17px;
  3500. color: #BDBDBD;
  3501. font-size: 10px;
  3502. border-right: 1px solid;
  3503. user-select: none;
  3504. }
  3505. .lineContent {
  3506. display: grid;
  3507. width: 100%;
  3508. }
  3509. .lineContent>span {
  3510. height: 16px;
  3511. padding-left: 3px;
  3512. }
  3513. .output_no_diff {
  3514. padding: 5px;
  3515. border: 1px solid #ddd;
  3516. }
  3517. .diff_note {
  3518. font-size: 10px;
  3519. }
  3520.  
  3521. /* 网站本地化替换规则标记 */
  3522. .markingTextReplaceRule{
  3523. color: #FFF3E0;
  3524. background-color: #FF9800;
  3525. }
  3526.  
  3527. /* SelectPage样式 */
  3528. .sp_input {
  3529. padding: 4px 6px px !important;
  3530. height: 20px !important;
  3531. min-height: 20px !important;
  3532. line-height: 20px !important;
  3533. }
  3534. div.sp_clear_btn {
  3535. padding: 0px !important;
  3536. }
  3537.  
  3538. /* 移动设备 */
  3539. @media (max-device-width: 450px) {
  3540. .ojb_btn{
  3541. height: 2em;
  3542. font-size: 1.2em;
  3543. }
  3544. .ojb_btn.OJBetter_setting{
  3545. height: 2.5em;
  3546. font-size: 1em;
  3547. }
  3548. .OJBetter_setting_menu{
  3549. width: 90%;
  3550. }
  3551. .OJBetter_setting_menu label, #darkMode_span, #loaded_span, .OJBetter_setting_menu_label_text,
  3552. .OJBetter_setting_sidebar li{
  3553. font-size: 1em;
  3554. }
  3555. .translate-problem-statement{
  3556. font-size: 1.2em;
  3557. }
  3558. .OJBetter_modal{
  3559. font-size: 1.5em;
  3560. }
  3561. .OJBetter_setting_list, .translate-problem-statement{
  3562. padding: 0.5em;
  3563. }
  3564. .OJBetter_setting_menu_label_text{
  3565. height: 2.5em;
  3566. padding: 0.5em;
  3567. }
  3568. #pagBar #jump-input, #pagBar #items-per-page, .OJBetter_modal button{
  3569. height: 2.5em;
  3570. font-size: 1em;
  3571. }
  3572. .translate-problem-statement p, .translate-problem-statement ul li{
  3573. line-height: 1.5em !important;
  3574. }
  3575. .OJBetter_contextmenu_label_text{
  3576. height: 3em;
  3577. font-size: 1em;
  3578. }
  3579. }
  3580.  
  3581. /* 覆盖网站原本的样式 */
  3582. div#select-lang {
  3583. padding: 0px;
  3584. }
  3585. `);
  3586.  
  3587. /**
  3588. * 添加一些依赖库和条件加载的css样式
  3589. */
  3590. function addDependencyStyles() {
  3591. GM_addStyle(GM_getResourceText("xtermcss"));
  3592. GM_addStyle(GM_getResourceText("selectpagecss"));
  3593. GM_addStyle(GM_getResourceText("dialogpolyfillcss"));
  3594. // 自定义图标大小
  3595. GM_addStyle(`
  3596. .iconfont {
  3597. font-size: ${OJBetter.preference.iconButtonSize}px;
  3598. }
  3599. `);
  3600. }
  3601.  
  3602. /**
  3603. * 添加包含i18n内容的css样式
  3604. */
  3605. function addI18nStyles() {
  3606. GM_addStyle(`
  3607. /* 加载鼠标悬浮覆盖层css */
  3608. .overlay::before {
  3609. content: '';
  3610. position: absolute;
  3611. top: 0;
  3612. left: 0;
  3613. width: 100%;
  3614. height: 100%;
  3615. 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);
  3616. z-index: 100;
  3617. }
  3618. .overlay::after {
  3619. content: '${i18next.t('targetArea', { ns: 'common' })}';
  3620. position: absolute;
  3621. top: 50%;
  3622. left: 50%;
  3623. transform: translate(-50%, -50%);
  3624. color: #00695C;
  3625. font-size: 16px;
  3626. font-weight: bold;
  3627. z-index: 100;
  3628. }
  3629.  
  3630. .config::before {
  3631. content: "${i18next.t('common.configManageTitle', { ns: 'settings' })}";
  3632. display: block;
  3633. height: 20px;
  3634. background-color: #f0f8ff;
  3635. border: 1px solid #c5cae9;
  3636. border-bottom: 0px;
  3637. line-height: 20px;
  3638. padding: 2px 10px;
  3639. border-radius: 8px 8px 0px 0px;
  3640. box-sizing: content-box;
  3641. }
  3642. .config.missing::before {
  3643. content: "${i18next.t('common.missing.radio', { ns: 'settings' })}";
  3644. background-color: #fef0f0;
  3645. color: #f56c6c;
  3646. border: 1px solid #fab6b6;
  3647. }
  3648. `);
  3649. }
  3650.  
  3651. // ------------------------------
  3652. // 一些工具类
  3653. // ------------------------------
  3654.  
  3655.  
  3656. /**
  3657. * 自定义错误类,以区分不同的错误类型
  3658. */
  3659. class OJB_GMError extends Error {
  3660. constructor(type, message, originalError) {
  3661. super(message);
  3662. this.name = 'GMError';
  3663. this.type = type;
  3664. this.stack = originalError.stack;
  3665. Object.assign(this, originalError);
  3666. }
  3667. }
  3668.  
  3669. /**
  3670. * 文本块替换/恢复类
  3671. */
  3672. class TextBlockReplacer {
  3673. constructor() {
  3674. /** @type {string[]} 匹配项 */
  3675. this.matches = [];
  3676. /** @type {Map<string, string>} 待还原项 */
  3677. this.replacements = new Map();
  3678. /** @type {Map<string, string>} 暂时未找到的待还原项 */
  3679. this.tempReplacements = new Map();
  3680. /** @type {string} 替换符号 */
  3681. this.replaceSymbol = OJBetter.translation.replaceSymbol;
  3682. }
  3683.  
  3684. /**
  3685. * 替换文本
  3686. * @param {string} text 原文本
  3687. * @param {RegExp} regex 匹配规则
  3688. * @returns {string} 替换后的文本
  3689. */
  3690. replace(text, regex) {
  3691. this.matches = text.match(regex) || [];
  3692. try {
  3693. for (let i = 0; i < this.matches.length; i++) {
  3694. const match = this.matches[i];
  3695. const id = OJB_getRandomNumber(8);
  3696. let replacement = '';
  3697. switch (this.replaceSymbol) {
  3698. case "1":
  3699. replacement = `【${id}】`;
  3700. break;
  3701. case "2":
  3702. replacement = `{${id}}`;
  3703. break;
  3704. case "3":
  3705. replacement = `[${id}]`;
  3706. break;
  3707. default:
  3708. replacement = `【${id}】`;
  3709. break;
  3710. }
  3711. text = text.replace(match, replacement);
  3712. this.replacements.set(id, match);
  3713. }
  3714. } catch (e) { }
  3715. return text;
  3716. }
  3717.  
  3718.  
  3719. /**
  3720. * 恢复替换的文本
  3721. * @param {string} text 还原前的文本
  3722. * @returns {string} 还原后的文本
  3723. */
  3724. recover(text) {
  3725. let textCopy = text;
  3726.  
  3727. /**
  3728. * 替换回文本
  3729. * @param {string} replacement 替换的文本
  3730. * @param {string} regexPattern 匹配规则
  3731. * @returns {void}
  3732. */
  3733. const replaceText = (replacement, regexPattern) => {
  3734. const latexMatch = '(?<latex_block>\\$\\$(\\\\\\$|[^\\$])*?\\$\\$)|(?<latex_inline>\\$(\\\\\\$|[^\\$])*?\\$)|';
  3735. const regex = new RegExp(latexMatch + regexPattern, 'g');
  3736. textCopy = textCopy.replace(regex, (match, ...args) => {
  3737. // LaTeX中的不替换
  3738. const groups = args[args.length - 1]; // groups是replace方法的最后一个参数
  3739. if (groups.latex_block || groups.latex_inline) return match;
  3740. // 没有空格则加一个
  3741. const offset = args[args.length - 3]; // offset是replace方法的倒数第三个参数
  3742. let leftSpace = "", rightSpace = "";
  3743. if (!/\s/.test(textCopy[offset - 1])) leftSpace = " ";
  3744. if (!/\s/.test(textCopy[offset + match.length])) rightSpace = " ";
  3745. return leftSpace + replacement + rightSpace;
  3746. });
  3747. };
  3748.  
  3749. /**
  3750. * 尝试还原
  3751. * @param {string} replacement 替换的文本
  3752. * @param {string} id 替换的 id
  3753. * @returns {boolean} 是否替换成功
  3754. */
  3755. const tryRecover = (replacement, id) => {
  3756. // 尝试还原,如果还原成功,则从 replacements 中删除
  3757. const originalText = textCopy;
  3758. replaceText(replacement, `【\\s*${id}\\s*】|\\[\\s*${id}\\s*\\]|{\\s*${id}\\s*}`); // 替换符完整匹配(考虑了多出空格的情况)
  3759. replaceText(replacement, `【\\s*${id}(?![】\\d])|(?<![【\\d])${id}\\s*】|\\[\\s*${id}(?![\\]\\d])|(?<![\\[\\d])${id}\\s*\\]|{\\s*${id}(?![}\\d])|(?<![{\\d])${id}\\s*}`); // 替换符部分匹配
  3760.  
  3761. if (textCopy === originalText) {
  3762. // 如果文本没有变化,说明没有找到,加入到 tempReplacements
  3763. this.tempReplacements.set(id, replacement);
  3764. return false;
  3765. } else {
  3766. // 如果文本变化了,说明找到并成功替换,则删除
  3767. this.replacements.delete(id);
  3768. this.tempReplacements.delete(id);
  3769. return true;
  3770. }
  3771. }
  3772.  
  3773. // 处理 replacements 中的项
  3774. this.replacements.forEach((replacement, id) => {
  3775. tryRecover(replacement, id);
  3776. });
  3777.  
  3778. // 处理 tempReplacements 中的项
  3779. while (this.tempReplacements.size > 0) {
  3780. let found = false;
  3781. this.tempReplacements.forEach((replacement, id) => {
  3782. found = tryRecover(replacement, id) || found;
  3783. });
  3784. if (!found) break; // 如果这一轮没有找到任何项,终止循环
  3785. }
  3786.  
  3787. // 如果 tempReplacements 还有未找到的项
  3788. if (this.tempReplacements.size > 0) {
  3789. console.warn("There are still some replacements not found:", this.tempReplacements);
  3790. }
  3791.  
  3792. return textCopy;
  3793. }
  3794. }
  3795.  
  3796. // ------------------------------
  3797. // 一些工具函数
  3798. // ------------------------------
  3799.  
  3800. /**
  3801. * 格式化链接格式
  3802. * @param {string} url 链接字符串
  3803. * @returns {string} 清理后的链接字符串
  3804. */
  3805. function OJB_cleanLink(url) {
  3806. if (url === null || url === undefined) return "";
  3807.  
  3808. // 替换'http://'为'https://'
  3809. let cleanUrl = url.replace(/^http:\/\//i, 'https://');
  3810.  
  3811. // 移除末尾的斜杠
  3812. cleanUrl = cleanUrl.replace(/\/$/, '');
  3813.  
  3814. return cleanUrl;
  3815. }
  3816.  
  3817. /**
  3818. * 深度比较两个对象或数组是否完全相等。
  3819. * @param {any} a - 第一个比较对象。
  3820. * @param {any} b - 第二个比较对象。
  3821. * @returns {boolean} - 如果两个对象或数组深度相等,则返回true,否则返回false。
  3822. */
  3823. function OJB_deepEquals(a, b) {
  3824. if (a === b) return true;
  3825. if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false;
  3826. const keysA = Object.keys(a);
  3827. const keysB = Object.keys(b);
  3828. if (keysA.length !== keysB.length) return false;
  3829. for (let key of keysA) {
  3830. if (!b.hasOwnProperty(key)) return false;
  3831. if (!OJB_deepEquals(a[key], b[key])) return false;
  3832. }
  3833. return true;
  3834. }
  3835.  
  3836. /**
  3837. * 用于封装需要重试的异步函数
  3838. * @param {Function} task 需要封装的异步函数
  3839. * @param {Object} options 配置项
  3840. * @param {Number} options.maxRetries 重试次数,默认为 5
  3841. * @param {Number} options.retryInterval 重试时间间隔,默认为 0 毫秒
  3842. * @param {Function} options.errorHandler 错误处理函数,默认为抛出错误
  3843. * @param {...any} args task 函数的参数
  3844. * @returns {Promise} 返回 Promise
  3845. */
  3846. async function OJB_promiseRetryWrapper(task, {
  3847. maxRetries = 5,
  3848. retryInterval = 0,
  3849. errorHandler = (err) => { throw err }
  3850. } = {}, ...args) {
  3851. let attemptsLeft = maxRetries;
  3852. while (attemptsLeft--) {
  3853. try {
  3854. return await task(...args);
  3855. } catch (err) {
  3856. if (attemptsLeft <= 0) {
  3857. return errorHandler(err, maxRetries, attemptsLeft);
  3858. }
  3859. if (retryInterval > 0) {
  3860. await OJB_delay(retryInterval);
  3861. }
  3862. }
  3863. }
  3864. }
  3865.  
  3866. /**
  3867. * GM_xmlhttpRequest 的 Promise 封装
  3868. * @param {Object} options GM_xmlhttpRequest 的参数
  3869. * @param {Boolean} isStream 是否为流式请求
  3870. * @returns {Promise<OJB_GMError>} 返回 Promise
  3871. */
  3872. function OJB_GMRequest(options, isStream = false) {
  3873. return new Promise((resolve, reject) => {
  3874. GM_xmlhttpRequest({
  3875. ...options,
  3876. ...(isStream ? {
  3877. onloadstart: resolve
  3878. } : {
  3879. onload: resolve
  3880. }),
  3881. onerror: (error) => reject(new OJB_GMError('error', 'An error occurred during the request.', error)),
  3882. ontimeout: (error) => reject(new OJB_GMError('timeout', 'The request timed out.', error)),
  3883. onabort: (error) => reject(new OJB_GMError('abort', 'The request was aborted.', error)),
  3884. });
  3885. });
  3886. }
  3887.  
  3888. /**
  3889. * 获取cookie
  3890. * @param {string} name cookie名称
  3891. * @returns {string} cookie值
  3892. */
  3893. function OJB_getCookie(name) {
  3894. const cookies = document.cookie.split(";");
  3895. for (let i = 0; i < cookies.length; i++) {
  3896. const cookie = cookies[i].trim();
  3897. const [cookieName, cookieValue] = cookie.split("=");
  3898.  
  3899. if (cookieName === name) {
  3900. return decodeURIComponent(cookieValue);
  3901. }
  3902. }
  3903. return "";
  3904. }
  3905.  
  3906. /**
  3907. * 检查是否仍在同一浏览器会话中
  3908. * @param {string} sessionKey - 会话键名,用于标识会话
  3909. * @returns {boolean} - 如果在当前会话中之前已经设置过这个键,则返回true,否则返回false
  3910. */
  3911. function OJB_isSameBrowserSession(sessionKey) {
  3912. const fullCookieName = `OJB_Session_${sessionKey}`;
  3913. const sessionValue = OJB_getCookie(fullCookieName);
  3914. if (sessionValue === "") {
  3915. document.cookie = `${fullCookieName}=true; path=/`;
  3916. return false;
  3917. }
  3918. return true;
  3919. }
  3920.  
  3921. /**
  3922. * 随机数生成
  3923. * @param {number} numDigits 位数
  3924. * @returns {number} 一个随机数
  3925. */
  3926. function OJB_getRandomNumber(numDigits) {
  3927. let min = Math.pow(10, numDigits - 1);
  3928. let max = Math.pow(10, numDigits) - 1;
  3929. return Math.floor(Math.random() * (max - min + 1)) + min;
  3930. }
  3931.  
  3932. /**
  3933. * 随机数生成-范围内
  3934. * @param {number} min 最小值
  3935. * @param {number} max 最大值
  3936. * @returns {number} 一个随机数
  3937. */
  3938. function OJB_getRandomNumberInRange(min, max) {
  3939. return Math.floor(Math.random() * (max - min + 1)) + min;
  3940. }
  3941.  
  3942. /**
  3943. * 防抖函数
  3944. * @param {Function} callback 回调函数
  3945. * @returns {Function}
  3946. */
  3947. function OJB_debounce(callback) {
  3948. let timer;
  3949. let immediateExecuted = false;
  3950. const delay = 500;
  3951. return function () {
  3952. clearTimeout(timer);
  3953. if (!immediateExecuted) { callback.call(this); immediateExecuted = true; }
  3954. timer = setTimeout(() => { immediateExecuted = false; }, delay);
  3955. };
  3956. }
  3957.  
  3958. /**
  3959. * 为元素添加鼠标拖拽支持
  3960. * @param {JQuery<HTMLElement>} element 要添加拖拽支持的元素
  3961. * @returns {void}
  3962. */
  3963. function OJB_addDraggable(element) {
  3964. let isDragging = false;
  3965. let x, y, l, t, nl, nt;
  3966. let isSpecialMouseDown = false; // 选取某些元素时不拖动
  3967.  
  3968. element.on('mousedown', function (e) {
  3969. isSpecialMouseDown = $(e.target).is('label, p, input, textarea, span, select, details, summary');
  3970. if (isSpecialMouseDown) return;
  3971.  
  3972. isDragging = true;
  3973. x = e.clientX;
  3974. y = e.clientY;
  3975. l = element.offset().left - $(window).scrollLeft();
  3976. t = element.offset().top - $(window).scrollTop();
  3977.  
  3978. element.css({ left: l + 'px', top: t + 'px', transform: 'none' });
  3979.  
  3980. $(document).on("mousemove", drag);
  3981. $(document).on("mouseup", stopDrag);
  3982. element.css('cursor', 'all-scroll');
  3983. });
  3984.  
  3985. const drag = (e) => {
  3986. if (!isDragging) return;
  3987. // 不执行拖动操作
  3988. if ($(e.target).is('label, p, input, textarea, span') || isSpecialMouseDown && !$(e.target).is('input, textarea')) return;
  3989. e.preventDefault();
  3990.  
  3991. const nx = e.clientX;
  3992. const ny = e.clientY;
  3993. nl = nx - (x - l);
  3994. nt = ny - (y - t);
  3995. element.css({ transform: `translate(${nx - x}px, ${ny - y}px)` });
  3996. };
  3997.  
  3998. const stopDrag = () => {
  3999. isDragging = false;
  4000. isSpecialMouseDown = false;
  4001. element.css('cursor', 'default');
  4002.  
  4003. // 在停止拖拽后,设置元素的left和top,并还原transform
  4004. element.css({ left: nl + 'px', top: nt + 'px', transform: 'none' });
  4005. $(document).off("mousemove", drag);
  4006. $(document).off("mouseup", stopDrag);
  4007. };
  4008. }
  4009.  
  4010. /**
  4011. * 切换元素的折叠/展开过渡动画
  4012. * @param {HTMLElement} element
  4013. */
  4014. function OJB_toggleCollapseExpand(element) {
  4015. // 设置transitionend事件监听器的函数
  4016. const setTransitionListener = (listener) => {
  4017. const listenerName = `transitionEndListener${Date.now()}`;
  4018. window[listenerName] = listener;
  4019. element.addEventListener('transitionend', listener);
  4020. element.setAttribute('data-transition-end-listener', listenerName);
  4021. };
  4022.  
  4023. // 移除事件监听器的函数
  4024. const removeTransitionListener = () => {
  4025. const transitionEndListenerName = element.getAttribute('data-transition-end-listener');
  4026. if (transitionEndListenerName) {
  4027. element.removeEventListener('transitionend', window[transitionEndListenerName]);
  4028. element.removeAttribute('data-transition-end-listener');
  4029. }
  4030. };
  4031.  
  4032. const collapsed = element.getAttribute('data-collapsed') === 'true';
  4033. const sectionHeight = element.scrollHeight;
  4034.  
  4035. // 移除事件监听器
  4036. removeTransitionListener();
  4037.  
  4038. // 设置初始样式
  4039. element.style.overflow = 'hidden';
  4040. element.style.transition = 'height 0.3s ease-out 0s';
  4041. element.style.height = collapsed ? `0px` : `${sectionHeight}px`;
  4042. element.style.opacity = collapsed ? '' : '1';
  4043.  
  4044. // 需要立即开始动画
  4045. requestAnimationFrame(() => {
  4046. // 设置结束样式
  4047. element.style.height = collapsed ? `${sectionHeight}px` : `0px`;
  4048. });
  4049.  
  4050. const transitionEndListener = (event) => {
  4051. if (event.propertyName === 'height') {
  4052. if (collapsed) {
  4053. // 展开后的设置
  4054. element.style.height = '';
  4055. element.style.overflow = '';
  4056. } else {
  4057. // 折叠后的设置
  4058. element.style.opacity = '0';
  4059. }
  4060. removeTransitionListener();
  4061. }
  4062. };
  4063.  
  4064. setTransitionListener(transitionEndListener);
  4065.  
  4066. // 更新data-collapsed属性
  4067. element.setAttribute('data-collapsed', collapsed ? 'false' : 'true');
  4068. }
  4069.  
  4070. /**
  4071. * 获取外部JSON并转换为Object
  4072. * @param {string} url JSON Url
  4073. * @param {boolean} [nacache=true] 是否不使用缓存
  4074. * @returns {Promise<Object>} JSON Object
  4075. */
  4076. async function OJB_getExternalJSON(url, nacache = true) {
  4077. const response = await OJB_GMRequest({
  4078. method: "GET",
  4079. url: url,
  4080. nocache: nacache
  4081. });
  4082. try {
  4083. return JSON.parse(response.responseText);
  4084. } catch (e) {
  4085. throw new Error(`JSON parse error\n${e}`);
  4086. }
  4087. }
  4088.  
  4089. /**
  4090. * 创建确认对话框dialog
  4091. * @param {string} title 标题
  4092. * @param {string} content 内容
  4093. * @param {string[]} buttons 按钮 (取消 确定) 可以为null
  4094. * @param {boolean} renderMarkdown 是否使用markdown渲染文本
  4095. * @returns {Promise<boolean>} 用户点击了确定按钮返回true, 否则返回false
  4096. */
  4097. function OJB_createDialog(title, content, buttons, renderMarkdown = false) {
  4098. return new Promise(resolve => {
  4099. let contentHtml = content;
  4100.  
  4101. if (renderMarkdown) {
  4102. const md = window.markdownit();
  4103. contentHtml = md.render(content);
  4104. }
  4105.  
  4106. const dialog = OJB_safeCreateJQElement(`
  4107. <dialog class="OJBetter_modal">
  4108. <h2>${title}</h2>
  4109. <div class="content">${contentHtml}</div>
  4110. </dialog>
  4111. `);
  4112. const buttonbox = OJB_safeCreateJQElement(`<div class="buttons"></div>`);
  4113. const cancelButton = OJB_safeCreateJQElement(`<button class="cancelButton">${buttons[0]}</button>`)
  4114. .addClass("secondary");
  4115. const continueButton = OJB_safeCreateJQElement(`<button class="continueButton">${buttons[1]}</button>`);
  4116. if (buttons[0] !== null) buttonbox.append(cancelButton);
  4117. if (buttons[1] !== null) buttonbox.append(continueButton);
  4118. dialog.append(buttonbox);
  4119. $('body').append(dialog);
  4120.  
  4121. OJB_showModal(dialog);
  4122. OJB_addDraggable(dialog);
  4123.  
  4124. continueButton.click(function () {
  4125. OJB_closeAndRemoveModal(dialog);
  4126. resolve(true);
  4127. });
  4128.  
  4129. cancelButton.click(function () {
  4130. OJB_closeAndRemoveModal(dialog);
  4131. resolve(false);
  4132. });
  4133. });
  4134. }
  4135.  
  4136. /**
  4137. * 显示模态对话框并阻止页面滚动,同时考虑滚动条宽度变化和原始marginRight
  4138. * @param {JQuery<HTMLElement>} element
  4139. */
  4140. function OJB_showModal(element) {
  4141. const dialog = element.get(0);
  4142. dialogPolyfill.registerDialog(dialog);
  4143. dialog.showModal();
  4144. OJBetter.state.openDialogCount++;
  4145.  
  4146. if (OJBetter.state.openDialogCount === 1) {
  4147. const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
  4148. // 获取原始的html marginRight,考虑到可能的非数字值,比如auto
  4149. const originalMarginRight = window.getComputedStyle(document.documentElement).marginRight;
  4150. const marginRightValue = parseFloat(originalMarginRight) || 0; // 将非数字值转换为0
  4151.  
  4152. if (scrollbarWidth > 0) {
  4153. // 保存原始的marginRight,并设置新的值以补偿滚动条宽度
  4154. document.documentElement.style.setProperty('--original-margin-right', originalMarginRight);
  4155. document.documentElement.style.marginRight = `${marginRightValue + scrollbarWidth}px`;
  4156. }
  4157.  
  4158. // 保存原始的overflow样式
  4159. document.documentElement.setAttribute('data-original-overflow', document.documentElement.style.overflow);
  4160. document.documentElement.style.overflow = 'hidden';
  4161. }
  4162.  
  4163. const allowScrollIfNeeded = () => {
  4164. OJBetter.state.openDialogCount--;
  4165. if (OJBetter.state.openDialogCount === 0) {
  4166. // 恢复原始的html marginRight和overflow样式
  4167. const originalMarginRight = document.documentElement.style.getPropertyValue('--original-margin-right');
  4168. document.documentElement.style.marginRight = originalMarginRight;
  4169. document.documentElement.style.removeProperty('--original-margin-right');
  4170.  
  4171. const originalOverflow = document.documentElement.getAttribute('data-original-overflow');
  4172. document.documentElement.style.overflow = originalOverflow;
  4173. document.documentElement.removeAttribute('data-original-overflow');
  4174. }
  4175. };
  4176.  
  4177. dialog.addEventListener('close', allowScrollIfNeeded);
  4178. }
  4179.  
  4180. /**
  4181. * 关闭并移除模态对话框
  4182. * @param {JQuery<HTMLElement>} element
  4183. */
  4184. function OJB_closeAndRemoveModal(element) {
  4185. const dialog = element.get(0);
  4186. dialog.close();
  4187. dialog.remove();
  4188. }
  4189.  
  4190. /**
  4191. * 关闭并移除模态对话框
  4192. * @param {JQuery<HTMLElement>} element
  4193. */
  4194. function OJB_closeModal(element) {
  4195. const dialog = element.get(0);
  4196. dialog.close();
  4197. }
  4198.  
  4199. /**
  4200. * 清除i18next的缓存数据并刷新
  4201. */
  4202. function clearI18nextCache() {
  4203. Object.keys(localStorage)
  4204. .filter(key => key.startsWith('i18next_res_'))
  4205. .forEach(key => localStorage.removeItem(key));
  4206. window.location.reload();
  4207. }
  4208.  
  4209. /**
  4210. * 清除网站本地化数据
  4211. */
  4212. async function clearWebsiteL10nData() {
  4213. OJBetter.common.database.localizeSubsData.clear().then(() => {
  4214. console.log('localizeSubsData table has been cleared');
  4215. window.location.reload();
  4216. }).catch((error) => {
  4217. console.error('Failed to clear localizeSubsData table:', error);
  4218. });
  4219. }
  4220.  
  4221. /**
  4222. * 从Pre代码块中获取原始代码
  4223. * @param {HTMLElement} element pre代码块元素
  4224. * @returns {string|null} 代码文本
  4225. */
  4226. function OJB_getCodeFromPre(element) {
  4227. /**
  4228. * 从Ace格式化的代码块中获取原始代码
  4229. * @param {HTMLElement} element pre代码块元素
  4230. * @returns {string} 代码文本
  4231. */
  4232. const getCodeFromAcePre = function (element) {
  4233. const editor = ace.edit(element);
  4234. return editor.getValue();
  4235. }
  4236.  
  4237. /**
  4238. * 从Pretty格式化的代码块中获取原始代码-1
  4239. * 代码直接存放在 pre 元素中
  4240. * @param {HTMLElement} element pre代码块元素
  4241. * @returns {string} 代码文本
  4242. */
  4243. const getCodeFromPrettyPre = function (element) {
  4244. return Array.from(element.querySelectorAll('li')).map(function (li) {
  4245. return li.textContent;
  4246. }).join('\n');
  4247. }
  4248.  
  4249. /**
  4250. * 从Pretty格式化的代码块中获取原始代码-2
  4251. * 代码存放在子元素 code 中
  4252. * @param {HTMLElement} element pre代码块元素
  4253. * @returns {string} 代码文本
  4254. */
  4255. const getCodeFromPreChild = function (element) {
  4256. const code = element.querySelector("code.prettyprint");
  4257. if (code.classList.contains("linenums")) {
  4258. return getCodeFromPrettyPre(element);
  4259. } else {
  4260. return element.querySelector("code.prettyprint").textContent;
  4261. }
  4262. }
  4263.  
  4264. let result;
  4265. if (element.id === "submission-code") {
  4266. result = getCodeFromAcePre(element);
  4267. } else if (element.classList.contains("prettyprint")) {
  4268. result = getCodeFromPrettyPre(element);
  4269. } else if (element.querySelector("code.prettyprint")) {
  4270. result = getCodeFromPreChild(element);
  4271. } else {
  4272. result = "";
  4273. }
  4274. result = result.replace(/\u00A0/g, ''); // 过滤文本中的U+00a0字符(由&nbsp;造成的)
  4275. return result;
  4276. }
  4277.  
  4278. /**
  4279. * 判断代码的语言
  4280. * @param {string} code 代码文本
  4281. * @returns {string} 可能的语言
  4282. */
  4283. function OJB_codeLangDetect(code) {
  4284. result = hljs.highlightAuto(code);
  4285. return result.language;
  4286. }
  4287.  
  4288. /**
  4289. * 获取指定命名空间下的所有i18n翻译键值对。
  4290. *
  4291. * @param {string} namespace - 要获取键值对的i18next命名空间。
  4292. * @returns {Map<string, string>} 一个包含命名空间下所有键值对的Map对象。
  4293. */
  4294. function OJB_getAllI18nKeysForNamespace(namespace) {
  4295. const language = i18next.language; // 获取当前语言
  4296. const resources = i18next.store.data[language]; // 获取当前语言的所有资源
  4297. const nsResources = resources[namespace]; // 获取特定命名空间的资源
  4298. const resultMap = new Map();
  4299.  
  4300. if (nsResources) {
  4301. // 遍历命名空间下的所有键值对,并添加到Map中
  4302. Object.keys(nsResources).forEach(key => {
  4303. resultMap.set(key, nsResources[key]);
  4304. });
  4305. } else {
  4306. console.log(`No resources found for namespace "${namespace}"`);
  4307. }
  4308.  
  4309. return resultMap;
  4310. }
  4311.  
  4312. /**
  4313. * 更新检查
  4314. */
  4315. async function checkScriptVersion() {
  4316. try {
  4317. const versionResponse = await OJB_GMRequest({
  4318. method: "GET",
  4319. url: "https://aowuucdn.oss-accelerate.aliyuncs.com/script/versions.json",
  4320. timeout: 10 * 1e3,
  4321. nocache: true
  4322. });
  4323. const versionData = JSON.parse(versionResponse.responseText);
  4324. const { [OJBetter.state.formatName]: { dev: version_dev, release: version_release } } = versionData;
  4325. const baseUrls = {
  4326. // greasyfork: 'https://update.greatest.deepsurf.us/scripts/465777/Codeforces%20Better%21.user.js',
  4327. greasyfork: 'https://update.greatest.deepsurf.us/scripts/471106/Atcoder%20Better%21.user.js',
  4328. github: `https://github.com/beijixiaohu/OJBetter/raw/main/script/${OJBetter.about.updateChannel}/${OJBetter.state.formatName}.user.js`,
  4329. aliyunoss: `https://aowuucdn.oss-accelerate.aliyuncs.com/script/${OJBetter.about.updateChannel}/${OJBetter.state.formatName}.user.js`
  4330. };
  4331. /** @type {string} 更新跳转url */
  4332. const updateUrl = baseUrls[OJBetter.about.updateSource];
  4333. /** @type {string} 是否暂时跳过cookie */
  4334. const skipUpdate = OJB_getCookie("skipUpdate");
  4335. /** @type {string} 当前更新频道的最新版本 */
  4336. const version = OJBetter.about.updateChannel == "release" ? version_release : version_dev;
  4337. if (OJB_compareVersions(version, OJBetter.state.version) === 1 && skipUpdate !== "true") {
  4338. const updateConfirmed = await OJB_createDialog(
  4339. i18next.t('update.title', { ns: 'dialog', scriptName: OJBetter.state.name }),
  4340. i18next.t('update.content', { ns: 'dialog', oldVersion: OJBetter.state.version, newVersion: version }),
  4341. [
  4342. i18next.t('update.buttons.0', { ns: 'dialog' }),
  4343. i18next.t('update.buttons.1', { ns: 'dialog' })
  4344. ],
  4345. true
  4346. );
  4347.  
  4348. if (updateConfirmed) {
  4349. window.location.href = updateUrl;
  4350. } else {
  4351. document.cookie = "skipUpdate=true; path=/";
  4352. }
  4353. }
  4354. } catch (error) {
  4355. console.error("Update check failed: ", error);
  4356. }
  4357. }
  4358.  
  4359. /**
  4360. * 公告
  4361. */
  4362. async function showAnnounce() {
  4363. /** @type {string} 最新公告版本*/
  4364. const lastAnnounceVer = i18next.t('lastVersion', { ns: 'announce' });
  4365. if (OJB_compareVersions(OJBetter.state.version, OJBetter.state.lastAnnounceVer) === 1) {
  4366. const title = `🎉${i18next.t('announce.title', { ns: 'dialog' })} ${OJBetter.state.version}`;
  4367. /** @type {Boolean} 是否是新的公告 */
  4368. const isNewAnnounceVer = OJB_compareVersions(lastAnnounceVer, OJBetter.state.lastReadAnnounceVer) === 1;
  4369. /** @type {Boolean} 是否展示新的公告(高于当前版本的测试公告不展示) */
  4370. const showNewAnnounceVer = OJB_compareVersions(lastAnnounceVer, OJBetter.state.version) !== 1;
  4371. /**
  4372. * 获取公告的内容
  4373. * @returns {string} 公告内容
  4374. */
  4375. const getAnnounceContent = function () {
  4376. // 获取公告
  4377. const announceMap = OJB_getAllI18nKeysForNamespace('announce');
  4378. // 移除 'lastVersion' 键
  4379. announceMap.delete('lastVersion');
  4380. // 将 Map 转换为数组并根据版本号排序
  4381. const sortedVersions = [...announceMap.keys()].sort(OJB_compareVersions).reverse();
  4382. let content = "";
  4383. sortedVersions.forEach(version => {
  4384. content += `### ${version}\n\n`; // 使用版本号作为标题
  4385. content += announceMap.get(version); // 添加对应版本的公告内容
  4386. content += "\n\n";
  4387. });
  4388.  
  4389. return content;
  4390. };
  4391.  
  4392. const content = (() => {
  4393. if (isNewAnnounceVer && showNewAnnounceVer) {
  4394. return `${i18next.t('announce.prefix', { ns: 'dialog' })}\n\n${getAnnounceContent()}`;
  4395. } else {
  4396. return i18next.t('announce.divContent', { ns: 'dialog' });
  4397. }
  4398. })();
  4399. const ok = await OJB_createDialog(
  4400. title,
  4401. content,
  4402. [
  4403. null,
  4404. i18next.t('announce.buttons.0', { ns: 'dialog' })
  4405. ],
  4406. true
  4407. ); //跳过折叠块确认
  4408. if (ok) {
  4409. if (isNewAnnounceVer && showNewAnnounceVer) {
  4410. GM_setValue('lastReadAnnounceVer', lastAnnounceVer);
  4411. }
  4412. GM_setValue('lastAnnounceVer', OJBetter.state.version);
  4413. }
  4414. }
  4415. };
  4416.  
  4417. /**
  4418. * 页面顶部提示信息alert类
  4419. */
  4420. class LoadingMessage {
  4421. constructor() {
  4422. this._statusElement = null;
  4423. this._isDisplayed = false;
  4424. this.init();
  4425. }
  4426.  
  4427. /**
  4428. * 初始化加载提示信息
  4429. */
  4430. init() {
  4431. this._statusElement = this.createStatusElement();
  4432. this.insertStatusElement();
  4433. }
  4434.  
  4435. /**
  4436. * 创建提示信息元素
  4437. */
  4438. createStatusElement() {
  4439. const statusElement = $("<div></div>").addClass("alert OJBetter_alert")
  4440. .css({
  4441. "margin": "1em",
  4442. "text-align": "center",
  4443. "position": "relative"
  4444. }).hide();
  4445. return statusElement;
  4446. }
  4447.  
  4448. /**
  4449. * 插入提示信息
  4450. * @returns {void}
  4451. */
  4452. insertStatusElement() {
  4453. // (OJBetter.typeOfPage.is_mSite ? $("header") : $(".menu-box:first").next()).after(this._statusElement);
  4454. $("#main-container").prepend(this._statusElement);
  4455. }
  4456.  
  4457. /**
  4458. * 显示提示信息
  4459. */
  4460. showStatus() {
  4461. this._statusElement.show();
  4462. this._isDisplayed = true;
  4463. }
  4464.  
  4465. /**
  4466. * 隐藏提示信息
  4467. */
  4468. hideStatus() {
  4469. this._statusElement.fadeOut(500);
  4470. this._isDisplayed = false;
  4471. }
  4472.  
  4473. /**
  4474. * 移除提示信息
  4475. */
  4476. removeStatus() {
  4477. this._statusElement.remove();
  4478. this._isDisplayed = false;
  4479. }
  4480.  
  4481. /**
  4482. * 更新提示信息
  4483. * @param {string} text 提示信息文本
  4484. * @param {string} type 提示信息类型,可选值:info, success, warning, error
  4485. * @param {number} timeout 提示信息显示的持续时间(毫秒), 默认为无限长
  4486. */
  4487. updateStatus(text, type = 'info', timeout = Infinity, isMarkdown = false) {
  4488. if (isMarkdown) {
  4489. let md = window.markdownit({
  4490. html: !is_escapeHTML,
  4491. });
  4492. text = md.render(text);
  4493. }
  4494. this._statusElement.html(text).removeClass("alert-info alert-success alert-warning alert-error").addClass(`alert-${type}`);
  4495. if (!this._isDisplayed) {
  4496. this.showStatus();
  4497. }
  4498. if (timeout !== Infinity) {
  4499. setTimeout(() => {
  4500. this.hideStatus();
  4501. }, timeout);
  4502. }
  4503. }
  4504. }
  4505.  
  4506. /**
  4507. * 获取网站本地化的数据
  4508. * @param {*} localizationLanguage 本地化语言
  4509. * @returns {Promise<Object>} 本地化数据
  4510. */
  4511. async function getLocalizeWebsiteJson(localizationLanguage) {
  4512. let data = await OJBetter.common.database.localizeSubsData.get(localizationLanguage);
  4513. let url = localizationLanguage === "zh" ?
  4514. `https://aowuucdn.oss-accelerate.aliyuncs.com/resources/subs/${OJBetter.state.formatName}.json` :
  4515. `https://aowuucdn.oss-accelerate.aliyuncs.com/i18n/${localizationLanguage}/resources/subs/${OJBetter.state.formatName}.json`;
  4516. if (data) data = data.data;
  4517. if (!data) {
  4518. // 如果本地没有数据,从远端获取并保存
  4519. data = await OJB_getExternalJSON(url);
  4520. await OJBetter.common.database.localizeSubsData.put({ lang: localizationLanguage, data: data });
  4521. } else {
  4522. // 如果本地有数据,检查是否已经在当前会话中尝试过更新
  4523. const sessionKey = `ojb_updateL10nWebsiteJson_${localizationLanguage}`;
  4524. if (!OJB_isSameBrowserSession(sessionKey)) {
  4525. // 如果尚未更新,则在后台更新
  4526. (async () => {
  4527. try {
  4528. const newData = await OJB_getExternalJSON(url);
  4529. await OJBetter.common.database.localizeSubsData.put({ lang: localizationLanguage, data: newData });
  4530. console.log("Website local data has been refreshed!");
  4531. } catch (error) {
  4532. console.error('Failed to update localization data:', error);
  4533. }
  4534. })();
  4535. }
  4536. }
  4537. return data;
  4538. }
  4539.  
  4540. /**
  4541. * 网站本地化替换
  4542. * @returns
  4543. */
  4544. async function localizeWebsite() {
  4545. if (OJBetter.localization.websiteLang === "initial") return;
  4546.  
  4547. // 设置网页语言
  4548. var htmlTag = document.getElementsByTagName("html")[0];
  4549. htmlTag.setAttribute("lang", OJBetter.localization.websiteLang);
  4550.  
  4551. // 获取网站本地化的数据
  4552. var subs = await getLocalizeWebsiteJson(OJBetter.localization.websiteLang);
  4553.  
  4554. /**
  4555. * 文本节点遍历替换
  4556. * @param {JQuery} $nodes jQuery对象
  4557. * @param {Object} textReplaceRules 文本替换规则对象
  4558. * @param {string} key 应用的规则集的名字
  4559. */
  4560. const traverseTextNodes = ($nodes, textReplaceRules, key) => {
  4561. if (!$nodes) return;
  4562.  
  4563. $nodes.each((_, node) => {
  4564. if (node.nodeType === Node.TEXT_NODE) {
  4565. Object.entries(textReplaceRules).forEach(([match, replace]) => {
  4566. try {
  4567. const regex = new RegExp(match, 'g');
  4568. const beforeText = node.textContent;
  4569. node.textContent = node.textContent.replace(regex, replace);
  4570. if (node.textContent !== beforeText && OJBetter.dev.isRuleMarkingEnabled) {
  4571. $(node).after(`<span class="markingTextReplaceRule">${key}</span>`);
  4572. }
  4573. } catch (error) {
  4574. console.error(`Error processing text replacement for match: ${match}`, error);
  4575. }
  4576. });
  4577. } else {
  4578. $(node).contents().each((_, childNode) => {
  4579. traverseTextNodes($(childNode), textReplaceRules, key);
  4580. });
  4581. }
  4582. });
  4583. };
  4584.  
  4585. /**
  4586. * value替换
  4587. * @param {JQuery} $nodes jQuery对象
  4588. * @param {Object} valueReplaceRules 值替换规则对象
  4589. * @param {string} key 应用的规则集的名字
  4590. */
  4591. function traverseValueNodes($nodes, valueReplaceRules, key) {
  4592. if (!$nodes) return;
  4593.  
  4594. $nodes.each(function () {
  4595. let $node = $(this);
  4596. if ($node.is('[value]')) {
  4597. Object.keys(valueReplaceRules).forEach(match => {
  4598. const replace = valueReplaceRules[match];
  4599. const regex = new RegExp(match, 'g');
  4600. let currentValue = $node.val();
  4601. let newValue = currentValue.replace(regex, replace);
  4602. $node.val(newValue);
  4603. if (OJBetter.dev.isRuleMarkingEnabled) {
  4604. if (newValue !== currentValue) $($node).after(`<span class="markingTextReplaceRule">${key}</span>`);
  4605. }
  4606. });
  4607. } else {
  4608. $node.children().each(function () {
  4609. traverseValueNodes($(this), valueReplaceRules, key);
  4610. });
  4611. }
  4612. });
  4613. }
  4614.  
  4615. /**
  4616. * 严格的文本节点遍历替换
  4617. * 要求被替换文本严格与规则文本一致
  4618. * @param {JQuery} $nodes jQuery对象
  4619. * @param {Object} textReplaceRules 文本替换规则对象
  4620. * @param {string} key 应用的规则集的名字
  4621. */
  4622. const strictTraverseTextNodes = ($nodes, textReplaceRules, key) => {
  4623. if (!$nodes) return;
  4624.  
  4625. $nodes.each((_, node) => {
  4626. if (node.nodeType === Node.TEXT_NODE) {
  4627. const trimmedNodeText = node.textContent.trim();
  4628. for (const [match, replacement] of Object.entries(textReplaceRules)) {
  4629. if (trimmedNodeText === match) {
  4630. const beforeText = node.textContent;
  4631. node.textContent = replacement;
  4632. if (node.textContent !== beforeText && OJBetter.dev.isRuleMarkingEnabled) {
  4633. $(node).after(`<span class="markingTextReplaceRule">${key}</span>`);
  4634. }
  4635. }
  4636. }
  4637. } else {
  4638. $(node).contents().each((_, childNode) => {
  4639. strictTraverseTextNodes($(childNode), textReplaceRules, key);
  4640. });
  4641. }
  4642. });
  4643. };
  4644.  
  4645. /**
  4646. * 应用文本替换
  4647. */
  4648. let commonReplacements = subs.commonReplacements;
  4649. Object.entries(commonReplacements).forEach(([key, value]) => {
  4650. const classSelectors = Array.isArray(value.class) ? value.class : [value.class]; // 兼容,class的值可以为数组或者字符串
  4651. classSelectors.forEach(classSelector => {
  4652. if (value.isStrict) {
  4653. strictTraverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4654. } else {
  4655. traverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4656. }
  4657. });
  4658. });
  4659.  
  4660. /**
  4661. * 应用value替换
  4662. */
  4663. let InputValueReplacements = subs.InputValueReplacements;
  4664. Object.entries(InputValueReplacements).forEach(([key, value]) => {
  4665. const classSelectors = Array.isArray(value.class) ? value.class : [value.class];
  4666. classSelectors.forEach(classSelector => {
  4667. traverseValueNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4668. });
  4669. });
  4670.  
  4671. /**
  4672. * 动态添加的文本的替换
  4673. */
  4674. let dynamicReplacements = subs.dynamicReplacements;
  4675. Object.entries(dynamicReplacements).forEach(([key, value]) => {
  4676. const classSelectors = Array.isArray(value.class) ? value.class : [value.class]; // 兼容,class的值可以为数组或者字符串
  4677. classSelectors.forEach(classSelector => {
  4678. OJB_observeElement({
  4679. selector: classSelector,
  4680. callback: (node) => {
  4681. // let popupContent = node.textContent.replace(/^×/, ''); // 去除开头多余的 '×' 字符
  4682. if (value.isStrict) {
  4683. strictTraverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4684. } else {
  4685. traverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4686. }
  4687. }
  4688. });
  4689. });
  4690. });
  4691.  
  4692. // // 杂项
  4693. // (function () {
  4694. // // 选项汉化input[type="radio"]
  4695. // var translations = {
  4696. // "as individual participant": "个人",
  4697. // "as a team member": "作为一个团队成员",
  4698. // };
  4699. // $('input[type="radio"]').each(function () {
  4700. // var tag = $(this).parent().contents().filter(function () {
  4701. // return this.nodeType === Node.TEXT_NODE;
  4702. // });
  4703. // for (var i = 0; i < tag.length; i++) {
  4704. // var text = tag[i].textContent.trim();
  4705. // if (translations.hasOwnProperty(text)) {
  4706. // $(this).addClass(text);
  4707. // tag[i].replaceWith(translations[text]);
  4708. // break;
  4709. // }
  4710. // }
  4711. // });
  4712. // })();
  4713. // (function () {
  4714. // var translations = {
  4715. // "(standard input\/output)": "标准输入/输出",
  4716. // };
  4717. // $("div.notice").each(function () {
  4718. // var tag = $(this).children().eq(0).text();
  4719. // for (var property in translations) {
  4720. // if (tag.match(property)) {
  4721. // $(this).children().eq(0).text(translations[property]);
  4722. // break;
  4723. // }
  4724. // }
  4725. // });
  4726. // })();
  4727.  
  4728. // // 轻量站特殊
  4729. // if (OJBetter.typeOfPage.is_mSite) {
  4730. // traverseTextNodes($('nav'), commonReplacements['.second-level-menu']['rules']);
  4731. // }
  4732. // if (OJBetter.typeOfPage.is_mSite) {
  4733. // (function () {
  4734. // var translations = {
  4735. // "Announcements": "公告",
  4736. // "Submissions": "提交记录",
  4737. // "Contests": "比赛",
  4738. // };
  4739. // $(".caption").each(function () {
  4740. // var optionValue = $(this).text();
  4741. // if (translations[optionValue]) {
  4742. // $(this).text(translations[optionValue]);
  4743. // }
  4744. // });
  4745. // })();
  4746. // }
  4747. };
  4748.  
  4749. /**
  4750. * i18next初始化
  4751. */
  4752. async function initI18next() {
  4753. return new Promise((resolve, reject) => {
  4754. i18next
  4755. .use(i18nextChainedBackend)
  4756. .init({
  4757. lng: OJBetter.localization.scriptLang,
  4758. ns: ['common', 'settings', 'config', 'dialog', 'alert', 'translator',
  4759. 'button', 'codeEditor', 'comments', 'announce', 'logMessage'], // 命名空间列表
  4760. defaultNS: 'settings',
  4761. fallbackLng: ['zh', OJBetter.translation.targetLang],
  4762. load: 'currentOnly',
  4763. debug: false,
  4764. backend: {
  4765. backends: [
  4766. i18nextLocalStorageBackend,
  4767. i18nextHttpBackend
  4768. ],
  4769. backendOptions: [{
  4770. prefix: 'i18next_res_',
  4771. expirationTime: 7 * 24 * 60 * 60 * 1000,
  4772. defaultVersion: `v${OJBetter.state.version}`,
  4773. store: typeof window !== 'undefined' ? window.localStorage : null
  4774. }, {
  4775. /* options for secondary backend */
  4776. loadPath: (lng, ns) => {
  4777. if (lng[0] === 'zh' || lng[0] === 'zh-Hans') {
  4778. return `https://aowuucdn.oss-accelerate.aliyuncs.com/resources/locales/${OJBetter.state.formatName}/${ns}.json`;
  4779. }
  4780. return `https://aowuucdn.oss-accelerate.aliyuncs.com/i18n/${lng}/resources/locales/${OJBetter.state.formatName}/${ns}.json`;
  4781. }
  4782. }]
  4783. }
  4784. }, (err, t) => {
  4785. if (err) {
  4786. reject(err);
  4787. } else {
  4788. jqueryI18next.init(i18next, $, {
  4789. useOptionsAttr: true
  4790. });
  4791. resolve(t);
  4792. }
  4793. });
  4794. });
  4795. };
  4796.  
  4797. /**
  4798. * 抽象命令类
  4799. */
  4800. class Command {
  4801. execute() { }
  4802. undo() { }
  4803. }
  4804.  
  4805. /**
  4806. * 命令调用者
  4807. */
  4808. class CommandInvoker {
  4809. constructor() {
  4810. this.history = [];
  4811. }
  4812.  
  4813. /**
  4814. * 执行命令
  4815. * @param {Command} command 命令对象
  4816. */
  4817. execute(command) {
  4818. this.history.push(command);
  4819. command.execute();
  4820. }
  4821.  
  4822. /**
  4823. * 撤销命令
  4824. */
  4825. undo() {
  4826. const command = this.history.pop();
  4827. if (command) {
  4828. command.undo();
  4829. }
  4830. }
  4831. }
  4832.  
  4833. /**
  4834. * 接收者
  4835. */
  4836. class DOMContainer {
  4837. /**
  4838. * @param {JQueryObject} element 容器对象
  4839. */
  4840. constructor(element) {
  4841. this.containerElement = element;
  4842. }
  4843.  
  4844. /**
  4845. * 添加元素
  4846. * @param {JQueryObject} element 元素对象
  4847. * @returns {JQueryObject} 添加的元素对象
  4848. */
  4849. add(element) {
  4850. this.containerElement.append(element);
  4851. return this.containerElement.children().last();
  4852. }
  4853.  
  4854. /**
  4855. * 删除元素
  4856. * @param {JQueryObject} element 元素对象
  4857. */
  4858. remove(element) {
  4859. $(element).remove();
  4860. }
  4861. }
  4862.  
  4863. /**
  4864. * 具体命令类:添加元素
  4865. */
  4866. class AddElementCommand extends Command {
  4867. /**
  4868. * @param {DOMContainer} receiver 接收者
  4869. * @param {JQueryObject} element 元素对象
  4870. */
  4871. constructor(receiver, element) {
  4872. super();
  4873. this.receiver = receiver;
  4874. this.element = element;
  4875. this.addedElement = null;
  4876. }
  4877.  
  4878. execute() {
  4879. this.addedElement = this.receiver.add(this.element);
  4880. }
  4881.  
  4882. undo() {
  4883. if (this.addedElement) {
  4884. this.receiver.remove(this.addedElement);
  4885. }
  4886. }
  4887. }
  4888.  
  4889. /**
  4890. * 具体命令类:删除元素
  4891. */
  4892. class RemoveElementCommand extends Command {
  4893. /**
  4894. * @param {DOMContainer} receiver 接收者
  4895. * @param {JQueryObject} element 元素对象
  4896. */
  4897. constructor(receiver, element) {
  4898. super();
  4899. this.receiver = receiver;
  4900. this.element = element;
  4901. this.parent = $(element).parent();
  4902. this.nextSibling = $(element).next();
  4903. }
  4904.  
  4905. execute() {
  4906. this.receiver.remove(this.element);
  4907. }
  4908.  
  4909. undo() {
  4910. if (this.nextSibling.length > 0) {
  4911. $(this.element).insertBefore(this.nextSibling);
  4912. } else {
  4913. this.parent.append(this.element);
  4914. }
  4915. }
  4916. }
  4917.  
  4918. /**
  4919. * 验证器
  4920. */
  4921. class Validator {
  4922. /**
  4923. * 表单必填项空值校验
  4924. */
  4925. static required(structure) {
  4926. let config = {};
  4927. let allFieldsValid = true;
  4928. for (const key in structure) {
  4929. let value = key.type == 'checkbox' ?
  4930. $(key).prop("checked") : $(key).val();
  4931.  
  4932. config[structure[key].value] = value;
  4933.  
  4934. if (value || structure[key].require === false) {
  4935. $(key).removeClass('is_null');
  4936. } else {
  4937. $(key).addClass('is_null');
  4938. allFieldsValid = false;
  4939. }
  4940. }
  4941. return {
  4942. valid: allFieldsValid,
  4943. config: config
  4944. };
  4945. }
  4946.  
  4947. /**
  4948. * 表单合法性校验
  4949. */
  4950. static checkKeyValuePairs(structure, config) {
  4951. let errorKeys = [];
  4952. let allFieldsValid = true;
  4953.  
  4954. for (const key in structure) {
  4955. const { check, value } = structure[key];
  4956. const fieldValue = config[value];
  4957.  
  4958. // 如果字段没有值或校验类型不匹配,则跳过当前迭代
  4959. if (!fieldValue) continue;
  4960.  
  4961. let isValid = true;
  4962. switch (check) {
  4963. case 'keyValuePairs':
  4964. isValid = Validator.keyValuePairs(fieldValue);
  4965. break;
  4966. case 'dotSeparatedPath':
  4967. isValid = Validator.validateDotSeparatedPath(fieldValue);
  4968. break;
  4969. default:
  4970. // 没有匹配的校验类型
  4971. continue;
  4972. }
  4973.  
  4974. Validator.toggleErrorDisplay(key, isValid);
  4975. if (!isValid) {
  4976. allFieldsValid = false;
  4977. errorKeys.push(key);
  4978. }
  4979. }
  4980.  
  4981. return {
  4982. valid: allFieldsValid,
  4983. errorKeys: errorKeys
  4984. };
  4985. }
  4986.  
  4987. /**
  4988. * 切换错误信息的显示和隐藏
  4989. * @param {string} key - 字段的键
  4990. * @param {boolean} isValid - 字段值是否有效
  4991. */
  4992. static toggleErrorDisplay(key, isValid) {
  4993. const errorMessage = i18next.t('common.unValid', { ns: 'settings' });
  4994. const $errorSpan = $(key).prev('span.text-error');
  4995. if (!isValid) {
  4996. if (!$errorSpan.length) {
  4997. $(key).before(`<span class="text-error" style="color: red;">${errorMessage}</span>`);
  4998. }
  4999. } else {
  5000. $errorSpan.remove();
  5001. }
  5002. }
  5003.  
  5004. /**
  5005. * 键值对合法性校验
  5006. * @param {string} value
  5007. * @returns {boolean}
  5008. */
  5009. static keyValuePairs(value) {
  5010. const keyValuePairs = value.split('\n');
  5011. // 允许值中包含空格和冒号
  5012. const regex = /^[a-zA-Z0-9_-]+\s*:\s*.+$/;
  5013. return keyValuePairs.every(pair => regex.test(pair));
  5014. }
  5015.  
  5016.  
  5017. /**
  5018. * 点分隔符路径格式校验,允许加减运算
  5019. * @param {string} path
  5020. * @returns {boolean}
  5021. */
  5022. static validateDotSeparatedPath(path) {
  5023. // 正则表达式允许标识符之间有点号,标识符可以包含加减运算
  5024. const regex = /^([a-zA-Z0-9_-]+(\s*[\+\-]\s*[a-zA-Z0-9_-]+)*\.)*([a-zA-Z0-9_-]+(\s*[\+\-]\s*[a-zA-Z0-9_-]+)*)$/;
  5025. return regex.test(path);
  5026. }
  5027. }
  5028.  
  5029. /**
  5030. * 配置管理
  5031. */
  5032. class ConfigManager {
  5033. /**
  5034. * @param {HTMLElement} element - 挂载容器
  5035. * @param {string} prefix - 前缀
  5036. * @param {object} tempConfig - 配置内容
  5037. * @param {object} structure - 配置结构
  5038. * @param {object} configHTML - 配置编辑页面HTML
  5039. * @param {boolean} allowChoice - 是否允许选择列表项
  5040. */
  5041. constructor(element, prefix, tempConfig, structure, configHTML, allowChoice = true) {
  5042. /** @param 设置面板DIV */
  5043. this.settingMenuDiv = $('#OJBetter_setting_menu');
  5044. this.element = $(element);
  5045. this.prefix = prefix;
  5046. this.tempConfig = tempConfig;
  5047. this.structure = structure;
  5048. this.configHTML = configHTML;
  5049. this.allowChoice = allowChoice;
  5050.  
  5051. this.controlTip = null;
  5052. this.config_bar_list = null;
  5053. this.config_bar_ul = null;
  5054. this.config_add_button = null;
  5055. this.menu = null;
  5056. this.editItem = null;
  5057. this.deleteItem = null;
  5058.  
  5059. // 绑定方法
  5060. this.onAdd = this.onAdd.bind(this);
  5061. this.onEdit = this.onEdit.bind(this);
  5062. this.onDelete = this.onDelete.bind(this);
  5063. this.createListItemElement = this.createListItemElement.bind(this);
  5064.  
  5065. this.lastItemId = 0; // 列表中当前最后一个元素的id号
  5066. this.init();
  5067. }
  5068.  
  5069. init() {
  5070. this.createControlBar();
  5071. this.createContextMenu();
  5072. this.renderList();
  5073. }
  5074.  
  5075. /**
  5076. * 创建控制栏
  5077. */
  5078. createControlBar() {
  5079. this.controlTip = OJB_safeCreateJQElement(`<div id='${this.prefix}configControlTip' style='color:red;'></div>`);
  5080. this.config_bar_list = OJB_safeCreateJQElement(`<div class='config_bar_list' id='${this.prefix}config_bar_list'></div>`);
  5081. this.config_bar_ul = OJB_safeCreateJQElement(`<ul class='config_bar_ul' id='${this.prefix}config_bar_ul'></ul>`);
  5082. this.element.append(this.controlTip);
  5083. this.element.append(this.config_bar_list);
  5084. this.config_bar_list.append(this.config_bar_ul);
  5085. }
  5086.  
  5087. /**
  5088. * 创建右键菜单
  5089. */
  5090. createContextMenu() {
  5091. const menu = OJB_safeCreateJQElement(`<div id='config_bar_menu' style='display: none;'></div>`);
  5092. const editItem = OJB_safeCreateJQElement(`
  5093. <div class='config_bar_menu_item' id='config_bar_menu_edit'>
  5094. ${i18next.t('contextMenu.edit', { ns: 'translator' })}
  5095. </div>`);
  5096. const deleteItem = OJB_safeCreateJQElement(`
  5097. <div class='config_bar_menu_item' id='config_bar_menu_delete'>
  5098. ${i18next.t('contextMenu.delete', { ns: 'translator' })}
  5099. </div>`);
  5100. menu.append(editItem);
  5101. menu.append(deleteItem);
  5102. this.editItem = editItem;
  5103. this.deleteItem = deleteItem;
  5104. this.menu = menu;
  5105. this.settingMenuDiv.append(menu);
  5106. }
  5107.  
  5108. /**
  5109. * 关闭右键菜单
  5110. */
  5111. closeContextMenu() {
  5112. this.menu.css({ display: "none" });
  5113. }
  5114.  
  5115. /**
  5116. * 创建列表项
  5117. * @param {string} text - 列表项文本
  5118. * @returns {HTMLElement} - 列表项
  5119. */
  5120. createListItemElement(text) {
  5121. const id = OJB_getRandomNumber(4);
  5122. const li = $("<li></li>");
  5123. const radio = OJB_safeCreateJQElement(`<input type='radio' name='${this.prefix}config_item'></input>`)
  5124. .attr("value", text)
  5125. .attr("id", id)
  5126. .attr("prev_id", this.lastItemId)
  5127. .appendTo(li);
  5128. if (!this.allowChoice) {
  5129. radio.prop("disabled", true);
  5130. }
  5131. const label = OJB_safeCreateJQElement(`<label for='${id}' class='config_bar_ul_li_text'>${text}</label>`).appendTo(li);
  5132.  
  5133.  
  5134. this.lastItemId = id;
  5135.  
  5136. // 添加右键菜单
  5137. li.on("contextmenu", (event) => {
  5138. event.preventDefault();
  5139. this.menu.css({
  5140. display: "block",
  5141. left: event.pageX, top: event.pageY
  5142. });
  5143.  
  5144. const deleteItem = this.deleteItem;
  5145. const editItem = this.editItem;
  5146.  
  5147. // 移除旧事件
  5148. deleteItem.off("click");
  5149. editItem.off("click");
  5150.  
  5151. // 获取 li 在 ul 中的索引
  5152. const index = li.index();
  5153.  
  5154. deleteItem.on("click", () => this.onDelete(index, li));
  5155. editItem.on("click", () => this.onEdit(index, li));
  5156.  
  5157. $(document).one("click", (event) => {
  5158. if (!this.menu.get(0).contains(event.target)) {
  5159. this.closeContextMenu();
  5160. deleteItem.off("click", () => this.onDelete);
  5161. editItem.off("click", () => this.onEdit);
  5162. }
  5163. });
  5164. });
  5165.  
  5166. return li;
  5167. }
  5168.  
  5169. /**
  5170. * 渲染配置列表
  5171. */
  5172. renderList() {
  5173. const list = this.config_bar_ul;
  5174. list.empty(); // 清空
  5175. this.tempConfig.configurations.forEach((item) => {
  5176. list.append(this.createListItemElement(item['name']));
  5177. });
  5178.  
  5179. // 添加按钮
  5180. let addButton = OJB_safeCreateJQElement(`<li id='${this.prefix}add_button' class="tempConfig_add_button">
  5181. <span>+ ${i18next.t('add', { ns: 'common' })}</span>
  5182. </li>`);
  5183. this.config_add_button = addButton;
  5184. list.append(addButton);
  5185. addButton.on("click", this.onAdd);
  5186. }
  5187.  
  5188. /**
  5189. * 添加配置项
  5190. */
  5191. onAdd() {
  5192. const configMenu = this.createConfigHTML();
  5193. const structure = this.structure;
  5194.  
  5195. configMenu.on("click", "#tempConfig_save", () => {
  5196.  
  5197. // 检查必填字段
  5198. const { valid, config } = Validator.required(structure);
  5199. if (!valid) return;
  5200.  
  5201. // 检查键值对
  5202. const { valid: checkOk, errorKey } = Validator.checkKeyValuePairs(structure, config);
  5203. if (!checkOk) return;
  5204.  
  5205. this.tempConfig.configurations.push(config);
  5206.  
  5207. this.createListItemElement(config.name).insertBefore(this.config_add_button);
  5208.  
  5209. configMenu.remove();
  5210. });
  5211.  
  5212. configMenu.on("click", ".btn-close", () => {
  5213. configMenu.remove();
  5214. });
  5215. }
  5216.  
  5217. /**
  5218. * 修改配置项
  5219. * @param {number} index - 配置项索引
  5220. * @param {HTMLElement} li - 配置项
  5221. * @returns {void}
  5222. */
  5223. onEdit(index, li) {
  5224. const configMenu = this.createConfigHTML();
  5225. const structure = this.structure;
  5226.  
  5227. this.closeContextMenu();
  5228.  
  5229. // 填充表单
  5230. for (const [key, { value, type }] of Object.entries(this.structure)) {
  5231. const configValue = this.tempConfig.configurations[index][value];
  5232. const $element = $(key);
  5233. if (type === 'checkbox') {
  5234. $element.prop("checked", configValue);
  5235. } else {
  5236. $element.val(configValue);
  5237. }
  5238. }
  5239.  
  5240. configMenu.on("click", "#tempConfig_save", () => {
  5241. // 检查必填字段
  5242. const { valid, config } = Validator.required(structure);
  5243. if (!valid) return;
  5244.  
  5245. // 检查键值对
  5246. const { valid: checkOk, errorKey } = Validator.checkKeyValuePairs(structure, config);
  5247. if (!checkOk) return;
  5248.  
  5249. // 更新配置
  5250. this.tempConfig.configurations[index] = config;
  5251. li.find('label').text(config.name);
  5252.  
  5253. OJB_closeAndRemoveModal(configMenu);
  5254. });
  5255.  
  5256. configMenu.on("click", ".btn-close", () => {
  5257. OJB_closeAndRemoveModal(configMenu);
  5258. });
  5259. }
  5260.  
  5261. /**
  5262. * 删除配置项
  5263. * @param {number} index - 配置项索引
  5264. * @param {HTMLElement} li - 配置项
  5265. * @returns {void}
  5266. */
  5267. onDelete(index, li) {
  5268. this.closeContextMenu();
  5269. this.tempConfig.configurations.splice(index, 1);
  5270. li.remove();
  5271. }
  5272.  
  5273. /**
  5274. * 创建配置编辑页面
  5275. * @returns {JQuery<HTMLElement>} 返回配置编辑页面
  5276. */
  5277. createConfigHTML() {
  5278. const configMenu = OJB_safeCreateJQElement(this.configHTML);
  5279. this.settingMenuDiv.after(configMenu);
  5280. OJB_showModal(configMenu);
  5281. OJB_addDraggable(configMenu);
  5282. elementLocalize(configMenu);
  5283. return configMenu;
  5284. }
  5285.  
  5286. /**
  5287. * 获取配置内容
  5288. * @returns {object} - 配置内容
  5289. */
  5290. getTempConfig() {
  5291. return this.tempConfig;
  5292. }
  5293.  
  5294. /**
  5295. * 注册列表项选中改变监听
  5296. */
  5297. registerChoiceChange() {
  5298. this.config_bar_ul.on("change", "input[type='radio']", (event) => {
  5299. const value = event.target.value;
  5300. this.tempConfig.choice = value;
  5301. });
  5302. }
  5303. }
  5304.  
  5305. const OJBetter_setting_sidebar_HTML = `
  5306. <div class="OJBetter_setting_sidebar">
  5307. <ul>
  5308. <li><a href="#basic-settings" id="sidebar-basic-settings" class="active" data-i18n="settings:sidebar.basic"></a></li>
  5309. <li><a href="#l10n_settings" id="sidebar-l10n_settings" data-i18n="settings:sidebar.localization"></a></li>
  5310. <li><a href="#translation-settings" id="sidebar-translation-settings" data-i18n="settings:sidebar.translation"></a></li>
  5311. <li><a href="#clist_rating-settings" id="sidebar-clist_rating-settings" data-i18n="settings:sidebar.clist"></a></li>
  5312. <li><a href="#code_editor-settings" id="sidebar-code_editor-settings" data-i18n="settings:sidebar.monaco"></a></li>
  5313. <li><a href="#preference-settings" id="sidebar-preference-settings" data-i18n="settings:sidebar.preference"></a></li>
  5314. <li><a href="#dev-settings" id="sidebar-dev-settings" data-i18n="settings:sidebar.dev"></a></li>
  5315. <li><a href="#about-settings" id="sidebar-about-settings" data-i18n="settings:sidebar.about"></a></li>
  5316. </ul>
  5317. </div>
  5318. `;
  5319.  
  5320. const basic_settings_HTML = `
  5321. <div id="basic-settings" class="settings-page active">
  5322. <h3 data-i18n="settings:basic.title"></h3>
  5323. <hr>
  5324. <div class='OJBetter_setting_list' style="padding: 0px 10px;">
  5325. <span id="darkMode_span" data-i18n="settings:basic.darkMode.name"></span>
  5326. <div class="dark-mode-selection">
  5327. <label>
  5328. <input class="radio-input" type="radio" name="darkMode" value="dark" />
  5329. <span class="OJBetter_setting_menu_label_text"
  5330. data-i18n="settings:basic.darkMode.options.dark"></span>
  5331. <span class="radio-icon"> </span>
  5332. </label>
  5333. <label>
  5334. <input checked="" class="radio-input" type="radio" name="darkMode" value="light" />
  5335. <span class="OJBetter_setting_menu_label_text"
  5336. data-i18n="settings:basic.darkMode.options.light"></span>
  5337. <span class="radio-icon"> </span>
  5338. </label>
  5339. <label>
  5340. <input class="radio-input" type="radio" name="darkMode" value="follow" />
  5341. <span class="OJBetter_setting_menu_label_text"
  5342. data-i18n="settings:basic.darkMode.options.system"></span>
  5343. <span class="radio-icon"> </span>
  5344. </label>
  5345. </div>
  5346. </div>
  5347. <div class='OJBetter_setting_list' style="display:none;">
  5348. <label for="expandFoldingblocks" data-i18n="settings:basic.expandBlocks"></label>
  5349. <input type="checkbox" id="expandFoldingblocks" name="expandFoldingblocks">
  5350. </div>
  5351. <div class='OJBetter_setting_list' style="display:none;">
  5352. <label for="renderPerfOpt" data-i18n="settings:basic.renderOptimization.label"></label>
  5353. <div class="help_tip">
  5354. ${helpCircleHTML}
  5355. <div class="tip_text" data-i18n="[html]settings:basic.renderOptimization.helpText"></div>
  5356. </div>
  5357. <input type="checkbox" id="renderPerfOpt" name="renderPerfOpt">
  5358. </div>
  5359. <div class='OJBetter_setting_list' style="display:none;">
  5360. <label for="selectElementPerfOpt" data-i18n="settings:basic.selectElementOptimization.label"></label>
  5361. <div class="help_tip">
  5362. ${helpCircleHTML}
  5363. <div class="tip_text" data-i18n="[html]settings:basic.selectElementOptimization.helpText"></div>
  5364. </div>
  5365. <input type="checkbox" id="selectElementPerfOpt" name="selectElementPerfOpt">
  5366. </div>
  5367. <div class='OJBetter_setting_list' style="display:none;">
  5368. <label for="commentPaging" data-i18n="settings:basic.paging.label"></label>
  5369. <div class="help_tip">
  5370. ${helpCircleHTML}
  5371. <div class="tip_text" data-i18n="[html]settings:basic.paging.helpText"></div>
  5372. </div>
  5373. <input type="checkbox" id="commentPaging" name="commentPaging">
  5374. </div>
  5375. <div class='OJBetter_setting_list'>
  5376. <label for="showJumpToLuogu" data-i18n="settings:basic.luoguJump.label"></label>
  5377. <div class="help_tip">
  5378. ${helpCircleHTML}
  5379. <div class="tip_text" data-i18n="[html]settings:basic.luoguJump.helpText"></div>
  5380. </div>
  5381. <input type="checkbox" id="showJumpToLuogu" name="showJumpToLuogu">
  5382. </div>
  5383. <div class='OJBetter_setting_list'>
  5384. <label for="showCF2vjudge" data-i18n="settings:basic.vjudgeJump.label"></label>
  5385. <div class="help_tip">
  5386. ${helpCircleHTML}
  5387. <div class="tip_text" data-i18n="[html]settings:basic.vjudgeJump.helpText"></div>
  5388. </div>
  5389. <input type="checkbox" id="showCF2vjudge" name="showCF2vjudge">
  5390. </div>
  5391. <div class='OJBetter_setting_list' style="display:none;">
  5392. <label for="standingsRecolor" data-i18n="settings:basic.recolor.label"></label>
  5393. <div class="help_tip">
  5394. ${helpCircleHTML}
  5395. <div class="tip_text" data-i18n="[html]settings:basic.recolor.helpText"></div>
  5396. </div>
  5397. <input type="checkbox" id="standingsRecolor" name="standingsRecolor">
  5398. </div>
  5399. </div>
  5400. `;
  5401.  
  5402. const l10n_settings_HTML = `
  5403. <div id="l10n_settings" class="settings-page">
  5404. <h3 data-i18n="settings:localization.title"></h3>
  5405. <hr>
  5406. <div class='OJBetter_setting_list'>
  5407. <label for="scriptL10nLanguage" style="display: flex;" data-i18n="settings:localization.scriptLanguageLabel"></label>
  5408. <select id="scriptL10nLanguage" name="scriptL10nLanguage">
  5409. <option value="zh">简体中文</option>
  5410. <option value="zh-Hant">繁體中文</option>
  5411. <option value="en">English</option>
  5412. <option value="de">Deutsch</option>
  5413. <option value="fr">Français</option>
  5414. <option value="ko">한국어</option>
  5415. <option value="pt">Português</option>
  5416. <option value="ja">日本語</option>
  5417. <option value="es">Español</option>
  5418. <option value="it">Italiano</option>
  5419. <option value="hi">हिन्दी</option>
  5420. </select>
  5421. </div>
  5422. <div class='OJBetter_setting_list'>
  5423. <label for="localizationLanguage" style="display: flex;" data-i18n="settings:localization.websiteLanguageLabel"></label>
  5424. <select id="localizationLanguage" name="localizationLanguage">
  5425. <option value="initial">——</option>
  5426. <option value="zh">简体中文</option>
  5427. <option value="zh-Hant">繁體中文</option>
  5428. <option value="de">Deutsch</option>
  5429. <option value="fr">Français</option>
  5430. <option value="ko">한국어</option>
  5431. <option value="pt">Português</option>
  5432. <option value="ja">日本語</option>
  5433. <option value="es">Español</option>
  5434. <option value="it">Italiano</option>
  5435. <option value="hi">हिन्दी</option>
  5436. </select>
  5437. </div>
  5438. <div class='OJBetter_setting_list alert_tip'>
  5439. <div data-i18n="[html]settings:localization.notice.1"></div>
  5440. </div>
  5441. <div class='OJBetter_setting_list alert_tip'>
  5442. <div data-i18n="[html]settings:localization.notice.2"></div>
  5443. </div>
  5444. </div>
  5445. `;
  5446.  
  5447. const translation_settings_HTML = `
  5448. <div id="translation-settings" class="settings-page">
  5449. <h3 data-i18n="settings:translation.title"></h3>
  5450. <hr>
  5451. <h4 data-i18n="settings:translation.options.title"></h4>
  5452. <div class='OJBetter_setting_list'>
  5453. <label for="transTargetLang" style="display: flex;" data-i18n="settings:translation.preference.target.title"></label>
  5454. <div class="help_tip">
  5455. ${helpCircleHTML}
  5456. <div class="tip_text" data-i18n="[html]settings:translation.preference.target.helpText"></div>
  5457. </div>
  5458. <select id="transTargetLang" name="transTargetLang">
  5459. <option value="zh">简体中文</option>
  5460. <option value="zh-Hant">繁體中文</option>
  5461. <option value="de">Deutsch</option>
  5462. <option value="fr">Français</option>
  5463. <option value="ko">한국어</option>
  5464. <option value="pt">Português</option>
  5465. <option value="ja">日本語</option>
  5466. <option value="es">Español</option>
  5467. <option value="it">Italiano</option>
  5468. <option value="hi">हिन्दी</option>
  5469. </select>
  5470. </div>
  5471. <div id="translationServices">
  5472. <label>
  5473. <input type='radio' name='translation' value='deepl'>
  5474. <span class='OJBetter_setting_menu_label_text'
  5475. data-i18n="settings:translation.options.services.deepl"></span>
  5476. </label>
  5477. <label>
  5478. <input type='radio' name='translation' value='iflyrec'>
  5479. <span class='OJBetter_setting_menu_label_text'
  5480. data-i18n="settings:translation.options.services.iflyrec"></span>
  5481. </label>
  5482. <label>
  5483. <input type='radio' name='translation' value='youdao'>
  5484. <span class='OJBetter_setting_menu_label_text'
  5485. data-i18n="settings:translation.options.services.youdao"></span>
  5486. </label>
  5487. <label>
  5488. <input type='radio' name='translation' value='google'>
  5489. <span class='OJBetter_setting_menu_label_text'
  5490. data-i18n="settings:translation.options.services.google"></span>
  5491. </label>
  5492. <label>
  5493. <input type='radio' name='translation' value='caiyun'>
  5494. <span class='OJBetter_setting_menu_label_text'
  5495. data-i18n="settings:translation.options.services.caiyun"></span>
  5496. </label>
  5497. <label>
  5498. <input type='radio' name='translation' value='openai'>
  5499. <span class='OJBetter_setting_menu_label_text'
  5500. data-i18n="settings:translation.options.services.openai.name">
  5501. <div class="help_tip">
  5502. ${helpCircleHTML}
  5503. <div class="tip_text"
  5504. data-i18n="[html]settings:translation.options.services.openai.helpText"></div>
  5505. </div>
  5506. </span>
  5507. </label>
  5508. </div>
  5509. <hr>
  5510. <h4>DeepL</h4>
  5511. <div class='OJBetter_setting_list'>
  5512. <label for="deepl_type" style="display: flex;" data-i18n="settings:translation.deepl.mode.title"></label>
  5513. <div class="help_tip">
  5514. ${helpCircleHTML}
  5515. <div class="tip_text" data-i18n="[html]settings:translation.deepl.mode.helpText"></div>
  5516. </div>
  5517. <select id="deepl_type" name="deepl_type">
  5518. <option value="free" data-i18n="settings:translation.deepl.mode.select.free"></option>
  5519. <option value="api" data-i18n="settings:translation.deepl.mode.select.api"></option>
  5520. </select>
  5521. </div>
  5522. <div id="deepl_config" class="config"></div>
  5523. <div class='OJBetter_setting_list'>
  5524. <label for="enableEmphasisProtection" data-i18n="settings:translation.deepl.enableEmphasisProtection.title"></label>
  5525. <div class="help_tip" style="margin-right: initial;">
  5526. ${helpCircleHTML}
  5527. <div class="tip_text" data-i18n="[html]settings:translation.deepl.enableEmphasisProtection.helpText"></div>
  5528. </div>
  5529. <div class="badge">Official API Only</div>
  5530. <input type="checkbox" id="enableEmphasisProtection" name="enableEmphasisProtection">
  5531. </div>
  5532. <div class='OJBetter_setting_list'>
  5533. <label for="enableLinkProtection" data-i18n="settings:translation.deepl.enableLinkProtection.title"></label>
  5534. <div class="help_tip" style="margin-right: initial;">
  5535. ${helpCircleHTML}
  5536. <div class="tip_text" data-i18n="[html]settings:translation.deepl.enableLinkProtection.helpText"></div>
  5537. </div>
  5538. <div class="badge">Official API Only</div>
  5539. <input type="checkbox" id="enableLinkProtection" name="enableLinkProtection">
  5540. </div>
  5541. <hr>
  5542. <h4>ChatGPT</h4>
  5543. <div id="chatgpt_config" class="config"></div>
  5544. <div class='OJBetter_setting_list'>
  5545. <label for="openai_isStream" data-i18n="settings:translation.chatgpt.isStream.name"></label>
  5546. <div class="help_tip">
  5547. ${helpCircleHTML}
  5548. <div class="tip_text" data-i18n="[html]settings:translation.chatgpt.isStream.helpText"></div>
  5549. </div>
  5550. <input type="checkbox" id="openai_isStream" name="openai_isStream">
  5551. </div>
  5552. <div class='OJBetter_setting_list'>
  5553. <label for="openai_asSystemPrompt" data-i18n="settings:translation.chatgpt.asSystemPrompt.name"></label>
  5554. <div class="help_tip">
  5555. ${helpCircleHTML}
  5556. <div class="tip_text" data-i18n="[html]settings:translation.chatgpt.asSystemPrompt.helpText"></div>
  5557. </div>
  5558. <input type="checkbox" id="openai_asSystemPrompt" name="openai_asSystemPrompt">
  5559. </div>
  5560. <div class="OJBetter_setting_list">
  5561. <label for='openai_customPrompt'>
  5562. <div style="display: flex;align-items: center;">
  5563. <span class="input_label" data-i18n="settings:translation.chatgpt.customPrompt.name"></span>
  5564. <div class="help_tip">
  5565. ${helpCircleHTML}
  5566. <div class="tip_text" data-i18n="[html]settings:translation.chatgpt.customPrompt.helpText"></div>
  5567. </div>
  5568. </div>
  5569. </label>
  5570. <textarea id="openai_customPrompt" placeholder='' require = false data-i18n="[placeholder]settings:translation.chatgpt.customPrompt.placeholder"></textarea>
  5571. </div>
  5572. <hr>
  5573. <h4 data-i18n="settings:translation.preference.title"></h4>
  5574. <div class='OJBetter_setting_list'>
  5575. <label for="comment_translation_choice" style="display: flex;"
  5576. data-i18n="settings:translation.preference.comment_translation_choice.title">
  5577. </label>
  5578. <select id="comment_translation_choice" name="comment_translation_choice">
  5579. <option value="0" data-i18n="settings:translation.preference.comment_translation_choice.services.follow"></option>
  5580. <option value="deepl" data-i18n="settings:translation.preference.comment_translation_choice.services.deepl"></option>
  5581. <option value="iflyrec" data-i18n="settings:translation.preference.comment_translation_choice.services.iflyrec"></option>
  5582. <option value="youdao" data-i18n="settings:translation.preference.comment_translation_choice.services.youdao"></option>
  5583. <option value="google" data-i18n="settings:translation.preference.comment_translation_choice.services.google"></option>
  5584. <option value="caiyun" data-i18n="settings:translation.preference.comment_translation_choice.services.caiyun"></option>
  5585. <option value="openai" data-i18n="settings:translation.preference.comment_translation_choice.services.openai"></option>
  5586. </select>
  5587. </div>
  5588. <hr>
  5589.  
  5590. <h4 data-i18n="settings:translation.autoTranslation.title"></h4>
  5591. <div class='OJBetter_setting_list'>
  5592. <label for="autoTranslation" data-i18n="settings:translation.autoTranslation.enable"></label>
  5593. <div class="help_tip">
  5594. ${helpCircleHTML}
  5595. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.helpText"></div>
  5596. </div>
  5597. <input type="checkbox" id="autoTranslation" name="autoTranslation">
  5598. </div>
  5599. <div class='OJBetter_setting_list'>
  5600. <label for='shortTextLength'>
  5601. <div style="display: flex;align-items: center;"
  5602. data-i18n="settings:translation.autoTranslation.shortTextLength.name"></div>
  5603. </label>
  5604. <div class="help_tip">
  5605. ${helpCircleHTML}
  5606. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.shortTextLength.helpText">
  5607. </div>
  5608. </div>
  5609. <input type='number' id='shortTextLength' class='no_default' require=true data-i18n="[placeholder]settings:translation.autoTranslation.shortTextLength.placeholder">
  5610. <span data-i18n="settings:translation.autoTranslation.shortTextLength.end"></span>
  5611. </div>
  5612. <div style="display:none;">
  5613. <div class='OJBetter_setting_list'>
  5614. <label for="allowMixTrans" data-i18n="settings:translation.autoTranslation.allowMixTrans.name"></label>
  5615. <div class="help_tip">
  5616. ${helpCircleHTML}
  5617. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.allowMixTrans.helpText">
  5618. </div>
  5619. </div>
  5620. <input type="checkbox" id="allowMixTrans" name="allowMixTrans">
  5621. <div class='OJBetter_checkboxs'>
  5622. <input type="checkbox" id="deepl" name="mixedTranslation" value="deepl">
  5623. <label for="deepl" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.deepl"></label>
  5624. <input type="checkbox" id="iflyrec" name="mixedTranslation" value="iflyrec">
  5625. <label for="iflyrec" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.iflyrec"></label>
  5626. <input type="checkbox" id="youdao" name="mixedTranslation" value="youdao">
  5627. <label for="youdao" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.youdao"></label>
  5628. <input type="checkbox" id="google" name="mixedTranslation" value="google">
  5629. <label for="google" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.google">Google</label>
  5630. <input type="checkbox" id="caiyun" name="mixedTranslation" value="caiyun">
  5631. <label for="caiyun" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.caiyun"></label>
  5632. </div>
  5633. </div>
  5634. </div>
  5635. <hr>
  5636.  
  5637. <h4 data-i18n="settings:translation.advanced.name"></h4>
  5638. <div class='OJBetter_setting_list'>
  5639. <label for="comment_translation_mode" style="display: flex;" data-i18n="settings:translation.advanced.mode.name"></label>
  5640. <div class="help_tip">
  5641. ${helpCircleHTML}
  5642. <div class="tip_text" data-i18n="[html]settings:translation.advanced.mode.helpText"></div>
  5643. </div>
  5644. <select id="comment_translation_mode" name="comment_translation_mode">
  5645. <option value="0" data-i18n="settings:translation.advanced.mode.options.0"></option>
  5646. <option value="1" data-i18n="settings:translation.advanced.mode.options.1"></option>
  5647. <option value="2" data-i18n="settings:translation.advanced.mode.options.2"></option>
  5648. </select>
  5649. </div>
  5650. <div class='OJBetter_setting_list'>
  5651. <label for="memoryTranslateHistory" data-i18n="settings:translation.advanced.memory.name"></label>
  5652. <div class="help_tip">
  5653. ${helpCircleHTML}
  5654. <div class="tip_text" data-i18n="[html]settings:translation.advanced.memory.helpText"></div>
  5655. </div>
  5656. <input type="checkbox" id="memoryTranslateHistory" name="memoryTranslateHistory">
  5657. </div>
  5658. <div class='OJBetter_setting_list'>
  5659. <label for="translation_retransAction" style="display: flex;" data-i18n="settings:translation.advanced.retrans.name"></label>
  5660. <div class="help_tip">
  5661. ${helpCircleHTML}
  5662. <div class="tip_text" data-i18n="[html]settings:translation.advanced.retrans.helpText"></div>
  5663. </div>
  5664. <select id="translation_retransAction" name="translation_retransAction">
  5665. <option value=0 data-i18n="settings:translation.advanced.retrans.options.0"></option>
  5666. <option value=1 data-i18n="settings:translation.advanced.retrans.options.1"></option>
  5667. </select>
  5668. </div>
  5669. <div class='OJBetter_setting_list'>
  5670. <label for='transWaitTime'>
  5671. <div style="display: flex;align-items: center;" data-i18n="settings:translation.advanced.transWaitTime.name"></div>
  5672. </label>
  5673. <div class="help_tip">
  5674. ${helpCircleHTML}
  5675. <div class="tip_text" data-i18n="[html]settings:translation.advanced.transWaitTime.helpText"></div>
  5676. </div>
  5677. <input type='number' id='transWaitTime' class='no_default' require=true data-i18n="[placeholder]settings:translation.advanced.transWaitTime.placeholder">
  5678. <span data-i18n="settings:translation.advanced.transWaitTime.end"></span>
  5679. </div>
  5680. <div class='OJBetter_setting_list'>
  5681. <label for="translation_replaceSymbol" style="display: flex;" data-i18n="settings:translation.advanced.replaceSymbol.name"></label>
  5682. <div class="help_tip">
  5683. ${helpCircleHTML}
  5684. <div class="tip_text" data-i18n="[html]settings:translation.advanced.replaceSymbol.helpText"></div>
  5685. </div>
  5686. <select id="translation_replaceSymbol" name="translation_replaceSymbol">
  5687. <option value=2 data-i18n="settings:translation.advanced.replaceSymbol.options.2"></option>
  5688. <option value=1 data-i18n="settings:translation.advanced.replaceSymbol.options.1"></option>
  5689. <option value=3 data-i18n="settings:translation.advanced.replaceSymbol.options.3"></option>
  5690. </select>
  5691. </div>
  5692. <div class='OJBetter_setting_list'>
  5693. <label for="filterTextWithoutEmphasis" data-i18n="settings:translation.advanced.filterTextWithoutEmphasis.name"></label>
  5694. <div class="help_tip">
  5695. ${helpCircleHTML}
  5696. <div class="tip_text" data-i18n="[html]settings:translation.advanced.filterTextWithoutEmphasis.helpText"></div>
  5697. </div>
  5698. <input type="checkbox" id="filterTextWithoutEmphasis" name="filterTextWithoutEmphasis">
  5699. </div>
  5700. </div>
  5701. `;
  5702.  
  5703. const clist_rating_settings_HTML = `
  5704. <div id="clist_rating-settings" class="settings-page">
  5705. <h3 data-i18n="settings:clist.title"></h3>
  5706. <hr>
  5707. <h4 data-i18n="settings:clist.basics.name"></h4>
  5708. <div class='OJBetter_setting_list alert_tip'>
  5709. <div>
  5710. <p data-i18n="[html]settings:clist.basics.notice"></p>
  5711. </div>
  5712. </div>
  5713. <div class='OJBetter_setting_list'>
  5714. <label for='clist_Authorization'>
  5715. <div style="display: flex;align-items: center;">
  5716. <span class="input_label" data-i18n="settings:clist.basics.key.title"></span>
  5717. </div>
  5718. </label>
  5719. <div class="help_tip">
  5720. ${helpCircleHTML}
  5721. <div class="tip_text" data-i18n="[html]settings:clist.basics.key.helpText"></div>
  5722. </div>
  5723. <input type='text' id='clist_Authorization' class='no_default' required="true"
  5724. data-i18n="[placeholder]settings:clist.basics.key.keyPlaceholder">
  5725. </div>
  5726. <hr>
  5727. <h4 data-i18n="settings:clist.displayRating.title"></h4>
  5728. <div class='OJBetter_setting_list'>
  5729. <label for="showClistRating_contest"><span data-i18n="settings:clist.displayRating.contest.name"></span></label>
  5730. <input type="checkbox" id="showClistRating_contest" name="showClistRating_contest">
  5731. </div>
  5732. <div class='OJBetter_setting_list'>
  5733. <label for="showClistRating_problem"><span data-i18n="settings:clist.displayRating.problem.name"></span></label>
  5734. <input type="checkbox" id="showClistRating_problem" name="showClistRating_problem">
  5735. </div>
  5736. <div class='OJBetter_setting_list' style='display:none;'>
  5737. <label for="showClistRating_problemset"><span data-i18n="settings:clist.displayRating.problemset.name"></span></label>
  5738. <input type="checkbox" id="showClistRating_problemset" name="showClistRating_problemset">
  5739. </div>
  5740. <hr>
  5741. <div class='OJBetter_setting_list'>
  5742. <label for="RatingHidden"><span data-i18n="settings:clist.spoilerProtection.title"></span></label>
  5743. <div class="help_tip">
  5744. ${helpCircleHTML}
  5745. <div class="tip_text" data-i18n="[html]settings:clist.spoilerProtection.helpText"></div>
  5746. </div>
  5747. <input type="checkbox" id="RatingHidden" name="RatingHidden">
  5748. </div>
  5749. </div>
  5750. `;
  5751.  
  5752. const code_editor_settings_HTML = `
  5753. <div id="code_editor-settings" class="settings-page">
  5754. <h3 data-i18n="settings:codeEditor.title"></h3>
  5755. <hr>
  5756. <h4 data-i18n="settings:codeEditor.basics"></h4>
  5757. <div class='OJBetter_setting_list'>
  5758. <label for="problemPageCodeEditor"><span
  5759. data-i18n="settings:codeEditor.problemPageCodeEditor.label"></span></label>
  5760. <div class="help_tip">
  5761. ${helpCircleHTML}
  5762. <div class="tip_text" data-i18n="settings:codeEditor.problemPageCodeEditor.helpText"></div>
  5763. </div>
  5764. <input type="checkbox" id="problemPageCodeEditor" name="problemPageCodeEditor">
  5765. </div>
  5766. <div class='OJBetter_setting_list'>
  5767. <label for="beautifyPreBlocks"><span
  5768. data-i18n="settings:codeEditor.beautifyPreBlocks.label"></span></label>
  5769. <div class="help_tip">
  5770. ${helpCircleHTML}
  5771. <div class="tip_text" data-i18n="settings:codeEditor.beautifyPreBlocks.helpText"></div>
  5772. </div>
  5773. <input type="checkbox" id="beautifyPreBlocks" name="beautifyPreBlocks">
  5774. </div>
  5775. <hr>
  5776. <h4 data-i18n="settings:codeEditor.preferences.title"></h4>
  5777. <div class='OJBetter_setting_list'>
  5778. <label for="isCodeSubmitConfirm"><span
  5779. data-i18n="settings:codeEditor.preferences.isCodeSubmitConfirm.label"></span></label>
  5780. <div class="help_tip">
  5781. ${helpCircleHTML}
  5782. <div class="tip_text" data-i18n="settings:codeEditor.preferences.isCodeSubmitConfirm.helpText"></div>
  5783. </div>
  5784. <input type="checkbox" id="isCodeSubmitConfirm" name="isCodeSubmitConfirm">
  5785. </div>
  5786. <div class='OJBetter_setting_list'>
  5787. <label for="autoSubmitAfterPass"><span
  5788. data-i18n="settings:codeEditor.preferences.autoSubmitAfterPass.label"></span></label>
  5789. <div class="help_tip">
  5790. ${helpCircleHTML}
  5791. <div class="tip_text" data-i18n="settings:codeEditor.preferences.autoSubmitAfterPass.helpText"></div>
  5792. </div>
  5793. <input type="checkbox" id="autoSubmitAfterPass" name="autoSubmitAfterPass">
  5794. </div>
  5795. <div class='OJBetter_setting_list'>
  5796. <label for="alwaysConsumeMouseWheel"><span
  5797. data-i18n="settings:codeEditor.preferences.alwaysConsumeMouseWheel.label"></span></label>
  5798. <div class="help_tip">
  5799. ${helpCircleHTML}
  5800. <div class="tip_text" data-i18n="settings:codeEditor.preferences.alwaysConsumeMouseWheel.helpText"></div>
  5801. </div>
  5802. <input type="checkbox" id="alwaysConsumeMouseWheel" name="alwaysConsumeMouseWheel">
  5803. </div>
  5804. <div class='OJBetter_setting_list'>
  5805. <label for="autoMemoryCode"><span
  5806. data-i18n="settings:codeEditor.preferences.autoMemoryCode.label"></span></label>
  5807. <div class="help_tip">
  5808. ${helpCircleHTML}
  5809. <div class="tip_text" data-i18n="settings:codeEditor.preferences.autoMemoryCode.helpText"></div>
  5810. </div>
  5811. <input type="checkbox" id="autoMemoryCode" name="autoMemoryCode">
  5812. </div>
  5813. <div class='OJBetter_setting_list'>
  5814. <label for="submitButtonPosition"><span
  5815. data-i18n="settings:codeEditor.preferences.submitButtonPosition.label"></span></label>
  5816. <div class="help_tip">
  5817. ${helpCircleHTML}
  5818. <div class="tip_text" data-i18n="settings:codeEditor.preferences.submitButtonPosition.helpText"></div>
  5819. </div>
  5820. <select id="submitButtonPosition" name="submitButtonPosition">
  5821. <option value="bottom" data-i18n="settings:codeEditor.preferences.submitButtonPosition.options.bottom"></option>
  5822. <option value="top" data-i18n="settings:codeEditor.preferences.submitButtonPosition.options.top"></option>
  5823. </select>
  5824. </div>
  5825. <hr>
  5826. <h4 data-i18n="settings:codeEditor.onlineCodeExecution.title"></h4>
  5827. <label>
  5828. <input type='radio' name='compiler' value='official'>
  5829. <span class='OJBetter_setting_menu_label_text'
  5830. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.codeforces"></span>
  5831. </label>
  5832. <label>
  5833. <input type='radio' name='compiler' value='wandbox'>
  5834. <span class='OJBetter_setting_menu_label_text'
  5835. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.wandbox"></span>
  5836. </label>
  5837. <label>
  5838. <input type='radio' name='compiler' value='rextester'>
  5839. <span class='OJBetter_setting_menu_label_text'
  5840. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.rextester"></span>
  5841. </label>
  5842. <hr>
  5843. <h4 data-i18n="settings:codeEditor.lsp.title"></h4>
  5844. <div class='OJBetter_setting_list'>
  5845. <label for="useLSP"><span data-i18n="settings:codeEditor.lsp.useLSP.label"></span></label>
  5846. <div class="help_tip">
  5847. ${helpCircleHTML}
  5848. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.useLSP.helpText"></div>
  5849. </div>
  5850. <input type="checkbox" id="useLSP" name="useLSP">
  5851. </div>
  5852. <div class='OJBetter_setting_list'>
  5853. <label for='OJBetter_Bridge_WorkUri'>
  5854. <div style="display: flex;align-items: center;">
  5855. <span class="input_label" data-i18n="settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.label"></span>
  5856. </div>
  5857. </label>
  5858. <div class="help_tip">
  5859. ${helpCircleHTML}
  5860. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.helpText"></div>
  5861. </div>
  5862. <input type='text' id='OJBetter_Bridge_WorkUri' class='no_default'
  5863. require=true data-i18n="[placeholder]settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.placeholder">
  5864. </div>
  5865. <div class='OJBetter_setting_list'>
  5866. <label for='OJBetter_Bridge_SocketUrl'>
  5867. <div style="display: flex;align-items: center;">
  5868. <span class="input_label"
  5869. data-i18n="settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.label"></span>
  5870. </div>
  5871. </label>
  5872. <div class="help_tip">
  5873. ${helpCircleHTML}
  5874. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.helpText"></div>
  5875. </div>
  5876. <input type='text' id='OJBetter_Bridge_SocketUrl' class='no_default'
  5877. require=true data-i18n="[placeholder]settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.placeholder">
  5878. </div>
  5879. <hr>
  5880. <h4 data-i18n="settings:codeEditor.staticCompletionEnhancement.title"></h4>
  5881. <div class='OJBetter_setting_list'>
  5882. <label for="cppCodeTemplateComplete"><span
  5883. data-i18n="settings:codeEditor.staticCompletionEnhancement.cppCodeTemplateComplete.label"></span></label>
  5884. <div class="help_tip">
  5885. ${helpCircleHTML}
  5886. <div class="tip_text" data-i18n="[html]settings:codeEditor.staticCompletionEnhancement.cppCodeTemplateComplete.helpText"></div>
  5887. </div>
  5888. <input type="checkbox" id="cppCodeTemplateComplete" name="cppCodeTemplateComplete">
  5889. </div>
  5890. <hr>
  5891. <h5 data-i18n="settings:codeEditor.staticCompletionEnhancement.customization"></h5>
  5892. <div class='OJBetter_setting_list alert_warn'>
  5893. <div>
  5894. <p data-i18n="settings:codeEditor.staticCompletionEnhancement.performanceWarning"></p>
  5895. </div>
  5896. </div>
  5897. <div id="Complet_config" class="config"></div>
  5898. </div>
  5899. `;
  5900.  
  5901. const preference_settings_HTML = `
  5902. <div id="preference-settings" class="settings-page">
  5903. <h3 data-i18n="settings:preference.title"></h3>
  5904. <hr>
  5905. <div class='OJBetter_setting_list'>
  5906. <label for="showLoading" data-i18n="settings:preference.loadingInfo.label"></label>
  5907. <div class="help_tip">
  5908. ${helpCircleHTML}
  5909. <div class="tip_text" data-i18n="[html]settings:preference.loadingInfo.helpText" data-i18n-options='{ "scriptName": "${OJBetter.state.name}" }'></div>
  5910. </div>
  5911. <input type="checkbox" id="showLoading" name="showLoading">
  5912. </div>
  5913. <div class='OJBetter_setting_list'>
  5914. <label for="hoverTargetAreaDisplay" data-i18n="settings:preference.targetArea.label"></label>
  5915. <div class="help_tip">
  5916. ${helpCircleHTML}
  5917. <div class="tip_text" data-i18n="[html]settings:preference.targetArea.helpText"></div>
  5918. </div>
  5919. <input type="checkbox" id="hoverTargetAreaDisplay" name="hoverTargetAreaDisplay">
  5920. </div>
  5921. <div class='OJBetter_setting_list'>
  5922. <label for='iconButtonSize'>
  5923. <div style="display: flex;align-items: center;" data-i18n="settings:preference.iconButtonSize.title"></div>
  5924. </label>
  5925. <div class="help_tip">
  5926. ${helpCircleHTML}
  5927. <div class="tip_text" data-i18n="[html]settings:preference.iconButtonSize.helpText"></div>
  5928. </div>
  5929. <input type='number' id='iconButtonSize' class='no_default' require=true data-i18n="[placeholder]settings:preference.iconButtonSize.placeholder">
  5930. <span>px</span>
  5931. </div>
  5932. </div>
  5933. `;
  5934.  
  5935. const dev_settings_HTML = `
  5936. <div id="dev-settings" class="settings-page">
  5937. <h3 data-i18n="settings:dev.title"></h3>
  5938. <hr>
  5939. <div class='OJBetter_setting_list alert_danger'>
  5940. <div>
  5941. <p data-i18n="[html]settings:dev.notice"></p>
  5942. </div>
  5943. </div>
  5944. <hr>
  5945. <h5 data-i18n="settings:dev.load.title"></h5>
  5946. <div class='OJBetter_setting_list'>
  5947. <label for="notWaiteLoaded"><span data-i18n="settings:dev.load.notWaiteLoaded.label"></span></label>
  5948. <div class="help_tip">
  5949. ${helpCircleHTML}
  5950. <div class="tip_text" data-i18n="[html]settings:dev.load.notWaiteLoaded.helpText"></div>
  5951. </div>
  5952. <input type="checkbox" id="notWaiteLoaded" name="notWaiteLoaded">
  5953. </div>
  5954. <hr>
  5955. <h5 data-i18n="settings:dev.l10n.title"></h5>
  5956. <div class='OJBetter_setting_list'>
  5957. <label><span data-i18n="settings:dev.l10n.refreshScrpitCache.label"></span></label>
  5958. <div class="help_tip">
  5959. ${helpCircleHTML}
  5960. <div class="tip_text" data-i18n="[html]settings:dev.l10n.refreshScrpitCache.helpText"></div>
  5961. </div>
  5962. <button type="button" id="l10n_refreshScrpitCacheButton" name="l10n_refreshScrpitCacheButton" data-i18n="settings:dev.l10n.refreshScrpitCache.button"></button>
  5963. </div>
  5964. <hr>
  5965. <h5 data-i18n="settings:dev.l10n_web.title"></h5>
  5966. <div class='OJBetter_setting_list'>
  5967. <label><span data-i18n="settings:dev.l10n_web.refreshScrpitCache.label"></span></label>
  5968. <div class="help_tip">
  5969. ${helpCircleHTML}
  5970. <div class="tip_text" data-i18n="[html]settings:dev.l10n_web.refreshScrpitCache.helpText"></div>
  5971. </div>
  5972. <button type="button" id="l10n_web_refreshScrpitCacheButton" name="l10n_web_refreshScrpitCacheButton" data-i18n="settings:dev.l10n_web.refreshScrpitCache.button"></button>
  5973. </div>
  5974. <div class='OJBetter_setting_list'>
  5975. <label for="isRuleMarkingEnabled"><span data-i18n="settings:dev.l10n_web.isRuleMarkingEnabled.label"></span></label>
  5976. <div class="help_tip">
  5977. ${helpCircleHTML}
  5978. <div class="tip_text" data-i18n="[html]settings:dev.l10n_web.isRuleMarkingEnabled.helpText"></div>
  5979. </div>
  5980. <input type="checkbox" id="isRuleMarkingEnabled" name="isRuleMarkingEnabled">
  5981. </div>
  5982. <hr>
  5983. <h5 data-i18n="settings:dev.indexedDB.title"></h5>
  5984. <div class='OJBetter_setting_list'>
  5985. <label><span data-i18n="settings:dev.indexedDB.clear.label"></span></label>
  5986. <div class="help_tip">
  5987. ${helpCircleHTML}
  5988. <div class="tip_text" data-i18n="[html]settings:dev.indexedDB.clear.helpText"></div>
  5989. </div>
  5990. <button type="button" id="indexedDB_clearButton" name="indexedDB_clearButton" data-i18n="settings:dev.indexedDB.clear.button"></button>
  5991. </div>
  5992. <div class='OJBetter_setting_list'>
  5993. <label><span data-i18n="settings:dev.indexedDB.inputOrExport.label"></span></label>
  5994. <div class="help_tip">
  5995. ${helpCircleHTML}
  5996. <div class="tip_text" data-i18n="[html]settings:dev.indexedDB.inputOrExport.helpText"></div>
  5997. </div>
  5998. <button type="button" id="indexedDB_exportButton" name="indexedDB_exportButton" data-i18n="settings:dev.indexedDB.inputOrExport.export"></button>
  5999. <button type="button" id="indexedDB_importButton" name="indexedDB_importButton" data-i18n="settings:dev.indexedDB.inputOrExport.import"></button>
  6000. </div>
  6001. <hr>
  6002. <h5 data-i18n="settings:dev.configuration.title"></h5>
  6003. <div class='OJBetter_setting_list'>
  6004. <label><span data-i18n="settings:dev.configuration.clear.label"></span></label>
  6005. <div class="help_tip">
  6006. ${helpCircleHTML}
  6007. <div class="tip_text" data-i18n="[html]settings:dev.configuration.clear.helpText"></div>
  6008. </div>
  6009. <button type="button" id="configuration_clearButton" name="configuration_clearButton" data-i18n="settings:dev.configuration.clear.button"></button>
  6010. </div>
  6011. <div class='OJBetter_setting_list'>
  6012. <label><span data-i18n="settings:dev.configuration.inputOrExport.label"></span></label>
  6013. <div class="help_tip">
  6014. ${helpCircleHTML}
  6015. <div class="tip_text" data-i18n="[html]settings:dev.configuration.inputOrExport.helpText"></div>
  6016. </div>
  6017. <button type="button" id="configuration_exportButton" name="configuration_exportButton" data-i18n="settings:dev.configuration.inputOrExport.export"></button>
  6018. <button type="button" id="configuration_importButton" name="configuration_importButton" data-i18n="settings:dev.configuration.inputOrExport.import"></button>
  6019. </div>
  6020. </div>
  6021. `;
  6022.  
  6023. const about_settings_HTML = `
  6024. <div id="about-settings" class="settings-page">
  6025. <h3 data-i18n="settings:about.title"></h3>
  6026. <hr>
  6027. <div class='versionInfo'>
  6028. <p>${OJBetter.state.name}</p>
  6029. <p><span data-i18n="settings:about.version"></span><span id="nowVersion">${OJBetter.state.version}</span></p>
  6030. <p> @北极小狐 <a target="_blank" href="https://github.com/beijixiaohu/OJBetter">Github</a>
  6031. <a target="_blank" href="https://greatest.deepsurf.us/zh-CN/scripts/465777">GreasyFork</a></p>
  6032. </div>
  6033. <hr>
  6034. <h5 data-i18n="settings:about.update.title"></h5>
  6035. <div id="thanksforDevChannelNotice" class='OJBetter_setting_list alert_info'>
  6036. <div data-i18n="[html]settings:about.update.thanksforDevChannelNotice"} data-i18n-options='{ "scriptName": "${OJBetter.state.name}" }' ></div>
  6037. </div>
  6038. <div class='OJBetter_setting_list'>
  6039. <label for="updateChannel"><span data-i18n="settings:about.update.channel.label"></span></label>
  6040. <div class="help_tip">
  6041. ${helpCircleHTML}
  6042. <div class="tip_text" data-i18n="[html]settings:about.update.channel.helpText"></div>
  6043. </div>
  6044. <select id="updateChannel" name="updateChannel">
  6045. <option value="release" data-i18n="settings:about.update.channel.options.release"></option>
  6046. <option value="dev" data-i18n="settings:about.update.channel.options.dev"></option>
  6047. </select>
  6048. </div>
  6049. <div class='OJBetter_setting_list'>
  6050. <label for="updateSource"><span data-i18n="settings:about.update.source.label"></span></label>
  6051. <div class="help_tip">
  6052. ${helpCircleHTML}
  6053. <div class="tip_text" data-i18n="[html]settings:about.update.source.helpText"></div>
  6054. </div>
  6055. <select id="updateSource" name="updateSource">
  6056. <option value="greasyfork" data-i18n="settings:about.update.source.options.greasyfork"></option>
  6057. <option value="github" data-i18n="settings:about.update.source.options.github"></option>
  6058. <option value="aliyunoss" data-i18n="settings:about.update.source.options.aliyunoss"></option>
  6059. </select>
  6060. </div>
  6061. </div>
  6062. `;
  6063.  
  6064. const OJBetter_setting_content_HTML = `
  6065. <div class="OJBetter_setting_content">
  6066. ${basic_settings_HTML}
  6067. ${l10n_settings_HTML}
  6068. ${translation_settings_HTML}
  6069. ${clist_rating_settings_HTML}
  6070. ${code_editor_settings_HTML}
  6071. ${preference_settings_HTML}
  6072. ${dev_settings_HTML}
  6073. ${about_settings_HTML}
  6074. </div>
  6075. `;
  6076.  
  6077. // 设置界面HTML
  6078. const OJBetterSettingMenu_HTML = `
  6079. <dialog class='OJBetter_setting_menu' id='OJBetter_setting_menu'>
  6080. <div class="tool-box">
  6081. <button class='ojb_btn ojb_btn_popover top btn-close'>
  6082. <i class="iconfont">&#xe614;</i>
  6083. </button>
  6084. </div>
  6085. <div class="OJBetter_setting_container">
  6086. ${OJBetter_setting_sidebar_HTML}
  6087. ${OJBetter_setting_content_HTML}
  6088. </div>
  6089. </dialog>
  6090. `;
  6091.  
  6092. const apiCustomConfigHTML = (prefix) => {
  6093. return `
  6094. <div class="OJBetter_setting_list">
  6095. <label for='${prefix}_header'>
  6096. <div style="display: flex;align-items: center;">
  6097. <span class="input_label" data-i18n="config:common.advanced.header.label"></span>
  6098. <div class="help_tip">
  6099. ${helpCircleHTML}
  6100. <div class="tip_text" data-i18n="[html]config:common.advanced.header.tipText"></div>
  6101. </div>
  6102. </div>
  6103. </label>
  6104. <textarea id="${prefix}_header" placeholder='' require = false data-i18n="[placeholder]config:common.advanced.header.placeholder"></textarea>
  6105. </div>
  6106. <div class="OJBetter_setting_list">
  6107. <label for='${prefix}_data'>
  6108. <div style="display: flex;align-items: center;">
  6109. <span class="input_label" data-i18n="config:common.advanced.data.label"></span>
  6110. <div class="help_tip">
  6111. ${helpCircleHTML}
  6112. <div class="tip_text" data-i18n="[html]config:common.advanced.data.tipText"></div>
  6113. </div>
  6114. </div>
  6115. </label>
  6116. <textarea id="${prefix}_data" placeholder='' require = false data-i18n="[placeholder]config:common.advanced.data.placeholder"></textarea>
  6117. </div>
  6118. `;
  6119. };
  6120.  
  6121. const apiQuotaConfigHTML = (prefix) => {
  6122. return `
  6123. <div class="OJBetter_setting_list">
  6124. <label for='${prefix}_quota_url'>
  6125. <div style="display: flex;align-items: center;">
  6126. <span class="input_label" data-i18n="config:common.quota.url.label"></span>
  6127. <div class="help_tip">
  6128. ${helpCircleHTML}
  6129. <div class="tip_text" data-i18n="[html]config:common.quota.url.tipText"></div>
  6130. </div>
  6131. </div>
  6132. </label>
  6133. <input type='text' id='${prefix}_quota_url' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:common.quota.url.placeholder">
  6134. </div>
  6135. <div class="OJBetter_setting_list">
  6136. <label for="${prefix}_quota_method" style="display: flex;" data-i18n="config:common.quota.method.label"></label>
  6137. <div class="help_tip">
  6138. ${helpCircleHTML}
  6139. <div class="tip_text" data-i18n="[html]config:common.quota.method.tipText"></div>
  6140. </div>
  6141. <select id="${prefix}_quota_method" name="${prefix}_quota_method">
  6142. <option value="get">GET</option>
  6143. <option value="post">POST</option>
  6144. </select>
  6145. </div>
  6146. <div class="OJBetter_setting_list">
  6147. <label for='${prefix}_quota_header'>
  6148. <div style="display: flex;align-items: center;">
  6149. <span class="input_label" data-i18n="config:common.quota.header.label"></span>
  6150. <div class="help_tip">
  6151. ${helpCircleHTML}
  6152. <div class="tip_text" data-i18n="[html]config:common.quota.header.tipText"></div>
  6153. </div>
  6154. </div>
  6155. </label>
  6156. <textarea id="${prefix}_quota_header" placeholder='' require = false data-i18n="[placeholder]config:common.quota.header.placeholder"></textarea>
  6157. </div>
  6158. <div class="OJBetter_setting_list">
  6159. <label for='${prefix}_quota_data'>
  6160. <div style="display: flex;align-items: center;">
  6161. <span class="input_label" data-i18n="config:common.quota.data.label"></span>
  6162. <div class="help_tip">
  6163. ${helpCircleHTML}
  6164. <div class="tip_text" data-i18n="[html]config:common.quota.data.tipText"></div>
  6165. </div>
  6166. </div>
  6167. </label>
  6168. <textarea id="${prefix}_quota_data" placeholder='' require = false data-i18n="[placeholder]config:common.quota.data.placeholder"></textarea>
  6169. </div>
  6170. <div class="OJBetter_setting_list">
  6171. <div style="display: flex;align-items: center;">
  6172. <span class="input_label" data-i18n="config:common.quota.surplus.label"></span>
  6173. <div class="help_tip">
  6174. ${helpCircleHTML}
  6175. <div class="tip_text" data-i18n="[html]config:common.quota.surplus.tipText"></div>
  6176. </div>
  6177. </div>
  6178. </label>
  6179. <input type='text' id='${prefix}_quota_surplus' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:common.quota.surplus.placeholder">
  6180. </div>
  6181. `;
  6182. }
  6183.  
  6184. const deeplConfigEditHTML = `
  6185. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  6186. <div class='OJBetter_setting_content'>
  6187. <div class="tool-box">
  6188. <button class='ojb_btn ojb_btn_popover top btn-close'>
  6189. <i class="iconfont">&#xe614;</i>
  6190. </button>
  6191. </div>
  6192. <h4 data-i18n="config:deepl.title"></h4>
  6193. <h5 data-i18n="config:deepl.basic.title"></h5>
  6194. <hr>
  6195. <div class="OJBetter_setting_list">
  6196. <label for='name'>
  6197. <span class="input_label" data-i18n="config:deepl.basic.name.label"></span>
  6198. </label>
  6199. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:deepl.basic.name.placeholder">
  6200. </div>
  6201. <div class='OJBetter_setting_list'>
  6202. <label for="deepl_apiGenre" style="display: flex;" data-i18n="config:deepl.genre.label"></label>
  6203. <div class="help_tip">
  6204. ${helpCircleHTML}
  6205. <div class="tip_text" data-i18n="[html]config:deepl.genre.tipText"></div>
  6206. </div>
  6207. <select id="deepl_apiGenre" name="deepl_apiGenre">
  6208. <option value="api-free">api-free</option>
  6209. <option value="api-pro">api-pro</option>
  6210. <option value="deeplx">deeplx</option>
  6211. </select>
  6212. </div>
  6213. <div class="OJBetter_setting_list">
  6214. <label for='deepl_key'>
  6215. <div style="display: flex;align-items: center;">
  6216. <span class="input_label" data-i18n="config:deepl.basic.key.label"></span>
  6217. <div class="help_tip">
  6218. ${helpCircleHTML}
  6219. <div class="tip_text" data-i18n="[html]config:deepl.basic.key.tipText"></div>
  6220. </div>
  6221. </div>
  6222. </label>
  6223. <input type='text' id='deepl_key' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:deepl.basic.key.placeholder">
  6224. </div>
  6225. <div class="OJBetter_setting_list">
  6226. <label for='deepl_proxy'>
  6227. <div style="display: flex;align-items: center;">
  6228. <span class="input_label" data-i18n="config:deepl.basic.proxy.label">Proxy API:</span>
  6229. <div class="help_tip">
  6230. ${helpCircleHTML}
  6231. <div class="tip_text" data-i18n="[html]config:deepl.basic.proxy.tipText"></div>
  6232. </div>
  6233. </div>
  6234. </label>
  6235. <input type='text' id='deepl_proxy' placeholder='' require = false>
  6236. </div>
  6237. <hr>
  6238. <details>
  6239. <summary data-i18n="config:common.advanced.title"></summary>
  6240. ${apiCustomConfigHTML('deepl')}
  6241. </details>
  6242. <details>
  6243. <summary data-i18n="config:common.quota.title"></summary>
  6244. ${apiQuotaConfigHTML('deepl')}
  6245. </details>
  6246. <button id='tempConfig_save' data-i18n="common:save"></button>
  6247. </div>
  6248. </dialog>
  6249. `;
  6250.  
  6251. const chatgptConfigEditHTML = `
  6252. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  6253. <div class='OJBetter_setting_content'>
  6254. <div class="tool-box">
  6255. <button class='ojb_btn ojb_btn_popover top btn-close'>
  6256. <i class="iconfont">&#xe614;</i>
  6257. </button>
  6258. </div>
  6259. <h4 data-i18n="config:chatgpt.title"></h4>
  6260. <h5 data-i18n="config:chatgpt.basic.title"></h5>
  6261. <hr>
  6262. <div class="OJBetter_setting_list">
  6263. <label for='name'>
  6264. <span class="input_label" data-i18n="config:chatgpt.basic.name.label"></span>
  6265. </label>
  6266. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:chatgpt.basic.name.placeholder">
  6267. </div>
  6268. <div class="OJBetter_setting_list">
  6269. <label for='chatgpt_model'>
  6270. <div style="display: flex;align-items: center;">
  6271. <span class="input_label" data-i18n="[html]config:chatgpt.basic.model.label"></span>
  6272. <div class="help_tip">
  6273. ${helpCircleHTML}
  6274. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.model.tipText"></div>
  6275. </div>
  6276. </div>
  6277. </label>
  6278. <input type='text' id='chatgpt_model' placeholder='gpt-3.5-turbo' require = false>
  6279. </div>
  6280. <div class="OJBetter_setting_list">
  6281. <label for='chatgpt_key'>
  6282. <div style="display: flex;align-items: center;">
  6283. <span class="input_label" data-i18n="config:chatgpt.basic.key.label"></span>
  6284. <div class="help_tip">
  6285. ${helpCircleHTML}
  6286. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.key.tipText"></div>
  6287. </div>
  6288. </div>
  6289. </label>
  6290. <input type='text' id='chatgpt_key' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:chatgpt.basic.key.placeholder">
  6291. </div>
  6292. <div class="OJBetter_setting_list">
  6293. <label for='chatgpt_proxy'>
  6294. <div style="display: flex;align-items: center;">
  6295. <span class="input_label" data-i18n="config:chatgpt.basic.proxy.label">Proxy API:</span>
  6296. <div class="help_tip">
  6297. ${helpCircleHTML}
  6298. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.proxy.tipText"></div>
  6299. </div>
  6300. </div>
  6301. </label>
  6302. <input type='text' id='chatgpt_proxy' placeholder='https://api.openai.com/v1/chat/completions' require = false>
  6303. </div>
  6304. <hr>
  6305. <details>
  6306. <summary data-i18n="config:common.advanced.title"></summary>
  6307. ${apiCustomConfigHTML('chatgpt')}
  6308. </details>
  6309. <details>
  6310. <summary data-i18n="config:common.quota.title"></summary>
  6311. ${apiQuotaConfigHTML('chatgpt')}
  6312. </details>
  6313. <button id='tempConfig_save' data-i18n="common:save"></button>
  6314. </div>
  6315. </dialog>
  6316. `;
  6317.  
  6318. const CompletConfigEditHTML = `
  6319. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  6320. <div class='OJBetter_setting_content'>
  6321. <div class="tool-box">
  6322. <button class='ojb_btn ojb_btn_popover top btn-close'>
  6323. <i class="iconfont">&#xe614;</i>
  6324. </button>
  6325. </div>
  6326. <h4 data-i18n="config:complet.title"></h4>
  6327. <hr>
  6328. <div class="OJBetter_setting_list">
  6329. <label for='name'>
  6330. <span class="input_label" data-i18n="config:complet.name.label"></span>
  6331. </label>
  6332. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:complet.name.placeholder">
  6333. </div>
  6334. <div class='OJBetter_setting_list'>
  6335. <label for="complet_isChoose"><span id="loaded_span" data-i18n="config:complet.choose.label"></span></label>
  6336. <input type="checkbox" id="complet_isChoose" name="complet_isChoose" require = false>
  6337. </div>
  6338. <div class='OJBetter_setting_list'>
  6339. <label for="complet_genre" style="display: flex;" data-i18n="config:complet.genre.label"></label>
  6340. <div class="help_tip">
  6341. ${helpCircleHTML}
  6342. <div class="tip_text" data-i18n="[html]config:complet.genre.tipText"></div>
  6343. </div>
  6344. <select id="complet_genre" name="complet_genre">
  6345. <option value="monaco">monaco</option>
  6346. <option value="ace">ace</option>
  6347. </select>
  6348. </div>
  6349. <div class='OJBetter_setting_list'>
  6350. <label for="complet_language" style="display: flex;" data-i18n="config:complet.language.label"></label>
  6351. <select id="complet_language" name="complet_language">
  6352. <option value="cpp">cpp</option>
  6353. <option value="python">python</option>
  6354. <option value="java">java</option>
  6355. <option value="c">c</option>
  6356. </select>
  6357. </div>
  6358. <div class="OJBetter_setting_list">
  6359. <label for='complet_jsonUrl'>
  6360. <div style="display: flex;align-items: center;">
  6361. <span class="input_label">JSON URL:</span>
  6362. <div class="help_tip">
  6363. ${helpCircleHTML}
  6364. <div class="tip_text" data-i18n="[html]config:complet.jsonurl.tipText"></div>
  6365. </div>
  6366. </div>
  6367. </label>
  6368. <div class='OJBetter_setting_list alert_warn' data-i18n="[html]config:complet.jsonurl.alert"></div>
  6369. <input type='text' id='complet_jsonUrl' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:complet.jsonurl.placeholder">
  6370. </div>
  6371. <button id='tempConfig_save' data-i18n="common:save"></button>
  6372. </div>
  6373. </dialog>
  6374. `;
  6375.  
  6376. /**
  6377. * 加载设置按钮面板
  6378. */
  6379. async function initSettingsPanel() {
  6380. /**
  6381. * 添加右上角设置按钮
  6382. * @param {string} location 位置选择器
  6383. * @param {string} method 插入方法
  6384. */
  6385. function insertOJBetterSettingButton(location, method) {
  6386. $(location)[method](`<button class='ojb_btn OJBetter_setting'>
  6387. ${OJBetter.state.name} ${i18next.t('settings', { ns: 'common' })}</button>`);
  6388. }
  6389.  
  6390. /**
  6391. * ============================================
  6392. * 该网站插入设置按钮的位置和方式
  6393. */
  6394. if (OJBetter.typeOfPage.isEnglishLanguage) {
  6395. insertOJBetterSettingButton("#navbar-collapse > ul:nth-child(2) > li:last-child", "after");
  6396. } else {
  6397. if ($('.header-mypage').length > 0) insertOJBetterSettingButton(".header-mypage", "after");
  6398. else insertOJBetterSettingButton("#navbar-collapse > ul:nth-child(2) > li:last-child", "after");
  6399. }
  6400. /**
  6401. * ============================================
  6402. */
  6403.  
  6404. const $settingBtns = $(".OJBetter_setting");
  6405. $settingBtns.click(() => {
  6406. $settingBtns.prop("disabled", true).addClass("open");
  6407.  
  6408. // 设置面板div
  6409. const settingMenu = OJB_safeCreateJQElement(OJBetterSettingMenu_HTML);
  6410. $("body").append(settingMenu);
  6411.  
  6412. elementLocalize(settingMenu); // 加载i18n
  6413. OJB_showModal(settingMenu);
  6414. OJB_addDraggable($('#OJBetter_setting_menu')); // 窗口支持拖拽
  6415.  
  6416. // help帮助悬浮窗位置更新
  6417. $(document).on('mouseenter', '.help-icon', function (event) {
  6418. var menuOffset = $('.OJBetter_setting_menu:last').offset();
  6419. var mouseX = event.pageX - menuOffset.left;
  6420. var mouseY = event.pageY - menuOffset.top;
  6421.  
  6422. $('.tip_text').css({
  6423. 'top': mouseY + 'px',
  6424. 'left': mouseX + 'px'
  6425. });
  6426. });
  6427.  
  6428. // 选项卡切换
  6429. $('.OJBetter_setting_sidebar a').click(function (event) {
  6430. event.preventDefault();
  6431. $('.OJBetter_setting_sidebar a').removeClass('active');
  6432. $('.settings-page').removeClass('active');
  6433. $(this).addClass('active');
  6434. const targetPageId = $(this).attr('href').substring(1);
  6435. $('#' + targetPageId).addClass('active');
  6436. });
  6437.  
  6438. /**
  6439. * 更新单选按钮组的可用状态
  6440. * @param {string} selector 单选按钮组的选择器
  6441. * @param {string} targetLanguage 目标语言
  6442. * @param {Object} translationSupport 翻译支持的语言对应表
  6443. */
  6444. const updateRadioButtonsAvailability = (selector, targetLanguage) => {
  6445. Object.entries(OJBetter.supportList.translationSupport).forEach(([service, languages]) => {
  6446. const radioButton = $(selector).find(`input[value="${service}"]`);
  6447. const isEnabled = languages[targetLanguage];
  6448. $(radioButton).prop('disabled', !isEnabled);
  6449. if (!isEnabled) {
  6450. $(radioButton).prop('checked', false);
  6451. }
  6452. });
  6453. };
  6454.  
  6455. /**
  6456. * 检查下拉框选中项是否有效,若无效则清空
  6457. * @param {string} selector 下拉框的选择器
  6458. */
  6459. const validateSelectOption = (selector) => {
  6460. const selectedValue = $(selector).val();
  6461. if (!selectedValue) {
  6462. $(selector).val('');
  6463. }
  6464. };
  6465.  
  6466. /**
  6467. * 更新翻译目标语言下拉框的可用状态
  6468. * @param {string} selector 下拉框的选择器
  6469. * @param {string} targetLanguage 目标语言
  6470. * @param {Object} translationSupport 翻译支持的语言对应表
  6471. */
  6472. const updateSelectOptionsAvailability = (selector, targetLanguage) => {
  6473. $(selector).children('option').each(function () {
  6474. const optionValue = $(this).val();
  6475. const isEnabled = OJBetter.supportList.translationSupport[optionValue] ? OJBetter.supportList.translationSupport[optionValue][targetLanguage] : true;
  6476. $(this).prop('disabled', !isEnabled);
  6477. });
  6478. validateSelectOption(selector);
  6479. };
  6480.  
  6481. /**
  6482. * 更新翻译服务复选框的可用状态
  6483. * @param {string} selector 复选框的选择器
  6484. * @param {string} targetLanguage 目标语言
  6485. * @param {Object} translationSupport 翻译支持的语言对应表
  6486. */
  6487. const updateCheckboxesAvailability = (selector, targetLanguage) => {
  6488. $(selector).children('input').each(function () {
  6489. const checkboxValue = $(this).val();
  6490. const isEnabled = OJBetter.supportList.translationSupport[checkboxValue][targetLanguage];
  6491. $(this).prop('disabled', !isEnabled);
  6492. if (!isEnabled) {
  6493. $(this).prop('checked', false);
  6494. }
  6495. });
  6496. };
  6497.  
  6498. /**
  6499. * 更新更新源下拉框的可用状态
  6500. * @param {string} selector 下拉框的选择器
  6501. * @param {string} targetLanguage 目标语言
  6502. * @param {Object} translationSupport 翻译支持的语言对应表
  6503. */
  6504. const updateUpdateSourceSelectOptionsAvailability = (selector, updateChannel) => {
  6505. $(selector).children('option').each(function () {
  6506. const optionValue = $(this).val();
  6507. const isEnabled = OJBetter.supportList.updateSourceSupportList[optionValue][updateChannel];
  6508. $(this).prop('disabled', !isEnabled);
  6509. });
  6510. validateSelectOption(selector);
  6511. };
  6512.  
  6513. /**
  6514. * 创建配置结构
  6515. * @param {string} type - 该字段的在表单中的类型
  6516. * @param {string} value - 在配置中的键值
  6517. * @param {boolean} require - 是否是表单的必填项
  6518. * @param {string} [check=""] check - 调用的合法性检查
  6519. */
  6520. function createStructure(type, value, require, check = "") {
  6521. return { type, value, require, check };
  6522. }
  6523.  
  6524. // deepl配置
  6525. const deeplStructure = {
  6526. '#name': createStructure('text', 'name', true),
  6527. '#deepl_apiGenre': createStructure('text', 'apiGenre', true),
  6528. '#deepl_key': createStructure('text', 'key', false),
  6529. '#deepl_proxy': createStructure('text', 'proxy', false),
  6530. '#deepl_header': createStructure('text', '_header', false, 'keyValuePairs'),
  6531. '#deepl_data': createStructure('text', '_data', false, 'keyValuePairs'),
  6532. '#deepl_quota_url': createStructure('text', 'quota_url', false),
  6533. '#deepl_quota_method': createStructure('text', 'quota_method', false),
  6534. '#deepl_quota_header': createStructure('text', 'quota_header', false, 'keyValuePairs'),
  6535. '#deepl_quota_data': createStructure('text', 'quota_data', false, 'keyValuePairs'),
  6536. '#deepl_quota_surplus': createStructure('text', 'quota_surplus', false, 'dotSeparatedPath'),
  6537. };
  6538. let tempConfig_deepl = GM_getValue('deepl_config'); // 获取配置信息
  6539. const configManager_deepl = new ConfigManager('#deepl_config', 'deepl_config_', tempConfig_deepl, deeplStructure, deeplConfigEditHTML);
  6540. configManager_deepl.registerChoiceChange();
  6541.  
  6542. // chatgpt配置
  6543. const chatgptStructure = {
  6544. '#name': createStructure('text', 'name', true),
  6545. '#chatgpt_model': createStructure('text', 'model', false),
  6546. '#chatgpt_key': createStructure('text', 'key', true),
  6547. '#chatgpt_proxy': createStructure('text', 'proxy', false),
  6548. '#chatgpt_header': createStructure('text', '_header', false, 'keyValuePairs'),
  6549. '#chatgpt_data': createStructure('text', '_data', false, 'keyValuePairs'),
  6550. '#chatgpt_quota_url': createStructure('text', 'quota_url', false),
  6551. '#chatgpt_quota_header': createStructure('text', 'quota_header', false, 'keyValuePairs'),
  6552. '#chatgpt_quota_data': createStructure('text', 'quota_data', false, 'keyValuePairs'),
  6553. '#chatgpt_quota_surplus': createStructure('text', 'quota_surplus', false, 'dotSeparatedPath'),
  6554. '#chatgpt_quota_method': createStructure('text', 'quota_method', false),
  6555. };
  6556. let tempConfig_chatgpt = GM_getValue('chatgpt_config'); // 获取配置信息
  6557. const configManager_chatgpt = new ConfigManager('#chatgpt_config', 'chatgpt_config_', tempConfig_chatgpt, chatgptStructure, chatgptConfigEditHTML);
  6558. configManager_chatgpt.registerChoiceChange();
  6559.  
  6560. // Complet配置
  6561. const CompletStructure = {
  6562. '#name': createStructure('text', 'name', true),
  6563. '#complet_isChoose': createStructure('checkbox', 'isChoose', true),
  6564. '#complet_genre': createStructure('text', 'genre', true),
  6565. '#complet_language': createStructure('text', 'language', true),
  6566. '#complet_jsonUrl': createStructure('text', 'jsonUrl', true)
  6567. };
  6568. let tempConfig_Complet = GM_getValue('Complet_config'); // 获取配置信息
  6569. const configManager_complet = new ConfigManager('#Complet_config', 'complet_config_', tempConfig_Complet, CompletStructure, CompletConfigEditHTML, false);
  6570.  
  6571. // 状态更新
  6572. $("input[name='darkMode'][value='" + OJBetter.basic.darkMode + "']").prop("checked", true);
  6573. $("#showLoading").prop("checked", GM_getValue("showLoading") === true);
  6574. $("#expandFoldingblocks").prop("checked", GM_getValue("expandFoldingblocks") === true);
  6575. $("#renderPerfOpt").prop("checked", GM_getValue("renderPerfOpt") === true);
  6576. $("#selectElementPerfOpt").prop("checked", GM_getValue("selectElementPerfOpt") === true);
  6577. $("#commentPaging").prop("checked", GM_getValue("commentPaging") === true);
  6578. $("#standingsRecolor").prop("checked", GM_getValue("standingsRecolor") === true);
  6579. $("#showJumpToLuogu").prop("checked", GM_getValue("showJumpToLuogu") === true);
  6580. $("#showCF2vjudge").prop("checked", GM_getValue("showCF2vjudge") === true);
  6581. $("#hoverTargetAreaDisplay").prop("checked", GM_getValue("hoverTargetAreaDisplay") === true);
  6582. $("#showClistRating_contest").prop("checked", GM_getValue("showClistRating_contest") === true);
  6583. $("#showClistRating_problemset").prop("checked", GM_getValue("showClistRating_problemset") === true);
  6584. $("#showClistRating_problem").prop("checked", GM_getValue("showClistRating_problem") === true);
  6585. $("#RatingHidden").prop("checked", GM_getValue("RatingHidden") === true);
  6586. $('#scriptL10nLanguage').val(GM_getValue("scriptL10nLanguage"));
  6587. $('#localizationLanguage').val(GM_getValue("localizationLanguage"));
  6588. $("input[name='translation'][value='" + OJBetter.translation.choice + "']").prop("checked", true);
  6589. $("input[name='translation']").css("color", "gray");
  6590. $('#deepl_type').val(GM_getValue("deepl_type"));
  6591. $("#deepl_config_config_bar_ul").find(`input[name='deepl_config_config_item'][value='${tempConfig_deepl.choice}']`).prop("checked", true);
  6592. $('#enableEmphasisProtection').prop("checked", GM_getValue("enableEmphasisProtection") === true);
  6593. $('#enableLinkProtection').prop("checked", GM_getValue("enableLinkProtection") === true);
  6594. $("#chatgpt_config_config_bar_ul").find(`input[name='chatgpt_config_config_item'][value='${tempConfig_chatgpt.choice}']`).prop("checked", true);
  6595. $("#openai_isStream").prop("checked", GM_getValue("openai_isStream") === true);
  6596. $("#openai_asSystemPrompt").prop("checked", GM_getValue("openai_asSystemPrompt") === true);
  6597. $('#openai_customPrompt').val(GM_getValue("openai_customPrompt"));
  6598. $('#comment_translation_choice').val(GM_getValue("commentTranslationChoice"));
  6599. $('#iconButtonSize').val(GM_getValue("iconButtonSize"));
  6600. $("#autoTranslation").prop("checked", GM_getValue("autoTranslation") === true);
  6601. $('#shortTextLength').val(GM_getValue("shortTextLength"));
  6602. $("#allowMixTrans").prop("checked", GM_getValue("allowMixTrans") === true);
  6603. $('.OJBetter_checkboxs').find('input[type="checkbox"][name="mixedTranslation"]').each(function () {
  6604. if (OJBetter.translation.auto.mixTrans.servers.indexOf($(this).val()) > -1) {
  6605. $(this).prop('checked', true);
  6606. }
  6607. });
  6608. // 翻译目标语言下拉框
  6609. $('#transTargetLang').change(function () {
  6610. var selectedLang = $(this).val();
  6611. updateRadioButtonsAvailability('#translationServices', selectedLang);
  6612. updateSelectOptionsAvailability('#comment_translation_choice', selectedLang);
  6613. updateCheckboxesAvailability('.OJBetter_checkboxs', selectedLang);
  6614. });
  6615. $('#transTargetLang').val(GM_getValue("transTargetLang"));
  6616. $('#transTargetLang').change();
  6617. //
  6618. $('#comment_translation_mode').val(GM_getValue("commentTranslationMode"));
  6619. $("#memoryTranslateHistory").prop("checked", GM_getValue("memoryTranslateHistory") === true);
  6620. $('#transWaitTime').val(GM_getValue("transWaitTime"));
  6621. $('#translation_replaceSymbol').val(GM_getValue("replaceSymbol"));
  6622. $("#filterTextWithoutEmphasis").prop("checked", GM_getValue("filterTextWithoutEmphasis") === true);
  6623. $('#translation_retransAction').val(GM_getValue("retransAction"));
  6624. $("#clist_Authorization").val(GM_getValue("clist_Authorization"));
  6625. $("#problemPageCodeEditor").prop("checked", GM_getValue("problemPageCodeEditor") === true);
  6626. $("#beautifyPreBlocks").prop("checked", GM_getValue("beautifyPreBlocks") === true);
  6627. $("#isCodeSubmitConfirm").prop("checked", GM_getValue("isCodeSubmitConfirm") === true);
  6628. $("#autoSubmitAfterPass").prop("checked", GM_getValue("autoSubmitAfterPass") === true);
  6629. $("#alwaysConsumeMouseWheel").prop("checked", GM_getValue("alwaysConsumeMouseWheel") === true);
  6630. $("#autoMemoryCode").prop("checked", GM_getValue("autoMemoryCode") === true);
  6631. $("#submitButtonPosition").val(GM_getValue("submitButtonPosition"));
  6632. $("#cppCodeTemplateComplete").prop("checked", GM_getValue("cppCodeTemplateComplete") === true);
  6633. $("#useLSP").prop("checked", GM_getValue("useLSP") === true);
  6634. $("#OJBetter_Bridge_WorkUri").val(GM_getValue("OJBetter_Bridge_WorkUri"));
  6635. $("#OJBetter_Bridge_SocketUrl").val(GM_getValue("OJBetter_Bridge_SocketUrl"));
  6636. $("input[name='compiler'][value='" + OJBetter.monaco.onlineCompilerChoice + "']").prop("checked", true);
  6637. $("input[name='compiler']").css("color", "gray");
  6638. // 调试
  6639. $("#notWaiteLoaded").prop("checked", GM_getValue("notWaiteLoaded") === true);
  6640. $('#l10n_refreshScrpitCacheButton').click(clearI18nextCache);
  6641. $('#l10n_web_refreshScrpitCacheButton').click(clearWebsiteL10nData);
  6642. $("#isRuleMarkingEnabled").prop("checked", GM_getValue("isRuleMarkingEnabled") === true);
  6643. $('#indexedDB_clearButton').click(async () => { await clearDatabase(); });
  6644. $('#indexedDB_exportButton').click(async () => { downloadDataAsFile(await exportDatabase(), 'database_export.json') });
  6645. $('#indexedDB_importButton').click(() => { readFileInput(async (fileContent) => { await importDatabase(fileContent); }); });
  6646. $('#configuration_clearButton').click(deleteAllConfigSettings);
  6647. $('#configuration_exportButton').click(() => { downloadDataAsFile(exportSettingsToJSON(), 'configuration_export.json') });
  6648. $('#configuration_importButton').click(() => { readFileInput((fileContent) => { importSettingsFromJSON(fileContent); }) });
  6649. // 关于
  6650. $('#updateChannel').val(GM_getValue("updateChannel"));
  6651. $('#updateSource').val(GM_getValue("updateSource"));
  6652. $('#updateChannel').change(function () {
  6653. var selectedLang = $(this).val();
  6654. updateUpdateSourceSelectOptionsAvailability('#updateSource', selectedLang);
  6655. if (selectedLang == "dev") $('#thanksforDevChannelNotice').show();
  6656. else $('#thanksforDevChannelNotice').hide();
  6657. });
  6658. $('#updateChannel').change();
  6659.  
  6660. // 关闭
  6661. const $settingMenu = $(".OJBetter_setting_menu");
  6662. $settingMenu.on("click", ".btn-close", async () => {
  6663. // 设置的数据
  6664. const settings = {
  6665. darkMode: $("input[name='darkMode']:checked").val(),
  6666. showLoading: $("#showLoading").prop("checked"),
  6667. hoverTargetAreaDisplay: $("#hoverTargetAreaDisplay").prop("checked"),
  6668. expandFoldingblocks: $("#expandFoldingblocks").prop("checked"),
  6669. renderPerfOpt: $("#renderPerfOpt").prop("checked"),
  6670. selectElementPerfOpt: $("#selectElementPerfOpt").prop("checked"),
  6671. commentPaging: $("#commentPaging").prop("checked"),
  6672. standingsRecolor: $("#standingsRecolor").prop("checked"),
  6673. showJumpToLuogu: $("#showJumpToLuogu").prop("checked"),
  6674. showCF2vjudge: $("#showCF2vjudge").prop("checked"),
  6675. scriptL10nLanguage: $('#scriptL10nLanguage').val(),
  6676. localizationLanguage: $('#localizationLanguage').val(),
  6677. transTargetLang: $('#transTargetLang').val(),
  6678. translation: $("input[name='translation']:checked").val(),
  6679. deepl_type: $('#deepl_type').val(),
  6680. enableEmphasisProtection: $("#enableEmphasisProtection").prop("checked"),
  6681. enableLinkProtection: $("#enableLinkProtection").prop("checked"),
  6682. openai_isStream: $("#openai_isStream").prop("checked"),
  6683. openai_asSystemPrompt: $("#openai_asSystemPrompt").prop("checked"),
  6684. openai_customPrompt: $('#openai_customPrompt').val(),
  6685. commentTranslationChoice: $('#comment_translation_choice').val(),
  6686. iconButtonSize: $('#iconButtonSize').val(),
  6687. autoTranslation: $("#autoTranslation").prop("checked"),
  6688. shortTextLength: $('#shortTextLength').val(),
  6689. allowMixTrans: $("#allowMixTrans").prop("checked"),
  6690. mixedTranslation: (() => {
  6691. let mixedTranslation = [];
  6692. $('.OJBetter_checkboxs').find('input[type="checkbox"][name="mixedTranslation"]').each(function () {
  6693. if ($(this).is(":checked")) {
  6694. mixedTranslation.push($(this).val());
  6695. }
  6696. });
  6697. return mixedTranslation;
  6698. })(),
  6699. commentTranslationMode: $('#comment_translation_mode').val(),
  6700. memoryTranslateHistory: $('#memoryTranslateHistory').prop("checked"),
  6701. transWaitTime: $('#transWaitTime').val(),
  6702. replaceSymbol: $('#translation_replaceSymbol').val(),
  6703. filterTextWithoutEmphasis: $('#filterTextWithoutEmphasis').prop("checked"),
  6704. retransAction: $('#translation_retransAction').val(),
  6705. showClistRating_contest: $('#showClistRating_contest').prop("checked"),
  6706. showClistRating_problemset: $('#showClistRating_problemset').prop("checked"),
  6707. showClistRating_problem: $('#showClistRating_problem').prop("checked"),
  6708. RatingHidden: $('#RatingHidden').prop("checked"),
  6709. clist_Authorization: $('#clist_Authorization').val(),
  6710. problemPageCodeEditor: $("#problemPageCodeEditor").prop("checked"),
  6711. beautifyPreBlocks: $("#beautifyPreBlocks").prop("checked"),
  6712. isCodeSubmitConfirm: $("#isCodeSubmitConfirm").prop("checked"),
  6713. autoSubmitAfterPass: $("#autoSubmitAfterPass").prop("checked"),
  6714. alwaysConsumeMouseWheel: $("#alwaysConsumeMouseWheel").prop("checked"),
  6715. autoMemoryCode: $("#autoMemoryCode").prop("checked"),
  6716. submitButtonPosition: $('#submitButtonPosition').val(),
  6717. cppCodeTemplateComplete: $("#cppCodeTemplateComplete").prop("checked"),
  6718. useLSP: $("#useLSP").prop("checked"),
  6719. OJBetter_Bridge_WorkUri: $('#OJBetter_Bridge_WorkUri').val().replace(/\\/g, '/').replace(/\/$/, ''),
  6720. OJBetter_Bridge_SocketUrl: $('#OJBetter_Bridge_SocketUrl').val(),
  6721. onlineCompilerChoice: $("input[name='compiler']:checked").val(),
  6722. notWaiteLoaded: $("#notWaiteLoaded").prop("checked"),
  6723. isRuleMarkingEnabled: $("#isRuleMarkingEnabled").prop("checked"),
  6724. updateChannel: $('#updateChannel').val(),
  6725. updateSource: $('#updateSource').val()
  6726. };
  6727. // tempConfigs的数据
  6728. const tempConfigs = {
  6729. 'deepl_config': configManager_deepl.getTempConfig(),
  6730. 'chatgpt_config': configManager_chatgpt.getTempConfig(),
  6731. 'Complet_config': configManager_complet.getTempConfig()
  6732. }
  6733.  
  6734. // 判断是否改变
  6735. let changes = {};
  6736. const combinedConfigs = Object.assign({}, settings, tempConfigs); // 合并settings和tempConfigs对象
  6737. for (const [key, value] of Object.entries(combinedConfigs)) {
  6738. const storedValue = GM_getValue(key);
  6739. if (!OJB_deepEquals(value, storedValue)) {
  6740. changes[key] = { oldValue: storedValue, newValue: value };
  6741. }
  6742. }
  6743.  
  6744. // 如果changes对象不为空,则有变化
  6745. if (Object.keys(changes).length > 0) {
  6746. console.log("Changes detected:", changes);
  6747. const shouldSave = await OJB_createDialog(
  6748. i18next.t('saveSetting.title', { ns: 'dialog' }),
  6749. i18next.t('saveSetting.content', { ns: 'dialog' }),
  6750. [
  6751. i18next.t('saveSetting.buttons.0', { ns: 'dialog' }),
  6752. i18next.t('saveSetting.buttons.1', { ns: 'dialog' })
  6753. ]
  6754. ); // 配置改变保存确认
  6755. if (shouldSave) {
  6756. // 数据校验
  6757. // TODO
  6758. if (settings.deepl_type !== 'free') {
  6759. let selectedIndex = $('input[name="deepl_config_config_item"]:checked').length > 0;
  6760. if (!selectedIndex) {
  6761. $('.deepl_config a').removeClass('active');
  6762. $('.settings-page').removeClass('active');
  6763. $('#sidebar-translation-settings').addClass('active');
  6764. $('#translation-settings').addClass('active');
  6765.  
  6766. $('#deepl_config').addClass('missing');
  6767. return;
  6768. } else {
  6769. $('#deepl_config').removeClass('missing');
  6770. }
  6771. }
  6772. if (settings.translation === "openai") {
  6773. let selectedIndex = $('input[name="chatgpt_config_config_item"]:checked').length > 0;
  6774. if (!selectedIndex) {
  6775. $('.chatgpt_config a').removeClass('active');
  6776. $('.settings-page').removeClass('active');
  6777. $('#sidebar-translation-settings').addClass('active');
  6778. $('#translation-settings').addClass('active');
  6779.  
  6780. $('#chatgpt_config').addClass('missing');
  6781. return;
  6782. } else {
  6783. $('#chatgpt_config').removeClass('missing');
  6784. }
  6785. }
  6786. {
  6787. let selectedIndex = $('input[name="translation"]:checked').length > 0;
  6788. if (!selectedIndex) {
  6789. $('.OJBetter_setting_sidebar a').removeClass('active');
  6790. $('.settings-page').removeClass('active');
  6791. $('#sidebar-translation-settings').addClass('active');
  6792. $('#translation-settings').addClass('active');
  6793.  
  6794. $('#translationServices').addClass('missing');
  6795. return;
  6796. } else {
  6797. $('#translationServices').removeClass('missing');
  6798. }
  6799. }
  6800.  
  6801. // 保存数据
  6802. let refreshPage = false; // 是否需要刷新页面
  6803. for (const [key, value] of Object.entries(settings)) {
  6804. if (!refreshPage && !(key == 'translation' || key == 'darkMode' || key == 'commentTranslationChoice')) {
  6805. if (GM_getValue(key) != value) refreshPage = true;
  6806. }
  6807. GM_setValue(key, value);
  6808. }
  6809. for (const [key, value] of Object.entries(tempConfigs)) {
  6810. if (!refreshPage && (JSON.stringify(GM_getValue(key)) != JSON.stringify(value))) refreshPage = true;
  6811. GM_setValue(key, value);
  6812. }
  6813.  
  6814. if (refreshPage) location.reload();
  6815. else {
  6816. // 切换黑暗模式
  6817. if (OJBetter.basic.darkMode != settings.darkMode) {
  6818. OJBetter.basic.darkMode = settings.darkMode;
  6819. // 移除旧的事件监听器
  6820. changeEventListeners.forEach(listener => {
  6821. mediaQueryList.removeEventListener('change', listener);
  6822. });
  6823.  
  6824. if (OJBetter.basic.darkMode == "follow") {
  6825. changeEventListeners.push(handleColorSchemeChange);
  6826. mediaQueryList.addEventListener('change', handleColorSchemeChange);
  6827. $('html').removeAttr('data-theme');
  6828. } else if (OJBetter.basic.darkMode == "dark") {
  6829. $('html').attr('data-theme', 'dark');
  6830. if (OJBetter.monaco.editor) {
  6831. monaco.editor.setTheme('vs-dark');
  6832. }
  6833. } else {
  6834. $('html').attr('data-theme', 'light');
  6835. if (OJBetter.monaco.editor) {
  6836. monaco.editor.setTheme('vs');
  6837. }
  6838. // 移除旧的事件监听器
  6839. const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
  6840. window.matchMedia('(prefers-color-scheme: dark)');
  6841. }
  6842. }
  6843. // 更新配置信息
  6844. OJBetter.translation.choice = settings.translation;
  6845. OJBetter.translation.comment.choice = settings.commentTranslationChoice;
  6846. }
  6847. }
  6848. }
  6849. OJB_closeAndRemoveModal(settingMenu);
  6850. $settingBtns.prop("disabled", false).removeClass("open");
  6851. });
  6852. });
  6853. };
  6854.  
  6855. /**
  6856. * 初始化html2markdown转换器
  6857. */
  6858. async function initHTML2MarkDown() {
  6859. OJBetter.common.turndownService = new TurndownService({ bulletListMarker: '-' });
  6860.  
  6861. // 保留原始
  6862. OJBetter.common.turndownService.keep(['del']);
  6863.  
  6864. OJBetter.common.turndownService.addRule('removeByClass', {
  6865. filter: function (node) {
  6866. return node.classList.contains('html2md-panel') ||
  6867. node.classList.contains('div-btn-copy') ||
  6868. node.classList.contains('btn-copy') ||
  6869. node.classList.contains('overlay') ||
  6870. node.classList.contains('monaco-editor') ||
  6871. node.nodeName === 'SCRIPT';
  6872. },
  6873. replacement: function () {
  6874. return '';
  6875. }
  6876. });
  6877.  
  6878. // inline math
  6879. OJBetter.common.turndownService.addRule('inline-math', {
  6880. filter: function (node, options) {
  6881. return node.tagName.toLowerCase() == "span" && node.className == "katex";
  6882. },
  6883. replacement: function (content, node) {
  6884. var latex = $(node).find('annotation').text();
  6885. latex = latex.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  6886. return "$" + latex + "$";
  6887. }
  6888. });
  6889.  
  6890. // block math
  6891. OJBetter.common.turndownService.addRule('block-math', {
  6892. filter: function (node, options) {
  6893. return node.tagName.toLowerCase() == "span" && node.className == "katex-display";
  6894. },
  6895. replacement: function (content, node) {
  6896. var latex = $(node).find('annotation').text();
  6897. latex = latex.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  6898. return "\n$$\n" + latex + "\n$$\n";
  6899. }
  6900. });
  6901.  
  6902. // pre
  6903. OJBetter.common.turndownService.addRule('pre', {
  6904. filter: function (node, options) {
  6905. return node.tagName.toLowerCase() == "pre";
  6906. },
  6907. replacement: function (content, node) {
  6908. if (!node.classList.contains('source-code-for-copy') && !node.classList.contains('prettyprint')) {
  6909. return "```\n" + content + "```\n";
  6910. } else {
  6911. return "";
  6912. }
  6913. }
  6914. });
  6915.  
  6916. // bordertable
  6917. OJBetter.common.turndownService.addRule('bordertable', {
  6918. filter: 'table',
  6919. replacement: function (content, node) {
  6920. if (node.classList.contains('table')) {
  6921. var output = [],
  6922. thead = '',
  6923. trs = node.querySelectorAll('tr');
  6924. if (trs.length > 0) {
  6925. var ths = trs[0].querySelectorAll('th, td');
  6926. if (ths.length > 0) {
  6927. thead = '| ' + Array.from(ths).map(th => OJBetter.common.turndownService.turndown(th.innerHTML.trim())).join(' | ') + ' |\n'
  6928. thead += '| ' + Array.from(ths).map(() => ' --- ').join('|') + ' |\n';
  6929. }
  6930. }
  6931. var rows = node.querySelectorAll('tr');
  6932. Array.from(rows).forEach(function (row, i) {
  6933. if (i > 0) {
  6934. var cells = row.querySelectorAll('td,th');
  6935. var trow = '| ' + Array.from(cells).map(cell => OJBetter.common.turndownService.turndown(cell.innerHTML.trim())).join(' | ') + ' |';
  6936. output.push(trow);
  6937. }
  6938. });
  6939. return thead + output.join('\n');
  6940. } else {
  6941. return content;
  6942. }
  6943. }
  6944. });
  6945. };
  6946.  
  6947. /**
  6948. * 任务队列
  6949. */
  6950. class TaskQueue {
  6951. constructor() {
  6952. this.taskQueues = {};
  6953. this.isProcessing = {}; // 处理状态
  6954. this.delays = {}; // 等待时间(毫秒)
  6955. }
  6956.  
  6957. getDelay(type) {
  6958. if (type === 'openai') {
  6959. return 0;
  6960. } else {
  6961. return OJBetter.translation.waitTime;
  6962. }
  6963. }
  6964.  
  6965. /**
  6966. * 添加任务
  6967. * @param {string} type 任务类型
  6968. * @param {function} fn 任务函数
  6969. * @param {boolean} isNonQueueTask 是否为非队列任务
  6970. */
  6971. addTask(type, fn, isNonQueueTask = false) {
  6972. if (!this.taskQueues[type]) {
  6973. this.taskQueues[type] = [];
  6974. }
  6975.  
  6976. if (isNonQueueTask) {
  6977. fn();
  6978. } else {
  6979. this.taskQueues[type].push(fn);
  6980.  
  6981. if (!this.isProcessing[type]) {
  6982. this.processQueue(type);
  6983. }
  6984. }
  6985. }
  6986.  
  6987. async processQueue(type) {
  6988. this.isProcessing[type] = true;
  6989.  
  6990. while (this.taskQueues[type].length > 0) {
  6991. const task = this.taskQueues[type].shift();
  6992. await task();
  6993.  
  6994. if (this.taskQueues[type].length > 0) {
  6995. await this.wait(this.getDelay(type));
  6996. }
  6997. }
  6998.  
  6999. this.isProcessing[type] = false;
  7000. }
  7001.  
  7002. wait(delay) {
  7003. return new Promise(resolve => {
  7004. setTimeout(resolve, delay);
  7005. });
  7006. }
  7007. }
  7008.  
  7009. /**
  7010. * 检测文本是否可能为代码片段
  7011. * @param {string} text 待检测的文本
  7012. * @returns {boolean} 是否可能为代码片段
  7013. */
  7014. /**
  7015. * 检测为空文本
  7016. * @param {string} text 待检测的文本
  7017. * @returns {boolean} 是否为空文本
  7018. */
  7019. const isEmptyText = text => text.trim() === '';
  7020.  
  7021. /**
  7022. * 加载按钮相关函数
  7023. */
  7024. async function initButtonFunc() {
  7025. // 鼠标悬浮时为目标元素区域添加一个覆盖层
  7026. $.fn.addHoverOverlay = function (target) {
  7027. let position = $(target).css('position');
  7028. let display = $(target).css('display');
  7029.  
  7030. this.hover(() => {
  7031. $(target)
  7032. .addClass('overlay')
  7033. .css('position', 'relative');
  7034. if (display == "inline" || display == "contents") {
  7035. $(target).css('display', 'block');
  7036. }
  7037. }, () => {
  7038. $(target)
  7039. .removeClass('overlay')
  7040. .css('position', position);
  7041. if (display == "inline" || display == "contents") {
  7042. $(target).css('display', display);
  7043. }
  7044. })
  7045. }
  7046.  
  7047. /**
  7048. * 为按钮设置图标
  7049. * @param {string} icon 图标
  7050. * @returns {JQuery<HTMLElement>} 按钮
  7051. */
  7052. $.fn.setButtonIcon = function (icon) {
  7053. let i = this.find("i");
  7054. if (i.length != 0 && i.hasClass("iconfont")) {
  7055. i.html(icon);
  7056. } else {
  7057. i = OJB_safeCreateJQElement(`<i>${icon}</i>`);
  7058. this.prepend(i);
  7059. }
  7060. return this;
  7061. }
  7062.  
  7063. /**
  7064. * 设置按钮为加载等待状态
  7065. */
  7066. $.fn.setButtonLoading = function () {
  7067. this.addClass("loading");
  7068. this.prop("disabled", true);
  7069. return this;
  7070. }
  7071.  
  7072. /**
  7073. * 解除按钮的加载等待状态
  7074. */
  7075. $.fn.setButtonLoaded = function () {
  7076. this.removeClass("loading");
  7077. this.prop("disabled", false);
  7078. return this;
  7079. }
  7080.  
  7081. /**
  7082. * 为按钮设置popover提示文本
  7083. * @param {string} text 文本
  7084. * @returns {JQuery<HTMLElement>} 按钮
  7085. */
  7086. $.fn.setButtonPopover = function (text) {
  7087. // find if has popover_content class element
  7088. let popover_content = this.find(".popover_content");
  7089. if (popover_content.length != 0) {
  7090. popover_content.text(text);
  7091. } else {
  7092. popover_content = OJB_safeCreateJQElement(`<span class="popover_content">${text}</span>`);
  7093. this.append(popover_content);
  7094. }
  7095. return this;
  7096. }
  7097.  
  7098. /**
  7099. * 获取MarkDown
  7100. * @returns {string} MarkDown
  7101. */
  7102. $.fn.getMarkdown = function () {
  7103. const markdown = this.data('markdown');
  7104. if (markdown === undefined) {
  7105. const htmlContent = this.html();
  7106. const newMarkdown = OJBetter.common.turndownService.turndown(htmlContent);
  7107. this.data('markdown', newMarkdown);
  7108. return newMarkdown;
  7109. }
  7110. return markdown;
  7111. }
  7112.  
  7113. // 设置按钮状态
  7114. $.fn.setButtonState = function (state, popoverText = null, disabled = false) {
  7115. this.data('buttonState', state)
  7116. .prop('disabled', disabled)
  7117. .css('cursor', disabled ? 'not-allowed' : 'pointer')
  7118. .removeClass('running success enabled error loading redo');
  7119. if (popoverText) this.setButtonPopover(popoverText);
  7120.  
  7121. if (state !== 'initial') this.addClass(state);
  7122. return this;
  7123. };
  7124.  
  7125. // 为按钮添加鼠标悬浮重试
  7126. $.fn.setHoverRedo = function () {
  7127. this.hover(() => {
  7128. prevState = this.getButtonState();
  7129. if (prevState !== "normal" && prevState !== "running") {
  7130. this.setButtonState('redo');
  7131. }
  7132. }, () => {
  7133. const currentState = this.getButtonState();
  7134. if (prevState !== "normal" && ["normal", "redo"].includes(currentState)) {
  7135. this.setButtonState(prevState);
  7136. prevState = null;
  7137. }
  7138. });
  7139. };
  7140.  
  7141. // 获取按钮状态
  7142. $.fn.getButtonState = function () {
  7143. return this.data('buttonState') || 'normal';
  7144. };
  7145.  
  7146. // 设置翻译按钮状态
  7147. $.fn.setTransButtonState = function (state, text = null) {
  7148. const popoverText = text || i18next.t(`trans.${state}`, { ns: 'button' });
  7149. const disabled = state === 'running' || state === 'loading';
  7150. this.setButtonState(state, popoverText, disabled);
  7151. return this;
  7152. };
  7153.  
  7154. // 存翻译结果
  7155. $.fn.pushResultToTransButton = function (result) {
  7156. let resultStack = this.data('resultStack');
  7157. if (!resultStack) resultStack = [];
  7158. resultStack.push(result);
  7159. this.data('resultStack', resultStack);
  7160. }
  7161.  
  7162. // 获取翻译结果
  7163. $.fn.getResultFromTransButton = function () {
  7164. return this.data('resultStack');
  7165. }
  7166.  
  7167. // 标记为不自动翻译
  7168. $.fn.setNotAutoTranslate = function () {
  7169. this.data('notAutoTranslate', true);
  7170. }
  7171.  
  7172. // 获取是否为不自动翻译
  7173. $.fn.getNotAutoTranslate = function () {
  7174. return this.data('notAutoTranslate');
  7175. }
  7176.  
  7177. // 判断是否已经翻译
  7178. $.fn.IsTranslated = function () {
  7179. if (this.hasAttr('translated')) {
  7180. return true;
  7181. } else {
  7182. return false;
  7183. }
  7184. }
  7185.  
  7186. // 判断是否为评论区按钮
  7187. $.fn.IsCommentButton = function () {
  7188. let isCommentButton = this.data('isCommentButton');
  7189. if (isCommentButton == undefined) {
  7190. this.parents('.comments').length > 0 ? isCommentButton = true : isCommentButton = false;
  7191. this.data('isCommentButton', isCommentButton);
  7192. }
  7193. return isCommentButton;
  7194. }
  7195.  
  7196. // 按钮点击效果
  7197. $(document).on('mousedown', '.ojb_btn', function () {
  7198. $(this).addClass('active').on('animationend', () => $(this).removeClass('active'));
  7199. });
  7200. }
  7201.  
  7202. /**
  7203. * 添加题目markdown转换/复制/翻译按钮面板
  7204. * @param {HTMLElement} element 需要添加按钮面板的元素
  7205. * @param {string} suffix 按钮面板id后缀
  7206. * @param {string} type 按钮面板添加位置
  7207. * @param {boolean} is_simple 是否是简单模式
  7208. * @returns {object} 返回按钮面板元素
  7209. */
  7210. function addButtonPanel(element, suffix, type, is_simple = false) {
  7211. let text;
  7212. if (OJBetter.translation.comment.transMode == "1") text = i18next.t('trans.segment', { ns: 'button' });
  7213. else if (OJBetter.translation.comment.transMode == "2") text = i18next.t('trans.select', { ns: 'button' });
  7214. else text = i18next.t('trans.normal', { ns: 'button' });
  7215.  
  7216. let panel = OJB_safeCreateJQElement(`<div class='html2md-panel input-output-copier ${is_simple ? 'is_simple' : ''}'></div>`);
  7217. let viewButton = OJB_safeCreateJQElement(`
  7218. <button class='ojb_btn ojb_btn_popover top' id='html2md-view${suffix}'>
  7219. <i class="iconfont">&#xe7e5;</i>
  7220. <span class="popover_content">${i18next.t('md.normal', { ns: 'button' })}</span>
  7221. </button>`);
  7222. let copyButton = OJB_safeCreateJQElement(`
  7223. <button class='ojb_btn ojb_btn_popover top' id='html2md-cb${suffix}'>
  7224. <i class="iconfont">&#xe608;</i>
  7225. <span class="popover_content">${i18next.t('copy.normal', { ns: 'button' })}</span>
  7226. </button>`);
  7227. let translateButton = OJB_safeCreateJQElement(`
  7228. <button class='ojb_btn translateButton ojb_btn_popover top' id='translateButton${suffix}'>
  7229. <i class="iconfont">&#xe6be;</i>
  7230. <span class="popover_content">${text}</span>
  7231. </button>`);
  7232. if (!is_simple) panel.append(viewButton);
  7233. if (!is_simple) panel.append(copyButton);
  7234. panel.append(translateButton);
  7235. if (type === "this_level") {
  7236. $(element).before(panel);
  7237. } else if (type === "child_level") {
  7238. $(element).prepend(panel);
  7239. }
  7240.  
  7241. return {
  7242. panel: panel,
  7243. viewButton: viewButton,
  7244. copyButton: copyButton,
  7245. translateButton: translateButton
  7246. }
  7247. }
  7248.  
  7249. /**
  7250. * 添加MD视图按钮
  7251. * @param {JQuery<HTMLElement>} button 按钮
  7252. * @param {JQuery<HTMLElement>} element 目标元素
  7253. * @param {string} suffix id后缀
  7254. * @param {string} type 类型
  7255. * @returns {void}
  7256. */
  7257. async function addButtonWithHTML2MD(button, element, suffix, type) {
  7258. /**
  7259. * 改变按钮状态
  7260. * @param {string} state 状态
  7261. */
  7262. const changeButtonState = (state) => {
  7263. if (state == "loading") {
  7264. button.setButtonLoading();
  7265. button.setButtonPopover(i18next.t('state.waitMathJax', { ns: 'button' }));
  7266. } else if (state == "loaded") {
  7267. button.setButtonLoaded();
  7268. button.setButtonPopover(i18next.t('md.normal', { ns: 'button' }));
  7269. } else if (state == "normal") {
  7270. button.removeClass("enabled");
  7271. button.setButtonPopover(i18next.t('md.normal', { ns: 'button' }));
  7272. } else if (state == "mdView") {
  7273. button.addClass("enabled");
  7274. button.setButtonPopover(i18next.t('md.reduction', { ns: 'button' }));
  7275. } else if (state == "disabled") {
  7276. button.prop("disabled", true);
  7277. button.setButtonPopover(i18next.t('md.disabled', { ns: 'button' }));
  7278. }
  7279. }
  7280.  
  7281. /**
  7282. * 存放目标元素的 JQueryObject
  7283. */
  7284. const target = (() => {
  7285. if (type = "child_level") {
  7286. return $(element).children(':not(.html2md-panel)');
  7287. } else {
  7288. return $(element);
  7289. }
  7290. })();
  7291.  
  7292. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7293. changeButtonState("disabled");
  7294. return;
  7295. } else {
  7296. changeButtonState("loading");
  7297. await waitForMathJaxIdle();
  7298. changeButtonState("loaded");
  7299. }
  7300.  
  7301. button.click(OJB_debounce(function () {
  7302. /**
  7303. * 检查是否是MarkDown视图
  7304. * @returns {boolean} 是否是MarkDown视图
  7305. */
  7306. function checkViewmd() {
  7307. if ($(element).attr("viewmd") === "true") {
  7308. return true;
  7309. } else {
  7310. return false;
  7311. }
  7312. }
  7313.  
  7314. /**
  7315. * 设置是否是MarkDown视图
  7316. * @param {boolean} value 是否是MarkDown视图
  7317. * @returns {void}
  7318. */
  7319. function setViewmd(value) {
  7320. $(element).attr("viewmd", value);
  7321. if (value) {
  7322. changeButtonState("mdView");
  7323. } else {
  7324. changeButtonState("normal");
  7325. }
  7326. }
  7327.  
  7328. if (checkViewmd()) {
  7329. setViewmd(false);
  7330. target.last().next(".mdViewContent").remove();
  7331. target.show();
  7332. } else {
  7333. setViewmd(true);
  7334. const markdown = $(element).getMarkdown();
  7335. const mdViewContent = OJB_safeCreateJQElement(`<span class="mdViewContent" style="width:auto; height:auto;">${markdown}</span>`);
  7336. target.last().after(mdViewContent);
  7337. target.hide();
  7338. }
  7339. }));
  7340.  
  7341. if (OJBetter.preference.hoverTargetAreaDisplay && !OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  7342. button.addHoverOverlay($(element));
  7343. }
  7344. }
  7345.  
  7346. /**
  7347. * 添加复制按钮
  7348. * @param {JQuery<HTMLElement>} button 按钮
  7349. * @param {JQuery<HTMLElement>} element 目标元素
  7350. * @param {string} suffix 后缀
  7351. * @param {string} type 类型
  7352. */
  7353. async function addButtonWithCopy(button, element, suffix, type) {
  7354. /**
  7355. * 改变按钮状态
  7356. * @param {string} state 状态
  7357. */
  7358. function changeButtonState(state) {
  7359. if (state == "loading") {
  7360. button.setButtonLoading();
  7361. button.setButtonPopover(i18next.t('state.waitMathJax', { ns: 'button' }));
  7362. } else if (state == "loaded") {
  7363. button.setButtonLoaded();
  7364. button.setButtonPopover(i18next.t('copy.normal', { ns: 'button' }));
  7365. } else if (state == "normal") {
  7366. button.setButtonPopover(i18next.t('copy.normal', { ns: 'button' }));
  7367. } else if (state == "copied") {
  7368. button.setButtonPopover(i18next.t('copy.copied', { ns: 'button' }));
  7369. } else if (state == "disabled") {
  7370. button.prop("disabled", true);
  7371. button.setButtonPopover(i18next.t('copy.disabled', { ns: 'button' }));
  7372. }
  7373. }
  7374.  
  7375. // 等待MathJax队列完成
  7376. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7377. changeButtonState("disabled");
  7378. return;
  7379. } else {
  7380. changeButtonState("loading");
  7381. await waitForMathJaxIdle();
  7382. changeButtonState("loaded");
  7383. }
  7384.  
  7385. button.click(OJB_debounce(function () {
  7386. var target = $(element).get(0);
  7387.  
  7388. var markdown = $(element).getMarkdown();
  7389.  
  7390. GM_setClipboard(markdown);
  7391.  
  7392. $(this).addClass("success");
  7393. changeButtonState("copied");
  7394.  
  7395.  
  7396. // 更新复制按钮文本
  7397. setTimeout(() => {
  7398. $(this).removeClass("success");
  7399. changeButtonState("normal")
  7400. }, 2000);
  7401. }));
  7402.  
  7403. if (OJBetter.preference.hoverTargetAreaDisplay && !OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  7404. button.addHoverOverlay($(element));
  7405. }
  7406. }
  7407.  
  7408. /**
  7409. * 添加翻译按钮
  7410. * @param {JQuery<HTMLElement>} button 按钮
  7411. * @param {JQuery<HTMLElement>} element 目标元素
  7412. * @param {string} suffix 后缀
  7413. * @param {string} type 类型
  7414. * @param {boolean} is_comment 是否是评论
  7415. */
  7416. async function addButtonWithTranslation(button, element, suffix, type, is_comment = false) {
  7417. /**
  7418. * 添加可指定翻译服务的方法调用
  7419. * @param {string} translation 翻译服务
  7420. */
  7421. button.data("translatedItBy", function (translation) {
  7422. button.setTransButtonState('running', i18next.t('trans.wait', { ns: 'button' }));
  7423. OJBetter.common.taskQueue.addTask(translation, () => transTask(button, element, type, is_comment, translation), translation == 'openai');
  7424. });
  7425.  
  7426. // 等待MathJax队列完成
  7427. button.setButtonLoading();
  7428. await waitForMathJaxIdle();
  7429. button.setButtonLoaded();
  7430.  
  7431. // 标记目标文本区域不自动翻译
  7432. {
  7433. let text;
  7434. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7435. text = $(element).html();
  7436. } else {
  7437. text = $(element).getMarkdown();
  7438. }
  7439. let length = text.length;
  7440. if (length > OJBetter.translation.auto.shortTextLength || isEmptyText(text) || $(element).find('.spoiler').length > 0) {
  7441. button.setNotAutoTranslate();
  7442. }
  7443. // button.after(`<span>${length}</span>`); // 显示字符数
  7444. }
  7445.  
  7446. button.click(OJB_debounce(async function () {
  7447. // 重新翻译
  7448. let resultStack = $(this).getResultFromTransButton();
  7449. if (resultStack) {
  7450. let pElements = $(element).find("p.block_selected:not(li p), li.block_selected");
  7451. for (let item of resultStack) {
  7452. if (OJBetter.translation.retransAction == "0") {
  7453. // 选段翻译不直接移除旧结果
  7454. if (OJBetter.translation.comment.transMode == "2") {
  7455. // 只移除即将要翻译的段的结果
  7456. if (pElements.is(item.translateDiv.getDiv().prev())) {
  7457. item.translateDiv.close();
  7458. }
  7459. } else {
  7460. item.translateDiv.close();
  7461. $($(element)).find(".translate-problem-statement, .translate-problem-statement-panel").remove();
  7462. }
  7463. } else {
  7464. item.translateDiv.fold();
  7465. }
  7466. }
  7467. }
  7468.  
  7469. // 翻译
  7470. button.setTransButtonState('running', i18next.t('trans.wait', { ns: 'button' }));
  7471. OJBetter.common.taskQueue.addTask(OJBetter.translation.choice, () => transTask(button, element, type, is_comment), OJBetter.translation.choice == 'openai');
  7472. }));
  7473.  
  7474. // 重新翻译提示
  7475. let prevState;
  7476. button.hover(() => {
  7477. prevState = button.getButtonState();
  7478. if (prevState !== "normal" && prevState !== "running") {
  7479. button.setTransButtonState('redo');
  7480. }
  7481. }, () => {
  7482. const currentState = button.getButtonState();
  7483. if (prevState !== "normal" && ["normal", "redo"].includes(currentState)) {
  7484. button.setTransButtonState(prevState);
  7485. prevState = null;
  7486. }
  7487. });
  7488.  
  7489. // 目标区域指示
  7490. if (OJBetter.preference.hoverTargetAreaDisplay) {
  7491. button.addHoverOverlay($(element));
  7492. }
  7493.  
  7494. // 翻译右键切换菜单
  7495. $(document).on('contextmenu', '#translateButton' + suffix, function (e) {
  7496. e.preventDefault();
  7497.  
  7498. // 是否为评论的翻译
  7499. let is_comment = button.IsCommentButton();
  7500.  
  7501. // 移除旧的
  7502. if (!$(e.target).closest('.OJBetter_contextmenu').length) {
  7503. $('.OJBetter_contextmenu').remove();
  7504. }
  7505.  
  7506. var menu = $('<div class="OJBetter_contextmenu"></div>');
  7507. var translations = [
  7508. { value: 'deepl', name: i18next.t('translation.options.services.deepl', { ns: 'settings' }) },
  7509. { value: 'iflyrec', name: i18next.t('translation.options.services.iflyrec', { ns: 'settings' }) },
  7510. { value: 'youdao', name: i18next.t('translation.options.services.youdao', { ns: 'settings' }) },
  7511. { value: 'google', name: i18next.t('translation.options.services.google', { ns: 'settings' }) },
  7512. { value: 'caiyun', name: i18next.t('translation.options.services.caiyun', { ns: 'settings' }) },
  7513. { value: 'openai', name: i18next.t('translation.options.services.openai.name', { ns: 'settings' }) }
  7514. ];
  7515.  
  7516. // Function to check if the service supports the target language
  7517. function supportsTargetLanguage(service, targetLang) {
  7518. return OJBetter.supportList.translationSupport[service] && OJBetter.supportList.translationSupport[service][targetLang] !== undefined;
  7519. }
  7520.  
  7521. if (is_comment) {
  7522. var label = OJB_safeCreateJQElement(`<label><input type="radio" name="translation" value="0">
  7523. <span class="OJBetter_contextmenu_label_text">
  7524. ${i18next.t('translation.preference.comment_translation_choice.services.follow', { ns: 'settings' })}
  7525. </span></label>`);
  7526. menu.append(label);
  7527. }
  7528. translations.forEach(function (translation) {
  7529. if (supportsTargetLanguage(translation.value, OJBetter.translation.targetLang)) {
  7530. var label = OJB_safeCreateJQElement(`<label><input type="radio" name="translation" value="${translation.value}">
  7531. <span class="OJBetter_contextmenu_label_text">${translation.name}</span></label>`);
  7532. menu.append(label);
  7533. }
  7534. });
  7535.  
  7536. // 初始化
  7537. if (is_comment) {
  7538. menu.find(`input[name="translation"][value="${OJBetter.translation.comment.choice}"]`).prop('checked', true);
  7539. } else {
  7540. menu.find(`input[name="translation"][value="${OJBetter.translation.choice}"]`).prop('checked', true);
  7541. }
  7542. menu.css({
  7543. top: e.pageY + 'px',
  7544. left: e.pageX + 'px'
  7545. }).appendTo('body');
  7546.  
  7547. $(document).one('change', 'input[name="translation"]', function () {
  7548. if (is_comment) {
  7549. OJBetter.translation.comment.choice = $('input[name="translation"]:checked').val();
  7550. GM_setValue("commentTranslationChoice", OJBetter.translation.comment.choice);
  7551. } else {
  7552. OJBetter.translation.choice = $('input[name="translation"]:checked').val();
  7553. GM_setValue("translation", OJBetter.translation.choice);
  7554. }
  7555. $('.OJBetter_contextmenu').remove();
  7556. });
  7557.  
  7558. // 点击区域外关闭菜单
  7559. function handleClick(event) {
  7560. if (!$(event.target).closest('.OJBetter_contextmenu').length) {
  7561. $('.OJBetter_contextmenu').remove();
  7562. $(document).off('change', 'input[name="translation"]');
  7563. } else {
  7564. $(document).one('click', handleClick);
  7565. }
  7566. }
  7567. $(document).one('click', handleClick);
  7568. });
  7569. }
  7570.  
  7571. /**
  7572. * 创建翻译任务
  7573. * @param {JQuery<HTMLElement>} button 按钮
  7574. * @param {HTMLElement} element 目标元素
  7575. * @param {string} type 类型
  7576. * @param {boolean} is_comment 是否是评论
  7577. * @param {string} overrideTrans 覆盖全局翻译服务设定
  7578. */
  7579. async function transTask(button, element, type, is_comment, overrideTrans) {
  7580. /** @type {HTMLElement} 目标元素 */
  7581. let target;
  7582. /**
  7583. * 错误计数数据结构
  7584. * @typedef {Object} count
  7585. * @property {number} errerNum 错误数量
  7586. * @property {number} skipNum 跳过数量
  7587. */
  7588. const count = {
  7589. errerNum: 0,
  7590. skipNum: 0
  7591. };
  7592. if (OJBetter.translation.comment.transMode == "1") {
  7593. // 分段翻译
  7594. let pElements = $(element).find("p:not(li p), li, .OJBetter_acmsguru");
  7595. for (let i = 0; i < pElements.length; i++) {
  7596. target = $(pElements[i]).eq(0).clone();
  7597. element_node = pElements[i];
  7598. await process(button, target, element_node, type, is_comment, count, overrideTrans);
  7599. }
  7600. } else if (OJBetter.translation.comment.transMode == "2") {
  7601. // 选段翻译
  7602. let pElements = $(element).find("p.block_selected:not(li p), li.block_selected, .OJBetter_acmsguru");
  7603. for (let i = 0; i < pElements.length; i++) {
  7604. target = $(pElements[i]).eq(0).clone();
  7605. element_node = pElements[i];
  7606. await process(button, target, element_node, type, is_comment, count, overrideTrans);
  7607. }
  7608. $(element).find("p.block_selected:not(li p), li.block_selected").removeClass('block_selected');
  7609. } else {
  7610. // 普通翻译
  7611. target = $(element).eq(0).clone();
  7612. if (type === "child_level") $(target).children(':first').remove();
  7613. element_node = $($(element)).get(0);
  7614. await process(button, target, element_node, type, is_comment, count, overrideTrans);
  7615. }
  7616.  
  7617. // 翻译完成
  7618. if (!count.errerNum && !count.skipNum) {
  7619. button.setTransButtonState('success');
  7620. }
  7621. }
  7622.  
  7623. /**
  7624. * 翻译处理
  7625. * @param {JQuery<HTMLElement>} button 按钮
  7626. * @param {HTMLElement} target 目标元素
  7627. * @param {HTMLElement} element_node 目标节点
  7628. * @param {string} type 类型
  7629. * @param {boolean} is_comment 是否是评论
  7630. * @param {string} overrideTrans 覆盖全局翻译服务设定
  7631. */
  7632. async function process(button, target, element_node, type, is_comment, count, overrideTrans) {
  7633. if (type === "child_level") {
  7634. let div = $("<div>");
  7635. $(element_node).append(div);
  7636. element_node = div.get(0);
  7637. }
  7638.  
  7639. //是否跳过折叠块
  7640. if ($(target).find('.spoiler').length > 0) {
  7641. const shouldSkip = await OJB_createDialog(
  7642. i18next.t('skipFold.title', { ns: 'dialog' }),
  7643. i18next.t('skipFold.content', { ns: 'dialog' }),
  7644. [
  7645. i18next.t('skipFold.buttons.0', { ns: 'dialog' }),
  7646. i18next.t('skipFold.buttons.1', { ns: 'dialog' })
  7647. ],
  7648. true
  7649. ); //跳过折叠块确认
  7650. if (shouldSkip) {
  7651. $(target).find('.spoiler').remove();
  7652. } else {
  7653. $(target).find('.html2md-panel').remove();
  7654. }
  7655. }
  7656.  
  7657. // 等待并获取结果
  7658. button.setTransButtonState('running');
  7659. const result = await blockProcessing(button, target, element_node, is_comment, overrideTrans);
  7660. button.pushResultToTransButton(result);
  7661.  
  7662. if (result.status == "error") count.errerNum += 1;
  7663. else if (result.status == "skip") count.skipNum += 1;
  7664. $(target).remove();
  7665. }
  7666.  
  7667. /**
  7668. * 块处理
  7669. * @param {JQuery<HTMLElement>} button
  7670. * @param {HTMLElement} target 目标元素
  7671. * @param {HTMLElement} element_node 目标节点
  7672. * @param {boolean} is_comment 是否是评论
  7673. * @param {string} overrideTrans 覆盖全局翻译服务设定
  7674. * @returns {TranslateResult} 翻译结果对象
  7675. */
  7676. async function blockProcessing(button, target, element_node, is_comment, overrideTrans) {
  7677. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7678. target.markdown = $(target).html();
  7679. } else if (!target.markdown) {
  7680. target.markdown = OJBetter.common.turndownService.turndown($(target).html());
  7681. }
  7682.  
  7683. const result = await translateProblemStatement(target.markdown, element_node, is_comment, overrideTrans);
  7684. if (result.status == "skip") {
  7685. button.setTransButtonState('error', i18next.t('trans.tooLong', { ns: 'button' }));
  7686. result.translateDiv.close();
  7687. } else if (result.status == "error" || !result.rawData.done) {
  7688. result.translateDiv.setError();
  7689. result.translateDiv.setRawData(result.rawData);
  7690. result.translateDiv.showDebugButton();
  7691. button.setTransButtonState('error', i18next.t('trans.error', { ns: 'button' }));
  7692. $(target).remove();
  7693. }
  7694. return result;
  7695. }
  7696.  
  7697. /**
  7698. * 选段翻译支持
  7699. */
  7700. async function multiChoiceTranslation() {
  7701. GM_addStyle(`
  7702. .topic .content #task-statement {
  7703. overflow: initial;
  7704. }
  7705. `);
  7706.  
  7707. $(document).on('click', 'p, li:not(:has(.comment)), .OJBetter_acmsguru', function (e) {
  7708. let $this = $(this);
  7709. e.stopPropagation();
  7710. if ($this.hasClass('block_selected')) {
  7711. $this.removeClass('block_selected');
  7712. // 移除对应的按钮
  7713. $('.OJBetter_MiniTranslateButton').remove("#translateButton_selected_" + $this.attr('OJBetter_p_id'));
  7714. } else {
  7715. let id = OJB_getRandomNumber(8);
  7716. $this.attr('OJBetter_p_id', id);
  7717. $this.addClass('block_selected');
  7718. // 添加按钮
  7719. let menu = OJB_safeCreateJQElement(`<div class="OJBetter_MiniTranslateButton" id='translateButton_selected_${id}'>${translateIcon}</div>`)
  7720. .css({
  7721. left: $($this).outerWidth(true) + $($this).position().left + 10 + 'px',
  7722. });
  7723. $this.before(menu);
  7724.  
  7725. $("#translateButton_selected_" + id).click(async function () {
  7726. // 处理旧的结果
  7727. if ($this.attr('translated')) {
  7728. let result = $this.data("resultData");
  7729. if (OJBetter.translation.retransAction == "0") {
  7730. result.translateDiv.close();
  7731. } else {
  7732. result.translateDiv.fold();
  7733. }
  7734. }
  7735. // 翻译
  7736. let target = $this.eq(0).clone();
  7737. let result = await blockProcessing(OJBetter.translation.choice, target, $this.eq(0), $("#translateButton_selected_" + id), false);
  7738. $this.data("resultData", result);
  7739. $this.removeClass('block_selected');
  7740. // 移除对应的按钮
  7741. $('.OJBetter_MiniTranslateButton').remove("#translateButton_selected_" + id);
  7742. $this.attr('translated', '1'); // 标记已翻译
  7743. });
  7744. }
  7745. });
  7746. }
  7747.  
  7748. /**
  7749. * 添加MD/复制/翻译按钮
  7750. */
  7751. async function addConversionButton() {
  7752. // TODO 7
  7753. let promises = []; // 用于收集所有的 Promise
  7754.  
  7755. // 基本添加
  7756. if (!OJBetter.typeOfPage.is_homepage) {
  7757. $('section').each((i, e) => {
  7758. let id = "_" + OJB_getRandomNumber(8);
  7759. let panel = addButtonPanel(e, id, "this_level");
  7760. promises.push(addButtonWithHTML2MD(panel.viewButton, e, id, "this_level"));
  7761. promises.push(addButtonWithCopy(panel.copyButton, e, id, "this_level"));
  7762. promises.push(addButtonWithTranslation(panel.translateButton, e, id, "this_level"));
  7763. });
  7764. }
  7765.  
  7766. // 添加按钮到题解部分
  7767. if (OJBetter.typeOfPage.isEditorial) {
  7768. let contestNavTabs = $("#contest-nav-tabs");
  7769. let nextElement = contestNavTabs.next();
  7770. let id = "_editorial_1_" + OJB_getRandomNumber(8);
  7771. let panel = addButtonPanel(nextElement, id, "this_level");
  7772. panel.panel.css('width', '100%'); // 加个样式,不然不显示
  7773. promises.push(addButtonWithHTML2MD(panel.viewButton, nextElement, id, "this_level"));
  7774. promises.push(addButtonWithCopy(panel.copyButton, nextElement, id, "this_level"));
  7775. promises.push(addButtonWithTranslation(panel.translateButton, nextElement, id, "this_level"));
  7776. }
  7777.  
  7778. // 添加按钮到折叠块部分
  7779. $('details').each((i, e) => {
  7780. // 自定义测试样例折叠块不添加
  7781. if ($(e).attr('id') !== "customTestBlock") {
  7782. let id = "_details_" + OJB_getRandomNumber(8);
  7783. let panel = addButtonPanel(e, id, "child_level");
  7784. promises.push(addButtonWithHTML2MD(panel.viewButton, e, id, "child_level"));
  7785. promises.push(addButtonWithCopy(panel.copyButton, e, id, "child_level"));
  7786. promises.push(addButtonWithTranslation(panel.translateButton, e, id, "child_level"));
  7787. }
  7788. });
  7789.  
  7790. // 添加到contest-statement部分
  7791. $('#contest-statement').each((i, e) => {
  7792. let id = "_contest-statement_" + OJB_getRandomNumber(8);
  7793. let panel = addButtonPanel(e, id, "this_level");
  7794. promises.push(addButtonWithHTML2MD(panel.viewButton, e, id, "this_level"));
  7795. promises.push(addButtonWithCopy(panel.copyButton, e, id, "this_level"));
  7796. promises.push(addButtonWithTranslation(panel.translateButton, e, id, "this_level"));
  7797. });
  7798.  
  7799. // 添加到blog-post部分
  7800. $('.blog-post').each((i, e) => {
  7801. let id = "_blog-post_" + OJB_getRandomNumber(8);
  7802. let panel = addButtonPanel(e, id, "this_level");
  7803. promises.push(addButtonWithHTML2MD(panel.viewButton, e, id, "this_level"));
  7804. promises.push(addButtonWithCopy(panel.copyButton, e, id, "this_level"));
  7805. promises.push(addButtonWithTranslation(panel.translateButton, e, id, "this_level"));
  7806. });
  7807.  
  7808. return Promise.all(promises).catch(error => {
  7809. console.error("One or more of the Add Button operations failed: ", error);
  7810. });
  7811. };
  7812.  
  7813. /**
  7814. * 等待LaTeX渲染队列全部完成
  7815. * @returns {Promise} 完成渲染
  7816. */
  7817. function waitForMathJaxIdle() {
  7818. return true;
  7819. // return new Promise((resolve, reject) => {
  7820. // // 检查MathJax对象是否存在
  7821. // const checkMathJaxExists = () => {
  7822. // if (typeof MathJax === 'undefined') {
  7823. // // 如果MathJax不存在,稍后再次检查
  7824. // OJB_delay(100).then(checkMathJaxExists);
  7825. // } else {
  7826. // // MathJax存在,开始监视渲染队列
  7827. // startMonitoringQueue();
  7828. // }
  7829. // };
  7830.  
  7831. // // 开始监视MathJax渲染队列
  7832. // const startMonitoringQueue = () => {
  7833. // const intervalId = setInterval(() => {
  7834. // const queue = MathJax.Hub.queue;
  7835. // if (queue.pending === 0 && queue.running === 0) {
  7836. // clearInterval(intervalId);
  7837. // resolve();
  7838. // }
  7839. // }, 100);
  7840. // };
  7841.  
  7842. // // 开始检查MathJax对象
  7843. // checkMathJaxExists();
  7844. // });
  7845. }
  7846.  
  7847. /**
  7848. * 翻译结果面板
  7849. */
  7850. class TranslateDiv {
  7851. /**
  7852. * 构造函数
  7853. * @param {string} id 指定翻译框的id
  7854. */
  7855. constructor(id) {
  7856. this.id = id;
  7857. this.div = $('<div>').attr('id', id).addClass('translateDiv bounce-in');
  7858. if (!OJBetter.typeOfPage.is_completeProblemset) {
  7859. this.div.addClass('input-output-copier');
  7860. }
  7861. this.panelDiv = $('<div>').addClass('translate-problem-statement-panel');
  7862. this.div.append(this.panelDiv);
  7863.  
  7864. // 主要信息
  7865. this.mainDiv = $('<div>').addClass('translate-problem-statement');
  7866. this.span = $('<span>');
  7867. this.mainDiv.append(this.span);
  7868. this.div.append(this.mainDiv);
  7869. this.mainDivState = {
  7870. current: 'transHTML',
  7871. transHTML: '',
  7872. rawDataHTML: ''
  7873. };
  7874.  
  7875. // 顶栏信息
  7876. this.topText = $('<div>').addClass('topText');
  7877. this.panelDiv.append(this.topText);
  7878.  
  7879. // 右侧
  7880. this.rightDiv = $('<div>').css('display', 'flex');
  7881. this.panelDiv.append(this.rightDiv);
  7882. this.debugButton = OJB_safeCreateJQElement(`
  7883. <button class='ojb_btn ojb_btn_popover top'>
  7884. <i class="iconfont">&#xe641;</i>
  7885. <span class="popover_content">${i18next.t('rawData.normal', { ns: 'button' })}</span>
  7886. </button>`).hide();
  7887. this.rightDiv.append(this.debugButton);
  7888. this.queryBalanceButton = OJB_safeCreateJQElement(`
  7889. <button class='ojb_btn ojb_btn_popover top'>
  7890. <i class="iconfont">&#xe6ae;</i>
  7891. <span class="popover_content">${i18next.t('queryBalance.normal', { ns: 'button' })}</span>
  7892. </button>`).hide();
  7893. this.rightDiv.append(this.queryBalanceButton);
  7894. this.copyButton = OJB_safeCreateJQElement(`
  7895. <button class='ojb_btn ojb_btn_popover top'>
  7896. <i class="iconfont">&#xe608;</i>
  7897. <span class="popover_content">${i18next.t('copy.normal', { ns: 'button' })}</span>
  7898. </button>`);
  7899. this.rightDiv.append(this.copyButton);
  7900. this.upButton = OJB_safeCreateJQElement(`
  7901. <button class='ojb_btn ojb_btn_popover top'>
  7902. <i class="iconfont">&#xe601;</i>
  7903. <span class="popover_content">${i18next.t('fold.normal', { ns: 'button' })}</span>
  7904. </button>`);
  7905. this.rightDiv.append(this.upButton);
  7906. this.closeButton = OJB_safeCreateJQElement(`
  7907. <button class='ojb_btn ojb_btn_popover top'>
  7908. <i class="iconfont">&#xe614;</i>
  7909. <span class="popover_content">${i18next.t('close.normal', { ns: 'button' })}</span>
  7910. </button>`);
  7911. this.rightDiv.append(this.closeButton);
  7912. }
  7913.  
  7914. /**
  7915. * 获取翻译框
  7916. * @returns {JQuery<HTMLElement>} 返回翻译框
  7917. */
  7918. getDiv() {
  7919. return this.div;
  7920. }
  7921.  
  7922. /**
  7923. * 设置翻译框顶部的文本
  7924. * @param {string} text 翻译框顶部的文本
  7925. */
  7926. setTopText(text) {
  7927. this.div.attr("data-topText", text);
  7928. this.topText.text(text);
  7929. }
  7930.  
  7931. /**
  7932. * 获取翻译框顶部的文本
  7933. * @returns {string} 返回翻译框顶部的文本
  7934. */
  7935. getTopText() {
  7936. return this.topText.text();
  7937. }
  7938.  
  7939. /**
  7940. * 渲染一个元素内的LaTeX公式
  7941. * @param {*} element
  7942. */
  7943. renderLaTeX(element) {
  7944. const latexRenderOptions = {
  7945. delimiters: [
  7946. { left: "$$", right: "$$", display: true },
  7947. { left: "\$$", right: "\\$$", display: true },
  7948. { left: "$", right: "$", display: false },
  7949. { left: "\$$", right: "\\$$", display: false }
  7950. ]
  7951. };
  7952.  
  7953. if (typeof renderMathInElement === 'function') {
  7954. renderMathInElement(element, latexRenderOptions);
  7955. }
  7956. }
  7957.  
  7958. /**
  7959. * 更新翻译框内容
  7960. * @param {string} text 文本内容
  7961. * @param {boolean} is_escapeHTML 是否转义HTML标签,为true则HTML标签将作为普通文本处理,默认为true
  7962. * @param {boolean} is_renderLaTeX 是否渲染LaTeX,为true则会渲染LaTeX,默认为true
  7963. */
  7964. updateTranslateDiv(text, is_escapeHTML = true, is_renderLaTeX = true,) {
  7965. // 渲染MarkDown
  7966. let md = window.markdownit({
  7967. html: !is_escapeHTML,
  7968. });
  7969. if (!text) text = "";
  7970. let html = md.render(text);
  7971. this.mainDiv.html(html);
  7972.  
  7973. // 渲染Latex
  7974. if (is_renderLaTeX) {
  7975. // MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.mainDiv.get(0)]);
  7976. this.renderLaTeX(this.mainDiv.get(0));
  7977. }
  7978. // 渲染代码块中的公式 (AtCoder)
  7979. this.mainDiv.find('pre code').each((index, element) => {
  7980. const codeText = $(element).text();
  7981. const latexPattern = /\$\$([^]*?)\$\$|\$(\\\$|[^\$])*?\$/;
  7982. if (latexPattern.test(codeText)) {
  7983. this.renderLaTeX(element);
  7984. }
  7985. });
  7986. }
  7987.  
  7988. /**
  7989. * 关闭元素
  7990. */
  7991. close() {
  7992. this.closeButton.click();
  7993. }
  7994.  
  7995. /**
  7996. * 收起元素
  7997. */
  7998. fold() {
  7999. if (!this.upButton.hasClass("reverse")) {
  8000. this.upButton.click();
  8001. }
  8002. }
  8003.  
  8004. /**
  8005. * 注册收起按钮事件
  8006. */
  8007. registerUpButtonEvent() {
  8008. this.upButton.on("click", () => {
  8009. // 如果没有reverse类,说明是展开状态
  8010. if (!this.upButton.hasClass("reverse")) {
  8011. // 执行收起操作
  8012. this.upButton.addClass("reverse");
  8013. this.upButton.setButtonState('initial', i18next.t('fold.unfold', { ns: 'button' }));
  8014. OJB_toggleCollapseExpand(this.mainDiv.get(0));
  8015. } else {
  8016. // 执行展开操作
  8017. this.upButton.removeClass("reverse");
  8018. this.upButton.setButtonState('initial', i18next.t('fold.normal', { ns: 'button' }));
  8019. OJB_toggleCollapseExpand(this.mainDiv.get(0));
  8020. }
  8021. });
  8022. }
  8023.  
  8024. /**
  8025. * 注册关闭按钮事件
  8026. */
  8027. registerCloseButtonEvent() {
  8028. this.closeButton.on("click", () => {
  8029. $(this.div).remove();
  8030. $(this.panelDiv).remove();
  8031. if (OJBetter.typeOfPage.is_problem && OJBetter.translation.memory.enabled) {
  8032. OJBetter.translation.memory.ttTree.rmTransResultMap(this.id); // 移除ttTree中的数据
  8033. OJBetter.translation.memory.ttTree.refreshNode("#task-statement");
  8034. updateTransDBData(OJBetter.translation.memory.ttTree.getNodeData(), OJBetter.translation.memory.ttTree.getTransResultMap()); // 更新DB中的数据
  8035. }
  8036. });
  8037. }
  8038.  
  8039. /**
  8040. * 注册复制按钮事件
  8041. * @param {string} text 复制的文本
  8042. */
  8043. registerCopyButtonEvent(text) {
  8044. this.copyButton.on("click", () => {
  8045. GM_setClipboard(text);
  8046. this.copyButton.setButtonState('success', i18next.t('copy.copied', { ns: 'button' }));
  8047. // 复制提示
  8048. setTimeout(() => {
  8049. this.copyButton.setButtonState('initial', i18next.t('copy.normal', { ns: 'button' }));
  8050. }, 2000);
  8051. });
  8052. }
  8053.  
  8054. /**
  8055. * 禁用复制按钮
  8056. */
  8057. disableCopyButton() {
  8058. this.copyButton.css({ 'fill': '#ccc' });
  8059. this.copyButton.off("click");
  8060. }
  8061.  
  8062. /**
  8063. * 设置面板为error状态
  8064. */
  8065. setError() {
  8066. this.div.addClass('error');
  8067. this.panelDiv.addClass('error');
  8068. this.mainDiv.addClass('error');
  8069. }
  8070.  
  8071. /**
  8072. * 设置原始数据数据
  8073. * @param {Object} Object 原始数据
  8074. */
  8075. setRawData(Object) {
  8076. this.mainDivState.rawDataHTML = $("<pre>").text(JSON.stringify(Object, null, 4)).get(0);
  8077. if (this.mainDivState.current === 'rawDataHTML') {
  8078. this.renderMainDiv();
  8079. }
  8080. }
  8081.  
  8082. /**
  8083. * 切换结果面板与原始数据面板
  8084. */
  8085. switchMainDiv() {
  8086. // 在切换之前,保存当前内容的状态
  8087. this.mainDivState[this.mainDivState.current] = this.mainDiv.html();
  8088. // 切换当前状态
  8089. this.debugButton.setButtonState(this.mainDivState.current === 'transHTML' ? 'enabled' : 'initial');
  8090. this.mainDivState.current = this.mainDivState.current === 'transHTML' ? 'rawDataHTML' : 'transHTML';
  8091. // 渲染新的当前状态
  8092. this.renderMainDiv();
  8093. }
  8094.  
  8095. // 渲染当前内容到 mainDiv
  8096. renderMainDiv() {
  8097. requestAnimationFrame(() => {
  8098. this.mainDiv.html(this.mainDivState[this.mainDivState.current]);
  8099. });
  8100. }
  8101.  
  8102. /**
  8103. * 注册debug按钮事件
  8104. */
  8105. registerDebugButtonEvent() {
  8106. this.debugButton.on("click", () => {
  8107. this.switchMainDiv();
  8108. });
  8109. }
  8110.  
  8111. /**
  8112. * 显示debug按钮
  8113. */
  8114. showDebugButton() {
  8115. this.debugButton.show();
  8116. this.registerDebugButtonEvent();
  8117. }
  8118.  
  8119. /**
  8120. * 注册查询余额按钮事件
  8121. * @param {function} callback 查询回调函数
  8122. */
  8123. registerQueryBalanceButtonEvent(callback) {
  8124. this.queryBalanceButton.on("click", async () => {
  8125. this.queryBalanceButton.setButtonState('loading', i18next.t('queryBalance.loading', { ns: 'button' }));
  8126. try {
  8127. const balance = await callback();
  8128. this.queryBalanceButton.setButtonState('success', `${i18next.t('queryBalance.success', { ns: 'button' })} ${balance}`);
  8129. } catch (error) {
  8130. this.queryBalanceButton.setButtonState('error', `${i18next.t('queryBalance.error', { ns: 'button' })} ${error.message}`);
  8131. }
  8132. });
  8133. }
  8134.  
  8135. /**
  8136. * 显示余额查询按钮
  8137. * @param {string} server 服务名称
  8138. */
  8139. showQueryBalanceButton(server) {
  8140. if (server == 'deepl') {
  8141. const quotaConfig = OJBetter.deepl.config.quota;
  8142. if (quotaConfig.url && quotaConfig.surplus && quotaConfig.header) {
  8143. this.queryBalanceButton.show();
  8144. this.registerQueryBalanceButtonEvent(() => {
  8145. return queryServerBalance(OJBetter.deepl.config.quota);
  8146. });
  8147. }
  8148. } else if (server == 'openai') {
  8149. const quotaConfig = OJBetter.chatgpt.config.quota;
  8150. if (quotaConfig.url && quotaConfig.surplus && quotaConfig.header) {
  8151. this.queryBalanceButton.show();
  8152. this.registerQueryBalanceButtonEvent(() => {
  8153. return queryServerBalance(OJBetter.chatgpt.config.quota);
  8154. });
  8155. }
  8156. }
  8157. }
  8158. }
  8159.  
  8160. // 元素关系树
  8161. class ElementsTree {
  8162. constructor(elements) {
  8163. this.node = [];
  8164. this.transResultMap = {};
  8165. this.index = 0;
  8166. // this.tagNames = ["DIV", "P", "UL", "LI"]
  8167. this.tagNames = ["DIV", "P", "UL", "LI", "SECTION", "SPAN"]
  8168. this.init($(elements));
  8169. }
  8170.  
  8171. // Iterate through all elements, because there may be multiple ttypography
  8172. init(elements) {
  8173. elements.each((i, e) => {
  8174. this.node.push({}); // add one element
  8175. this.index = 0; // reset index
  8176. this.create(i, $(e));
  8177. });
  8178. }
  8179.  
  8180. // 刷新关系树
  8181. refreshNode(elements) {
  8182. this.node = [];
  8183. this.index = 0;
  8184. this.init($(elements));
  8185. }
  8186.  
  8187. // 创建节点间的关系树
  8188. create(i_, element) {
  8189. var prev = null;
  8190. var node = this.node[i_];
  8191. element.children().each((i, e) => {
  8192. // only add element with tagNames
  8193. if (this.tagNames.includes($(e).prop("tagName"))) {
  8194. prev = this.addNode(i_, prev, e);
  8195. }
  8196. // recursively child element
  8197. if ($(e).children().length > 0 && prev !== null) {
  8198. node[prev].firstChild = this.index;
  8199. this.create(i_, $(e));
  8200. }
  8201. });
  8202. }
  8203.  
  8204. // 向树中添加一个节点
  8205. addNode(i_, prev, e) {
  8206. let node = this.node[i_];
  8207. node[this.index] = {
  8208. prev: prev,
  8209. next: null,
  8210. firstChild: null,
  8211. type: $(e).prop("tagName"),
  8212. isTranslateDiv: $(e).hasClass("translateDiv"),
  8213. topText: $(e).attr("data-topText"),
  8214. id: $(e).attr("id"),
  8215. };
  8216.  
  8217. if (prev !== null) {
  8218. node[prev].next = this.index;
  8219. }
  8220.  
  8221. prev = this.index;
  8222.  
  8223. this.index++;
  8224. return prev;
  8225. }
  8226.  
  8227. getNodeData() {
  8228. return this.node;
  8229. }
  8230.  
  8231. setNodeData(node) {
  8232. this.node = node;
  8233. }
  8234.  
  8235. getTransResultMap() {
  8236. return this.transResultMap;
  8237. }
  8238.  
  8239. setTransResultMap(transResultMap) {
  8240. this.transResultMap = transResultMap;
  8241. }
  8242.  
  8243. rmTransResultMap(id) {
  8244. delete this.transResultMap[id];
  8245. }
  8246.  
  8247. addTransResultMap(id, text) {
  8248. this.transResultMap[id] = text;
  8249. }
  8250.  
  8251. getTranslateDivNum(ttTree) {
  8252. var num = 0;
  8253. for (var i in ttTree) {
  8254. if (ttTree[i].isTranslateDiv) {
  8255. num++;
  8256. }
  8257. }
  8258. return num;
  8259. }
  8260.  
  8261. // 恢复目标元素中的translateDiv
  8262. recover(elements) {
  8263. elements.each((i, e) => {
  8264. var ttTreeNode = this.node[i];
  8265. var missingTranslateDivs = this.getTranslateDivNum(ttTreeNode);
  8266. if (missingTranslateDivs > 0) {
  8267. this.recoverOneElement($(e), ttTreeNode);
  8268. }
  8269. });
  8270. }
  8271.  
  8272. recoverOneElement(element, ttTreeNode) {
  8273. this.recoverOneFork(element.children().eq(0), ttTreeNode, 0);
  8274. }
  8275.  
  8276. // 恢复一个分支
  8277. recoverOneFork(pElement, ttTreeNode, index) {
  8278. do {
  8279. // only recover element with tagNames
  8280. if (!this.tagNames.includes(pElement.prop("tagName"))) {
  8281. if (pElement.next().length > 0) {
  8282. pElement = pElement.next();
  8283. } else {
  8284. return;
  8285. }
  8286. }
  8287. if (!ttTreeNode[index] || pElement.prop("tagName") !== ttTreeNode[index].type) {
  8288. // console.warn(`元素不存在或类型不同, 元素结构可能已经发生了变化: \nindex: ${index}`, pElement);
  8289. return;
  8290. } else {
  8291. // recursively child element
  8292. var node = ttTreeNode[index];
  8293. if (node.firstChild !== null) {
  8294. this.recoverOneFork(
  8295. pElement.children().eq(0),
  8296. ttTreeNode,
  8297. node.firstChild
  8298. );
  8299. }
  8300. // check if next node is translateDiv
  8301. if (node.next !== null) {
  8302. index = node.next;
  8303.  
  8304. var ne_node = ttTreeNode[index];
  8305. if (ne_node.isTranslateDiv) {
  8306. var id = ne_node.id;
  8307. var topText = ne_node.topText;
  8308. var text = this.transResultMap[id];
  8309. // create element after pElement
  8310. this.reCreateTransDiv(pElement, id, text, topText, node.isTranslateDiv); // 如果前面一个也是翻译结果,则该结果折叠
  8311. }
  8312. pElement = pElement.next(); // go to next element
  8313. }
  8314. }
  8315. } while (node.next !== null);
  8316. }
  8317.  
  8318. /**
  8319. * 重新创建translateDiv
  8320. * @param {*} pElement
  8321. * @param {*} id
  8322. * @param {*} translatedText
  8323. * @param {*} topText
  8324. * @param {Boolean} isFold 是否折叠
  8325. */
  8326. reCreateTransDiv(pElement, id, translatedText, topText, isFold) {
  8327. const translateDiv = new TranslateDiv(id);
  8328. pElement.after(translateDiv.getDiv());
  8329. translateDiv.setTopText(topText);
  8330. translateDiv.registerUpButtonEvent();
  8331. translateDiv.registerCloseButtonEvent();
  8332. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  8333. translateDiv.registerCopyButtonEvent(translatedText);
  8334. } else {
  8335. translateDiv.disableCopyButton();
  8336. }
  8337. translateDiv.updateTranslateDiv(translatedText, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru));
  8338. // 标记已翻译并添加到翻译按钮的结果栈中
  8339. let transButton = pElement.prev('.html2md-panel').find('.translateButton');
  8340. if (transButton.length == 0) {
  8341. // 如果没有找到,则应该是得在父元素中找到
  8342. transButton = pElement.parent().prev('.html2md-panel').find('.translateButton');
  8343. }
  8344. if (isFold) translateDiv.fold(); // 是否折叠该翻译
  8345. transButton.pushResultToTransButton({
  8346. translateDiv: translateDiv,
  8347. status: 0
  8348. });
  8349. transButton.setTransButtonState('success');
  8350. }
  8351. }
  8352.  
  8353. // 更新TransDB中的翻译数据
  8354. async function updateTransDBData(nodeDate, transResultMap) {
  8355. var url = window.location.href.replace(/#/, "");
  8356. try {
  8357. await OJBetter.common.database.translateData.put({ url, transResultMap, nodeDate });
  8358. return 'translateData saved successfully';
  8359. } catch (error) {
  8360. throw new Error(`Failed to save translateData: ${error}`);
  8361. }
  8362. }
  8363.  
  8364. // 获取TransDB中保存的翻译数据
  8365. async function getTransDBData() {
  8366. var url = window.location.href.replace(/#/, "");
  8367. try {
  8368. const result = await OJBetter.common.database.translateData.get(url);
  8369. return result;
  8370. } catch (error) {
  8371. throw new Error(`Failed to get translateData: ${error}`);
  8372. }
  8373. }
  8374.  
  8375. /**
  8376. * 翻译结果恢复功能初始化
  8377. * @returns
  8378. */
  8379. async function initTransResultsRecover() {
  8380. OJBetter.translation.memory.ttTree = new ElementsTree("#task-statement"); // 初始化当前页面#task-statement元素的结构树
  8381. let result = await getTransDBData();
  8382. if (!result) return;
  8383. OJBetter.translation.memory.ttTree.setNodeData(result.nodeDate);
  8384. OJBetter.translation.memory.ttTree.setTransResultMap(result.transResultMap);
  8385. OJBetter.translation.memory.ttTree.recover($("#task-statement"));
  8386. }
  8387.  
  8388. /**
  8389. * 自动翻译
  8390. */
  8391. async function initTransWhenViewable() {
  8392. await waitForMathJaxIdle();
  8393.  
  8394. // const elements = $('.ttypography, .comments').find('.translateButton');
  8395. const elements = $('#task-statement').find('.translateButton');
  8396. const observers = [];
  8397.  
  8398. // Use a single Intersection Observer for all elements
  8399. const observer = new IntersectionObserver((entries, obs) => {
  8400. entries.forEach((entry) => {
  8401. if (entry.isIntersecting) {
  8402. const button = $(entry.target);
  8403. const state = button.getButtonState();
  8404. const notAutoTranslate = button.getNotAutoTranslate();
  8405. // Check if the button meets the criteria
  8406. if (state === 'normal' && !notAutoTranslate) {
  8407. let trans = OJBetter.translation.choice;
  8408.  
  8409. if (OJBetter.translation.auto.mixTrans.enabled && button.IsCommentButton() && OJBetter.translation.auto.mixTrans.servers.length > 0) {
  8410. const randomIndex = Math.floor(Math.random() * OJBetter.translation.auto.mixTrans.servers.length);
  8411. trans = OJBetter.translation.auto.mixTrans.servers[randomIndex];
  8412. }
  8413. button.data("translatedItBy")(trans);
  8414. }
  8415.  
  8416. // Stop observing the element
  8417. obs.unobserve(entry.target);
  8418. }
  8419. });
  8420. });
  8421.  
  8422. // Observe each element
  8423. elements.each((i, e) => {
  8424. observer.observe(e);
  8425. });
  8426.  
  8427. // Store the observer in case you need to disconnect it later
  8428. observers.push(observer);
  8429. }
  8430.  
  8431. /**
  8432. * 翻译返回结果结构体
  8433. * @typedef {Object} TranslateResult
  8434. * @property {string} status 翻译状态
  8435. * @property {TranslateDiv} translateDiv 翻译结果面板
  8436. * @property {TransRawData} rawData 原始翻译数据
  8437. */
  8438.  
  8439. /**
  8440. * 翻译主方法
  8441. * @param {string} text 待翻译文本
  8442. * @param {HTMLElement} element_node 元素节点
  8443. * @param {Boolean} is_comment 是否为评论区文本
  8444. * @param {string} overrideTrans 覆盖全局翻译服务设定
  8445. * @returns {TranslateResult} 翻译结果对象
  8446. */
  8447. async function translateProblemStatement(text, element_node, is_comment, overrideTrans) {
  8448. /** @type {number} 翻译结果的ID*/
  8449. const id = OJB_getRandomNumber(8);
  8450. /** @type {TextBlockReplacer} 文本块替换/恢复实例*/
  8451. const textBlockReplacer = new TextBlockReplacer();
  8452. /** @type {string} 翻译结果文本*/
  8453. let translatedText = "";
  8454.  
  8455. /** @type {string} 当前实际应用的翻译服务 */
  8456. const realTransServer = overrideTrans ||
  8457. (is_comment && OJBetter.translation.comment.choice != "0" ?
  8458. OJBetter.translation.comment.choice :
  8459. OJBetter.translation.choice);
  8460.  
  8461. /** @type {TranslateResult} 翻译结果对象 */
  8462. const translateResult = {
  8463. status: "ok",
  8464. rawData: {
  8465. done: false
  8466. }
  8467. }
  8468.  
  8469. /**
  8470. * LaTeX替换
  8471. * @param {string} text 待翻译文本
  8472. * @returns {string} 处理后的文本
  8473. */
  8474. const replaceLatex = function (text) {
  8475. if (OJBetter.typeOfPage.is_oldLatex) {
  8476. const regex = /<span\s+class="tex-span">.*?<\/span>/gi;
  8477. text = textBlockReplacer.replace(text, regex);
  8478. text = text.replace(/<p>(.*?)<\/p>/g, "$1\n\n"); // <p/>标签换为换行
  8479. } else if (OJBetter.typeOfPage.is_acmsguru) {
  8480. const regex = /<i>.*?<\/i>|<sub>.*?<\/sub>|<sup>.*?<\/sup>|<pre>.*?<\/pre>/gi;
  8481. text = textBlockReplacer.replace(text, regex);
  8482. } else if (realTransServer != "openai") {
  8483. // 使用GPT翻译时不必替换latex公式
  8484. const regex = /\$\$([^]*?)\$\$|\$(\\\$|[^\$])*?\$/g;
  8485. text = textBlockReplacer.replace(text, regex);
  8486.  
  8487. // 替换行间代码块```
  8488. const regex2 = /```[\s\S]*?```/g;
  8489. text = textBlockReplacer.replace(text, regex2);
  8490. }
  8491. return text;
  8492. }
  8493.  
  8494. /**
  8495. * LaTeX恢复
  8496. * @param {string} text 已翻译的文本
  8497. * @returns {string} 恢复后的文本
  8498. */
  8499. const recoverLatex = function (text) {
  8500. // 两个公式之间加个空格,防止有些LaTeX解析器解析错误
  8501. let resultText = text
  8502. .replace(/】【/g, '】 【')
  8503. .replace(/\]\[/g, '] [')
  8504. .replace(/\}\{/g, '} {');
  8505.  
  8506. if (OJBetter.typeOfPage.is_oldLatex) {
  8507. resultText = resultText.replace(/(.+?)(\n\n|$)/g, "<p>$1</p>"); // 换行符还原为<p/>标签
  8508. resultText = textBlockReplacer.recover(resultText);
  8509. } else if (OJBetter.typeOfPage.is_acmsguru) {
  8510. resultText = textBlockReplacer.recover(resultText);
  8511. } else if (realTransServer != "openai") {
  8512. resultText = textBlockReplacer.recover(resultText);
  8513. }
  8514. return resultText;
  8515. }
  8516.  
  8517. /**
  8518. * 格式化翻译结果
  8519. * @param {string} text
  8520. * @returns {string} 处理后的翻译结果
  8521. */
  8522. const formatText = function (text) {
  8523. // 转义LaTex中的特殊符号
  8524. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  8525.  
  8526. // 先替换掉行间代码块
  8527. const replacer = new TextBlockReplacer();
  8528. text = replacer.replace(text, /```[\s\S]*?```/g);
  8529.  
  8530. // 处理LaTeX公式
  8531. const escapeRules = [
  8532. { pattern: /(?<!\\)>(?!\s)/g, replacement: " &gt; " }, // >符号
  8533. { pattern: /(?<!\\)</g, replacement: " &lt; " }, // <符号
  8534. { pattern: /(?<!\\)\*/g, replacement: " &#42; " }, // *符号
  8535. { pattern: /(?<!\\)_/g, replacement: " &#95; " }, // _符号
  8536. { pattern: /(?<!\\)\\\\(?=\s)/g, replacement: "\\\\\\\\" }, // \\符号
  8537. { pattern: /(?<!\\)\\(?![\\a-zA-Z0-9])/g, replacement: "\\\\" }, // \符号
  8538. ];
  8539.  
  8540. let latexMatches = [...text.matchAll(/\$\$([\s\S]*?)\$\$|\$(.*?)\$|\$([\s\S]*?)\$/g)];
  8541. for (const match of latexMatches) {
  8542. const matchedText = match[0];
  8543. let escapedText = matchedText;
  8544.  
  8545. for (const rule of escapeRules) {
  8546. escapedText = escapedText.replaceAll(rule.pattern, rule.replacement);
  8547. }
  8548. escapedText = escapedText.replace(/\$\$/g, "$$$$$$$$");// $$符号(因为后面需要作为replacement,双倍消耗)
  8549. text = text.replace(matchedText, escapedText);
  8550. }
  8551.  
  8552. // 恢复行间代码块
  8553. text = replacer.recover(text);
  8554. }
  8555.  
  8556. // // 使符合mathjx的转换语法
  8557. // const mathjaxRuleMap = [
  8558. // { pattern: /\$/g, replacement: "$$$$$$" }, // $$ 行间
  8559. // ];
  8560. // mathjaxRuleMap.forEach(({ pattern, replacement }) => {
  8561. // text = text.replace(pattern, replacement);
  8562. // });
  8563.  
  8564. // markdown修正
  8565. const mdRuleMap = [
  8566. { pattern: /(\s_[\u4e00-\u9fa5]+_)([\u4e00-\u9fa5]+)/g, replacement: "$1 $2" }, // 斜体
  8567. { pattern: /(_[\u4e00-\u9fa5]+_\s)([\u4e00-\u9fa5]+)/g, replacement: " $1$2" },
  8568. { pattern: /(_[\u4e00-\u9fa5]+_)([\u4e00-\u9fa5]+)/g, replacement: " $1 $2" },
  8569. { pattern: /(([\s\S]*?))/g, replacement: "($1)" }, // 中文()
  8570. // { pattern: /:/g, replacement: ":" }, // 中文:
  8571. { pattern: /\*\* (.*?) \*\*/g, replacement: "\*\*$1\*\*" } // 加粗
  8572. ];
  8573. mdRuleMap.forEach(({ pattern, replacement }) => {
  8574. text = text.replace(pattern, replacement);
  8575. });
  8576.  
  8577. return text;
  8578. }
  8579.  
  8580. // 创建翻译结果元素并放在element_node的后面
  8581. translateResult.translateDiv = new TranslateDiv(id);
  8582. $(element_node).after(translateResult.translateDiv.getDiv());
  8583.  
  8584. // 顶栏左侧信息
  8585. translateResult.translateDiv.setTopText(i18next.t('servers.' + realTransServer, { ns: 'translator' }) +
  8586. i18next.t('translateDiv.topTextSuffix', { ns: 'translator' }));
  8587.  
  8588. // 注册按钮
  8589. translateResult.translateDiv.registerUpButtonEvent();
  8590. translateResult.translateDiv.registerCloseButtonEvent();
  8591. if (OJBetter.translation.choice == 'openai' || OJBetter.translation.choice == 'deepl') {
  8592. translateResult.translateDiv.showQueryBalanceButton(OJBetter.translation.choice); // 显示额度查询
  8593. }
  8594.  
  8595. // 翻译内容是否可能为代码片段
  8596. if (isEmptyText(text)) {
  8597. const shouldContinue = await OJB_createDialog(
  8598. i18next.t('isEmptyText.title', { ns: 'dialog' }),
  8599. i18next.t('isEmptyText.content', { ns: 'dialog' }),
  8600. [
  8601. i18next.t('isEmptyText.buttons.0', { ns: 'dialog' }),
  8602. i18next.t('isEmptyText.buttons.1', { ns: 'dialog' })
  8603. ],
  8604. true
  8605. );
  8606. if (shouldContinue) {
  8607. translateResult.status = "skip";
  8608. return translateResult;
  8609. }
  8610. }
  8611.  
  8612. // 替换latex公式
  8613. text = replaceLatex(text);
  8614.  
  8615. // 过滤**号
  8616. if (OJBetter.translation.filterTextWithoutEmphasis && GM_getValue("translation") !== "openai") { // TODO
  8617. text = text.replace(/\*\*/g, "");
  8618. }
  8619.  
  8620. // 字符数上限
  8621. const translationLimits = {
  8622. deepl: 5000,
  8623. iflyrec: 2000,
  8624. youdao: 5000,
  8625. google: 5000,
  8626. caiyun: 5000
  8627. };
  8628. if (translationLimits.hasOwnProperty(realTransServer) && text.length > translationLimits[realTransServer]) {
  8629. let textLength = translationLimits[realTransServer];
  8630. let realTextLength = text.length;
  8631. const shouldContinue = await OJB_createDialog(
  8632. i18next.t('transTextLimits.title', { ns: 'dialog' }),
  8633. i18next.t('transTextLimits.content', { ns: 'dialog', textLength: textLength, realTextLength: realTextLength }),
  8634. [
  8635. i18next.t('transTextLimits.buttons.0', { ns: 'dialog' }),
  8636. i18next.t('transTextLimits.buttons.1', { ns: 'dialog' })
  8637. ],
  8638. true
  8639. ); // 字数超限确认
  8640. if (shouldContinue) {
  8641. translateResult.status = "skip";
  8642. return translateResult;
  8643. }
  8644. }
  8645.  
  8646. /**
  8647. * 调用各个翻译服务
  8648. * @param {string} transServer 翻译服务
  8649. * @returns {TransRawData} 原始翻译数据
  8650. */
  8651. async function translate(transServer) {
  8652. const is_renderLaTeX = !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru);
  8653. const servername = i18next.t('servers.' + realTransServer, { ns: 'translator' });
  8654. /** @type {TransRawData} 原始翻译数据*/
  8655. let rawData = {};
  8656. try {
  8657. if (transServer == "deepl") {
  8658. if (OJBetter.deepl.config.type == 'free') {
  8659. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8660. rawData = await translate_deepl(text);
  8661. } else if (OJBetter.deepl.config.type == 'api') {
  8662. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.deeplApi', { ns: 'translator', deepl_configName: OJBetter.deepl.config.name })}`, is_renderLaTeX);
  8663. if (OJBetter.deepl.config.apiGenre == 'deeplx') {
  8664. rawData = await translate_deeplx(text);
  8665. } else {
  8666. if (OJBetter.deepl.enableEmphasisProtection) text = convertBoldMarkdownToHTML(text);
  8667. if (OJBetter.deepl.enableLinkProtection) text = convertLinksMarkdownToHTML(text);
  8668. if (OJBetter.deepl.config.apiGenre == 'api-free') {
  8669. rawData = await translate_deepl_api_free(text);
  8670. } else if (OJBetter.deepl.config.apiGenre == 'api-pro') {
  8671. rawData = await translate_deepl_api_pro(text);
  8672. }
  8673. if (OJBetter.deepl.enableEmphasisProtection) rawData.text = convertBoldHTMLToMarkdown(rawData.text);
  8674. if (OJBetter.deepl.enableLinkProtection) rawData.text = convertLinksHTMLToMarkdown(rawData.text);
  8675. }
  8676. }
  8677. } else if (transServer == "iflyrec") {
  8678. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8679. rawData = await translate_iflyrec(text);
  8680. } else if (transServer == "youdao") {
  8681. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8682. rawData = await translate_youdao_mobile(text);
  8683. } else if (transServer == "google") {
  8684. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8685. rawData = await translate_gg(text);
  8686. } else if (transServer == "caiyun") {
  8687. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8688. rawData = await translate_caiyun(text);
  8689. } else if (transServer == "openai") {
  8690. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.openai', { ns: 'translator', openai_name: OJBetter.chatgpt.config.name })}${!OJBetter.chatgpt.isStream
  8691. ? i18next.t('transingTip.openai_isStream', { ns: 'translator' }) : ""}`,
  8692. is_renderLaTeX);
  8693. if (OJBetter.chatgpt.isStream) {
  8694. // 流式传输
  8695. rawData = await translate_openai_stream(text, translateResult.translateDiv);
  8696. } else {
  8697. // 普通模式
  8698. rawData = await translate_openai(text);
  8699. }
  8700. }
  8701. translatedText = rawData.text;
  8702. if (!rawData.done) {
  8703. translateResult.status = "error";
  8704. }
  8705. } catch (e) {
  8706. translateResult.status = "error";
  8707. rawData.message = i18next.t('error.unexpected', { ns: 'translator' });
  8708. console.warn(e);
  8709. }
  8710. return rawData;
  8711. }
  8712. translateResult.rawData = await translate(realTransServer);
  8713.  
  8714. if (translateResult.status == "error") {
  8715. translateResult.translateDiv.updateTranslateDiv(translateResult.rawData.message);
  8716. return translateResult;
  8717. }
  8718.  
  8719. // 还原latex公式
  8720. translatedText = recoverLatex(translatedText);
  8721.  
  8722. // 注册结果复制按钮
  8723. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  8724. translateResult.translateDiv.registerCopyButtonEvent(translatedText);
  8725. } else {
  8726. translateResult.translateDiv.disableCopyButton();
  8727. }
  8728.  
  8729. // 翻译结果格式化
  8730. translatedText = formatText(translatedText);
  8731.  
  8732. // 保存翻译结果
  8733. if ((OJBetter.typeOfPage.is_problem || OJBetter.typeOfPage.is_completeProblemset) && OJBetter.translation.memory.enabled) {
  8734. // OJBetter.translation.memory.ttTree.refreshNode(".ttypography"); // 刷新当前页面.ttypography元素的结构树实例
  8735. OJBetter.translation.memory.ttTree.refreshNode("#task-statement"); // 刷新当前页面.ttypography元素的结构树实例
  8736. OJBetter.translation.memory.ttTree.addTransResultMap(id, translatedText);
  8737. updateTransDBData(OJBetter.translation.memory.ttTree.getNodeData(), OJBetter.translation.memory.ttTree.getTransResultMap()); // 更新翻译结果到transDB
  8738. }
  8739.  
  8740. // 翻译结果面板更新
  8741. translateResult.translateDiv.updateTranslateDiv(translatedText, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru));
  8742.  
  8743. return translateResult;
  8744. }
  8745.  
  8746. //弹窗翻译
  8747. function alertZh() {
  8748. // var _alert = window.alert;
  8749. // window.alert = async function (msg) {
  8750. // _alert(msg + "\n=========翻译=========\n" + await translate_deepl(msg));
  8751. // return true;
  8752. // }
  8753. };
  8754.  
  8755. /**
  8756. * 折叠块展开
  8757. */
  8758. function ExpandFoldingblocks() {
  8759. $('.spoiler').addClass('spoiler-open');
  8760. $('.spoiler-content').attr('style', '');
  8761. };
  8762.  
  8763. /**
  8764. * 折叠块渲染优化
  8765. */
  8766. function RenderPerfOpt() {
  8767. GM_addStyle(`
  8768. .spoiler-content {
  8769. contain: layout style;
  8770. }
  8771. `);
  8772. }
  8773.  
  8774. /**
  8775. * 下拉选择框性能优化
  8776. */
  8777. async function SelectElementPerfOpt() {
  8778. // TODO 10
  8779. // 加载库资源
  8780. await OJB_LoadJS("https://aowuucdn.oss-accelerate.aliyuncs.com/js/selectpage.min.js");
  8781. /**
  8782. * 将一个<select>元素转换为SelectPage控件
  8783. * @param {HTMLElement|string} selector - 要转换的<select>元素或其选择器
  8784. */
  8785. const OJB_transformSelectToSelectPage = (selector) => {
  8786. const $select = $(selector);
  8787. if ($select.length === 0 || !$select.is('select')) {
  8788. console.error('Invalid select element provided.');
  8789. return;
  8790. }
  8791.  
  8792. // 隐藏原生的<select>元素
  8793. $select.hide();
  8794.  
  8795. // 创建一个新的<input>元素用于SelectPage控件
  8796. const $inputForSelectPage = $('<input>', {
  8797. type: 'text',
  8798. class: 'selectpage-input',
  8799. autocomplete: 'off'
  8800. });
  8801. $select.after($inputForSelectPage);
  8802.  
  8803. // 准备SelectPage所需的数据格式
  8804. const data = $select.find('option').map((_, option) => ({
  8805. id: option.value,
  8806. text: option.text
  8807. })).get();
  8808.  
  8809. // 初始化SelectPage
  8810. $inputForSelectPage.selectPage({
  8811. showField: 'text',
  8812. keyField: 'id',
  8813. data,
  8814. lang: 'en',
  8815. // 当选中一个选项时,更新隐藏的<select>元素的值
  8816. eSelect: (data) => {
  8817. $select.val(data.id).trigger('change');
  8818. },
  8819. // 初始化时根据<select>的当前值设置SelectPage
  8820. initRecord: $select.val()
  8821. });
  8822. };
  8823.  
  8824. // 遍历页面上的所有select
  8825. $('select').each((_, select) => {
  8826. // 选项大于500才优化
  8827. if ($(select).find('option').length > 500) {
  8828. OJB_transformSelectToSelectPage(select);
  8829. }
  8830. });
  8831. }
  8832.  
  8833. /**
  8834. * 题目页相关链接栏
  8835. */
  8836. class ProblemPageLinkbar {
  8837. constructor() {
  8838. this.containerElement = this.createToolbar();
  8839. this.commandInvoker = new CommandInvoker();
  8840. }
  8841.  
  8842. /**
  8843. * 创建工具栏
  8844. */
  8845. createToolbar() {
  8846. // const toolbarElement = $("<div>").attr("id", "problemToolbar").insertBefore($(".problemindexholder"));
  8847. const toolbarElement = $("<div>").attr("id", "problemToolbar").insertBefore($(".h2"));
  8848. return new DOMContainer(toolbarElement);
  8849. }
  8850.  
  8851. /**
  8852. * 添加按钮
  8853. * @param {string} id 按钮id
  8854. * @param {string} url 按钮链接
  8855. * @param {string} text 按钮文字
  8856. * @param {JQueryObject} icon 按钮图标
  8857. * @param {string} iconHeight 图标高度
  8858. * @returns {object} 按钮对象
  8859. */
  8860. addLinkButton(id, url, text, icon = $('<div>'), iconHeight = "22px") {
  8861. const linkElement = $("<a>")
  8862. .attr("href", url)
  8863. .attr("target", "_blank")
  8864. .addClass("ojb_btn")
  8865. .attr("id", id);
  8866.  
  8867. linkElement.append(icon);
  8868. icon.css("height", iconHeight);
  8869.  
  8870. const textSpan = $("<span>").html(text);
  8871. linkElement.append(textSpan);
  8872.  
  8873. this.commandInvoker.execute(new AddElementCommand(this.containerElement, linkElement));
  8874. return {
  8875. element: linkElement,
  8876. text: textSpan,
  8877. icon: icon
  8878. };
  8879. }
  8880.  
  8881. /**
  8882. * 更新链接
  8883. * @param {object} button 按钮对象
  8884. * @param {string} url 按钮链接
  8885. */
  8886. updateUrl(button, url) {
  8887. button.element.attr("href", url);
  8888. }
  8889.  
  8890. /**
  8891. * 更新文字
  8892. * @param {object} button 按钮对象
  8893. * @param {string} text 按钮文字
  8894. */
  8895. updateText(button, text) {
  8896. button.text.html(text);
  8897. }
  8898.  
  8899. /**
  8900. * 设置文字为粗体
  8901. * @param {object} button 按钮对象
  8902. */
  8903. setBold(button) {
  8904. button.text.css("font-weight", "bold");
  8905. }
  8906.  
  8907. /**
  8908. * 更新图标
  8909. * @param {object} button 按钮对象
  8910. * @param {JQueryObject} icon 按钮图标
  8911. * @param {string} iconHeight 图标高度
  8912. */
  8913. updateIcon(button, icon, iconHeight = "16px") {
  8914. button.icon.remove();
  8915. button.text.prepend(icon);
  8916. icon.css("height", iconHeight);
  8917. button.icon = icon;
  8918. }
  8919.  
  8920. /**
  8921. * 添加类
  8922. * @param {object} button 按钮对象
  8923. * @param {string} className 类名
  8924. */
  8925. addClass(button, className) {
  8926. button.element.addClass(className);
  8927. }
  8928.  
  8929. /**
  8930. * 禁用链接按钮
  8931. * @param {object} button 按钮对象
  8932. */
  8933. disableButton(button) {
  8934. button.element.addClass("disabled");
  8935. }
  8936.  
  8937. /**
  8938. * 启用链接按钮
  8939. * @param {object} button 按钮对象
  8940. */
  8941. enableButton(button) {
  8942. button.element.removeClass("disabled");
  8943. }
  8944. }
  8945.  
  8946. /**
  8947. * 获取题目的id
  8948. * @param {String} url 题目的链接
  8949. * @returns 题目的id,形如2000A
  8950. */
  8951. function getProblemId(url) {
  8952. const regex = /\/contests\/([A-Za-z\d\-]+)\/tasks\/([A-Za-z\d\_]+)/;
  8953. const matchResult = url.match(regex);
  8954. return matchResult && matchResult.length >= 3
  8955. ? `${matchResult[2]}`
  8956. : '';
  8957. };
  8958.  
  8959. /**
  8960. * 跳转到洛谷
  8961. * @param {ProblemPageLinkbar} problemToolbar
  8962. */
  8963. async function CF2luogu(problemToolbar) {
  8964. const url = window.location.href;
  8965. const problemId = getProblemId(url);
  8966. const luoguButton = problemToolbar.addLinkButton(
  8967. "luoguButton",
  8968. "https://www.luogu.com.cn/",
  8969. i18next.t('state.loading', { ns: 'button' }),
  8970. $("<img>").attr("src", "https://cdn.luogu.com.cn/fe/logo.png")
  8971. );
  8972.  
  8973. const checkLinkExistence = async (url) => {
  8974. return OJB_promiseRetryWrapper(async () => {
  8975. const response = await OJB_GMRequest({
  8976. method: "GET",
  8977. url
  8978. });
  8979. return !response.responseText.match(/出错了/g);
  8980. }, {
  8981. maxRetries: 3,
  8982. retryInterval: 1000
  8983. });
  8984. };
  8985.  
  8986. const LuoguUrl = `https://www.luogu.com.cn/problem/AT_${problemId}`;
  8987. try {
  8988. const result = await checkLinkExistence(LuoguUrl);
  8989. if (problemId && result) {
  8990. problemToolbar.updateText(luoguButton, "");
  8991. problemToolbar.updateUrl(luoguButton, LuoguUrl);
  8992. } else {
  8993. problemToolbar.updateText(luoguButton, i18next.t('state.404', { ns: 'button' }));
  8994. problemToolbar.disableButton(luoguButton);
  8995. }
  8996. } catch (error) {
  8997. if (error instanceof OJB_GMError && error.type == "error") {
  8998. problemToolbar.updateText(luoguButton, i18next.t('state.netError', { ns: 'button' }));
  8999. problemToolbar.disableButton(luoguButton);
  9000. }
  9001. }
  9002. }
  9003.  
  9004. /**
  9005. * 跳转到 Virtual Judge
  9006. * @param {ProblemPageLinkbar} problemToolbar
  9007. */
  9008. async function CF2vjudge(problemToolbar) {
  9009. const url = window.location.href;
  9010. const problemId = getProblemId(url);
  9011. const vjudgeButton = problemToolbar.addLinkButton(
  9012. "vjudgeButton",
  9013. "https://vjudge.net/",
  9014. i18next.t('state.loading', { ns: 'button' }),
  9015. $("<img>").attr("src", "https://aowuucdn.oss-accelerate.aliyuncs.com/vjudge.ico")
  9016. );
  9017.  
  9018. const checkLinkExistence = async (url) => {
  9019. return OJB_promiseRetryWrapper(async () => {
  9020. const response = await OJB_GMRequest({
  9021. method: "HEAD",
  9022. url: url,
  9023. });
  9024. if (response.status >= 200 && response.status < 300) return true;
  9025. else if (response.status == 404) return false;
  9026. else throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9027. }, {
  9028. maxRetries: 3,
  9029. retryInterval: 1000
  9030. });
  9031. };
  9032.  
  9033. const VjudgeUrl = `https://vjudge.net/problem/AtCoder-${problemId}`;
  9034. try {
  9035. const result = await checkLinkExistence(VjudgeUrl);
  9036. if (problemId && result) {
  9037. problemToolbar.updateText(vjudgeButton, "VJudge");
  9038. problemToolbar.updateUrl(vjudgeButton, VjudgeUrl);
  9039. } else {
  9040. problemToolbar.updateText(vjudgeButton, i18next.t('state.404', { ns: 'button' }));
  9041. problemToolbar.disableButton(vjudgeButton);
  9042. }
  9043. } catch (error) {
  9044. if (error instanceof OJB_GMError && error.type == "error") {
  9045. problemToolbar.updateText(vjudgeButton, i18next.t('state.netError', { ns: 'button' }));
  9046. problemToolbar.disableButton(vjudgeButton);
  9047. }
  9048. }
  9049. }
  9050.  
  9051. // RatingClass
  9052. const ratingClassMap = {
  9053. NaN: "rating_by_clist_colorNaN",
  9054. 0: "rating_by_clist_color0",
  9055. 400: "rating_by_clist_color1",
  9056. 800: "rating_by_clist_color2",
  9057. 1200: "rating_by_clist_color3",
  9058. 1600: "rating_by_clist_color4",
  9059. 2000: "rating_by_clist_color5",
  9060. 2400: "rating_by_clist_color6",
  9061. 2800: "rating_by_clist_color7",
  9062. 2800: "rating_by_clist_color8",
  9063. 2800: "rating_by_clist_color9"
  9064. };
  9065. const cssMap = {
  9066. "rating_by_clist_colorNaN": "#cccccc",
  9067. "rating_by_clist_color0": "#808080",
  9068. "rating_by_clist_color1": "#804000",
  9069. "rating_by_clist_color2": "#008000",
  9070. "rating_by_clist_color3": "#00c0c0",
  9071. "rating_by_clist_color4": "#0000ff",
  9072. "rating_by_clist_color5": "#c0c000",
  9073. "rating_by_clist_color6": "#fb7e00",
  9074. "rating_by_clist_color7": "#ff0000",
  9075. "rating_by_clist_color8": "#ff0000",
  9076. "rating_by_clist_color9": "#ff0000"
  9077. };
  9078. // TODO 7
  9079. /**
  9080. * clist 访问有效性检查
  9081. * @param {boolean} onlyCookie 是否只检查Cookie
  9082. * @returns {Promise<boolean>} 是否有效
  9083. */
  9084. async function validateClistConnection(onlyCookie = false) {
  9085. const clistApiUrl = "https://clist.by:443/api/v4/contest/?limit=1&resource_id=1";
  9086. const requestOptions = {
  9087. method: "GET",
  9088. url: clistApiUrl,
  9089. timeout: 5000,
  9090. };
  9091.  
  9092. // 尝试发送请求
  9093. async function tryRequest(options) {
  9094. try {
  9095. const response = await OJB_GMRequest(options);
  9096. if (response.status === 200) {
  9097. return { ok: true };
  9098. } else if (response.status === 401) {
  9099. throw new Error('unauthorized');
  9100. } else if (response.status === 404) {
  9101. throw new Error('not_found');
  9102. } else {
  9103. throw new Error('other_error');
  9104. }
  9105. } catch (error) {
  9106. console.warn(`Error accessing clist.by: ${error.message}`);
  9107. return { ok: false, error: error.message };
  9108. }
  9109. }
  9110.  
  9111. // 尝试携带Key发送请求
  9112. let result = await tryRequest(requestOptions);
  9113. if (!onlyCookie && !result.ok) {
  9114. requestOptions.headers = { "Authorization": OJBetter.clist.authorization };
  9115. result = await tryRequest(requestOptions);
  9116. }
  9117.  
  9118. // 根据结果显示错误信息
  9119. if (!result.ok) {
  9120. let errorType = result.error;
  9121. const loadingMessage = new LoadingMessage();
  9122. let state;
  9123. if (errorType === 'not_found') {
  9124. state = i18next.t('error.clist.404', { ns: 'alert' });
  9125. } else if (errorType === 'unauthorized') {
  9126. state = i18next.t('error.clist.cookie', { ns: 'alert' });
  9127. } else {
  9128. state = i18next.t('error.clist.other', { ns: 'alert' });
  9129. }
  9130. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${state}`, 'error');
  9131. }
  9132. return result.ok;
  9133. }
  9134.  
  9135. /**
  9136. * 创建Rating相关css
  9137. * @param {boolean} [hasBorder=true] 是否有边框
  9138. */
  9139. function creatRatingCss(hasBorder = true) {
  9140. const defaultBorderColor = '#dcdfe6';
  9141. let dynamicCss = "";
  9142. let hoverSelector = OJBetter.clist.ratingHidden ? ":hover" : "";
  9143. for (let cssClass in cssMap) {
  9144. dynamicCss += `a.${cssClass}${hoverSelector}, a.${cssClass}${hoverSelector}:link {\n`;
  9145. let borderColor = hasBorder ? cssMap[cssClass] : defaultBorderColor;
  9146. dynamicCss += ` color: ${cssMap[cssClass]} ${OJBetter.clist.ratingHidden ? "!important" : ""};\n`;
  9147. dynamicCss += `}\n`;
  9148. }
  9149. GM_addStyle(dynamicCss);
  9150. if (OJBetter.clist.ratingHidden) {
  9151. GM_addStyle(`
  9152. #clistButton {
  9153. color: #ffffff00;
  9154. }
  9155. `);
  9156. }
  9157. }
  9158.  
  9159. /**
  9160. * 模拟clist网页访问获取rating
  9161. * @param {string} problem 题目名称
  9162. * @param {string} problem_url 题目链接
  9163. * @param {string} contest 比赛名称
  9164. * @returns {Promise<{rating: number, problem: string}>} 题目难度
  9165. */
  9166. async function getRatingFromHTML(problem, problem_url, contest = null) {
  9167. // 去除题目名称中的括号,以及首尾的空白符
  9168. problem = problem.replace(/\([\s\S]*?\)/g, '').trim();
  9169.  
  9170. return OJB_promiseRetryWrapper(async () => {
  9171. const queryString = `search=${encodeURIComponent(problem)}&resource=1`;
  9172. const response = await OJB_GMRequest({
  9173. method: 'GET',
  9174. url: `https://clist.by/problems/?${queryString}`,
  9175. });
  9176.  
  9177. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9178. const html = response.responseText;
  9179. const cleanedHtml = html.replace(/src=(.|\s)*?"/g, '');
  9180. const parser = new DOMParser();
  9181. const doc = parser.parseFromString(cleanedHtml, 'text/html');
  9182. const trs = doc.querySelectorAll('table tbody tr');
  9183.  
  9184. for (let tr of trs) {
  9185. const rating = tr.querySelector('.problem-rating-column').textContent.trim();
  9186. const link = OJB_cleanLink(tr.querySelector('.problem-name-column a:nth-of-type(2)')?.href);
  9187.  
  9188. if (link === problem_url || link === problem_url + '/') {
  9189. return {
  9190. rating: parseInt(rating),
  9191. problem: problem
  9192. };
  9193. } else if (contest !== null) {
  9194. const contestTitles = [...tr.querySelectorAll('.problem-name-column .pull-right a[title], .problem-name-column .pull-right span[title]')].map(el => el.title);
  9195. if (contestTitles.includes(contest)) {
  9196. return {
  9197. rating: parseInt(rating),
  9198. problem: problem
  9199. };
  9200. }
  9201. }
  9202. }
  9203. console.warn(`No data found for the question: ${problem}`);
  9204. }, {
  9205. maxRetries: 3,
  9206. retryInterval: 500
  9207. });
  9208. }
  9209.  
  9210. /**
  9211. * 从clist API获取题目的rating
  9212. * @param {string} problem_name 题目名
  9213. * @param {string} problem_url 题目链接
  9214. * @returns {Promise<number>} 题目rating
  9215. *
  9216. * 使用两个Map对象来存储和快速访问题目信息:
  9217. * - problemsMap: 通过题目的URL作为键来存储题目信息。
  9218. * - nameMap: 通过题目的名称作为键来存储题目信息。
  9219. *
  9220. * 每个题目信息是一个对象,包含以下属性:
  9221. * @typedef {Object} ProblemInfo
  9222. * @property {string} name 题目名称
  9223. * @property {string} url 题目URL
  9224. * @property {number} rating 题目评分,如果没有评分信息则为NaN
  9225. */
  9226. async function getRatingFromApi_problem(problem_name, problem_url) {
  9227. return OJB_promiseRetryWrapper(async () => {
  9228. const response = await OJB_GMRequest({
  9229. method: "GET",
  9230. // url: `https://clist.by:443/api/v4/problem/?name=${encodeURIComponent(problem_name)}&resource__regex=codeforces.com`,
  9231. url: `https://clist.by:443/api/v4/problem/?url__regex=${encodeURIComponent(problem_name)}&resource__regex=atcoder.jp`,
  9232. headers: { "Authorization": OJBetter.clist.authorization }
  9233. });
  9234.  
  9235. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9236. let data = JSON.parse(response.responseText);
  9237. /**
  9238. * 使用题目的URL作为键来存储题目信息。
  9239. * @type {Map<string, ProblemInfo>}
  9240. */
  9241. let problemsMap = new Map();
  9242.  
  9243. /**
  9244. * 使用题目的名称作为键来存储题目信息。
  9245. * @type {Map<string, ProblemInfo>}
  9246. */
  9247. let nameMap = new Map();
  9248.  
  9249. data.objects.forEach(problem => {
  9250. /** @type {ProblemInfo} 题目信息*/
  9251. let problemInfo = {
  9252. name: problem.name,
  9253. url: problem.url,
  9254. rating: problem.rating ? problem.rating : NaN
  9255. };
  9256. problemsMap.set(OJB_cleanLink(problem.url), problemInfo);
  9257. nameMap.set(problem.name, problemInfo);
  9258. });
  9259.  
  9260. if (problemsMap.has(problem_url)) {
  9261. return problemsMap.get(problem_url).rating;
  9262. } else if (nameMap.has(problem_name)) {
  9263. return nameMap.get(problem_name).rating;
  9264. } else {
  9265. console.warn('Problem not found in the response');
  9266. }
  9267. }, {
  9268. maxRetries: 5,
  9269. retryInterval: 1000
  9270. });
  9271. }
  9272.  
  9273. /**
  9274. * 获取字符串中的关键词列表
  9275. * @param {string} text 字符串文本
  9276. * @returns {array<string>} 返回关键词列表
  9277. */
  9278. function getKeywords(text) {
  9279. // 定义要过滤掉的高频词
  9280. const highFrequencyWords = ['Educational', 'Codeforces', 'Round', 'Div'];
  9281.  
  9282. // 使用正则表达式替换掉特殊符号(保留空格以便分词)
  9283. const sanitizedText = text.replace(/[^\w\s]|_/g, '').replace(/\s+/g, ' ');
  9284.  
  9285. // 将字符串拆分为单词数组
  9286. const words = sanitizedText.split(' ');
  9287.  
  9288. // 过滤掉高频词和空字符串
  9289. const filteredWords = words.filter(word => {
  9290. return word && highFrequencyWords.indexOf(word) === -1;
  9291. });
  9292.  
  9293. // 返回关键词列表
  9294. return filteredWords;
  9295. }
  9296.  
  9297. /**
  9298. * 根据关键词从 Clist API 中获取实际比赛名称
  9299. * @param {string} contestName 比赛名
  9300. * @param {string} contestUrl 比赛链接
  9301. * @returns {string|null} 该比赛在Clist中的实际名字
  9302. */
  9303. async function getContestNameFromApi(contestName, contestUrl) {
  9304. return OJB_promiseRetryWrapper(async () => {
  9305. const options = {
  9306. method: "GET",
  9307. // url: `https://clist.by:443/api/v4/contest/?resource_id=1&event__regex=${encodeURIComponent(contestName)}`,
  9308. url: `https://clist.by:443/api/v4/contest/?resource_id=93&event__regex=${encodeURIComponent(contestName)}`,
  9309. headers: {
  9310. "Authorization": OJBetter.clist.authorization
  9311. }
  9312. };
  9313.  
  9314. let response = await OJB_GMRequest(options);
  9315.  
  9316. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9317.  
  9318. let data = JSON.parse(response.responseText);
  9319. let objects = data.objects;
  9320.  
  9321. if (objects.length > 0) {
  9322. for (const contest of objects) {
  9323. // const href = contest.href.replace(/\/contests\//i, '/contest/'); // 链接可能是contests而不是contest,换回来
  9324. const href = contest.href;
  9325. if (OJB_cleanLink(href) == contestUrl) {
  9326. return contest.event;
  9327. }
  9328. }
  9329. }
  9330. return null;
  9331. }, {
  9332. maxRetries: 5,
  9333. retryInterval: 1000
  9334. });
  9335. }
  9336.  
  9337. /**
  9338. * 获取在clist中的实际比赛名称
  9339. * @param {string} contestName 待搜索的比赛名称
  9340. * @param {string} contestUrl 比赛的url
  9341. * @returns {Promise<string|null>} 在clist中的实际比赛名称,如果没有找到,则返回null
  9342. */
  9343. async function getActualContestName(contestName, contestUrl) {
  9344. // 首先尝试使用完整的比赛名称进行搜索
  9345. let actualContestName = await getContestNameFromApi(contestName, contestUrl);
  9346. if (actualContestName) {
  9347. return actualContestName;
  9348. }
  9349.  
  9350. // 如果使用完整名称没有找到,则尝试使用关键词进行搜索
  9351. const keywords = getKeywords(contestName);
  9352. const maxKeywordAttempts = 1; // 最多尝试到第几个关键词(因为Clist API有频率限制)
  9353. for (let i = 0; i < Math.min(keywords.length, maxKeywordAttempts); i++) {
  9354. actualContestName = await getContestNameFromApi(keywords[i], contestUrl);
  9355. if (actualContestName) {
  9356. return actualContestName;
  9357. }
  9358. }
  9359.  
  9360. // 如果全部尝试后仍没有找到,返回null
  9361. return null;
  9362. }
  9363.  
  9364. /**
  9365. * 从clist API获取比赛题目集的rating
  9366. * @param {string} contestName 比赛名
  9367. * @returns {Promise<Map<string, number>>} 题目rating
  9368. */
  9369. async function getRatingFromApi_contest(contestName, contestUrl) {
  9370. const actualContestName = await getActualContestName(contestName, contestUrl);
  9371. return OJB_promiseRetryWrapper(async () => {
  9372. const options = {
  9373. method: "GET",
  9374. url: `https://clist.by:443/api/v4/contest/?resource_id=93&with_problems=true&event=${encodeURIComponent(actualContestName)}`,
  9375. headers: {
  9376. "Authorization": OJBetter.clist.authorization
  9377. }
  9378. };
  9379.  
  9380. let response = await OJB_GMRequest(options);
  9381.  
  9382. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9383.  
  9384. let data = JSON.parse(response.responseText);
  9385. let objects = data.objects;
  9386. let problemsMap = new Map();
  9387.  
  9388. if (objects.length > 0 && objects[0].problems) {
  9389. objects[0].problems.forEach(problem => {
  9390. problemsMap.set(OJB_cleanLink(problem.url), problem.rating ? problem.rating : NaN);
  9391. });
  9392. }
  9393.  
  9394. return problemsMap;
  9395. }, {
  9396. maxRetries: 5,
  9397. retryInterval: 1000
  9398. });
  9399. }
  9400.  
  9401. /**
  9402. * 根据rating获取对应的颜色class名
  9403. * @param {number} rating 题目rating
  9404. * @returns {string} 颜色class名
  9405. */
  9406. function getClassNameByRating(rating) {
  9407. let className = "rating_by_clist_color9";
  9408. if (Number.isNaN(rating)) {
  9409. className = "rating_by_clist_colorNaN";
  9410. } else {
  9411. let keys = Object.keys(ratingClassMap);
  9412. for (let i = 0; i < keys.length; i++) {
  9413. if (rating < keys[i]) {
  9414. className = ratingClassMap[keys[i - 1]];
  9415. break;
  9416. }
  9417. }
  9418. }
  9419. return className;
  9420. }
  9421.  
  9422. /**
  9423. * problem题目页显示Rating
  9424. * @param {ProblemPageLinkbar} problemToolbar
  9425. * @returns {Promise<void>}
  9426. */
  9427. async function showRatingByClist_problem(problemToolbar) {
  9428. // 题目名
  9429. // const problem = $('.header .title').eq(0).text().replace(/[\s\S]*?. /, '');
  9430. // if (OJBetter.typeOfPage.is_acmsguru) problem = $('h4').eq(0).text().replace(/[\s\S]*?. /, '');
  9431. const url = window.location.href;
  9432. const problemId = getProblemId(url);
  9433.  
  9434. // 创建Rating按钮元素
  9435. creatRatingCss(false);
  9436. // TODO
  9437. // const clistButton = problemToolbar.addLinkButton(
  9438. // 'clistButton',
  9439. // `https://clist.by/problems/?search=${problem}&resource=1`,
  9440. // i18next.t('state.wait', { ns: 'button' }),
  9441. // $("<img>").attr("src", "https://clist.by/static/img/logo-48.png"),
  9442. // "15px"
  9443. // );
  9444. const clistButton = problemToolbar.addLinkButton(
  9445. 'clistButton',
  9446. `https://clist.by/problems/?search=${problemId}&resource=93`,
  9447. i18next.t('state.wait', { ns: 'button' }),
  9448. $("<img>").attr("src", "https://clist.by/static/img/logo-48.png"),
  9449. "15px"
  9450. );
  9451.  
  9452. // 检测clist连接
  9453. if (!await validateClistConnection()) {
  9454. problemToolbar.updateText(clistButton, i18next.t('state.netError', { ns: 'button' }));
  9455. return;
  9456. }
  9457.  
  9458. // 题目链接
  9459. let problem_url = window.location.href;
  9460. if (problem_url.includes('/contest/')) {
  9461. problem_url = problem_url.replace(/\/contest\/(\d+)\/problem\/(\w+)[^\w]*/, '/contest/$1/problem/$2');
  9462. } else {
  9463. problem_url = problem_url.replace(/\/problemset\/problem\/(\d+)\/(\w+)/, '/contest/$1/problem/$2');
  9464. }
  9465. if (OJBetter.typeOfPage.is_mSite) problem_url = problem_url.replace(/\/\/(\w+).codeforces.com/, '//codeforces.com'); // 轻量站
  9466.  
  9467. // 比赛名
  9468. // let contest = $('#sidebar').children().first().find('.rtable th').first().text();
  9469.  
  9470. // rating
  9471. problemToolbar.updateText(clistButton, i18next.t('state.loading', { ns: 'button' }));
  9472. let rating = await getRatingFromApi_problem(problemId, problem_url);
  9473. if (rating) {
  9474. let className = getClassNameByRating(rating);
  9475. problemToolbar.updateText(clistButton, rating);
  9476. problemToolbar.setBold(clistButton);
  9477. problemToolbar.addClass(clistButton, className);
  9478. } else {
  9479. problemToolbar.updateText(clistButton, i18next.t('state.404', { ns: 'button' }));
  9480. problemToolbar.disableButton(clistButton);
  9481. }
  9482. }
  9483.  
  9484. /**
  9485. * contest页显示Rating
  9486. * @returns {Promise<void>}
  9487. */
  9488. async function showRatingByClist_contest() {
  9489. // 创建Rating显示框
  9490. creatRatingCss();
  9491. let ratingBadges = {};
  9492. // $('.datatable .id.left').each(function () {
  9493. // let href = 'https://codeforces.com' + $(this).find('a').attr('href');
  9494. // let badge = OJB_safeCreateJQElement(`<a id="clistButton" class="ratingBadge">${i18next.t('state.wait', { ns: 'button' })}</a>`);
  9495. // $(this).find('a').after(badge);
  9496. // ratingBadges[href] = badge;
  9497. // });
  9498. $('table tbody tr').each(function () {
  9499. let href = 'https://atcoder.jp' + $(this).find('a').attr('href');
  9500. let badge = OJB_safeCreateJQElement(`<a id="clistButton" class="ratingBadge">${i18next.t('state.wait', { ns: 'button' })}</a>`);
  9501. $(this).find('a:first').after(badge);
  9502. ratingBadges[href] = badge;
  9503. });
  9504.  
  9505. // 检测clist连接
  9506. if (!await validateClistConnection()) {
  9507. for (let href in ratingBadges) {
  9508. ratingBadges[href].text('error').addClass('ratingBadge_error');
  9509. }
  9510. return;
  9511. }
  9512.  
  9513. // 显示loading
  9514. for (let href in ratingBadges) {
  9515. ratingBadges[href].text(i18next.t('state.loading', { ns: 'button' })).addClass('ratingBadge_loading');
  9516. }
  9517.  
  9518. // 获取Rating
  9519. // let contestName = $('#sidebar').children().first().find('.rtable th').first().text();
  9520. let contestName = window.location.href.match(/\/contests\/[^\/]*?(\d+)\/tasks/)?.[1];
  9521. // let contestUrl = OJB_cleanLink(window.location.href);
  9522. let contestUrl = OJB_cleanLink(window.location.href.replace(/\/tasks\/?.*$/, ''));
  9523. try {
  9524. let problemsMap = await getRatingFromApi_contest(contestName, contestUrl);
  9525.  
  9526. // 填充数据
  9527. for (let href in ratingBadges) {
  9528. if (problemsMap.has(href)) {
  9529. let rating = problemsMap.get(href);
  9530. let className = getClassNameByRating(rating);
  9531. ratingBadges[href].text(rating).addClass(className);
  9532. } else {
  9533. ratingBadges[href].text(i18next.t('state.404', { ns: 'button' })).addClass('ratingBadge_no');
  9534. }
  9535. }
  9536. } catch (error) {
  9537. // 填充数据
  9538. for (let href in ratingBadges) {
  9539. ratingBadges[href].text(i18next.t('state.netError', { ns: 'button' })).addClass('ratingBadge_no');
  9540. }
  9541. console.warn(error);
  9542. }
  9543. }
  9544.  
  9545. /**
  9546. * problemset页显示Rating
  9547. * @returns {Promise<void>}
  9548. */
  9549. async function showRatingByClist_problemset() {
  9550. creatRatingCss();
  9551. let ratingBadges = [];
  9552. const $problems = $('.problems');
  9553. const $trs = $problems.find('tbody tr:gt(0)');
  9554.  
  9555. // 先创建Rating显示框,并将关系存进数组ratingBadges
  9556. for (let i = 0; i < $trs.length; i++) {
  9557. const $tds = $($trs[i]).find('td');
  9558. const $firstDiv = $($tds[1]).find('div:first');
  9559. let problem = $firstDiv.text();
  9560. let problem_url = $firstDiv.find('a').attr('href');
  9561. problem_url = problem_url.replace(/^\/problemset\/problem\/(\d+)\/(\w+)/, 'https://codeforces.com/contest/$1/problem/$2');
  9562.  
  9563. const ratingBadge = OJB_safeCreateJQElement(`<a id="clistButton" class="ratingBadge"></a>`);
  9564. const rating = OJB_safeCreateJQElement(`<span class="rating">${i18next.t('state.wait', { ns: 'button' })}</span>`);
  9565. ratingBadge.append(rating);
  9566. $($tds[0]).find('a').after(ratingBadge);
  9567. ratingBadges.push({ ratingBadge, rating, problem, problem_url });
  9568. }
  9569.  
  9570. // 检测clist连接
  9571. if (!await validateClistConnection()) {
  9572. for (let i = 0; i < rating.length; i++) {
  9573. ratingBadges[i].rating.text(i18next.t('state.netError', { ns: 'button' }));
  9574. }
  9575. return;
  9576. }
  9577.  
  9578. // 每次只获取3个rating
  9579. for (let i = 0; i < ratingBadges.length; i += 3) {
  9580. const promises = [];
  9581. const endIndex = Math.min(i + 3, ratingBadges.length);
  9582.  
  9583. for (let j = i; j < endIndex; j++) {
  9584. const ratingBadge = ratingBadges[j];
  9585. // 显示请求中
  9586. ratingBadge.rating.text(i18next.t('state.loading', { ns: 'button' }));
  9587. promises.push(getRatingFromHTML(ratingBadge.problem, ratingBadge.problem_url).catch(error => console.warn(error)));
  9588. }
  9589.  
  9590. const results = await Promise.all(promises);
  9591.  
  9592. for (let j = i; j < endIndex; j++) {
  9593. const result = results[j - i];
  9594. const ratingBadge = ratingBadges[j];
  9595. if (result) {
  9596. let className = getClassNameByRating(result.rating);
  9597. ratingBadge.ratingBadge.addClass(className);
  9598. ratingBadge.rating.text(result.rating);
  9599. } else {
  9600. ratingBadge.rating.text(i18next.t('state.404', { ns: 'button' }));
  9601. }
  9602. }
  9603. }
  9604. }
  9605.  
  9606. /**
  9607. * 存放编辑器语言select的值与Monaco语言对应关系的map.
  9608. * @type {Object.<string, string>}
  9609. */
  9610. // const value_monacoLanguageMap = {
  9611. // "4": "pascal", "6": "php", "7": "python", "9": "csharp", "13": "perl", "20": "scala", "31": "python",
  9612. // "32": "go", "34": "javascript", "36": "java", "40": "python", "41": "python", "43": "cpp",
  9613. // "50": "cpp", "51": "pascal", "52": "cpp", "54": "cpp", "55": "javascript", "59": "cpp", "60": "java",
  9614. // "61": "cpp", "65": "csharp", "67": "ruby", "70": "python", "73": "cpp", "74": "java", "75": "rust",
  9615. // "77": "kotlin", "79": "csharp", "80": "cpp", "83": "kotlin", "87": "java"
  9616. // };
  9617. const value_monacoLanguageMap = {
  9618. "5001": "cpp", "5002": "go", "5003": "csharp", "5004": "kotlin", "5005": "java",
  9619. "5006": "nim", "5007": "text", "5008": "text", "5009": "javascript", "5010": "javascript",
  9620. "5011": "r", "5012": "d", "5013": "d", "5014": "swift", "5015": "dart",
  9621. "5016": "php", "5017": "cpp", "5018": "ruby", "5019": "crystal", "5020": "text",
  9622. "5021": "fsharp", "5022": "julia", "5023": "sh", "5024": "text", "5025": "haskell",
  9623. "5026": "fortran", "5027": "lua", "5028": "cpp", "5029": "lisp", "5030": "cobol",
  9624. "5031": "cpp", "5032": "sh", "5033": "python", "5034": "sh", "5035": "text",
  9625. "5036": "text", "5037": "perl", "5038": "sh", "5039": "text", "5040": "text",
  9626. "5041": "pascal", "5042": "csharp", "5043": "lua", "5044": "prolog", "5045": "sh",
  9627. "5046": "scheme", "5047": "scala", "5048": "vbscript", "5049": "text", "5050": "clojure",
  9628. "5051": "erlang", "5052": "typescript", "5053": "cpp", "5054": "rust", "5055": "python",
  9629. "5056": "scala", "5057": "text", "5058": "typescript", "5059": "ocaml", "5060": "raku",
  9630. "5061": "text", "5062": "lisp", "5063": "python", "5064": "clojure", "5065": "text",
  9631. "5066": "text", "5067": "text", "5068": "ada", "5069": "text", "5070": "text",
  9632. "5071": "clojure", "5072": "cpp", "5073": "cpp", "5074": "text", "5075": "lisp",
  9633. "5076": "text", "5077": "d", "5078": "python", "5079": "text", "5080": "text",
  9634. "5081": "ocaml", "5082": "python", "5083": "matlab", "5084": "haxe", "5085": "elixir",
  9635. "5086": "text", "5087": "text", "5088": "lisp", "5089": "text", "5090": "cobol"
  9636. };
  9637.  
  9638. /**
  9639. * 更新代码提交页的HTML
  9640. * @param {string} submitUrl 提交页面的URL
  9641. * @param {string} cacheKey 本地缓存的键名
  9642. * @returns {Promise<jQuery<HTMLElement>>} 返回 jQuery 包装的 HTML 元素
  9643. */
  9644. async function CloneOriginalHTML(submitUrl, cacheKey) {
  9645. return OJB_promiseRetryWrapper(async () => {
  9646. const response = await OJB_GMRequest({
  9647. method: 'GET',
  9648. url: submitUrl
  9649. });
  9650. const html = response.responseText;
  9651. const parser = new DOMParser();
  9652. const doc = parser.parseFromString(html, 'text/html');
  9653. const cloneHTML = $(doc.body).html();
  9654. localStorage.setItem(cacheKey, html);
  9655. return $(cloneHTML);
  9656. }, {
  9657. maxRetries: 5,
  9658. retryInterval: 1000,
  9659. errorHandler: (err) => {
  9660. console.error('A network error occurred while retrieving the HTML for the code submission page.', submitUrl);
  9661. }
  9662. });
  9663. }
  9664.  
  9665. /**
  9666. * 获取代码提交页的HTML元素
  9667. * @param {string} submitUrl
  9668. * @returns {Promise<jQuery>}
  9669. */
  9670. async function getSubmitHTML(submitUrl) {
  9671. const cacheKey = 'OJBetter_CloneOriginalHTML';
  9672. const cookieKey = 'OJBetter_CloneOriginalHTML_time';
  9673. if (OJB_getCookie(cookieKey) === '1') {
  9674. // 存在缓存
  9675. CloneOriginalHTML(submitUrl, cacheKey);
  9676. // 校验
  9677. let cloneHTML = $(localStorage.getItem(cacheKey));
  9678. if (cloneHTML.find('form.submit-form').length > 0) {
  9679. return cloneHTML;
  9680. } else {
  9681. // 存在错误,更新缓存
  9682. console.warn(`Cache error detected!\nattempting to update, cache destination submitUrl:\n${submitUrl}`);
  9683. return await CloneOriginalHTML(submitUrl, cacheKey);
  9684. }
  9685.  
  9686. } else {
  9687. // 没有缓存,更新
  9688. document.cookie = `${cookieKey}=1; path=/`;
  9689. return await CloneOriginalHTML(submitUrl, cacheKey);
  9690. }
  9691. }
  9692.  
  9693. // 代码自动保存
  9694. async function saveCode(url, code) {
  9695. try {
  9696. await OJBetter.common.database.editorCode.put({ url, code });
  9697. return 'Code saved successfully';
  9698. } catch (error) {
  9699. throw new Error('Failed to save code');
  9700. }
  9701. }
  9702.  
  9703. async function getCode(url) {
  9704. try {
  9705. const result = await OJBetter.common.database.editorCode.get(url);
  9706. return result ? result.code : null;
  9707. } catch (error) {
  9708. throw new Error('Failed to get code');
  9709. }
  9710. }
  9711.  
  9712. // 创建代码编辑调试表单元素
  9713. // async function createCodeEditorForm(submitUrl, cloneHTML) {
  9714. async function createCodeEditorForm(submitUrl) {
  9715. // 表单
  9716. let formDiv = $('<form method="post" id="OJBetter_SubmitForm" class="input-output-copier"></form>');
  9717. // $('.ttypography').after(formDiv);
  9718. $('#task-statement').after(formDiv);
  9719. // formDiv.attr('action', submitUrl + "?csrf_token=" + OJBetter.common.at_csrf_token);
  9720. formDiv.attr('action', submitUrl);
  9721. formDiv.attr('method', 'POST');
  9722.  
  9723. // 顶部区域
  9724. let topDiv = OJB_safeCreateJQElement(`<div class="topDiv"></div>`);
  9725. let selectLang = $('#select-lang').clone(); // 语言选择
  9726. // selectLang.css({ 'margin': '10px 0px' }).attr('id', 'programTypeId');
  9727. topDiv.append(selectLang);
  9728. let topRightDiv = OJB_safeCreateJQElement(`<div class="topRightDiv"></div>`);
  9729. topDiv.append(topRightDiv);
  9730. formDiv.append(topDiv);
  9731.  
  9732. // 问题选择/编号
  9733. // let selectProblem = $('<input name="submittedProblemIndex" style="display:none;"></input>');
  9734. // let problemCode;
  9735. // if (OJBetter.typeOfPage.is_acmsguru) {
  9736. // problemCode = $('h4').eq(0).text();
  9737. // let matchResult = problemCode.match(/([A-Z0-9]+)/);
  9738. // problemCode = matchResult[0];
  9739. // } else if (OJBetter.typeOfPage.is_problemset_problem) {
  9740. // let match = window.location.href.match(/\/problem\/([0-9]+?)\/([A-Za-z0-9]+?)(?!=[A-Za-z0-9])/);
  9741. // problemCode = match[1] + match[2];
  9742. // selectProblem.attr('name', 'submittedProblemCode');
  9743. // } else {
  9744. // problemCode = $('.header .title').eq(0).text();
  9745. // let matchResult = problemCode.match(/([A-Z0-9]+)/);
  9746. // problemCode = matchResult[0];
  9747. // }
  9748. // selectProblem.val(problemCode);
  9749. let selectProblem = $('input[name="data.TaskScreenName"]').clone();
  9750. formDiv.append(selectProblem);
  9751.  
  9752. // 隐藏的代码记录
  9753. // let sourceDiv = $('<textarea id="sourceCodeTextarea" name="source" style="display: none;"></textarea>');
  9754. let sourceDiv = $('<textarea id="plain-textarea" name="sourceCode" style="display: none;"></textarea>');
  9755. formDiv.append(sourceDiv);
  9756.  
  9757. // 隐藏的crsf token
  9758. let csrfDiv = $(`<input type="hidden" name="csrf_token" value=${OJBetter.common.at_csrf_token}>`);
  9759. formDiv.append(csrfDiv);
  9760.  
  9761. // 代码编辑器
  9762. let editorDiv = $('<div id="OJBetter_editor"></div>');
  9763. formDiv.append(editorDiv);
  9764.  
  9765. // monaco
  9766. let monaco = $('<div id="OJBetter_monaco"></div>');
  9767. editorDiv.append(monaco);
  9768.  
  9769. // 自定义调试
  9770. let customTestDiv = OJB_safeCreateJQElement(`
  9771. <details id="customTestBlock">
  9772. <summary >${i18next.t('customTestBlock.title', { ns: 'codeEditor' })}</summary>
  9773. <div id="customTests" style="min-height: 30px;"></div>
  9774. <div id="control" style="display:flex;">
  9775. <div style="display: flex;margin: 5px;">
  9776. <input type="checkbox" id="onlyCustomTest"}><label for="onlyCustomTest">
  9777. ${i18next.t('customTestBlock.onlyCustom', { ns: 'codeEditor' })}
  9778. </label>
  9779. </div>
  9780. <div style="display: flex;margin: 5px;">
  9781. <input type="checkbox" id="DontShowDiff"}>
  9782. <label for="DontShowDiff">
  9783. ${i18next.t('customTestBlock.DontShowDiff', { ns: 'codeEditor' })}
  9784. </label>
  9785. </div>
  9786. <button type="button" id="addCustomTest">${i18next.t('customTestBlock.add', { ns: 'codeEditor' })}</button>
  9787. </div>
  9788. </details>
  9789. `)
  9790. formDiv.append(customTestDiv);
  9791.  
  9792. // 调试/提交
  9793. let submitDiv = $('<div id="OJBetter_submitDiv"></div>');
  9794. let CompilerArgsInput = $('<input type="text" id="CompilerArgsInput">');
  9795. submitDiv.append(CompilerArgsInput);
  9796.  
  9797. let runButton = OJB_safeCreateJQElement(`
  9798. <button type="button" id="RunTestButton" class="ojb_btn ojb_btn_popover top">
  9799. <i class="iconfont">&#xe6c1;</i>
  9800. <span class="popover_content">${i18next.t('runTestButton.initial', { ns: 'codeEditor' })}</span>
  9801. </button>
  9802. `);
  9803. let submitButton = OJB_safeCreateJQElement(`
  9804. <button id="SubmitButton" class="ojb_btn ojb_btn_popover top" type="submit">
  9805. <i class="iconfont">&#xe633;</i>
  9806. <span class="popover_content">${i18next.t('submitButton', { ns: 'codeEditor' })}</span>
  9807. </button>
  9808. `);
  9809. if (OJBetter.monaco.setting.submitButtonPosition == "bottom") {
  9810. // 添加测试/提交按钮到底部
  9811. submitDiv.append(runButton);
  9812. submitDiv.append(submitButton);
  9813. }
  9814.  
  9815. formDiv.append(submitDiv);
  9816. let CompilerSetting = OJB_safeCreateJQElement(`
  9817. <div id="CompilerSetting"></div>
  9818. `);
  9819. formDiv.append(CompilerSetting);
  9820. let statePanel = OJB_safeCreateJQElement(`
  9821. <div id="statePanel"></div>
  9822. `);
  9823. formDiv.append(statePanel);
  9824.  
  9825. //==================================
  9826. // 去除原有的编辑器
  9827. //==================================
  9828. $('.form-code-submit').remove();
  9829.  
  9830. let from = {
  9831. formDiv: formDiv,
  9832. selectLang: selectLang.find('select:first'),
  9833. topRightDiv: topRightDiv,
  9834. sourceDiv: sourceDiv,
  9835. editorDiv: editorDiv,
  9836. monaco: monaco,
  9837. runButton: runButton,
  9838. submitButton: submitButton,
  9839. submitDiv: submitDiv,
  9840. CompilerSetting: CompilerSetting,
  9841. statePanel: statePanel
  9842. };
  9843. return from;
  9844. }
  9845.  
  9846. // 解析ace格式的补全规则(acwing)
  9847. function parseAceCompleter(rules, range) {
  9848. const suggestions = [];
  9849. if (rules && rules.templates && rules.templates.items) {
  9850. const items = rules.templates.items;
  9851. for (let i = 0; i < items.length; i++) {
  9852. const item = items[i];
  9853. const parts = item.caption.split(' ');
  9854. for (let i = 0; i < parts.length; i++) {
  9855. if (item.value.startsWith(parts[i])) {
  9856. item.value = item.value.replace(parts[i], parts.slice(0, i + 1).join(' '));
  9857. break;
  9858. }
  9859. }
  9860. const completionItem = {
  9861. label: item.caption,
  9862. kind: monaco.languages.CompletionItemKind.Function,
  9863. insertText: item.value,
  9864. range: range
  9865. };
  9866. suggestions.push(completionItem);
  9867. }
  9868. }
  9869. return { suggestions };
  9870. }
  9871.  
  9872. // 解析monaco格式的补全规则
  9873. function parseMonacoCompleter(rules, range) {
  9874. const suggestions_ = [];
  9875. if (rules && rules.suggestions) {
  9876. const suggestion = rules.suggestions;
  9877. for (let i = 0; i < rules.suggestions.length; i++) {
  9878. const item = suggestion[i];
  9879. const completionItem = {
  9880. ...item,
  9881. range: range
  9882. };
  9883. suggestions_.push(completionItem);
  9884. }
  9885. }
  9886. return { suggestions: suggestions_ };
  9887. }
  9888.  
  9889. /**
  9890. * 创建monaco编辑器的一个实例
  9891. */
  9892. async function createMonacoEditor(language, form, support) {
  9893. // 判断monacoLoader是否加载完毕
  9894. await OJB_waitUntilTrue(() => OJBetter.monaco.loaderOnload);
  9895.  
  9896. /**
  9897. * 通用参数
  9898. */
  9899. var id = 0; // 协议中的id标识
  9900. var workspace = language + "_workspace";
  9901. var rootUri = OJBetter.monaco.lsp.workUri + "/" + workspace;
  9902. // 文件名
  9903. var InstanceID = OJB_getRandomNumber(8).toString();
  9904. var filename = language == "java" ? "hello/src/" + InstanceID : InstanceID;
  9905. // 后缀名
  9906. var fileExtension =
  9907. language === "cpp"
  9908. ? ".cpp"
  9909. : language === "python"
  9910. ? ".py"
  9911. : language === "java"
  9912. ? ".java"
  9913. : "";
  9914. var uri = rootUri + "/" + filename + fileExtension;
  9915. var initialized = false; // 是否已初始化
  9916. var serverInfo; // 服务器返回的支持信息
  9917. var model; // model
  9918. var OJBetter_monaco = {};
  9919. window.OJBetter_monaco = OJBetter_monaco; // 全局方法
  9920.  
  9921. /**
  9922. * 一些工具函数
  9923. */
  9924. // 将lsp格式的rang转换为Monaco格式
  9925. OJBetter_monaco.lspRangeToMonacoRange = function (range) {
  9926. const { start, end } = range;
  9927. return new monaco.Range(
  9928. start.line + 1,
  9929. start.character + 1,
  9930. end.line + 1,
  9931. end.character + 1
  9932. );
  9933. };
  9934. // 将Monaco格式的rang转为lsp格式
  9935. OJBetter_monaco.MonacoRangeTolspRange = function (range) {
  9936. return {
  9937. start: {
  9938. line: range.startLineNumber - 1,
  9939. character: range.startColumn - 1,
  9940. },
  9941. end: {
  9942. line: range.endLineNumber - 1,
  9943. character: range.endColumn - 1,
  9944. },
  9945. };
  9946. };
  9947. // 将Monaco格式的position转为lsp格式的
  9948. OJBetter_monaco.MonacoPositionTolspPosition = function (position) {
  9949. return {
  9950. line: position.lineNumber - 1,
  9951. character: position.column - 1,
  9952. };
  9953. };
  9954. // 将Monaco格式的severity转为lsp格式的
  9955. OJBetter_monaco.MonacoSeverityTolspSeverity = function (severity) {
  9956. switch (severity) {
  9957. case 8:
  9958. return 1;
  9959. case 1:
  9960. return 4;
  9961. case 2:
  9962. return 3;
  9963. case 4:
  9964. return 2;
  9965. default:
  9966. return severity;
  9967. }
  9968. };
  9969. // 将lsp格式的severity转为Monaco格式的
  9970. OJBetter_monaco.lspSeverityToMonacoSeverity = function (severity) {
  9971. switch (severity) {
  9972. case 1:
  9973. return 8;
  9974. case 4:
  9975. return 1;
  9976. case 3:
  9977. return 2;
  9978. case 2:
  9979. return 4;
  9980. default:
  9981. return severity;
  9982. }
  9983. };
  9984. // 收集Monaco数据中的rang数据
  9985. OJBetter_monaco.CollectRange = function (item) {
  9986. return {
  9987. startLineNumber: item.startLineNumber,
  9988. startColumn: item.startColumn,
  9989. endLineNumber: item.endLineNumber,
  9990. endColumn: item.endColumn,
  9991. };
  9992. };
  9993. // 收集Monaco position数据中的rang数据
  9994. OJBetter_monaco.CollectRangeByPosition = function (item) {
  9995. var word = model.getWordUntilPosition(item);
  9996. return {
  9997. startLineNumber: item.lineNumber,
  9998. endLineNumber: item.lineNumber,
  9999. startColumn: word.startColumn,
  10000. endColumn: word.endColumn,
  10001. };
  10002. };
  10003. // 将lsp格式的Edit转换为Monaco格式
  10004. OJBetter_monaco.lspEditToMonacoEdit = function (edit) {
  10005. const edits = [];
  10006.  
  10007. if (language == "python") {
  10008. for (const item1 of edit.documentChanges) {
  10009. for (const item2 of item1.edits) {
  10010. const newElement = {
  10011. textEdit: {
  10012. range: OJBetter_monaco.lspRangeToMonacoRange(item2.range),
  10013. text: item2.newText,
  10014. },
  10015. resource: monaco.Uri.parse(item1.textDocument.uri),
  10016. versionId: model.getVersionId(),
  10017. };
  10018. edits.push(newElement);
  10019. }
  10020. }
  10021. } else if (language == "java") {
  10022. for (const item1 in edit.changes) {
  10023. edit.changes[item1].forEach((item2) => {
  10024. const newElement = {
  10025. textEdit: {
  10026. range: OJBetter_monaco.lspRangeToMonacoRange(item2.range),
  10027. text: item2.newText,
  10028. },
  10029. resource: uri,
  10030. versionId: model.getVersionId(),
  10031. };
  10032. edits.push(newElement);
  10033. });
  10034. }
  10035. } else {
  10036. for (const key in edit.changes) {
  10037. const arr = edit.changes[key];
  10038. for (const item of arr) {
  10039. const newElement = {
  10040. textEdit: {
  10041. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  10042. text: item.newText,
  10043. },
  10044. resource: monaco.Uri.parse(key),
  10045. versionId: model.getVersionId(),
  10046. };
  10047. edits.push(newElement);
  10048. }
  10049. }
  10050. }
  10051. return { edits: edits };
  10052. };
  10053.  
  10054. /**
  10055. * 实例化一个editor
  10056. */
  10057. uri = monaco.Uri.file(uri);
  10058. model = monaco.editor.createModel('', language, uri);
  10059. OJBetter.monaco.editor = monaco.editor.create(document.getElementById("OJBetter_monaco"), {
  10060. model: model,
  10061. rootUri: rootUri,
  10062. fontSize: 15,
  10063. tabSize: 4,
  10064. theme: OJBetter.basic.darkMode == "dark" ? "vs-dark" : "vs",
  10065. bracketPairColorization: {
  10066. enabled: true,
  10067. independentColorPoolPerBracketType: true,
  10068. },
  10069. automaticLayout: true,
  10070. lineNumbersMinChars: 3,
  10071. matchOnWordStartOnly: false,
  10072. wordWrap: "on",
  10073. wrappingIndent: "same",
  10074. glyphMargin: true,
  10075. formatOnType: true,
  10076. scrollbar: {
  10077. verticalScrollbarSize: 10,
  10078. horizontalScrollbarSize: 10,
  10079. alwaysConsumeMouseWheel: OJBetter.monaco.setting.alwaysConsumeMouseWheel
  10080. },
  10081. suggest: {
  10082. selectionMode: 'never' // 代码建议不自动选择
  10083. }
  10084. });
  10085.  
  10086. /**
  10087. * 添加快捷功能
  10088. */
  10089. (OJBetter_monaco.addShortCuts = async () => {
  10090. // 从配置信息更新字体大小
  10091. OJBetter.monaco.editor.updateOptions({ fontSize: parseInt(OJBetter.monaco.setting.fontsize) });
  10092.  
  10093. // 调整字体大小
  10094. let changeSize = OJB_safeCreateJQElement(`
  10095. <div class="ojb_btn ojb_btn_popover top">
  10096. <input type="number" id="fontSizeInput" value="${OJBetter.monaco.setting.fontsize}">
  10097. <span class="popover_content">${i18next.t('fontSizeInput', { ns: 'codeEditor' })}</span>
  10098. </div>`)
  10099. form.topRightDiv.append(changeSize);
  10100. changeSize.find('input#fontSizeInput').on('input', function () {
  10101. var size = $(this).val();
  10102. OJBetter.monaco.editor.updateOptions({ fontSize: parseInt(size) });
  10103. GM_setValue('editorFontSize', size);
  10104. });
  10105.  
  10106. // 全屏按钮
  10107. let fullscreenButton = OJB_safeCreateJQElement(`
  10108. <button type="button" class="ojb_btn ojb_btn_popover top">
  10109. <i class="iconfont">&#xe606;</i>
  10110. <span class="popover_content">${i18next.t('fullscreenButton', { ns: 'codeEditor' })}</span>
  10111. </button>
  10112. `);
  10113. form.topRightDiv.append(fullscreenButton);
  10114. fullscreenButton.on('click', enterFullscreen);
  10115.  
  10116. // 固定到底部按钮
  10117. let fixToBottomButton = OJB_safeCreateJQElement(`
  10118. <button type="button" class="ojb_btn ojb_btn_popover top">
  10119. <i class="iconfont">&#xe607;</i>
  10120. <span class="popover_content">${i18next.t('fixToBottomButton', { ns: 'codeEditor' })}</span>
  10121. </button>
  10122. `);
  10123. form.topRightDiv.append(fixToBottomButton);
  10124. fixToBottomButton.on('click', fixToBottom);
  10125.  
  10126. // 固定到右侧按钮
  10127. let fixToRightButton = OJB_safeCreateJQElement(`
  10128. <button type="button" class="ojb_btn ojb_btn_popover top">
  10129. <i class="iconfont">&#xe605;</i>
  10130. <span class="popover_content">${i18next.t('fixToRightButton', { ns: 'codeEditor' })}</span>
  10131. </button>
  10132. `);
  10133. // form.topRightDiv.append(fixToRightButton);
  10134. fixToRightButton.on('click', fixToRight);
  10135.  
  10136. // 添加测试/提交按钮到顶部
  10137. if (OJBetter.monaco.setting.submitButtonPosition == "top") {
  10138. form.topRightDiv.append(form.runButton);
  10139. form.topRightDiv.append(form.submitButton);
  10140. }
  10141.  
  10142. // 选择记忆
  10143. if (!OJBetter.monaco.setting.position_initialized) {
  10144. OJBetter.monaco.setting.position_initialized = true; // 标记是否已经初始化过
  10145. if (OJBetter.monaco.setting.position == "full") {
  10146. fullscreenButton.click();
  10147. } else if (OJBetter.monaco.setting.position == "bottom") {
  10148. fixToBottomButton.click();
  10149. } else if (OJBetter.monaco.setting.position == "right") {
  10150. fixToRightButton.click();
  10151. }
  10152. }
  10153.  
  10154. // 禁用按钮
  10155. function disableButtons() {
  10156. fullscreenButton.prop("disabled", true);
  10157. fixToBottomButton.prop("disabled", true);
  10158. fixToRightButton.prop("disabled", true);
  10159. }
  10160.  
  10161. // 启用按钮
  10162. function enableButtons() {
  10163. fullscreenButton.prop("disabled", false);
  10164. fixToBottomButton.prop("disabled", false);
  10165. fixToRightButton.prop("disabled", false);
  10166. }
  10167.  
  10168. // 进入全屏
  10169. function enterFullscreen() {
  10170. let editor = $('#OJBetter_editor');
  10171. editor.addClass('fullscreen');
  10172.  
  10173. // 取消按钮
  10174. let cancelButton = OJB_safeCreateJQElement(`
  10175. <button type="button" class="ojb_btn ojb_btn_popover top primary exit_button_bottom">
  10176. <i class="iconfont">&#xe60b;</i>
  10177. <span class="popover_content">${i18next.t('exitFullscreenButton', { ns: 'codeEditor' })}</span>
  10178. </button>
  10179. `).on('click', () => exitFullscreen(cancelButton));
  10180. $('body').append(cancelButton);
  10181.  
  10182. disableButtons();
  10183. GM_setValue("monacoEditor_position", "full");
  10184. }
  10185.  
  10186. // 退出全屏
  10187. const exitFullscreen = (cancelButton) => {
  10188. let editor = $('#OJBetter_editor');
  10189. editor.removeClass('fullscreen');
  10190. cancelButton.remove();
  10191. enableButtons();
  10192. GM_setValue("monacoEditor_position", "initial");
  10193. };
  10194.  
  10195. // 固定到底部
  10196. function fixToBottom() {
  10197. let editor = $('#OJBetter_editor');
  10198. editor.addClass('bottom');
  10199.  
  10200. let halfHeight = $(window).height() * 0.5;
  10201. let blankSpace = $('<div>', {
  10202. 'class': 'blank-space',
  10203. 'style': 'height: ' + (halfHeight + 30) + 'px;'
  10204. });
  10205. $('body').append(blankSpace);
  10206.  
  10207. let cancelButton = OJB_safeCreateJQElement(`
  10208. <button type="button" class="ojb_btn ojb_btn_popover top enabled exit_button_bottom">
  10209. <i class="iconfont">&#xe625;</i>
  10210. <span class="popover_content">${i18next.t('cancelFixButton', { ns: 'codeEditor' })}</span>
  10211. </button>
  10212. `).on('click', () => cancelFixingToBottom(cancelButton, blankSpace));
  10213. $('body').append(cancelButton);
  10214.  
  10215. disableButtons();
  10216. GM_setValue("monacoEditor_position", "bottom");
  10217. }
  10218.  
  10219. // 取消固定到底部
  10220. const cancelFixingToBottom = (cancelButton, blankSpace) => {
  10221. let editor = $('#OJBetter_editor');
  10222. editor.removeClass('bottom');
  10223. cancelButton.remove();
  10224. blankSpace.remove();
  10225. enableButtons();
  10226. GM_setValue("monacoEditor_position", "initial");
  10227. };
  10228.  
  10229. // 固定到右侧边栏
  10230. function fixToRight() {
  10231. const sidebar = $('#sidebar').hide();
  10232.  
  10233. // 添加样式
  10234. const styleElement = GM_addStyle(`
  10235. #body {
  10236. min-width: 50vw;
  10237. max-width: 50vw;
  10238. max-height: 100vh;
  10239. overflow-x: hidden;
  10240. overflow-y: auto;
  10241. padding: 1rem;
  10242. box-sizing: border-box;
  10243. }
  10244. body {
  10245. margin: 0px;
  10246. }
  10247. .content-with-sidebar {
  10248. margin-right: 0px !important;
  10249. }
  10250. .menu-list li {
  10251. margin-right: 0.5em;
  10252. }
  10253. .menu-list li a {
  10254. font-size: 1.4rem;
  10255. }
  10256. #OJBetter_editor{
  10257. height: 100vh;
  10258. width: 50vw;
  10259. }
  10260. `);
  10261.  
  10262. // 包装一层div
  10263. $('#body').wrap('<div id="right-side-wrapper" style="display:flex; max-width: 100vw; overflow: hidden;"></div>');
  10264. const blankSpace = $('<div>').appendTo('#right-side-wrapper');
  10265.  
  10266. // 移动编辑器
  10267. const editor = $('#OJBetter_editor').prependTo(blankSpace).addClass('right-side');
  10268.  
  10269. // 取消按钮
  10270. const cancelButton = OJB_safeCreateJQElement(`
  10271. <button type="button" class="ojb_btn ojb_btn_popover top enabled exit_button_bottom">
  10272. <i class="iconfont">&#xe625;</i>
  10273. <span class="popover_content">${i18next.t('cancelFixButton', { ns: 'codeEditor' })}</span>
  10274. </button>
  10275. `).on('click', () => cancelFixingToRight(sidebar, styleElement, editor, cancelButton)).appendTo('body');
  10276.  
  10277. disableButtons();
  10278. GM_setValue("monacoEditor_position", "right");
  10279.  
  10280. // 补丁:修复固定到右侧导致的样例元素.sample-test相关代码重复执行的问题(具体原因未查)
  10281. $('.sample-test').find('.title').each((i, e) => {
  10282. if ($(e).find('.input-output-copier').length > 1) {
  10283. $(e).find('.input-output-copier').first().remove();
  10284. }
  10285. });
  10286. darkModeStyleAdjustment();
  10287. }
  10288.  
  10289. const cancelFixingToRight = (sidebar, styleElement, editor, cancelButton) => {
  10290. sidebar.show();
  10291. // 移回来
  10292. editor.insertAfter(form.sourceDiv).removeClass('right-side');
  10293.  
  10294. // 移除包装
  10295. $('#body').unwrap();
  10296. cancelButton.remove();
  10297. styleElement.remove(); // 移除添加的样式
  10298.  
  10299. enableButtons();
  10300. GM_setValue("monacoEditor_position", "initial");
  10301. }
  10302.  
  10303. // 代码同步与保存
  10304. if (OJBetter.monaco.setting.autoMemoryCode) {
  10305. let nowUrl = window.location.href;
  10306. nowUrl = nowUrl.replace(/#/, ""); // 当页面存在更改时url会多出一个#,去掉
  10307. const code = await getCode(nowUrl);
  10308. if (code) {
  10309. OJBetter.monaco.editor.setValue(code); // 恢复代码
  10310. form.sourceDiv.val(code);
  10311. }
  10312. OJBetter.monaco.editor.onDidChangeModelContent(async () => {
  10313. // 将monaco editor的内容同步到sourceDiv
  10314. const code = OJBetter.monaco.editor.getValue();
  10315. form.sourceDiv.val(code);
  10316. await saveCode(nowUrl, code);
  10317. });
  10318. }
  10319. })();
  10320.  
  10321. /**
  10322. * 注册本地自动补全
  10323. */
  10324. (OJBetter_monaco.RegisterLocalComplet = async () => {
  10325. // 补全器注册函数
  10326. function registMyCompletionItemProvider(language, genre, rule) {
  10327. if (genre == "monaco") {
  10328. monaco.languages.registerCompletionItemProvider(language, {
  10329. provideCompletionItems: function (model, position) {
  10330. return parseMonacoCompleter(rule, OJBetter_monaco.CollectRangeByPosition(position));
  10331. }
  10332. })
  10333. } else if (genre == "ace") {
  10334. monaco.languages.registerCompletionItemProvider(language, {
  10335. provideCompletionItems: function (model, position) {
  10336. return parseAceCompleter(rule, OJBetter_monaco.CollectRangeByPosition(position));
  10337. }
  10338. })
  10339. }
  10340. }
  10341.  
  10342. // 注册acwing cpp 模板
  10343. if (language == "cpp" && OJBetter.monaco.complet.cppCodeTemplate) {
  10344. try {
  10345. var acwing_cpp_code_completer = JSON.parse(GM_getResourceText("acwing_cpp_code_completer"));
  10346. registMyCompletionItemProvider('cpp', 'ace', acwing_cpp_code_completer);
  10347. } catch (error) {
  10348. console.error("Error registering acwing cpp template:", error);
  10349. }
  10350. }
  10351.  
  10352. // 注册自定义的补全
  10353. let complet_length = OJBetter.monaco.complet.customConfig.configurations.length;
  10354. if (complet_length > 0) {
  10355. for (let i = 0; i < complet_length; i++) {
  10356. let item = OJBetter.monaco.complet.customConfig.configurations[i];
  10357. if (item.isChoose && item.language == language) {
  10358. try {
  10359. let rule = await OJB_getExternalJSON(item.jsonUrl);
  10360. registMyCompletionItemProvider(item.language, item.genre, rule);
  10361. } catch (error) {
  10362. console.error(`Error registering custom completer for ${item.language}:`, error);
  10363. }
  10364. }
  10365. }
  10366. }
  10367. })();
  10368.  
  10369. if (!support || !OJBetter.monaco.lsp.enabled) { return; } // 如果不支持lsp,则到此为止
  10370.  
  10371. /**
  10372. * LSP连接状态指示
  10373. */
  10374. const lspStateButton = OJB_safeCreateJQElement(`
  10375. <div id="lspStateDiv" class="ojb_btn ojb_btn_popover top loading">
  10376. <i class="iconfont">&#xe658;</i>
  10377. <span class="popover_content">${i18next.t('lsp.connect', { ns: 'codeEditor' })}</span>
  10378. </div>
  10379. `).on('click', () => {
  10380. OJB_showModal(LSPLogDiv);
  10381. LSPLogDiv.show();
  10382. });
  10383. form.topRightDiv.prepend(lspStateButton);
  10384.  
  10385. const LSPLogDiv = OJB_safeCreateJQElement(`
  10386. <dialog id="LSPLog" style="display: none;">
  10387. <button class="ojb_btn">${i18next.t('close', { ns: 'common' })}</button>
  10388. <div id="LSPLogList" style="overflow: auto;"></div>
  10389. <dialog>`);
  10390. $('body').append(LSPLogDiv);
  10391.  
  10392. const LSPLogList = $('<ul></ul>');
  10393. $('#LSPLogList').append(LSPLogList);
  10394.  
  10395. const closeButton = LSPLogDiv.find('button');
  10396. closeButton.on('click', function () {
  10397. OJB_closeModal(LSPLogDiv);
  10398. });
  10399.  
  10400. /**
  10401. * 推送新的消息到LSP日志中
  10402. * @param {'error' | 'warn' | 'info'} status
  10403. * @param {string} msg
  10404. * @param {boolean} data
  10405. */
  10406. function pushLSPLogMessage(status, msg, data) {
  10407. var li = $('<li>').text('[' + new Date().toLocaleString() + '] ' + msg);
  10408. if (status === 'error') {
  10409. li.attr('style', 'color: #f44336;');
  10410. } else if (status === 'warn') {
  10411. li.attr('style', 'color: #ff9800;');
  10412. } else if (status === 'info') {
  10413. li.attr('style', 'color: #616161;');
  10414. }
  10415. if (data) {
  10416. var jsonText = JSON.stringify(data, null, 2);
  10417. var details = $('<details>');
  10418. var summary = $('<summary>').text('Data');
  10419. var pre = $('<pre>').text(jsonText);
  10420. details.append(summary, pre);
  10421. li.append(details);
  10422. }
  10423. LSPLogList.append(li);
  10424. }
  10425.  
  10426. /**
  10427. * 添加状态底栏
  10428. */
  10429. var statusBar = $('<div id="OJBetter_statusBar">');
  10430. form.editorDiv.append(statusBar);
  10431.  
  10432. /**
  10433. * languageSocket
  10434. */
  10435. var url = OJBetter.monaco.lsp.socketUrl;
  10436. var languageSocket = new WebSocket(url + language);
  10437. OJBetter.monaco.lsp.socket.push(languageSocket);
  10438. var languageSocketState = false;
  10439. var responseHandlers = new Map(); // 映射表,需要等待返回数据的请求 -> 对应的事件触发函数
  10440.  
  10441. languageSocket.onopen = () => {
  10442. languageSocketState = true;
  10443. lspStateButton.setButtonPopover(i18next.t('lsp.waitingAnswer', { ns: 'codeEditor' }));
  10444. pushLSPLogMessage("info", `languageSocket ${i18next.t('lsp.socket.open', { ns: 'logMessage' })}`);
  10445. };
  10446. languageSocket.onmessage = (event) => {
  10447. const message = JSON.parse(event.data);
  10448. if (message.id === 0 && message.result) {
  10449. // 初始化完成
  10450. lspStateButton.setButtonState('success', i18next.t('lsp.connected', { ns: 'codeEditor' }));
  10451. pushLSPLogMessage("info", `Initialization ${i18next.t('lsp.state.finished', { ns: 'logMessage' })}`);
  10452. serverInfo = message.result; // 存下服务器支持信息
  10453. OJBetter_monaco.openDocRequest(); // 打开文档
  10454. if (!OJBetter.monaco.setting.language.includes(language)) {
  10455. OJBetter.monaco.setting.language.push(language);
  10456. OJBetter_monaco.RegistrationAfterInit(); // 注册语言及功能
  10457. } else {
  10458. location.reload(); // 这里有问题,先贴个补丁
  10459. }
  10460. OJBetter_monaco.PassiveReceiveHandler(); // 注册被动接收函数
  10461. } else if (message.id === 0 && message.error) {
  10462. pushLSPLogMessage("warn", `Initialization ${i18next.t('lsp.state.error', { ns: 'logMessage' })}`);
  10463. } else if (message.id !== undefined && responseHandlers.has(message.id)) {
  10464. // 如果收到带有id字段的消息,则回传给对应的事件触发函数
  10465. const handler = responseHandlers.get(message.id);
  10466. if (handler) {
  10467. handler(message);
  10468. responseHandlers.delete(message.id); // 删除已处理的事件触发函数
  10469. }
  10470. } else if (message.method == "textDocument/publishDiagnostics") {
  10471. // 接收代码诊断推送
  10472. OJBetter_monaco.updateMarkers(message);
  10473. } else if (message.method == "workspace/applyEdit") {
  10474. // 应用服务器推送的更改
  10475. OJBetter_monaco.applyEdit(message);
  10476. }
  10477. };
  10478. languageSocket.onerror = (error) => {
  10479. pushLSPLogMessage("error", `languageSocket ${i18next.t('lsp.state.error', { ns: 'logMessage' })}`, error);
  10480. console.warn(`Error connecting to languageSocket: ${error}`)
  10481. };
  10482. languageSocket.onclose = (event) => {
  10483. languageSocketState = false;
  10484. lspStateButton.setButtonState('error', i18next.t('lsp.error', { ns: 'codeEditor' }));
  10485. pushLSPLogMessage("warn", `languageSocket ${i18next.t('lsp.socket.close', { ns: 'logMessage' })}`);
  10486. };
  10487.  
  10488. /**
  10489. * 等待LanguageSocketState
  10490. */
  10491. async function waitForLanguageSocketState() {
  10492. return new Promise((resolve) => {
  10493. const checkInitialized = () => {
  10494. if (languageSocketState) {
  10495. resolve();
  10496. } else {
  10497. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  10498. }
  10499. };
  10500. checkInitialized();
  10501. });
  10502. }
  10503.  
  10504. // 等待lsp响应初始化结果
  10505. async function waitForInitialized() {
  10506. return new Promise((resolve) => {
  10507. const checkInitialized = () => {
  10508. if (initialized) {
  10509. resolve();
  10510. } else {
  10511. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  10512. }
  10513. };
  10514. checkInitialized();
  10515. });
  10516. }
  10517.  
  10518. /**
  10519. * 与languageSocket通信的包装方法
  10520. */
  10521. async function sendMessage(data, requiresResponse, callback) {
  10522. if (!initialized) {
  10523. await waitForInitialized(); // 等待initialized为真
  10524. }
  10525. if (requiresResponse) {
  10526. responseHandlers.set(data.id, callback) // 将事件触发函数与id关联起来
  10527. }
  10528. if (!languageSocketState) await waitForLanguageSocketState();
  10529. languageSocket.send(JSON.stringify(data));
  10530. }
  10531. // 发送消息并等待返回结果
  10532. function fetchData(params, callback) {
  10533. sendMessage(params, true, callback);
  10534. }
  10535. // 发送消息,不需要等待返回结果
  10536. function sendData(data) {
  10537. sendMessage(data, false);
  10538. }
  10539.  
  10540. /**
  10541. * 代码文件更新fileWebSocket
  10542. */
  10543. var fileWebSocket = new WebSocket(url + "file");
  10544. var fileWebSocketState = false;
  10545. OJBetter.monaco.lsp.socket.push(fileWebSocket);
  10546. fileWebSocket.onopen = () => {
  10547. fileWebSocketState = true;
  10548. pushLSPLogMessage("info", `fileWebSocket ${i18next.t('lsp.socket.open', { ns: 'logMessage' })}`);
  10549. };
  10550. fileWebSocket.onclose = (ev) => {
  10551. fileWebSocketState = false;
  10552. pushLSPLogMessage("warn", `fileWebSocket ${i18next.t('lsp.socket.close', { ns: 'logMessage' })}`, ev);
  10553. };
  10554. fileWebSocket.onmessage = (ev) => {
  10555. let message = JSON.parse(ev.data);
  10556. if (message.result !== "ok")
  10557. pushLSPLogMessage("error", `update file failed: ${ev}`);
  10558. };
  10559. fileWebSocket.onerror = (error) => {
  10560. console.warn(`Error connecting to fileWebSocket: ${error}`);
  10561. };
  10562. async function updateFile(workspace, filename, fileExtension, code) {
  10563. async function waitForfileWebSocketState() {
  10564. return new Promise((resolve) => {
  10565. const checkInitialized = () => {
  10566. if (fileWebSocketState) {
  10567. resolve();
  10568. } else {
  10569. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  10570. }
  10571. };
  10572. checkInitialized();
  10573. });
  10574. }
  10575. if (!fileWebSocketState) await waitForfileWebSocketState();
  10576. fileWebSocket.send(
  10577. JSON.stringify({
  10578. type: "update",
  10579. workspace,
  10580. filename,
  10581. fileExtension,
  10582. code,
  10583. })
  10584. );
  10585. }
  10586.  
  10587. /**
  10588. * 发送初始化请求
  10589. */
  10590. OJBetter_monaco.Initialize = () => {
  10591. //初始化initialize
  10592. const capabilities = {
  10593. workspace: {
  10594. applyEdit: true,
  10595. },
  10596. textDocument: {
  10597. publishDiagnostics: {
  10598. relatedInformation: true,
  10599. versionSupport: false,
  10600. tagSupport: {
  10601. valueSet: [1, 2],
  10602. },
  10603. codeDescriptionSupport: true,
  10604. },
  10605. completion: {
  10606. contextSupport: true,
  10607. completionItem: {
  10608. snippetSupport: true,
  10609. commitCharactersSupport: true,
  10610. documentationFormat: ["markdown", "plaintext"],
  10611. deprecatedSupport: true,
  10612. preselectSupport: true,
  10613. tagSupport: {
  10614. valueSet: [1],
  10615. },
  10616. insertReplaceSupport: true,
  10617. resolveSupport: {
  10618. properties: [
  10619. "documentation",
  10620. "detail",
  10621. "additionalTextEdits",
  10622. ],
  10623. },
  10624. insertTextModeSupport: {
  10625. valueSet: [1, 2],
  10626. },
  10627. },
  10628. },
  10629. hover: {
  10630. dynamicRegistration: true,
  10631. contentFormat: ["markdown", "plaintext"],
  10632. },
  10633. signatureHelp: {
  10634. signatureInformation: {
  10635. documentationFormat: ["markdown", "plaintext"],
  10636. parameterInformation: {
  10637. labelOffsetSupport: true,
  10638. },
  10639. activeParameterSupport: true,
  10640. },
  10641. contextSupport: true,
  10642. },
  10643. definition: {
  10644. dynamicRegistration: true,
  10645. linkSupport: true,
  10646. },
  10647. references: {
  10648. dynamicRegistration: true,
  10649. },
  10650. documentHighlight: {
  10651. dynamicRegistration: true,
  10652. },
  10653. codeAction: {
  10654. codeActionLiteralSupport: {
  10655. codeActionKind: {
  10656. valueSet:
  10657. language == "java"
  10658. ? []
  10659. : [
  10660. "",
  10661. "quickfix",
  10662. "refactor",
  10663. "refactor.extract",
  10664. "refactor.inline",
  10665. "refactor.rewrite",
  10666. "source",
  10667. "source.organizeImports",
  10668. ],
  10669. },
  10670. },
  10671. },
  10672. rename: {
  10673. dynamicRegistration: true,
  10674. prepareSupport: true,
  10675. prepareSupportDefaultBehavior: 1,
  10676. honorsChangeAnnotations: true,
  10677. },
  10678. documentLink: {
  10679. tooltipSupport: true,
  10680. },
  10681. typeDefinition: {
  10682. dynamicRegistration: true,
  10683. linkSupport: true,
  10684. },
  10685. implementation: {
  10686. dynamicRegistration: true,
  10687. linkSupport: true,
  10688. },
  10689. colorProvider: {
  10690. dynamicRegistration: true,
  10691. },
  10692. foldingRange: {
  10693. dynamicRegistration: true,
  10694. rangeLimit: 5000,
  10695. lineFoldingOnly: true,
  10696. },
  10697. declaration: {
  10698. dynamicRegistration: true,
  10699. linkSupport: true,
  10700. },
  10701. semanticTokens: {
  10702. dynamicRegistration: true,
  10703. tokenTypes: [
  10704. "namespace",
  10705. "type",
  10706. "class",
  10707. "enum",
  10708. "interface",
  10709. "struct",
  10710. "typeParameter",
  10711. "parameter",
  10712. "variable",
  10713. "property",
  10714. "enumMember",
  10715. "event",
  10716. "function",
  10717. "method",
  10718. "macro",
  10719. "keyword",
  10720. "modifier",
  10721. "comment",
  10722. "string",
  10723. "number",
  10724. "regexp",
  10725. "operator",
  10726. ],
  10727. tokenModifiers: [
  10728. "declaration",
  10729. "definition",
  10730. "readonly",
  10731. "static",
  10732. "deprecated",
  10733. "abstract",
  10734. "async",
  10735. "modification",
  10736. "documentation",
  10737. "defaultLibrary",
  10738. ],
  10739. formats: ["relative"],
  10740. requests: {
  10741. range: true,
  10742. full: {
  10743. delta: true,
  10744. },
  10745. },
  10746. multilineTokenSupport: false,
  10747. overlappingTokenSupport: false,
  10748. },
  10749. callHierarchy: {
  10750. dynamicRegistration: true,
  10751. },
  10752. },
  10753. window: {
  10754. showMessage: {
  10755. messageActionItem: {
  10756. additionalPropertiesSupport: true,
  10757. },
  10758. },
  10759. showDocument: {
  10760. support: true,
  10761. },
  10762. workDoneProgress: true,
  10763. },
  10764. general: {
  10765. regularExpressions: {
  10766. engine: "ECMAScript",
  10767. version: "ES2020",
  10768. },
  10769. markdown: {
  10770. parser: "marked",
  10771. version: "1.1.0",
  10772. },
  10773. },
  10774. };
  10775.  
  10776. const initializeRequest = {
  10777. id: id++,
  10778. jsonrpc: "2.0",
  10779. method: "initialize",
  10780. params: {
  10781. processId: null,
  10782. clientInfo: {
  10783. name: "CFMonaco" + InstanceID,
  10784. },
  10785. locale: "zh-CN",
  10786. rootPath: null,
  10787. rootUri: null,
  10788. capabilities: capabilities,
  10789. trace: "off",
  10790. workspaceFolders: [
  10791. {
  10792. uri:
  10793. "file:///" + OJBetter.monaco.lsp.workUri + workspace,
  10794. name:
  10795. "file:///" + OJBetter.monaco.lsp.workUri + workspace,
  10796. },
  10797. ],
  10798. },
  10799. };
  10800. languageSocket.send(JSON.stringify(initializeRequest));
  10801.  
  10802. // 打开文档函数
  10803. OJBetter_monaco.openDocRequest = function () {
  10804. const initializ = {
  10805. jsonrpc: "2.0",
  10806. method: "initialized",
  10807. params: {},
  10808. };
  10809. languageSocket.send(JSON.stringify(initializ));
  10810. const openDocRequest = {
  10811. jsonrpc: "2.0",
  10812. method: "textDocument/didOpen",
  10813. params: {
  10814. textDocument: {
  10815. uri: model.uri.toString(),
  10816. languageId: language,
  10817. version: model.getVersionId(),
  10818. text: model.getValue(),
  10819. },
  10820. },
  10821. };
  10822. languageSocket.send(JSON.stringify(openDocRequest));
  10823. initialized = true; // 初始化完成,这里确认逻辑待完善
  10824. };
  10825.  
  10826. // 初始化更新文件
  10827. updateFile(workspace, filename, fileExtension, model.getValue());
  10828. }
  10829.  
  10830. /**
  10831. * 注册语言及功能
  10832. */
  10833. OJBetter_monaco.RegistrationAfterInit = () => {
  10834. // 注册语言
  10835. monaco.languages.register({ id: language });
  10836.  
  10837. // 注册"Command"
  10838. (function registerCommand() {
  10839. serverInfo.capabilities.executeCommandProvider.commands.forEach(
  10840. (item) => {
  10841. pushLSPLogMessage("info", `${i18next.t('lsp.server.regist', { ns: 'logMessage' })}`, item);
  10842. monaco.editor.registerCommand(item, (accessor, ...args) => {
  10843. sendData({
  10844. jsonrpc: "2.0",
  10845. id: id++,
  10846. method: "workspace/executeCommand",
  10847. params: {
  10848. command: item,
  10849. arguments: args,
  10850. },
  10851. });
  10852. });
  10853. }
  10854. );
  10855. })();
  10856.  
  10857. // 注册"增量更新"
  10858. model.onDidChangeContent((event) => {
  10859. updateFile(workspace, filename, fileExtension, model.getValue()); // 更新文件
  10860. const changeDocRequest = {
  10861. jsonrpc: "2.0",
  10862. method: "textDocument/didChange",
  10863. params: {
  10864. textDocument: {
  10865. uri: model.uri.toString(),
  10866. version: model.getVersionId(),
  10867. },
  10868. contentChanges: event.changes.map((change) => ({
  10869. range: OJBetter_monaco.MonacoRangeTolspRange(change.range),
  10870. rangeLength: change.rangeLength,
  10871. text: change.text,
  10872. })),
  10873. },
  10874. };
  10875. sendData(changeDocRequest);
  10876. });
  10877.  
  10878. //注册"自动补全"
  10879. monaco.languages.registerCompletionItemProvider(language, {
  10880. provideCompletionItems: (model, position, context) => {
  10881. const request = {
  10882. jsonrpc: "2.0",
  10883. id: id++,
  10884. method: "textDocument/completion",
  10885. params: {
  10886. textDocument: {
  10887. uri: model.uri.toString(),
  10888. },
  10889. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  10890. context: {
  10891. triggerKind: context.triggerKind + 1, // 这里要+1,两边的定义不一样。。。
  10892. triggerCharacter: context.triggerCharacter,
  10893. },
  10894. },
  10895. };
  10896. return new Promise((resolve, reject) => {
  10897. fetchData(request, (response) => {
  10898. const result = response.result;
  10899. pushLSPLogMessage("info", `completion ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10900. if (!result) return resolve(null);
  10901. const CompletionItems = {
  10902. suggestions: result.items.map(
  10903. ({
  10904. label,
  10905. kind,
  10906. filterText,
  10907. insertText,
  10908. insertTextFormat,
  10909. sortText,
  10910. textEdit,
  10911. documentation,
  10912. additionalTextEdits,
  10913. }) => ({
  10914. additionalTextEdits: additionalTextEdits
  10915. ? additionalTextEdits.map(({ newText, range }) => ({
  10916. text: newText,
  10917. range: OJBetter_monaco.lspRangeToMonacoRange(range),
  10918. }))
  10919. : [],
  10920. documentation: documentation ? documentation.value : "",
  10921. filterText,
  10922. insertText: insertText ? insertText : textEdit.newText,
  10923. insertTextRules:
  10924. insertTextFormat === 2
  10925. ? monaco.languages.CompletionItemInsertTextRule
  10926. .InsertAsSnippet
  10927. : monaco.languages.CompletionItemInsertTextRule
  10928. .KeepWhitespace,
  10929. kind,
  10930. label,
  10931. sortText,
  10932. range: textEdit
  10933. ? textEdit.range
  10934. ? OJBetter_monaco.lspRangeToMonacoRange(textEdit.range)
  10935. : OJBetter_monaco.lspRangeToMonacoRange(textEdit.insert)
  10936. : null,
  10937. })
  10938. ),
  10939. };
  10940. pushLSPLogMessage("info", `completion ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, CompletionItems);
  10941. resolve(CompletionItems);
  10942. });
  10943. });
  10944. },
  10945. });
  10946.  
  10947. // 注册"代码修复"
  10948. monaco.languages.registerCodeActionProvider(language, {
  10949. provideCodeActions: (model, range, context) => {
  10950. const request = {
  10951. id: id++,
  10952. jsonrpc: "2.0",
  10953. method: "textDocument/codeAction",
  10954. params: {
  10955. textDocument: {
  10956. uri: model.uri.toString(),
  10957. },
  10958. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  10959. context: {
  10960. diagnostics: context.markers.map((item) => ({
  10961. range: OJBetter_monaco.MonacoRangeTolspRange({
  10962. startLineNumber: item.startLineNumber,
  10963. startColumn: item.startColumn,
  10964. endLineNumber: item.endLineNumber,
  10965. endColumn: item.endColumn,
  10966. }),
  10967. severity: OJBetter_monaco.MonacoSeverityTolspSeverity(
  10968. item.severity
  10969. ),
  10970. code: item.code,
  10971. source: item.source,
  10972. message: item.message,
  10973. tags: item.tags,
  10974. relatedInformation: item.relatedInformation
  10975. ? item.relatedInformation.map((item) => ({
  10976. location: {
  10977. uri: item.resource.toString(),
  10978. range: OJBetter_monaco.MonacoRangeTolspRange({
  10979. startLineNumber: item.startLineNumber,
  10980. startColumn: item.startColumn,
  10981. endLineNumber: item.endLineNumber,
  10982. endColumn: item.endColumn,
  10983. }),
  10984. },
  10985. message: item.message,
  10986. }))
  10987. : null,
  10988. })),
  10989. only: context.only ? [context.only] : [],
  10990. triggerKind: context.trigger,
  10991. },
  10992. },
  10993. };
  10994.  
  10995. return new Promise((resolve, reject) => {
  10996. fetchData(request, (response) => {
  10997. const result = response.result;
  10998. pushLSPLogMessage("info", `codeAction ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10999. if (!result) return resolve(null);
  11000. const codeAction = {
  11001. actions: result.map((item) => ({
  11002. title: item.title,
  11003. kind: item.kind ? item.kind : "quickfix",
  11004. command: item.command
  11005. ? item.command.command
  11006. ? {
  11007. id: item.command.command,
  11008. arguments: item.command.arguments,
  11009. title: item.command.title,
  11010. }
  11011. : null
  11012. : null,
  11013. diagnostics: item.diagnostics
  11014. ? item.diagnostics.map((item) => ({
  11015. code: item.code,
  11016. message: item.message,
  11017. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11018. severity: OJBetter_monaco.lspSeverityToMonacoSeverity(
  11019. item.severity
  11020. ),
  11021. source: item.source,
  11022. }))
  11023. : null,
  11024. edit: item.edit
  11025. ? OJBetter_monaco.lspEditToMonacoEdit(item.edit)
  11026. : item.arguments
  11027. ? {
  11028. edits: item.arguments.flatMap(
  11029. (item1) => OJBetter_monaco.lspEditToMonacoEdit(item1).edits
  11030. ),
  11031. }
  11032. : null,
  11033. })),
  11034. dispose: () => { },
  11035. };
  11036. pushLSPLogMessage("info", `codeAction ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, codeAction);
  11037.  
  11038. resolve(codeAction);
  11039. });
  11040. });
  11041. },
  11042. });
  11043.  
  11044. // 注册"hover提示"
  11045. monaco.languages.registerHoverProvider(language, {
  11046. provideHover: (model, position) => {
  11047. const request = {
  11048. jsonrpc: "2.0",
  11049. id: id++,
  11050. method: "textDocument/hover",
  11051. params: {
  11052. textDocument: {
  11053. uri: model.uri.toString(),
  11054. },
  11055. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11056. },
  11057. };
  11058.  
  11059. return new Promise((resolve, reject) => {
  11060. fetchData(request, (response) => {
  11061. pushLSPLogMessage("info", `Hover ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11062. const result = response.result;
  11063.  
  11064. if (!result) return resolve(null);
  11065. const Hover = {
  11066. range: result.range
  11067. ? OJBetter_monaco.lspRangeToMonacoRange(result.range)
  11068. : new monaco.Range(
  11069. position.lineNumber,
  11070. position.column,
  11071. position.lineNumber,
  11072. position.column
  11073. ),
  11074. contents: Array.isArray(result.contents)
  11075. ? result.contents.map((item) => ({
  11076. value: item.value ? item.value : item,
  11077. }))
  11078. : [
  11079. {
  11080. value: result.contents.value,
  11081. },
  11082. ],
  11083. };
  11084. pushLSPLogMessage("info", `Hover ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, Hover);
  11085. resolve(Hover);
  11086. });
  11087. });
  11088. },
  11089. });
  11090.  
  11091. // 注册"inlay提示"
  11092. if (language == "cpp" || language == "java")
  11093. monaco.languages.registerInlayHintsProvider(language, {
  11094. provideInlayHints: (model, range, token) => {
  11095. return new Promise((resolve, reject) => {
  11096. const request = {
  11097. jsonrpc: "2.0",
  11098. id: id++,
  11099. method: "textDocument/inlayHint",
  11100. params: {
  11101. textDocument: {
  11102. uri: model.uri.toString(),
  11103. },
  11104. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  11105. },
  11106. };
  11107.  
  11108. fetchData(request, (response) => {
  11109. const result = response.result;
  11110. pushLSPLogMessage("info", `Inlay Hints ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11111.  
  11112. if (!result) return resolve(null);
  11113.  
  11114. const inlayHints = {
  11115. hints: result.map((item) => {
  11116. return {
  11117. kind: item.kind,
  11118. label: item.label,
  11119. paddingLeft: item.paddingLeft,
  11120. paddingRight: item.paddingRight,
  11121. position: {
  11122. lineNumber: item.position.line + 1,
  11123. column: item.position.character + 1,
  11124. },
  11125. };
  11126. }),
  11127. };
  11128. pushLSPLogMessage("info", `Inlay Hints ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, inlayHints);
  11129.  
  11130. resolve(inlayHints);
  11131. });
  11132. });
  11133. },
  11134. });
  11135.  
  11136. // 注册"转到定义"
  11137. monaco.languages.registerDefinitionProvider(language, {
  11138. provideDefinition: (model, position) => {
  11139. const request = {
  11140. jsonrpc: "2.0",
  11141. id: id++,
  11142. method: "textDocument/definition",
  11143. params: {
  11144. textDocument: {
  11145. uri: model.uri.toString(),
  11146. },
  11147. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11148. },
  11149. };
  11150.  
  11151. return new Promise((resolve, reject) => {
  11152. fetchData(request, (response) => {
  11153. const result = response.result;
  11154. pushLSPLogMessage("info", `definition ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11155.  
  11156. if (result.length == 0) return resolve(null);
  11157. const definition = result.map((item) => ({
  11158. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11159. uri: monaco.Uri.parse(item.uri), //
  11160. }));
  11161. pushLSPLogMessage("info", `definition ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, definition);
  11162.  
  11163. resolve(definition);
  11164. });
  11165. });
  11166.  
  11167. },
  11168. });
  11169.  
  11170. // 注册"转到引用"
  11171. monaco.languages.registerReferenceProvider(language, {
  11172. provideReferences: (model, position, context) => {
  11173. const request = {
  11174. jsonrpc: "2.0",
  11175. id: id++,
  11176. method: "textDocument/references",
  11177. params: {
  11178. context: context,
  11179. textDocument: {
  11180. uri: model.uri.toString(),
  11181. },
  11182. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11183. },
  11184. };
  11185.  
  11186. return new Promise((resolve, reject) => {
  11187. fetchData(request, (response) => {
  11188. const result = response.result;
  11189. pushLSPLogMessage("info", `references ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11190.  
  11191. if (result.length == 0) return resolve([]);
  11192.  
  11193. const references = result.map((item) => ({
  11194. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11195. uri: monaco.Uri.parse(item.uri), //
  11196. }));
  11197. pushLSPLogMessage("info", `references ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, references);
  11198. resolve(references);
  11199. });
  11200. });
  11201. },
  11202. });
  11203.  
  11204. // 注册"符号引用点击高亮"
  11205. monaco.languages.registerDocumentHighlightProvider(language, {
  11206. provideDocumentHighlights: (model, position) => {
  11207. const request = {
  11208. jsonrpc: "2.0",
  11209. id: id++,
  11210. method: "textDocument/documentHighlight",
  11211. params: {
  11212. textDocument: {
  11213. uri: model.uri.toString(),
  11214. },
  11215. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11216. },
  11217. };
  11218.  
  11219. return new Promise((resolve, reject) => {
  11220. fetchData(request, (response) => {
  11221. const result = response.result;
  11222. pushLSPLogMessage("info", `documentHighlight ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11223.  
  11224. if (!result || result.length == 0) return resolve([]);
  11225. const highlights = result.map((item) => ({
  11226. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11227. kind: item.kind,
  11228. }));
  11229. pushLSPLogMessage("info",
  11230. `documentHighlight ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`,
  11231. highlights
  11232. );
  11233.  
  11234. resolve(highlights);
  11235. });
  11236. });
  11237. },
  11238. });
  11239.  
  11240. // 注册"文件链接"
  11241. if (language == "cpp" || language == "java")
  11242. monaco.languages.registerLinkProvider(language, {
  11243. provideLinks: (model) => {
  11244. const request = {
  11245. jsonrpc: "2.0",
  11246. id: id++,
  11247. method: "textDocument/documentLink",
  11248. params: {
  11249. textDocument: {
  11250. uri: model.uri.toString(),
  11251. },
  11252. },
  11253. };
  11254.  
  11255. return new Promise((resolve, reject) => {
  11256. fetchData(request, (response) => {
  11257. const result = response.result;
  11258. pushLSPLogMessage("info", `DocumentLink ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11259.  
  11260. if (!result) return resolve(null);
  11261. const links = {
  11262. links: result.map((item) => ({
  11263. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11264. url: item.target.toString(),
  11265. tooltip: item.tooltip ? item.tooltip : null,
  11266. })),
  11267. };
  11268. pushLSPLogMessage("info", `DocumentLink ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, links);
  11269. resolve(links);
  11270. });
  11271. });
  11272. },
  11273. });
  11274.  
  11275. // 注册"格式化"
  11276. monaco.languages.registerDocumentFormattingEditProvider(language, {
  11277. provideDocumentFormattingEdits: (model, options, token) => {
  11278. const request = {
  11279. jsonrpc: "2.0",
  11280. id: id++,
  11281. method: "textDocument/formatting",
  11282. params: {
  11283. textDocument: {
  11284. uri: model.uri.toString(),
  11285. },
  11286. options: options,
  11287. },
  11288. };
  11289.  
  11290. return new Promise((resolve, reject) => {
  11291. fetchData(request, (response) => {
  11292. const result = response.result;
  11293. pushLSPLogMessage("info", `formatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11294.  
  11295. const TextEdit = result.map((edit) => ({
  11296. range: OJBetter_monaco.lspRangeToMonacoRange(edit.range),
  11297. text: edit.newText,
  11298. }));
  11299. pushLSPLogMessage("info", `formatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, TextEdit);
  11300. resolve(TextEdit);
  11301. });
  11302. });
  11303. },
  11304. });
  11305.  
  11306. // 注册"部分格式化"
  11307. monaco.languages.registerDocumentRangeFormattingEditProvider(language, {
  11308. provideDocumentRangeFormattingEdits: (model, range, options) => {
  11309. const request = {
  11310. jsonrpc: "2.0",
  11311. id: id++,
  11312. method: "textDocument/rangeFormatting",
  11313. params: {
  11314. textDocument: {
  11315. uri: model.uri.toString(),
  11316. },
  11317. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  11318. options,
  11319. },
  11320. };
  11321.  
  11322. return new Promise((resolve, reject) => {
  11323. fetchData(request, (response) => {
  11324. const result = response.result;
  11325. pushLSPLogMessage("info", `rangeFormatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11326.  
  11327. if (!result || result.length == 0) return resolve([]);
  11328. const edits = result.map((item) => ({
  11329. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11330. text: item.newText,
  11331. }));
  11332. pushLSPLogMessage("info", `rangeFormatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, edits);
  11333. resolve(edits);
  11334. });
  11335. });
  11336. },
  11337. });
  11338.  
  11339. // 注册"重命名"
  11340. monaco.languages.registerRenameProvider(language, {
  11341. provideRenameEdits: (model, position, newName, token) => {
  11342. const request = {
  11343. jsonrpc: "2.0",
  11344. id: id++,
  11345. method: "textDocument/rename",
  11346. params: {
  11347. textDocument: {
  11348. uri: model.uri.toString(),
  11349. },
  11350. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11351. newName: newName,
  11352. },
  11353. };
  11354.  
  11355. return new Promise((resolve, reject) => {
  11356. fetchData(request, (response) => {
  11357. const result = response.result;
  11358. pushLSPLogMessage("info", `rename ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11359.  
  11360. const rename = OJBetter_monaco.lspEditToMonacoEdit(result);
  11361. pushLSPLogMessage("info", `rename ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, rename);
  11362. resolve(rename);
  11363. });
  11364. });
  11365. },
  11366. });
  11367.  
  11368. // 注册"折叠范围分析"
  11369. monaco.languages.registerFoldingRangeProvider(language, {
  11370. provideFoldingRanges: (model) => {
  11371. const request = {
  11372. jsonrpc: "2.0",
  11373. id: id++,
  11374. method: "textDocument/foldingRange",
  11375. params: {
  11376. textDocument: {
  11377. uri: model.uri.toString(),
  11378. },
  11379. },
  11380. };
  11381.  
  11382. return new Promise((resolve, reject) => {
  11383. fetchData(request, (response) => {
  11384. const result = response.result;
  11385. pushLSPLogMessage("info", `FoldingRange ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11386.  
  11387. if (!result) return resolve([]);
  11388. const foldingRanges = result.map((item) => ({
  11389. start: item.startLine + 1,
  11390. end: item.endLine + 1,
  11391. kind: monaco.languages.FoldingRangeKind.fromValue(item.kind),
  11392. }));
  11393. pushLSPLogMessage("info", `FoldingRange ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, foldingRanges);
  11394. resolve(foldingRanges);
  11395. });
  11396. });
  11397. },
  11398. });
  11399.  
  11400. // 注册"方法签名提示"
  11401. monaco.languages.registerSignatureHelpProvider(language, {
  11402. signatureHelpTriggerCharacters:
  11403. serverInfo.capabilities.signatureHelpProvider.triggerCharacters,
  11404. provideSignatureHelp: (model, position, token, context) => {
  11405. const request = {
  11406. jsonrpc: "2.0",
  11407. id: id++,
  11408. method: "textDocument/signatureHelp",
  11409. params: {
  11410. textDocument: {
  11411. uri: model.uri.toString(),
  11412. },
  11413. position: {
  11414. line: position.lineNumber - 1,
  11415. character: position.column - 1,
  11416. },
  11417. context: {
  11418. triggerKind: context.triggerKind,
  11419. triggerCharacter: context.triggerCharacter,
  11420. isRetrigger: context.isRetrigger,
  11421. activeSignatureHelp: context.activeSignatureHelp,
  11422. },
  11423. },
  11424. };
  11425.  
  11426. return new Promise((resolve, reject) => {
  11427. fetchData(request, (response) => {
  11428. const result = response.result;
  11429.  
  11430. pushLSPLogMessage("info", `signatureHelp ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11431.  
  11432. if (!result) return resolve(null);
  11433. const SignatureHelpResult = {
  11434. value: {
  11435. activeParameter: result.activeParameter,
  11436. activeSignature: result.activeSignature,
  11437. signatures: result.signatures,
  11438. },
  11439. dispose: () => { },
  11440. };
  11441.  
  11442. pushLSPLogMessage("info",
  11443. `signatureHelp ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`,
  11444. SignatureHelpResult
  11445. );
  11446. resolve(SignatureHelpResult);
  11447. });
  11448. });
  11449. },
  11450. });
  11451.  
  11452. // 注册"渐进式自动格式化" 如果server有这个
  11453. if (serverInfo.capabilities.documentOnTypeFormattingProvider)
  11454. monaco.languages.registerOnTypeFormattingEditProvider(language, {
  11455. autoFormatTriggerCharacters: [
  11456. serverInfo.capabilities.documentOnTypeFormattingProvider
  11457. .firstTriggerCharacter,
  11458. ],
  11459. provideOnTypeFormattingEdits: (model, position, ch, options) => {
  11460. const request = {
  11461. jsonrpc: "2.0",
  11462. id: id++,
  11463. method: "textDocument/onTypeFormatting",
  11464. params: {
  11465. textDocument: {
  11466. uri: model.uri.toString(),
  11467. },
  11468. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11469. ch,
  11470. options,
  11471. },
  11472. };
  11473.  
  11474. return new Promise((resolve, reject) => {
  11475. fetchData(request, (response) => {
  11476. const result = response.result;
  11477. pushLSPLogMessage("info", `onTypeFormatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11478.  
  11479. if (!result || result.length == 0) return resolve([]);
  11480.  
  11481. const edits = result.map((item) => ({
  11482. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11483. text: item.newText,
  11484. }));
  11485. pushLSPLogMessage("info", `onTypeFormatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, edits);
  11486. resolve(edits);
  11487. });
  11488. });
  11489. },
  11490. });
  11491. };
  11492.  
  11493. /**
  11494. * 被动式接收处理
  11495. */
  11496. OJBetter_monaco.PassiveReceiveHandler = () => {
  11497.  
  11498. // "实时代码诊断"
  11499. OJBetter_monaco.updateMarkers = function (message) {
  11500. const params = message.params;
  11501. pushLSPLogMessage("info", `Markers ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, message);
  11502.  
  11503. if (!params) return;
  11504. const markers = params.diagnostics.map((item1) => ({
  11505. code: item1.code,
  11506. message: item1.message,
  11507. ...OJBetter_monaco.lspRangeToMonacoRange(item1.range),
  11508. relatedInformation: item1.relatedInformation
  11509. ? item1.relatedInformation.map((item2) => ({
  11510. ...(item2.location.range
  11511. ? OJBetter_monaco.lspRangeToMonacoRange(item2.location.range)
  11512. : OJBetter_monaco.lspRangeToMonacoRange(item2.location)),
  11513. message: item2.message,
  11514. resource: monaco.Uri.parse(item2.location.uri),
  11515. }))
  11516. : null,
  11517. severity: OJBetter_monaco.lspSeverityToMonacoSeverity(item1.severity),
  11518. source: item1.source,
  11519. }));
  11520.  
  11521. pushLSPLogMessage("info", `Markers ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, markers);
  11522. monaco.editor.setModelMarkers(model, "eslint", markers);
  11523.  
  11524. // 更新状态底栏信息
  11525. const nowMarks = monaco.editor.getModelMarkers();
  11526. warningCount = 0;
  11527. errorCount = 0;
  11528. for (const marker of nowMarks) {
  11529. if (marker.severity === monaco.MarkerSeverity.Warning) {
  11530. warningCount++;
  11531. } else if (marker.severity === monaco.MarkerSeverity.Error) {
  11532. errorCount++;
  11533. }
  11534. }
  11535. $('#OJBetter_statusBar').text(`Warnings: ${warningCount}, Errors: ${errorCount}`);
  11536. };
  11537.  
  11538. // "应用服务器推送的更改"(代码修复)
  11539. OJBetter_monaco.applyEdit = function (message) {
  11540. const params = message.params;
  11541. pushLSPLogMessage("info", `applyEdit ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, message);
  11542.  
  11543. if (!params) return;
  11544. const operations = Object.values(params.edit.changes)
  11545. .flat()
  11546. .map((item) => ({
  11547. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11548. text: item.newText,
  11549. }));
  11550.  
  11551. pushLSPLogMessage("info", `applyEdit ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, operations);
  11552. model.pushEditOperations([], operations, () => null); // 入栈编辑操作
  11553. };
  11554. }
  11555.  
  11556. if (!languageSocketState) await waitForLanguageSocketState();
  11557. OJBetter_monaco.Initialize();
  11558. }
  11559.  
  11560. // 语言更改
  11561. function changeMonacoLanguage(form) {
  11562. let nowSelect = form.selectLang.val();
  11563.  
  11564. // 这里是因为在Chrome上Select2会莫名其妙触发一次不会改变值的change事件,而在其他浏览器中没有,所以贴个补丁
  11565. if (nowSelect === OJBetter.monaco.nowLangSelect) return;
  11566. else OJBetter.monaco.nowLangSelect = nowSelect;
  11567.  
  11568. // 记忆更改
  11569. GM_setValue('compilerSelection', nowSelect);
  11570. // 销毁旧的编辑器
  11571. try {
  11572. if (OJBetter.monaco.editor) OJBetter.monaco.editor.dispose();
  11573. } catch (error) {
  11574. console.warn("Encountered an error while attempting to delete the old editor, but it should not affect your regular usage.", error)
  11575. }
  11576. // 关闭旧的socket
  11577. OJBetter.monaco.lsp.socket.forEach(socket => {
  11578. socket.close();
  11579. });
  11580. // 移除相关元素
  11581. form.topRightDiv.empty();
  11582. $('#LSPLog').remove();
  11583. $('#OJBetter_statusBar').remove();
  11584. // 创建新的编辑器
  11585. if (nowSelect in value_monacoLanguageMap) {
  11586. let language = value_monacoLanguageMap[nowSelect];
  11587. if (language == "python" || language == "cpp") {
  11588. createMonacoEditor(language, form, true);
  11589. } else {
  11590. createMonacoEditor(language, form, false);
  11591. }
  11592. } else {
  11593. createMonacoEditor(null, form, false);
  11594. }
  11595. // 更新在线编译器参数
  11596. changeCompilerArgs(nowSelect);
  11597. }
  11598.  
  11599. // 收集样例数据
  11600. function getTestData() {
  11601. let testData = {};
  11602.  
  11603. /**
  11604. * 从pre中获取文本信息
  11605. * @param {JQuery<HTMLElement>} node 元素
  11606. * @returns {string} 文本信息
  11607. */
  11608. function getTextFromPre(node) {
  11609. let text;
  11610. if (node.find("br").length > 0) {
  11611. text = node.html().replace(/<br>/g, "\n"); // <br>作换行符的情况
  11612. } else {
  11613. text = node.text();
  11614. }
  11615. return text;
  11616. }
  11617.  
  11618. // $('.input').each(function (index) {
  11619. // var inputText = '';
  11620. // if ($(this).find('pre').find('div').length > 0) {
  11621. // $(this).find('pre').find('div').each(function () {
  11622. // inputText += getTextFromPre($(this)) + '\n';
  11623. // });
  11624. // } else {
  11625. // inputText = getTextFromPre($(this).find('pre'));
  11626. // }
  11627. // var outputText = '';
  11628. // if ($('.output').eq(index).find('pre').find('div').length > 0) {
  11629. // $('.output').eq(index).find('pre').find('div').each(function () {
  11630. // inputText += getTextFromPre($(this)) + '\n';
  11631. // });
  11632. // } else {
  11633. // outputText = getTextFromPre($('.output').eq(index).find('pre'));
  11634. // }
  11635.  
  11636. // testData[index + 1] = {
  11637. // input: inputText.trim(),
  11638. // output: outputText.trim()
  11639. // };
  11640. // });
  11641.  
  11642. // 需要过滤重复的样例(因为有日文英文两个页面,样例元素重复了两遍)
  11643. let uniqueTestData = [];
  11644. const filteredPreElements = $('pre').clone().filter((index, element) => element.id.startsWith('pre-sample'));
  11645. for (let i = 0; i < filteredPreElements.length; i += 2) {
  11646. if (i + 1 < filteredPreElements.length) {
  11647. const inputElement = $(filteredPreElements[i]);
  11648. const outputElement = $(filteredPreElements[i + 1]);
  11649.  
  11650. const inputText = getTextFromPre(inputElement).trim();
  11651. const outputText = getTextFromPre(outputElement).trim();
  11652.  
  11653. // 检查是否已经存在相同的样例
  11654. let isDuplicate = uniqueTestData.some(testCase => testCase.input === inputText && testCase.output === outputText);
  11655. if (!isDuplicate) {
  11656. uniqueTestData.push({
  11657. input: inputText,
  11658. output: outputText
  11659. });
  11660. }
  11661. }
  11662. }
  11663.  
  11664. // 把唯一的测试数据赋值给testData,保持原有的索引风格
  11665. uniqueTestData.forEach((testCase, index) => {
  11666. testData[index + 1] = testCase;
  11667. });
  11668.  
  11669. return testData;
  11670. }
  11671.  
  11672. // 初始化自定义测试数据面板
  11673. function CustomTestInit() {
  11674. const url = window.location.href;
  11675.  
  11676. restoreText();
  11677.  
  11678. // 添加
  11679. $('#addCustomTest').click(function () {
  11680. var sampleDiv = $('<div class="sampleDiv">');
  11681. var inputTextarea = $('<p style="padding: 0px 5px;">input</p><textarea class="dynamicTextarea inputTextarea"></textarea>');
  11682. var outputTextarea = $('<p style="padding: 0px 5px;">output</p><textarea class="dynamicTextarea outputTextarea"></textarea>');
  11683. var deleteCustomTest = OJB_safeCreateJQElement(`<button type="button" class="deleteCustomTest">${closeIcon}</button>`);
  11684. sampleDiv.append(deleteCustomTest);
  11685. sampleDiv.append(inputTextarea);
  11686. sampleDiv.append(outputTextarea);
  11687. $('#customTests').append(sampleDiv);
  11688. });
  11689.  
  11690. // 实时保存文本内容到 IndexedDB 中
  11691. $(document).on('input', '.inputTextarea, .outputTextarea', function () {
  11692. OJBetter.common.database.transaction('rw', OJBetter.common.database.samplesData, function () {
  11693. var objectStore = OJBetter.common.database.samplesData;
  11694. var samples = {
  11695. url: url,
  11696. samples: []
  11697. };
  11698. var index = 0;
  11699. $('.sampleDiv').each(function () {
  11700. var $sampleDiv = $(this);
  11701. var inputTextarea = $sampleDiv.find('.inputTextarea');
  11702. var outputTextarea = $sampleDiv.find('.outputTextarea');
  11703. $sampleDiv.attr('data-index', index);
  11704. inputTextarea.attr('id', 'input' + index);
  11705. outputTextarea.attr('id', 'output' + index);
  11706. var sample = {
  11707. id: index,
  11708. input: inputTextarea.val(),
  11709. output: outputTextarea.val()
  11710. };
  11711. samples.samples.push(sample);
  11712. index++;
  11713. });
  11714. objectStore.put(samples);
  11715. });
  11716. });
  11717.  
  11718. // 删除
  11719. $(document).on('click', '.deleteCustomTest', function () {
  11720. var $sampleDiv = $(this).closest('.sampleDiv');
  11721. OJBetter.common.database.transaction('rw', OJBetter.common.database.samplesData, function () {
  11722. var objectStore = OJBetter.common.database.samplesData;
  11723. var index = parseInt($sampleDiv.attr('data-index'));
  11724. if (!isNaN(index)) {
  11725. objectStore.get(url).then(row => {
  11726. let samples = row.samples;
  11727. samples.splice(index, 1); // 移除第index个元素
  11728. objectStore.put({
  11729. url: url,
  11730. samples: samples
  11731. });
  11732. })
  11733. }
  11734. $sampleDiv.remove();
  11735. });
  11736. });
  11737.  
  11738. // 恢复保存的内容
  11739. function restoreText() {
  11740. OJBetter.common.database.transaction('r', OJBetter.common.database.samplesData, function () {
  11741. return OJBetter.common.database.samplesData.get(url);
  11742. }).then(function (data) {
  11743. if (data.samples && data.samples.length > 0) {
  11744. data.samples.forEach(function (item, index) {
  11745. var sampleDiv = $('<div class="sampleDiv">');
  11746. var inputTextarea = OJB_safeCreateJQElement(`<p style="padding: 0px 5px;">input</p><textarea id="input${index}" class="dynamicTextarea inputTextarea"></textarea>`);
  11747. var outputTextarea = OJB_safeCreateJQElement(`<p style="padding: 0px 5px;">output</p><textarea id="output${index}" class="dynamicTextarea outputTextarea"></textarea>`);
  11748. var deleteCustomTest = OJB_safeCreateJQElement(`<button type="button" class="deleteCustomTest">${closeIcon}</button>`);
  11749.  
  11750. inputTextarea.val(item.input);
  11751. outputTextarea.val(item.output);
  11752.  
  11753. sampleDiv.append(deleteCustomTest);
  11754. sampleDiv.append(inputTextarea);
  11755. sampleDiv.append(outputTextarea);
  11756. sampleDiv.attr('data-index', index)
  11757. $('#customTests').append(sampleDiv);
  11758. });
  11759. }
  11760. });
  11761. }
  11762. }
  11763.  
  11764. // 获取自定义测试数据
  11765. function getCustomTestData() {
  11766. const url = window.location.href;
  11767.  
  11768. return new Promise(function (resolve) {
  11769. var customTestData = {};
  11770. OJBetter.common.database.transaction('r', OJBetter.common.database.samplesData, function () {
  11771. return OJBetter.common.database.samplesData.get(url);
  11772. }).then(function (data) {
  11773. if (!data) resolve(customTestData);
  11774. if (data.samples && data.samples.length > 0) {
  11775. data.samples.forEach(function (item, index) {
  11776. customTestData[index + 1] = {
  11777. input: item.input,
  11778. output: item.output
  11779. };
  11780. });
  11781. }
  11782. resolve(customTestData);
  11783. });
  11784. });
  11785. }
  11786.  
  11787. // codeforces编译器参数列表
  11788. let officialLanguage = "";
  11789. function officialCompilerArgsChange(nowSelect) {
  11790. officialLanguage = nowSelect;
  11791. $('#CompilerArgsInput').prop("disabled", true);
  11792. }
  11793.  
  11794. // codeforces编译器通信
  11795. async function officialCompiler(code, input) {
  11796. // const data = new FormData();
  11797. // data.append('csrf_token', OJBetter.common.cf_csrf_token);
  11798. const data = new URLSearchParams();
  11799. data.append('csrf_token', OJBetter.common.at_csrf_token);
  11800. // data.append('source', code);
  11801. // data.append('tabSize', '4');
  11802. // data.append('programTypeId', officialLanguage);
  11803. data.append('data.LanguageId', officialLanguage);
  11804. data.append('input', input);
  11805. // data.append('output', '');
  11806. // data.append('communityCode', '');
  11807. // data.append('action', 'submitSourceCode');
  11808. // data.append('programTypeId', officialLanguage);
  11809. data.append('sourceCode', code);
  11810.  
  11811. const requestOptions = {
  11812. method: 'POST',
  11813. // url: `${OJBetter.common.hostAddress}/data/customtest`,
  11814. url: `${OJBetter.common.hostAddress}/contests/arc172/custom_test/submit/json`,
  11815. data: data,
  11816. headers: {
  11817. // 'X-Csrf-Token': OJBetter.common.cf_csrf_token
  11818. 'Content-Type': 'application/x-www-form-urlencoded'
  11819. }
  11820. };
  11821.  
  11822. const result = {
  11823. Errors: '',
  11824. Result: '',
  11825. Stats: ''
  11826. };
  11827.  
  11828. try {
  11829. const submitResponse = await OJB_GMRequest(requestOptions);
  11830. // if (submitResponse.status !== 200 || !submitResponse.response) {
  11831. // result.Errors = `${i18next.t('compiler.official.pushError', { ns: 'codeEditor' })}`;
  11832. // return result;
  11833. // }
  11834. if (submitResponse.status !== 200) {
  11835. result.Errors = `${i18next.t('compiler.official.pushError', { ns: 'codeEditor' })}`;
  11836. return result;
  11837. }
  11838.  
  11839. // const submitResult = JSON.parse(submitResponse.response);
  11840. // const customTestSubmitId = submitResult.customTestSubmitId;
  11841.  
  11842. const verdictResponse = await OJB_promiseRetryWrapper(
  11843. getOfficialCompilerVerdict,
  11844. {
  11845. maxRetries: 10,
  11846. retryInterval: 500
  11847. },
  11848. // customTestSubmitId
  11849. );
  11850. return verdictResponse;
  11851. } catch (error) {
  11852. result.Errors = error.message;
  11853. return result;
  11854. }
  11855. }
  11856.  
  11857. // 获取codeforces编译器的执行结果
  11858. // async function getOfficialCompilerVerdict(customTestSubmitId) {
  11859. async function getOfficialCompilerVerdict() {
  11860. // const newdata = new FormData();
  11861. // newdata.append('csrf_token', OJBetter.common.cf_csrf_token);
  11862. // newdata.append('action', 'getVerdict');
  11863. // newdata.append('customTestSubmitId', customTestSubmitId);
  11864.  
  11865. // const requestOptions = {
  11866. // method: 'POST',
  11867. // url: `${OJBetter.common.hostAddress}/data/customtest`,
  11868. // data: newdata,
  11869. // headers: {
  11870. // 'X-Csrf-Token': OJBetter.common.cf_csrf_token
  11871. // }
  11872. // };
  11873. const requestOptions = {
  11874. method: 'GET',
  11875. url: `https://atcoder.jp/contests/arc172/custom_test/json?reload=true`,
  11876. };
  11877.  
  11878. const responseDetails = await OJB_GMRequest(requestOptions);
  11879. if (responseDetails.status !== 200 || !responseDetails.response) {
  11880. throw new Error(`${i18next.t('compiler.official.getResultError', { ns: 'codeEditor' })}`);
  11881. }
  11882.  
  11883. const response = JSON.parse(responseDetails.response);
  11884. // if (!response.stat) {
  11885. // throw new Error('Verdict not ready, retrying...');
  11886. // }
  11887. if (response.Interval) {
  11888. throw new Error('Verdict not ready, retrying...');
  11889. }
  11890.  
  11891. // return {
  11892. // Errors: response.verdict === "OK" ? null : response.verdict + '<br>' + response.output,
  11893. // Result: response.output.replace(/\r\n/g, "\n"),
  11894. // Stats: `Status: ${response.stat}`
  11895. // };
  11896. return {
  11897. Errors: response.Result.ExitCode === "0" ? null : response.Stderr,
  11898. Result: response.Stdout.replace(/\r\n/g, "\n"),
  11899. Stats: `Status: ExitCode: ${response.Result.ExitCode} Exec Time: ${response.Result.TimeConsumption} ms Memory: ${response.Result.MemoryConsumption} KB`
  11900. };
  11901. }
  11902.  
  11903. // rextester编译器参数列表
  11904. let rextesterLanguage = "";
  11905. function rextesterCompilerArgsChange(nowSelect) {
  11906. // let LanguageChoiceList = {
  11907. // "4": "9", "6": "8", "7": "5", "9": "1", "13": "13", "19": "42", "20": "21", "28": "30", "31": "24", "32": "20",
  11908. // "34": "17", "36": "4", "43": "6", "45": "7", "46": "4", "50": "7", "51": "9", "52": "27", "54": "7", "55": "23", "60": "4",
  11909. // "61": "7", "65": "1", "67": "12", "70": "5", "73": "7", "74": "4", "75": "46", "77": "43", "79": "1", "80": "27", "83": "43", "87": "4"
  11910. // }
  11911. let LanguageChoiceList = {
  11912. "5001": "7", "5002": "20", "5003": "1", "5005": "4", "5009": "17", "5016": "8", "5037": "13", "5041": "9",
  11913. "5047": "21", "5055": "5", "5058": "17", "5063": "5", "5078": "5", "5082": "5"
  11914. }
  11915.  
  11916. let CompilerArgsList = {
  11917. "6": "-Wall -std=gnu99 -O2 -o a.out source_file.c",
  11918. "7": "-Wall -std=c++14 -O2 -o a.out source_file.cpp",
  11919. "20": "-o a.out source_file.go",
  11920. "27": "-Wall -std=c++14 -stdlib=libc++ -O2 -o a.out source_file.cpp",
  11921. "30": "source_file.d -ofa.out"
  11922. }
  11923. if (nowSelect in LanguageChoiceList) {
  11924. $('#RunTestButton').prop("disabled", false);
  11925. rextesterLanguage = LanguageChoiceList[nowSelect];
  11926. } else {
  11927. $('#RunTestButton').prop("disabled", true);
  11928. }
  11929. if (rextesterLanguage in CompilerArgsList) {
  11930. const rextesterCompilerArgs = CompilerArgsList[rextesterLanguage];
  11931. $('#CompilerArgsInput').val(rextesterCompilerArgs);
  11932. } else {
  11933. $('#CompilerArgsInput').val("");
  11934. }
  11935. }
  11936.  
  11937. // rextester编译器通信
  11938. async function rextesterCompiler(code, input) {
  11939. try {
  11940. const result = await OJB_promiseRetryWrapper(rextesterCompilerRequest, {
  11941. maxRetries: 5,
  11942. retryInterval: 500,
  11943. errorHandler: (err) => ({ Errors: err.message, Result: '', Stats: '' })
  11944. }, code, input);
  11945. return result;
  11946. } catch (error) {
  11947. return { Errors: error.message, Result: '', Stats: '' };
  11948. }
  11949. }
  11950.  
  11951. // rextester编译器请求方法
  11952. async function rextesterCompilerRequest(code, input) {
  11953. const data = new FormData();
  11954. data.append('LanguageChoiceWrapper', rextesterLanguage);
  11955. data.append('EditorChoiceWrapper', '1');
  11956. data.append('LayoutChoiceWrapper', '1');
  11957. data.append('Program', code);
  11958. data.append('CompilerArgs', $('#CompilerArgsInput').val());
  11959. data.append('Input', input);
  11960. data.append('ShowWarnings', 'false');
  11961. data.append('IsInEditMode', 'false');
  11962. data.append('IsLive', 'false');
  11963.  
  11964. const responseDetails = await OJB_GMRequest({
  11965. method: 'POST',
  11966. url: 'https://rextester.com/rundotnet/Run',
  11967. data: data
  11968. });
  11969.  
  11970. if (responseDetails.status !== 200 || !responseDetails.response) {
  11971. throw new Error(`${i18next.t('compiler.common.error', { ns: 'codeEditor' })}`);
  11972. }
  11973.  
  11974. const response = JSON.parse(responseDetails.response);
  11975. return {
  11976. Errors: response.Errors || '',
  11977. Result: response.Result || '',
  11978. Stats: response.Stats || ''
  11979. };
  11980. }
  11981.  
  11982. // wandbox编译器参数列表
  11983. var wandboxlist = JSON.parse(GM_getResourceText("wandboxlist"));
  11984. function wandboxCompilerArgsChange(nowSelect) {
  11985. // let LanguageChoiceList = {
  11986. // "6": "PHP", "7": "Python", "9": "C#", "12": "Haskell", "13": "Perl", "19": "OCaml",
  11987. // "20": "Scala", "28": "D", "31": "Python", "32": "Go", "34": "JavaScript", "36": "Java", "40": "Python", "41": "Python",
  11988. // "43": "C++", "50": "C++", "51": "Pascal", "52": "C++", "54": "C++", "60": "Java", "61": "C++", "65": "C#", "67": "Ruby",
  11989. // "70": "Python", "73": "C++", "74": "Java", "75": "Rust", "79": "C#", "80": "C++", "87": "Java"
  11990. // }
  11991. let LanguageChoiceList = {
  11992. "5001": "C++", "5002": "Go", "5003": "C#", "5005": "Java", "5009": "JavaScript", "5010": "JavaScript",
  11993. "5012": "D", "5013": "D", "5016": "PHP", "5017": "C++", "5018": "Ruby", "5025": "Haskell",
  11994. "5027": "Lua", "5028": "C++", "5031": "C++", "5037": "Perl", "5041": "Pascal", "5042": "C#",
  11995. "5043": "Lua", "5047": "Scala", "5055": "Python", "5056": "Scala", "5059": "OCaml", "5062": "Lisp",
  11996. "5063": "Python", "5077": "D", "5078": "Python", "5081": "OCaml", "5082": "Python"
  11997. }
  11998.  
  11999. // 移除旧的
  12000. $('#CompilerChange').remove();
  12001.  
  12002. if (nowSelect in LanguageChoiceList) {
  12003. $('#RunTestButton').prop("disabled", false);
  12004. const Languagefiltered = wandboxlist.filter(obj => obj.language === LanguageChoiceList[nowSelect]);
  12005.  
  12006. // 创建编译器下拉框
  12007. let CompilerChange = $('<select id="CompilerChange" style="width: 100%;"></select>');
  12008.  
  12009. $('#CompilerSetting').show().append(CompilerChange);
  12010. for (let i = 0; i < Languagefiltered.length; i++) {
  12011. let Compiler = Languagefiltered[i];
  12012. let op = $("<option></option>")
  12013. .val(Compiler.name)
  12014. .text(Compiler["display-name"] + " " + Compiler.version);
  12015. $("#CompilerChange").append(op);
  12016. }
  12017.  
  12018. // 编译器参数刷新
  12019. function refreshCompilerArgs() {
  12020. var flags = '';
  12021. $("#CompilerBox").find("*").each(function () {
  12022. if ($(this).is("input[type='checkbox']")) {
  12023. let flag = $(this).prop("checked") ? $(this).val() : '';
  12024. flags += flag + (flag ? ' ' : '');
  12025. } else if ($(this).is("select") || $(this).is("input") || $(this).is("textarea")) {
  12026. let flag = $(this).val();
  12027. flags += flag + (flag ? ' ' : '');
  12028. }
  12029. });
  12030. $("#CompilerArgsInput").val(flags);
  12031. $("#CompilerArgsInput").prop("readonly", true); // 只读
  12032. }
  12033.  
  12034. // 编译器切换监听
  12035. CompilerChange.change(function () {
  12036. let selectedName = CompilerChange.val();
  12037. let Compiler = Languagefiltered.find(
  12038. (obj) => obj.name === selectedName
  12039. );
  12040.  
  12041. $("#CompilerArgsInput").val(); // 初始化编译器输入框
  12042.  
  12043. $("#CompilerBox").remove();
  12044. let div = $("<div id='CompilerBox'></div>");
  12045.  
  12046. let display_compile_command = OJB_safeCreateJQElement(`<input id='${Compiler.name}' value='${Compiler['display-compile-command']}' style="display:none;"}></input>`);
  12047. div.append(display_compile_command);
  12048.  
  12049. let switches = Compiler.switches;
  12050. for (let i = 0; i < switches.length; i++) {
  12051. let switche = switches[i];
  12052.  
  12053. if (switche.type == "single") {
  12054. let single = OJB_safeCreateJQElement(`
  12055. <div>
  12056. <input type='checkbox' id='${switche.name}' value='${switche['display-flags']}' ${switche.default ? 'checked' : ''}></input>
  12057. <label for='${switche.name}'>${switche['display-name']}</label>
  12058. </div>
  12059. `);
  12060. div.append(single);
  12061. single.find("input").change(function () {
  12062. refreshCompilerArgs();
  12063. });
  12064. } else if (switche.type == "select") {
  12065. let select = OJB_safeCreateJQElement(`<select id='${switche.name}'></select>`);
  12066. select.data('previousValue', switche.options[0]['display-flags']);
  12067. div.append(select);
  12068. for (let i = 0; i < switche.options.length; i++) {
  12069. let option = switche.options[i];
  12070. let op = $("<option></option>")
  12071. .val(option['display-flags'])
  12072. .text(option['display-name']);
  12073. select.append(op);
  12074. }
  12075. select.change(function () {
  12076. refreshCompilerArgs();
  12077. });
  12078. }
  12079. }
  12080.  
  12081. if (Compiler['compiler-option-raw'] == true) {
  12082. let textarea = OJB_safeCreateJQElement(`<textarea id="compiler_option_raw" placeholder="Raw compiler options" style="resize: vertical;"></textarea>`);
  12083. div.append(textarea);
  12084. textarea.on('input', function () {
  12085. refreshCompilerArgs();
  12086. });
  12087. }
  12088. if (Compiler['runtime-option-raw'] == true) {
  12089. let textarea = OJB_safeCreateJQElement(`<textarea id="runtime_option_raw" placeholder="Raw runtime options" style="resize: vertical;"></textarea>`);
  12090. div.append(textarea);
  12091. textarea.on('input', function () {
  12092. refreshCompilerArgs();
  12093. });
  12094. }
  12095. $("#CompilerSetting").append(div);
  12096.  
  12097. refreshCompilerArgs(); // 初始化
  12098. });
  12099.  
  12100. CompilerChange.trigger("change"); // 初始化
  12101. } else {
  12102. $('#RunTestButton').prop("disabled", true);
  12103. }
  12104. }
  12105.  
  12106. // wandbox编译器通信
  12107. async function wandboxCompiler(code, input) {
  12108. try {
  12109. const result = await OJB_promiseRetryWrapper(wandboxCompilerRequest, {
  12110. maxRetries: 5,
  12111. retryInterval: 500,
  12112. errorHandler: (err) => ({ Errors: err.message, Result: '', Stats: '' })
  12113. }, code, input);
  12114. return result;
  12115. } catch (error) {
  12116. return { Errors: error.message, Result: '', Stats: '' };
  12117. }
  12118. }
  12119.  
  12120. // wandbox编译器请求方法
  12121. async function wandboxCompilerRequest(code, input) {
  12122. const data = {
  12123. code: code,
  12124. codes: [],
  12125. compiler: $('#CompilerChange').val().replace($('#compiler_option_raw').val(), '').replace($('#runtime_option_raw').val(), ''),
  12126. 'compiler-option-raw': $('#compiler_option_raw').val(),
  12127. 'runtime-option-raw': $('#runtime_option_raw').val(),
  12128. options: $("#CompilerArgsInput").val(),
  12129. description: '',
  12130. stdin: input,
  12131. title: ''
  12132. };
  12133.  
  12134. const responseDetails = await OJB_GMRequest({
  12135. method: 'POST',
  12136. url: 'https://wandbox.org/api/compile.json',
  12137. data: JSON.stringify(data),
  12138. headers: {
  12139. 'Content-Type': 'application/json'
  12140. }
  12141. });
  12142.  
  12143. if (responseDetails.status !== 200 || !responseDetails.response) {
  12144. throw new Error(`${i18next.t('compiler.common.error', { ns: 'codeEditor' })}`);
  12145. }
  12146.  
  12147. const response = JSON.parse(responseDetails.response);
  12148. return {
  12149. Errors: response.compiler_error === "" ? response.signal : response.compiler_error,
  12150. Result: response.program_output || '',
  12151. Stats: response.status === "0" ? "Finish" : "Error"
  12152. };
  12153. }
  12154.  
  12155. // 更改编译器参数
  12156. function changeCompilerArgs(nowSelect) {
  12157. if (OJBetter.monaco.onlineCompilerChoice == "official") {
  12158. officialCompilerArgsChange(nowSelect);
  12159. } else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
  12160. rextesterCompilerArgsChange(nowSelect);
  12161. } else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
  12162. wandboxCompilerArgsChange(nowSelect);
  12163. }
  12164. }
  12165.  
  12166. // 在线编译器通信
  12167. async function onlineCompilerConnect(code, input) {
  12168. if (OJBetter.monaco.onlineCompilerChoice == "official") {
  12169. return await officialCompiler(code, input);
  12170. } else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
  12171. return await rextesterCompiler(code, input);
  12172. } else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
  12173. return await wandboxCompiler(code, input);
  12174. }
  12175. }
  12176.  
  12177. // 差异对比
  12178. function codeDiff(expectedText, actualText) {
  12179. // 将文本按行拆分
  12180. const expectedLines = expectedText ? expectedText.split('\n') : [];
  12181. const actualLines = actualText ? actualText.split('\n') : [];
  12182.  
  12183. const output = document.createElement('div');
  12184.  
  12185. const createLineElement = (lineNo, contentElement) => {
  12186. const lineDiv = document.createElement('div');
  12187. lineDiv.className = 'diffLine';
  12188.  
  12189. const lineNoDiv = document.createElement('div');
  12190. lineNoDiv.className = 'lineNo';
  12191. lineNoDiv.textContent = lineNo;
  12192.  
  12193. lineDiv.appendChild(lineNoDiv);
  12194. lineDiv.appendChild(contentElement);
  12195.  
  12196. return lineDiv;
  12197. };
  12198.  
  12199. const createContentElement = (isEquals = true, expected = null, removed = null) => {
  12200. const contentDiv = document.createElement('div');
  12201. contentDiv.className = 'lineContent';
  12202.  
  12203. if (isEquals) {
  12204. const span = document.createElement('span');
  12205. span.textContent = expected;
  12206. contentDiv.appendChild(span);
  12207. } else {
  12208. if (removed != null) {
  12209. const removedSpan = document.createElement('span');
  12210. removedSpan.className = 'removed';
  12211. removedSpan.textContent = removed;
  12212. contentDiv.appendChild(removedSpan);
  12213. }
  12214. if (expected != null) {
  12215. const addedSpan = document.createElement('span');
  12216. addedSpan.className = 'added';
  12217. addedSpan.textContent = expected;
  12218. contentDiv.appendChild(addedSpan);
  12219. }
  12220. }
  12221.  
  12222. return contentDiv;
  12223. };
  12224.  
  12225. let index = 1;
  12226.  
  12227. expectedLines.forEach((expectedLine, i) => {
  12228. const actualLine = actualLines[i];
  12229. if (actualLine === undefined) {
  12230. output.appendChild(createLineElement(index++, createContentElement(false, expectedLine)));
  12231. } else if (expectedLine === actualLine) {
  12232. output.appendChild(createLineElement(index++, createContentElement(true, expectedLine)));
  12233. } else {
  12234. output.appendChild(createLineElement(index++, createContentElement(false, expectedLine, actualLine)));
  12235. }
  12236. });
  12237.  
  12238. // 处理多余的 actualLines
  12239. for (let i = expectedLines.length; i < actualLines.length; i++) {
  12240. output.appendChild(createLineElement(index++, createContentElement(false, null, actualLines[i])));
  12241. }
  12242.  
  12243. return output.innerHTML;
  12244. }
  12245.  
  12246. // 内容类型常量
  12247. const TestCaseContentType = {
  12248. TERMINAL: 'terminal',
  12249. DIFF: 'diff',
  12250. NO_DIFF: 'no_diff',
  12251. SUCCESS: 'success'
  12252. };
  12253.  
  12254. // 样例测试状态类
  12255. class TestCaseStatus {
  12256. constructor(item, prefix) {
  12257. this.testCase = OJB_safeCreateJQElement(`<div class="test-case"></div>`);
  12258. this.item = item;
  12259. this.prefix = prefix;
  12260. this.titleElement = OJB_safeCreateJQElement(`<div class="test-case-title">${this.prefix} ${this.item}</div>`);
  12261. this.statusElement = OJB_safeCreateJQElement(`<div class="test-case-status"></div>`);
  12262. this.contentElement = OJB_safeCreateJQElement(`<div class="test-case-content"></div>`);
  12263. this.judgeElement = OJB_safeCreateJQElement(`<div class="test-case-judge"></div>`);
  12264. this.testCase.append(this.titleElement, this.statusElement, this.contentElement, this.judgeElement);
  12265. $('#statePanel').append(this.testCase);
  12266. this.setStatus('Queued', 'queued');
  12267. }
  12268.  
  12269. setTitle(title) {
  12270. this.titleElement.text(title);
  12271. }
  12272.  
  12273. setStatus(text, status) {
  12274. this.statusElement.text(text).removeClass('queued running success error').addClass(status);
  12275. }
  12276.  
  12277. setContent(content, type) {
  12278. // 如果内容类型为SUCCESS,隐藏内容元素并提前返回
  12279. if (type === TestCaseContentType.SUCCESS) {
  12280. this.contentElement.hide();
  12281. return;
  12282. }
  12283.  
  12284. // 根据内容类型创建内容元素
  12285. const createContentElementByType = (content, type) => {
  12286. let contentElement;
  12287. switch (type) {
  12288. case TestCaseContentType.TERMINAL:
  12289. // 为TERMINAL类型创建一个新的终端容器
  12290. contentElement = OJB_safeCreateJQElement(`<div class="terminal-container" style="overflow: auto;"></div>`);
  12291. break;
  12292. case TestCaseContentType.DIFF:
  12293. case TestCaseContentType.NO_DIFF:
  12294. // 为DIFF和NO_DIFF类型创建相应的内容元素,并添加差异说明
  12295. const className = type === TestCaseContentType.DIFF ? "output_diff" : "output_no_diff";
  12296. contentElement = OJB_safeCreateJQElement(`<pre class="${className}">${content}</pre>`);
  12297. appendDiffNote();
  12298. break;
  12299. default:
  12300. throw new Error("Unsupported content type.");
  12301. }
  12302. return contentElement;
  12303. };
  12304.  
  12305. // 初始化终端
  12306. const initializeTerminal = (content, contentElement) => {
  12307. const term = new Terminal({ rows: 10, cols: 150 });
  12308. term.setOption('theme', { background: '#2d2e2c' });
  12309. term.setOption('convertEol', true); // 将换行符\n转换为\r\n
  12310. term.write(content);
  12311. term.open(contentElement.get(0));
  12312. };
  12313.  
  12314. // 添加差异说明
  12315. const appendDiffNote = () => {
  12316. const diffNote = OJB_safeCreateJQElement(`<div class="diff_note">${i18next.t('resultBlock.diffNote', { ns: 'codeEditor' })}</div>`);
  12317. this.testCase.append(diffNote);
  12318. };
  12319.  
  12320. // 创建并追加内容元素
  12321. const contentElement = createContentElementByType(content, type);
  12322. this.contentElement.append(contentElement);
  12323.  
  12324. // 如果内容类型为TERMINAL,初始化并打开终端
  12325. if (type === TestCaseContentType.TERMINAL) {
  12326. initializeTerminal(content, contentElement);
  12327. }
  12328. }
  12329.  
  12330. setJudge(judge) {
  12331. this.judgeElement.text(judge);
  12332. }
  12333. }
  12334.  
  12335. // 样例测试函数
  12336. async function runCode(event, runButton, sourceDiv) {
  12337. event.preventDefault();
  12338. const statePanel = $('#statePanel').show().empty();
  12339. const testData = getTestData();
  12340. const customTestData = await getCustomTestData();
  12341. const totalTests = Object.keys(customTestData).length + Object.keys(testData).length;
  12342.  
  12343. let passedTests = 0;
  12344. let failedTests = 0;
  12345. let hasError = false;
  12346.  
  12347. // 定义一个对象队列,包括创建的样例块实例和对应的样例数据
  12348. const queue = [];
  12349.  
  12350. // 先生成各个样例的块,并显示排队中,将创建的各个对象存到队列中,以便后面更新
  12351. for (const [item, data] of Object.entries(customTestData)) {
  12352. const testCase = new TestCaseStatus(item, i18next.t('resultBlock.title.custom', { ns: 'codeEditor' }));
  12353. queue.push({ testCase, data });
  12354. }
  12355.  
  12356. if (!$('#onlyCustomTest').prop('checked')) {
  12357. for (const [item, data] of Object.entries(testData)) {
  12358. const testCase = new TestCaseStatus(item, i18next.t('resultBlock.title.sample', { ns: 'codeEditor' }));
  12359. queue.push({ testCase, data });
  12360. }
  12361. }
  12362.  
  12363. // 测试函数
  12364. const runTest = async (testCase, data, index) => {
  12365. runButton.setButtonState('running', `${index}/${totalTests}`);
  12366.  
  12367. testCase.setStatus('Running', 'running');
  12368. const result = await onlineCompilerConnect(sourceDiv.val(), data.input);
  12369.  
  12370. if (result.Errors) {
  12371. testCase.setStatus('Compilation error or Time limit', 'error');
  12372. testCase.setContent(result.Errors, TestCaseContentType.TERMINAL);
  12373. hasError = true;
  12374. } else if (result.Result.trim() === data.output.trim()) {
  12375. testCase.setStatus('Accepted', 'success');
  12376. testCase.setContent('The output is correct.', TestCaseContentType.SUCCESS);
  12377. passedTests++;
  12378. } else {
  12379. testCase.setStatus('Wrong Answer', 'error');
  12380. const diffContent = $('#DontShowDiff').prop('checked') ? result.Result.trim() : codeDiff(data.output.trim(), result.Result.trim());
  12381. const contentType = $('#DontShowDiff').prop('checked') ? TestCaseContentType.NO_DIFF : TestCaseContentType.DIFF;
  12382. testCase.setContent(diffContent, contentType);
  12383. failedTests++;
  12384. }
  12385.  
  12386. const judgeStats = `${i18next.t('resultBlock.state', { ns: 'codeEditor' })}${result.Stats}`;
  12387. testCase.setJudge(judgeStats);
  12388.  
  12389. await OJB_delay(500); // 等待500毫秒
  12390. };
  12391.  
  12392. // 对队列中的对象进行测试
  12393. for (let i = 0; i < queue.length; i++) {
  12394. const { testCase, data } = queue[i];
  12395. await runTest(testCase, data, i + 1);
  12396. }
  12397.  
  12398. // 测试完成后更新按钮状态
  12399. if (hasError) {
  12400. runButton.setButtonState('error', i18next.t('runTestButton.error', { ns: 'codeEditor' }));
  12401. } else if (failedTests > 0) {
  12402. runButton.setButtonState('error', `${passedTests}/${totalTests} ` + i18next.t('runTestButton.partial', { ns: 'codeEditor' }));
  12403. } else {
  12404. runButton.setButtonState('success', i18next.t('runTestButton.success', { ns: 'codeEditor' }));
  12405. if (OJBetter.monaco.setting.autoSubmitAfterPass) {
  12406. $('#OJBetter_SubmitForm').submit(); // 自动提交
  12407. }
  12408. }
  12409. }
  12410.  
  12411. /**
  12412. * 添加题目页代码编辑器
  12413. * @returns
  12414. */
  12415. async function addProblemPageCodeEditor() {
  12416. // if (typeof ace === 'undefined') {
  12417. // const loadingMessage = new LoadingMessage();
  12418. // loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('error.codeEditor.load', { ns: 'alert' })}`, 'error');
  12419. // return; // 因为Codeforces设定的是未登录时不能访问提交页,也不会加载ace库
  12420. // }
  12421.  
  12422. // 获取提交页链接
  12423. const href = window.location.href;
  12424. let submitUrl = OJBetter.common.hostAddress + $('.form-code-submit').attr('action');
  12425. // if (/\/problemset\//.test(href)) {
  12426. // // problemset
  12427. // submitUrl = OJBetter.common.hostAddress + '/problemset/submit';
  12428. // } else if (/\/gym\//.test(href)) {
  12429. // // gym 题目
  12430. // submitUrl = OJBetter.common.hostAddress + '/gym/' + ((href) => {
  12431. // const regex = /\/gym\/(?<num>[0-9a-zA-Z]*?)\/problem\//;
  12432. // const match = href.match(regex);
  12433. // return match && match.groups.num;
  12434. // })(href) + '/submit';
  12435. // } else if (OJBetter.typeOfPage.is_acmsguru) {
  12436. // // acmsguru 题目
  12437. // submitUrl = href.replace(/\/problemsets[A-Za-z0-9\/#]*/, "/problemsets/acmsguru/submit");
  12438. // } else {
  12439. // submitUrl = href.replace(/\/problem[A-Za-z0-9\/#]*/, "/submit");
  12440. // }
  12441.  
  12442. // // 获取提交页HTML
  12443. // let cloneHTML = await getSubmitHTML(submitUrl);
  12444.  
  12445. // 创建
  12446. // let form = await createCodeEditorForm(submitUrl, cloneHTML);
  12447. let form = await createCodeEditorForm(submitUrl);
  12448. let selectLang = form.selectLang;
  12449. let submitButton = form.submitButton;
  12450. let runButton = form.runButton;
  12451.  
  12452. // 初始化
  12453. CustomTestInit(); // 自定义测试数据面板
  12454. selectLang.val(OJBetter.monaco.compilerSelection); // 恢复上一次的语言选择
  12455.  
  12456. // 设置语言选择change事件监听器
  12457. selectLang.on('change', () => {
  12458. changeMonacoLanguage(form); // 编辑器语言切换监听
  12459. });
  12460. changeMonacoLanguage(form);
  12461.  
  12462. // 样例测试
  12463. runButton.on('click', (event) => runCode(event, runButton, form.sourceDiv, form.submitDiv))
  12464. .setHoverRedo();
  12465.  
  12466. // 提交
  12467. submitButton.on('click', async function (event) {
  12468. event.preventDefault();
  12469. if (OJBetter.monaco.setting.isCodeSubmitDoubleConfirm) {
  12470. // 获取题目名
  12471. const questionTitle = (() => {
  12472. const element = document.querySelector('.h2');
  12473. return Array.from(element.childNodes)
  12474. .filter(node => node.nodeType === Node.TEXT_NODE)
  12475. .map(textNode => textNode.textContent.trim())
  12476. .join(' ');
  12477. })();
  12478. const submit = await OJB_createDialog(
  12479. i18next.t('submitCode.title', { ns: 'dialog' }),
  12480. i18next.t('submitCode.content', { ns: 'dialog', questionTitle: questionTitle }),
  12481. [
  12482. i18next.t('submitCode.buttons.0', { ns: 'dialog' }),
  12483. i18next.t('submitCode.buttons.1', { ns: 'dialog' })
  12484. ],
  12485. true
  12486. ); //提交确认
  12487. if (submit) {
  12488. submitButton.after(`<img class="OJBetter_loding" src="//codeforces.org/s/84141/images/ajax-loading-24x24.gif">`);
  12489. $('#OJBetter_SubmitForm').submit();
  12490. } else {
  12491. submitButton.addClass('disabled');
  12492. setTimeout(function () {
  12493. submitButton.removeClass('disabled');
  12494. }, 300);
  12495. }
  12496. } else {
  12497. $('#OJBetter_SubmitForm').submit();
  12498. }
  12499. });
  12500. }
  12501.  
  12502. /**
  12503. * 获取翻译服务目标语言的对应代码
  12504. * @param {string} serverName 服务名称
  12505. * @returns {string} 目标语言,如果没有对应代码则返回中文
  12506. */
  12507. function getTargetLanguage(serverName) {
  12508. let targetLanguage = OJBetter.supportList.translationSupport[serverName][OJBetter.translation.targetLang];
  12509. if (targetLanguage) return targetLanguage;
  12510. else return OJBetter.supportList.translationSupport[serverName]['zh'];
  12511. }
  12512.  
  12513. /**
  12514. * 将文本中Markdown格式的加粗**转换成HTML格式。
  12515. * @param {string} text 文本
  12516. * @returns {string} 替换后的字符串
  12517. */
  12518. function convertBoldMarkdownToHTML(text) {
  12519. return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
  12520. }
  12521.  
  12522. /**
  12523. * 将文本中Markdown格式的链接文本转换成HTML格式。
  12524. * @param {string} text 文本
  12525. * @returns {string} 替换后的字符串
  12526. */
  12527. function convertLinksMarkdownToHTML(text) {
  12528. return text.replace(/(?<!!)\[(.*?)\]\(([^"]*?)("(.*?)")*\)/g, '<a href="$2" title="$4">$1</a>');
  12529. }
  12530.  
  12531. /**
  12532. * 将HTML格式的加粗文本转换回Markdown格式。
  12533. * @param {string} text 文本
  12534. * @returns {string} 替换后的字符串
  12535. */
  12536. function convertBoldHTMLToMarkdown(text) {
  12537. return text.replace(/<strong>(.*?)<\/strong>/g, '**$1**');
  12538. }
  12539.  
  12540. /**
  12541. * 将HTML格式的链接文本转换回Markdown格式。
  12542. * @param {string} html - 包含HTML链接标签<a>的字符串。
  12543. * @returns {string} 转换后的字符串,其中HTML链接标签被替换为Markdown的链接语法。
  12544. */
  12545. function convertLinksHTMLToMarkdown(html) {
  12546. return html.replace(/<a href="([^"]*)"( title="([^"]*)")*>([^<]+)<\/a>/g, '[$4]($1 "$3")');
  12547. }
  12548.  
  12549. /**
  12550. * DeepL翻译
  12551. * @param {string} raw 原文
  12552. * @returns {Promise<TransRawData>} 翻译结果对象
  12553. */
  12554. async function translate_deepl(raw) {
  12555. const id = (Math.floor(Math.random() * 99999) + 100000) * 1000;
  12556. const data = {
  12557. jsonrpc: '2.0',
  12558. method: 'LMT_handle_texts',
  12559. id,
  12560. params: {
  12561. splitting: 'newlines',
  12562. lang: {
  12563. source_lang_user_selected: 'auto',
  12564. target_lang: getTargetLanguage('deepl'),
  12565. },
  12566. texts: [{
  12567. text: raw,
  12568. requestAlternatives: 3
  12569. }],
  12570. timestamp: getTimeStamp(raw.split('i').length - 1)
  12571. }
  12572. }
  12573. let postData = JSON.stringify(data);
  12574. if ((id + 5) % 29 === 0 || (id + 3) % 13 === 0) {
  12575. postData = postData.replace('"method":"', '"method" : "');
  12576. } else {
  12577. postData = postData.replace('"method":"', '"method": "');
  12578. }
  12579. const options = {
  12580. method: 'POST',
  12581. url: 'https://www2.deepl.com/jsonrpc',
  12582. data: postData,
  12583. headers: {
  12584. 'Content-Type': 'application/json',
  12585. 'Host': 'www2.deepl.com',
  12586. 'Origin': 'https://www.deepl.com',
  12587. 'Referer': 'https://www.deepl.com/',
  12588. },
  12589. anonymous: true,
  12590. nocache: true,
  12591. }
  12592.  
  12593. return await BaseTranslate(options, res => JSON.parse(res)?.result?.texts?.[0]?.text || res, res => {
  12594. const resObj = {
  12595. status: true,
  12596. message: 'ok'
  12597. };
  12598. if (res.includes('"message":"Too many requests"')) {
  12599. resObj.status = false;
  12600. resObj.message = i18next.t('error.deepl429', { ns: 'translator' }); // Too many requests 提示
  12601. return resObj;
  12602. };
  12603. return resObj;
  12604. });
  12605. }
  12606.  
  12607. /**
  12608. * 使用 DeepL Free API 进行翻译
  12609. * @param {string} raw 原文
  12610. * @returns {Promise<TransRawData>} 翻译结果对象
  12611. */
  12612. async function translate_deepl_api_free(raw) {
  12613. const data = JSON.stringify({
  12614. text: [raw],
  12615. target_lang: getTargetLanguage('deepl'),
  12616. split_sentences: '1',
  12617. ...(OJBetter.deepl.enableEmphasisProtection || OJBetter.deepl.enableLinkProtection ? { tag_handling: 'html' } : {}),
  12618. ...Object.assign({}, ...OJBetter.deepl.config.data)
  12619. });
  12620.  
  12621. const options = {
  12622. method: "POST",
  12623. url: OJBetter.deepl.config.proxy || "https://api-free.deepl.com/v2/translate",
  12624. headers: {
  12625. "Authorization": `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
  12626. "Content-Type": "application/json",
  12627. ...Object.assign({}, ...OJBetter.deepl.config.header)
  12628. },
  12629. data: data,
  12630. onload: response => response.responseText,
  12631. onerror: error => console.error(error)
  12632. };
  12633.  
  12634. return await BaseTranslate(options, res => JSON.parse(res).translations[0].text);
  12635. }
  12636.  
  12637. /**
  12638. * 使用 DeepL Pro API 进行翻译
  12639. * @param {string} raw 原文
  12640. * @returns {Promise<TransRawData>} 翻译结果对象
  12641. */
  12642. async function translate_deepl_api_pro(raw) {
  12643. const data = JSON.stringify({
  12644. text: [raw],
  12645. target_lang: getTargetLanguage('deepl'),
  12646. split_sentences: '1',
  12647. ...(OJBetter.deepl.enableEmphasisProtection || OJBetter.deepl.enableLinkProtection ? { tag_handling: 'html' } : {}),
  12648. ...Object.assign({}, ...OJBetter.deepl.config.data)
  12649. });
  12650.  
  12651. const options = {
  12652. method: "POST",
  12653. url: OJBetter.deepl.config.proxy || "https://api.deepl.com/v2/translate",
  12654. headers: {
  12655. "Authorization": `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
  12656. "Content-Type": "application/json",
  12657. ...Object.assign({}, ...OJBetter.deepl.config.header)
  12658. },
  12659. data: data,
  12660. onload: response => response.responseText,
  12661. onerror: error => console.error(error)
  12662. };
  12663.  
  12664. return await BaseTranslate(options, res => JSON.parse(res).translations[0].text);
  12665. }
  12666.  
  12667. /**
  12668. * 使用 DeepLX 进行翻译
  12669. * @param {String} text 原文
  12670. * @returns {Promise<TransRawData>} 翻译结果对象
  12671. */
  12672. async function translate_deeplx(text) {
  12673. const options = {
  12674. method: "POST",
  12675. url: OJBetter.deepl.config.proxy || 'https://api.deeplx.org/translate',
  12676. data: JSON.stringify({
  12677. "text": text,
  12678. "source_lang": "EN",
  12679. "target_lang": getTargetLanguage('deepl'),
  12680. }),
  12681. headers: {
  12682. 'Content-Type': 'application/json',
  12683. ...(OJBetter.deepl.config.key ? { Authorization: `Bearer ${OJBetter.deepl.config.key}` } : {})
  12684. },
  12685. responseType: "json",
  12686. };
  12687.  
  12688. return await BaseTranslate(options, res => {
  12689. const parsedResponse = JSON.parse(res);
  12690. if (parsedResponse.code === 200 && parsedResponse.data) {
  12691. return parsedResponse.data;
  12692. } else {
  12693. throw new Error('Translation failed or invalid response format.');
  12694. }
  12695. });
  12696. }
  12697.  
  12698. function getTimeStamp(iCount) {
  12699. const ts = Date.now();
  12700. if (iCount !== 0) {
  12701. iCount = iCount + 1;
  12702. return ts - (ts % iCount) + iCount;
  12703. } else {
  12704. return ts;
  12705. }
  12706. }
  12707.  
  12708. /**
  12709. * 讯飞听见翻译
  12710. * @param {String} text 要翻译的文本
  12711. * @returns {Promise<TransRawData>} 翻译结果对象
  12712. */
  12713. async function translate_iflyrec(text) {
  12714. const options = {
  12715. method: "POST",
  12716. url: 'https://www.iflyrec.com/TranslationService/v1/textTranslation',
  12717. data: JSON.stringify({
  12718. "from": "2",
  12719. "to": getTargetLanguage('iflyrec'),
  12720. "contents": [{
  12721. "text": text,
  12722. "frontBlankLine": 0
  12723. }]
  12724. }),
  12725. anonymous: true,
  12726. headers: {
  12727. 'Content-Type': 'application/json',
  12728. 'Origin': 'https://www.iflyrec.com',
  12729. },
  12730. responseType: "json",
  12731. };
  12732. return await BaseTranslate(options, res => JSON.parse(res).biz[0].translateResult.replace(/\\n/g, "\n\n"));
  12733. }
  12734.  
  12735. /**
  12736. * 有道翻译
  12737. * @param {string} raw 原文
  12738. * @returns {Promise<TransRawData>} 翻译结果对象
  12739. */
  12740. async function translate_youdao_mobile(raw) {
  12741. /**
  12742. * 生成cookie
  12743. */
  12744. const getcookie = (() => {
  12745. // 生成IP地址
  12746. const generateIP = () => {
  12747. return `${OJB_getRandomNumberInRange(1, 255)}.${OJB_getRandomNumberInRange(1, 255)}.${OJB_getRandomNumberInRange(1, 255)}.${OJB_getRandomNumberInRange(1, 255)}`;
  12748. }
  12749. // 生成OUTFOX_SEARCH_USER_ID_NCOO的值
  12750. const OUTFOX_SEARCH_USER_ID_NCOO = `${OJB_getRandomNumberInRange(100000000, 999999999)}.${OJB_getRandomNumberInRange(100000000, 999999999)}`;
  12751. // 生成OUTFOX_SEARCH_USER_ID的值
  12752. const OUTFOX_SEARCH_USER_ID = `${OJB_getRandomNumberInRange(100000000, 999999999)}@${generateIP()}`;
  12753. return `OUTFOX_SEARCH_USER_ID_NCOO=${OUTFOX_SEARCH_USER_ID_NCOO}; OUTFOX_SEARCH_USER_ID=${OUTFOX_SEARCH_USER_ID}`;
  12754. })();
  12755.  
  12756. /**
  12757. * 生成随机时间戳
  12758. */
  12759. const gettime = (new Date()).getTime();
  12760.  
  12761. /**
  12762. * 生成sign
  12763. */
  12764. const getsign = (() => {
  12765. const d = "fanyideskweb";
  12766. const u = "webfanyi";
  12767. const t = "fsdsogkndfokasodnaso";
  12768. function A(e) {
  12769. return CryptoJS.MD5(e.toString()).toString(CryptoJS.enc.Hex);
  12770. }
  12771. function w(e) {
  12772. return A(`client=${d}&mysticTime=${e}&product=${u}&key=${t}`);
  12773. }
  12774. return w(gettime);
  12775. })();
  12776.  
  12777. /**
  12778. * 解码方法
  12779. * @param {string} src 待解码的字符串
  12780. * @returns {Object} 解码后的数据
  12781. */
  12782. const decode = function (src) {
  12783. // 解码URL安全的Base64
  12784. const decodeUrlSafeBase64 = (str) => {
  12785. let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
  12786. return base64;
  12787. }
  12788.  
  12789. // 使用MD5生成key和iv,取前16字节
  12790. const key = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl";
  12791. const iv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4";
  12792. const keyHash = CryptoJS.MD5(key).toString();
  12793. const ivHash = CryptoJS.MD5(iv).toString();
  12794.  
  12795. // 使用AES-128-CBC模式进行解密
  12796. const keyForAES = CryptoJS.enc.Hex.parse(keyHash).toString().substring(0, 32);
  12797. const ivForAES = CryptoJS.enc.Hex.parse(ivHash).toString().substring(0, 32);
  12798.  
  12799. // 解码URL安全的Base64
  12800. const decodedBase64 = decodeUrlSafeBase64(src);
  12801.  
  12802. // 解密
  12803. const decrypted = CryptoJS.AES.decrypt({
  12804. ciphertext: CryptoJS.enc.Base64.parse(decodedBase64)
  12805. }, CryptoJS.enc.Hex.parse(keyForAES), {
  12806. iv: CryptoJS.enc.Hex.parse(ivForAES),
  12807. mode: CryptoJS.mode.CBC,
  12808. padding: CryptoJS.pad.Pkcs7
  12809. });
  12810.  
  12811. // 将解密结果转换为Utf8字符串
  12812. const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
  12813.  
  12814. // 处理JSON字符串并解析
  12815. const jsonStr = decryptedStr.substring(0, decryptedStr.lastIndexOf("}") + 1);
  12816. return JSON.parse(jsonStr);
  12817. }
  12818. // 整理数据
  12819. const organizeTranslation = (data) => {
  12820. // 提取translateResult数组
  12821. const { translateResult } = data;
  12822.  
  12823. // 整理tgt字段
  12824. return translateResult
  12825. .flat()
  12826. .map(item => item.tgt)
  12827. .join('');
  12828. };
  12829. // 表单数据
  12830. const data = {
  12831. "i": raw,
  12832. "from": "auto",
  12833. "to": getTargetLanguage('youdao'),
  12834. "dictResult": "true",
  12835. "keyid": "webfanyi",
  12836. "sign": getsign,
  12837. "client": "fanyideskweb",
  12838. "product": "webfanyi",
  12839. "appVersion": "1.0.0",
  12840. "vendor": "web",
  12841. "pointParam": "client,mysticTime,product",
  12842. "mysticTime": gettime,
  12843. "keyfrom": "fanyi.web",
  12844. "mid": "1",
  12845. "screen": "1",
  12846. "model": "1",
  12847. "network": "wifi",
  12848. "abtest": "0",
  12849. "yduuid": "abcdefg"
  12850. };
  12851. const options = {
  12852. method: "POST",
  12853. url: 'https://dict.youdao.com/webtranslate',
  12854. data: new URLSearchParams(data),
  12855. anonymous: true,
  12856. cookie: getcookie,
  12857. headers: {
  12858. "Content-Type": "application/x-www-form-urlencoded",
  12859. 'Referer': 'https://fanyi.youdao.com/',
  12860. }
  12861. }
  12862. return await BaseTranslate(options,
  12863. res => {
  12864. const decodeData = decode(res)
  12865. const result = organizeTranslation(decodeData);
  12866. return result;
  12867. }
  12868. );
  12869. }
  12870.  
  12871. /**
  12872. * google翻译
  12873. * @param {string} raw 原文
  12874. * @returns {Promise<TransRawData>} 翻译结果对象
  12875. */
  12876. async function translate_gg(raw) {
  12877. const params = `tl=${getTargetLanguage('google')}&q=${encodeURIComponent(raw)}`;
  12878. const options = {
  12879. method: "GET",
  12880. url: `https://translate.google.com/m?${params}`,
  12881. }
  12882. return await BaseTranslate(options,
  12883. res => $(res).filter('.result-container').text() || $(res).find('.result-container').text());
  12884. }
  12885.  
  12886. /**
  12887. * 彩云翻译
  12888. * @param {string} raw 原文
  12889. * @returns {Promise<TransRawData>} 翻译结果对象
  12890. */
  12891. async function translate_caiyun(raw) {
  12892. const source = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";
  12893. const dic = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"].reduce((dic, current, index) => { dic[current] = source[index]; return dic }, {});
  12894. const browser_id = CryptoJS.MD5(Math.random().toString()).toString();
  12895. const caiyun_jwt = await (async () => {
  12896. const options = {
  12897. method: "POST",
  12898. url: 'https://api.interpreter.caiyunai.com/v1/user/jwt/generate',
  12899. headers: {
  12900. "content-type": "application/json",
  12901. "x-authorization": "token:qgemv4jr1y38jyq6vhvi",
  12902. "origin": "https://fanyi.caiyunapp.com",
  12903. },
  12904. data: JSON.stringify({ browser_id }),
  12905. }
  12906. const res = await OJB_GMRequest(options);
  12907. return JSON.parse(res.responseText).jwt;
  12908. })();
  12909.  
  12910. // 解码
  12911. const decodeUnicode = str => {
  12912. const decoder = new TextDecoder();
  12913. const data = Uint8Array.from(atob(str), c => c.charCodeAt(0));
  12914. return decoder.decode(data);
  12915. };
  12916. const decoder = line => decodeUnicode([...line].map(i => dic[i] || i).join(""));
  12917.  
  12918. const options = {
  12919. method: "POST",
  12920. url: 'https://api.interpreter.caiyunai.com/v1/translator',
  12921. data: JSON.stringify({
  12922. "source": raw.split('\n'),
  12923. "browser_id": browser_id,
  12924. "trans_type": getTargetLanguage('caiyun'),
  12925. "request_id": "web_fanyi",
  12926. "media": "text",
  12927. "os_type": "web",
  12928. "dict": true,
  12929. "cached": true,
  12930. "replaced": true,
  12931. "style": "formal",
  12932. "model": "",
  12933. "detect": true,
  12934. }),
  12935. headers: {
  12936. "content-type": "application/json;charset=UTF-8",
  12937. "x-authorization": "token:qgemv4jr1y38jyq6vhvi",
  12938. "t-authorization": caiyun_jwt
  12939. }
  12940. }
  12941. return await BaseTranslate(options, res => JSON.parse(res).target.map(decoder).join('\n'))
  12942. }
  12943.  
  12944. /**
  12945. * ChatGPT
  12946. * @param {string} raw 原文
  12947. * @returns {Promise<TransRawData>} 翻译结果对象
  12948. */
  12949. async function translate_openai(raw) {
  12950. const modelDefault = 'gpt-3.5-turbo';
  12951. const lang = getTargetLanguage('openai');
  12952. let prompt = "";
  12953. if (OJBetter.chatgpt.customPrompt) {
  12954. prompt = `\n${OJBetter.chatgpt.customPrompt}`;
  12955. if (!OJBetter.chatgpt.asSystemPrompt) {
  12956. prompt += `\n${raw}`;
  12957. };
  12958. } else {
  12959. prompt = `
  12960. As a professional English translator, your task is to accurately translate a segment of an algorithm programming competition question into ${lang}.
  12961. The translation should use professional terms and maintain the text format, including ${OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru
  12962. ? "keeping the LaTeX equations unchanged."
  12963. : "keeping the brackets【】, HTML tags, and their content unchanged."
  12964. }
  12965. After translation, please ensure that the ${lang} version conforms to normal expression habits.
  12966. What I need is a carefully polished ${lang} translation of my question segment. ${OJBetter.chatgpt.asSystemPrompt ? '' :
  12967. `The segment to be translated is as follows: "
  12968. ${raw}
  12969. "`}`;
  12970. };
  12971. const data = {
  12972. model: OJBetter.chatgpt.config.model || modelDefault,
  12973. messages: OJBetter.chatgpt.asSystemPrompt ?
  12974. [
  12975. {
  12976. role: "system",
  12977. content: prompt
  12978. },
  12979. {
  12980. role: "user",
  12981. content: raw
  12982. }
  12983. ] :
  12984. [
  12985. {
  12986. role: "user",
  12987. content: prompt
  12988. }
  12989. ],
  12990. temperature: 0.7,
  12991. ...Object.assign({}, ...OJBetter.chatgpt.config.data)
  12992. };
  12993. const options = {
  12994. method: "POST",
  12995. url: OJBetter.chatgpt.config.proxy || 'https://api.openai.com/v1/chat/completions',
  12996. data: JSON.stringify(data),
  12997. responseType: 'json',
  12998. headers: {
  12999. 'Content-Type': 'application/json',
  13000. 'Authorization': 'Bearer ' + OJBetter.chatgpt.config.key,
  13001. ...Object.assign({}, ...OJBetter.chatgpt.config.header)
  13002. }
  13003. }
  13004. return await BaseTranslate(options,
  13005. res => res,
  13006. undefined,
  13007. response => response.response.choices[0].message.content);
  13008. }
  13009.  
  13010. /**
  13011. * ChatGPT 流式传输
  13012. * @param {string} raw 原文
  13013. * @param {TranslateDiv} translateDiv 翻译结果面板
  13014. * @returns {Promise<TransRawData>} 翻译结果对象
  13015. */
  13016. async function translate_openai_stream(raw, translateDiv) {
  13017. const result = {
  13018. done: true,
  13019. checkPassed: null,
  13020. response: null,
  13021. responseText: null,
  13022. text: "",
  13023. error: null,
  13024. message: null
  13025. };
  13026. const helpText = i18next.t('error.basic', { ns: 'translator' }); // 基本帮助提示信息
  13027. try {
  13028. for await (const delta of openai_stream(raw)) {
  13029. result.text += delta;
  13030. // 翻译结果面板更新
  13031. translateDiv.updateTranslateDiv(result.text, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru), false);
  13032. }
  13033. return result;
  13034. } catch (err) {
  13035. console.warn(err);
  13036. result.error = {
  13037. message: err.message || null,
  13038. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  13039. enumerable: err,
  13040. source: 'openai_stream'
  13041. };
  13042. result.message = `${i18next.t('error.GMRequest', { ns: 'translator' })}${helpText}`;
  13043. }
  13044.  
  13045. return result;
  13046. }
  13047.  
  13048. /**
  13049. * 流式传输
  13050. * @param {string} raw 原文
  13051. * @returns {AsyncGenerator<string>} 返回 AsyncGenerator
  13052. */
  13053. async function* openai_stream(raw) {
  13054. const modelDefault = 'gpt-3.5-turbo';
  13055. const lang = getTargetLanguage('openai');
  13056. let prompt = "";
  13057. if (OJBetter.chatgpt.customPrompt) {
  13058. prompt = `\n${OJBetter.chatgpt.customPrompt}`;
  13059. if (!OJBetter.chatgpt.asSystemPrompt) {
  13060. prompt += `\n${raw}`;
  13061. };
  13062. } else {
  13063. prompt = `
  13064. As a professional English translator, your task is to accurately translate a segment of an algorithm programming competition question into ${lang}.
  13065. The translation should use professional terms and maintain the text format, including ${OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru
  13066. ? "keeping the LaTeX equations unchanged."
  13067. : "keeping the brackets【】, HTML tags, and their content unchanged."
  13068. }
  13069. After translation, please ensure that the ${lang} version conforms to normal expression habits.
  13070. What I need is a carefully polished ${lang} translation of my question segment. ${OJBetter.chatgpt.asSystemPrompt ? '' :
  13071. `The segment to be translated is as follows: "
  13072. ${raw}
  13073. "`}`;
  13074. };
  13075. const data = {
  13076. model: OJBetter.chatgpt.config.model || modelDefault,
  13077. messages: OJBetter.chatgpt.asSystemPrompt ?
  13078. [
  13079. {
  13080. role: "system",
  13081. content: prompt
  13082. },
  13083. {
  13084. role: "user",
  13085. content: raw
  13086. }
  13087. ] :
  13088. [
  13089. {
  13090. role: "user",
  13091. content: prompt
  13092. }
  13093. ],
  13094. temperature: 0.7,
  13095. stream: true,
  13096. ...Object.assign({}, ...OJBetter.chatgpt.config.data)
  13097. };
  13098. const options = {
  13099. method: "POST",
  13100. url: OJBetter.chatgpt.config.proxy || 'https://api.openai.com/v1/chat/completions',
  13101. data: JSON.stringify(data),
  13102. responseType: 'stream',
  13103. headers: {
  13104. 'Content-Type': 'application/json',
  13105. 'Authorization': 'Bearer ' + OJBetter.chatgpt.config.key,
  13106. ...Object.assign({}, ...OJBetter.chatgpt.config.header)
  13107. }
  13108. }
  13109. const response = await OJB_GMRequest(options, true);
  13110. const reader = response.response.getReader();
  13111. const decoder = new TextDecoder();
  13112. let buffer = ''; // 用于累积数据片段的缓冲区
  13113.  
  13114. while (true) {
  13115. const { done, value } = await reader.read();
  13116. if (done) break;
  13117. buffer += decoder.decode(value, { stream: true }); // 将新的数据片段追加到缓冲区
  13118. let lines = buffer.split("\n\n"); // 处理累积的数据
  13119.  
  13120. // 缓冲区的最后一行可能还未完整接收,保留在缓冲区中,-1
  13121. for (let i = 0; i < lines.length - 1; i++) {
  13122. let line = lines[i];
  13123. line = line.substring(5); // 移除 'data:' 前缀
  13124. if (line.includes('[DONE]')) {
  13125. return; // End
  13126. }
  13127. try {
  13128. let data = JSON.parse(line);
  13129. let delta = data['choices'][0]['delta'];
  13130. let content = delta['content'] ? delta['content'] : "";
  13131. yield content; // 传递数据给调用者
  13132. } catch (error) {
  13133. console.warn(`Error parsing JSON: ${error}\n\nError data: ${line}`);
  13134. }
  13135. }
  13136.  
  13137. // 保留最后一行在缓冲区中
  13138. buffer = lines.slice(-1);
  13139. }
  13140.  
  13141. return buffer;
  13142. }
  13143.  
  13144. /**
  13145. * @typedef {Object} CheckResponseResult
  13146. * @property {boolean} status 检查是否通过
  13147. * @property {string} message 检查失败时的消息
  13148. */
  13149.  
  13150. /**
  13151. * @typedef {Object} ErrorResponse
  13152. * @property {Object} message 错误消息
  13153. * @property {Object} stack 错误堆栈
  13154. * @property {Object} enumerable 可枚举的错误属性
  13155. * @property {string} source 错误来源
  13156. */
  13157.  
  13158. /**
  13159. * @typedef {Object} TransRawData
  13160. * @property {boolean} done 操作是否完成
  13161. * @property {CheckResponseResult|null} checkPassed 检查是否通过的结果
  13162. * @property {Object|null} response 响应对象
  13163. * @property {string|null} text 处理后的文本
  13164. * @property {ErrorResponse} error 错误列表
  13165. * @property {string|null} message 可能的消息
  13166. */
  13167.  
  13168. /**
  13169. * 通用翻译函数
  13170. * @param {Object} options GM_xmlhttpRequest 的参数
  13171. * @param {Function} processer 响应再处理函数,它接收响应文本,并应返回处理后的文本。
  13172. * @param {Function} checkResponse 检查文本是否符合预期的函数,它接收文本,并返回一个Object,包含状态和信息。默认为返回 { status: true, message: 'ok' }
  13173. * @param {Function} getResponseText 重写响应文本获取函数,它接收response,并返回响应文本。 默认为 response.responseText
  13174. * @returns {Promise<TransRawData>} 返回 Promise,其解析值为翻译结果对象
  13175. */
  13176. async function BaseTranslate(options, processer, checkResponse = () => { return { status: true, message: 'ok' } }, getResponseText = (response) => response.responseText) {
  13177. const result = {
  13178. done: false,
  13179. checkPassed: null,
  13180. response: null,
  13181. responseText: null,
  13182. text: "",
  13183. error: null,
  13184. message: null
  13185. };
  13186. const helpText = i18next.t('error.basic', { ns: 'translator' }); // 基本帮助提示信息
  13187. const toDo = async () => {
  13188. try {
  13189. result.response = await OJB_GMRequest(options);
  13190. result.responseText = result.response.responseText;
  13191. result.text = getResponseText(result.response);
  13192. } catch (err) {
  13193. console.warn(err);
  13194. result.error = {
  13195. message: err.message || null,
  13196. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  13197. enumerable: err,
  13198. source: 'GMRequest'
  13199. };
  13200. result.message = `${i18next.t('error.GMRequest', { ns: 'translator' })}${helpText}`;
  13201. throw result;
  13202. }
  13203. try {
  13204. result.text = processer(result.text);
  13205. } catch (err) {
  13206. console.warn(err);
  13207. result.error = {
  13208. message: err.message || null,
  13209. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  13210. enumerable: err,
  13211. source: 'processer'
  13212. };
  13213. result.message = `${i18next.t('error.processer', { ns: 'translator' })}${helpText}`;
  13214. throw result;
  13215. }
  13216. try {
  13217. result.checkPassed = checkResponse(result.text);
  13218. if (result.checkPassed.status) result.done = true;
  13219. else result.message = result.checkPassed.message;
  13220. return result;
  13221. } catch (err) {
  13222. console.warn(err);
  13223. result.error = {
  13224. message: err.message || null,
  13225. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  13226. enumerable: err,
  13227. source: 'checkResponse'
  13228. };
  13229. result.message = `${i18next.t('error.checkResponse', { ns: 'translator' })}${helpText}`;
  13230. throw result;
  13231. }
  13232. };
  13233.  
  13234. return await OJB_promiseRetryWrapper(toDo, {
  13235. maxRetries: 3,
  13236. errorHandler: (err, maxRetries, attemptsLeft) => {
  13237. const detailedError = {
  13238. maxRetries: maxRetries,
  13239. attemptsLeft: attemptsLeft,
  13240. ...err
  13241. };
  13242. return detailedError;
  13243. }
  13244. });
  13245. }
  13246.  
  13247. /**
  13248. * 查询服务余额
  13249. * @param {Object} quotaConfig - 配额配置对象
  13250. * @returns {Promise} 返回包含余额信息的 Promise
  13251. */
  13252. async function queryServerBalance(quotaConfig) {
  13253. // 确保传入了有效的配置对象
  13254. if (!quotaConfig || !quotaConfig.url) {
  13255. return Promise.reject(new Error('Quota configuration is missing.'));
  13256. }
  13257.  
  13258. // 准备请求选项
  13259. const requestOptions = {
  13260. method: quotaConfig.method || 'GET',
  13261. url: quotaConfig.url,
  13262. headers: {
  13263. ...Object.assign({}, ...quotaConfig.header)
  13264. },
  13265. data: JSON.stringify({ ...Object.assign({}, ...quotaConfig.data) })
  13266. };
  13267.  
  13268. // 发送请求并返回 Promise
  13269. return OJB_GMRequest(requestOptions).then(response => {
  13270. try {
  13271. const responseData = JSON.parse(response.responseText);
  13272. // 从响应数据中提取余额
  13273. const surplusPath = quotaConfig.surplus;
  13274. const surplusValue = OJB_evaluatePathOrExpression(responseData, surplusPath);
  13275. return surplusValue;
  13276. } catch (error) {
  13277. return Promise.reject(new Error('Failed to parse balance response.'));
  13278. }
  13279. }).catch(error => {
  13280. console.warn('Error querying balance:', error);
  13281. return Promise.reject(error);
  13282. });
  13283. }
  13284.  
  13285. /**
  13286. * 确认 jQuery 已加载
  13287. * @param {number} retryDelay 重试延迟(毫秒)
  13288. * @returns {Promise<void>}
  13289. */
  13290. async function ensureJQueryIsLoaded(retryDelay = 50) {
  13291. while (typeof jQuery === 'undefined') {
  13292. console.warn(`JQuery is not loaded. Retry after ${retryDelay} ms.`);
  13293. await OJB_delay(retryDelay);
  13294. retryDelay = Math.min(retryDelay * 2, 2000);
  13295. }
  13296. }
  13297.  
  13298. /**
  13299. * 加载必须的函数
  13300. * @returns {Promise} 加载提示信息
  13301. */
  13302. async function loadRequiredFunctions() {
  13303. await initVar();// 初始化全局变量
  13304. return Promise.allSettled([
  13305. initDB(), // 连接数据库
  13306. initI18next(), // i18next初始化
  13307. initButtonFunc(), // 加载按钮相关函数
  13308. initHTML2MarkDown(), // 初始化html2markdown转换器
  13309. checkScriptVersion(), // 更新检查
  13310. // ...(OJBetter.typeOfPage.is_acmsguru ? [acmsguruReblock()] : []) // 为acmsguru题面重新划分div
  13311. ]);
  13312. }
  13313.  
  13314. /**
  13315. * DOM加载后即可执行
  13316. */
  13317. function initOnDOMReady() {
  13318. showAnnounce(); // 显示公告
  13319. showWarnMessage(); // 显示警告消息
  13320. initSettingsPanel(); // 加载设置按钮面板
  13321. initMonacoEditor(); // 初始化monaco编辑器资源
  13322. localizeWebsite(); // 网站本地化替换
  13323. addDependencyStyles(); // 添加一些依赖库的样式
  13324. addI18nStyles(); // 添加包含i18n内容的样式
  13325. // if (OJBetter.basic.expandFoldingblocks) ExpandFoldingblocks(); // 折叠块展开
  13326. // if (OJBetter.basic.renderPerfOpt) RenderPerfOpt(); // 折叠块渲染优化
  13327. // if (OJBetter.basic.selectElementPerfOpt) SelectElementPerfOpt(); // 下拉选择框性能优化
  13328. if (OJBetter.typeOfPage.is_problem) {
  13329. const problemPageLinkbar = new ProblemPageLinkbar(); // 创建题目页相关链接栏
  13330. if (OJBetter.basic.showCF2vjudge) CF2vjudge(problemPageLinkbar); // 跳转到Vjudge按钮
  13331. if (OJBetter.basic.showJumpToLuogu) CF2luogu(problemPageLinkbar); // 跳转到洛谷按钮
  13332. if (OJBetter.clist.enabled.problem) showRatingByClist_problem(problemPageLinkbar); // problem页显示Rating
  13333. }
  13334. if (OJBetter.typeOfPage.is_contest) {
  13335. if (OJBetter.clist.enabled.contest) showRatingByClist_contest(); // contest页显示Rating
  13336. }
  13337. // if (OJBetter.typeOfPage.is_problemset) {
  13338. // if (OJBetter.clist.enabled.problemset) showRatingByClist_problemset(); // problemset页显示Rating
  13339. // }
  13340. if (OJBetter.typeOfPage.is_problem && OJBetter.monaco.enableOnProblemPage) {
  13341. addProblemPageCodeEditor(); // 添加题目页代码编辑器
  13342. }
  13343. }
  13344.  
  13345. /**
  13346. * 需要在页面资源完全加载后执行的函数
  13347. */
  13348. function onResourcesReady(loadingMessage) {
  13349. if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('loadFunc', { ns: 'alert' })}`);
  13350. initializeInParallel(loadingMessage);
  13351. initializeSequentially(loadingMessage);
  13352. }
  13353.  
  13354. /**
  13355. * 可以异步并行的函数
  13356. */
  13357. function initializeInParallel(loadingMessage) {
  13358. if (OJBetter.basic.darkMode == "dark") darkModeStyleAdjustment(); // 黑暗模式额外的处理事件
  13359. // if (OJBetter.basic.commentPaging) CommentPagination(); // 评论区分页
  13360. if (OJBetter.translation.comment.transMode == "2") multiChoiceTranslation(); // 选段翻译支持
  13361. if (OJBetter.monaco.beautifyPreBlocks) beautifyPreBlocksWithMonaco(); // 美化Pre代码块
  13362. }
  13363.  
  13364. /**
  13365. * 必须按序执行的函数
  13366. */
  13367. async function initializeSequentially(loadingMessage) {
  13368. await addConversionButton(); // 添加MD/复制/翻译按钮
  13369. if ((OJBetter.typeOfPage.is_problem || OJBetter.typeOfPage.is_completeProblemset) && OJBetter.translation.memory.enabled) {
  13370. await initTransResultsRecover(); // 翻译结果恢复功能初始化
  13371. }
  13372. if (OJBetter.translation.auto.enabled) {
  13373. await initTransWhenViewable(); // 自动翻译
  13374. }
  13375. // if (OJBetter.basic.standingsRecolor && OJBetter.typeOfPage.is_cfStandings) {
  13376. // await recolorStandings(); // cf赛制榜单重新着色
  13377. // }
  13378. if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('loadSuccess', { ns: 'alert' })}`, 'success', 3000);
  13379. }
  13380.  
  13381. /**
  13382. * 主方法
  13383. */
  13384. async function main() {
  13385. await ensureJQueryIsLoaded(); // 等待jQuery加载
  13386. const loadingMessage = new LoadingMessage();
  13387. await loadRequiredFunctions(); // 加载必须的函数
  13388. initOnDOMReady(); // DOM加载后即可执行的函数
  13389. if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('onload', { ns: 'alert' })}`);
  13390.  
  13391. // 检查页面资源是否已经完全加载
  13392. if (OJBetter.state.notWaiteLoaded) {
  13393. onResourcesReady(loadingMessage);
  13394. } else {
  13395. if (document.readyState === 'complete') {
  13396. onResourcesReady(loadingMessage);
  13397. } else {
  13398. window.addEventListener('load', () => onResourcesReady(loadingMessage));
  13399. }
  13400. }
  13401. };
  13402.  
  13403. // ------------------------------
  13404. // 脚本加载入口
  13405. if (document.readyState === 'loading') {
  13406. document.addEventListener("DOMContentLoaded", main);
  13407. } else {
  13408. main(); // 如果DOMContentLoaded已经触发,立即执行
  13409. }
  13410. // ------------------------------
  13411.  
  13412. // ------------------------------
  13413. // 配置自动迁移代码(将在10个小版本后移除-1.19)
  13414. // ------------------------------
  13415.  
  13416. if (GM_getValue("openai_key") || GM_getValue("api2d_key")) {
  13417. const newConfig = { "choice": -1, "configurations": [] };
  13418. if (GM_getValue("openai_key")) {
  13419. let config1 = {
  13420. "note": "我的配置1",
  13421. "model": GM_getValue("openai_model"),
  13422. "key": GM_getValue("openai_key"),
  13423. "proxy": GM_getValue("openai_proxy"),
  13424. "_header": "",
  13425. "_data": ""
  13426. }
  13427. if (GM_getValue("translation") === "openai") newConfig.choice = 0;
  13428. newConfig.configurations.push(config1);
  13429. }
  13430. if (GM_getValue("api2d_key")) {
  13431. let config2 = {
  13432. "note": "api2d",
  13433. "model": GM_getValue("api2d_model"),
  13434. "key": GM_getValue("api2d_key"),
  13435. "proxy": GM_getValue("api2d_request_entry") + '/v1/chat/completions',
  13436. "_header": GM_getValue("x_api2d_no_cache") ? "" : " x-api2d-no-cache : 1",
  13437. "_data": ""
  13438. }
  13439. if (GM_getValue("translation") === "api2d") {
  13440. if (GM_getValue("openai_key")) newConfig.choice = 1;
  13441. else newConfig.choice = 0;
  13442. }
  13443. newConfig.configurations.push(config2);
  13444. }
  13445. GM_setValue("chatgpt-config", newConfig);
  13446. const keysToDelete = ["openai_key", "openai_model", "openai_proxy", "api2d_key", "api2d_model", "api2d_request_entry", "x_api2d_no_cache", "showOpneAiAdvanced"];
  13447. keysToDelete.forEach(key => {
  13448. if (GM_getValue(key) != undefined) GM_deleteValue(key);
  13449. });
  13450. if (GM_getValue("translation") === "api2d") GM_setValue("translation", "openai");
  13451. location.reload();
  13452. }
  13453.  
  13454.  
  13455. // ------------------------------
  13456. // 配置自动迁移代码(将在10个小版本后移除-1.23)
  13457. // ------------------------------
  13458.  
  13459. {
  13460. let bottomZh_CN = GM_getValue("bottomZh_CN");
  13461. if (bottomZh_CN !== undefined) {
  13462. if (bottomZh_CN == true) {
  13463. GM_setValue("localizationLanguage", "zh");
  13464. } else {
  13465. GM_setValue("localizationLanguage", "initial");
  13466. }
  13467. GM_deleteValue("bottomZh_CN");
  13468. location.reload();
  13469. }
  13470. }
  13471. {
  13472. let config = GM_getValue("chatgpt-config");
  13473. if (config && config !== undefined) {
  13474. let index = parseInt(config.choice, 10);
  13475. if (index == -1) config.choice = "";
  13476. else config.choice = config.configurations[index].note;
  13477. config.configurations.forEach(function (item) {
  13478. item.name = item.note;
  13479. delete item.note;
  13480. });
  13481. GM_deleteValue("chatgpt-config");
  13482. GM_setValue("chatgpt_config", config);
  13483. location.reload();
  13484. }
  13485. }
  13486.  
  13487. // ------------------------------
  13488. // 配置自动迁移代码(将在10个小版本后移除-1.24)
  13489. // ------------------------------
  13490.  
  13491. {
  13492. let config = GM_getValue("compilerSelection");
  13493. if (config !== undefined) {
  13494. if (config === "61") {
  13495. GM_setValue("compilerSelection", "5001");
  13496. location.reload();
  13497. }
  13498. }
  13499. }