Atcoder Better!

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

2024-03-22 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

  1. // ==UserScript==
  2. // @name Atcoder Better!
  3. // @namespace https://greatest.deepsurf.us/users/747162
  4. // @version 1.15.2
  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 m.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://update.greatest.deepsurf.us/scripts/484742/1311040/i18nextChainedBackendjs.js
  49. // @require https://update.greatest.deepsurf.us/scripts/484743/1311041/i18next-localstorage-backendjs.js
  50. // @resource acwing_cpp_code_completer https://aowuucdn.oss-accelerate.aliyuncs.com/acwing_cpp_code_completer-0.0.11.json
  51. // @resource wandboxlist https://wandbox.org/api/list.json
  52. // @resource xtermcss https://cdn.staticfile.net/xterm/3.9.2/xterm.min.css
  53. // @resource selectpagecss https://aowuucdn.oss-accelerate.aliyuncs.com/css/selectpage.css
  54. // @license GPL3
  55. // @compatible Chrome
  56. // @compatible Firefox
  57. // @compatible Edge
  58. // @incompatible safari
  59. // @supportURL https://github.com/beijixiaohu/OJBetter/issues
  60. // @name:zh-TW Atcoder Better!
  61. // @name:en Atcoder Better!
  62. // @name:de Atcoder Better!
  63. // @name:fr Atcoder Better!
  64. // @name:ko Atcoder Better!
  65. // @name:pt Atcoder Better!
  66. // @name:ja Atcoder Better!
  67. // @name:es Atcoder Better!
  68. // @name:it Atcoder Better!
  69. // @name:hi Atcoder Better!
  70. // @description 一个适用于 AtCoder 的 Tampermonkey 脚本,增强功能与界面。
  71. // @description:zh-TW 一個適用於 AtCoder 的 Tampermonkey 腳本,增強功能與界面。
  72. // @description:en A Tampermonkey script for AtCoder that enhances functionality and interface.
  73. // @description:de Ein Tampermonkey-Skript für AtCoder, das Funktionalität und Benutzeroberfläche verbessert.
  74. // @description:fr Un script Tampermonkey pour AtCoder qui améliore les fonctionnalités et l'interface.
  75. // @description:ko AtCoder를 위한 Tampermonkey 스크립트로 기능과 인터페이스를 개선합니다.
  76. // @description:pt Um script Tampermonkey para AtCoder que aprimora a funcionalidade e a interface.
  77. // @description:ja AtCoder用のTampermonkeyスクリプトで機能とインターフェースを強化します。
  78. // @description:es Un script Tampermonkey para AtCoder que mejora la funcionalidad y la interfaz.
  79. // @description:it Uno script Tampermonkey per AtCoder che migliora la funzionalità e l'interfaccia.
  80. // @description:hi AtCoder के लिए एक Tampermonkey स्क्रिप्ट जो कार्यक्षमता और इंटरफ़ेस को बेहतर बनाता है।
  81. // ==/UserScript==
  82.  
  83. /**
  84. * @namespace OJBetter
  85. * @desc 主命名空间
  86. */
  87. const OJBetter = {};
  88.  
  89. /**
  90. * @namespace state
  91. * @desc 描述脚本的当前状态。
  92. * @memberof OJBetter
  93. */
  94. OJBetter.state = {
  95. /** @type {string} 脚本名*/
  96. name: GM_info.script.name,
  97. /** @type {string} 格式化后的脚本名*/
  98. formatName: undefined,
  99. /** @type {string} 版本号*/
  100. version: GM_info.script.version,
  101. /** @type {boolean?} 是否跳过页面加载等待 */
  102. notWaiteLoaded: undefined,
  103. /** @type {string} 最后公告版本,用于标识版本更新完成提示 */
  104. lastAnnounceVer: undefined,
  105. /** @type {string} 最后读取的有效公告版本 */
  106. lastReadAnnounceVer: undefined,
  107. /** @type {number} 当前已打开的模态对话框数量*/
  108. openDialogCount: 0
  109. };
  110.  
  111. /**
  112. * @namespace common
  113. * @desc 通用设置和属性。
  114. * @memberof OJBetter
  115. */
  116. OJBetter.common = {
  117. /** @type {string} 网站的主机地址 */
  118. hostAddress: location.origin,
  119. /** @type {string?} Atcoder的CSRF令牌 */
  120. at_csrf_token: undefined,
  121. /** @type {Array?} 任务队列 */
  122. taskQueue: undefined,
  123. /** @type {object} OJBetter数据库连接实例*/
  124. database: undefined,
  125. /** @type {object} turndownService实例*/
  126. turndownService: undefined,
  127. };
  128.  
  129. /**
  130. * @namespace basic
  131. * @desc 基本的用户界面设置。
  132. * @memberof OJBetter
  133. */
  134. OJBetter.basic = {
  135. /** @type {string} 黑暗模式设置 */
  136. darkMode: undefined,
  137. /** @type {boolean?} 是否展开折叠块 */
  138. expandFoldingblocks: undefined,
  139. /** @type {boolean?} 是否开启折叠块渲染性能优化 */
  140. renderPerfOpt: undefined,
  141. /** @type {boolean?} 是否开启下拉选择框性能优化 */
  142. selectElementPerfOpt: undefined,
  143. /** @type {boolean?} 评论区分页 */
  144. commentPaging: undefined,
  145. /** @type {boolean?} 显示跳转到Luogu按钮 */
  146. showJumpToLuogu: undefined,
  147. /** @type {boolean?} 显示跳转到Virtual Judge按钮 */
  148. showCF2vjudge: undefined,
  149. /** @type {boolean?} 比赛排行榜重新着色 */
  150. standingsRecolor: undefined
  151. };
  152.  
  153. /**
  154. * @namespace typeOfPage
  155. * @desc 页面类型判断。
  156. * @memberof OJBetter
  157. */
  158. OJBetter.typeOfPage = {
  159. /** @type {boolean?} 是否是轻量站 */
  160. is_mSite: false,
  161. /** @type {boolean?} 是否是acmsguru页面 */
  162. is_acmsguru: false,
  163. /** @type {boolean?} 是否是旧版LaTeX页面 */
  164. is_oldLatex: false,
  165. /** @type {boolean?} 是否是题目集页面 */
  166. is_contest: undefined,
  167. /** @type {boolean?} 是否是题目页面 */
  168. is_problem: undefined,
  169. /** @type {boolean?} 是否是完整的问题集页面 */
  170. is_completeProblemset: false,
  171. /** @type {boolean?} 是否是问题集中的问题页面 */
  172. is_problemset_problem: false,
  173. /** @type {boolean?} 是否是问题集页面 */
  174. is_problemset: false,
  175. /** @type {boolean?} 是否是Codeforces排名页面 */
  176. is_cfStandings: false,
  177. /** @type {boolean?} 是否是提交页面 */
  178. is_submitPage: false,
  179. /** @type {boolean?} 是否是代码状态页面 */
  180. is_statePage: false,
  181. /** @type {boolean?} 是否是提交记录页面 */
  182. is_submissions: false,
  183. /** @type {boolean?} 是否是主页 */
  184. is_homepage: undefined,
  185. /** @type {boolean?} 是否选择的是英语页面 */
  186. isEnglishLanguage: undefined,
  187. /** @type {boolean?} 是否是题解页面 */
  188. isEditorial: undefined,
  189. };
  190.  
  191. /**
  192. * @namespace localization
  193. * @desc 本地化设置。
  194. * @memberof OJBetter
  195. */
  196. OJBetter.localization = {
  197. /** @type {string?} 网站语言 */
  198. websiteLang: undefined,
  199. /** @type {string?} 脚本语言 */
  200. scriptLang: undefined
  201. };
  202.  
  203. /**
  204. * @namespace translation
  205. * @desc 翻译设置。
  206. * @memberof OJBetter
  207. */
  208. OJBetter.translation = {
  209. /** @type {string?} 翻译服务选择 */
  210. choice: undefined,
  211. /** @type {string?} 目标语言 */
  212. targetLang: undefined,
  213. comment: {
  214. /** @type {string?} 评论翻译服务选择 */
  215. choice: undefined,
  216. /** @type {string?} 评论翻译模式 */
  217. transMode: undefined
  218. },
  219. auto: {
  220. /** @type {boolean?} 自动翻译开关 */
  221. enabled: undefined,
  222. /** @type {number?} 短文本长度限制 */
  223. shortTextLength: undefined,
  224. mixTrans: {
  225. /** @type {boolean?} 混合翻译开关 */
  226. enabled: undefined,
  227. /** @type {Array?} 混合翻译服务列表 */
  228. servers: undefined
  229. }
  230. },
  231. memory: {
  232. /** @type {boolean?} 翻译记忆开关 */
  233. enabled: undefined,
  234. /** @type {Object?} 翻译记忆树 */
  235. ttTree: undefined
  236. },
  237. /** @type {string?} 重翻译时的行为 */
  238. retransAction: undefined,
  239. /** @type {number?} 等待时间 */
  240. waitTime: undefined,
  241. /** @type {boolean?} 替换符 */
  242. replaceSymbol: undefined,
  243. /** @type {boolean?} 过滤文本中的*号 */
  244. filterTextWithoutEmphasis: undefined
  245. };
  246.  
  247. /**
  248. * @namespace clist
  249. * @desc Clist相关设置。
  250. * @memberof OJBetter
  251. */
  252. OJBetter.clist = {
  253. enabled: {
  254. /** @type {boolean?} 比赛页面开关 */
  255. contest: undefined,
  256. /** @type {boolean?} 问题页面开关 */
  257. problem: undefined,
  258. /** @type {boolean?} 问题集页面开关 */
  259. problemset: undefined
  260. },
  261. /** @type {boolean?} Rating数据防剧透 */
  262. ratingHidden: undefined,
  263. /** @type {string?} Clist key */
  264. authorization: undefined
  265. };
  266.  
  267. /**
  268. * @namespace monaco
  269. * @desc Monaco编辑器配置。
  270. * @memberof OJBetter
  271. */
  272. OJBetter.monaco = {
  273. /** @type {boolean?} 在问题页面上启用Monaco编辑器 */
  274. enableOnProblemPage: undefined,
  275. /** @type {boolean?} 美化pre代码块 */
  276. beautifyPreBlocks: undefined,
  277. /** @type {boolean} Monaco编辑器加载完成标志 */
  278. loaderOnload: false,
  279. lsp: {
  280. /** @type {Array?} LSP套接字数组 */
  281. socket: [],
  282. /** @type {boolean?} 是否启用LSP */
  283. enabled: undefined,
  284. /** @type {string?} 工作路径 */
  285. workUri: undefined,
  286. /** @type {string?} 套接字URL */
  287. socketUrl: undefined
  288. },
  289. complet: {
  290. /** @type {boolean?} 是否启用C++代码补全模板 */
  291. cppCodeTemplate: undefined,
  292. /** @type {Object?} 自定义配置 */
  293. customConfig: undefined
  294. },
  295. /** @type {Object?} Monaco编辑器实例 */
  296. editor: null,
  297. /** @type {string?} 在线编译器选择 */
  298. onlineCompilerChoice: undefined,
  299. /** @type {string?} 记忆编译器语言选择 */
  300. compilerSelection: undefined,
  301. /** @type {string?} 当前选择的语言 */
  302. nowLangSelect: undefined,
  303. setting: {
  304. /** @type {Array?} 语言设置数组 */
  305. language: [],
  306. /** @type {string?} 位置 */
  307. position: undefined,
  308. /** @type {boolean} 位置初始化标志 */
  309. position_initialized: false,
  310. /** @type {number?} 字体大小 */
  311. fontsize: undefined,
  312. /** @type {boolean?} 鼠标滚动锁定 */
  313. alwaysConsumeMouseWheel: undefined,
  314. /** @type {boolean?} 提交代码二次确认 */
  315. isCodeSubmitDoubleConfirm: undefined,
  316. /** @type {string?} 提交按钮位置 */
  317. submitButtonPosition: undefined
  318. }
  319. };
  320.  
  321. /**
  322. * @namespace deepl
  323. * @desc DeepL翻译服务配置。
  324. * @memberof OJBetter
  325. */
  326. OJBetter.deepl = {
  327. /** @type {Object?} DeepL配置对象 */
  328. configs: undefined,
  329. config: {
  330. /** @type {string?} 类型 */
  331. type: undefined,
  332. /** @type {string?} 名称 */
  333. name: undefined,
  334. /** @type {string?} API类型 */
  335. apiGenre: undefined,
  336. /** @type {string?} API密钥 */
  337. key: undefined,
  338. /** @type {string?} 代理 */
  339. proxy: undefined,
  340. /** @type {Object?} 额外请求头 */
  341. header: undefined,
  342. /** @type {Object?} 额外请求数据 */
  343. data: undefined,
  344. quota: {
  345. /** @type {string?} 余额URL */
  346. url: undefined,
  347. /** @type {string?} 余额请求方法 */
  348. method: undefined,
  349. /** @type {Object?} 余额请求头 */
  350. header: undefined,
  351. /** @type {Object?} 余额请求数据 */
  352. data: undefined,
  353. /** @type {number?} 剩余配额 */
  354. surplus: undefined
  355. }
  356. },
  357. /** @type {boolean?} 启用重点保护 */
  358. enableEmphasisProtection: undefined,
  359. /** @type {boolean?} 启用链接保护 */
  360. enableLinkProtection: undefined
  361. };
  362.  
  363. /**
  364. * @namespace chatgpt
  365. * @desc ChatGPT服务配置。
  366. * @memberof OJBetter
  367. */
  368. OJBetter.chatgpt = {
  369. /** @type {Object?} ChatGPT配置对象 */
  370. configs: undefined,
  371. config: {
  372. /** @type {string?} 名称 */
  373. name: undefined,
  374. /** @type {string?} 模型 */
  375. model: undefined,
  376. /** @type {string?} API密钥 */
  377. key: undefined,
  378. /** @type {string?} 代理 */
  379. proxy: undefined,
  380. /** @type {Object?} 额外请求头 */
  381. header: undefined,
  382. /** @type {Object?} 额外请求数据 */
  383. data: undefined,
  384. quota: {
  385. /** @type {string?} 余额URL */
  386. url: undefined,
  387. /** @type {string?} 余额请求方法 */
  388. method: undefined,
  389. /** @type {Object?} 余额请求头 */
  390. header: undefined,
  391. /** @type {Object?} 余额请求数据 */
  392. data: undefined,
  393. /** @type {number?} 剩余配额 */
  394. surplus: undefined
  395. }
  396. },
  397. /** @type {boolean?} 是否为流式传输 */
  398. isStream: undefined
  399. };
  400.  
  401. /**
  402. * @namespace preference
  403. * @desc 偏好设置
  404. * @memberof OJBetter
  405. */
  406. OJBetter.preference = {
  407. /** @type {boolean?} 是否显示加载动画 */
  408. showLoading: undefined,
  409. /** @type {boolean?} 是否显示悬停目标区域 */
  410. hoverTargetAreaDisplay: undefined,
  411. /** @type {string?} 按钮图标大小 */
  412. iconButtonSize: undefined,
  413. };
  414.  
  415. /**
  416. * @namespace dev
  417. * @desc 维护
  418. * @memberof OJBetter
  419. */
  420. OJBetter.dev = {
  421. /** @type {boolean?} 是否显示规则标记 */
  422. isRuleMarkingEnabled: undefined,
  423. };
  424.  
  425. /**
  426. * @namespace about
  427. * @desc 关于页信息
  428. * @memberof OJBetter
  429. */
  430. OJBetter.about = {
  431. /** @type {string?} 更新通道 */
  432. updateChannel: undefined,
  433. /** @type {string?} 更新源 */
  434. updateSource: undefined
  435. };
  436.  
  437. /**
  438. * @namespace supportList
  439. * @desc 支持列表
  440. * @memberof OJBetter
  441. */
  442. OJBetter.supportList = {
  443. /** @type {object} 翻译支持列表和对应语言代码*/
  444. translationSupport: {
  445. 'deepl': { 'zh': 'ZH', 'de': 'DE', 'fr': 'FR', 'ko': 'KO', 'pt': 'PT', 'ja': 'JA', 'es': 'ES', 'it': 'IT' },
  446. 'iflyrec': { 'zh': '1' },
  447. 'youdao': { 'zh': 'AUTO' },
  448. 'google': { 'zh': 'zh-CN', 'zh-Hant': 'zh-TW', 'de': 'de', 'fr': 'fr', 'ko': 'ko', 'pt': 'pt', 'ja': 'ja', 'es': 'es', 'it': 'it', 'hi': 'hi' },
  449. 'caiyun': { 'zh': 'auto2zh', 'ja': 'auto2ja', 'ko': 'auto2ko', 'es': 'auto2es', 'fr': 'auto2fr' },
  450. 'openai': { 'zh': 'Chinese', 'zh-Hant': 'Traditional Chinese', 'de': 'German', 'fr': 'French', 'ko': 'Korean', 'pt': 'Portuguese', 'ja': 'Japanese', 'es': 'Spanish', 'it': 'Italian', 'hi': 'Hindi' }
  451. },
  452. /** @type {object} 更新源支持列表*/
  453. updateSourceSupportList: {
  454. 'greasyfork': {
  455. 'release': true,
  456. 'dev': false
  457. },
  458. 'github': {
  459. 'release': true,
  460. 'dev': true
  461. },
  462. 'aliyunoss': {
  463. 'release': true,
  464. 'dev': true
  465. }
  466. }
  467. }
  468.  
  469. // ------------------------------
  470. // 一些工具函数
  471. // ------------------------------
  472.  
  473. /**
  474. * 延迟函数
  475. * @param {number} ms 延迟时间(毫秒)
  476. * @returns {Promise<void>}
  477. */
  478. function OJB_delay(ms) {
  479. return new Promise(resolve => setTimeout(resolve, ms));
  480. }
  481.  
  482. /**
  483. * 等待直到指定的条件函数返回true。
  484. *
  485. * @param {() => boolean} conditionCheck 一个无参数的函数,用于检查条件是否满足。当函数返回true时,表示条件已满足。
  486. * @param {number} [interval=100] 检查条件的间隔时间,单位为毫秒。默认为100毫秒。
  487. * @returns {Promise<void>} 返回一个Promise,在条件满足时解决。
  488. */
  489. async function OJB_waitUntilTrue(conditionCheck, interval = 100) {
  490. return new Promise((resolve) => {
  491. const checkCondition = async () => {
  492. if (conditionCheck()) {
  493. resolve();
  494. } else {
  495. await OJB_delay(interval);
  496. checkCondition();
  497. }
  498. };
  499. checkCondition();
  500. });
  501. }
  502.  
  503. /**
  504. * 动态加载JavaScript库并返回一个Promise,该Promise在脚本加载完成后解决。
  505. *
  506. * @param {string} url - 要加载的JavaScript库的URL地址。
  507. * @returns {Promise<void>} 一个Promise,它在脚本加载并执行完成后解决。
  508. */
  509. function OJB_LoadJS(url) {
  510. return new Promise((resolve, reject) => {
  511. let scriptElement = document.createElement("script");
  512. scriptElement.src = url;
  513. document.head.prepend(scriptElement);
  514. scriptElement.onload = resolve;
  515. scriptElement.onerror = reject;
  516. });
  517. }
  518.  
  519. /**
  520. * 安全地创建JQuery对象
  521. * @description 通过字符串创建JQuery对象时,如果字符串以空格开头,在某些Jquery版本中会发生错误,过滤空格以安全的创建元素。
  522. * @param {string} string - 字符串。
  523. * @returns JQuery对象
  524. */
  525. const OJB_safeCreateJQElement = function (string) {
  526. return $(string.replace(/^\s+/, ""));
  527. }
  528.  
  529.  
  530. /**
  531. * 将数字或者字符串解析为数字。
  532. * @memberof OJBetter.common
  533. * @param {string} val 要解析的字符串
  534. * @param {boolean} [strict=false] 是否进行严格类型检查
  535. * @returns {number} 解析结果
  536. * @throws {Error} 如果解析失败,则抛出错误
  537. */
  538. const OJB_parseNumber = (val, strict = false) => {
  539. const num = Number(val);
  540. if (isNaN(num) || (strict && val.toString() !== num.toString())) {
  541. throw new Error('Invalid number');
  542. }
  543. return num;
  544. };
  545.  
  546. /**
  547. * 将字符串解析为布尔值
  548. * @param {string} val - 要解析的字符串
  549. * @param {boolean} strict - 是否进行严格类型检查
  550. * @returns {boolean} - 解析结果
  551. * @throws {Error} - 如果解析失败,则抛出错误
  552. */
  553. const OJB_parseBoolean = (val, strict) => {
  554. if (strict) {
  555. if (val === true || val === false) return val;
  556. throw new Error('Invalid boolean');
  557. }
  558. return val === 'true' ? true : val === 'false' ? false : val;
  559. };
  560.  
  561. /**
  562. * 将字符串解析为对象
  563. * @param {string} val - 要解析的字符串
  564. * @returns {Object} - 解析结果
  565. * @throws {Error} - 如果解析失败,则抛出错误
  566. */
  567. const OJB_parseObject = val => {
  568. try {
  569. return JSON.parse(val);
  570. } catch {
  571. throw new Error('Invalid JSON');
  572. }
  573. };
  574.  
  575. /**
  576. * 将字符串解析为键值对数组
  577. * @param {string} val - 要解析的字符串
  578. * @returns {Object[]} - 解析结果
  579. * @throws {Error} - 如果解析失败,则抛出错误
  580. */
  581. const OJB_parseLinePairArray = val => {
  582. if (typeof val !== 'string' || val.trim() === '') return [];
  583. return val.split("\n").filter(line => line.trim() !== '').map(line => {
  584. const indexOfFirstColon = line.indexOf(":");
  585. if (indexOfFirstColon === -1) throw new Error('Invalid LinePairArray format: ":" is missing');
  586. const key = line.substring(0, indexOfFirstColon).trim();
  587. const value = line.substring(indexOfFirstColon + 1).trim();
  588. return { [key]: value };
  589. });
  590. };
  591.  
  592. /**
  593. * 移除文本中的HTML标签
  594. * @param {string} text - 包含HTML标签的文本
  595. * @returns {string} - 移除HTML标签后的文本
  596. */
  597. const OJB_removeHTMLTags = function (text) {
  598. return text.replace(/<\/?[a-zA-Z]+("[^"]*"|'[^']*'|[^'">])*>/g, '');
  599. }
  600.  
  601. /**
  602. * 获取对象中指定路径表达式的值
  603. * @param {Object} obj - 要计算的对象
  604. * @param {string} pathOrExpression - 要计算的路径表达式
  605. * @returns {any} - 计算结果
  606. * @example
  607. * const obj = {
  608. * "a": {
  609. * "b": 1
  610. * },
  611. * "c": 2
  612. * };
  613. * OJB_evaluatePathOrExpression(obj, "a.b"); // 1
  614. * OJB_evaluatePathOrExpression(obj, "a.b + c"); // 3
  615. * OJB_evaluatePathOrExpression(obj, "a.b + a.c"); // 1
  616. */
  617. function OJB_evaluatePathOrExpression(obj, pathOrExpression) {
  618. const hasOperator = /[\+\-\*\/]/.test(pathOrExpression);
  619. const getPathValue = (obj, path) => {
  620. return path.split('.').reduce((acc, part) => {
  621. return acc !== undefined && acc !== null && acc.hasOwnProperty(part) ? acc[part] : undefined;
  622. }, obj);
  623. };
  624. const evaluateExpression = (obj, expression) => {
  625. const tokens = expression.split(/([\+\-\*\/])/).map(token => token.trim());
  626. const values = tokens.map(token => {
  627. if (/[\+\-\*\/]/.test(token)) {
  628. return token;
  629. } else {
  630. const value = getPathValue(obj, token);
  631. return value !== undefined ? value : 0;
  632. }
  633. });
  634. const evaluatedExpression = values.join(' ');
  635. try {
  636. return Function(`'use strict'; return (${evaluatedExpression});`)();
  637. } catch (e) {
  638. console.error('Expression evaluation error:', e);
  639. return undefined;
  640. }
  641. };
  642. return hasOperator ? evaluateExpression(obj, pathOrExpression) : getPathValue(obj, pathOrExpression);
  643. }
  644.  
  645. /**
  646. * 获取 GM 存储的值并根据类型进行处理
  647. * @param {string} key - 要检索的值的键。
  648. * @param {any} defaultValue - 如果值未找到,则返回的默认值。
  649. * @param {Object} [options={}] - 配置选项对象。
  650. * @param {string} [options.type='string'] - 期望的值的类型。可选值:'string', 'number', 'boolean', 'object', 'array', 'linePairArray'。
  651. * @param {boolean} [options.strict=false] - 用于数字和布尔类型,表示是否进行严格类型检查。
  652. * @param {string} [options.pathOrExpression=''] - 用于对象或数组类型,表示路径表达式或获取元素的索引。
  653. * @returns {any} - 检索到的值。
  654. */
  655. const OJB_getGMValue = (key, defaultValue, { type = 'string', strict = false, pathOrExpression = '' } = {}) => {
  656. let value = GM_getValue(key);
  657. if (value === undefined || value === null || value === "") {
  658. GM_setValue?.(key, defaultValue);
  659. return defaultValue;
  660. }
  661.  
  662. const parsers = {
  663. string: val => val,
  664. number: (val) => OJB_parseNumber(val, strict),
  665. boolean: (val) => OJB_parseBoolean(val, strict),
  666. object: OJB_parseObject,
  667. array: OJB_parseObject,
  668. linePairArray: OJB_parseLinePairArray
  669. };
  670.  
  671. if (!(type in parsers)) {
  672. console.error(`Unsupported type: ${type}`);
  673. return defaultValue;
  674. }
  675.  
  676. try {
  677. value = parsers[type](value);
  678. } catch (e) {
  679. console.error('Error:', e.message);
  680. return defaultValue;
  681. }
  682.  
  683. // The pathOrExpression processing is not applicable to linePairArray type
  684. if ((type === 'object' || type === 'array') && pathOrExpression) {
  685. const evaluated = OJB_evaluatePathOrExpression(value, pathOrExpression);
  686. if (evaluated === undefined) {
  687. console.error('Path or expression evaluation returned undefined');
  688. return defaultValue;
  689. }
  690. value = evaluated;
  691. }
  692.  
  693. return value;
  694. };
  695.  
  696. /**
  697. * 版本号比较方法
  698. * @param {string} version1 版本号1
  699. * @param {string} version2 版本号2
  700. * @returns {number} -1: version1 < version2, 0: version1 = version2, 1: version1 > version2
  701. */
  702. const OJB_compareVersions = function (version1 = "0", version2 = "0") {
  703. const v1Array = version1.split(".").map(Number);
  704. const v2Array = version2.split(".").map(Number);
  705. const length = Math.max(v1Array.length, v2Array.length);
  706. for (let i = 0; i < length; i++) {
  707. const diff = (v1Array[i] || 0) - (v2Array[i] || 0);
  708. if (diff) return Math.sign(diff);
  709. }
  710. return 0;
  711. }
  712.  
  713. /**
  714. * 获取上一个主版本号
  715. * @param {string} currentVersion 当前版本号
  716. * @returns {string} 上一个主版本号
  717. */
  718. const OJB_getPreviousVersion = function (currentVersion) {
  719. const versionArray = currentVersion.split(".").map(Number);
  720. let lastNonZeroIndex = versionArray.length - 1;
  721. while (lastNonZeroIndex >= 0 && versionArray[lastNonZeroIndex] === 0) {
  722. lastNonZeroIndex--;
  723. }
  724. if (lastNonZeroIndex >= 0) {
  725. versionArray[lastNonZeroIndex]--;
  726. for (let i = lastNonZeroIndex + 1; i < versionArray.length; i++) {
  727. versionArray[i] = 0;
  728. }
  729. }
  730. return versionArray.join(".");
  731. };
  732.  
  733. /**
  734. * 在指定根节点下观察指定选择器的元素,当元素存在时,执行回调函数
  735. * @param {Object} options - 配置对象
  736. * @param {string} options.selector - CSS选择器文本
  737. * @param {Function} options.callback - 回调函数,接收变动的节点作为参数
  738. * @param {Boolean} [options.triggerOnExist=true] - 如果为true,元素已存在时立即触发一次回调
  739. * @param {Element} [options.root=document.body] - 在哪个根节点下监听变化
  740. * @param {Boolean} [options.subtree=false] - 是否监听子树变化(即非直接子元素)
  741. */
  742. function OJB_observeElement({
  743. selector,
  744. callback,
  745. triggerOnExist = true,
  746. root = document.body,
  747. subtree = false
  748. }) {
  749. // 尝试获取选择器指定的元素
  750. const targetNode = root.querySelector(selector);
  751.  
  752. if (targetNode) {
  753. // 如果元素已存在,直接开始观察
  754. observeAndReport(targetNode, callback);
  755. // 如果triggerOnExist为true,则立即触发一次回调
  756. if (triggerOnExist) {
  757. callback(targetNode);
  758. }
  759. } else {
  760. // 如果元素不存在,监听DOM变化直到该元素被添加
  761. const observer = new MutationObserver((mutations) => {
  762. mutations.forEach((mutation) => {
  763. mutation.addedNodes.forEach((node) => {
  764. if (node.nodeType === Node.ELEMENT_NODE && node.matches(selector)) {
  765. observeAndReport(node, callback);
  766. if (triggerOnExist) {
  767. callback(node);
  768. }
  769. observer.disconnect(); // 停止监听
  770. }
  771. });
  772. });
  773. });
  774.  
  775. observer.observe(root, { childList: true, subtree, attributes: false });
  776. }
  777.  
  778. function observeAndReport(node, callback) {
  779. const childObserver = new MutationObserver((mutations) => {
  780. mutations.forEach((mutation) => {
  781. mutation.addedNodes.forEach((addedNode) => {
  782. if (addedNode.nodeType === Node.ELEMENT_NODE) {
  783. callback(addedNode); // 执行回调函数
  784. }
  785. });
  786. });
  787. });
  788.  
  789. childObserver.observe(node, { childList: true, subtree: true, attributes: false });
  790. }
  791. }
  792.  
  793. /**
  794. * 初始化全局变量
  795. */
  796. async function initVar() {
  797. const { hostname, href } = window.location;
  798. OJBetter.state.formatName = (() => OJBetter.state.name
  799. .toLowerCase()
  800. .replace(/\s+/g, '-')
  801. .replace(/[^a-z0-9-]/g, ''))();
  802. OJBetter.state.lastAnnounceVer = OJB_getGMValue("lastAnnounceVer", "0");
  803. OJBetter.state.lastReadAnnounceVer = OJB_getGMValue("lastReadAnnounceVer", "0");
  804. OJBetter.typeOfPage.is_contest = /\/contests\/[^\/]+\/tasks\/?$/.test(href);
  805. OJBetter.typeOfPage.is_problem = href.includes('/tasks/');
  806. OJBetter.typeOfPage.is_homepage = (href === 'https://atcoder.jp/' || href === 'https://atcoder.jp/?lang=ja');
  807. OJBetter.typeOfPage.isEnglishLanguage = $('meta[http-equiv="Content-Language"]').attr('content') === 'en';
  808. OJBetter.typeOfPage.isEditorial = href.includes("editorial");
  809. OJBetter.localization.websiteLang = OJB_getGMValue("localizationLanguage", "zh");
  810. OJBetter.localization.scriptLang = OJB_getGMValue("scriptL10nLanguage", "zh");
  811. OJBetter.basic.renderPerfOpt = OJB_getGMValue("renderPerfOpt", false);
  812. OJBetter.basic.selectElementPerfOpt = OJB_getGMValue("selectElementPerfOpt", true);
  813. OJBetter.basic.commentPaging = OJB_getGMValue("commentPaging", true);
  814. OJBetter.basic.showJumpToLuogu = OJB_getGMValue("showJumpToLuogu", true);
  815. OJBetter.basic.showCF2vjudge = OJB_getGMValue("showCF2vjudge", true);
  816. OJBetter.basic.standingsRecolor = OJB_getGMValue("standingsRecolor", true);
  817. OJBetter.state.notWaiteLoaded = OJB_getGMValue("notWaiteLoaded", false);
  818. OJBetter.translation.targetLang = OJB_getGMValue("transTargetLang", "zh");
  819. OJBetter.translation.choice = OJB_getGMValue("translation", "deepl");
  820. OJBetter.translation.comment.transMode = OJB_getGMValue("commentTranslationMode", "0");
  821. OJBetter.translation.comment.choice = OJB_getGMValue("commentTranslationChoice", "0");
  822. OJBetter.translation.memory.enabled = OJB_getGMValue("memoryTranslateHistory", true);
  823. OJBetter.translation.auto.enabled = OJB_getGMValue("autoTranslation", false);
  824. OJBetter.translation.auto.shortTextLength = OJB_getGMValue("shortTextLength", "2000");
  825. OJBetter.translation.retransAction = OJB_getGMValue("retransAction", "0");
  826. OJBetter.translation.waitTime = OJB_getGMValue("transWaitTime", "200");
  827. OJBetter.translation.auto.mixTrans.enabled = OJB_getGMValue("allowMixTrans", true);
  828. OJBetter.translation.auto.mixTrans.servers = OJB_getGMValue("mixedTranslation", ['deepl', 'iflyrec', 'youdao', 'caiyun']);
  829. OJBetter.common.taskQueue = new TaskQueue();
  830. OJBetter.translation.replaceSymbol = OJB_getGMValue("replaceSymbol", "2");
  831. OJBetter.translation.filterTextWithoutEmphasis = OJB_getGMValue("filterTextWithoutEmphasis", false);
  832. OJBetter.clist.enabled.contest = OJB_getGMValue("showClistRating_contest", false);
  833. OJBetter.clist.enabled.problem = OJB_getGMValue("showClistRating_problem", false);
  834. OJBetter.clist.enabled.problemset = OJB_getGMValue("showClistRating_problemset", false);
  835. OJBetter.clist.ratingHidden = OJB_getGMValue("RatingHidden", false);
  836. OJBetter.clist.authorization = OJB_getGMValue("clist_Authorization", "");
  837. //deepl
  838. OJBetter.deepl.config.type = OJB_getGMValue("deepl_type", "free");
  839. OJBetter.deepl.configs = OJB_getGMValue("deepl_config", {
  840. "choice": "",
  841. "configurations": []
  842. });
  843. if (OJBetter.deepl.configs.choice !== "" && OJBetter.deepl.configs.configurations.length !== 0) {
  844. const choice = OJBetter.deepl.configs.choice;
  845. const configuration = OJBetter.deepl.configs.configurations.find(obj => obj.name === choice);;
  846. if (configuration == undefined) {
  847. let existingConfig = GM_getValue('deepl_config');
  848. existingConfig.choice = "";
  849. GM_setValue('deepl_config', existingConfig);
  850. location.reload();
  851. }
  852. OJBetter.deepl.config.name = configuration.name;
  853. OJBetter.deepl.config.apiGenre = configuration.apiGenre;
  854. OJBetter.deepl.config.key = configuration.key;
  855. OJBetter.deepl.config.proxy = configuration.proxy;
  856. OJBetter.deepl.config.header = OJB_parseLinePairArray(configuration._header);
  857. OJBetter.deepl.config.data = OJB_parseLinePairArray(configuration._data);
  858. OJBetter.deepl.config.quota.url = configuration.quota_url;
  859. OJBetter.deepl.config.quota.method = configuration.quota_method;
  860. OJBetter.deepl.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
  861. OJBetter.deepl.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
  862. OJBetter.deepl.config.quota.surplus = configuration.quota_surplus;
  863. }
  864. OJBetter.deepl.enableEmphasisProtection = OJB_getGMValue("enableEmphasisProtection", true);
  865. OJBetter.deepl.enableLinkProtection = OJB_getGMValue("enableLinkProtection", true);
  866. //openai
  867. OJBetter.chatgpt.isStream = OJB_getGMValue("openai_isStream", true);
  868. OJBetter.chatgpt.configs = OJB_getGMValue("chatgpt_config", {
  869. "choice": "",
  870. "configurations": []
  871. });
  872. if (OJBetter.chatgpt.configs.choice !== "" && OJBetter.chatgpt.configs.configurations.length !== 0) {
  873. const choice = OJBetter.chatgpt.configs.choice;
  874. const configuration = OJBetter.chatgpt.configs.configurations.find(obj => obj.name === choice);;
  875. if (configuration == undefined) {
  876. let existingConfig = GM_getValue('chatgpt_config');
  877. existingConfig.choice = "";
  878. GM_setValue('chatgpt_config', existingConfig);
  879. location.reload();
  880. }
  881. OJBetter.chatgpt.config.name = configuration.name;
  882. OJBetter.chatgpt.config.model = configuration.model;
  883. OJBetter.chatgpt.config.key = configuration.key;
  884. OJBetter.chatgpt.config.proxy = configuration.proxy;
  885. OJBetter.chatgpt.config.header = OJB_parseLinePairArray(configuration._header);
  886. OJBetter.chatgpt.config.data = OJB_parseLinePairArray(configuration._data);
  887. OJBetter.chatgpt.config.quota.url = configuration.quota_url;
  888. OJBetter.chatgpt.config.quota.method = configuration.quota_method;
  889. OJBetter.chatgpt.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
  890. OJBetter.chatgpt.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
  891. OJBetter.chatgpt.config.quota.surplus = configuration.quota_surplus;
  892. }
  893. // 编辑器
  894. // if (!OJBetter.typeOfPage.is_mSite) OJBetter.common.cf_csrf_token = Codeforces.getCsrfToken();
  895. // else OJBetter.common.cf_csrf_token = "";
  896. OJBetter.common.at_csrf_token = csrfToken;
  897. // OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "61");
  898. OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "5001");
  899. OJBetter.monaco.setting.fontsize = OJB_getGMValue("editorFontSize", "15");
  900. OJBetter.monaco.enableOnProblemPage = OJB_getGMValue("problemPageCodeEditor", true);
  901. OJBetter.monaco.beautifyPreBlocks = OJB_getGMValue("beautifyPreBlocks", true);
  902. OJBetter.monaco.complet.cppCodeTemplate = OJB_getGMValue("cppCodeTemplateComplete", true);
  903. OJBetter.monaco.onlineCompilerChoice = OJB_getGMValue("onlineCompilerChoice", "official");
  904. OJBetter.monaco.setting.isCodeSubmitDoubleConfirm = OJB_getGMValue("isCodeSubmitConfirm", true);
  905. OJBetter.monaco.setting.alwaysConsumeMouseWheel = OJB_getGMValue("alwaysConsumeMouseWheel", true);
  906. OJBetter.monaco.setting.submitButtonPosition = OJB_getGMValue("submitButtonPosition", "bottom");
  907. //自定义补全
  908. OJBetter.monaco.complet.customConfig = OJB_getGMValue("Complet_config", {
  909. "choice": -1,
  910. "configurations": []
  911. });
  912. // monaco
  913. OJBetter.monaco.lsp.enabled = OJB_getGMValue("useLSP", false);
  914. OJBetter.monaco.setting.position = OJB_getGMValue("monacoEditor_position", "initial");
  915. OJBetter.monaco.lsp.workUri = OJB_getGMValue("OJBetter_Bridge_WorkUri", "C:/OJBetter_Bridge");
  916. OJBetter.monaco.lsp.socketUrl = OJB_getGMValue("OJBetter_Bridge_SocketUrl", "ws://127.0.0.1:2323/");
  917. OJBetter.preference.showLoading = OJB_getGMValue("showLoading", true);
  918. OJBetter.preference.hoverTargetAreaDisplay = OJB_getGMValue("hoverTargetAreaDisplay", false);
  919. OJBetter.basic.expandFoldingblocks = OJB_getGMValue("expandFoldingblocks", true);
  920. OJBetter.preference.iconButtonSize = OJB_getGMValue("iconButtonSize", "16");
  921. OJBetter.dev.isRuleMarkingEnabled = OJB_getGMValue("isRuleMarkingEnabled", false);
  922. OJBetter.about.updateChannel = OJB_getGMValue("updateChannel", "release");
  923. OJBetter.about.updateSource = OJB_getGMValue("updateSource", "greasyfork");
  924. }
  925.  
  926. /**
  927. * 显示警告消息
  928. */
  929. function showWarnMessage() {
  930. if (OJBetter.typeOfPage.is_oldLatex) {
  931. const loadingMessage = new LoadingMessage();
  932. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_oldLatex', { ns: 'alert' })}`, 'warning');
  933. }
  934. if (OJBetter.typeOfPage.is_acmsguru) {
  935. const loadingMessage = new LoadingMessage();
  936. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_acmsguru', { ns: 'alert' })}`, 'warning');
  937. }
  938. if (OJBetter.translation.comment.transMode == "1") {
  939. const loadingMessage = new LoadingMessage();
  940. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_segment', { ns: 'alert' })}`, 'warning');
  941. }
  942. if (OJBetter.translation.comment.transMode == "2") {
  943. const loadingMessage = new LoadingMessage();
  944. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_select', { ns: 'alert' })}`, 'warning');
  945. }
  946. if (OJBetter.typeOfPage.is_submitPage && OJBetter.monaco.enableOnProblemPage) {
  947. const loadingMessage = new LoadingMessage();
  948. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_submitPage', { ns: 'alert' })}`, 'warning');
  949. }
  950. }
  951.  
  952. // 常量
  953. 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>';
  954. 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>`;
  955. 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
  956. 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>`;
  957. 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" />
  958. <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" />
  959. <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>`;
  960.  
  961. /**
  962. * 连接数据库
  963. */
  964. async function initDB() {
  965. OJBetter.common.database = new Dexie('OJBetterDB');
  966. OJBetter.common.database.version(3).stores({
  967. samplesData: '&url',
  968. editorCode: '&url',
  969. translateData: '&url',
  970. localizeSubsData: '&lang'
  971. });
  972.  
  973. // 等待数据库打开
  974. await OJBetter.common.database.open();
  975. }
  976.  
  977. /**
  978. * 清空数据库
  979. */
  980. async function clearDatabase() {
  981. const isConfirmed = await OJB_createDialog(
  982. i18next.t('isClearDatabase.title', { ns: 'dialog' }),
  983. i18next.t('isClearDatabase.content', { ns: 'dialog' }),
  984. [
  985. i18next.t('isClearDatabase.buttons.0', { ns: 'dialog' }),
  986. i18next.t('isClearDatabase.buttons.1', { ns: 'dialog' })
  987. ]
  988. );
  989. if (!isConfirmed) {
  990. try {
  991. // 开启一个读写事务,包含数据库中的所有表
  992. await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
  993. // 遍历所有表
  994. for (const table of OJBetter.common.database.tables) {
  995. // 清空当前表
  996. await table.clear();
  997. }
  998. });
  999. console.log("All tables in the database have been cleared.");
  1000. alert("All tables in the database have been cleared.");
  1001. } catch (error) {
  1002. console.error("Error clearing the database:", error);
  1003. }
  1004. }
  1005. }
  1006.  
  1007. /**
  1008. * 导出数据库
  1009. * @returns {Promise<string>} 数据库的JSON字符串
  1010. */
  1011. async function exportDatabase() {
  1012. try {
  1013. // 创建一个存储数据的对象
  1014. const exportData = {};
  1015. // 获取数据库中所有表的名称
  1016. const tableNames = OJBetter.common.database.tables.map(table => table.name);
  1017.  
  1018. // 遍历每一个表,获取数据
  1019. for (const tableName of tableNames) {
  1020. const tableData = await OJBetter.common.database.table(tableName).toArray();
  1021. exportData[tableName] = tableData;
  1022. }
  1023.  
  1024. // 将数据对象转换为JSON字符串
  1025. const jsonData = JSON.stringify(exportData, null, 4);
  1026. return jsonData;
  1027. } catch (error) {
  1028. console.error("Error exporting database:", error);
  1029. }
  1030. }
  1031.  
  1032. /**
  1033. * 导入数据库
  1034. * @param {string} jsonData 数据库的JSON字符串
  1035. */
  1036. async function importDatabase(jsonData) {
  1037. const isConfirmed = await OJB_createDialog(
  1038. i18next.t('isImportDatabase.title', { ns: 'dialog' }),
  1039. i18next.t('isImportDatabase.content', { ns: 'dialog' }),
  1040. [
  1041. i18next.t('isImportDatabase.buttons.0', { ns: 'dialog' }),
  1042. i18next.t('isImportDatabase.buttons.1', { ns: 'dialog' })
  1043. ]
  1044. );
  1045. if (!isConfirmed) {
  1046. try {
  1047. // 将JSON字符串解析为对象
  1048. const importData = JSON.parse(jsonData);
  1049.  
  1050. // 开启一个事务,并清空现有数据
  1051. await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
  1052. // 清空所有表的数据
  1053. for (const tableName of OJBetter.common.database.tables.map(table => table.name)) {
  1054. await OJBetter.common.database.table(tableName).clear();
  1055. }
  1056.  
  1057. // 插入新数据
  1058. for (const [tableName, rows] of Object.entries(importData)) {
  1059. await OJBetter.common.database.table(tableName).bulkAdd(rows);
  1060. }
  1061. });
  1062. alert("Data imported successfully");
  1063. } catch (error) {
  1064. console.error("Error importing database:", error);
  1065. }
  1066. }
  1067. }
  1068.  
  1069. /**
  1070. * 将数据下载为文件
  1071. * @param {string} data 数据
  1072. * @param {string} filename 文件名,默认为'export.json'
  1073. * @param {string} fileType 文件MIME类型,默认为'application/json'
  1074. * @returns {void}
  1075. */
  1076. function downloadDataAsFile(data, filename = 'export.json', fileType = 'application/json') {
  1077. // 创建一个blob对象,指定文件类型
  1078. const blob = new Blob([data], { type: fileType });
  1079. const url = URL.createObjectURL(blob);
  1080.  
  1081. // 创建一个隐藏的a标签,模拟点击进行下载
  1082. const a = document.createElement('a');
  1083. a.href = url;
  1084. a.download = filename;
  1085. document.body.appendChild(a);
  1086. a.click();
  1087.  
  1088. // 清理
  1089. document.body.removeChild(a);
  1090. URL.revokeObjectURL(url);
  1091. }
  1092.  
  1093.  
  1094. /**
  1095. * 从文件中读取数据
  1096. * @param {Function} callback 回调函数
  1097. * @returns {void}
  1098. */
  1099. function readFileInput(callback) {
  1100. const fileInput = document.createElement('input');
  1101. fileInput.type = 'file';
  1102. fileInput.accept = '.json';
  1103. fileInput.style.display = 'none'; // 隐藏input元素
  1104.  
  1105. fileInput.onchange = (e) => {
  1106. const file = e.target.files[0];
  1107. if (file) {
  1108. const reader = new FileReader();
  1109. reader.onload = (e) => {
  1110. const fileContent = e.target.result;
  1111. if (callback && typeof callback === 'function') {
  1112. callback(fileContent); // 调用回调函数,传入文件内容
  1113. }
  1114. };
  1115. reader.readAsText(file);
  1116. }
  1117. };
  1118.  
  1119. document.body.appendChild(fileInput);
  1120. fileInput.click();
  1121. document.body.removeChild(fileInput);
  1122. }
  1123.  
  1124. /**
  1125. * 清除所有设置
  1126. */
  1127. async function deleteAllConfigSettings() {
  1128. const isConfirmed = await OJB_createDialog(
  1129. i18next.t('isDeleteAllConfigSettings.title', { ns: 'dialog' }),
  1130. i18next.t('isDeleteAllConfigSettings.content', { ns: 'dialog' }),
  1131. [
  1132. i18next.t('isDeleteAllConfigSettings.buttons.0', { ns: 'dialog' }),
  1133. i18next.t('isDeleteAllConfigSettings.buttons.1', { ns: 'dialog' })
  1134. ]
  1135. );
  1136. if (!isConfirmed) {
  1137. const keys = GM_listValues();
  1138.  
  1139. keys.forEach(key => {
  1140. GM_deleteValue(key);
  1141. });
  1142.  
  1143. alert('All settings have been deleted.');
  1144. window.location.reload();
  1145. }
  1146. }
  1147.  
  1148. /**
  1149. * 导出设置到JSON
  1150. * @returns {string} JSON字符串
  1151. */
  1152. function exportSettingsToJSON() {
  1153. const keys = GM_listValues();
  1154. let settings = {};
  1155.  
  1156. keys.forEach(key => {
  1157. settings[key] = GM_getValue(key);
  1158. });
  1159.  
  1160. return JSON.stringify(settings, null, 4);
  1161. }
  1162.  
  1163. /**
  1164. * 从JSON导入设置
  1165. * @param {string} jsonData JSON字符串
  1166. * @returns {void}
  1167. */
  1168. async function importSettingsFromJSON(jsonData) {
  1169. const isConfirmed = await OJB_createDialog(
  1170. i18next.t('isImportSettings.title', { ns: 'dialog' }),
  1171. i18next.t('isImportSettings.content', { ns: 'dialog' }),
  1172. [
  1173. i18next.t('isImportSettings.buttons.0', { ns: 'dialog' }),
  1174. i18next.t('isImportSettings.buttons.1', { ns: 'dialog' })
  1175. ]
  1176. );
  1177. if (!isConfirmed) {
  1178. let settings;
  1179. try {
  1180. settings = JSON.parse(jsonData);
  1181. } catch (e) {
  1182. console.error('JSON parsing error:', e);
  1183. return;
  1184. }
  1185.  
  1186. Object.keys(settings).forEach(key => {
  1187. GM_setValue(key, settings[key]);
  1188. });
  1189.  
  1190. alert('Settings imported successfully!');
  1191. window.location.reload();
  1192. }
  1193. }
  1194.  
  1195. /**
  1196. * 加载元素本地化语言数据
  1197. * @param {JQuery} element jQuery元素
  1198. * @param {number} [retries=10] 重试次数
  1199. * @param {number} [interval=50] 重试间隔
  1200. */
  1201. function elementLocalize(element, retries = 10, interval = 50) {
  1202. if ($.isFunction(element.localize)) {
  1203. element.localize();
  1204. } else if (retries > 0) {
  1205. setTimeout(elementLocalize, interval, element, retries - 1, interval);
  1206. } else {
  1207. console.error('Unable to localize', element);
  1208. }
  1209. }
  1210.  
  1211. // 切换系统黑暗监听
  1212. const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
  1213. const changeEventListeners = [];
  1214. function handleColorSchemeChange(event) {
  1215. event.matches ? $('html').attr('data-theme', 'dark') : $('html').attr('data-theme', 'light');
  1216. if (!event.matches) {
  1217. var originalColor = $(this).data("original-color");
  1218. $(this).css("background-color", originalColor);
  1219. if (OJBetter.monaco.editor) {
  1220. monaco.editor.setTheme('vs');
  1221. }
  1222. } else {
  1223. if (OJBetter.monaco.editor) {
  1224. monaco.editor.setTheme('vs-dark');
  1225. }
  1226. }
  1227. }
  1228.  
  1229. // 黑暗模式
  1230. (function setDark() {
  1231. // 初始化
  1232. function setDarkTheme() {
  1233. const htmlElement = document.querySelector('html');
  1234. if (htmlElement) {
  1235. htmlElement.setAttribute('data-theme', 'dark');
  1236. } else {
  1237. setTimeout(setDarkTheme, 100);
  1238. }
  1239. }
  1240. OJBetter.basic.darkMode = OJB_getGMValue("darkMode", "follow")
  1241. if (OJBetter.basic.darkMode == "dark") {
  1242. setDarkTheme();
  1243. } else if (OJBetter.basic.darkMode == "follow") {
  1244. // 添加事件监听器
  1245. changeEventListeners.push(handleColorSchemeChange);
  1246. mediaQueryList.addEventListener('change', handleColorSchemeChange);
  1247.  
  1248. if (window.matchMedia('(prefers-color-scheme: dark)').matches) setDarkTheme();
  1249. }
  1250.  
  1251. // 定义全局变量
  1252. GM_addStyle(`
  1253. /* 黑暗支持 */
  1254. html[data-theme=dark]:root {
  1255. color-scheme: light dark;
  1256. }
  1257. /* 颜色 */
  1258. :root {
  1259. /* 文字颜色 */
  1260. --ojb-color-text-primary: #a0adb9; /* 主要文字颜色 */
  1261. --ojb-color-text-secondary: #9AA4B1; /* 次要文字颜色 */
  1262. --ojb-color-text-tertiary: #9BA5B2; /* 第三级文字颜色 */
  1263. --ojb-color-text-success: #43A047; /* 成功状态文字颜色 */
  1264. --ojb-color-text-highlight: #cbd6e2; /* 高亮文字颜色 */
  1265. --ojb-color-text-disabled: #506778; /* 禁用状态文字颜色 */
  1266. --ojb-color-text-icon-success: #2e7d32; /* 成功状态图标颜色 */
  1267. --ojb-color-text-link: #4b8eda; /* 链接颜色 */
  1268.  
  1269. /* 背景颜色 */
  1270. --ojb-color-bg-primary: #22272e; /* 主背景颜色 */
  1271. --ojb-color-bg-secondary: #2d333b; /* 次级背景颜色 */
  1272. --ojb-color-bg-disabled: #24292e; /* 禁用元素背景颜色 */
  1273.  
  1274. /* 边框颜色 */
  1275. --ojb-color-border-primary: #48535F; /* 主要边框颜色 */
  1276. --ojb-color-border-disabled: #404950; /* 禁用状态边框颜色 */
  1277. --ojb-color-border-dashed-hover: #03A9F4; /* 虚线边框悬浮颜色 */
  1278. --ojb-color-border-radio-checked: #326154; /* 选中的单选框边框颜色 */
  1279.  
  1280. /* 阴影颜色 */
  1281. --ojb-shadow-standard: 0px 0px 0.5px 0.5px #3A4048; /* 标准阴影 */
  1282. --ojb-shadow-menu-modal: 0px 0px 0px 4px #2d333b; /* 菜单和模态框阴影 */
  1283.  
  1284. /* 区域遮罩颜色 */
  1285. --ojb-overlay-background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px); /* 区域遮罩背景 */
  1286.  
  1287. /* 文字阴影 */
  1288. --ojb-text-shadow-icon: 1px 1px 0px #2d333b, 1px -1px 0px #2d333b, -1px -1px 0px #2d333b, -1px 1px 0px #2d333b; /* 图标文字阴影 */
  1289. }
  1290. /* 边框样式 */
  1291. :root {
  1292. /* 边框样式 */
  1293. --ojb-border-width: 1px; /* 边框宽度 */
  1294. --ojb-border-style-solid: solid; /* 实线样式 */
  1295. --ojb-border-style-dashed: dashed; /* 虚线样式 */
  1296. --ojb-border-radius-small: 4px; /* 小圆角 */
  1297. --ojb-border-radius-medium: 8px; /* 中圆角 */
  1298. --ojb-border-radius-large: 12px; /* 大圆角 */
  1299. /* 组合边框样式 */
  1300. --ojb-border-solid-primary: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-primary); /* 主要实线边框 */
  1301. --ojb-border-dashed: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-primary); /* 主要虚线边框 */
  1302. --ojb-border-dashed-hover: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-dashed-hover); /* 悬浮虚线边框 */
  1303. --ojb-border-solid-disabled: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-disabled); /* 禁用状态实线边框 */
  1304. }
  1305. `);
  1306.  
  1307. // OJBetter界面样式
  1308. GM_addStyle(`
  1309. /* 主要文字颜色 */
  1310. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1311. html[data-theme=dark] .alert-warning, html[data-theme=dark] .markItUpEditor,
  1312. html[data-theme=dark] .translate-problem-statement, html[data-theme=dark] .OJBetter_setting_menu,
  1313. html[data-theme=dark] .help_tip .tip_text,
  1314. html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
  1315. html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
  1316. html[data-theme=dark] #items-per-page, html[data-theme=dark] #pagBar,
  1317. html[data-theme=dark] .OJBetter_setting_sidebar li a:link,
  1318. html[data-theme=dark] .popup .content{
  1319. color: var(--ojb-color-text-primary);
  1320. }
  1321. /* 次要文字颜色 */
  1322. html[data-theme=dark] .ojb_btn:hover, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_statusBar,
  1323. html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #addCustomTest,
  1324. html[data-theme=dark] #customTestBlock, html[data-theme=dark] .OJBetter_setting_list.alert_info{
  1325. color: var(--ojb-color-text-secondary);
  1326. }
  1327. /* 文字颜色3 */
  1328. html[data-theme=dark] .ojb_btn{
  1329. color: var(--ojb-color-text-tertiary);
  1330. }
  1331. /* 文字颜色 浅绿 */
  1332. html[data-theme=dark] #SubmitButton{
  1333. color: var(--ojb-color-text-success);
  1334. }
  1335. /* 禁止文字颜色 */
  1336. html[data-theme=dark] .ojb_btn[disabled]{
  1337. color: var(--ojb-color-text-disabled);
  1338. }
  1339. /* 主要背景层次 */
  1340. html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] li#add_button:hover,
  1341. html[data-theme=dark] .ojb_btn:hover,
  1342. html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
  1343. html[data-theme=dark] #OJBetter_SubmitForm input,
  1344. html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
  1345. html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
  1346. html[data-theme=dark] .OJBetter_setting_sidebar li a.active, html[data-theme=dark] .OJBetter_setting_sidebar li,
  1347. html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-track, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-track,
  1348. html[data-theme=dark] .OJBetter_modal, html[data-theme=dark] .OJBetter_modal button:hover,
  1349. html[data-theme=dark] .popup .content,
  1350. html[data-theme=dark] .config_bar_list, html[data-theme=dark] #LSPLog,
  1351. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
  1352. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before,
  1353. html[data-theme=dark] .OJBetter_setting_menu a, html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button:hover,
  1354. html[data-theme=dark] .OJBetter_setting_menu select{
  1355. background-color: var(--ojb-color-bg-primary);
  1356. background-image: none;
  1357. }
  1358. /* 次要背景层次 */
  1359. html[data-theme=dark] .ojb_btn,
  1360. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1361. html[data-theme=dark] .alert-warning, html[data-theme=dark] .SumoSelect>.optWrapper>.options li.opt:hover,
  1362. html[data-theme=dark] .translate-problem-statement-panel,
  1363. html[data-theme=dark] .translate-problem-statement,
  1364. html[data-theme=dark] .OJBetter_setting_list,
  1365. html[data-theme=dark] .OJBetter_setting_menu hr,
  1366. html[data-theme=dark] .OJBetter_setting_sidebar li a,
  1367. html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-thumb, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-thumb,
  1368. html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] .test-for-popup pre,
  1369. html[data-theme=dark] .popup .content pre, html[data-theme=dark] .popup .content pre code,
  1370. html[data-theme=dark] ul.config_bar_ul::-webkit-scrollbar-thumb, html[data-theme=dark] #OJBetter_statusBar,
  1371. html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] .sampleDiv,
  1372. html[data-theme=dark] #addCustomTest, html[data-theme=dark] #LSPLog li:nth-child(odd),
  1373. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before,
  1374. html[data-theme=dark] .config::before, html[data-theme=dark] .config li.tempConfig_add_button:hover,
  1375. html[data-theme=dark] .OJBetter_setting_menu details, html[data-theme=dark] #config_bar_menu,
  1376. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button,
  1377. html[data-theme=dark] .OJBetter_setting_menu .badge, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton{
  1378. background-color: var(--ojb-color-bg-secondary);
  1379. }
  1380. /* 禁止背景层次 */
  1381. html[data-theme=dark] .ojb_btn[disabled]{
  1382. background-color: var(--ojb-color-bg-disabled);
  1383. }
  1384. /* 实线边框颜色-圆角 */
  1385. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1386. html[data-theme=dark] .alert-warning, html[data-theme=dark] .translate-problem-statement{
  1387. border: var(--ojb-border-solid-primary);
  1388. border-radius: 2px;
  1389. }
  1390. /* 实线边框颜色-无圆角 */
  1391. html[data-theme=dark] .ojb_btn,
  1392. html[data-theme=dark] .OJBetter_setting_list, html[data-theme=dark] .config_bar_list,
  1393. html[data-theme=dark] label.config_bar_ul_li_text,
  1394. html[data-theme=dark] .OJBetter_setting_sidebar li, html[data-theme=dark] .OJBetter_setting_menu select,
  1395. html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_SubmitForm select,
  1396. html[data-theme=dark] #OJBetter_editor, html[data-theme=dark] #OJBetter_statusBar,
  1397. html[data-theme=dark] #OJBetter_SubmitForm #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #customTestBlock,
  1398. html[data-theme=dark] #OJBetter_SubmitForm #addCustomTest, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton,
  1399. html[data-theme=dark] .OJBetter_setting_menu input,
  1400. html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
  1401. html[data-theme=dark] .OJBetter_setting_menu textarea,
  1402. html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea,
  1403. html[data-theme=dark] #CompilerSetting select, html[data-theme=dark] #CompilerSetting textarea, html[data-theme=dark] #CompilerBox,
  1404. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
  1405. html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] .config::before,
  1406. html[data-theme=dark] #statePanel, html[data-theme=dark] .test-case, html[data-theme=dark] .OJBetter_setting_menu .badge{
  1407. border: var(--ojb-border-solid-primary);
  1408. }
  1409. html[data-theme=dark] #customTestBlock #customTests{
  1410. border-top: var(--ojb-border-solid-primary);
  1411. }
  1412. html[data-theme=dark] .OJBetter_setting_sidebar {
  1413. border-right: var(--ojb-border-solid-primary);
  1414. }
  1415. /* 实线边框-禁止 */
  1416. html[data-theme=dark] .ojb_btn[disabled]{
  1417. border: var(--ojb-border-solid-disabled);
  1418. }
  1419. /* 虚线边框 */
  1420. html[data-theme=dark] li#add_button,
  1421. html[data-theme=dark] .OJBetter_setting_menu_label_text{
  1422. border: var(--ojb-border-dashed);
  1423. }
  1424. /* 虚线边框-悬浮 */
  1425. html[data-theme=dark] li#add_button:hover{
  1426. border: var(--ojb-border-dashed-hover);
  1427. background-color: var(--ojb-color-bg-secondary);
  1428. color: var(--ojb-color-border-dashed-hover);
  1429. }
  1430. /* 无边框 */
  1431. html[data-theme=dark] .translate-problem-statement-panel .ojb_btn{
  1432. border: none;
  1433. }
  1434. /* 区域遮罩 */
  1435. html[data-theme=dark] .overlay::before {
  1436. background: var(--ojb-overlay-background);
  1437. color: var(--ojb-color-text-secondary);
  1438. text-shadow: 0px 0px 2px #000000;
  1439. }
  1440. /* 阴影 */
  1441. html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .translate-problem-statement{
  1442. box-shadow: var(--ojb-shadow-standard);
  1443. }
  1444. /* 图标按钮状态样式 */
  1445. html[data-theme=dark] .ojb_btn_popover.success i:before, html[data-theme=dark] .ojb_btn_popover.success i {
  1446. color: var(--ojb-color-text-icon-success);
  1447. }
  1448. html[data-theme=dark] .ojb_btn_popover i:before {
  1449. text-shadow: var(--ojb-text-shadow-icon);
  1450. }
  1451. /* 其他样式 */
  1452. html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .OJBetter_modal{
  1453. box-shadow: var(--ojb-shadow-menu-modal);
  1454. border: 1px solid var(--ojb-color-bg-secondary);
  1455. }
  1456. html[data-theme=dark] input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
  1457. color: var(--ojb-color-text-primary);
  1458. border: 1px solid var(--ojb-color-border-radio-checked);
  1459. }
  1460. html[data-theme=dark] .alert{
  1461. text-shadow: none;
  1462. }
  1463. `);
  1464.  
  1465. // 网站界面样式
  1466. GM_addStyle(`
  1467. /* 文字颜色1 */
  1468. html[data-theme=dark] body, html[data-theme=dark] .float-container>#main-container,
  1469. html[data-theme=dark] .panel-default>.panel-heading, html[data-theme=dark] #header a,
  1470. html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span, html[data-theme=dark] .dropdown-menu,
  1471. html[data-theme=dark] .select2-container--bootstrap .select2-selection--single .select2-selection__rendered,
  1472. html[data-theme=dark] .ace-tm .ace_gutter, html[data-theme=dark] .translate-problem-statement-panel,
  1473. html[data-theme=dark] .select2-container--bootstrap .select2-results__option--highlighted[aria-selected],
  1474. 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,
  1475. html[data-theme=dark] .m-box_inner, html[data-theme=dark] .m-list-job_item, html[data-theme=dark] .a-btn_arrow,
  1476. html[data-theme=dark] #header, html[data-theme=dark] #header .header-sub_page li a,
  1477. html[data-theme=dark] .select2-container--default .select2-selection--single .select2-selection__rendered, html[data-theme=dark] .select2-results{
  1478. color: var(--ojb-color-text-primary) !important;
  1479. }
  1480. /* 文字颜色2 */
  1481. html[data-theme=dark] pre, html[data-theme=dark] .html2mdButton, html[data-theme=dark] .btn-default, html[data-theme=dark] .btn-pre,
  1482. html[data-theme=dark] small.contest-duration, html[data-theme=dark] .select2-container--bootstrap .select2-results__option,
  1483. html[data-theme=dark] #ace_settingsmenu, #kbshortcutmenu, html[data-theme=dark] code{
  1484. color: var(--ojb-color-text-secondary) !important;
  1485. }
  1486. /* 文字颜色3 */
  1487. html[data-theme=dark] input, html[data-theme=dark] #header .header-page li a:hover{
  1488. color: var(--ojb-color-text-secondary);
  1489. }
  1490. /* 文字颜色4 */
  1491. html[data-theme=dark] .katex{
  1492. color: var(--ojb-color-text-highlight) !important;
  1493. }
  1494. /* 链接颜色 */
  1495. html[data-theme=dark] a:link {
  1496. color: var(--ojb-color-text-link);
  1497. }
  1498. html[data-theme=dark] a:visited {
  1499. color: var(--ojb-color-text-secondary);
  1500. }
  1501. /* 按钮 */
  1502. html[data-theme=dark] input:hover, html[data-theme=dark] .btn-default:hover{
  1503. background-color: var(--ojb-color-bg-primary) !important;
  1504. }
  1505. /* 背景层次1 */
  1506. html[data-theme=dark] body, html[data-theme=dark] #main-div.float-container, html[data-theme=dark] pre,
  1507. html[data-theme=dark] .html2mdButton:hover, html[data-theme=dark] .pagination>.active>a, html[data-theme=dark] .ace-tm,
  1508. html[data-theme=dark] .dropdown-menu>li>a:hover, html[data-theme=dark] .dropdown-menu>li>a:focus,
  1509. html[data-theme=dark] .dropdown-menu .divider, html[data-theme=dark] .select2-container--bootstrap .select2-selection,
  1510. html[data-theme=dark] .ace-tm .ace_gutter-active-line, html[data-theme=dark] .select2-dropdown,
  1511. html[data-theme=dark] input, html[data-theme=dark] button, html[data-theme=dark] select, html[data-theme=dark] textarea,
  1512. html[data-theme=dark] code, html[data-theme=dark] #keyvisual .keyvisual-inner:before, html[data-theme=dark] .m-box_inner,
  1513. html[data-theme=dark] .m-list-job_item, html[data-theme=dark] .select2-container--default .select2-selection--single,
  1514. html[data-theme=dark] ol.linenums, html[data-theme=dark] li.L0, html[data-theme=dark] li.L1, html[data-theme=dark] li.L2,
  1515. html[data-theme=dark] li.L3, html[data-theme=dark] li.L4, html[data-theme=dark] li.L5, html[data-theme=dark] li.L6,
  1516. html[data-theme=dark] li.L7, html[data-theme=dark] li.L8, html[data-theme=dark] li.L9{
  1517. background-color: var(--ojb-color-bg-primary) !important;
  1518. }
  1519. /* 背景层次2 */
  1520. html[data-theme=dark] .float-container>#main-container, html[data-theme=dark] #contest-nav-tabs,
  1521. html[data-theme=dark] .btn-default, html[data-theme=dark] .html2mdButton,
  1522. 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,
  1523. html[data-theme=dark] .nav>li>a:hover, html[data-theme=dark] .nav>li>a:focus, html[data-theme=dark] .panel,
  1524. html[data-theme=dark] .table-striped>tbody>tr:nth-of-type(odd), html[data-theme=dark] .insert-participant-box,
  1525. html[data-theme=dark] .btn-pre, html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-danger,
  1526. html[data-theme=dark] .alert-warning, html[data-theme=dark] .panel-default>.panel-heading,
  1527. html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span, html[data-theme=dark] .dropdown-menu,
  1528. html[data-theme=dark] .ace-tm .ace_gutter, html[data-theme=dark] .select2-container--bootstrap .select2-results__option[aria-selected=true],
  1529. html[data-theme=dark] #ace_settingsmenu, #kbshortcutmenu, html[data-theme=dark] #header .header-inner,
  1530. html[data-theme=dark] ul#config_bar_ul::-webkit-scrollbar-thumb, html[data-theme=dark] .panel-info>.panel-heading,
  1531. html[data-theme=dark] .post-footer, html[data-theme=dark] .a-btn_arrow:before,
  1532. html[data-theme=dark] .table-hover>tbody>tr:hover,
  1533. html[data-theme=dark] li.L1, html[data-theme=dark] li.L3, html[data-theme=dark] li.L5, html[data-theme=dark] li.L7,
  1534. html[data-theme=dark] li.L9{
  1535. background-color: var(--ojb-color-bg-secondary) !important;
  1536. }
  1537. /* 实线边框颜色-圆角 */
  1538. html[data-theme=dark] input{
  1539. border: var(--ojb-border-solid-primary) !important;
  1540. border-radius: 2px;
  1541. }
  1542. /* 实线边框颜色-无圆角 */
  1543. html[data-theme=dark] .btn-default, html[data-theme=dark] .html2mdButton, html[data-theme=dark] .nav-tabs>li>a:hover,
  1544. html[data-theme=dark] .nav-tabs>li.active>a, html[data-theme=dark] .nav-tabs>li.active>a:hover,
  1545. html[data-theme=dark] .nav-tabs>li.active>a:focus, html[data-theme=dark] .btn-pre, html[data-theme=dark] .btn-pre:hover,
  1546. html[data-theme=dark] pre, html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span,
  1547. 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,
  1548. 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,
  1549. 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,
  1550. html[data-theme=dark] .select2-container--bootstrap .select2-selection, html[data-theme=dark] .select2-container--default .select2-selection--single{
  1551. border: var(--ojb-border-solid-primary) !important;
  1552. }
  1553. html[data-theme=dark] hr, html[data-theme=dark] .panel-footer,
  1554. html[data-theme=dark] .table>thead>tr>th, html[data-theme=dark] .table>tbody>tr>th, html[data-theme=dark] .table>tfoot>tr>th,
  1555. html[data-theme=dark] .table>thead>tr>td, html[data-theme=dark] .table>tbody>tr>td, html[data-theme=dark] .table>tfoot>tr>td{
  1556. border-top: var(--ojb-border-solid-primary) !important;
  1557. }
  1558. html[data-theme=dark] .nav-tabs, html[data-theme=dark] .panel-info>.panel-heading, html[data-theme=dark] .panel-default>.panel-heading,
  1559. html[data-theme=dark] .a-btn_arrow{
  1560. border-bottom: var(--ojb-border-solid-primary) !important;
  1561. }
  1562. html[data-theme=dark] .table>thead>tr>th{
  1563. border-bottom: 2px solid var(--ojb-color-border-primary) !important;
  1564. }
  1565. /* 双实线边框 */
  1566. html[data-theme=dark] #header .header-inner{
  1567. border-bottom: 5px double var(--ojb-color-border-primary) !important;
  1568. }
  1569. /* 阴影 */
  1570. html[data-theme=dark] .float-container>#main-container{
  1571. box-shadow: 0px 0px 10px 5px #fff0;
  1572. }
  1573. /* 图片-亮度 */
  1574. html[data-theme=dark] img{
  1575. opacity: .75;
  1576. }
  1577. /* 反转 */
  1578. html[data-theme=dark] .ace_content, html[data-theme=dark] #header .header-logo img, html[data-theme=dark] pre code{
  1579. filter: invert(1) hue-rotate(.5turn);
  1580. }
  1581. /* 区域遮罩 */
  1582. html[data-theme=dark] .overlay {
  1583. background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px);
  1584. color: #9099a3;
  1585. text-shadow: 0px 0px 2px #000000;
  1586. }
  1587. /* 其他样式 */
  1588. 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{
  1589. border-bottom-color: transparent !important;
  1590. }
  1591. html[data-theme=dark] .collapsible-topic.collapsed .content .collapsible-topic-options:before{
  1592. background-image: linear-gradient(#22272e00, #22272e);
  1593. }
  1594. html[data-theme=dark] .alert{
  1595. text-shadow: none;
  1596. }
  1597. html[data-theme=dark] .m-box-news_post:before{
  1598. background: linear-gradient(0deg, #22272e 50%, rgba(255,255,255,0) 100%);
  1599. }
  1600. html[data-theme=dark] #header .header-sub_page li a:before, html[data-theme=dark] #header .header-page li a:before{
  1601. background-color: #9e9e9e !important;
  1602. }
  1603. html[data-theme=dark] .standings-score{
  1604. color: #2196f3;
  1605. }
  1606. html[data-theme=dark] pre code{
  1607. background-color: transparent !important;
  1608. }
  1609. html[data-theme=dark] #fixed-server-timer {
  1610. color: #000;
  1611. }
  1612. `);
  1613. })()
  1614.  
  1615. /**
  1616. * 黑暗模式额外的处理事件
  1617. */
  1618. function darkModeStyleAdjustment() {
  1619.  
  1620. }
  1621.  
  1622. /**
  1623. * 初始化monaco编辑器资源
  1624. */
  1625. async function initMonacoEditor() {
  1626. if (OJBetter.monaco.enableOnProblemPage || OJBetter.monaco.beautifyPreBlocks) {
  1627. try {
  1628. // 等待Monaco Editor加载器脚本加载完成
  1629. await OJB_LoadJS("https://cdn.staticfile.net/monaco-editor/0.44.0/min/vs/loader.min.js");
  1630.  
  1631. // 配置Monaco Editor
  1632. require.config({
  1633. paths: { vs: "https://cdn.staticfile.net/monaco-editor/0.44.0/min/vs" },
  1634. "vs/nls": { availableLanguages: { "*": "zh-cn" } },
  1635. });
  1636.  
  1637. // 加载Monaco Editor主脚本
  1638. require(["vs/editor/editor.main"], () => {
  1639. OJBetter.monaco.loaderOnload = true;
  1640. });
  1641. } catch (error) {
  1642. console.error("Failed to load Monaco Editor: ", error);
  1643. }
  1644. }
  1645. }
  1646.  
  1647. /**
  1648. * 美化代码块
  1649. */
  1650. async function beautifyPreBlocksWithMonaco() {
  1651. // 判断monacoLoader是否加载完毕
  1652. await OJB_waitUntilTrue(() => OJBetter.monaco.loaderOnload);
  1653.  
  1654. // 用于替换 <pre> 标签为 Monaco 编辑器的函数
  1655. function replacePreWithMonaco(preElement) {
  1656. const pre = $(preElement);
  1657. if (pre.hasClass('source-code-for-copy')) return; // 跳过复制块
  1658. const code = OJB_getCodeFromPre(pre.get(0));
  1659. if (!code) return;
  1660. const language = OJB_codeLangDetect(code);
  1661.  
  1662. // 创建一个用于 Monaco 编辑器的容器
  1663. const container = $('<div></div>');
  1664. const lineCount = code.split('\n').length; // 代码的行数
  1665.  
  1666. // 计算容器的高度
  1667. const calculateContainerHeight = (lineCount) => {
  1668. const lineHeight = 20; // 每行代码的高度
  1669. const minHeight = 100; // 最小高度
  1670. const maxHeight = 1000; // 最大高度
  1671. const dynamicHeight = lineCount * lineHeight;
  1672. return Math.min(Math.max(dynamicHeight, minHeight), maxHeight) + 'px';
  1673. };
  1674.  
  1675. // 应用样式
  1676. container.css({
  1677. height: calculateContainerHeight(lineCount),
  1678. width: '100%'
  1679. });
  1680. pre.replaceWith(container);
  1681.  
  1682. // 初始化 Monaco 编辑器
  1683. monaco.editor.create(container[0], {
  1684. value: code,
  1685. language: language,
  1686. readOnly: true,
  1687. tabSize: 4,
  1688. theme: OJBetter.basic.darkMode == "dark" ? "vs-dark" : "vs",
  1689. scrollbar: {
  1690. verticalScrollbarSize: 10,
  1691. horizontalScrollbarSize: 10,
  1692. alwaysConsumeMouseWheel: false
  1693. },
  1694. automaticLayout: true,
  1695. scrollBeyondLastLine: false
  1696. });
  1697. }
  1698. // 全局替换页面上所有的 <pre> 元素
  1699. $('pre').each(function () {
  1700. replacePreWithMonaco(this);
  1701. });
  1702. // 监听页面上的提交状态页面窗口的 <pre> 元素
  1703. if (OJBetter.typeOfPage.is_statePage || OJBetter.typeOfPage.is_submissions) {
  1704. OJB_observeElement({
  1705. selector: '#facebox',
  1706. callback: (node) => {
  1707. // 如果 facebox 中存在 pre 元素,则替换它们
  1708. const preElements = $(node).find('pre');
  1709. preElements.each(function () {
  1710. replacePreWithMonaco(this);
  1711. });
  1712. }
  1713. });
  1714. }
  1715. }
  1716.  
  1717. // 样式
  1718. GM_addStyle(`
  1719. /*动画*/
  1720. @keyframes shake {
  1721. 0% { transform: translateX(-5px); }
  1722. 100% { transform: translateX(5px); }
  1723. }
  1724. @keyframes rotate {
  1725. from {
  1726. transform: rotate(0deg);
  1727. }
  1728.  
  1729. to {
  1730. transform: rotate(360deg);
  1731. }
  1732. }
  1733. @keyframes rippleout {
  1734. 0% {
  1735. box-shadow: 0 0 0 0 rgba(96, 98, 102, 0.2);
  1736. }
  1737.  
  1738. 100% {
  1739. box-shadow: 0 0 0 6px rgba(0, 0, 0, 0);
  1740. }
  1741. }
  1742. @keyframes bounce-in {
  1743. 20%,40%,60%,80%,from,to {
  1744. animation-timing-function: cubic-bezier(.215,.61,.355,1);
  1745. }
  1746.  
  1747. 0% {
  1748. opacity: 0;
  1749. transform: scale3d(.995,.995,.995);
  1750. }
  1751.  
  1752. 20% {
  1753. opacity: 1;
  1754. transform: scale3d(1.005,1.005,1.005);
  1755. }
  1756.  
  1757. 40% {
  1758. transform: scale3d(.998,.998,.998);
  1759. }
  1760.  
  1761. 60% {
  1762. transform: scale3d(1.002,1.002,1.002);
  1763. }
  1764.  
  1765. 80% {
  1766. transform: scale3d(.995,.995,.995);
  1767. }
  1768.  
  1769. to {
  1770. opacity: 1;
  1771. transform: scale3d(1,1,1);
  1772. }
  1773. }
  1774. /*iconfont图标*/
  1775. .iconfont {
  1776. font-family: "iconfont" !important;
  1777. font-size: 16px;
  1778. font-style: normal !important;
  1779. -webkit-font-smoothing: antialiased;
  1780. -moz-osx-font-smoothing: grayscale;
  1781. }
  1782. @font-face {
  1783. font-family: 'iconfont'; /* Project id 4284341 */
  1784. src: url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2') format('woff2'),
  1785. url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2.ttf') format('truetype');
  1786. }
  1787. html {
  1788. scroll-behavior: smooth;
  1789. }
  1790. :root {
  1791. --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";
  1792. }
  1793. span.mdViewContent {
  1794. white-space: pre-wrap;
  1795. }
  1796.  
  1797. /* dialog */
  1798. dialog {
  1799. margin: 0px;
  1800. }
  1801. dialog::backdrop {
  1802. background-color: rgba(0, 0, 0, 0.4);
  1803. }
  1804.  
  1805. /*题目页链接栏样式*/
  1806. #problemToolbar {
  1807. display: flex;
  1808. flex-wrap: wrap;
  1809. justify-content: flex-end;
  1810. overflow: auto;
  1811. height: 100%;
  1812. margin: 0.5em;
  1813. }
  1814.  
  1815. /*html2md面板*/
  1816. .html2md-panel {
  1817. display: flex;
  1818. justify-content: flex-end;
  1819. align-items: center;
  1820. }
  1821. .html2md-panel a {
  1822. text-decoration: none;
  1823. }
  1824. .html2md-panel > button {
  1825. margin: 5px;
  1826. }
  1827. .html2md-panel.is_simple {
  1828. position: absolute;
  1829. right: 2%;
  1830. }
  1831.  
  1832. /*通用按钮*/
  1833. .ojb_btn {
  1834. display: flex;
  1835. align-items: center;
  1836. justify-content: center;
  1837. cursor: pointer;
  1838. background-color: #ffffff;
  1839. color: #606266;
  1840. width: auto;
  1841. font-size: 13px;
  1842. border-radius: 0.3rem;
  1843. padding: 2px 5px;
  1844. margin: 0px 5px;
  1845. border: 1px solid #dcdfe6;
  1846. }
  1847. .ojb_btn[disabled] {
  1848. cursor: not-allowed !important;
  1849. color: rgb(168, 171, 178) !important;
  1850. border: 1px solid #e4e7ed;
  1851. background-color: #ffffff;
  1852. }
  1853. .ojb_btn:hover {
  1854. color: #409eff;
  1855. border-color: #409eff;
  1856. background-color: #f1f8ff;
  1857. z-index: 150;
  1858. }
  1859. .ojb_btn.primary {
  1860. color: #ffffff;
  1861. border: 1px solid #409eff;
  1862. background-color: #409eff;
  1863. }
  1864. .ojb_btn.primary:hover {
  1865. color: #ffffff;
  1866. border: 1px solid #79bbff;
  1867. background-color: #79bbff;
  1868. }
  1869. .ojb_btn.success {
  1870. color: #4caf50;
  1871. border: 1px solid #C8E6C9;
  1872. background-color: #f0f9eb;
  1873. }
  1874. .ojb_btn.warning {
  1875. color: #e6a23c;
  1876. border: 1px solid #f3d19e;
  1877. background-color: #fdf6ec;
  1878. }
  1879. .ojb_btn.error {
  1880. color: #f56c6c;
  1881. border: 1px solid #fab6b6;
  1882. background-color: #fef0f0;
  1883. }
  1884. .ojb_btn.enabled {
  1885. color: #42A5F5;
  1886. border: 1px solid #90CAF9;
  1887. background-color: #fafbff;
  1888. }
  1889. .ojb_btn.active {
  1890. animation: rippleout 0.5s ease-in-out;
  1891. }
  1892. a.ojb_btn {
  1893. text-decoration: none;
  1894. }
  1895. a.ojb_btn:link {
  1896. color: #606266;
  1897. }
  1898. a.ojb_btn span {
  1899. margin-left: 2px;
  1900. }
  1901. /*按钮图标和popover*/
  1902. .ojb_btn_popover {
  1903. display: flex;
  1904. justify-content: center;
  1905. position: relative;
  1906. outline: none;
  1907. appearance: none;
  1908. }
  1909. .ojb_btn_popover:hover span {
  1910. opacity: 1;
  1911. visibility: visible;
  1912. }
  1913. .ojb_btn_popover i:before {
  1914. position: absolute;
  1915. text-shadow: 1px 1px 0px #ffffff, 1px -1px 0px #ffffff, -1px -1px 0px #ffffff, -1px 1px 0px #ffffff;
  1916. }
  1917. .ojb_btn_popover span {
  1918. cursor: initial;
  1919. position: absolute;
  1920. left: 50%;
  1921. opacity: 0;
  1922. visibility: hidden;
  1923. padding: 4px 8px;
  1924. background-color: rgba(33, 33, 33, 0.8);
  1925. color: rgba(255, 255, 255, 0.9019607843);
  1926. font-size: 12px;
  1927. border-radius: 6px;
  1928. line-height: 1.6;
  1929. text-align: left;
  1930. white-space: nowrap;
  1931. transition: all 0.15s ease-in-out;
  1932. z-index: 999;
  1933. }
  1934. .ojb_btn_popover span:hover {
  1935. opacity: 0;
  1936. visibility: hidden;
  1937. }
  1938. .ojb_btn_popover.top:hover span {
  1939. transform: translate(-50%, 0);
  1940. }
  1941. .ojb_btn_popover.top span {
  1942. bottom: 100%;
  1943. transform: translate(-50%, -20%);
  1944. margin-bottom: 4px;
  1945. }
  1946. .ojb_btn_popover.top span:hover {
  1947. transform: translate(-50%, -20%);
  1948. }
  1949. .ojb_btn_popover.bottom:hover span {
  1950. transform: translate(-50%, 105%);
  1951. }
  1952. .ojb_btn_popover.bottom span {
  1953. bottom: -2%;
  1954. transform: translate(-50%, 100%);
  1955. margin-top: 4px;
  1956. }
  1957. .ojb_btn_popover.bottom span:hover {
  1958. transform: translate(-50%, 50%);
  1959. }
  1960. .ojb_btn_popover.loading i {
  1961. color: rgba(33, 33, 33, 0.1);
  1962. }
  1963. .ojb_btn_popover.loading i:before {
  1964. content: "\\e640";
  1965. color: rgb(168, 171, 178);
  1966. animation: rotate 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
  1967. }
  1968. .ojb_btn_popover.running i {
  1969. color: rgba(33, 33, 33, 0.1);
  1970. }
  1971. .ojb_btn_popover.running i:before {
  1972. content: "\\e600";
  1973. color: rgb(168, 171, 178);
  1974. animation: rotate 1s linear infinite;
  1975. }
  1976. .ojb_btn_popover.warning i {
  1977. color: rgba(230, 162, 61, 0.8);
  1978. }
  1979. .ojb_btn_popover.warning i:before {
  1980. content: "\\e68b";
  1981. font-size: 15px;
  1982. left: 10px;
  1983. bottom: 0%;
  1984. color: #ff9800;
  1985. }
  1986. .ojb_btn_popover.error i {
  1987. color: rgba(245, 108, 108, 0.8);
  1988. }
  1989. .ojb_btn_popover.error i:before {
  1990. content: "\\e651";
  1991. font-size: 15px;
  1992. left: 10px;
  1993. bottom: 0%;
  1994. color: #F44336;
  1995. }
  1996. .ojb_btn_popover.success i {
  1997. color: rgba(76, 175, 80, 0.9);
  1998. }
  1999. .ojb_btn_popover.success i:before {
  2000. content: "\\e61e";
  2001. font-size: 15px;
  2002. left: 10px;
  2003. bottom: 0%;
  2004. color: #4caf50;
  2005. }
  2006. .ojb_btn_popover.enabled i {
  2007. color: rgba(33, 150, 243, 0.6);
  2008. }
  2009. .ojb_btn_popover.enabled i:before {
  2010. content: "\\e6f4";
  2011. font-size: 15px;
  2012. left: 10px;
  2013. bottom: 0%;
  2014. color: #2196F3;
  2015. }
  2016. .ojb_btn_popover.redo i {
  2017. color: rgba(33, 33, 33, 0.1);
  2018. }
  2019. .ojb_btn_popover.redo i:before {
  2020. content: "\\e831";
  2021. color: #616161;
  2022. }
  2023. .ojb_btn_popover.reverse i {
  2024. transform: rotate(180deg);
  2025. }
  2026.  
  2027. /*translateDiv样式*/
  2028. .translateDiv .topText {
  2029. display: flex;
  2030. margin-left: 5px;
  2031. color: #9e9e9e;
  2032. font-size: 13px;
  2033. align-items: center;
  2034. }
  2035. .translateDiv .borderlessButton{
  2036. display: flex;
  2037. align-items: center;
  2038. margin: 2.5px 7px;
  2039. fill: #9E9E9E;
  2040. }
  2041. .translateDiv .borderlessButton:hover{
  2042. cursor: pointer;
  2043. fill: #059669;
  2044. }
  2045. .translateDiv.bounce-in {
  2046. animation: bounce-in 1s forwards;
  2047. }
  2048. html:not([data-theme='dark']) .translateDiv {
  2049. box-shadow: 0px 0px 0.5px 0.5px #defdf378;
  2050. }
  2051. .translate-problem-statement {
  2052. justify-items: start;
  2053. letter-spacing: 1.8px;
  2054. color: #059669;
  2055. background-color: #f9f9fa;
  2056. border: 1px solid #c5ebdf;
  2057. border-radius: 0rem 0rem 0.3rem 0.3rem;
  2058. padding: 5px;
  2059. margin: -5px 0px 6px 0px;
  2060. width: 100%;
  2061. box-sizing: border-box;
  2062. font-size: 13px;
  2063. }
  2064. .translate-problem-statement h2 {
  2065. font-size: 1.6em;
  2066. font-weight: 700;
  2067. }
  2068. .translate-problem-statement h3 {
  2069. font-size: 1.3em;
  2070. font-weight: 700;
  2071. }
  2072. .translate-problem-statement-panel{
  2073. display: flex;
  2074. justify-content: space-between;
  2075. background-color: #f9f9fa;
  2076. border: 1px solid #c5ebdf;
  2077. border-radius: 0.3rem;
  2078. margin: 4px 0px;
  2079. }
  2080. .translate-problem-statement-panel .ojb_btn {
  2081. background: none;
  2082. border: none;
  2083. color: #9e9e9e;
  2084. }
  2085. .translate-problem-statement-panel.error, .translate-problem-statement.error {
  2086. color: red;
  2087. border-color: red;
  2088. }
  2089. .translate-problem-statement a, .translate-problem-statement a:link {
  2090. color: #10b981;
  2091. font-weight: 600;
  2092. background: 0 0;
  2093. text-decoration: none;
  2094. }
  2095. .translate-problem-statement ol, .translate-problem-statement ul {
  2096. display: grid;
  2097. margin-inline-start: 0.8em;
  2098. margin-block-start: 0em;
  2099. margin: 0.5em 0 0 3em;
  2100. padding-inline-start: 0px;
  2101. }
  2102. .translate-problem-statement li {
  2103. display: list-item;
  2104. height: auto;
  2105. word-wrap: break-word;
  2106. }
  2107. .translate-problem-statement ol li {
  2108. list-style-type: auto;
  2109. }
  2110. .translate-problem-statement ul li {
  2111. list-style-type: disc;
  2112. }
  2113. .translate-problem-statement img {
  2114. max-width: 100.0%;
  2115. max-height: 100.0%;
  2116. }
  2117. #task-statement .translate-problem-statement .MathJax {
  2118. color: #059669!important;
  2119. }
  2120. .translate-problem-statement span.math {
  2121. margin: 0px 2.5px !important;
  2122. }
  2123. .translate-problem-statement a:hover {
  2124. background-color: #800;
  2125. color: #fff;
  2126. text-decoration: none;
  2127. }
  2128. .translate-problem-statement table {
  2129. border: 1px #ccc solid !important;
  2130. margin: 1.5em 0 !important;
  2131. color: #059669 !important;
  2132. }
  2133. .translate-problem-statement table thead th {
  2134. border: 1px #ccc solid !important;
  2135. color: #059669 !important;
  2136. }
  2137. .translate-problem-statement table td {
  2138. border-right: 1px solid #ccc;
  2139. border-top: 1px solid #ccc;
  2140. padding: 0.7143em 0.5em;
  2141. }
  2142. .translate-problem-statement table th {
  2143. padding: 0.7143em 0.5em;
  2144. }
  2145. .translate-problem-statement p:not(:first-child) {
  2146. margin: 1.5em 0 0;
  2147. }
  2148. .translate-problem-statement p {
  2149. line-height: 20px !important;
  2150. word-wrap: break-word;
  2151. font-size: 13px !important
  2152. }
  2153. .problem-statement p:last-child {
  2154. margin-bottom: 0px !important;
  2155. }
  2156.  
  2157. /*设置按钮*/
  2158. header .enter-or-register-box, header .languages {
  2159. position: absolute;
  2160. right: 170px;
  2161. }
  2162. .ojb_btn.OJBetter_setting {
  2163. float: right;
  2164. height: 30px;
  2165. background: #60a5fa;
  2166. color: white;
  2167. margin: 10px;
  2168. border: 1px solid #60a5fa;
  2169. }
  2170. .ojb_btn.OJBetter_setting.open {
  2171. background-color: #e6e6e6;
  2172. color: #727378;
  2173. cursor: not-allowed;
  2174. }
  2175.  
  2176. /*设置面板*/
  2177. .OJBetter_setting_menu {
  2178. box-shadow: 0px 0px 0px 4px #ffffff;
  2179. position: fixed;
  2180. top: 50%;
  2181. left: 50%;
  2182. width: 600px;
  2183. min-height: 600px;
  2184. transform: translate(-50%, -50%);
  2185. border-radius: 6px;
  2186. background-color: #f0f4f9;
  2187. border-collapse: collapse;
  2188. border: 1px solid #ffffff;
  2189. color: #697e91;
  2190. font-family: var(--vp-font-family-base);
  2191. padding: 10px 20px 20px 10px;
  2192. box-sizing: content-box;
  2193. }
  2194. .OJBetter_setting_menu h3 {
  2195. margin-top: 10px;
  2196. font-size: 1.4em;
  2197. font-weight: 700;
  2198. }
  2199. .OJBetter_setting_menu h4 {
  2200. margin: 15px 0px 10px 0px;
  2201. }
  2202. .OJBetter_setting_menu h4,.OJBetter_setting_menu h5 {
  2203. font-weight: 600;
  2204. }
  2205. .OJBetter_setting_menu hr {
  2206. border: none;
  2207. height: 1px;
  2208. background-color: #ccc;
  2209. margin: 10px 0;
  2210. }
  2211. .OJBetter_setting_menu details {
  2212. padding: 10px;
  2213. margin-bottom: 5px;
  2214. background-color: #ffffff;
  2215. border-bottom: 1px solid #c9c6c696;
  2216. border-radius: 8px;
  2217. }
  2218. .OJBetter_setting_menu .badge {
  2219. border-radius: 4px;
  2220. border: 1px solid #009688;
  2221. color: #009688;
  2222. background-color: #fff;
  2223. padding: 0.5px 4px;
  2224. margin-left: 5px;
  2225. margin-right: auto;
  2226. line-height: initial;
  2227. font-weight: initial;
  2228. }
  2229. .OJBetter_setting_menu .missing {
  2230. box-shadow: inset 0px 0px 1px 1px red;
  2231. }
  2232. /* 页面切换 */
  2233. .OJBetter_setting_menu .settings-page {
  2234. display: none;
  2235. }
  2236. .OJBetter_setting_menu .settings-page.active {
  2237. display: block;
  2238. }
  2239. .OJBetter_setting_container {
  2240. display: flex;
  2241. }
  2242. .OJBetter_setting_sidebar {
  2243. flex: 0 0 auto;
  2244. min-width: 110px;
  2245. padding: 6px 10px 6px 6px;
  2246. margin: 20px 0px;
  2247. border-right: 1px solid #d4d8e9;
  2248. }
  2249. .OJBetter_setting_content {
  2250. flex-grow: 1;
  2251. margin: 20px 0px 0px 12px;
  2252. padding-right: 10px;
  2253. max-height: 580px;
  2254. overflow-y: auto;
  2255. box-sizing: border-box;
  2256. }
  2257. .OJBetter_setting_sidebar h3 {
  2258. margin-top: 0;
  2259. }
  2260. .OJBetter_setting_sidebar hr {
  2261. margin-top: 10px;
  2262. margin-bottom: 10px;
  2263. border: none;
  2264. border-top: 1px solid #DADCE0;
  2265. }
  2266. .OJBetter_setting_sidebar ul {
  2267. list-style-type: none;
  2268. margin: 0;
  2269. padding: 0;
  2270. }
  2271. .OJBetter_setting_sidebar li {
  2272. margin: 5px 0px;
  2273. background-color: #ffffff;
  2274. border: 1px solid #d4d8e9;
  2275. border-radius: 4px;
  2276. font-size: 16px;
  2277. }
  2278. .OJBetter_setting_sidebar li a {
  2279. text-decoration: none;
  2280. display: flex;
  2281. width: 100%;
  2282. font-size: 16px;
  2283. color: gray;
  2284. background-color: #ffffff;
  2285. border: none;
  2286. letter-spacing: 2px;
  2287. padding: 7px;
  2288. margin: 0px;
  2289. border-radius: 4px;
  2290. align-items: center;
  2291. -webkit-box-sizing: border-box;
  2292. -moz-box-sizing: border-box;
  2293. box-sizing: border-box;
  2294. }
  2295. .OJBetter_setting_sidebar li a.active {
  2296. background-color: #eceff1c7;
  2297. }
  2298. /* 链接样式 */
  2299. .OJBetter_setting_menu a {
  2300. font-size: 13px;
  2301. color: #009688;
  2302. background-color: #E0F2F1;
  2303. border: 1px solid #009688;
  2304. border-radius: 4px;
  2305. padding: 0px 5px;
  2306. margin: 0px 5px;
  2307. text-decoration: none;
  2308. }
  2309. /* 下拉选择框 */
  2310. .OJBetter_setting_menu select {
  2311. appearance: none;
  2312. padding: 5px 10px;
  2313. margin: -5px 0px;
  2314. border-radius: 6px;
  2315. border-style: solid;
  2316. border: 1px solid #ced4da;
  2317. color: #009688;
  2318. background: #ffffff;
  2319. font-size: 15px;
  2320. }
  2321. .OJBetter_setting_menu select:focus-visible {
  2322. outline: none;
  2323. }
  2324. .OJBetter_setting_menu select option:disabled {
  2325. color: #EEEEEE;
  2326. }
  2327. /* 数值输入框 */
  2328. .OJBetter_setting_menu input[type="number"] {
  2329. width: 40px;
  2330. color: #009688;
  2331. font-size: 15px;
  2332. appearance: none;
  2333. padding: 5px 10px;
  2334. margin: -5px 3px;
  2335. border-radius: 6px;
  2336. border-style: solid;
  2337. border: 1px solid #ced4da;
  2338. }
  2339. .OJBetter_setting_menu input[type="number"]:focus-visible {
  2340. outline: none;
  2341. }
  2342. .OJBetter_setting_menu input[type="number"]::-webkit-inner-spin-button,
  2343. .OJBetter_setting_menu input[type="number"]::-webkit-outer-spin-button {
  2344. -webkit-appearance: none;
  2345. margin: 0;
  2346. }
  2347. /*设置面板-滚动条*/
  2348. .OJBetter_setting_menu::-webkit-scrollbar, .OJBetter_setting_content::-webkit-scrollbar,
  2349. .OJBetter_modal .content::-webkit-scrollbar {
  2350. width: 5px;
  2351. height: 7px;
  2352. background-color: #aaa;
  2353. }
  2354. .OJBetter_setting_menu::-webkit-scrollbar-thumb, .OJBetter_setting_content::-webkit-scrollbar-thumb,
  2355. .OJBetter_modal .content::-webkit-scrollbar-thumb {
  2356. background-clip: padding-box;
  2357. background-color: #d7d9e4;
  2358. }
  2359. .OJBetter_setting_menu::-webkit-scrollbar-track, .OJBetter_setting_content::-webkit-scrollbar-track,
  2360. .OJBetter_modal .content::-webkit-scrollbar-track {
  2361. background-color: #f1f1f1;
  2362. }
  2363. /*设置面板-关闭按钮*/
  2364. .OJBetter_setting_menu .tool-box {
  2365. position: absolute;
  2366. width: 20px;
  2367. height: 20px;
  2368. top: 3px;
  2369. right: 3px;
  2370. }
  2371. .OJBetter_setting_menu .btn-close {
  2372. width: 20px;
  2373. height: 20px;
  2374. border-radius: 50%;
  2375. border: none;
  2376. margin: 0px;
  2377. padding: 0px;
  2378. background-color: #ff000080;
  2379. transition: .15s ease all;
  2380. box-sizing: border-box;
  2381. text-align: center;
  2382. color: transparent;
  2383. }
  2384. .OJBetter_setting_menu .iconfont {
  2385. font-size: 10px;
  2386. font-weight: bolder;
  2387. }
  2388. .OJBetter_setting_menu .btn-close:hover {
  2389. color: #ffffff;
  2390. background-color: #ff0000cc;
  2391. box-shadow: 0 5px 5px 0 #00000026;
  2392. }
  2393. .OJBetter_setting_menu .btn-close:active {
  2394. color: #ffffffde;
  2395. background-color: #ff000080;
  2396. }
  2397. /*设置面板-checkbox*/
  2398. .OJBetter_setting_menu input[type=checkbox]:focus {
  2399. outline: 0px;
  2400. }
  2401. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"] {
  2402. margin: 0px;
  2403. appearance: none;
  2404. -webkit-appearance: none;
  2405. width: 40px;
  2406. height: 20px;
  2407. border: 1.5px solid #D7CCC8;
  2408. padding: 0px !important;
  2409. border-radius: 20px;
  2410. background: #efebe978;
  2411. position: relative;
  2412. box-sizing: border-box;
  2413. }
  2414. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::before {
  2415. content: "";
  2416. width: 17px;
  2417. height: 17px;
  2418. background: #D7CCC8;
  2419. border: 1.5px solid #BCAAA4;
  2420. border-radius: 50%;
  2421. position: absolute;
  2422. top: 0;
  2423. left: 0;
  2424. transform: translate(2%, 2%);
  2425. transition: all 0.3s ease-in-out;
  2426. box-sizing: border-box;
  2427. }
  2428. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::after {
  2429. 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");
  2430. position: absolute;
  2431. top: 0;
  2432. left: 24px;
  2433. }
  2434. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked {
  2435. border: 1.5px solid #C5CAE9;
  2436. background: #E8EAF6;
  2437. }
  2438. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::before {
  2439. background: #C5CAE9;
  2440. border: 1.5px solid #7986CB;
  2441. transform: translate(122%, 2%);
  2442. transition: all 0.3s ease-in-out;
  2443. }
  2444. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::after {
  2445. 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");
  2446. position: absolute;
  2447. top: 1.5px;
  2448. left: 4.5px;
  2449. }
  2450. .OJBetter_setting_menu .OJBetter_setting_list button {
  2451. cursor: pointer;
  2452. color: #7986cb;
  2453. background-color: #e8eaf6;
  2454. border: 1px solid #7986cb;
  2455. border-radius: 6px;
  2456. width: 100px;
  2457. margin: -5px 2px;
  2458. padding: 5px 10px;
  2459. }
  2460. .OJBetter_setting_menu .OJBetter_setting_list button:hover {
  2461. color: #e8eaf6;
  2462. background-color: #7986cb;
  2463. border: 1px solid #7986cb;
  2464. }
  2465. .OJBetter_setting_menu label, #darkMode_span, #loaded_span {
  2466. font-size: 16px;
  2467. }
  2468. .OJBetter_setting_list {
  2469. display: flex;
  2470. flex-wrap: wrap;
  2471. align-items: center;
  2472. padding: 10px;
  2473. margin: 5px 0px;
  2474. background-color: #ffffff;
  2475. border: 1px solid #c9c6c642;
  2476. border-bottom-color: #c9c6c696;
  2477. border-radius: 8px;
  2478. justify-content: space-between;
  2479. }
  2480. .OJBetter_setting_list.alert_danger {
  2481. color: #F44336;
  2482. background-color: #FFEBEE;
  2483. border: 1px solid #F44336;
  2484. margin: 10px 0px;
  2485. }
  2486. .OJBetter_setting_list.alert_warn {
  2487. color: #E65100;
  2488. background-color: #FFF3E0;
  2489. border: 1px solid #FF9800;
  2490. margin: 10px 0px;
  2491. }
  2492. .OJBetter_setting_list.alert_tip {
  2493. color: #009688;
  2494. background-color: #E0F2F1;
  2495. border: 1px solid #009688;
  2496. margin: 10px 0px;
  2497. }
  2498. .OJBetter_setting_list.alert_info {
  2499. color: #ffffff;
  2500. background-color: #009688;
  2501. margin: 10px 0px;
  2502. box-shadow: rgba(0, 0, 0, 0.06) 0px 2px 4px 0px inset;
  2503. }
  2504. .OJBetter_setting_list p:not(:last-child) {
  2505. margin-bottom: 10px;
  2506. }
  2507. .OJBetter_setting_list p:not(:first-child) {
  2508. margin-top: 10px;
  2509. }
  2510. /*设置面板-checkboxs*/
  2511. .OJBetter_setting_menu .OJBetter_checkboxs {
  2512. flex-basis: 100%;
  2513. display: flex;
  2514. padding: 8px;
  2515. margin: 10px 0px 0px 0px;
  2516. border-bottom: 1px solid #c9c6c696;
  2517. border-radius: 8px;
  2518. border: 1px solid #c5cae9;
  2519. background-color: #f0f8ff;
  2520. }
  2521. .OJBetter_setting_menu .OJBetter_checkboxs label {
  2522. font-size: 13px;
  2523. margin: 0px 6px 0px 3px;
  2524. }
  2525. .OJBetter_setting_menu .OJBetter_checkboxs input[type=checkbox]:checked+label{
  2526. color: #7986cb;
  2527. }
  2528. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"] {
  2529. border: none;
  2530. width: 16px;
  2531. height: 16px;
  2532. }
  2533. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before{
  2534. background: #ffffff;
  2535. transform: none;
  2536. width: 16px;
  2537. height: 16px;
  2538. }
  2539. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked {
  2540. background: none;
  2541. border: none;
  2542. }
  2543. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before {
  2544. border: 1.5px solid #95a2de;
  2545. background: #e8eaf6;
  2546. transform: none;
  2547. }
  2548. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::after {
  2549. 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");
  2550. top: 0px;
  2551. left: 3.5px;
  2552. }
  2553. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:disabled+label {
  2554. color: #BDBDBD;
  2555. }
  2556. /*设置面板-radio*/
  2557. .OJBetter_setting_menu label {
  2558. display: block;
  2559. font-weight: initial;
  2560. list-style-type: none;
  2561. padding-inline-start: 0px;
  2562. overflow-x: auto;
  2563. max-width: 100%;
  2564. margin: 3px 0px;
  2565. overflow-x: visible;
  2566. }
  2567. .OJBetter_setting_menu_label_text {
  2568. display: flex;
  2569. border: 1px dashed #00aeeccc;
  2570. height: 35px;
  2571. width: 100%;
  2572. color: #6e6e6e;
  2573. font-weight: 300;
  2574. font-size: 14px;
  2575. letter-spacing: 2px;
  2576. padding: 7px;
  2577. margin-bottom: 4px;
  2578. align-items: center;
  2579. -webkit-box-sizing: border-box;
  2580. -moz-box-sizing: border-box;
  2581. box-sizing: border-box;
  2582. }
  2583. input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
  2584. background: #41e49930;
  2585. border: 1px solid green;
  2586. color: green;
  2587. text-shadow: 0px 0px 0.5px green;
  2588. }
  2589. input[type="radio"]:disabled+.OJBetter_setting_menu_label_text {
  2590. background: #fafafa00;
  2591. border: 1px solid #e0e0e07a;
  2592. color: #e0e0e0;
  2593. }
  2594. .OJBetter_setting_menu label input[type="radio"], .OJBetter_contextmenu label input[type="radio"]{
  2595. appearance: none;
  2596. list-style: none;
  2597. padding: 0px !important;
  2598. margin: 0px;
  2599. clip: rect(0 0 0 0);
  2600. -webkit-clip-path: inset(100%);
  2601. clip-path: inset(100%);
  2602. height: 1px;
  2603. overflow: hidden;
  2604. position: absolute;
  2605. white-space: nowrap;
  2606. width: 1px;
  2607. }
  2608. /*设置面板-文本输入框*/
  2609. .OJBetter_setting_menu input[type="text"] {
  2610. display: block;
  2611. height: 25px !important;
  2612. width: 100%;
  2613. background-color: #ffffff;
  2614. color: #727378;
  2615. font-size: 12px;
  2616. border-radius: 0.3rem;
  2617. padding: 1px 5px !important;
  2618. box-sizing: border-box;
  2619. margin: 5px 0px 5px 0px;
  2620. border: 1px solid #00aeeccc;
  2621. box-shadow: 0 0 1px #0000004d;
  2622. }
  2623. .OJBetter_setting_menu .OJBetter_setting_list input[type="text"] {
  2624. margin-left: 5px;
  2625. }
  2626. .OJBetter_setting_menu input[type="text"]:focus-visible{
  2627. border-style: solid;
  2628. border-color: #3f51b5;
  2629. outline: none;
  2630. }
  2631. .OJBetter_setting_menu_config_box {
  2632. width: 100%;
  2633. display: grid;
  2634. margin-top: 5px;
  2635. -webkit-box-sizing: border-box;
  2636. -moz-box-sizing: border-box;
  2637. box-sizing: border-box;
  2638. }
  2639. .OJBetter_setting_menu input::placeholder {
  2640. color: #727378;
  2641. }
  2642. .OJBetter_setting_menu input.no_default::placeholder{
  2643. color: #BDBDBD;
  2644. }
  2645. .OJBetter_setting_menu input.is_null::placeholder{
  2646. color: red;
  2647. border-width: 1.5px;
  2648. }
  2649. .OJBetter_setting_menu input.is_null{
  2650. border-color: red;
  2651. }
  2652. .OJBetter_setting_menu textarea {
  2653. resize: vertical;
  2654. display: block;
  2655. width: 100%;
  2656. height: 60px;
  2657. background-color: #ffffff;
  2658. color: #727378;
  2659. font-size: 12px;
  2660. padding: 1px 5px !important;
  2661. box-sizing: border-box;
  2662. margin: 5px 0px 5px 0px;
  2663. border: 1px solid #00aeeccc;
  2664. box-shadow: 0 0 1px #0000004d;
  2665. }
  2666. .OJBetter_setting_menu textarea:focus-visible{
  2667. border-style: solid;
  2668. border-color: #3f51b5;
  2669. outline: none;
  2670. }
  2671. .OJBetter_setting_menu textarea::placeholder{
  2672. color: #BDBDBD;
  2673. font-size: 14px;
  2674. }
  2675. .OJBetter_setting_menu #tempConfig_save {
  2676. cursor: pointer;
  2677. display: inline-flex;
  2678. padding: 5px;
  2679. background-color: #1aa06d;
  2680. color: #ffffff;
  2681. font-size: 14px;
  2682. line-height: 1.5rem;
  2683. font-weight: 500;
  2684. justify-content: center;
  2685. width: 100%;
  2686. border-radius: 0.375rem;
  2687. border: none;
  2688. box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  2689. margin-top: 20px
  2690. }
  2691. .OJBetter_setting_menu button#debug_button.debug_button {
  2692. width: 18%;
  2693. }
  2694. .OJBetter_setting_menu span.tip {
  2695. color: #999;
  2696. font-size: 12px;
  2697. font-weight: 500;
  2698. padding: 5px 0px;
  2699. }
  2700. /*设置面板-tip*/
  2701. .help_tip {
  2702. margin-right: auto;
  2703. }
  2704. span.input_label {
  2705. font-size: 14px;
  2706. }
  2707. .help_tip .tip_text {
  2708. display: none;
  2709. position: absolute;
  2710. color: #697e91;
  2711. font-weight: 400;
  2712. font-size: 14px;
  2713. letter-spacing: 0px;
  2714. background-color: #ffffff;
  2715. padding: 10px;
  2716. margin: 5px 0px;
  2717. border-radius: 4px;
  2718. border: 1px solid #e4e7ed;
  2719. box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);
  2720. z-index: 100;
  2721. }
  2722. .help_tip .tip_text p {
  2723. margin-bottom: 5px;
  2724. }
  2725. .help_tip .tip_text:before {
  2726. content: "";
  2727. position: absolute;
  2728. top: -20px;
  2729. right: -10px;
  2730. bottom: -10px;
  2731. left: -10px;
  2732. z-index: -1;
  2733. }
  2734. .help-icon {
  2735. cursor: help;
  2736. width: 15px;
  2737. color: #b4b9d4;
  2738. margin-left: 5px;
  2739. margin-top: 3px;
  2740. }
  2741. .OJBetter_setting_menu .OJBetter_setting_menu_label_text .help_tip .help-icon {
  2742. color: #7fbeb2;
  2743. }
  2744. .help_tip .help-icon:hover + .tip_text, .help_tip .tip_text:hover {
  2745. display: block;
  2746. cursor: help;
  2747. width: 250px;
  2748. }
  2749. /* 版本信息 */
  2750. .OJBetter_setting_menu .versionInfo{
  2751. display: grid;
  2752. justify-items: center;
  2753. font-size: 16px;
  2754. padding: 10px;
  2755. }
  2756. .OJBetter_setting_menu .versionInfo>* {
  2757. margin: 10px 0px;
  2758. }
  2759.  
  2760. /* 配置管理面板 */
  2761. .config{
  2762. width: 100%;
  2763. margin: 10px 0px;
  2764. }
  2765. .config li.tempConfig_add_button {
  2766. cursor: pointer;
  2767. height: 40px;
  2768. border: 1px dashed #BDBDBD;
  2769. border-radius: 8px;
  2770. background-color: #fcfbfb36;
  2771. color: #bdbdbd;
  2772. font-size: 14px;
  2773. align-items: center;
  2774. justify-content: center;
  2775. }
  2776. .config li.tempConfig_add_button:hover {
  2777. border: 1px dashed #03A9F4;
  2778. background-color: #d7f0fb8c;
  2779. color: #03A9F4;
  2780. }
  2781. .config .config_bar_list {
  2782. display: flex;
  2783. width: 100%;
  2784. padding-bottom: 2px;
  2785. border: 1px solid #c5cae9;
  2786. background-color: #f0f8ff;
  2787. box-sizing: border-box;
  2788. border-radius: 0px 0px 8px 8px;
  2789. }
  2790. .config .config_bar_list input[type="radio"] {
  2791. appearance: none;
  2792. width: 0;
  2793. height: 0;
  2794. overflow: hidden;
  2795. }
  2796. .config .config_bar_list input[type="radio"] {
  2797. margin: 0px;
  2798. }
  2799. .config .config_bar_list input[type=radio]:focus {
  2800. outline: 0px;
  2801. }
  2802. .config .config_bar_ul_li_text {
  2803. display: flex;
  2804. align-items: center;
  2805. justify-content: center;
  2806. max-width: 100%;
  2807. height: 40px;
  2808. overflow-x: auto;
  2809. font-size: 14px;
  2810. font-weight: 400;
  2811. margin: 0px 4px;
  2812. padding: 3px;
  2813. border: 1px solid #dedede;
  2814. border-radius: 10px;
  2815. box-shadow: 0px 2px 4px 0px rgba(0,0,0,.05);
  2816. box-sizing: border-box;
  2817. }
  2818. .config .config_bar_ul li button {
  2819. background-color: #e6e6e6;
  2820. color: #727378;
  2821. height: 23px;
  2822. font-size: 14px;
  2823. border-radius: 0.3rem;
  2824. padding: 1px 5px;
  2825. margin: 5px;
  2826. border: none;
  2827. box-shadow: 0 0 1px #0000004d;
  2828. }
  2829. .config .config_bar_ul {
  2830. display: flex;
  2831. align-items: center;
  2832. list-style-type: none;
  2833. padding-inline-start: 0px;
  2834. overflow-x: auto;
  2835. max-width: 100%;
  2836. margin: 0px;
  2837. padding: 5px;
  2838. }
  2839. .config .config_bar_ul li {
  2840. width: 80px;
  2841. display: grid;
  2842. margin: 4px 4px;
  2843. min-width: 100px;
  2844. box-sizing: border-box;
  2845. }
  2846. .config .config_bar_ul_li_text:hover {
  2847. background-color: #eae4dc24;
  2848. }
  2849. input[type="radio"]:checked + .config_bar_ul_li_text {
  2850. background: #41b3e430;
  2851. border: 1px solid #5e7ce0;
  2852. color: #5e7ce0;
  2853. }
  2854. .config .config_bar_ul::-webkit-scrollbar {
  2855. width: 5px;
  2856. height: 4px;
  2857. }
  2858. .config .config_bar_ul::-webkit-scrollbar-thumb {
  2859. background-clip: padding-box;
  2860. background-color: #d7d9e4;
  2861. border-radius: 8px;
  2862. }
  2863. .config .config_bar_ul::-webkit-scrollbar-button:start:decrement {
  2864. width: 4px;
  2865. background-color: transparent;
  2866. }
  2867. .config .config_bar_ul::-webkit-scrollbar-button:end:increment {
  2868. width: 4px;
  2869. background-color: transparent;
  2870. }
  2871. .config .config_bar_ul::-webkit-scrollbar-track {
  2872. border-radius: 5px;
  2873. }
  2874. .config .config_bar_ul_li_text::-webkit-scrollbar {
  2875. width: 5px;
  2876. height: 7px;
  2877. background-color: #aaa;
  2878. }
  2879. .config .config_bar_ul_li_text::-webkit-scrollbar-thumb {
  2880. background-clip: padding-box;
  2881. background-color: #d7d9e4;
  2882. }
  2883. .config .config_bar_ul_li_text::-webkit-scrollbar-track {
  2884. background-color: #f1f1f1;
  2885. }
  2886. .config .config_bar_list_add_div {
  2887. display: flex;
  2888. height: 40px;
  2889. margin: 4px 2px;
  2890. }
  2891.  
  2892. /* 修改菜单 */
  2893. #config_bar_menu {
  2894. z-index: 400;
  2895. position: fixed;
  2896. width: 60px;
  2897. background: #ffffff;
  2898. box-shadow: 1px 1px 4px 0px #0000004d;
  2899. border: 0px solid rgba(0,0,0,0.04);
  2900. border-radius: 4px;
  2901. padding: 8px 0;
  2902. }
  2903. .config_bar_menu_item {
  2904. cursor: pointer;
  2905. padding: 2px 6px;
  2906. display: flex;
  2907. justify-content: center;
  2908. align-items: center;
  2909. height: 32px;
  2910. font-size: 14px;
  2911. font-weight: 500;
  2912. box-shadow: inset 0px 0px 0px 0px #8bb2d9;
  2913. }
  2914. #config_bar_menu_edit:hover {
  2915. background-color: #00aeec;
  2916. color: white;
  2917. }
  2918. #config_bar_menu_delete:hover {
  2919. background-color: #FF5722;
  2920. color: white;
  2921. }
  2922.  
  2923. /* 配置编辑页面 */
  2924. #config_edit_menu {
  2925. z-index: 300;
  2926. width: 450px;
  2927. }
  2928.  
  2929. /* 黑暗模式选项按钮 */
  2930. .dark-mode-selection {
  2931. display: flex;
  2932. justify-content: center;
  2933. align-items: center;
  2934. max-width: 350px;
  2935. -webkit-user-select: none;
  2936. -moz-user-select: none;
  2937. -ms-user-select: none;
  2938. user-select: none;
  2939. }
  2940. .dark-mode-selection label {
  2941. margin: 8px 0px 8px 8px;
  2942. }
  2943. .dark-mode-selection > * {
  2944. margin: 6px;
  2945. }
  2946. .dark-mode-selection .OJBetter_setting_menu_label_text {
  2947. border-radius: 8px;
  2948. margin-bottom: 0px;
  2949. }
  2950.  
  2951. /*确认弹窗*/
  2952. .OJBetter_modal {
  2953. z-index: 600;
  2954. display: grid;
  2955. position: fixed;
  2956. top: 50%;
  2957. left: 50%;
  2958. transform: translate(-50%, -50%);
  2959. font-size: 12px;
  2960. font-family: var(--vp-font-family-base);
  2961. width: max-content;
  2962. padding: 10px 20px;
  2963. box-shadow: 0px 0px 0px 4px #ffffff;
  2964. border-radius: 6px;
  2965. background-color: #f0f4f9;
  2966. border-collapse: collapse;
  2967. border: 1px solid #ffffff;
  2968. color: #697e91;
  2969. }
  2970. .OJBetter_modal h2 {
  2971. font-size: 1.6em;
  2972. font-weight: 700;
  2973. }
  2974. .OJBetter_modal .content{
  2975. white-space: nowrap;
  2976. max-height: 500px;
  2977. overflow-y: auto;
  2978. }
  2979. .OJBetter_modal .buttons{
  2980. display: flex;
  2981. padding-top: 15px;
  2982. }
  2983. .OJBetter_modal button {
  2984. display: inline-flex;
  2985. justify-content: center;
  2986. align-items: center;
  2987. line-height: 1;
  2988. white-space: nowrap;
  2989. cursor: pointer;
  2990. text-align: center;
  2991. box-sizing: border-box;
  2992. outline: none;
  2993. transition: .1s;
  2994. user-select: none;
  2995. vertical-align: middle;
  2996. -webkit-appearance: none;
  2997. height: 24px;
  2998. padding: 5px 11px;
  2999. margin-right: 15px;
  3000. font-size: 12px;
  3001. border-radius: 4px;
  3002. color: #ffffff;
  3003. background: #009688;
  3004. border-color: #009688;
  3005. border: none;
  3006. }
  3007. .OJBetter_modal button.secondary{
  3008. background-color:#4DB6AC;
  3009. }
  3010. .OJBetter_modal button:hover{
  3011. background-color:#4DB6AC;
  3012. }
  3013. .OJBetter_modal button.secondary:hover {
  3014. background-color: #80CBC4;
  3015. }
  3016. .OJBetter_modal .help-icon {
  3017. margin: 0px 8px 0px 0px;
  3018. height: 1em;
  3019. width: 1em;
  3020. line-height: 1em;
  3021. display: inline-flex;
  3022. justify-content: center;
  3023. align-items: center;
  3024. position: relative;
  3025. fill: currentColor;
  3026. font-size: inherit;
  3027. }
  3028. .OJBetter_modal p {
  3029. margin: 5px 0px;
  3030. }
  3031.  
  3032. /* 右键菜单 */
  3033. .OJBetter_contextmenu {
  3034. z-index: 500;
  3035. display: grid;
  3036. position: absolute;
  3037. background-color: #f0f4f9;
  3038. border-collapse: collapse;
  3039. color: #697e91;
  3040. font-family: var(--vp-font-family-base);
  3041. overflow: hidden;
  3042. box-sizing: content-box;
  3043. box-shadow: 0px 0px 0px 2px #eddbdb4d;
  3044. }
  3045. .OJBetter_contextmenu label {
  3046. margin: 0px;
  3047. }
  3048. input[type="radio"]:checked+.OJBetter_contextmenu_label_text {
  3049. background: #41e49930;
  3050. border: 1px solid green;
  3051. color: green;
  3052. font-weight: 500;
  3053. }
  3054. .OJBetter_contextmenu_label_text {
  3055. display: flex;
  3056. border: 1px dashed #80cbc4;
  3057. height: 26px;
  3058. width: 100%;
  3059. color: gray;
  3060. font-size: 13px;
  3061. font-weight: initial;
  3062. padding: 4px;
  3063. align-items: center;
  3064. -webkit-box-sizing: border-box;
  3065. -moz-box-sizing: border-box;
  3066. box-sizing: border-box;
  3067. }
  3068. .OJBetter_contextmenu_label_text:hover {
  3069. color: #F44336;
  3070. border: 1px dashed #009688;
  3071. background-color: #ffebcd;
  3072. }
  3073.  
  3074. /* RatingByClist */
  3075. .ratingBadge, html[data-theme=dark] button.ratingBadge{
  3076. display: block;
  3077. font-weight: 700;
  3078. font-size: 11px;
  3079. margin-top: 5px;
  3080. padding: 2px;
  3081. border-radius: 4px;
  3082. color: #B0BEC5;
  3083. border: 1px solid #cccccc66;
  3084. }
  3085.  
  3086. /* 多选翻译 */
  3087. .block_selected{
  3088. box-shadow: 0px 0px 0px 1px #FF9800;
  3089. outline: none;
  3090. }
  3091.  
  3092. /* 悬浮菜单 */
  3093. .OJBetter_MiniTranslateButton {
  3094. z-index: 100;
  3095. display: grid;
  3096. position: absolute;
  3097. border-collapse: collapse;
  3098. fill: #F57C00;
  3099. background-color: #FFF3E0;
  3100. overflow: hidden;
  3101. box-sizing: content-box;
  3102. box-shadow: 0px 0px 0px 2px #FFE0B2;
  3103. border-radius: 100%;
  3104. }
  3105. .OJBetter_MiniTranslateButton:hover {
  3106. cursor: pointer;
  3107. box-shadow: 0px 0px 0px 2px #FFB74D;
  3108. }
  3109.  
  3110. /* acmsguru划分块 */
  3111. .OJBetter_acmsguru {
  3112. margin: 0 0 1em!important;
  3113. }
  3114.  
  3115. /* 代码提交表单 */
  3116. #OJBetter_SubmitForm.input-output-copier:hover {
  3117. background-color: #ffffff00;
  3118. }
  3119. #OJBetter_SubmitForm input[type="number"] {
  3120. width: 40px;
  3121. color: #009688;
  3122. appearance: none;
  3123. border-radius: 6px;
  3124. border-style: solid;
  3125. border: none;
  3126. background-color: #ffffff00;
  3127. }
  3128. #OJBetter_SubmitForm :focus-visible {
  3129. outline: none;
  3130. }
  3131. #OJBetter_SubmitForm .topDiv {
  3132. height: 50px;
  3133. display: flex;
  3134. align-items: center;
  3135. justify-content: space-between;
  3136. padding: 10px 0px;
  3137. box-sizing: border-box;
  3138. }
  3139. #OJBetter_SubmitForm .topDiv .topRightDiv {
  3140. height: 100%;
  3141. display: flex;
  3142. flex-wrap: wrap;
  3143. gap: 0px;
  3144. }
  3145. #OJBetter_SubmitForm input[type="checkbox"], #OJBetter_SubmitForm label {
  3146. margin: 0px;
  3147. font-weight: initial;
  3148. }
  3149. #OJBetter_SubmitForm #fontSizeInput {
  3150. border: none;
  3151. background-color: #ffffff00;
  3152. }
  3153.  
  3154. /* 顶部区域 */
  3155. #OJBetter_SubmitForm .topRightDiv>* {
  3156. height: 100%;
  3157. box-sizing: border-box;
  3158. }
  3159. #OJBetter_SubmitForm .topRightDiv>button{
  3160. padding: 0px 8px;
  3161. }
  3162. #OJBetter_SubmitForm .topRightDiv {
  3163. display: flex;
  3164. flex-wrap: wrap;
  3165. gap: 0px;
  3166. align-items: center;
  3167. }
  3168.  
  3169. /* LSP连接Log */
  3170. #LSPLog{
  3171. width: 500px;
  3172. height: 500px;
  3173. position: fixed;
  3174. top: 50%;
  3175. left: 50%;
  3176. padding: 10px;
  3177. transform: translate(-50%, -50%);
  3178. border: 1px solid;
  3179. z-index: 200;
  3180. background-color: #ffffff;
  3181. }
  3182. #LSPLog button{
  3183. position: fixed;
  3184. top: 10px;
  3185. right: 10px;
  3186. z-index: 200;
  3187. }
  3188. #LSPLog #LSPLogList{
  3189. width: 500px;
  3190. height: 500px;
  3191. overflow: auto;
  3192. color: #424242;
  3193. }
  3194. #LSPLog li:nth-child(odd){
  3195. background-color: #f5f5f5;
  3196. }
  3197. #LSPLog details{
  3198. padding: 2px;
  3199. }
  3200.  
  3201. /* 代码编辑器 */
  3202. #OJBetter_editor{
  3203. box-sizing: border-box;
  3204. height: 600px;
  3205. border: 1px solid #d3d3d3;
  3206. width: 100%;
  3207. resize: vertical;
  3208. display: flex;
  3209. flex-direction: column;
  3210. }
  3211. #OJBetter_editor.fullscreen{
  3212. position: fixed;
  3213. top: 0;
  3214. left: 0;
  3215. width: 100%;
  3216. height: 100vh;
  3217. z-index: 2000;
  3218. }
  3219. #OJBetter_editor.bottom{
  3220. position: fixed;
  3221. bottom: 0;
  3222. left: 0;
  3223. width: 100%;
  3224. height: 50vh;
  3225. z-index: 2000;
  3226. }
  3227. .ojb_btn.exit_button_bottom {
  3228. position: fixed;
  3229. bottom: 30px;
  3230. right: 15px;
  3231. z-index: 2000;
  3232. height: 28px;
  3233. }
  3234.  
  3235. /* monaco */
  3236. #OJBetter_monaco {
  3237. flex: 1;
  3238. min-height: 0;
  3239. width: 100%;
  3240. }
  3241. #OJBetter_monaco .highlight {
  3242. border: 1px solid #ffffff00;
  3243. background-color: #ffffff00!important
  3244. }
  3245. .monaco-hover hr {
  3246. margin: 4px -8px 4px !important;
  3247. }
  3248.  
  3249. /* 状态底栏 */
  3250. #OJBetter_statusBar{
  3251. height: 22px;
  3252. font-size: 12px;
  3253. color: #757575;
  3254. border: 1px solid #d3d3d3;
  3255. background-color: #f8f8f8;
  3256. padding: 3px;
  3257. box-sizing: border-box;
  3258. }
  3259.  
  3260. /* 提交 */
  3261. #OJBetter_submitDiv{
  3262. display: flex;
  3263. padding-top: 15px;
  3264. height: 50px;
  3265. box-sizing: border-box;
  3266. }
  3267. #OJBetter_submitDiv >* {
  3268. border-radius: 6px;
  3269. }
  3270. #OJBetter_submitDiv > button {
  3271. height: 100%;
  3272. aspect-ratio: 1 / 1;
  3273. }
  3274. #SubmitButton {
  3275. color: #fff;
  3276. background-color: #209978;
  3277. border-color: #17795E;
  3278. }
  3279. #SubmitButton:hover {
  3280. background-color: #17795e;
  3281. }
  3282. #SubmitButton.disabled {
  3283. background-color: red;
  3284. animation: shake 0.07s infinite alternate;
  3285. }
  3286. #programTypeId{
  3287. height: 100%;
  3288. padding: 5px 10px;
  3289. border-radius: 6px;
  3290. border-style: solid;
  3291. border: 1px solid #ced4da;
  3292. color: #212529;
  3293. }
  3294.  
  3295. /* 调试 */
  3296. .OJBetter_loding{
  3297. padding: 6px 0px 0px 5px;
  3298. height: 22px;
  3299. }
  3300. #CompilerArgsInput{
  3301. flex-grow: 1;
  3302. width: 100%;
  3303. height: 100%;
  3304. margin-bottom: 10px;
  3305. padding: 5px 10px;
  3306. border-radius: 6px;
  3307. box-sizing: border-box;
  3308. border: 1px solid #ccc;
  3309. box-shadow: inset 0px 1px 1px rgba(0,0,0,.075);
  3310. }
  3311. #CompilerArgsInput[disabled] {
  3312. cursor: not-allowed;
  3313. }
  3314. #CompilerSetting{
  3315. font-size: 14px;
  3316. margin-top: 10px;
  3317. display: none;
  3318. }
  3319. #CompilerSetting select, #CompilerSetting textarea{
  3320. padding: 4px 10px;
  3321. border-radius: 6px;
  3322. border-style: solid;
  3323. border: 1px solid #ced4da;
  3324. color: #212529;
  3325. }
  3326. #CompilerBox{
  3327. display: grid;
  3328. margin-top: 10px;
  3329. border: #d0d7de solid 1px;
  3330. border-radius: 6px;
  3331. }
  3332. #CompilerBox > * {
  3333. margin: 5px;
  3334. }
  3335.  
  3336. /* 自定义样例 */
  3337. #customTestBlock {
  3338. margin-top: 10px;
  3339. font-size: 14px;
  3340. color: #616161;
  3341. border: 1px solid #d3d3d3;
  3342. box-sizing: border-box;
  3343. position: relative;
  3344. }
  3345. #customTestBlock #customTests{
  3346. border-top: 1px solid #d3d3d3;
  3347. margin: 0px 0px 40px 0px;
  3348. }
  3349. #customTestBlock summary {
  3350. cursor: pointer;
  3351. padding: 10px;
  3352. }
  3353. #customTestBlock textarea {
  3354. resize: vertical;
  3355. }
  3356. .sampleDiv {
  3357. color: #727378;
  3358. background-color: #FAFAFA;
  3359. padding: 5px;
  3360. margin-bottom: 10px;
  3361. box-shadow: inset 0 0 1px #0000004d;
  3362. position: relative;
  3363. }
  3364. .dynamicTextarea {
  3365. width: 98%;
  3366. height: 120px;
  3367. margin: 10px 5px;
  3368. border: 1px solid #E0E0E0;
  3369. }
  3370. .deleteCustomTest {
  3371. cursor: pointer;
  3372. position: absolute;
  3373. top: 5px;
  3374. right: 5px;
  3375. display: flex;
  3376. fill: #9E9E9E;
  3377. padding: 2px 2px;
  3378. border-radius: 4px;
  3379. border: 1px solid #ffffff00;
  3380. background-color: #ffffff00;
  3381. align-items: center;
  3382. }
  3383. .deleteCustomTest:hover {
  3384. fill: #EF5350;
  3385. border: 1px solid #ef9a9a;
  3386. background-color: #FFEBEE;
  3387. }
  3388. #addCustomTest {
  3389. cursor: pointer;
  3390. position: absolute;
  3391. bottom: 5px;
  3392. right: 5px;
  3393. padding: 3px 10px;
  3394. color: #795548;
  3395. border: 1px solid #ccc;
  3396. border-radius: 4px;
  3397. background-color: #FAFAFA;
  3398. }
  3399. #addCustomTest:hover {
  3400. background-color: #f5f5f5;
  3401. }
  3402.  
  3403. /* 调试结果 */
  3404. #statePanel{
  3405. display: none;
  3406. padding: 5px;
  3407. margin-top: 10px;
  3408. border: 1px solid #ddd;
  3409. border-radius: 4px;
  3410. }
  3411. .test-case {
  3412. padding: 10px;
  3413. border: 1px solid #ddd;
  3414. border-radius: 4px;
  3415. }
  3416. .test-case:not(:first-child){
  3417. margin-top: 5px;
  3418. }
  3419. .test-case > * {
  3420. margin: 5px 0px;
  3421. }
  3422. .test-case > :first-child {
  3423. margin-top: 0px;
  3424. }
  3425. .test-case > :last-child {
  3426. margin-bottom: 0px;
  3427. }
  3428. .test-case-title, .test-case-status {
  3429. font-size: 16px;
  3430. display: inline;
  3431. }
  3432. .test-case-status{
  3433. margin-left: 5px;
  3434. }
  3435. .test-case-status.error{
  3436. color: red;
  3437. }
  3438. .test-case-status.success{
  3439. color: #449d44;
  3440. }
  3441. .test-case-judge {
  3442. font-size: 13px;
  3443. }
  3444.  
  3445. /* 差异对比 */
  3446. .output_diff {
  3447. color: #5d4037;
  3448. margin: 5px 0px;
  3449. display: grid;
  3450. border: 1px solid #bcaaa4;
  3451. font-size: 13px;
  3452. font-family: Consolas, "Lucida Console", "Andale Mono", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
  3453. overflow: auto;
  3454. }
  3455. .output_diff .added {
  3456. background-color: #c8f7c5;
  3457. user-select: none;
  3458. }
  3459. .output_diff .removed {
  3460. background-color: #f7c5c5;
  3461. }
  3462. .output_diff .diffLine {
  3463. display: flex;
  3464. }
  3465. .output_diff .diffLine:nth-child(odd) {
  3466. background-color: #f5f5f5;
  3467. }
  3468. .lineNo {
  3469. display: flex;
  3470. align-items: center;
  3471. justify-content: center;
  3472. width: 17px;
  3473. color: #BDBDBD;
  3474. font-size: 10px;
  3475. border-right: 1px solid;
  3476. user-select: none;
  3477. }
  3478. .lineContent {
  3479. display: grid;
  3480. width: 100%;
  3481. }
  3482. .lineContent>span {
  3483. height: 16px;
  3484. padding-left: 3px;
  3485. }
  3486. .output_no_diff {
  3487. padding: 5px;
  3488. border: 1px solid #ddd;
  3489. }
  3490. .diff_note {
  3491. font-size: 10px;
  3492. }
  3493.  
  3494. /* 网站本地化替换规则标记 */
  3495. .markingTextReplaceRule{
  3496. color: #FFF3E0;
  3497. background-color: #FF9800;
  3498. }
  3499.  
  3500. /* SelectPage样式 */
  3501. .sp_input {
  3502. padding: 4px 6px px !important;
  3503. height: 20px !important;
  3504. min-height: 20px !important;
  3505. line-height: 20px !important;
  3506. }
  3507. div.sp_clear_btn {
  3508. padding: 0px !important;
  3509. }
  3510.  
  3511. /* 移动设备 */
  3512. @media (max-device-width: 450px) {
  3513. .ojb_btn{
  3514. height: 2em;
  3515. font-size: 1.2em;
  3516. }
  3517. .ojb_btn.OJBetter_setting{
  3518. height: 2.5em;
  3519. font-size: 1em;
  3520. }
  3521. .OJBetter_setting_menu{
  3522. width: 90%;
  3523. }
  3524. .OJBetter_setting_menu label, #darkMode_span, #loaded_span, .OJBetter_setting_menu_label_text,
  3525. .OJBetter_setting_sidebar li{
  3526. font-size: 1em;
  3527. }
  3528. .translate-problem-statement{
  3529. font-size: 1.2em;
  3530. }
  3531. .OJBetter_modal{
  3532. font-size: 1.5em;
  3533. }
  3534. .OJBetter_setting_list, .translate-problem-statement{
  3535. padding: 0.5em;
  3536. }
  3537. .OJBetter_setting_menu_label_text{
  3538. height: 2.5em;
  3539. padding: 0.5em;
  3540. }
  3541. #pagBar #jump-input, #pagBar #items-per-page, .OJBetter_modal button{
  3542. height: 2.5em;
  3543. font-size: 1em;
  3544. }
  3545. .translate-problem-statement p, .translate-problem-statement ul li{
  3546. line-height: 1.5em !important;
  3547. }
  3548. .OJBetter_contextmenu_label_text{
  3549. height: 3em;
  3550. font-size: 1em;
  3551. }
  3552. }
  3553.  
  3554. /* 覆盖网站原本的样式 */
  3555. div#select-lang {
  3556. padding: 0px;
  3557. }
  3558. `);
  3559.  
  3560. /**
  3561. * 添加一些依赖库和条件加载的css样式
  3562. */
  3563. function addDependencyStyles() {
  3564. GM_addStyle(GM_getResourceText("xtermcss"));
  3565. GM_addStyle(GM_getResourceText("selectpagecss"));
  3566. // 自定义图标大小
  3567. GM_addStyle(`
  3568. .iconfont {
  3569. font-size: ${OJBetter.preference.iconButtonSize}px;
  3570. }
  3571. `);
  3572. }
  3573.  
  3574. /**
  3575. * 添加包含i18n内容的css样式
  3576. */
  3577. function addI18nStyles() {
  3578. GM_addStyle(`
  3579. /* 加载鼠标悬浮覆盖层css */
  3580. .overlay::before {
  3581. content: '';
  3582. position: absolute;
  3583. top: 0;
  3584. left: 0;
  3585. width: 100%;
  3586. height: 100%;
  3587. 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);
  3588. z-index: 100;
  3589. }
  3590. .overlay::after {
  3591. content: '${i18next.t('targetArea', { ns: 'common' })}';
  3592. position: absolute;
  3593. top: 50%;
  3594. left: 50%;
  3595. transform: translate(-50%, -50%);
  3596. color: #00695C;
  3597. font-size: 16px;
  3598. font-weight: bold;
  3599. z-index: 100;
  3600. }
  3601.  
  3602. .config::before {
  3603. content: "${i18next.t('common.configManageTitle', { ns: 'settings' })}";
  3604. display: block;
  3605. height: 20px;
  3606. background-color: #f0f8ff;
  3607. border: 1px solid #c5cae9;
  3608. border-bottom: 0px;
  3609. line-height: 20px;
  3610. padding: 2px 10px;
  3611. border-radius: 8px 8px 0px 0px;
  3612. box-sizing: content-box;
  3613. }
  3614. .config.missing::before {
  3615. content: "${i18next.t('common.missing.radio', { ns: 'settings' })}";
  3616. background-color: #fef0f0;
  3617. color: #f56c6c;
  3618. border: 1px solid #fab6b6;
  3619. }
  3620. `);
  3621. }
  3622.  
  3623. // ------------------------------
  3624. // 一些工具类
  3625. // ------------------------------
  3626.  
  3627.  
  3628. /**
  3629. * 自定义错误类,以区分不同的错误类型
  3630. */
  3631. class OJB_GMError extends Error {
  3632. constructor(type, message, originalError) {
  3633. super(message);
  3634. this.name = 'GMError';
  3635. this.type = type;
  3636. this.stack = originalError.stack;
  3637. Object.assign(this, originalError);
  3638. }
  3639. }
  3640.  
  3641. /**
  3642. * 文本块替换/恢复类
  3643. */
  3644. class TextBlockReplacer {
  3645. constructor() {
  3646. /** @type {string[]} 匹配项 */
  3647. this.matches = [];
  3648. /** @type {Map<string, string>} 待还原项 */
  3649. this.replacements = new Map();
  3650. /** @type {Map<string, string>} 暂时未找到的待还原项 */
  3651. this.tempReplacements = new Map();
  3652. /** @type {string} 替换符号 */
  3653. this.replaceSymbol = OJBetter.translation.replaceSymbol;
  3654. }
  3655.  
  3656. /**
  3657. * 替换文本
  3658. * @param {string} text 原文本
  3659. * @param {RegExp} regex 匹配规则
  3660. * @returns {string} 替换后的文本
  3661. */
  3662. replace(text, regex) {
  3663. this.matches = text.match(regex) || [];
  3664. try {
  3665. for (let i = 0; i < this.matches.length; i++) {
  3666. const match = this.matches[i];
  3667. const id = OJB_getRandomNumber(8);
  3668. let replacement = '';
  3669. switch (this.replaceSymbol) {
  3670. case "1":
  3671. replacement = `【${id}】`;
  3672. break;
  3673. case "2":
  3674. replacement = `{${id}}`;
  3675. break;
  3676. case "3":
  3677. replacement = `[${id}]`;
  3678. break;
  3679. default:
  3680. replacement = `【${id}】`;
  3681. break;
  3682. }
  3683. text = text.replace(match, replacement);
  3684. this.replacements.set(id, match);
  3685. }
  3686. } catch (e) { }
  3687. return text;
  3688. }
  3689.  
  3690.  
  3691. /**
  3692. * 恢复替换的文本
  3693. * @param {string} text 还原前的文本
  3694. * @returns {string} 还原后的文本
  3695. */
  3696. recover(text) {
  3697. let textCopy = text;
  3698.  
  3699. /**
  3700. * 替换文本
  3701. * @param {string} replacement 替换的文本
  3702. * @param {string} regexPattern 匹配规则
  3703. * @returns {void}
  3704. */
  3705. const replaceText = (replacement, regexPattern) => {
  3706. const latexMatch = '(?<latex_block>\\$\\$(\\\\.|[^\\$])*?\\$\\$)|(?<latex_inline>\\$(\\\\.|[^\\$])*?\\$)|';
  3707. const regex = new RegExp(latexMatch + regexPattern, 'g');
  3708. textCopy = textCopy.replace(regex, (match, ...args) => {
  3709. // LaTeX中的不替换
  3710. const groups = args[args.length - 1]; // groups是replace方法的最后一个参数
  3711. if (groups.latex_block || groups.latex_inline) return match;
  3712. // 没有空格则加一个
  3713. const offset = args[args.length - 3]; // offset是replace方法的倒数第三个参数
  3714. let leftSpace = "", rightSpace = "";
  3715. if (!/\s/.test(textCopy[offset - 1])) leftSpace = " ";
  3716. if (!/\s/.test(textCopy[offset + match.length])) rightSpace = " ";
  3717. return leftSpace + replacement + rightSpace;
  3718. });
  3719. };
  3720.  
  3721. /**
  3722. * 尝试还原
  3723. * @param {string} replacement 替换的文本
  3724. * @param {string} id 替换的 id
  3725. * @returns {boolean} 是否替换成功
  3726. */
  3727. const tryRecover = (replacement, id) => {
  3728. // 尝试还原,如果还原成功,则从 replacements 中删除
  3729. const originalText = textCopy;
  3730. replaceText(replacement, `【\\s*${id}\\s*】|\\[\\s*${id}\\s*\\]|{\\s*${id}\\s*}`); // 替换符完整匹配(考虑了多出空格的情况)
  3731. replaceText(replacement, `【\\s*${id}(?![】\\d])|(?<![【\\d])${id}\\s*】|\\[\\s*${id}(?![\\]\\d])|(?<![\\[\\d])${id}\\s*\\]|{\\s*${id}(?![}\\d])|(?<![{\\d])${id}\\s*}`); // 替换符部分匹配
  3732.  
  3733. if (textCopy === originalText) {
  3734. // 如果文本没有变化,说明没有找到,加入到 tempReplacements
  3735. this.tempReplacements.set(id, replacement);
  3736. return false;
  3737. } else {
  3738. // 如果文本变化了,说明找到并成功替换,则删除
  3739. this.replacements.delete(id);
  3740. this.tempReplacements.delete(id);
  3741. return true;
  3742. }
  3743. }
  3744.  
  3745. // 处理 replacements 中的项
  3746. this.replacements.forEach((replacement, id) => {
  3747. tryRecover(replacement, id);
  3748. });
  3749.  
  3750. // 处理 tempReplacements 中的项
  3751. while (this.tempReplacements.size > 0) {
  3752. let found = false;
  3753. this.tempReplacements.forEach((replacement, id) => {
  3754. found = tryRecover(replacement, id) || found;
  3755. });
  3756. if (!found) break; // 如果这一轮没有找到任何项,终止循环
  3757. }
  3758.  
  3759. // 如果 tempReplacements 还有未找到的项
  3760. if (this.tempReplacements.size > 0) {
  3761. console.warn("There are still some replacements not found:", this.tempReplacements);
  3762. }
  3763.  
  3764. return textCopy;
  3765. }
  3766. }
  3767.  
  3768. // ------------------------------
  3769. // 一些工具函数
  3770. // ------------------------------
  3771.  
  3772. /**
  3773. * 格式化链接格式
  3774. * @param {string} url 链接字符串
  3775. * @returns {string} 清理后的链接字符串
  3776. */
  3777. function OJB_cleanLink(url) {
  3778. if (url === null || url === undefined) return "";
  3779.  
  3780. // 替换'http://'为'https://'
  3781. let cleanUrl = url.replace(/^http:\/\//i, 'https://');
  3782.  
  3783. // 移除末尾的斜杠
  3784. cleanUrl = cleanUrl.replace(/\/$/, '');
  3785.  
  3786. return cleanUrl;
  3787. }
  3788.  
  3789. /**
  3790. * 深度比较两个对象或数组是否完全相等。
  3791. * @param {any} a - 第一个比较对象。
  3792. * @param {any} b - 第二个比较对象。
  3793. * @returns {boolean} - 如果两个对象或数组深度相等,则返回true,否则返回false。
  3794. */
  3795. function OJB_deepEquals(a, b) {
  3796. if (a === b) return true;
  3797. if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false;
  3798. const keysA = Object.keys(a);
  3799. const keysB = Object.keys(b);
  3800. if (keysA.length !== keysB.length) return false;
  3801. for (let key of keysA) {
  3802. if (!b.hasOwnProperty(key)) return false;
  3803. if (!OJB_deepEquals(a[key], b[key])) return false;
  3804. }
  3805. return true;
  3806. }
  3807.  
  3808. /**
  3809. * 用于封装需要重试的异步函数
  3810. * @param {Function} task 需要封装的异步函数
  3811. * @param {Object} options 配置项
  3812. * @param {Number} options.maxRetries 重试次数,默认为 5
  3813. * @param {Number} options.retryInterval 重试时间间隔,默认为 0 毫秒
  3814. * @param {Function} options.errorHandler 错误处理函数,默认为抛出错误
  3815. * @param {...any} args task 函数的参数
  3816. * @returns {Promise} 返回 Promise
  3817. */
  3818. async function OJB_promiseRetryWrapper(task, {
  3819. maxRetries = 5,
  3820. retryInterval = 0,
  3821. errorHandler = (err) => { throw err }
  3822. } = {}, ...args) {
  3823. let attemptsLeft = maxRetries;
  3824. while (attemptsLeft--) {
  3825. try {
  3826. return await task(...args);
  3827. } catch (err) {
  3828. if (attemptsLeft <= 0) {
  3829. return errorHandler(err, maxRetries, attemptsLeft);
  3830. }
  3831. if (retryInterval > 0) {
  3832. await OJB_delay(retryInterval);
  3833. }
  3834. }
  3835. }
  3836. }
  3837.  
  3838. /**
  3839. * GM_xmlhttpRequest 的 Promise 封装
  3840. * @param {Object} options GM_xmlhttpRequest 的参数
  3841. * @param {Boolean} isStream 是否为流式请求
  3842. * @returns {Promise<OJB_GMError>} 返回 Promise
  3843. */
  3844. function OJB_GMRequest(options, isStream = false) {
  3845. return new Promise((resolve, reject) => {
  3846. GM_xmlhttpRequest({
  3847. ...options,
  3848. ...(isStream ? {
  3849. onloadstart: resolve
  3850. } : {
  3851. onload: resolve
  3852. }),
  3853. onerror: (error) => reject(new OJB_GMError('error', 'An error occurred during the request.', error)),
  3854. ontimeout: (error) => reject(new OJB_GMError('timeout', 'The request timed out.', error)),
  3855. onabort: (error) => reject(new OJB_GMError('abort', 'The request was aborted.', error)),
  3856. });
  3857. });
  3858. }
  3859.  
  3860. /**
  3861. * 获取cookie
  3862. * @param {string} name cookie名称
  3863. * @returns {string} cookie值
  3864. */
  3865. function OJB_getCookie(name) {
  3866. const cookies = document.cookie.split(";");
  3867. for (let i = 0; i < cookies.length; i++) {
  3868. const cookie = cookies[i].trim();
  3869. const [cookieName, cookieValue] = cookie.split("=");
  3870.  
  3871. if (cookieName === name) {
  3872. return decodeURIComponent(cookieValue);
  3873. }
  3874. }
  3875. return "";
  3876. }
  3877.  
  3878. /**
  3879. * 检查是否仍在同一浏览器会话中
  3880. * @param {string} sessionKey - 会话键名,用于标识会话
  3881. * @returns {boolean} - 如果在当前会话中之前已经设置过这个键,则返回true,否则返回false
  3882. */
  3883. function OJB_isSameBrowserSession(sessionKey) {
  3884. const fullCookieName = `OJB_Session_${sessionKey}`;
  3885. const sessionValue = OJB_getCookie(fullCookieName);
  3886. if (sessionValue === "") {
  3887. document.cookie = `${fullCookieName}=true; path=/`;
  3888. return false;
  3889. }
  3890. return true;
  3891. }
  3892.  
  3893. /**
  3894. * 随机数生成
  3895. * @param {number} numDigits 位数
  3896. * @returns {number}
  3897. */
  3898. function OJB_getRandomNumber(numDigits) {
  3899. let min = Math.pow(10, numDigits - 1);
  3900. let max = Math.pow(10, numDigits) - 1;
  3901. return Math.floor(Math.random() * (max - min + 1)) + min;
  3902. }
  3903.  
  3904. /**
  3905. * 防抖函数
  3906. * @param {Function} callback 回调函数
  3907. * @returns {Function}
  3908. */
  3909. function OJB_debounce(callback) {
  3910. let timer;
  3911. let immediateExecuted = false;
  3912. const delay = 500;
  3913. return function () {
  3914. clearTimeout(timer);
  3915. if (!immediateExecuted) { callback.call(this); immediateExecuted = true; }
  3916. timer = setTimeout(() => { immediateExecuted = false; }, delay);
  3917. };
  3918. }
  3919.  
  3920. /**
  3921. * 为元素添加鼠标拖拽支持
  3922. * @param {JQuery<HTMLElement>} element 要添加拖拽支持的元素
  3923. * @returns {void}
  3924. */
  3925. function OJB_addDraggable(element) {
  3926. let isDragging = false;
  3927. let x, y, l, t, nl, nt;
  3928. let isSpecialMouseDown = false; // 选取某些元素时不拖动
  3929.  
  3930. element.on('mousedown', function (e) {
  3931. isSpecialMouseDown = $(e.target).is('label, p, input, textarea, span, select, details, summary');
  3932. if (isSpecialMouseDown) return;
  3933.  
  3934. isDragging = true;
  3935. x = e.clientX;
  3936. y = e.clientY;
  3937. l = element.offset().left - $(window).scrollLeft();
  3938. t = element.offset().top - $(window).scrollTop();
  3939.  
  3940. element.css({ left: l + 'px', top: t + 'px', transform: 'none' });
  3941.  
  3942. $(document).on("mousemove", drag);
  3943. $(document).on("mouseup", stopDrag);
  3944. element.css('cursor', 'all-scroll');
  3945. });
  3946.  
  3947. const drag = (e) => {
  3948. if (!isDragging) return;
  3949. // 不执行拖动操作
  3950. if ($(e.target).is('label, p, input, textarea, span') || isSpecialMouseDown && !$(e.target).is('input, textarea')) return;
  3951. e.preventDefault();
  3952.  
  3953. const nx = e.clientX;
  3954. const ny = e.clientY;
  3955. nl = nx - (x - l);
  3956. nt = ny - (y - t);
  3957. element.css({ transform: `translate(${nx - x}px, ${ny - y}px)` });
  3958. };
  3959.  
  3960. const stopDrag = () => {
  3961. isDragging = false;
  3962. isSpecialMouseDown = false;
  3963. element.css('cursor', 'default');
  3964.  
  3965. // 在停止拖拽后,设置元素的left和top,并还原transform
  3966. element.css({ left: nl + 'px', top: nt + 'px', transform: 'none' });
  3967. $(document).off("mousemove", drag);
  3968. $(document).off("mouseup", stopDrag);
  3969. };
  3970. }
  3971.  
  3972. /**
  3973. * 切换元素的折叠/展开过渡动画
  3974. * @param {HTMLElement} element
  3975. */
  3976. function OJB_toggleCollapseExpand(element) {
  3977. // 设置transitionend事件监听器的函数
  3978. const setTransitionListener = (listener) => {
  3979. const listenerName = `transitionEndListener${Date.now()}`;
  3980. window[listenerName] = listener;
  3981. element.addEventListener('transitionend', listener);
  3982. element.setAttribute('data-transition-end-listener', listenerName);
  3983. };
  3984.  
  3985. // 移除事件监听器的函数
  3986. const removeTransitionListener = () => {
  3987. const transitionEndListenerName = element.getAttribute('data-transition-end-listener');
  3988. if (transitionEndListenerName) {
  3989. element.removeEventListener('transitionend', window[transitionEndListenerName]);
  3990. element.removeAttribute('data-transition-end-listener');
  3991. }
  3992. };
  3993.  
  3994. const collapsed = element.getAttribute('data-collapsed') === 'true';
  3995. const sectionHeight = element.scrollHeight;
  3996.  
  3997. // 移除事件监听器
  3998. removeTransitionListener();
  3999.  
  4000. // 设置初始样式
  4001. element.style.overflow = 'hidden';
  4002. element.style.transition = 'height 0.3s ease-out 0s';
  4003. element.style.height = collapsed ? `0px` : `${sectionHeight}px`;
  4004. element.style.opacity = collapsed ? '' : '1';
  4005.  
  4006. // 需要立即开始动画
  4007. requestAnimationFrame(() => {
  4008. // 设置结束样式
  4009. element.style.height = collapsed ? `${sectionHeight}px` : `0px`;
  4010. });
  4011.  
  4012. const transitionEndListener = (event) => {
  4013. if (event.propertyName === 'height') {
  4014. if (collapsed) {
  4015. // 展开后的设置
  4016. element.style.height = '';
  4017. element.style.overflow = '';
  4018. } else {
  4019. // 折叠后的设置
  4020. element.style.opacity = '0';
  4021. }
  4022. removeTransitionListener();
  4023. }
  4024. };
  4025.  
  4026. setTransitionListener(transitionEndListener);
  4027.  
  4028. // 更新data-collapsed属性
  4029. element.setAttribute('data-collapsed', collapsed ? 'false' : 'true');
  4030. }
  4031.  
  4032. /**
  4033. * 获取外部JSON并转换为Object
  4034. * @param {string} url JSON Url
  4035. * @param {boolean} [nacache=true] 是否不使用缓存
  4036. * @returns {Promise<Object>} JSON Object
  4037. */
  4038. async function OJB_getExternalJSON(url, nacache = true) {
  4039. const response = await OJB_GMRequest({
  4040. method: "GET",
  4041. url: url,
  4042. nocache: nacache
  4043. });
  4044. try {
  4045. return JSON.parse(response.responseText);
  4046. } catch (e) {
  4047. throw new Error(`JSON parse error\n${e}`);
  4048. }
  4049. }
  4050.  
  4051. /**
  4052. * 创建确认对话框dialog
  4053. * @param {string} title 标题
  4054. * @param {string} content 内容
  4055. * @param {string[]} buttons 按钮 (取消 确定) 可以为null
  4056. * @param {boolean} renderMarkdown 是否使用markdown渲染文本
  4057. * @returns {Promise<boolean>} 用户点击了确定按钮返回true, 否则返回false
  4058. */
  4059. function OJB_createDialog(title, content, buttons, renderMarkdown = false) {
  4060. return new Promise(resolve => {
  4061. let contentHtml = content;
  4062.  
  4063. if (renderMarkdown) {
  4064. const md = window.markdownit();
  4065. contentHtml = md.render(content);
  4066. }
  4067.  
  4068. const dialog = OJB_safeCreateJQElement(`
  4069. <dialog class="OJBetter_modal">
  4070. <h2>${title}</h2>
  4071. <div class="content">${contentHtml}</div>
  4072. </dialog>
  4073. `);
  4074. const buttonbox = OJB_safeCreateJQElement(`<div class="buttons"></div>`);
  4075. const cancelButton = OJB_safeCreateJQElement(`<button class="cancelButton">${buttons[0]}</button>`)
  4076. .addClass("secondary");
  4077. const continueButton = OJB_safeCreateJQElement(`<button class="continueButton">${buttons[1]}</button>`);
  4078. if (buttons[0] !== null) buttonbox.append(cancelButton);
  4079. if (buttons[1] !== null) buttonbox.append(continueButton);
  4080. dialog.append(buttonbox);
  4081. $('body').append(dialog);
  4082.  
  4083. OJB_showModal(dialog);
  4084. OJB_addDraggable(dialog);
  4085.  
  4086. continueButton.click(function () {
  4087. OJB_closeAndRemoveModal(dialog);
  4088. resolve(true);
  4089. });
  4090.  
  4091. cancelButton.click(function () {
  4092. OJB_closeAndRemoveModal(dialog);
  4093. resolve(false);
  4094. });
  4095. });
  4096. }
  4097.  
  4098. /**
  4099. * 显示模态对话框并阻止页面滚动,同时考虑滚动条宽度变化和原始marginRight
  4100. * @param {JQuery<HTMLElement>} element
  4101. */
  4102. function OJB_showModal(element) {
  4103. const dialog = element.get(0);
  4104. dialog.showModal();
  4105. OJBetter.state.openDialogCount++;
  4106.  
  4107. if (OJBetter.state.openDialogCount === 1) {
  4108. const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
  4109. // 获取原始的html marginRight,考虑到可能的非数字值,比如auto
  4110. const originalMarginRight = window.getComputedStyle(document.documentElement).marginRight;
  4111. const marginRightValue = parseFloat(originalMarginRight) || 0; // 将非数字值转换为0
  4112.  
  4113. if (scrollbarWidth > 0) {
  4114. // 保存原始的marginRight,并设置新的值以补偿滚动条宽度
  4115. document.documentElement.style.setProperty('--original-margin-right', originalMarginRight);
  4116. document.documentElement.style.marginRight = `${marginRightValue + scrollbarWidth}px`;
  4117. }
  4118.  
  4119. // 保存原始的overflow样式
  4120. document.documentElement.setAttribute('data-original-overflow', document.documentElement.style.overflow);
  4121. document.documentElement.style.overflow = 'hidden';
  4122. }
  4123.  
  4124. const allowScrollIfNeeded = () => {
  4125. OJBetter.state.openDialogCount--;
  4126. if (OJBetter.state.openDialogCount === 0) {
  4127. // 恢复原始的html marginRight和overflow样式
  4128. const originalMarginRight = document.documentElement.style.getPropertyValue('--original-margin-right');
  4129. document.documentElement.style.marginRight = originalMarginRight;
  4130. document.documentElement.style.removeProperty('--original-margin-right');
  4131.  
  4132. const originalOverflow = document.documentElement.getAttribute('data-original-overflow');
  4133. document.documentElement.style.overflow = originalOverflow;
  4134. document.documentElement.removeAttribute('data-original-overflow');
  4135. }
  4136. };
  4137.  
  4138. dialog.addEventListener('close', allowScrollIfNeeded);
  4139. }
  4140.  
  4141. /**
  4142. * 关闭并移除模态对话框
  4143. * @param {JQuery<HTMLElement>} element
  4144. */
  4145. function OJB_closeAndRemoveModal(element) {
  4146. const dialog = element.get(0);
  4147. dialog.close();
  4148. dialog.remove();
  4149. }
  4150.  
  4151. /**
  4152. * 关闭并移除模态对话框
  4153. * @param {JQuery<HTMLElement>} element
  4154. */
  4155. function OJB_closeModal(element) {
  4156. const dialog = element.get(0);
  4157. dialog.close();
  4158. }
  4159.  
  4160. /**
  4161. * 清除i18next的缓存数据并刷新
  4162. */
  4163. function clearI18nextCache() {
  4164. Object.keys(localStorage)
  4165. .filter(key => key.startsWith('i18next_res_'))
  4166. .forEach(key => localStorage.removeItem(key));
  4167. window.location.reload();
  4168. }
  4169.  
  4170. /**
  4171. * 清除网站本地化数据
  4172. */
  4173. async function clearWebsiteL10nData() {
  4174. OJBetter.common.database.localizeSubsData.clear().then(() => {
  4175. console.log('localizeSubsData table has been cleared');
  4176. window.location.reload();
  4177. }).catch((error) => {
  4178. console.error('Failed to clear localizeSubsData table:', error);
  4179. });
  4180. }
  4181.  
  4182. /**
  4183. * 从Pre代码块中获取原始代码
  4184. * @param {HTMLElement} element pre代码块元素
  4185. * @returns {string|null} 代码文本
  4186. */
  4187. function OJB_getCodeFromPre(element) {
  4188. /**
  4189. * 从Ace格式化的代码块中获取原始代码
  4190. * @param {HTMLElement} element pre代码块元素
  4191. * @returns {string} 代码文本
  4192. */
  4193. const getCodeFromAcePre = function (element) {
  4194. const editor = ace.edit(element);
  4195. return editor.getValue();
  4196. }
  4197.  
  4198. /**
  4199. * 从Pretty格式化的代码块中获取原始代码-1
  4200. * 代码直接存放在 pre 元素中
  4201. * @param {HTMLElement} element pre代码块元素
  4202. * @returns {string} 代码文本
  4203. */
  4204. const getCodeFromPrettyPre = function (element) {
  4205. return Array.from(element.querySelectorAll('li')).map(function (li) {
  4206. return li.textContent;
  4207. }).join('\n');
  4208. }
  4209.  
  4210. /**
  4211. * 从Pretty格式化的代码块中获取原始代码-2
  4212. * 代码存放在子元素 code 中
  4213. * @param {HTMLElement} element pre代码块元素
  4214. * @returns {string} 代码文本
  4215. */
  4216. const getCodeFromPreChild = function (element) {
  4217. const code = element.querySelector("code.prettyprint");
  4218. if (code.classList.contains("linenums")) {
  4219. return getCodeFromPrettyPre(element);
  4220. } else {
  4221. return element.querySelector("code.prettyprint").textContent;
  4222. }
  4223. }
  4224.  
  4225. if (element.id === "submission-code") {
  4226. return getCodeFromAcePre(element);
  4227. } else if (element.classList.contains("prettyprint")) {
  4228. return getCodeFromPrettyPre(element);
  4229. } else if (element.querySelector("code.prettyprint")) {
  4230. return getCodeFromPreChild(element);
  4231. } else {
  4232. return null;
  4233. }
  4234. }
  4235.  
  4236. /**
  4237. * 判断代码的语言
  4238. * @param {string} code 代码文本
  4239. * @returns {string} 可能的语言
  4240. */
  4241. function OJB_codeLangDetect(code) {
  4242. result = hljs.highlightAuto(code);
  4243. return result.language;
  4244. }
  4245.  
  4246. /**
  4247. * 获取指定命名空间下的所有i18n翻译键值对。
  4248. *
  4249. * @param {string} namespace - 要获取键值对的i18next命名空间。
  4250. * @returns {Map<string, string>} 一个包含命名空间下所有键值对的Map对象。
  4251. */
  4252. function OJB_getAllI18nKeysForNamespace(namespace) {
  4253. const language = i18next.language; // 获取当前语言
  4254. const resources = i18next.store.data[language]; // 获取当前语言的所有资源
  4255. const nsResources = resources[namespace]; // 获取特定命名空间的资源
  4256. const resultMap = new Map();
  4257.  
  4258. if (nsResources) {
  4259. // 遍历命名空间下的所有键值对,并添加到Map中
  4260. Object.keys(nsResources).forEach(key => {
  4261. resultMap.set(key, nsResources[key]);
  4262. });
  4263. } else {
  4264. console.log(`No resources found for namespace "${namespace}"`);
  4265. }
  4266.  
  4267. return resultMap;
  4268. }
  4269.  
  4270. /**
  4271. * 更新检查
  4272. */
  4273. async function checkScriptVersion() {
  4274. try {
  4275. const versionResponse = await OJB_GMRequest({
  4276. method: "GET",
  4277. url: "https://aowuucdn.oss-accelerate.aliyuncs.com/script/versions.json",
  4278. timeout: 10 * 1e3,
  4279. nocache: true
  4280. });
  4281. const versionData = JSON.parse(versionResponse.responseText);
  4282. const { [OJBetter.state.formatName]: { dev: version_dev, release: version_release } } = versionData;
  4283. const baseUrls = {
  4284. // greasyfork: 'https://update.greatest.deepsurf.us/scripts/465777/Codeforces%20Better%21.user.js',
  4285. greasyfork: 'https://update.greatest.deepsurf.us/scripts/471106/Atcoder%20Better%21.user.js',
  4286. github: `https://github.com/beijixiaohu/OJBetter/raw/main/script/${OJBetter.about.updateChannel}/${OJBetter.state.formatName}.user.js`,
  4287. aliyunoss: `https://aowuucdn.oss-accelerate.aliyuncs.com/script/${OJBetter.about.updateChannel}/${OJBetter.state.formatName}.user.js`
  4288. };
  4289. /** @type {string} 更新跳转url */
  4290. const updateUrl = baseUrls[OJBetter.about.updateSource];
  4291. /** @type {string} 是否暂时跳过cookie */
  4292. const skipUpdate = OJB_getCookie("skipUpdate");
  4293. /** @type {string} 当前更新频道的最新版本 */
  4294. const version = OJBetter.about.updateChannel == "release" ? version_release : version_dev;
  4295. if (OJB_compareVersions(version, OJBetter.state.version) === 1 && skipUpdate !== "true") {
  4296. const updateConfirmed = await OJB_createDialog(
  4297. i18next.t('update.title', { ns: 'dialog', scriptName: OJBetter.state.name }),
  4298. i18next.t('update.content', { ns: 'dialog', oldVersion: OJBetter.state.version, newVersion: version }),
  4299. [
  4300. i18next.t('update.buttons.0', { ns: 'dialog' }),
  4301. i18next.t('update.buttons.1', { ns: 'dialog' })
  4302. ],
  4303. true
  4304. );
  4305.  
  4306. if (updateConfirmed) {
  4307. window.location.href = updateUrl;
  4308. } else {
  4309. document.cookie = "skipUpdate=true; path=/";
  4310. }
  4311. }
  4312. } catch (error) {
  4313. console.error("Update check failed: ", error);
  4314. }
  4315. }
  4316.  
  4317. /**
  4318. * 公告
  4319. */
  4320. async function showAnnounce() {
  4321. /** @type {string} 最新公告版本*/
  4322. const lastAnnounceVer = i18next.t('lastVersion', { ns: 'announce' });
  4323. if (OJB_compareVersions(OJBetter.state.version, OJBetter.state.lastAnnounceVer) === 1) {
  4324. const title = `🎉${i18next.t('announce.title', { ns: 'dialog' })} ${OJBetter.state.version}`;
  4325. /** @type {Boolean} 是否是新的公告 */
  4326. const isNewAnnounceVer = OJB_compareVersions(lastAnnounceVer, OJBetter.state.lastReadAnnounceVer) === 1;
  4327. /** @type {Boolean} 是否展示新的公告(高于当前版本的测试公告不展示) */
  4328. const showNewAnnounceVer = OJB_compareVersions(lastAnnounceVer, OJBetter.state.version) !== 1;
  4329. /**
  4330. * 获取公告的内容
  4331. * @returns {string} 公告内容
  4332. */
  4333. const getAnnounceContent = function () {
  4334. // 获取公告
  4335. const announceMap = OJB_getAllI18nKeysForNamespace('announce');
  4336. // 移除 'lastVersion' 键
  4337. announceMap.delete('lastVersion');
  4338. // 将 Map 转换为数组并根据版本号排序
  4339. const sortedVersions = [...announceMap.keys()].sort(OJB_compareVersions).reverse();
  4340. let content = "";
  4341. sortedVersions.forEach(version => {
  4342. content += `### ${version}\n\n`; // 使用版本号作为标题
  4343. content += announceMap.get(version); // 添加对应版本的公告内容
  4344. content += "\n\n";
  4345. });
  4346.  
  4347. return content;
  4348. };
  4349.  
  4350. const content = (() => {
  4351. if (isNewAnnounceVer && showNewAnnounceVer) {
  4352. return `${i18next.t('announce.prefix', { ns: 'dialog' })}\n\n${getAnnounceContent()}`;
  4353. } else {
  4354. return i18next.t('announce.divContent', { ns: 'dialog' });
  4355. }
  4356. })();
  4357. const ok = await OJB_createDialog(
  4358. title,
  4359. content,
  4360. [
  4361. null,
  4362. i18next.t('announce.buttons.0', { ns: 'dialog' })
  4363. ],
  4364. true
  4365. ); //跳过折叠块确认
  4366. if (ok) {
  4367. if (isNewAnnounceVer && showNewAnnounceVer) {
  4368. GM_setValue('lastReadAnnounceVer', lastAnnounceVer);
  4369. }
  4370. GM_setValue('lastAnnounceVer', OJBetter.state.version);
  4371. }
  4372. }
  4373. };
  4374.  
  4375. /**
  4376. * 页面顶部提示信息alert类
  4377. */
  4378. class LoadingMessage {
  4379. constructor() {
  4380. this._statusElement = null;
  4381. this._isDisplayed = false;
  4382. this.init();
  4383. }
  4384.  
  4385. /**
  4386. * 初始化加载提示信息
  4387. */
  4388. init() {
  4389. this._statusElement = this.createStatusElement();
  4390. this.insertStatusElement();
  4391. }
  4392.  
  4393. /**
  4394. * 创建提示信息元素
  4395. */
  4396. createStatusElement() {
  4397. const statusElement = $("<div></div>").addClass("alert OJBetter_alert")
  4398. .css({
  4399. "margin": "1em",
  4400. "text-align": "center",
  4401. "position": "relative"
  4402. }).hide();
  4403. return statusElement;
  4404. }
  4405.  
  4406. /**
  4407. * 插入提示信息
  4408. * @returns {void}
  4409. */
  4410. insertStatusElement() {
  4411. // (OJBetter.typeOfPage.is_mSite ? $("header") : $(".menu-box:first").next()).after(this._statusElement);
  4412. $("#main-container").prepend(this._statusElement);
  4413. }
  4414.  
  4415. /**
  4416. * 显示提示信息
  4417. */
  4418. showStatus() {
  4419. this._statusElement.show();
  4420. this._isDisplayed = true;
  4421. }
  4422.  
  4423. /**
  4424. * 隐藏提示信息
  4425. */
  4426. hideStatus() {
  4427. this._statusElement.fadeOut(500);
  4428. this._isDisplayed = false;
  4429. }
  4430.  
  4431. /**
  4432. * 移除提示信息
  4433. */
  4434. removeStatus() {
  4435. this._statusElement.remove();
  4436. this._isDisplayed = false;
  4437. }
  4438.  
  4439. /**
  4440. * 更新提示信息
  4441. * @param {string} text 提示信息文本
  4442. * @param {string} type 提示信息类型,可选值:info, success, warning, error
  4443. * @param {number} timeout 提示信息显示的持续时间(毫秒), 默认为无限长
  4444. */
  4445. updateStatus(text, type = 'info', timeout = Infinity, isMarkdown = false) {
  4446. if (isMarkdown) {
  4447. let md = window.markdownit({
  4448. html: !is_escapeHTML,
  4449. });
  4450. text = md.render(text);
  4451. }
  4452. this._statusElement.html(text).removeClass("alert-info alert-success alert-warning alert-error").addClass(`alert-${type}`);
  4453. if (!this._isDisplayed) {
  4454. this.showStatus();
  4455. }
  4456. if (timeout !== Infinity) {
  4457. setTimeout(() => {
  4458. this.hideStatus();
  4459. }, timeout);
  4460. }
  4461. }
  4462. }
  4463.  
  4464. /**
  4465. * 获取网站本地化的数据
  4466. * @param {*} localizationLanguage 本地化语言
  4467. * @returns {Promise<Object>} 本地化数据
  4468. */
  4469. async function getLocalizeWebsiteJson(localizationLanguage) {
  4470. let data = await OJBetter.common.database.localizeSubsData.get(localizationLanguage);
  4471. let url = localizationLanguage === "zh" ?
  4472. `https://aowuucdn.oss-accelerate.aliyuncs.com/resources/subs/${OJBetter.state.formatName}.json` :
  4473. `https://aowuucdn.oss-accelerate.aliyuncs.com/i18n/${localizationLanguage}/resources/subs/${OJBetter.state.formatName}.json`;
  4474. if (data) data = data.data;
  4475. if (!data) {
  4476. // 如果本地没有数据,从远端获取并保存
  4477. data = await OJB_getExternalJSON(url);
  4478. await OJBetter.common.database.localizeSubsData.put({ lang: localizationLanguage, data: data });
  4479. } else {
  4480. // 如果本地有数据,检查是否已经在当前会话中尝试过更新
  4481. const sessionKey = `ojb_updateL10nWebsiteJson_${localizationLanguage}`;
  4482. if (!OJB_isSameBrowserSession(sessionKey)) {
  4483. // 如果尚未更新,则在后台更新
  4484. (async () => {
  4485. try {
  4486. const newData = await OJB_getExternalJSON(url);
  4487. await OJBetter.common.database.localizeSubsData.put({ lang: localizationLanguage, data: newData });
  4488. console.log("Website local data has been refreshed!");
  4489. } catch (error) {
  4490. console.error('Failed to update localization data:', error);
  4491. }
  4492. })();
  4493. }
  4494. }
  4495. return data;
  4496. }
  4497.  
  4498. /**
  4499. * 网站本地化替换
  4500. * @returns
  4501. */
  4502. async function localizeWebsite() {
  4503. if (OJBetter.localization.websiteLang === "initial") return;
  4504.  
  4505. // 设置网页语言
  4506. var htmlTag = document.getElementsByTagName("html")[0];
  4507. htmlTag.setAttribute("lang", OJBetter.localization.websiteLang);
  4508.  
  4509. // 获取网站本地化的数据
  4510. var subs = await getLocalizeWebsiteJson(OJBetter.localization.websiteLang);
  4511.  
  4512. /**
  4513. * 文本节点遍历替换
  4514. * @param {JQuery} $nodes jQuery对象
  4515. * @param {Object} textReplaceRules 文本替换规则对象
  4516. * @param {string} key 应用的规则集的名字
  4517. */
  4518. const traverseTextNodes = ($nodes, textReplaceRules, key) => {
  4519. if (!$nodes) return;
  4520.  
  4521. $nodes.each((_, node) => {
  4522. if (node.nodeType === Node.TEXT_NODE) {
  4523. Object.entries(textReplaceRules).forEach(([match, replace]) => {
  4524. try {
  4525. const regex = new RegExp(match, 'g');
  4526. const beforeText = node.textContent;
  4527. node.textContent = node.textContent.replace(regex, replace);
  4528. if (node.textContent !== beforeText && OJBetter.dev.isRuleMarkingEnabled) {
  4529. $(node).after(`<span class="markingTextReplaceRule">${key}</span>`);
  4530. }
  4531. } catch (error) {
  4532. console.error(`Error processing text replacement for match: ${match}`, error);
  4533. }
  4534. });
  4535. } else {
  4536. $(node).contents().each((_, childNode) => {
  4537. traverseTextNodes($(childNode), textReplaceRules, key);
  4538. });
  4539. }
  4540. });
  4541. };
  4542.  
  4543. /**
  4544. * value替换
  4545. * @param {JQuery} $nodes jQuery对象
  4546. * @param {Object} valueReplaceRules 值替换规则对象
  4547. * @param {string} key 应用的规则集的名字
  4548. */
  4549. function traverseValueNodes($nodes, valueReplaceRules, key) {
  4550. if (!$nodes) return;
  4551.  
  4552. $nodes.each(function () {
  4553. let $node = $(this);
  4554. if ($node.is('[value]')) {
  4555. Object.keys(valueReplaceRules).forEach(match => {
  4556. const replace = valueReplaceRules[match];
  4557. const regex = new RegExp(match, 'g');
  4558. let currentValue = $node.val();
  4559. let newValue = currentValue.replace(regex, replace);
  4560. $node.val(newValue);
  4561. if (OJBetter.dev.isRuleMarkingEnabled) {
  4562. if (newValue !== currentValue) $($node).after(`<span class="markingTextReplaceRule">${key}</span>`);
  4563. }
  4564. });
  4565. } else {
  4566. $node.children().each(function () {
  4567. traverseValueNodes($(this), valueReplaceRules, key);
  4568. });
  4569. }
  4570. });
  4571. }
  4572.  
  4573. /**
  4574. * 严格的文本节点遍历替换
  4575. * 要求被替换文本严格与规则文本一致
  4576. * @param {JQuery} $nodes jQuery对象
  4577. * @param {Object} textReplaceRules 文本替换规则对象
  4578. * @param {string} key 应用的规则集的名字
  4579. */
  4580. const strictTraverseTextNodes = ($nodes, textReplaceRules, key) => {
  4581. if (!$nodes) return;
  4582.  
  4583. $nodes.each((_, node) => {
  4584. if (node.nodeType === Node.TEXT_NODE) {
  4585. const trimmedNodeText = node.textContent.trim();
  4586. for (const [match, replacement] of Object.entries(textReplaceRules)) {
  4587. if (trimmedNodeText === match) {
  4588. const beforeText = node.textContent;
  4589. node.textContent = replacement;
  4590. if (node.textContent !== beforeText && OJBetter.dev.isRuleMarkingEnabled) {
  4591. $(node).after(`<span class="markingTextReplaceRule">${key}</span>`);
  4592. }
  4593. }
  4594. }
  4595. } else {
  4596. $(node).contents().each((_, childNode) => {
  4597. strictTraverseTextNodes($(childNode), textReplaceRules, key);
  4598. });
  4599. }
  4600. });
  4601. };
  4602.  
  4603. /**
  4604. * 应用文本替换
  4605. */
  4606. let commonReplacements = subs.commonReplacements;
  4607. Object.entries(commonReplacements).forEach(([key, value]) => {
  4608. const classSelectors = Array.isArray(value.class) ? value.class : [value.class]; // 兼容,class的值可以为数组或者字符串
  4609. classSelectors.forEach(classSelector => {
  4610. if (value.isStrict) {
  4611. strictTraverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4612. } else {
  4613. traverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4614. }
  4615. });
  4616. });
  4617.  
  4618. /**
  4619. * 应用value替换
  4620. */
  4621. let InputValueReplacements = subs.InputValueReplacements;
  4622. Object.entries(InputValueReplacements).forEach(([key, value]) => {
  4623. const classSelectors = Array.isArray(value.class) ? value.class : [value.class];
  4624. classSelectors.forEach(classSelector => {
  4625. traverseValueNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4626. });
  4627. });
  4628.  
  4629. /**
  4630. * 动态添加的文本的替换
  4631. */
  4632. let dynamicReplacements = subs.dynamicReplacements;
  4633. Object.entries(dynamicReplacements).forEach(([key, value]) => {
  4634. const classSelectors = Array.isArray(value.class) ? value.class : [value.class]; // 兼容,class的值可以为数组或者字符串
  4635. classSelectors.forEach(classSelector => {
  4636. OJB_observeElement({
  4637. selector: classSelector,
  4638. callback: (node) => {
  4639. // let popupContent = node.textContent.replace(/^×/, ''); // 去除开头多余的 '×' 字符
  4640. if (value.isStrict) {
  4641. strictTraverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4642. } else {
  4643. traverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules, key);
  4644. }
  4645. }
  4646. });
  4647. });
  4648. });
  4649.  
  4650. // // 杂项
  4651. // (function () {
  4652. // // 选项汉化input[type="radio"]
  4653. // var translations = {
  4654. // "as individual participant": "个人",
  4655. // "as a team member": "作为一个团队成员",
  4656. // };
  4657. // $('input[type="radio"]').each(function () {
  4658. // var tag = $(this).parent().contents().filter(function () {
  4659. // return this.nodeType === Node.TEXT_NODE;
  4660. // });
  4661. // for (var i = 0; i < tag.length; i++) {
  4662. // var text = tag[i].textContent.trim();
  4663. // if (translations.hasOwnProperty(text)) {
  4664. // $(this).addClass(text);
  4665. // tag[i].replaceWith(translations[text]);
  4666. // break;
  4667. // }
  4668. // }
  4669. // });
  4670. // })();
  4671. // (function () {
  4672. // var translations = {
  4673. // "(standard input\/output)": "标准输入/输出",
  4674. // };
  4675. // $("div.notice").each(function () {
  4676. // var tag = $(this).children().eq(0).text();
  4677. // for (var property in translations) {
  4678. // if (tag.match(property)) {
  4679. // $(this).children().eq(0).text(translations[property]);
  4680. // break;
  4681. // }
  4682. // }
  4683. // });
  4684. // })();
  4685.  
  4686. // // 轻量站特殊
  4687. // if (OJBetter.typeOfPage.is_mSite) {
  4688. // traverseTextNodes($('nav'), commonReplacements['.second-level-menu']['rules']);
  4689. // }
  4690. // if (OJBetter.typeOfPage.is_mSite) {
  4691. // (function () {
  4692. // var translations = {
  4693. // "Announcements": "公告",
  4694. // "Submissions": "提交记录",
  4695. // "Contests": "比赛",
  4696. // };
  4697. // $(".caption").each(function () {
  4698. // var optionValue = $(this).text();
  4699. // if (translations[optionValue]) {
  4700. // $(this).text(translations[optionValue]);
  4701. // }
  4702. // });
  4703. // })();
  4704. // }
  4705. };
  4706.  
  4707. /**
  4708. * i18next初始化
  4709. */
  4710. async function initI18next() {
  4711. return new Promise((resolve, reject) => {
  4712. i18next
  4713. .use(i18nextChainedBackend)
  4714. .init({
  4715. lng: OJBetter.localization.scriptLang,
  4716. ns: ['common', 'settings', 'config', 'dialog', 'alert', 'translator',
  4717. 'button', 'codeEditor', 'comments', 'announce', 'logMessage'], // 命名空间列表
  4718. defaultNS: 'settings',
  4719. fallbackLng: ['zh', OJBetter.translation.targetLang],
  4720. load: 'currentOnly',
  4721. debug: false,
  4722. backend: {
  4723. backends: [
  4724. i18nextLocalStorageBackend,
  4725. i18nextHttpBackend
  4726. ],
  4727. backendOptions: [{
  4728. prefix: 'i18next_res_',
  4729. expirationTime: 7 * 24 * 60 * 60 * 1000,
  4730. defaultVersion: `v${OJBetter.state.version}`,
  4731. store: typeof window !== 'undefined' ? window.localStorage : null
  4732. }, {
  4733. /* options for secondary backend */
  4734. loadPath: (lng, ns) => {
  4735. if (lng[0] === 'zh' || lng[0] === 'zh-Hans') {
  4736. return `https://aowuucdn.oss-accelerate.aliyuncs.com/resources/locales/${OJBetter.state.formatName}/${ns}.json`;
  4737. }
  4738. return `https://aowuucdn.oss-accelerate.aliyuncs.com/i18n/${lng}/resources/locales/${OJBetter.state.formatName}/${ns}.json`;
  4739. }
  4740. }]
  4741. }
  4742. }, (err, t) => {
  4743. if (err) {
  4744. reject(err);
  4745. } else {
  4746. jqueryI18next.init(i18next, $, {
  4747. useOptionsAttr: true
  4748. });
  4749. resolve(t);
  4750. }
  4751. });
  4752. });
  4753. };
  4754.  
  4755. /**
  4756. * 抽象命令类
  4757. */
  4758. class Command {
  4759. execute() { }
  4760. undo() { }
  4761. }
  4762.  
  4763. /**
  4764. * 命令调用者
  4765. */
  4766. class CommandInvoker {
  4767. constructor() {
  4768. this.history = [];
  4769. }
  4770.  
  4771. /**
  4772. * 执行命令
  4773. * @param {Command} command 命令对象
  4774. */
  4775. execute(command) {
  4776. this.history.push(command);
  4777. command.execute();
  4778. }
  4779.  
  4780. /**
  4781. * 撤销命令
  4782. */
  4783. undo() {
  4784. const command = this.history.pop();
  4785. if (command) {
  4786. command.undo();
  4787. }
  4788. }
  4789. }
  4790.  
  4791. /**
  4792. * 接收者
  4793. */
  4794. class DOMContainer {
  4795. /**
  4796. * @param {JQueryObject} element 容器对象
  4797. */
  4798. constructor(element) {
  4799. this.containerElement = element;
  4800. }
  4801.  
  4802. /**
  4803. * 添加元素
  4804. * @param {JQueryObject} element 元素对象
  4805. * @returns {JQueryObject} 添加的元素对象
  4806. */
  4807. add(element) {
  4808. this.containerElement.append(element);
  4809. return this.containerElement.children().last();
  4810. }
  4811.  
  4812. /**
  4813. * 删除元素
  4814. * @param {JQueryObject} element 元素对象
  4815. */
  4816. remove(element) {
  4817. $(element).remove();
  4818. }
  4819. }
  4820.  
  4821. /**
  4822. * 具体命令类:添加元素
  4823. */
  4824. class AddElementCommand extends Command {
  4825. /**
  4826. * @param {DOMContainer} receiver 接收者
  4827. * @param {JQueryObject} element 元素对象
  4828. */
  4829. constructor(receiver, element) {
  4830. super();
  4831. this.receiver = receiver;
  4832. this.element = element;
  4833. this.addedElement = null;
  4834. }
  4835.  
  4836. execute() {
  4837. this.addedElement = this.receiver.add(this.element);
  4838. }
  4839.  
  4840. undo() {
  4841. if (this.addedElement) {
  4842. this.receiver.remove(this.addedElement);
  4843. }
  4844. }
  4845. }
  4846.  
  4847. /**
  4848. * 具体命令类:删除元素
  4849. */
  4850. class RemoveElementCommand extends Command {
  4851. /**
  4852. * @param {DOMContainer} receiver 接收者
  4853. * @param {JQueryObject} element 元素对象
  4854. */
  4855. constructor(receiver, element) {
  4856. super();
  4857. this.receiver = receiver;
  4858. this.element = element;
  4859. this.parent = $(element).parent();
  4860. this.nextSibling = $(element).next();
  4861. }
  4862.  
  4863. execute() {
  4864. this.receiver.remove(this.element);
  4865. }
  4866.  
  4867. undo() {
  4868. if (this.nextSibling.length > 0) {
  4869. $(this.element).insertBefore(this.nextSibling);
  4870. } else {
  4871. this.parent.append(this.element);
  4872. }
  4873. }
  4874. }
  4875.  
  4876. /**
  4877. * 验证器
  4878. */
  4879. class Validator {
  4880. /**
  4881. * 表单必填项空值校验
  4882. */
  4883. static required(structure) {
  4884. let config = {};
  4885. let allFieldsValid = true;
  4886. for (const key in structure) {
  4887. let value = key.type == 'checkbox' ?
  4888. $(key).prop("checked") : $(key).val();
  4889.  
  4890. config[structure[key].value] = value;
  4891.  
  4892. if (value || structure[key].require === false) {
  4893. $(key).removeClass('is_null');
  4894. } else {
  4895. $(key).addClass('is_null');
  4896. allFieldsValid = false;
  4897. }
  4898. }
  4899. return {
  4900. valid: allFieldsValid,
  4901. config: config
  4902. };
  4903. }
  4904.  
  4905. /**
  4906. * 表单合法性校验
  4907. */
  4908. static checkKeyValuePairs(structure, config) {
  4909. let errorKeys = [];
  4910. let allFieldsValid = true;
  4911.  
  4912. for (const key in structure) {
  4913. const { check, value } = structure[key];
  4914. const fieldValue = config[value];
  4915.  
  4916. // 如果字段没有值或校验类型不匹配,则跳过当前迭代
  4917. if (!fieldValue) continue;
  4918.  
  4919. let isValid = true;
  4920. switch (check) {
  4921. case 'keyValuePairs':
  4922. isValid = Validator.keyValuePairs(fieldValue);
  4923. break;
  4924. case 'dotSeparatedPath':
  4925. isValid = Validator.validateDotSeparatedPath(fieldValue);
  4926. break;
  4927. default:
  4928. // 没有匹配的校验类型
  4929. continue;
  4930. }
  4931.  
  4932. Validator.toggleErrorDisplay(key, isValid);
  4933. if (!isValid) {
  4934. allFieldsValid = false;
  4935. errorKeys.push(key);
  4936. }
  4937. }
  4938.  
  4939. return {
  4940. valid: allFieldsValid,
  4941. errorKeys: errorKeys
  4942. };
  4943. }
  4944.  
  4945. /**
  4946. * 切换错误信息的显示和隐藏
  4947. * @param {string} key - 字段的键
  4948. * @param {boolean} isValid - 字段值是否有效
  4949. */
  4950. static toggleErrorDisplay(key, isValid) {
  4951. const errorMessage = i18next.t('common.unValid', { ns: 'settings' });
  4952. const $errorSpan = $(key).prev('span.text-error');
  4953. if (!isValid) {
  4954. if (!$errorSpan.length) {
  4955. $(key).before(`<span class="text-error" style="color: red;">${errorMessage}</span>`);
  4956. }
  4957. } else {
  4958. $errorSpan.remove();
  4959. }
  4960. }
  4961.  
  4962. /**
  4963. * 键值对合法性校验
  4964. * @param {string} value
  4965. * @returns {boolean}
  4966. */
  4967. static keyValuePairs(value) {
  4968. const keyValuePairs = value.split('\n');
  4969. // 允许值中包含空格和冒号
  4970. const regex = /^[a-zA-Z0-9_-]+\s*:\s*.+$/;
  4971. return keyValuePairs.every(pair => regex.test(pair));
  4972. }
  4973.  
  4974.  
  4975. /**
  4976. * 点分隔符路径格式校验,允许加减运算
  4977. * @param {string} path
  4978. * @returns {boolean}
  4979. */
  4980. static validateDotSeparatedPath(path) {
  4981. // 正则表达式允许标识符之间有点号,标识符可以包含加减运算
  4982. const regex = /^([a-zA-Z0-9_-]+(\s*[\+\-]\s*[a-zA-Z0-9_-]+)*\.)*([a-zA-Z0-9_-]+(\s*[\+\-]\s*[a-zA-Z0-9_-]+)*)$/;
  4983. return regex.test(path);
  4984. }
  4985. }
  4986.  
  4987. /**
  4988. * 配置管理
  4989. */
  4990. class ConfigManager {
  4991. /**
  4992. * @param {HTMLElement} element - 挂载容器
  4993. * @param {string} prefix - 前缀
  4994. * @param {object} tempConfig - 配置内容
  4995. * @param {object} structure - 配置结构
  4996. * @param {object} configHTML - 配置编辑页面HTML
  4997. * @param {boolean} allowChoice - 是否允许选择列表项
  4998. */
  4999. constructor(element, prefix, tempConfig, structure, configHTML, allowChoice = true) {
  5000. /** @param 设置面板DIV */
  5001. this.settingMenuDiv = $('#OJBetter_setting_menu');
  5002. this.element = $(element);
  5003. this.prefix = prefix;
  5004. this.tempConfig = tempConfig;
  5005. this.structure = structure;
  5006. this.configHTML = configHTML;
  5007. this.allowChoice = allowChoice;
  5008.  
  5009. this.controlTip = null;
  5010. this.config_bar_list = null;
  5011. this.config_bar_ul = null;
  5012. this.config_add_button = null;
  5013. this.menu = null;
  5014. this.editItem = null;
  5015. this.deleteItem = null;
  5016.  
  5017. // 绑定方法
  5018. this.onAdd = this.onAdd.bind(this);
  5019. this.onEdit = this.onEdit.bind(this);
  5020. this.onDelete = this.onDelete.bind(this);
  5021. this.createListItemElement = this.createListItemElement.bind(this);
  5022.  
  5023. this.lastItemId = 0; // 列表中当前最后一个元素的id号
  5024. this.init();
  5025. }
  5026.  
  5027. init() {
  5028. this.createControlBar();
  5029. this.createContextMenu();
  5030. this.renderList();
  5031. }
  5032.  
  5033. /**
  5034. * 创建控制栏
  5035. */
  5036. createControlBar() {
  5037. this.controlTip = OJB_safeCreateJQElement(`<div id='${this.prefix}configControlTip' style='color:red;'></div>`);
  5038. this.config_bar_list = OJB_safeCreateJQElement(`<div class='config_bar_list' id='${this.prefix}config_bar_list'></div>`);
  5039. this.config_bar_ul = OJB_safeCreateJQElement(`<ul class='config_bar_ul' id='${this.prefix}config_bar_ul'></ul>`);
  5040. this.element.append(this.controlTip);
  5041. this.element.append(this.config_bar_list);
  5042. this.config_bar_list.append(this.config_bar_ul);
  5043. }
  5044.  
  5045. /**
  5046. * 创建右键菜单
  5047. */
  5048. createContextMenu() {
  5049. const menu = OJB_safeCreateJQElement(`<div id='config_bar_menu' style='display: none;'></div>`);
  5050. const editItem = OJB_safeCreateJQElement(`
  5051. <div class='config_bar_menu_item' id='config_bar_menu_edit'>
  5052. ${i18next.t('contextMenu.edit', { ns: 'translator' })}
  5053. </div>`);
  5054. const deleteItem = OJB_safeCreateJQElement(`
  5055. <div class='config_bar_menu_item' id='config_bar_menu_delete'>
  5056. ${i18next.t('contextMenu.delete', { ns: 'translator' })}
  5057. </div>`);
  5058. menu.append(editItem);
  5059. menu.append(deleteItem);
  5060. this.editItem = editItem;
  5061. this.deleteItem = deleteItem;
  5062. this.menu = menu;
  5063. this.settingMenuDiv.append(menu);
  5064. }
  5065.  
  5066. /**
  5067. * 关闭右键菜单
  5068. */
  5069. closeContextMenu() {
  5070. this.menu.css({ display: "none" });
  5071. }
  5072.  
  5073. /**
  5074. * 创建列表项
  5075. * @param {string} text - 列表项文本
  5076. * @returns {HTMLElement} - 列表项
  5077. */
  5078. createListItemElement(text) {
  5079. const id = OJB_getRandomNumber(4);
  5080. const li = $("<li></li>");
  5081. const radio = OJB_safeCreateJQElement(`<input type='radio' name='${this.prefix}config_item'></input>`)
  5082. .attr("value", text)
  5083. .attr("id", id)
  5084. .attr("prev_id", this.lastItemId)
  5085. .appendTo(li);
  5086. if (!this.allowChoice) {
  5087. radio.prop("disabled", true);
  5088. }
  5089. const label = OJB_safeCreateJQElement(`<label for='${id}' class='config_bar_ul_li_text'>${text}</label>`).appendTo(li);
  5090.  
  5091.  
  5092. this.lastItemId = id;
  5093.  
  5094. // 添加右键菜单
  5095. li.on("contextmenu", (event) => {
  5096. event.preventDefault();
  5097. this.menu.css({
  5098. display: "block",
  5099. left: event.pageX, top: event.pageY
  5100. });
  5101.  
  5102. const deleteItem = this.deleteItem;
  5103. const editItem = this.editItem;
  5104.  
  5105. // 移除旧事件
  5106. deleteItem.off("click");
  5107. editItem.off("click");
  5108.  
  5109. // 获取 li 在 ul 中的索引
  5110. const index = li.index();
  5111.  
  5112. deleteItem.on("click", () => this.onDelete(index, li));
  5113. editItem.on("click", () => this.onEdit(index, li));
  5114.  
  5115. $(document).one("click", (event) => {
  5116. if (!this.menu.get(0).contains(event.target)) {
  5117. this.closeContextMenu();
  5118. deleteItem.off("click", () => this.onDelete);
  5119. editItem.off("click", () => this.onEdit);
  5120. }
  5121. });
  5122. });
  5123.  
  5124. return li;
  5125. }
  5126.  
  5127. /**
  5128. * 渲染配置列表
  5129. */
  5130. renderList() {
  5131. const list = this.config_bar_ul;
  5132. list.empty(); // 清空
  5133. this.tempConfig.configurations.forEach((item) => {
  5134. list.append(this.createListItemElement(item['name']));
  5135. });
  5136.  
  5137. // 添加按钮
  5138. let addButton = OJB_safeCreateJQElement(`<li id='${this.prefix}add_button' class="tempConfig_add_button">
  5139. <span>+ ${i18next.t('add', { ns: 'common' })}</span>
  5140. </li>`);
  5141. this.config_add_button = addButton;
  5142. list.append(addButton);
  5143. addButton.on("click", this.onAdd);
  5144. }
  5145.  
  5146. /**
  5147. * 添加配置项
  5148. */
  5149. onAdd() {
  5150. const configMenu = this.createConfigHTML();
  5151. const structure = this.structure;
  5152.  
  5153. configMenu.on("click", "#tempConfig_save", () => {
  5154.  
  5155. // 检查必填字段
  5156. const { valid, config } = Validator.required(structure);
  5157. if (!valid) return;
  5158.  
  5159. // 检查键值对
  5160. const { valid: checkOk, errorKey } = Validator.checkKeyValuePairs(structure, config);
  5161. if (!checkOk) return;
  5162.  
  5163. this.tempConfig.configurations.push(config);
  5164.  
  5165. this.createListItemElement(config.name).insertBefore(this.config_add_button);
  5166.  
  5167. configMenu.remove();
  5168. });
  5169.  
  5170. configMenu.on("click", ".btn-close", () => {
  5171. configMenu.remove();
  5172. });
  5173. }
  5174.  
  5175. /**
  5176. * 修改配置项
  5177. * @param {number} index - 配置项索引
  5178. * @param {HTMLElement} li - 配置项
  5179. * @returns {void}
  5180. */
  5181. onEdit(index, li) {
  5182. const configMenu = this.createConfigHTML();
  5183. const structure = this.structure;
  5184.  
  5185. this.closeContextMenu();
  5186.  
  5187. // 填充表单
  5188. for (const [key, { value, type }] of Object.entries(this.structure)) {
  5189. const configValue = this.tempConfig.configurations[index][value];
  5190. const $element = $(key);
  5191. if (type === 'checkbox') {
  5192. $element.prop("checked", configValue);
  5193. } else {
  5194. $element.val(configValue);
  5195. }
  5196. }
  5197.  
  5198. configMenu.on("click", "#tempConfig_save", () => {
  5199. // 检查必填字段
  5200. const { valid, config } = Validator.required(structure);
  5201. if (!valid) return;
  5202.  
  5203. // 检查键值对
  5204. const { valid: checkOk, errorKey } = Validator.checkKeyValuePairs(structure, config);
  5205. if (!checkOk) return;
  5206.  
  5207. // 更新配置
  5208. this.tempConfig.configurations[index] = config;
  5209. li.find('label').text(config.name);
  5210.  
  5211. OJB_closeAndRemoveModal(configMenu);
  5212. });
  5213.  
  5214. configMenu.on("click", ".btn-close", () => {
  5215. OJB_closeAndRemoveModal(configMenu);
  5216. });
  5217. }
  5218.  
  5219. /**
  5220. * 删除配置项
  5221. * @param {number} index - 配置项索引
  5222. * @param {HTMLElement} li - 配置项
  5223. * @returns {void}
  5224. */
  5225. onDelete(index, li) {
  5226. this.closeContextMenu();
  5227. this.tempConfig.configurations.splice(index, 1);
  5228. li.remove();
  5229. }
  5230.  
  5231. /**
  5232. * 创建配置编辑页面
  5233. * @returns {JQuery<HTMLElement>} 返回配置编辑页面
  5234. */
  5235. createConfigHTML() {
  5236. const configMenu = OJB_safeCreateJQElement(this.configHTML);
  5237. this.settingMenuDiv.after(configMenu);
  5238. OJB_showModal(configMenu);
  5239. OJB_addDraggable(configMenu);
  5240. elementLocalize(configMenu);
  5241. return configMenu;
  5242. }
  5243.  
  5244. /**
  5245. * 获取配置内容
  5246. * @returns {object} - 配置内容
  5247. */
  5248. getTempConfig() {
  5249. return this.tempConfig;
  5250. }
  5251.  
  5252. /**
  5253. * 注册列表项选中改变监听
  5254. */
  5255. registerChoiceChange() {
  5256. this.config_bar_ul.on("change", "input[type='radio']", (event) => {
  5257. const value = event.target.value;
  5258. this.tempConfig.choice = value;
  5259. });
  5260. }
  5261. }
  5262.  
  5263. const OJBetter_setting_sidebar_HTML = `
  5264. <div class="OJBetter_setting_sidebar">
  5265. <ul>
  5266. <li><a href="#basic-settings" id="sidebar-basic-settings" class="active" data-i18n="settings:sidebar.basic"></a></li>
  5267. <li><a href="#l10n_settings" id="sidebar-l10n_settings" data-i18n="settings:sidebar.localization"></a></li>
  5268. <li><a href="#translation-settings" id="sidebar-translation-settings" data-i18n="settings:sidebar.translation"></a></li>
  5269. <li><a href="#clist_rating-settings" id="sidebar-clist_rating-settings" data-i18n="settings:sidebar.clist"></a></li>
  5270. <li><a href="#code_editor-settings" id="sidebar-code_editor-settings" data-i18n="settings:sidebar.monaco"></a></li>
  5271. <li><a href="#preference-settings" id="sidebar-preference-settings" data-i18n="settings:sidebar.preference"></a></li>
  5272. <li><a href="#dev-settings" id="sidebar-dev-settings" data-i18n="settings:sidebar.dev"></a></li>
  5273. <li><a href="#about-settings" id="sidebar-about-settings" data-i18n="settings:sidebar.about"></a></li>
  5274. </ul>
  5275. </div>
  5276. `;
  5277.  
  5278. const basic_settings_HTML = `
  5279. <div id="basic-settings" class="settings-page active">
  5280. <h3 data-i18n="settings:basic.title"></h3>
  5281. <hr>
  5282. <div class='OJBetter_setting_list' style="padding: 0px 10px;">
  5283. <span id="darkMode_span" data-i18n="settings:basic.darkMode.name"></span>
  5284. <div class="dark-mode-selection">
  5285. <label>
  5286. <input class="radio-input" type="radio" name="darkMode" value="dark" />
  5287. <span class="OJBetter_setting_menu_label_text"
  5288. data-i18n="settings:basic.darkMode.options.dark"></span>
  5289. <span class="radio-icon"> </span>
  5290. </label>
  5291. <label>
  5292. <input checked="" class="radio-input" type="radio" name="darkMode" value="light" />
  5293. <span class="OJBetter_setting_menu_label_text"
  5294. data-i18n="settings:basic.darkMode.options.light"></span>
  5295. <span class="radio-icon"> </span>
  5296. </label>
  5297. <label>
  5298. <input class="radio-input" type="radio" name="darkMode" value="follow" />
  5299. <span class="OJBetter_setting_menu_label_text"
  5300. data-i18n="settings:basic.darkMode.options.system"></span>
  5301. <span class="radio-icon"> </span>
  5302. </label>
  5303. </div>
  5304. </div>
  5305. <div class='OJBetter_setting_list' style="display:none;">
  5306. <label for="expandFoldingblocks" data-i18n="settings:basic.expandBlocks"></label>
  5307. <input type="checkbox" id="expandFoldingblocks" name="expandFoldingblocks">
  5308. </div>
  5309. <div class='OJBetter_setting_list' style="display:none;">
  5310. <label for="renderPerfOpt" data-i18n="settings:basic.renderOptimization.label"></label>
  5311. <div class="help_tip">
  5312. ${helpCircleHTML}
  5313. <div class="tip_text" data-i18n="[html]settings:basic.renderOptimization.helpText"></div>
  5314. </div>
  5315. <input type="checkbox" id="renderPerfOpt" name="renderPerfOpt">
  5316. </div>
  5317. <div class='OJBetter_setting_list' style="display:none;">
  5318. <label for="selectElementPerfOpt" data-i18n="settings:basic.selectElementOptimization.label"></label>
  5319. <div class="help_tip">
  5320. ${helpCircleHTML}
  5321. <div class="tip_text" data-i18n="[html]settings:basic.selectElementOptimization.helpText"></div>
  5322. </div>
  5323. <input type="checkbox" id="selectElementPerfOpt" name="selectElementPerfOpt">
  5324. </div>
  5325. <div class='OJBetter_setting_list' style="display:none;">
  5326. <label for="commentPaging" data-i18n="settings:basic.paging.label"></label>
  5327. <div class="help_tip">
  5328. ${helpCircleHTML}
  5329. <div class="tip_text" data-i18n="[html]settings:basic.paging.helpText"></div>
  5330. </div>
  5331. <input type="checkbox" id="commentPaging" name="commentPaging">
  5332. </div>
  5333. <div class='OJBetter_setting_list'>
  5334. <label for="showJumpToLuogu" data-i18n="settings:basic.luoguJump.label"></label>
  5335. <div class="help_tip">
  5336. ${helpCircleHTML}
  5337. <div class="tip_text" data-i18n="[html]settings:basic.luoguJump.helpText"></div>
  5338. </div>
  5339. <input type="checkbox" id="showJumpToLuogu" name="showJumpToLuogu">
  5340. </div>
  5341. <div class='OJBetter_setting_list'>
  5342. <label for="showCF2vjudge" data-i18n="settings:basic.vjudgeJump.label"></label>
  5343. <div class="help_tip">
  5344. ${helpCircleHTML}
  5345. <div class="tip_text" data-i18n="[html]settings:basic.vjudgeJump.helpText"></div>
  5346. </div>
  5347. <input type="checkbox" id="showCF2vjudge" name="showCF2vjudge">
  5348. </div>
  5349. <div class='OJBetter_setting_list' style="display:none;">
  5350. <label for="standingsRecolor" data-i18n="settings:basic.recolor.label"></label>
  5351. <div class="help_tip">
  5352. ${helpCircleHTML}
  5353. <div class="tip_text" data-i18n="[html]settings:basic.recolor.helpText"></div>
  5354. </div>
  5355. <input type="checkbox" id="standingsRecolor" name="standingsRecolor">
  5356. </div>
  5357. </div>
  5358. `;
  5359.  
  5360. const l10n_settings_HTML = `
  5361. <div id="l10n_settings" class="settings-page">
  5362. <h3 data-i18n="settings:localization.title"></h3>
  5363. <hr>
  5364. <div class='OJBetter_setting_list'>
  5365. <label for="scriptL10nLanguage" style="display: flex;" data-i18n="settings:localization.scriptLanguageLabel"></label>
  5366. <select id="scriptL10nLanguage" name="scriptL10nLanguage">
  5367. <option value="zh">简体中文</option>
  5368. <option value="zh-Hant">繁體中文</option>
  5369. <option value="en">English</option>
  5370. <option value="de">Deutsch</option>
  5371. <option value="fr">Français</option>
  5372. <option value="ko">한국어</option>
  5373. <option value="pt">Português</option>
  5374. <option value="ja">日本語</option>
  5375. <option value="es">Español</option>
  5376. <option value="it">Italiano</option>
  5377. <option value="hi">हिन्दी</option>
  5378. </select>
  5379. </div>
  5380. <div class='OJBetter_setting_list'>
  5381. <label for="localizationLanguage" style="display: flex;" data-i18n="settings:localization.websiteLanguageLabel"></label>
  5382. <select id="localizationLanguage" name="localizationLanguage">
  5383. <option value="initial">——</option>
  5384. <option value="zh">简体中文</option>
  5385. <option value="zh-Hant">繁體中文</option>
  5386. <option value="de">Deutsch</option>
  5387. <option value="fr">Français</option>
  5388. <option value="ko">한국어</option>
  5389. <option value="pt">Português</option>
  5390. <option value="ja">日本語</option>
  5391. <option value="es">Español</option>
  5392. <option value="it">Italiano</option>
  5393. <option value="hi">हिन्दी</option>
  5394. </select>
  5395. </div>
  5396. <div class='OJBetter_setting_list alert_tip'>
  5397. <div data-i18n="[html]settings:localization.notice.1"></div>
  5398. </div>
  5399. <div class='OJBetter_setting_list alert_tip'>
  5400. <div data-i18n="[html]settings:localization.notice.2"></div>
  5401. </div>
  5402. </div>
  5403. `;
  5404.  
  5405. const translation_settings_HTML = `
  5406. <div id="translation-settings" class="settings-page">
  5407. <h3 data-i18n="settings:translation.title"></h3>
  5408. <hr>
  5409. <h4 data-i18n="settings:translation.options.title"></h4>
  5410. <div class='OJBetter_setting_list'>
  5411. <label for="transTargetLang" style="display: flex;" data-i18n="settings:translation.preference.target.title"></label>
  5412. <div class="help_tip">
  5413. ${helpCircleHTML}
  5414. <div class="tip_text" data-i18n="[html]settings:translation.preference.target.helpText"></div>
  5415. </div>
  5416. <select id="transTargetLang" name="transTargetLang">
  5417. <option value="zh">简体中文</option>
  5418. <option value="zh-Hant">繁體中文</option>
  5419. <option value="de">Deutsch</option>
  5420. <option value="fr">Français</option>
  5421. <option value="ko">한국어</option>
  5422. <option value="pt">Português</option>
  5423. <option value="ja">日本語</option>
  5424. <option value="es">Español</option>
  5425. <option value="it">Italiano</option>
  5426. <option value="hi">हिन्दी</option>
  5427. </select>
  5428. </div>
  5429. <div id="translationServices">
  5430. <label>
  5431. <input type='radio' name='translation' value='deepl'>
  5432. <span class='OJBetter_setting_menu_label_text'
  5433. data-i18n="settings:translation.options.services.deepl"></span>
  5434. </label>
  5435. <label>
  5436. <input type='radio' name='translation' value='iflyrec'>
  5437. <span class='OJBetter_setting_menu_label_text'
  5438. data-i18n="settings:translation.options.services.iflyrec"></span>
  5439. </label>
  5440. <label>
  5441. <input type='radio' name='translation' value='youdao'>
  5442. <span class='OJBetter_setting_menu_label_text'
  5443. data-i18n="settings:translation.options.services.youdao"></span>
  5444. </label>
  5445. <label>
  5446. <input type='radio' name='translation' value='google'>
  5447. <span class='OJBetter_setting_menu_label_text'
  5448. data-i18n="settings:translation.options.services.google"></span>
  5449. </label>
  5450. <label>
  5451. <input type='radio' name='translation' value='caiyun'>
  5452. <span class='OJBetter_setting_menu_label_text'
  5453. data-i18n="settings:translation.options.services.caiyun"></span>
  5454. </label>
  5455. <label>
  5456. <input type='radio' name='translation' value='openai'>
  5457. <span class='OJBetter_setting_menu_label_text'
  5458. data-i18n="settings:translation.options.services.openai.name">
  5459. <div class="help_tip">
  5460. ${helpCircleHTML}
  5461. <div class="tip_text"
  5462. data-i18n="[html]settings:translation.options.services.openai.helpText"></div>
  5463. </div>
  5464. </span>
  5465. </label>
  5466. </div>
  5467. <hr>
  5468. <h4>DeepL</h4>
  5469. <div class='OJBetter_setting_list'>
  5470. <label for="deepl_type" style="display: flex;" data-i18n="settings:translation.deepl.mode.title"></label>
  5471. <div class="help_tip">
  5472. ${helpCircleHTML}
  5473. <div class="tip_text" data-i18n="[html]settings:translation.deepl.mode.helpText"></div>
  5474. </div>
  5475. <select id="deepl_type" name="deepl_type">
  5476. <option value="free" data-i18n="settings:translation.deepl.mode.select.free"></option>
  5477. <option value="api" data-i18n="settings:translation.deepl.mode.select.api"></option>
  5478. </select>
  5479. </div>
  5480. <div id="deepl_config" class="config"></div>
  5481. <div class='OJBetter_setting_list'>
  5482. <label for="enableEmphasisProtection" data-i18n="settings:translation.deepl.enableEmphasisProtection.title"></label>
  5483. <div class="help_tip" style="margin-right: initial;">
  5484. ${helpCircleHTML}
  5485. <div class="tip_text" data-i18n="[html]settings:translation.deepl.enableEmphasisProtection.helpText"></div>
  5486. </div>
  5487. <div class="badge">Official API Only</div>
  5488. <input type="checkbox" id="enableEmphasisProtection" name="enableEmphasisProtection">
  5489. </div>
  5490. <div class='OJBetter_setting_list'>
  5491. <label for="enableLinkProtection" data-i18n="settings:translation.deepl.enableLinkProtection.title"></label>
  5492. <div class="help_tip" style="margin-right: initial;">
  5493. ${helpCircleHTML}
  5494. <div class="tip_text" data-i18n="[html]settings:translation.deepl.enableLinkProtection.helpText"></div>
  5495. </div>
  5496. <div class="badge">Official API Only</div>
  5497. <input type="checkbox" id="enableLinkProtection" name="enableLinkProtection">
  5498. </div>
  5499. <hr>
  5500. <h4>ChatGPT</h4>
  5501. <div id="chatgpt_config" class="config"></div>
  5502. <div class='OJBetter_setting_list'>
  5503. <label for="openai_isStream" data-i18n="settings:translation.chatgpt.isStream.name"></label>
  5504. <div class="help_tip">
  5505. ${helpCircleHTML}
  5506. <div class="tip_text" data-i18n="[html]settings:translation.chatgpt.isStream.helpText"></div>
  5507. </div>
  5508. <input type="checkbox" id="openai_isStream" name="openai_isStream">
  5509. </div>
  5510. <hr>
  5511. <h4 data-i18n="settings:translation.preference.title"></h4>
  5512. <div class='OJBetter_setting_list'>
  5513. <label for="comment_translation_choice" style="display: flex;"
  5514. data-i18n="settings:translation.preference.comment_translation_choice.title">
  5515. </label>
  5516. <select id="comment_translation_choice" name="comment_translation_choice">
  5517. <option value="0" data-i18n="settings:translation.preference.comment_translation_choice.services.follow"></option>
  5518. <option value="deepl" data-i18n="settings:translation.preference.comment_translation_choice.services.deepl"></option>
  5519. <option value="iflyrec" data-i18n="settings:translation.preference.comment_translation_choice.services.iflyrec"></option>
  5520. <option value="youdao" data-i18n="settings:translation.preference.comment_translation_choice.services.youdao"></option>
  5521. <option value="google" data-i18n="settings:translation.preference.comment_translation_choice.services.google"></option>
  5522. <option value="caiyun" data-i18n="settings:translation.preference.comment_translation_choice.services.caiyun"></option>
  5523. <option value="openai" data-i18n="settings:translation.preference.comment_translation_choice.services.openai"></option>
  5524. </select>
  5525. </div>
  5526. <hr>
  5527.  
  5528. <div style="display:none;">
  5529. <h4 data-i18n="settings:translation.autoTranslation.title"></h4>
  5530. <div class='OJBetter_setting_list'>
  5531. <label for="autoTranslation" data-i18n="settings:translation.autoTranslation.enable"></label>
  5532. <div class="help_tip">
  5533. ${helpCircleHTML}
  5534. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.helpText"></div>
  5535. </div>
  5536. <input type="checkbox" id="autoTranslation" name="autoTranslation">
  5537. </div>
  5538. <div class='OJBetter_setting_list'>
  5539. <label for='shortTextLength'>
  5540. <div style="display: flex;align-items: center;"
  5541. data-i18n="settings:translation.autoTranslation.shortTextLength.name"></div>
  5542. </label>
  5543. <div class="help_tip">
  5544. ${helpCircleHTML}
  5545. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.shortTextLength.helpText">
  5546. </div>
  5547. </div>
  5548. <input type='number' id='shortTextLength' class='no_default' require=true data-i18n="[placeholder]settings:translation.autoTranslation.shortTextLength.placeholder">
  5549. <span data-i18n="settings:translation.autoTranslation.shortTextLength.end"></span>
  5550. </div>
  5551. <div class='OJBetter_setting_list'>
  5552. <label for="allowMixTrans" data-i18n="settings:translation.autoTranslation.allowMixTrans.name"></label>
  5553. <div class="help_tip">
  5554. ${helpCircleHTML}
  5555. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.allowMixTrans.helpText">
  5556. </div>
  5557. </div>
  5558. <input type="checkbox" id="allowMixTrans" name="allowMixTrans">
  5559. <div class='OJBetter_checkboxs'>
  5560. <input type="checkbox" id="deepl" name="mixedTranslation" value="deepl">
  5561. <label for="deepl" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.deepl"></label>
  5562. <input type="checkbox" id="iflyrec" name="mixedTranslation" value="iflyrec">
  5563. <label for="iflyrec" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.iflyrec"></label>
  5564. <input type="checkbox" id="youdao" name="mixedTranslation" value="youdao">
  5565. <label for="youdao" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.youdao"></label>
  5566. <input type="checkbox" id="google" name="mixedTranslation" value="google">
  5567. <label for="google" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.google">Google</label>
  5568. <input type="checkbox" id="caiyun" name="mixedTranslation" value="caiyun">
  5569. <label for="caiyun" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.caiyun"></label>
  5570. </div>
  5571. </div>
  5572. <hr>
  5573. </div>
  5574.  
  5575. <h4 data-i18n="settings:translation.advanced.name"></h4>
  5576. <div class='OJBetter_setting_list'>
  5577. <label for="comment_translation_mode" style="display: flex;" data-i18n="settings:translation.advanced.mode.name"></label>
  5578. <div class="help_tip">
  5579. ${helpCircleHTML}
  5580. <div class="tip_text" data-i18n="[html]settings:translation.advanced.mode.helpText"></div>
  5581. </div>
  5582. <select id="comment_translation_mode" name="comment_translation_mode">
  5583. <option value="0" data-i18n="settings:translation.advanced.mode.options.0"></option>
  5584. <option value="1" data-i18n="settings:translation.advanced.mode.options.1"></option>
  5585. <option value="2" data-i18n="settings:translation.advanced.mode.options.2"></option>
  5586. </select>
  5587. </div>
  5588. <div class='OJBetter_setting_list'>
  5589. <label for="memoryTranslateHistory" data-i18n="settings:translation.advanced.memory.name"></label>
  5590. <div class="help_tip">
  5591. ${helpCircleHTML}
  5592. <div class="tip_text" data-i18n="[html]settings:translation.advanced.memory.helpText"></div>
  5593. </div>
  5594. <input type="checkbox" id="memoryTranslateHistory" name="memoryTranslateHistory">
  5595. </div>
  5596. <div class='OJBetter_setting_list'>
  5597. <label for="translation_retransAction" style="display: flex;" data-i18n="settings:translation.advanced.retrans.name"></label>
  5598. <div class="help_tip">
  5599. ${helpCircleHTML}
  5600. <div class="tip_text" data-i18n="[html]settings:translation.advanced.retrans.helpText"></div>
  5601. </div>
  5602. <select id="translation_retransAction" name="translation_retransAction">
  5603. <option value=0 data-i18n="settings:translation.advanced.retrans.options.0"></option>
  5604. <option value=1 data-i18n="settings:translation.advanced.retrans.options.1"></option>
  5605. </select>
  5606. </div>
  5607. <div class='OJBetter_setting_list'>
  5608. <label for='transWaitTime'>
  5609. <div style="display: flex;align-items: center;" data-i18n="settings:translation.advanced.transWaitTime.name"></div>
  5610. </label>
  5611. <div class="help_tip">
  5612. ${helpCircleHTML}
  5613. <div class="tip_text" data-i18n="[html]settings:translation.advanced.transWaitTime.helpText"></div>
  5614. </div>
  5615. <input type='number' id='transWaitTime' class='no_default' require=true data-i18n="[placeholder]settings:translation.advanced.transWaitTime.placeholder">
  5616. <span data-i18n="settings:translation.advanced.transWaitTime.end"></span>
  5617. </div>
  5618. <div class='OJBetter_setting_list'>
  5619. <label for="translation_replaceSymbol" style="display: flex;" data-i18n="settings:translation.advanced.replaceSymbol.name"></label>
  5620. <div class="help_tip">
  5621. ${helpCircleHTML}
  5622. <div class="tip_text" data-i18n="[html]settings:translation.advanced.replaceSymbol.helpText"></div>
  5623. </div>
  5624. <select id="translation_replaceSymbol" name="translation_replaceSymbol">
  5625. <option value=2 data-i18n="settings:translation.advanced.replaceSymbol.options.2"></option>
  5626. <option value=1 data-i18n="settings:translation.advanced.replaceSymbol.options.1"></option>
  5627. <option value=3 data-i18n="settings:translation.advanced.replaceSymbol.options.3"></option>
  5628. </select>
  5629. </div>
  5630. <div class='OJBetter_setting_list'>
  5631. <label for="filterTextWithoutEmphasis" data-i18n="settings:translation.advanced.filterTextWithoutEmphasis.name"></label>
  5632. <div class="help_tip">
  5633. ${helpCircleHTML}
  5634. <div class="tip_text" data-i18n="[html]settings:translation.advanced.filterTextWithoutEmphasis.helpText"></div>
  5635. </div>
  5636. <input type="checkbox" id="filterTextWithoutEmphasis" name="filterTextWithoutEmphasis">
  5637. </div>
  5638. </div>
  5639. `;
  5640.  
  5641. const clist_rating_settings_HTML = `
  5642. <div id="clist_rating-settings" class="settings-page">
  5643. <h3 data-i18n="settings:clist.title"></h3>
  5644. <hr>
  5645. <h4 data-i18n="settings:clist.basics.name"></h4>
  5646. <div class='OJBetter_setting_list alert_tip'>
  5647. <div>
  5648. <p data-i18n="[html]settings:clist.basics.notice"></p>
  5649. </div>
  5650. </div>
  5651. <div class='OJBetter_setting_list'>
  5652. <label for='clist_Authorization'>
  5653. <div style="display: flex;align-items: center;">
  5654. <span class="input_label" data-i18n="settings:clist.basics.key.title"></span>
  5655. </div>
  5656. </label>
  5657. <div class="help_tip">
  5658. ${helpCircleHTML}
  5659. <div class="tip_text" data-i18n="[html]settings:clist.basics.key.helpText"></div>
  5660. </div>
  5661. <input type='text' id='clist_Authorization' class='no_default' required="true"
  5662. data-i18n="[placeholder]settings:clist.basics.key.keyPlaceholder">
  5663. </div>
  5664. <hr>
  5665. <h4 data-i18n="settings:clist.displayRating.title"></h4>
  5666. <div class='OJBetter_setting_list'>
  5667. <label for="showClistRating_contest"><span data-i18n="settings:clist.displayRating.contest.name"></span></label>
  5668. <input type="checkbox" id="showClistRating_contest" name="showClistRating_contest">
  5669. </div>
  5670. <div class='OJBetter_setting_list'>
  5671. <label for="showClistRating_problem"><span data-i18n="settings:clist.displayRating.problem.name"></span></label>
  5672. <input type="checkbox" id="showClistRating_problem" name="showClistRating_problem">
  5673. </div>
  5674. <div class='OJBetter_setting_list' style='display:none;'>
  5675. <label for="showClistRating_problemset"><span data-i18n="settings:clist.displayRating.problemset.name"></span></label>
  5676. <input type="checkbox" id="showClistRating_problemset" name="showClistRating_problemset">
  5677. </div>
  5678. <hr>
  5679. <div class='OJBetter_setting_list'>
  5680. <label for="RatingHidden"><span data-i18n="settings:clist.spoilerProtection.title"></span></label>
  5681. <div class="help_tip">
  5682. ${helpCircleHTML}
  5683. <div class="tip_text" data-i18n="[html]settings:clist.spoilerProtection.helpText"></div>
  5684. </div>
  5685. <input type="checkbox" id="RatingHidden" name="RatingHidden">
  5686. </div>
  5687. </div>
  5688. `;
  5689.  
  5690. const code_editor_settings_HTML = `
  5691. <div id="code_editor-settings" class="settings-page">
  5692. <h3 data-i18n="settings:codeEditor.title"></h3>
  5693. <hr>
  5694. <h4 data-i18n="settings:codeEditor.basics"></h4>
  5695. <div class='OJBetter_setting_list'>
  5696. <label for="problemPageCodeEditor"><span
  5697. data-i18n="settings:codeEditor.problemPageCodeEditor.label"></span></label>
  5698. <div class="help_tip">
  5699. ${helpCircleHTML}
  5700. <div class="tip_text" data-i18n="settings:codeEditor.problemPageCodeEditor.helpText"></div>
  5701. </div>
  5702. <input type="checkbox" id="problemPageCodeEditor" name="problemPageCodeEditor">
  5703. </div>
  5704. <div class='OJBetter_setting_list'>
  5705. <label for="beautifyPreBlocks"><span
  5706. data-i18n="settings:codeEditor.beautifyPreBlocks.label"></span></label>
  5707. <div class="help_tip">
  5708. ${helpCircleHTML}
  5709. <div class="tip_text" data-i18n="settings:codeEditor.beautifyPreBlocks.helpText"></div>
  5710. </div>
  5711. <input type="checkbox" id="beautifyPreBlocks" name="beautifyPreBlocks">
  5712. </div>
  5713. <hr>
  5714. <h4 data-i18n="settings:codeEditor.preferences.title"></h4>
  5715. <div class='OJBetter_setting_list'>
  5716. <label for="isCodeSubmitConfirm"><span
  5717. data-i18n="settings:codeEditor.preferences.isCodeSubmitConfirm.label"></span></label>
  5718. <div class="help_tip">
  5719. ${helpCircleHTML}
  5720. <div class="tip_text" data-i18n="settings:codeEditor.preferences.isCodeSubmitConfirm.helpText"></div>
  5721. </div>
  5722. <input type="checkbox" id="isCodeSubmitConfirm" name="isCodeSubmitConfirm">
  5723. </div>
  5724. <div class='OJBetter_setting_list'>
  5725. <label for="alwaysConsumeMouseWheel"><span
  5726. data-i18n="settings:codeEditor.preferences.alwaysConsumeMouseWheel.label"></span></label>
  5727. <div class="help_tip">
  5728. ${helpCircleHTML}
  5729. <div class="tip_text" data-i18n="settings:codeEditor.preferences.alwaysConsumeMouseWheel.helpText"></div>
  5730. </div>
  5731. <input type="checkbox" id="alwaysConsumeMouseWheel" name="alwaysConsumeMouseWheel">
  5732. </div>
  5733. <div class='OJBetter_setting_list'>
  5734. <label for="submitButtonPosition"><span
  5735. data-i18n="settings:codeEditor.preferences.submitButtonPosition.label"></span></label>
  5736. <div class="help_tip">
  5737. ${helpCircleHTML}
  5738. <div class="tip_text" data-i18n="settings:codeEditor.preferences.submitButtonPosition.helpText"></div>
  5739. </div>
  5740. <select id="submitButtonPosition" name="submitButtonPosition">
  5741. <option value="bottom" data-i18n="settings:codeEditor.preferences.submitButtonPosition.options.bottom"></option>
  5742. <option value="top" data-i18n="settings:codeEditor.preferences.submitButtonPosition.options.top"></option>
  5743. </select>
  5744. </div>
  5745. <hr>
  5746. <h4 data-i18n="settings:codeEditor.onlineCodeExecution.title"></h4>
  5747. <label>
  5748. <input type='radio' name='compiler' value='official'>
  5749. <span class='OJBetter_setting_menu_label_text'
  5750. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.codeforces"></span>
  5751. </label>
  5752. <label>
  5753. <input type='radio' name='compiler' value='wandbox'>
  5754. <span class='OJBetter_setting_menu_label_text'
  5755. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.wandbox"></span>
  5756. </label>
  5757. <label>
  5758. <input type='radio' name='compiler' value='rextester'>
  5759. <span class='OJBetter_setting_menu_label_text'
  5760. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.rextester"></span>
  5761. </label>
  5762. <hr>
  5763. <h4 data-i18n="settings:codeEditor.lsp.title"></h4>
  5764. <div class='OJBetter_setting_list'>
  5765. <label for="useLSP"><span data-i18n="settings:codeEditor.lsp.useLSP.label"></span></label>
  5766. <div class="help_tip">
  5767. ${helpCircleHTML}
  5768. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.useLSP.helpText"></div>
  5769. </div>
  5770. <input type="checkbox" id="useLSP" name="useLSP">
  5771. </div>
  5772. <div class='OJBetter_setting_list'>
  5773. <label for='OJBetter_Bridge_WorkUri'>
  5774. <div style="display: flex;align-items: center;">
  5775. <span class="input_label" data-i18n="settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.label"></span>
  5776. </div>
  5777. </label>
  5778. <div class="help_tip">
  5779. ${helpCircleHTML}
  5780. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.helpText"></div>
  5781. </div>
  5782. <input type='text' id='OJBetter_Bridge_WorkUri' class='no_default'
  5783. require=true data-i18n="[placeholder]settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.placeholder">
  5784. </div>
  5785. <div class='OJBetter_setting_list'>
  5786. <label for='OJBetter_Bridge_SocketUrl'>
  5787. <div style="display: flex;align-items: center;">
  5788. <span class="input_label"
  5789. data-i18n="settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.label"></span>
  5790. </div>
  5791. </label>
  5792. <div class="help_tip">
  5793. ${helpCircleHTML}
  5794. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.helpText"></div>
  5795. </div>
  5796. <input type='text' id='OJBetter_Bridge_SocketUrl' class='no_default'
  5797. require=true data-i18n="[placeholder]settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.placeholder">
  5798. </div>
  5799. <hr>
  5800. <h4 data-i18n="settings:codeEditor.staticCompletionEnhancement.title"></h4>
  5801. <div class='OJBetter_setting_list'>
  5802. <label for="cppCodeTemplateComplete"><span
  5803. data-i18n="settings:codeEditor.staticCompletionEnhancement.cppCodeTemplateComplete.label"></span></label>
  5804. <div class="help_tip">
  5805. ${helpCircleHTML}
  5806. <div class="tip_text" data-i18n="[html]settings:codeEditor.staticCompletionEnhancement.cppCodeTemplateComplete.helpText"></div>
  5807. </div>
  5808. <input type="checkbox" id="cppCodeTemplateComplete" name="cppCodeTemplateComplete">
  5809. </div>
  5810. <hr>
  5811. <h5 data-i18n="settings:codeEditor.staticCompletionEnhancement.customization"></h5>
  5812. <div class='OJBetter_setting_list alert_warn'>
  5813. <div>
  5814. <p data-i18n="settings:codeEditor.staticCompletionEnhancement.performanceWarning"></p>
  5815. </div>
  5816. </div>
  5817. <div id="Complet_config" class="config"></div>
  5818. </div>
  5819. `;
  5820.  
  5821. const preference_settings_HTML = `
  5822. <div id="preference-settings" class="settings-page">
  5823. <h3 data-i18n="settings:preference.title"></h3>
  5824. <hr>
  5825. <div class='OJBetter_setting_list'>
  5826. <label for="showLoading" data-i18n="settings:preference.loadingInfo.label"></label>
  5827. <div class="help_tip">
  5828. ${helpCircleHTML}
  5829. <div class="tip_text" data-i18n="[html]settings:preference.loadingInfo.helpText" data-i18n-options='{ "scriptName": "${OJBetter.state.name}" }'></div>
  5830. </div>
  5831. <input type="checkbox" id="showLoading" name="showLoading">
  5832. </div>
  5833. <div class='OJBetter_setting_list'>
  5834. <label for="hoverTargetAreaDisplay" data-i18n="settings:preference.targetArea.label"></label>
  5835. <div class="help_tip">
  5836. ${helpCircleHTML}
  5837. <div class="tip_text" data-i18n="[html]settings:preference.targetArea.helpText"></div>
  5838. </div>
  5839. <input type="checkbox" id="hoverTargetAreaDisplay" name="hoverTargetAreaDisplay">
  5840. </div>
  5841. <div class='OJBetter_setting_list'>
  5842. <label for='iconButtonSize'>
  5843. <div style="display: flex;align-items: center;" data-i18n="settings:preference.iconButtonSize.title"></div>
  5844. </label>
  5845. <div class="help_tip">
  5846. ${helpCircleHTML}
  5847. <div class="tip_text" data-i18n="[html]settings:preference.iconButtonSize.helpText"></div>
  5848. </div>
  5849. <input type='number' id='iconButtonSize' class='no_default' require=true data-i18n="[placeholder]settings:preference.iconButtonSize.placeholder">
  5850. <span>px</span>
  5851. </div>
  5852. </div>
  5853. `;
  5854.  
  5855. const dev_settings_HTML = `
  5856. <div id="dev-settings" class="settings-page">
  5857. <h3 data-i18n="settings:dev.title"></h3>
  5858. <hr>
  5859. <div class='OJBetter_setting_list alert_danger'>
  5860. <div>
  5861. <p data-i18n="[html]settings:dev.notice"></p>
  5862. </div>
  5863. </div>
  5864. <hr>
  5865. <h5 data-i18n="settings:dev.load.title"></h5>
  5866. <div class='OJBetter_setting_list'>
  5867. <label for="notWaiteLoaded"><span data-i18n="settings:dev.load.notWaiteLoaded.label"></span></label>
  5868. <div class="help_tip">
  5869. ${helpCircleHTML}
  5870. <div class="tip_text" data-i18n="[html]settings:dev.load.notWaiteLoaded.helpText"></div>
  5871. </div>
  5872. <input type="checkbox" id="notWaiteLoaded" name="notWaiteLoaded">
  5873. </div>
  5874. <hr>
  5875. <h5 data-i18n="settings:dev.l10n.title"></h5>
  5876. <div class='OJBetter_setting_list'>
  5877. <label><span data-i18n="settings:dev.l10n.refreshScrpitCache.label"></span></label>
  5878. <div class="help_tip">
  5879. ${helpCircleHTML}
  5880. <div class="tip_text" data-i18n="[html]settings:dev.l10n.refreshScrpitCache.helpText"></div>
  5881. </div>
  5882. <button type="button" id="l10n_refreshScrpitCacheButton" name="l10n_refreshScrpitCacheButton" data-i18n="settings:dev.l10n.refreshScrpitCache.button"></button>
  5883. </div>
  5884. <hr>
  5885. <h5 data-i18n="settings:dev.l10n_web.title"></h5>
  5886. <div class='OJBetter_setting_list'>
  5887. <label><span data-i18n="settings:dev.l10n_web.refreshScrpitCache.label"></span></label>
  5888. <div class="help_tip">
  5889. ${helpCircleHTML}
  5890. <div class="tip_text" data-i18n="[html]settings:dev.l10n_web.refreshScrpitCache.helpText"></div>
  5891. </div>
  5892. <button type="button" id="l10n_web_refreshScrpitCacheButton" name="l10n_web_refreshScrpitCacheButton" data-i18n="settings:dev.l10n_web.refreshScrpitCache.button"></button>
  5893. </div>
  5894. <div class='OJBetter_setting_list'>
  5895. <label for="isRuleMarkingEnabled"><span data-i18n="settings:dev.l10n_web.isRuleMarkingEnabled.label"></span></label>
  5896. <div class="help_tip">
  5897. ${helpCircleHTML}
  5898. <div class="tip_text" data-i18n="[html]settings:dev.l10n_web.isRuleMarkingEnabled.helpText"></div>
  5899. </div>
  5900. <input type="checkbox" id="isRuleMarkingEnabled" name="isRuleMarkingEnabled">
  5901. </div>
  5902. <hr>
  5903. <h5 data-i18n="settings:dev.indexedDB.title"></h5>
  5904. <div class='OJBetter_setting_list'>
  5905. <label><span data-i18n="settings:dev.indexedDB.clear.label"></span></label>
  5906. <div class="help_tip">
  5907. ${helpCircleHTML}
  5908. <div class="tip_text" data-i18n="[html]settings:dev.indexedDB.clear.helpText"></div>
  5909. </div>
  5910. <button type="button" id="indexedDB_clearButton" name="indexedDB_clearButton" data-i18n="settings:dev.indexedDB.clear.button"></button>
  5911. </div>
  5912. <div class='OJBetter_setting_list'>
  5913. <label><span data-i18n="settings:dev.indexedDB.inputOrExport.label"></span></label>
  5914. <div class="help_tip">
  5915. ${helpCircleHTML}
  5916. <div class="tip_text" data-i18n="[html]settings:dev.indexedDB.inputOrExport.helpText"></div>
  5917. </div>
  5918. <button type="button" id="indexedDB_exportButton" name="indexedDB_exportButton" data-i18n="settings:dev.indexedDB.inputOrExport.export"></button>
  5919. <button type="button" id="indexedDB_importButton" name="indexedDB_importButton" data-i18n="settings:dev.indexedDB.inputOrExport.import"></button>
  5920. </div>
  5921. <hr>
  5922. <h5 data-i18n="settings:dev.configuration.title"></h5>
  5923. <div class='OJBetter_setting_list'>
  5924. <label><span data-i18n="settings:dev.configuration.clear.label"></span></label>
  5925. <div class="help_tip">
  5926. ${helpCircleHTML}
  5927. <div class="tip_text" data-i18n="[html]settings:dev.configuration.clear.helpText"></div>
  5928. </div>
  5929. <button type="button" id="configuration_clearButton" name="configuration_clearButton" data-i18n="settings:dev.configuration.clear.button"></button>
  5930. </div>
  5931. <div class='OJBetter_setting_list'>
  5932. <label><span data-i18n="settings:dev.configuration.inputOrExport.label"></span></label>
  5933. <div class="help_tip">
  5934. ${helpCircleHTML}
  5935. <div class="tip_text" data-i18n="[html]settings:dev.configuration.inputOrExport.helpText"></div>
  5936. </div>
  5937. <button type="button" id="configuration_exportButton" name="configuration_exportButton" data-i18n="settings:dev.configuration.inputOrExport.export"></button>
  5938. <button type="button" id="configuration_importButton" name="configuration_importButton" data-i18n="settings:dev.configuration.inputOrExport.import"></button>
  5939. </div>
  5940. </div>
  5941. `;
  5942.  
  5943. const about_settings_HTML = `
  5944. <div id="about-settings" class="settings-page">
  5945. <h3 data-i18n="settings:about.title"></h3>
  5946. <hr>
  5947. <div class='versionInfo'>
  5948. <p>${OJBetter.state.name}</p>
  5949. <p><span data-i18n="settings:about.version"></span><span id="nowVersion">${OJBetter.state.version}</span></p>
  5950. <p> @北极小狐 <a target="_blank" href="https://github.com/beijixiaohu/OJBetter">Github</a>
  5951. <a target="_blank" href="https://greatest.deepsurf.us/zh-CN/scripts/465777">GreasyFork</a></p>
  5952. </div>
  5953. <hr>
  5954. <h5 data-i18n="settings:about.update.title"></h5>
  5955. <div id="thanksforDevChannelNotice" class='OJBetter_setting_list alert_info'>
  5956. <div data-i18n="[html]settings:about.update.thanksforDevChannelNotice"} data-i18n-options='{ "scriptName": "${OJBetter.state.name}" }' ></div>
  5957. </div>
  5958. <div class='OJBetter_setting_list'>
  5959. <label for="updateChannel"><span data-i18n="settings:about.update.channel.label"></span></label>
  5960. <div class="help_tip">
  5961. ${helpCircleHTML}
  5962. <div class="tip_text" data-i18n="[html]settings:about.update.channel.helpText"></div>
  5963. </div>
  5964. <select id="updateChannel" name="updateChannel">
  5965. <option value="release" data-i18n="settings:about.update.channel.options.release"></option>
  5966. <option value="dev" data-i18n="settings:about.update.channel.options.dev"></option>
  5967. </select>
  5968. </div>
  5969. <div class='OJBetter_setting_list'>
  5970. <label for="updateSource"><span data-i18n="settings:about.update.source.label"></span></label>
  5971. <div class="help_tip">
  5972. ${helpCircleHTML}
  5973. <div class="tip_text" data-i18n="[html]settings:about.update.source.helpText"></div>
  5974. </div>
  5975. <select id="updateSource" name="updateSource">
  5976. <option value="greasyfork" data-i18n="settings:about.update.source.options.greasyfork"></option>
  5977. <option value="github" data-i18n="settings:about.update.source.options.github"></option>
  5978. <option value="aliyunoss" data-i18n="settings:about.update.source.options.aliyunoss"></option>
  5979. </select>
  5980. </div>
  5981. </div>
  5982. `;
  5983.  
  5984. const OJBetter_setting_content_HTML = `
  5985. <div class="OJBetter_setting_content">
  5986. ${basic_settings_HTML}
  5987. ${l10n_settings_HTML}
  5988. ${translation_settings_HTML}
  5989. ${clist_rating_settings_HTML}
  5990. ${code_editor_settings_HTML}
  5991. ${preference_settings_HTML}
  5992. ${dev_settings_HTML}
  5993. ${about_settings_HTML}
  5994. </div>
  5995. `;
  5996.  
  5997. // 设置界面HTML
  5998. const OJBetterSettingMenu_HTML = `
  5999. <dialog class='OJBetter_setting_menu' id='OJBetter_setting_menu'>
  6000. <div class="tool-box">
  6001. <button class='ojb_btn ojb_btn_popover top btn-close'>
  6002. <i class="iconfont">&#xe614;</i>
  6003. </button>
  6004. </div>
  6005. <div class="OJBetter_setting_container">
  6006. ${OJBetter_setting_sidebar_HTML}
  6007. ${OJBetter_setting_content_HTML}
  6008. </div>
  6009. </dialog>
  6010. `;
  6011.  
  6012. const apiCustomConfigHTML = (prefix) => {
  6013. return `
  6014. <div class="OJBetter_setting_list">
  6015. <label for='${prefix}_header'>
  6016. <div style="display: flex;align-items: center;">
  6017. <span class="input_label" data-i18n="config:common.advanced.header.label"></span>
  6018. <div class="help_tip">
  6019. ${helpCircleHTML}
  6020. <div class="tip_text" data-i18n="[html]config:common.advanced.header.tipText"></div>
  6021. </div>
  6022. </div>
  6023. </label>
  6024. <textarea id="${prefix}_header" placeholder='' require = false data-i18n="[placeholder]config:common.advanced.header.placeholder"></textarea>
  6025. </div>
  6026. <div class="OJBetter_setting_list">
  6027. <label for='${prefix}_data'>
  6028. <div style="display: flex;align-items: center;">
  6029. <span class="input_label" data-i18n="config:common.advanced.data.label"></span>
  6030. <div class="help_tip">
  6031. ${helpCircleHTML}
  6032. <div class="tip_text" data-i18n="[html]config:common.advanced.data.tipText"></div>
  6033. </div>
  6034. </div>
  6035. </label>
  6036. <textarea id="${prefix}_data" placeholder='' require = false data-i18n="[placeholder]config:common.advanced.data.placeholder"></textarea>
  6037. </div>
  6038. `;
  6039. };
  6040.  
  6041. const apiQuotaConfigHTML = (prefix) => {
  6042. return `
  6043. <div class="OJBetter_setting_list">
  6044. <label for='${prefix}_quota_url'>
  6045. <div style="display: flex;align-items: center;">
  6046. <span class="input_label" data-i18n="config:common.quota.url.label"></span>
  6047. <div class="help_tip">
  6048. ${helpCircleHTML}
  6049. <div class="tip_text" data-i18n="[html]config:common.quota.url.tipText"></div>
  6050. </div>
  6051. </div>
  6052. </label>
  6053. <input type='text' id='${prefix}_quota_url' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:common.quota.url.placeholder">
  6054. </div>
  6055. <div class="OJBetter_setting_list">
  6056. <label for="${prefix}_quota_method" style="display: flex;" data-i18n="config:common.quota.method.label"></label>
  6057. <div class="help_tip">
  6058. ${helpCircleHTML}
  6059. <div class="tip_text" data-i18n="[html]config:common.quota.method.tipText"></div>
  6060. </div>
  6061. <select id="${prefix}_quota_method" name="${prefix}_quota_method">
  6062. <option value="get">GET</option>
  6063. <option value="post">POST</option>
  6064. </select>
  6065. </div>
  6066. <div class="OJBetter_setting_list">
  6067. <label for='${prefix}_quota_header'>
  6068. <div style="display: flex;align-items: center;">
  6069. <span class="input_label" data-i18n="config:common.quota.header.label"></span>
  6070. <div class="help_tip">
  6071. ${helpCircleHTML}
  6072. <div class="tip_text" data-i18n="[html]config:common.quota.header.tipText"></div>
  6073. </div>
  6074. </div>
  6075. </label>
  6076. <textarea id="${prefix}_quota_header" placeholder='' require = false data-i18n="[placeholder]config:common.quota.header.placeholder"></textarea>
  6077. </div>
  6078. <div class="OJBetter_setting_list">
  6079. <label for='${prefix}_quota_data'>
  6080. <div style="display: flex;align-items: center;">
  6081. <span class="input_label" data-i18n="config:common.quota.data.label"></span>
  6082. <div class="help_tip">
  6083. ${helpCircleHTML}
  6084. <div class="tip_text" data-i18n="[html]config:common.quota.data.tipText"></div>
  6085. </div>
  6086. </div>
  6087. </label>
  6088. <textarea id="${prefix}_quota_data" placeholder='' require = false data-i18n="[placeholder]config:common.quota.data.placeholder"></textarea>
  6089. </div>
  6090. <div class="OJBetter_setting_list">
  6091. <div style="display: flex;align-items: center;">
  6092. <span class="input_label" data-i18n="config:common.quota.surplus.label"></span>
  6093. <div class="help_tip">
  6094. ${helpCircleHTML}
  6095. <div class="tip_text" data-i18n="[html]config:common.quota.surplus.tipText"></div>
  6096. </div>
  6097. </div>
  6098. </label>
  6099. <input type='text' id='${prefix}_quota_surplus' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:common.quota.surplus.placeholder">
  6100. </div>
  6101. `;
  6102. }
  6103.  
  6104. const deeplConfigEditHTML = `
  6105. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  6106. <div class='OJBetter_setting_content'>
  6107. <div class="tool-box">
  6108. <button class='ojb_btn ojb_btn_popover top btn-close'>
  6109. <i class="iconfont">&#xe614;</i>
  6110. </button>
  6111. </div>
  6112. <h4 data-i18n="config:deepl.title"></h4>
  6113. <h5 data-i18n="config:deepl.basic.title"></h5>
  6114. <hr>
  6115. <div class="OJBetter_setting_list">
  6116. <label for='name'>
  6117. <span class="input_label" data-i18n="config:deepl.basic.name.label"></span>
  6118. </label>
  6119. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:deepl.basic.name.placeholder">
  6120. </div>
  6121. <div class='OJBetter_setting_list'>
  6122. <label for="deepl_apiGenre" style="display: flex;" data-i18n="config:deepl.genre.label"></label>
  6123. <div class="help_tip">
  6124. ${helpCircleHTML}
  6125. <div class="tip_text" data-i18n="[html]config:deepl.genre.tipText"></div>
  6126. </div>
  6127. <select id="deepl_apiGenre" name="deepl_apiGenre">
  6128. <option value="api-free">api-free</option>
  6129. <option value="api-pro">api-pro</option>
  6130. <option value="deeplx">deeplx</option>
  6131. </select>
  6132. </div>
  6133. <div class="OJBetter_setting_list">
  6134. <label for='deepl_key'>
  6135. <div style="display: flex;align-items: center;">
  6136. <span class="input_label" data-i18n="config:deepl.basic.key.label"></span>
  6137. <div class="help_tip">
  6138. ${helpCircleHTML}
  6139. <div class="tip_text" data-i18n="[html]config:deepl.basic.key.tipText"></div>
  6140. </div>
  6141. </div>
  6142. </label>
  6143. <input type='text' id='deepl_key' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:deepl.basic.key.placeholder">
  6144. </div>
  6145. <div class="OJBetter_setting_list">
  6146. <label for='deepl_proxy'>
  6147. <div style="display: flex;align-items: center;">
  6148. <span class="input_label" data-i18n="config:deepl.basic.proxy.label">Proxy API:</span>
  6149. <div class="help_tip">
  6150. ${helpCircleHTML}
  6151. <div class="tip_text" data-i18n="[html]config:deepl.basic.proxy.tipText"></div>
  6152. </div>
  6153. </div>
  6154. </label>
  6155. <input type='text' id='deepl_proxy' placeholder='' require = false>
  6156. </div>
  6157. <hr>
  6158. <details>
  6159. <summary data-i18n="config:common.advanced.title"></summary>
  6160. ${apiCustomConfigHTML('deepl')}
  6161. </details>
  6162. <details>
  6163. <summary data-i18n="config:common.quota.title"></summary>
  6164. ${apiQuotaConfigHTML('deepl')}
  6165. </details>
  6166. <button id='tempConfig_save' data-i18n="common:save"></button>
  6167. </div>
  6168. </dialog>
  6169. `;
  6170.  
  6171. const chatgptConfigEditHTML = `
  6172. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  6173. <div class='OJBetter_setting_content'>
  6174. <div class="tool-box">
  6175. <button class='ojb_btn ojb_btn_popover top btn-close'>
  6176. <i class="iconfont">&#xe614;</i>
  6177. </button>
  6178. </div>
  6179. <h4 data-i18n="config:chatgpt.title"></h4>
  6180. <h5 data-i18n="config:chatgpt.basic.title"></h5>
  6181. <hr>
  6182. <div class="OJBetter_setting_list">
  6183. <label for='name'>
  6184. <span class="input_label" data-i18n="config:chatgpt.basic.name.label"></span>
  6185. </label>
  6186. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:chatgpt.basic.name.placeholder">
  6187. </div>
  6188. <div class="OJBetter_setting_list">
  6189. <label for='chatgpt_model'>
  6190. <div style="display: flex;align-items: center;">
  6191. <span class="input_label" data-i18n="[html]config:chatgpt.basic.model.label"></span>
  6192. <div class="help_tip">
  6193. ${helpCircleHTML}
  6194. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.model.tipText"></div>
  6195. </div>
  6196. </div>
  6197. </label>
  6198. <input type='text' id='chatgpt_model' placeholder='gpt-3.5-turbo' require = false>
  6199. </div>
  6200. <div class="OJBetter_setting_list">
  6201. <label for='chatgpt_key'>
  6202. <div style="display: flex;align-items: center;">
  6203. <span class="input_label" data-i18n="config:chatgpt.basic.key.label"></span>
  6204. <div class="help_tip">
  6205. ${helpCircleHTML}
  6206. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.key.tipText"></div>
  6207. </div>
  6208. </div>
  6209. </label>
  6210. <input type='text' id='chatgpt_key' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:chatgpt.basic.key.placeholder">
  6211. </div>
  6212. <div class="OJBetter_setting_list">
  6213. <label for='chatgpt_proxy'>
  6214. <div style="display: flex;align-items: center;">
  6215. <span class="input_label" data-i18n="config:chatgpt.basic.proxy.label">Proxy API:</span>
  6216. <div class="help_tip">
  6217. ${helpCircleHTML}
  6218. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.proxy.tipText"></div>
  6219. </div>
  6220. </div>
  6221. </label>
  6222. <input type='text' id='chatgpt_proxy' placeholder='https://api.openai.com/v1/chat/completions' require = false>
  6223. </div>
  6224. <hr>
  6225. <details>
  6226. <summary data-i18n="config:common.advanced.title"></summary>
  6227. ${apiCustomConfigHTML('chatgpt')}
  6228. </details>
  6229. <details>
  6230. <summary data-i18n="config:common.quota.title"></summary>
  6231. ${apiQuotaConfigHTML('chatgpt')}
  6232. </details>
  6233. <button id='tempConfig_save' data-i18n="common:save"></button>
  6234. </div>
  6235. </dialog>
  6236. `;
  6237.  
  6238. const CompletConfigEditHTML = `
  6239. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  6240. <div class='OJBetter_setting_content'>
  6241. <div class="tool-box">
  6242. <button class='ojb_btn ojb_btn_popover top btn-close'>
  6243. <i class="iconfont">&#xe614;</i>
  6244. </button>
  6245. </div>
  6246. <h4 data-i18n="config:complet.title"></h4>
  6247. <hr>
  6248. <div class="OJBetter_setting_list">
  6249. <label for='name'>
  6250. <span class="input_label" data-i18n="config:complet.name.label"></span>
  6251. </label>
  6252. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:complet.name.placeholder">
  6253. </div>
  6254. <div class='OJBetter_setting_list'>
  6255. <label for="complet_isChoose"><span id="loaded_span" data-i18n="config:complet.choose.label"></span></label>
  6256. <input type="checkbox" id="complet_isChoose" name="complet_isChoose" require = false>
  6257. </div>
  6258. <div class='OJBetter_setting_list'>
  6259. <label for="complet_genre" style="display: flex;" data-i18n="config:complet.genre.label"></label>
  6260. <div class="help_tip">
  6261. ${helpCircleHTML}
  6262. <div class="tip_text" data-i18n="[html]config:complet.genre.tipText"></div>
  6263. </div>
  6264. <select id="complet_genre" name="complet_genre">
  6265. <option value="monaco">monaco</option>
  6266. <option value="ace">ace</option>
  6267. </select>
  6268. </div>
  6269. <div class='OJBetter_setting_list'>
  6270. <label for="complet_language" style="display: flex;" data-i18n="config:complet.language.label"></label>
  6271. <select id="complet_language" name="complet_language">
  6272. <option value="cpp">cpp</option>
  6273. <option value="python">python</option>
  6274. <option value="java">java</option>
  6275. <option value="c">c</option>
  6276. </select>
  6277. </div>
  6278. <div class="OJBetter_setting_list">
  6279. <label for='complet_jsonUrl'>
  6280. <div style="display: flex;align-items: center;">
  6281. <span class="input_label">JSON URL:</span>
  6282. <div class="help_tip">
  6283. ${helpCircleHTML}
  6284. <div class="tip_text" data-i18n="[html]config:complet.jsonurl.tipText"></div>
  6285. </div>
  6286. </div>
  6287. </label>
  6288. <div class='OJBetter_setting_list alert_warn' data-i18n="[html]config:complet.jsonurl.alert"></div>
  6289. <input type='text' id='complet_jsonUrl' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:complet.jsonurl.placeholder">
  6290. </div>
  6291. <button id='tempConfig_save' data-i18n="common:save"></button>
  6292. </div>
  6293. </dialog>
  6294. `;
  6295.  
  6296. /**
  6297. * 加载设置按钮面板
  6298. */
  6299. async function initSettingsPanel() {
  6300. /**
  6301. * 添加右上角设置按钮
  6302. * @param {string} location 位置选择器
  6303. * @param {string} method 插入方法
  6304. */
  6305. function insertOJBetterSettingButton(location, method) {
  6306. $(location)[method](`<button class='ojb_btn OJBetter_setting'>
  6307. ${OJBetter.state.name} ${i18next.t('settings', { ns: 'common' })}</button>`);
  6308. }
  6309.  
  6310. /**
  6311. * ============================================
  6312. * 该网站插入设置按钮的位置和方式
  6313. */
  6314. if (OJBetter.typeOfPage.isEnglishLanguage) {
  6315. insertOJBetterSettingButton("#navbar-collapse > ul:nth-child(2) > li:last-child", "after");
  6316. } else {
  6317. if ($('.header-mypage').length > 0) insertOJBetterSettingButton(".header-mypage", "after");
  6318. else insertOJBetterSettingButton("#navbar-collapse > ul:nth-child(2) > li:last-child", "after");
  6319. }
  6320. /**
  6321. * ============================================
  6322. */
  6323.  
  6324. const $settingBtns = $(".OJBetter_setting");
  6325. $settingBtns.click(() => {
  6326. $settingBtns.prop("disabled", true).addClass("open");
  6327.  
  6328. // 设置面板div
  6329. const settingMenu = OJB_safeCreateJQElement(OJBetterSettingMenu_HTML);
  6330. $("body").append(settingMenu);
  6331.  
  6332. elementLocalize(settingMenu); // 加载i18n
  6333. OJB_showModal(settingMenu);
  6334. OJB_addDraggable($('#OJBetter_setting_menu')); // 窗口支持拖拽
  6335.  
  6336. // help帮助悬浮窗位置更新
  6337. $(document).on('mouseenter', '.help-icon', function (event) {
  6338. var menuOffset = $('.OJBetter_setting_menu:last').offset();
  6339. var mouseX = event.pageX - menuOffset.left;
  6340. var mouseY = event.pageY - menuOffset.top;
  6341.  
  6342. $('.tip_text').css({
  6343. 'top': mouseY + 'px',
  6344. 'left': mouseX + 'px'
  6345. });
  6346. });
  6347.  
  6348. // 选项卡切换
  6349. $('.OJBetter_setting_sidebar a').click(function (event) {
  6350. event.preventDefault();
  6351. $('.OJBetter_setting_sidebar a').removeClass('active');
  6352. $('.settings-page').removeClass('active');
  6353. $(this).addClass('active');
  6354. const targetPageId = $(this).attr('href').substring(1);
  6355. $('#' + targetPageId).addClass('active');
  6356. });
  6357.  
  6358. /**
  6359. * 更新单选按钮组的可用状态
  6360. * @param {string} selector 单选按钮组的选择器
  6361. * @param {string} targetLanguage 目标语言
  6362. * @param {Object} translationSupport 翻译支持的语言对应表
  6363. */
  6364. const updateRadioButtonsAvailability = (selector, targetLanguage) => {
  6365. Object.entries(OJBetter.supportList.translationSupport).forEach(([service, languages]) => {
  6366. const radioButton = $(selector).find(`input[value="${service}"]`);
  6367. const isEnabled = languages[targetLanguage];
  6368. $(radioButton).prop('disabled', !isEnabled);
  6369. if (!isEnabled) {
  6370. $(radioButton).prop('checked', false);
  6371. }
  6372. });
  6373. };
  6374.  
  6375. /**
  6376. * 检查下拉框选中项是否有效,若无效则清空
  6377. * @param {string} selector 下拉框的选择器
  6378. */
  6379. const validateSelectOption = (selector) => {
  6380. const selectedValue = $(selector).val();
  6381. if (!selectedValue) {
  6382. $(selector).val('');
  6383. }
  6384. };
  6385.  
  6386. /**
  6387. * 更新翻译目标语言下拉框的可用状态
  6388. * @param {string} selector 下拉框的选择器
  6389. * @param {string} targetLanguage 目标语言
  6390. * @param {Object} translationSupport 翻译支持的语言对应表
  6391. */
  6392. const updateSelectOptionsAvailability = (selector, targetLanguage) => {
  6393. $(selector).children('option').each(function () {
  6394. const optionValue = $(this).val();
  6395. const isEnabled = OJBetter.supportList.translationSupport[optionValue] ? OJBetter.supportList.translationSupport[optionValue][targetLanguage] : true;
  6396. $(this).prop('disabled', !isEnabled);
  6397. });
  6398. validateSelectOption(selector);
  6399. };
  6400.  
  6401. /**
  6402. * 更新翻译服务复选框的可用状态
  6403. * @param {string} selector 复选框的选择器
  6404. * @param {string} targetLanguage 目标语言
  6405. * @param {Object} translationSupport 翻译支持的语言对应表
  6406. */
  6407. const updateCheckboxesAvailability = (selector, targetLanguage) => {
  6408. $(selector).children('input').each(function () {
  6409. const checkboxValue = $(this).val();
  6410. const isEnabled = OJBetter.supportList.translationSupport[checkboxValue][targetLanguage];
  6411. $(this).prop('disabled', !isEnabled);
  6412. if (!isEnabled) {
  6413. $(this).prop('checked', false);
  6414. }
  6415. });
  6416. };
  6417.  
  6418. /**
  6419. * 更新更新源下拉框的可用状态
  6420. * @param {string} selector 下拉框的选择器
  6421. * @param {string} targetLanguage 目标语言
  6422. * @param {Object} translationSupport 翻译支持的语言对应表
  6423. */
  6424. const updateUpdateSourceSelectOptionsAvailability = (selector, updateChannel) => {
  6425. $(selector).children('option').each(function () {
  6426. const optionValue = $(this).val();
  6427. const isEnabled = OJBetter.supportList.updateSourceSupportList[optionValue][updateChannel];
  6428. $(this).prop('disabled', !isEnabled);
  6429. });
  6430. validateSelectOption(selector);
  6431. };
  6432.  
  6433. /**
  6434. * 创建配置结构
  6435. * @param {string} type - 该字段的在表单中的类型
  6436. * @param {string} value - 在配置中的键值
  6437. * @param {boolean} require - 是否是表单的必填项
  6438. * @param {string} [check=""] check - 调用的合法性检查
  6439. */
  6440. function createStructure(type, value, require, check = "") {
  6441. return { type, value, require, check };
  6442. }
  6443.  
  6444. // deepl配置
  6445. const deeplStructure = {
  6446. '#name': createStructure('text', 'name', true),
  6447. '#deepl_apiGenre': createStructure('text', 'apiGenre', true),
  6448. '#deepl_key': createStructure('text', 'key', false),
  6449. '#deepl_proxy': createStructure('text', 'proxy', false),
  6450. '#deepl_header': createStructure('text', '_header', false, 'keyValuePairs'),
  6451. '#deepl_data': createStructure('text', '_data', false, 'keyValuePairs'),
  6452. '#deepl_quota_url': createStructure('text', 'quota_url', false),
  6453. '#deepl_quota_method': createStructure('text', 'quota_method', false),
  6454. '#deepl_quota_header': createStructure('text', 'quota_header', false, 'keyValuePairs'),
  6455. '#deepl_quota_data': createStructure('text', 'quota_data', false, 'keyValuePairs'),
  6456. '#deepl_quota_surplus': createStructure('text', 'quota_surplus', false, 'dotSeparatedPath'),
  6457. };
  6458. let tempConfig_deepl = GM_getValue('deepl_config'); // 获取配置信息
  6459. const configManager_deepl = new ConfigManager('#deepl_config', 'deepl_config_', tempConfig_deepl, deeplStructure, deeplConfigEditHTML);
  6460. configManager_deepl.registerChoiceChange();
  6461.  
  6462. // chatgpt配置
  6463. const chatgptStructure = {
  6464. '#name': createStructure('text', 'name', true),
  6465. '#chatgpt_model': createStructure('text', 'model', false),
  6466. '#chatgpt_key': createStructure('text', 'key', true),
  6467. '#chatgpt_proxy': createStructure('text', 'proxy', false),
  6468. '#chatgpt_header': createStructure('text', '_header', false, 'keyValuePairs'),
  6469. '#chatgpt_data': createStructure('text', '_data', false, 'keyValuePairs'),
  6470. '#chatgpt_quota_url': createStructure('text', 'quota_url', false),
  6471. '#chatgpt_quota_header': createStructure('text', 'quota_header', false, 'keyValuePairs'),
  6472. '#chatgpt_quota_data': createStructure('text', 'quota_data', false, 'keyValuePairs'),
  6473. '#chatgpt_quota_surplus': createStructure('text', 'quota_surplus', false, 'dotSeparatedPath'),
  6474. '#chatgpt_quota_method': createStructure('text', 'quota_method', false),
  6475. };
  6476. let tempConfig_chatgpt = GM_getValue('chatgpt_config'); // 获取配置信息
  6477. const configManager_chatgpt = new ConfigManager('#chatgpt_config', 'chatgpt_config_', tempConfig_chatgpt, chatgptStructure, chatgptConfigEditHTML);
  6478. configManager_chatgpt.registerChoiceChange();
  6479.  
  6480. // Complet配置
  6481. const CompletStructure = {
  6482. '#name': createStructure('text', 'name', true),
  6483. '#complet_isChoose': createStructure('checkbox', 'isChoose', true),
  6484. '#complet_genre': createStructure('text', 'genre', true),
  6485. '#complet_language': createStructure('text', 'language', true),
  6486. '#complet_jsonUrl': createStructure('text', 'jsonUrl', true)
  6487. };
  6488. let tempConfig_Complet = GM_getValue('Complet_config'); // 获取配置信息
  6489. const configManager_complet = new ConfigManager('#Complet_config', 'complet_config_', tempConfig_Complet, CompletStructure, CompletConfigEditHTML, false);
  6490.  
  6491. // 状态更新
  6492. $("input[name='darkMode'][value='" + OJBetter.basic.darkMode + "']").prop("checked", true);
  6493. $("#showLoading").prop("checked", GM_getValue("showLoading") === true);
  6494. $("#expandFoldingblocks").prop("checked", GM_getValue("expandFoldingblocks") === true);
  6495. $("#renderPerfOpt").prop("checked", GM_getValue("renderPerfOpt") === true);
  6496. $("#selectElementPerfOpt").prop("checked", GM_getValue("selectElementPerfOpt") === true);
  6497. $("#commentPaging").prop("checked", GM_getValue("commentPaging") === true);
  6498. $("#standingsRecolor").prop("checked", GM_getValue("standingsRecolor") === true);
  6499. $("#showJumpToLuogu").prop("checked", GM_getValue("showJumpToLuogu") === true);
  6500. $("#showCF2vjudge").prop("checked", GM_getValue("showCF2vjudge") === true);
  6501. $("#hoverTargetAreaDisplay").prop("checked", GM_getValue("hoverTargetAreaDisplay") === true);
  6502. $("#showClistRating_contest").prop("checked", GM_getValue("showClistRating_contest") === true);
  6503. $("#showClistRating_problemset").prop("checked", GM_getValue("showClistRating_problemset") === true);
  6504. $("#showClistRating_problem").prop("checked", GM_getValue("showClistRating_problem") === true);
  6505. $("#RatingHidden").prop("checked", GM_getValue("RatingHidden") === true);
  6506. $('#scriptL10nLanguage').val(GM_getValue("scriptL10nLanguage"));
  6507. $('#localizationLanguage').val(GM_getValue("localizationLanguage"));
  6508. $("input[name='translation'][value='" + OJBetter.translation.choice + "']").prop("checked", true);
  6509. $("input[name='translation']").css("color", "gray");
  6510. $('#deepl_type').val(GM_getValue("deepl_type"));
  6511. $("#deepl_config_config_bar_ul").find(`input[name='deepl_config_config_item'][value='${tempConfig_deepl.choice}']`).prop("checked", true);
  6512. $('#enableEmphasisProtection').prop("checked", GM_getValue("enableEmphasisProtection") === true);
  6513. $('#enableLinkProtection').prop("checked", GM_getValue("enableLinkProtection") === true);
  6514. $("#chatgpt_config_config_bar_ul").find(`input[name='chatgpt_config_config_item'][value='${tempConfig_chatgpt.choice}']`).prop("checked", true);
  6515. $("#openai_isStream").prop("checked", GM_getValue("openai_isStream") === true);
  6516. $('#comment_translation_choice').val(GM_getValue("commentTranslationChoice"));
  6517. $('#iconButtonSize').val(GM_getValue("iconButtonSize"));
  6518. $("#autoTranslation").prop("checked", GM_getValue("autoTranslation") === true);
  6519. $('#shortTextLength').val(GM_getValue("shortTextLength"));
  6520. $("#allowMixTrans").prop("checked", GM_getValue("allowMixTrans") === true);
  6521. $('.OJBetter_checkboxs').find('input[type="checkbox"][name="mixedTranslation"]').each(function () {
  6522. if (OJBetter.translation.auto.mixTrans.servers.indexOf($(this).val()) > -1) {
  6523. $(this).prop('checked', true);
  6524. }
  6525. });
  6526. // 翻译目标语言下拉框
  6527. $('#transTargetLang').change(function () {
  6528. var selectedLang = $(this).val();
  6529. updateRadioButtonsAvailability('#translationServices', selectedLang);
  6530. updateSelectOptionsAvailability('#comment_translation_choice', selectedLang);
  6531. updateCheckboxesAvailability('.OJBetter_checkboxs', selectedLang);
  6532. });
  6533. $('#transTargetLang').val(GM_getValue("transTargetLang"));
  6534. $('#transTargetLang').change();
  6535. //
  6536. $('#comment_translation_mode').val(GM_getValue("commentTranslationMode"));
  6537. $("#memoryTranslateHistory").prop("checked", GM_getValue("memoryTranslateHistory") === true);
  6538. $('#transWaitTime').val(GM_getValue("transWaitTime"));
  6539. $('#translation_replaceSymbol').val(GM_getValue("replaceSymbol"));
  6540. $("#filterTextWithoutEmphasis").prop("checked", GM_getValue("filterTextWithoutEmphasis") === true);
  6541. $('#translation_retransAction').val(GM_getValue("retransAction"));
  6542. $("#clist_Authorization").val(GM_getValue("clist_Authorization"));
  6543. $("#problemPageCodeEditor").prop("checked", GM_getValue("problemPageCodeEditor") === true);
  6544. $("#beautifyPreBlocks").prop("checked", GM_getValue("beautifyPreBlocks") === true);
  6545. $("#isCodeSubmitConfirm").prop("checked", GM_getValue("isCodeSubmitConfirm") === true);
  6546. $("#alwaysConsumeMouseWheel").prop("checked", GM_getValue("alwaysConsumeMouseWheel") === true);
  6547. $("#submitButtonPosition").val(GM_getValue("submitButtonPosition"));
  6548. $("#cppCodeTemplateComplete").prop("checked", GM_getValue("cppCodeTemplateComplete") === true);
  6549. $("#useLSP").prop("checked", GM_getValue("useLSP") === true);
  6550. $("#OJBetter_Bridge_WorkUri").val(GM_getValue("OJBetter_Bridge_WorkUri"));
  6551. $("#OJBetter_Bridge_SocketUrl").val(GM_getValue("OJBetter_Bridge_SocketUrl"));
  6552. $("input[name='compiler'][value='" + OJBetter.monaco.onlineCompilerChoice + "']").prop("checked", true);
  6553. $("input[name='compiler']").css("color", "gray");
  6554. // 调试
  6555. $("#notWaiteLoaded").prop("checked", GM_getValue("notWaiteLoaded") === true);
  6556. $('#l10n_refreshScrpitCacheButton').click(clearI18nextCache);
  6557. $('#l10n_web_refreshScrpitCacheButton').click(clearWebsiteL10nData);
  6558. $("#isRuleMarkingEnabled").prop("checked", GM_getValue("isRuleMarkingEnabled") === true);
  6559. $('#indexedDB_clearButton').click(async () => { await clearDatabase(); });
  6560. $('#indexedDB_exportButton').click(async () => { downloadDataAsFile(await exportDatabase(), 'database_export.json') });
  6561. $('#indexedDB_importButton').click(() => { readFileInput(async (fileContent) => { await importDatabase(fileContent); }); });
  6562. $('#configuration_clearButton').click(deleteAllConfigSettings);
  6563. $('#configuration_exportButton').click(() => { downloadDataAsFile(exportSettingsToJSON(), 'configuration_export.json') });
  6564. $('#configuration_importButton').click(() => { readFileInput((fileContent) => { importSettingsFromJSON(fileContent); }) });
  6565. // 关于
  6566. $('#updateChannel').val(GM_getValue("updateChannel"));
  6567. $('#updateSource').val(GM_getValue("updateSource"));
  6568. $('#updateChannel').change(function () {
  6569. var selectedLang = $(this).val();
  6570. updateUpdateSourceSelectOptionsAvailability('#updateSource', selectedLang);
  6571. if (selectedLang == "dev") $('#thanksforDevChannelNotice').show();
  6572. else $('#thanksforDevChannelNotice').hide();
  6573. });
  6574. $('#updateChannel').change();
  6575.  
  6576. // 关闭
  6577. const $settingMenu = $(".OJBetter_setting_menu");
  6578. $settingMenu.on("click", ".btn-close", async () => {
  6579. // 设置的数据
  6580. const settings = {
  6581. darkMode: $("input[name='darkMode']:checked").val(),
  6582. showLoading: $("#showLoading").prop("checked"),
  6583. hoverTargetAreaDisplay: $("#hoverTargetAreaDisplay").prop("checked"),
  6584. expandFoldingblocks: $("#expandFoldingblocks").prop("checked"),
  6585. renderPerfOpt: $("#renderPerfOpt").prop("checked"),
  6586. selectElementPerfOpt: $("#selectElementPerfOpt").prop("checked"),
  6587. commentPaging: $("#commentPaging").prop("checked"),
  6588. standingsRecolor: $("#standingsRecolor").prop("checked"),
  6589. showJumpToLuogu: $("#showJumpToLuogu").prop("checked"),
  6590. showCF2vjudge: $("#showCF2vjudge").prop("checked"),
  6591. scriptL10nLanguage: $('#scriptL10nLanguage').val(),
  6592. localizationLanguage: $('#localizationLanguage').val(),
  6593. transTargetLang: $('#transTargetLang').val(),
  6594. translation: $("input[name='translation']:checked").val(),
  6595. deepl_type: $('#deepl_type').val(),
  6596. enableEmphasisProtection: $("#enableEmphasisProtection").prop("checked"),
  6597. enableLinkProtection: $("#enableLinkProtection").prop("checked"),
  6598. openai_isStream: $("#openai_isStream").prop("checked"),
  6599. commentTranslationChoice: $('#comment_translation_choice').val(),
  6600. iconButtonSize: $('#iconButtonSize').val(),
  6601. autoTranslation: $("#autoTranslation").prop("checked"),
  6602. shortTextLength: $('#shortTextLength').val(),
  6603. allowMixTrans: $("#allowMixTrans").prop("checked"),
  6604. mixedTranslation: (() => {
  6605. let mixedTranslation = [];
  6606. $('.OJBetter_checkboxs').find('input[type="checkbox"][name="mixedTranslation"]').each(function () {
  6607. if ($(this).is(":checked")) {
  6608. mixedTranslation.push($(this).val());
  6609. }
  6610. });
  6611. return mixedTranslation;
  6612. })(),
  6613. commentTranslationMode: $('#comment_translation_mode').val(),
  6614. memoryTranslateHistory: $('#memoryTranslateHistory').prop("checked"),
  6615. transWaitTime: $('#transWaitTime').val(),
  6616. replaceSymbol: $('#translation_replaceSymbol').val(),
  6617. filterTextWithoutEmphasis: $('#filterTextWithoutEmphasis').prop("checked"),
  6618. retransAction: $('#translation_retransAction').val(),
  6619. showClistRating_contest: $('#showClistRating_contest').prop("checked"),
  6620. showClistRating_problemset: $('#showClistRating_problemset').prop("checked"),
  6621. showClistRating_problem: $('#showClistRating_problem').prop("checked"),
  6622. RatingHidden: $('#RatingHidden').prop("checked"),
  6623. clist_Authorization: $('#clist_Authorization').val(),
  6624. problemPageCodeEditor: $("#problemPageCodeEditor").prop("checked"),
  6625. beautifyPreBlocks: $("#beautifyPreBlocks").prop("checked"),
  6626. isCodeSubmitConfirm: $("#isCodeSubmitConfirm").prop("checked"),
  6627. alwaysConsumeMouseWheel: $("#alwaysConsumeMouseWheel").prop("checked"),
  6628. submitButtonPosition: $('#submitButtonPosition').val(),
  6629. cppCodeTemplateComplete: $("#cppCodeTemplateComplete").prop("checked"),
  6630. useLSP: $("#useLSP").prop("checked"),
  6631. OJBetter_Bridge_WorkUri: $('#OJBetter_Bridge_WorkUri').val().replace(/\\/g, '/').replace(/\/$/, ''),
  6632. OJBetter_Bridge_SocketUrl: $('#OJBetter_Bridge_SocketUrl').val(),
  6633. onlineCompilerChoice: $("input[name='compiler']:checked").val(),
  6634. notWaiteLoaded: $("#notWaiteLoaded").prop("checked"),
  6635. isRuleMarkingEnabled: $("#isRuleMarkingEnabled").prop("checked"),
  6636. updateChannel: $('#updateChannel').val(),
  6637. updateSource: $('#updateSource').val()
  6638. };
  6639. // tempConfigs的数据
  6640. const tempConfigs = {
  6641. 'deepl_config': configManager_deepl.getTempConfig(),
  6642. 'chatgpt_config': configManager_chatgpt.getTempConfig(),
  6643. 'Complet_config': configManager_complet.getTempConfig()
  6644. }
  6645.  
  6646. // 判断是否改变
  6647. let changes = {};
  6648. const combinedConfigs = Object.assign({}, settings, tempConfigs); // 合并settings和tempConfigs对象
  6649. for (const [key, value] of Object.entries(combinedConfigs)) {
  6650. const storedValue = GM_getValue(key);
  6651. if (!OJB_deepEquals(value, storedValue)) {
  6652. changes[key] = { oldValue: storedValue, newValue: value };
  6653. }
  6654. }
  6655.  
  6656. // 如果changes对象不为空,则有变化
  6657. if (Object.keys(changes).length > 0) {
  6658. console.log("Changes detected:", changes);
  6659. const shouldSave = await OJB_createDialog(
  6660. i18next.t('saveSetting.title', { ns: 'dialog' }),
  6661. i18next.t('saveSetting.content', { ns: 'dialog' }),
  6662. [
  6663. i18next.t('saveSetting.buttons.0', { ns: 'dialog' }),
  6664. i18next.t('saveSetting.buttons.1', { ns: 'dialog' })
  6665. ]
  6666. ); // 配置改变保存确认
  6667. if (shouldSave) {
  6668. // 数据校验
  6669. // TODO
  6670. if (settings.deepl_type !== 'free') {
  6671. let selectedIndex = $('input[name="deepl_config_config_item"]:checked').length > 0;
  6672. if (!selectedIndex) {
  6673. $('.deepl_config a').removeClass('active');
  6674. $('.settings-page').removeClass('active');
  6675. $('#sidebar-translation-settings').addClass('active');
  6676. $('#translation-settings').addClass('active');
  6677.  
  6678. $('#deepl_config').addClass('missing');
  6679. return;
  6680. } else {
  6681. $('#deepl_config').removeClass('missing');
  6682. }
  6683. }
  6684. if (settings.translation === "openai") {
  6685. let selectedIndex = $('input[name="chatgpt_config_config_item"]:checked').length > 0;
  6686. if (!selectedIndex) {
  6687. $('.chatgpt_config a').removeClass('active');
  6688. $('.settings-page').removeClass('active');
  6689. $('#sidebar-translation-settings').addClass('active');
  6690. $('#translation-settings').addClass('active');
  6691.  
  6692. $('#chatgpt_config').addClass('missing');
  6693. return;
  6694. } else {
  6695. $('#chatgpt_config').removeClass('missing');
  6696. }
  6697. }
  6698. {
  6699. let selectedIndex = $('input[name="translation"]:checked').length > 0;
  6700. if (!selectedIndex) {
  6701. $('.OJBetter_setting_sidebar a').removeClass('active');
  6702. $('.settings-page').removeClass('active');
  6703. $('#sidebar-translation-settings').addClass('active');
  6704. $('#translation-settings').addClass('active');
  6705.  
  6706. $('#translationServices').addClass('missing');
  6707. return;
  6708. } else {
  6709. $('#translationServices').removeClass('missing');
  6710. }
  6711. }
  6712.  
  6713. // 保存数据
  6714. let refreshPage = false; // 是否需要刷新页面
  6715. for (const [key, value] of Object.entries(settings)) {
  6716. if (!refreshPage && !(key == 'translation' || key == 'darkMode' || key == 'commentTranslationChoice')) {
  6717. if (GM_getValue(key) != value) refreshPage = true;
  6718. }
  6719. GM_setValue(key, value);
  6720. }
  6721. for (const [key, value] of Object.entries(tempConfigs)) {
  6722. if (!refreshPage && (JSON.stringify(GM_getValue(key)) != JSON.stringify(value))) refreshPage = true;
  6723. GM_setValue(key, value);
  6724. }
  6725.  
  6726. if (refreshPage) location.reload();
  6727. else {
  6728. // 切换黑暗模式
  6729. if (OJBetter.basic.darkMode != settings.darkMode) {
  6730. OJBetter.basic.darkMode = settings.darkMode;
  6731. // 移除旧的事件监听器
  6732. changeEventListeners.forEach(listener => {
  6733. mediaQueryList.removeEventListener('change', listener);
  6734. });
  6735.  
  6736. if (OJBetter.basic.darkMode == "follow") {
  6737. changeEventListeners.push(handleColorSchemeChange);
  6738. mediaQueryList.addEventListener('change', handleColorSchemeChange);
  6739. $('html').removeAttr('data-theme');
  6740. } else if (OJBetter.basic.darkMode == "dark") {
  6741. $('html').attr('data-theme', 'dark');
  6742. if (OJBetter.monaco.editor) {
  6743. monaco.editor.setTheme('vs-dark');
  6744. }
  6745. } else {
  6746. $('html').attr('data-theme', 'light');
  6747. if (OJBetter.monaco.editor) {
  6748. monaco.editor.setTheme('vs');
  6749. }
  6750. // 移除旧的事件监听器
  6751. const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
  6752. window.matchMedia('(prefers-color-scheme: dark)');
  6753. }
  6754. }
  6755. // 更新配置信息
  6756. OJBetter.translation.choice = settings.translation;
  6757. OJBetter.translation.comment.choice = settings.commentTranslationChoice;
  6758. }
  6759. }
  6760. }
  6761. OJB_closeAndRemoveModal(settingMenu);
  6762. $settingBtns.prop("disabled", false).removeClass("open");
  6763. });
  6764. });
  6765. };
  6766.  
  6767. /**
  6768. * 初始化html2markdown转换器
  6769. */
  6770. async function initHTML2MarkDown() {
  6771. OJBetter.common.turndownService = new TurndownService({ bulletListMarker: '-' });
  6772.  
  6773. // 保留原始
  6774. OJBetter.common.turndownService.keep(['del']);
  6775.  
  6776. OJBetter.common.turndownService.addRule('removeByClass', {
  6777. filter: function (node) {
  6778. return node.classList.contains('html2md-panel') ||
  6779. node.classList.contains('div-btn-copy') ||
  6780. node.classList.contains('btn-copy') ||
  6781. node.classList.contains('overlay') ||
  6782. node.classList.contains('monaco-editor')
  6783. },
  6784. replacement: function () {
  6785. return '';
  6786. }
  6787. });
  6788.  
  6789. // inline math
  6790. OJBetter.common.turndownService.addRule('inline-math', {
  6791. filter: function (node, options) {
  6792. return node.tagName.toLowerCase() == "span" && node.className == "katex";
  6793. },
  6794. replacement: function (content, node) {
  6795. var latex = $(node).find('annotation').text();
  6796. latex = latex.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  6797. return "$" + latex + "$";
  6798. }
  6799. });
  6800.  
  6801. // block math
  6802. OJBetter.common.turndownService.addRule('block-math', {
  6803. filter: function (node, options) {
  6804. return node.tagName.toLowerCase() == "span" && node.className == "katex-display";
  6805. },
  6806. replacement: function (content, node) {
  6807. var latex = $(node).find('annotation').text();
  6808. latex = latex.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  6809. return "\n$$\n" + latex + "\n$$\n";
  6810. }
  6811. });
  6812.  
  6813. // pre
  6814. OJBetter.common.turndownService.addRule('pre', {
  6815. filter: function (node, options) {
  6816. return node.tagName.toLowerCase() == "pre";
  6817. },
  6818. replacement: function (content, node) {
  6819. if (!node.classList.contains('source-code-for-copy') && !node.classList.contains('prettyprint')) {
  6820. return "```\n" + content + "```\n";
  6821. } else {
  6822. return "";
  6823. }
  6824. }
  6825. });
  6826.  
  6827. // bordertable
  6828. OJBetter.common.turndownService.addRule('bordertable', {
  6829. filter: 'table',
  6830. replacement: function (content, node) {
  6831. if (node.classList.contains('table')) {
  6832. var output = [],
  6833. thead = '',
  6834. trs = node.querySelectorAll('tr');
  6835. if (trs.length > 0) {
  6836. var ths = trs[0].querySelectorAll('th, td');
  6837. if (ths.length > 0) {
  6838. thead = '| ' + Array.from(ths).map(th => OJBetter.common.turndownService.turndown(th.innerHTML.trim())).join(' | ') + ' |\n'
  6839. thead += '| ' + Array.from(ths).map(() => ' --- ').join('|') + ' |\n';
  6840. }
  6841. }
  6842. var rows = node.querySelectorAll('tr');
  6843. Array.from(rows).forEach(function (row, i) {
  6844. if (i > 0) {
  6845. var cells = row.querySelectorAll('td,th');
  6846. var trow = '| ' + Array.from(cells).map(cell => OJBetter.common.turndownService.turndown(cell.innerHTML.trim())).join(' | ') + ' |';
  6847. output.push(trow);
  6848. }
  6849. });
  6850. return thead + output.join('\n');
  6851. } else {
  6852. return content;
  6853. }
  6854. }
  6855. });
  6856. };
  6857.  
  6858. /**
  6859. * 任务队列
  6860. */
  6861. class TaskQueue {
  6862. constructor() {
  6863. this.taskQueues = {};
  6864. this.isProcessing = {}; // 处理状态
  6865. this.delays = {}; // 等待时间(毫秒)
  6866. }
  6867.  
  6868. getDelay(type) {
  6869. if (type === 'openai') {
  6870. return 0;
  6871. } else {
  6872. return OJBetter.translation.waitTime;
  6873. }
  6874. }
  6875.  
  6876. /**
  6877. * 添加任务
  6878. * @param {string} type 任务类型
  6879. * @param {function} fn 任务函数
  6880. * @param {boolean} isNonQueueTask 是否为非队列任务
  6881. */
  6882. addTask(type, fn, isNonQueueTask = false) {
  6883. if (!this.taskQueues[type]) {
  6884. this.taskQueues[type] = [];
  6885. }
  6886.  
  6887. if (isNonQueueTask) {
  6888. fn();
  6889. } else {
  6890. this.taskQueues[type].push(fn);
  6891.  
  6892. if (!this.isProcessing[type]) {
  6893. this.processQueue(type);
  6894. }
  6895. }
  6896. }
  6897.  
  6898. async processQueue(type) {
  6899. this.isProcessing[type] = true;
  6900.  
  6901. while (this.taskQueues[type].length > 0) {
  6902. const task = this.taskQueues[type].shift();
  6903. await task();
  6904.  
  6905. if (this.taskQueues[type].length > 0) {
  6906. await this.wait(this.getDelay(type));
  6907. }
  6908. }
  6909.  
  6910. this.isProcessing[type] = false;
  6911. }
  6912.  
  6913. wait(delay) {
  6914. return new Promise(resolve => {
  6915. setTimeout(resolve, delay);
  6916. });
  6917. }
  6918. }
  6919.  
  6920. /**
  6921. * 检测文本是否可能为代码片段
  6922. * @param {string} text 待检测的文本
  6923. * @returns {boolean} 是否可能为代码片段
  6924. */
  6925. /**
  6926. * 检测为空文本
  6927. * @param {string} text 待检测的文本
  6928. * @returns {boolean} 是否为空文本
  6929. */
  6930. const isEmptyText = text => text.trim() === '';
  6931.  
  6932. /**
  6933. * 加载按钮相关函数
  6934. */
  6935. async function initButtonFunc() {
  6936. // 鼠标悬浮时为目标元素区域添加一个覆盖层
  6937. $.fn.addHoverOverlay = function (target) {
  6938. let position = $(target).css('position');
  6939. let display = $(target).css('display');
  6940.  
  6941. this.hover(() => {
  6942. $(target)
  6943. .addClass('overlay')
  6944. .css('position', 'relative');
  6945. if (display == "inline" || display == "contents") {
  6946. $(target).css('display', 'block');
  6947. }
  6948. }, () => {
  6949. $(target)
  6950. .removeClass('overlay')
  6951. .css('position', position);
  6952. if (display == "inline" || display == "contents") {
  6953. $(target).css('display', display);
  6954. }
  6955. })
  6956. }
  6957.  
  6958. /**
  6959. * 为按钮设置图标
  6960. * @param {string} icon 图标
  6961. * @returns {JQuery<HTMLElement>} 按钮
  6962. */
  6963. $.fn.setButtonIcon = function (icon) {
  6964. let i = this.find("i");
  6965. if (i.length != 0 && i.hasClass("iconfont")) {
  6966. i.html(icon);
  6967. } else {
  6968. i = OJB_safeCreateJQElement(`<i>${icon}</i>`);
  6969. this.prepend(i);
  6970. }
  6971. return this;
  6972. }
  6973.  
  6974. /**
  6975. * 设置按钮为加载等待状态
  6976. */
  6977. $.fn.setButtonLoading = function () {
  6978. this.addClass("loading");
  6979. this.prop("disabled", true);
  6980. return this;
  6981. }
  6982.  
  6983. /**
  6984. * 解除按钮的加载等待状态
  6985. */
  6986. $.fn.setButtonLoaded = function () {
  6987. this.removeClass("loading");
  6988. this.prop("disabled", false);
  6989. return this;
  6990. }
  6991.  
  6992. /**
  6993. * 为按钮设置popover提示文本
  6994. * @param {string} text 文本
  6995. * @returns {JQuery<HTMLElement>} 按钮
  6996. */
  6997. $.fn.setButtonPopover = function (text) {
  6998. // find if has popover_content class element
  6999. let popover_content = this.find(".popover_content");
  7000. if (popover_content.length != 0) {
  7001. popover_content.text(text);
  7002. } else {
  7003. popover_content = OJB_safeCreateJQElement(`<span class="popover_content">${text}</span>`);
  7004. this.append(popover_content);
  7005. }
  7006. return this;
  7007. }
  7008.  
  7009. /**
  7010. * 获取MarkDown
  7011. * @returns {string} MarkDown
  7012. */
  7013. $.fn.getMarkdown = function () {
  7014. const markdown = this.data('markdown');
  7015. if (markdown === undefined) {
  7016. const htmlContent = this.html();
  7017. const newMarkdown = OJBetter.common.turndownService.turndown(htmlContent);
  7018. this.data('markdown', newMarkdown);
  7019. return newMarkdown;
  7020. }
  7021. return markdown;
  7022. }
  7023.  
  7024. // 设置按钮状态
  7025. $.fn.setButtonState = function (state, popoverText = null, disabled = false) {
  7026. this.data('buttonState', state)
  7027. .prop('disabled', disabled)
  7028. .css('cursor', disabled ? 'not-allowed' : 'pointer')
  7029. .removeClass('running success enabled error loading redo');
  7030. if (popoverText) this.setButtonPopover(popoverText);
  7031.  
  7032. if (state !== 'initial') this.addClass(state);
  7033. return this;
  7034. };
  7035.  
  7036. // 为按钮添加鼠标悬浮重试
  7037. $.fn.setHoverRedo = function () {
  7038. this.hover(() => {
  7039. prevState = this.getButtonState();
  7040. if (prevState !== "normal" && prevState !== "running") {
  7041. this.setButtonState('redo');
  7042. }
  7043. }, () => {
  7044. const currentState = this.getButtonState();
  7045. if (prevState !== "normal" && ["normal", "redo"].includes(currentState)) {
  7046. this.setButtonState(prevState);
  7047. prevState = null;
  7048. }
  7049. });
  7050. };
  7051.  
  7052. // 获取按钮状态
  7053. $.fn.getButtonState = function () {
  7054. return this.data('buttonState') || 'normal';
  7055. };
  7056.  
  7057. // 设置翻译按钮状态
  7058. $.fn.setTransButtonState = function (state, text = null) {
  7059. const popoverText = text || i18next.t(`trans.${state}`, { ns: 'button' });
  7060. const disabled = state === 'running' || state === 'loading';
  7061. this.setButtonState(state, popoverText, disabled);
  7062. return this;
  7063. };
  7064.  
  7065. // 存翻译结果
  7066. $.fn.pushResultToTransButton = function (result) {
  7067. let resultStack = this.data('resultStack');
  7068. if (!resultStack) resultStack = [];
  7069. resultStack.push(result);
  7070. this.data('resultStack', resultStack);
  7071. }
  7072.  
  7073. // 获取翻译结果
  7074. $.fn.getResultFromTransButton = function () {
  7075. return this.data('resultStack');
  7076. }
  7077.  
  7078. // 标记为不自动翻译
  7079. $.fn.setNotAutoTranslate = function () {
  7080. this.data('notAutoTranslate', true);
  7081. }
  7082.  
  7083. // 获取是否为不自动翻译
  7084. $.fn.getNotAutoTranslate = function () {
  7085. return this.data('notAutoTranslate');
  7086. }
  7087.  
  7088. // 判断是否已经翻译
  7089. $.fn.IsTranslated = function () {
  7090. if (this.hasAttr('translated')) {
  7091. return true;
  7092. } else {
  7093. return false;
  7094. }
  7095. }
  7096.  
  7097. // 判断是否为评论区按钮
  7098. $.fn.IsCommentButton = function () {
  7099. let isCommentButton = this.data('isCommentButton');
  7100. if (isCommentButton == undefined) {
  7101. this.parents('.comments').length > 0 ? isCommentButton = true : isCommentButton = false;
  7102. this.data('isCommentButton', isCommentButton);
  7103. }
  7104. return isCommentButton;
  7105. }
  7106.  
  7107. // 按钮点击效果
  7108. $(document).on('mousedown', '.ojb_btn', function () {
  7109. $(this).addClass('active').on('animationend', () => $(this).removeClass('active'));
  7110. });
  7111. }
  7112.  
  7113. /**
  7114. * 添加题目markdown转换/复制/翻译按钮面板
  7115. * @param {HTMLElement} element 需要添加按钮面板的元素
  7116. * @param {string} suffix 按钮面板id后缀
  7117. * @param {string} type 按钮面板添加位置
  7118. * @param {boolean} is_simple 是否是简单模式
  7119. * @returns {object} 返回按钮面板元素
  7120. */
  7121. function addButtonPanel(element, suffix, type, is_simple = false) {
  7122. let text;
  7123. if (OJBetter.translation.comment.transMode == "1") text = i18next.t('trans.segment', { ns: 'button' });
  7124. else if (OJBetter.translation.comment.transMode == "2") text = i18next.t('trans.select', { ns: 'button' });
  7125. else text = i18next.t('trans.normal', { ns: 'button' });
  7126.  
  7127. let panel = OJB_safeCreateJQElement(`<div class='html2md-panel input-output-copier ${is_simple ? 'is_simple' : ''}'></div>`);
  7128. let viewButton = OJB_safeCreateJQElement(`
  7129. <button class='ojb_btn ojb_btn_popover top' id='html2md-view${suffix}'>
  7130. <i class="iconfont">&#xe7e5;</i>
  7131. <span class="popover_content">${i18next.t('md.normal', { ns: 'button' })}</span>
  7132. </button>`);
  7133. let copyButton = OJB_safeCreateJQElement(`
  7134. <button class='ojb_btn ojb_btn_popover top' id='html2md-cb${suffix}'>
  7135. <i class="iconfont">&#xe608;</i>
  7136. <span class="popover_content">${i18next.t('copy.normal', { ns: 'button' })}</span>
  7137. </button>`);
  7138. let translateButton = OJB_safeCreateJQElement(`
  7139. <button class='ojb_btn translateButton ojb_btn_popover top' id='translateButton${suffix}'>
  7140. <i class="iconfont">&#xe6be;</i>
  7141. <span class="popover_content">${text}</span>
  7142. </button>`);
  7143. if (!is_simple) panel.append(viewButton);
  7144. if (!is_simple) panel.append(copyButton);
  7145. panel.append(translateButton);
  7146. if (type === "this_level") {
  7147. $(element).before(panel);
  7148. } else if (type === "child_level") {
  7149. $(element).prepend(panel);
  7150. }
  7151.  
  7152. return {
  7153. panel: panel,
  7154. viewButton: viewButton,
  7155. copyButton: copyButton,
  7156. translateButton: translateButton
  7157. }
  7158. }
  7159.  
  7160. /**
  7161. * 添加MD视图按钮
  7162. * @param {JQuery<HTMLElement>} button 按钮
  7163. * @param {JQuery<HTMLElement>} element 目标元素
  7164. * @param {string} suffix id后缀
  7165. * @param {string} type 类型
  7166. * @returns {void}
  7167. */
  7168. async function addButtonWithHTML2MD(button, element, suffix, type) {
  7169. /**
  7170. * 改变按钮状态
  7171. * @param {string} state 状态
  7172. */
  7173. function changeButtonState(state) {
  7174. if (state == "loading") {
  7175. button.setButtonLoading();
  7176. button.setButtonPopover(i18next.t('state.waitMathJax', { ns: 'button' }));
  7177. } else if (state == "loaded") {
  7178. button.setButtonLoaded();
  7179. button.setButtonPopover(i18next.t('md.normal', { ns: 'button' }));
  7180. } else if (state == "normal") {
  7181. button.removeClass("enabled");
  7182. button.setButtonPopover(i18next.t('md.normal', { ns: 'button' }));
  7183. } else if (state == "mdView") {
  7184. button.addClass("enabled");
  7185. button.setButtonPopover(i18next.t('md.reduction', { ns: 'button' }));
  7186. } else if (state == "disabled") {
  7187. button.prop("disabled", true);
  7188. button.setButtonPopover(i18next.t('md.disabled', { ns: 'button' }));
  7189. }
  7190. }
  7191.  
  7192. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7193. changeButtonState("disabled");
  7194. return;
  7195. } else {
  7196. changeButtonState("loading");
  7197. await waitForMathJaxIdle();
  7198. changeButtonState("loaded");
  7199. }
  7200.  
  7201. button.click(OJB_debounce(function () {
  7202. var target = $(element).get(0);
  7203.  
  7204. /**
  7205. * 检查是否是MarkDown视图
  7206. * @returns {boolean} 是否是MarkDown视图
  7207. */
  7208. function checkViewmd() {
  7209. if ($(element).attr("viewmd") === "true") {
  7210. return true;
  7211. } else {
  7212. return false;
  7213. }
  7214. }
  7215.  
  7216. /**
  7217. * 设置是否是MarkDown视图
  7218. * @param {boolean} value 是否是MarkDown视图
  7219. * @returns {void}
  7220. */
  7221. function setViewmd(value) {
  7222. $(element).attr("viewmd", value);
  7223. if (value) {
  7224. changeButtonState("mdView");
  7225. } else {
  7226. changeButtonState("normal");
  7227. }
  7228. }
  7229.  
  7230. if (checkViewmd()) {
  7231. setViewmd(false);
  7232. $(element).next(".mdViewContent").remove();
  7233. $(element).show();
  7234. } else {
  7235. setViewmd(true);
  7236. var markdown = $(element).getMarkdown();
  7237. var mdViewContent = OJB_safeCreateJQElement(`<span class="mdViewContent" style="width:auto; height:auto;">${markdown}</span>`);
  7238. $(element).after(mdViewContent);
  7239. $(element).hide();
  7240. }
  7241. }));
  7242.  
  7243. if (OJBetter.preference.hoverTargetAreaDisplay && !OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  7244. button.addHoverOverlay($(element));
  7245. }
  7246. }
  7247.  
  7248. /**
  7249. * 添加复制按钮
  7250. * @param {JQuery<HTMLElement>} button 按钮
  7251. * @param {JQuery<HTMLElement>} element 目标元素
  7252. * @param {string} suffix 后缀
  7253. * @param {string} type 类型
  7254. */
  7255. async function addButtonWithCopy(button, element, suffix, type) {
  7256. /**
  7257. * 改变按钮状态
  7258. * @param {string} state 状态
  7259. */
  7260. function changeButtonState(state) {
  7261. if (state == "loading") {
  7262. button.setButtonLoading();
  7263. button.setButtonPopover(i18next.t('state.waitMathJax', { ns: 'button' }));
  7264. } else if (state == "loaded") {
  7265. button.setButtonLoaded();
  7266. button.setButtonPopover(i18next.t('copy.normal', { ns: 'button' }));
  7267. } else if (state == "normal") {
  7268. button.setButtonPopover(i18next.t('copy.normal', { ns: 'button' }));
  7269. } else if (state == "copied") {
  7270. button.setButtonPopover(i18next.t('copy.copied', { ns: 'button' }));
  7271. } else if (state == "disabled") {
  7272. button.prop("disabled", true);
  7273. button.setButtonPopover(i18next.t('copy.disabled', { ns: 'button' }));
  7274. }
  7275. }
  7276.  
  7277. // 等待MathJax队列完成
  7278. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7279. changeButtonState("disabled");
  7280. return;
  7281. } else {
  7282. changeButtonState("loading");
  7283. await waitForMathJaxIdle();
  7284. changeButtonState("loaded");
  7285. }
  7286.  
  7287. button.click(OJB_debounce(function () {
  7288. var target = $(element).get(0);
  7289.  
  7290. var markdown = $(element).getMarkdown();
  7291.  
  7292. GM_setClipboard(markdown);
  7293.  
  7294. $(this).addClass("success");
  7295. changeButtonState("copied");
  7296.  
  7297.  
  7298. // 更新复制按钮文本
  7299. setTimeout(() => {
  7300. $(this).removeClass("success");
  7301. changeButtonState("normal")
  7302. }, 2000);
  7303. }));
  7304.  
  7305. if (OJBetter.preference.hoverTargetAreaDisplay && !OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  7306. button.addHoverOverlay($(element));
  7307. }
  7308. }
  7309.  
  7310. /**
  7311. * 添加翻译按钮
  7312. * @param {JQuery<HTMLElement>} button 按钮
  7313. * @param {JQuery<HTMLElement>} element 目标元素
  7314. * @param {string} suffix 后缀
  7315. * @param {string} type 类型
  7316. * @param {boolean} is_comment 是否是评论
  7317. */
  7318. async function addButtonWithTranslation(button, element, suffix, type, is_comment = false) {
  7319. /**
  7320. * 添加可指定翻译服务的方法调用
  7321. * @param {string} translation 翻译服务
  7322. */
  7323. button.data("translatedItBy", function (translation) {
  7324. button.setTransButtonState('running', i18next.t('trans.wait', { ns: 'button' }));
  7325. OJBetter.common.taskQueue.addTask(translation, () => transTask(button, element, type, is_comment, translation), translation == 'openai');
  7326. });
  7327.  
  7328. // 等待MathJax队列完成
  7329. button.setButtonLoading();
  7330. await waitForMathJaxIdle();
  7331. button.setButtonLoaded();
  7332.  
  7333. // 标记目标文本区域不自动翻译
  7334. {
  7335. let text;
  7336. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7337. text = $(element).html();
  7338. } else {
  7339. text = $(element).getMarkdown();
  7340. }
  7341. let length = text.length;
  7342. if (length > OJBetter.translation.auto.shortTextLength || isEmptyText(text) || $(element).find('.spoiler').length > 0) {
  7343. button.setNotAutoTranslate();
  7344. }
  7345. // button.after(`<span>${length}</span>`); // 显示字符数
  7346. }
  7347.  
  7348. button.click(OJB_debounce(async function () {
  7349. // 重新翻译
  7350. let resultStack = $(this).getResultFromTransButton();
  7351. if (resultStack) {
  7352. let pElements = $(element).find("p.block_selected:not(li p), li.block_selected");
  7353. for (let item of resultStack) {
  7354. if (OJBetter.translation.retransAction == "0") {
  7355. // 选段翻译不直接移除旧结果
  7356. if (OJBetter.translation.comment.transMode == "2") {
  7357. // 只移除即将要翻译的段的结果
  7358. if (pElements.is(item.translateDiv.getDiv().prev())) {
  7359. item.translateDiv.close();
  7360. }
  7361. } else {
  7362. item.translateDiv.close();
  7363. $($(element)).find(".translate-problem-statement, .translate-problem-statement-panel").remove();
  7364. }
  7365. } else {
  7366. item.translateDiv.foldMainDiv();
  7367. }
  7368. }
  7369. }
  7370.  
  7371. // 翻译
  7372. button.setTransButtonState('running', i18next.t('trans.wait', { ns: 'button' }));
  7373. OJBetter.common.taskQueue.addTask(OJBetter.translation.choice, () => transTask(button, element, type, is_comment), OJBetter.translation.choice == 'openai');
  7374. }));
  7375.  
  7376. // 重新翻译提示
  7377. let prevState;
  7378. button.hover(() => {
  7379. prevState = button.getButtonState();
  7380. if (prevState !== "normal" && prevState !== "running") {
  7381. button.setTransButtonState('redo');
  7382. }
  7383. }, () => {
  7384. const currentState = button.getButtonState();
  7385. if (prevState !== "normal" && ["normal", "redo"].includes(currentState)) {
  7386. button.setTransButtonState(prevState);
  7387. prevState = null;
  7388. }
  7389. });
  7390.  
  7391. // 目标区域指示
  7392. if (OJBetter.preference.hoverTargetAreaDisplay) {
  7393. button.addHoverOverlay($(element));
  7394. }
  7395.  
  7396. // 翻译右键切换菜单
  7397. $(document).on('contextmenu', '#translateButton' + suffix, function (e) {
  7398. e.preventDefault();
  7399.  
  7400. // 是否为评论的翻译
  7401. let is_comment = button.IsCommentButton();
  7402.  
  7403. // 移除旧的
  7404. if (!$(e.target).closest('.OJBetter_contextmenu').length) {
  7405. $('.OJBetter_contextmenu').remove();
  7406. }
  7407.  
  7408. var menu = $('<div class="OJBetter_contextmenu"></div>');
  7409. var translations = [
  7410. { value: 'deepl', name: i18next.t('translation.options.services.deepl', { ns: 'settings' }) },
  7411. { value: 'iflyrec', name: i18next.t('translation.options.services.iflyrec', { ns: 'settings' }) },
  7412. { value: 'youdao', name: i18next.t('translation.options.services.youdao', { ns: 'settings' }) },
  7413. { value: 'google', name: i18next.t('translation.options.services.google', { ns: 'settings' }) },
  7414. { value: 'caiyun', name: i18next.t('translation.options.services.caiyun', { ns: 'settings' }) },
  7415. { value: 'openai', name: i18next.t('translation.options.services.openai.name', { ns: 'settings' }) }
  7416. ];
  7417.  
  7418. // Function to check if the service supports the target language
  7419. function supportsTargetLanguage(service, targetLang) {
  7420. return OJBetter.supportList.translationSupport[service] && OJBetter.supportList.translationSupport[service][targetLang] !== undefined;
  7421. }
  7422.  
  7423. if (is_comment) {
  7424. var label = OJB_safeCreateJQElement(`<label><input type="radio" name="translation" value="0">
  7425. <span class="OJBetter_contextmenu_label_text">
  7426. ${i18next.t('translation.preference.comment_translation_choice.services.follow', { ns: 'settings' })}
  7427. </span></label>`);
  7428. menu.append(label);
  7429. }
  7430. translations.forEach(function (translation) {
  7431. if (supportsTargetLanguage(translation.value, OJBetter.translation.targetLang)) {
  7432. var label = OJB_safeCreateJQElement(`<label><input type="radio" name="translation" value="${translation.value}">
  7433. <span class="OJBetter_contextmenu_label_text">${translation.name}</span></label>`);
  7434. menu.append(label);
  7435. }
  7436. });
  7437.  
  7438. // 初始化
  7439. if (is_comment) {
  7440. menu.find(`input[name="translation"][value="${OJBetter.translation.comment.choice}"]`).prop('checked', true);
  7441. } else {
  7442. menu.find(`input[name="translation"][value="${OJBetter.translation.choice}"]`).prop('checked', true);
  7443. }
  7444. menu.css({
  7445. top: e.pageY + 'px',
  7446. left: e.pageX + 'px'
  7447. }).appendTo('body');
  7448.  
  7449. $(document).one('change', 'input[name="translation"]', function () {
  7450. if (is_comment) {
  7451. OJBetter.translation.comment.choice = $('input[name="translation"]:checked').val();
  7452. GM_setValue("commentTranslationChoice", OJBetter.translation.comment.choice);
  7453. } else {
  7454. OJBetter.translation.choice = $('input[name="translation"]:checked').val();
  7455. GM_setValue("translation", OJBetter.translation.choice);
  7456. }
  7457. $('.OJBetter_contextmenu').remove();
  7458. });
  7459.  
  7460. // 点击区域外关闭菜单
  7461. function handleClick(event) {
  7462. if (!$(event.target).closest('.OJBetter_contextmenu').length) {
  7463. $('.OJBetter_contextmenu').remove();
  7464. $(document).off('change', 'input[name="translation"]');
  7465. } else {
  7466. $(document).one('click', handleClick);
  7467. }
  7468. }
  7469. $(document).one('click', handleClick);
  7470. });
  7471. }
  7472.  
  7473. /**
  7474. * 创建翻译任务
  7475. * @param {JQuery<HTMLElement>} button 按钮
  7476. * @param {HTMLElement} element 目标元素
  7477. * @param {string} type 类型
  7478. * @param {boolean} is_comment 是否是评论
  7479. * @param {string} overrideTrans 覆盖全局翻译服务设定
  7480. */
  7481. async function transTask(button, element, type, is_comment, overrideTrans) {
  7482. /** @type {HTMLElement} 目标元素 */
  7483. let target;
  7484. /**
  7485. * 错误计数数据结构
  7486. * @typedef {Object} count
  7487. * @property {number} errerNum 错误数量
  7488. * @property {number} skipNum 跳过数量
  7489. */
  7490. const count = {
  7491. errerNum: 0,
  7492. skipNum: 0
  7493. };
  7494. if (OJBetter.translation.comment.transMode == "1") {
  7495. // 分段翻译
  7496. let pElements = $(element).find("p:not(li p), li, .OJBetter_acmsguru");
  7497. for (let i = 0; i < pElements.length; i++) {
  7498. target = $(pElements[i]).eq(0).clone();
  7499. element_node = pElements[i];
  7500. await process(button, target, element_node, type, is_comment, count, overrideTrans);
  7501. }
  7502. } else if (OJBetter.translation.comment.transMode == "2") {
  7503. // 选段翻译
  7504. let pElements = $(element).find("p.block_selected:not(li p), li.block_selected, .OJBetter_acmsguru");
  7505. for (let i = 0; i < pElements.length; i++) {
  7506. target = $(pElements[i]).eq(0).clone();
  7507. element_node = pElements[i];
  7508. await process(button, target, element_node, type, is_comment, count, overrideTrans);
  7509. }
  7510. $(element).find("p.block_selected:not(li p), li.block_selected").removeClass('block_selected');
  7511. } else {
  7512. // 普通翻译
  7513. target = $(element).eq(0).clone();
  7514. if (type === "child_level") $(target).children(':first').remove();
  7515. element_node = $($(element)).get(0);
  7516. await process(button, target, element_node, type, is_comment, count, overrideTrans);
  7517. }
  7518.  
  7519. // 翻译完成
  7520. if (!count.errerNum && !count.skipNum) {
  7521. button.setTransButtonState('success');
  7522. }
  7523. }
  7524.  
  7525. /**
  7526. * 翻译处理
  7527. * @param {JQuery<HTMLElement>} button 按钮
  7528. * @param {HTMLElement} target 目标元素
  7529. * @param {HTMLElement} element_node 目标节点
  7530. * @param {string} type 类型
  7531. * @param {boolean} is_comment 是否是评论
  7532. * @param {string} overrideTrans 覆盖全局翻译服务设定
  7533. */
  7534. async function process(button, target, element_node, type, is_comment, count, overrideTrans) {
  7535. if (type === "child_level") {
  7536. let div = $("<div>");
  7537. $(element_node).append(div);
  7538. element_node = div.get(0);
  7539. }
  7540.  
  7541. //是否跳过折叠块
  7542. if ($(target).find('.spoiler').length > 0) {
  7543. const shouldSkip = await OJB_createDialog(
  7544. i18next.t('skipFold.title', { ns: 'dialog' }),
  7545. i18next.t('skipFold.content', { ns: 'dialog' }),
  7546. [
  7547. i18next.t('skipFold.buttons.0', { ns: 'dialog' }),
  7548. i18next.t('skipFold.buttons.1', { ns: 'dialog' })
  7549. ],
  7550. true
  7551. ); //跳过折叠块确认
  7552. if (shouldSkip) {
  7553. $(target).find('.spoiler').remove();
  7554. } else {
  7555. $(target).find('.html2md-panel').remove();
  7556. }
  7557. }
  7558.  
  7559. // 等待并获取结果
  7560. button.setTransButtonState('running');
  7561. const result = await blockProcessing(button, target, element_node, is_comment, overrideTrans);
  7562. button.pushResultToTransButton(result);
  7563.  
  7564. if (result.status == "error") count.errerNum += 1;
  7565. else if (result.status == "skip") count.skipNum += 1;
  7566. $(target).remove();
  7567. }
  7568.  
  7569. /**
  7570. * 块处理
  7571. * @param {JQuery<HTMLElement>} button
  7572. * @param {HTMLElement} target 目标元素
  7573. * @param {HTMLElement} element_node 目标节点
  7574. * @param {boolean} is_comment 是否是评论
  7575. * @param {string} overrideTrans 覆盖全局翻译服务设定
  7576. * @returns {TranslateResult} 翻译结果对象
  7577. */
  7578. async function blockProcessing(button, target, element_node, is_comment, overrideTrans) {
  7579. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7580. target.markdown = $(target).html();
  7581. } else if (!target.markdown) {
  7582. target.markdown = OJBetter.common.turndownService.turndown($(target).html());
  7583. }
  7584.  
  7585. const result = await translateProblemStatement(target.markdown, element_node, is_comment, overrideTrans);
  7586. if (result.status == "skip") {
  7587. button.setTransButtonState('error', i18next.t('trans.tooLong', { ns: 'button' }));
  7588. result.translateDiv.close();
  7589. } else if (result.status == "error" || !result.rawData.done) {
  7590. result.translateDiv.setError();
  7591. result.translateDiv.setRawData(result.rawData);
  7592. result.translateDiv.showDebugButton();
  7593. button.setTransButtonState('error', i18next.t('trans.error', { ns: 'button' }));
  7594. $(target).remove();
  7595. }
  7596. return result;
  7597. }
  7598.  
  7599. /**
  7600. * 选段翻译支持
  7601. */
  7602. async function multiChoiceTranslation() {
  7603. GM_addStyle(`
  7604. .topic .content #task-statement {
  7605. overflow: initial;
  7606. }
  7607. `);
  7608.  
  7609. $(document).on('click', 'p, li:not(:has(.comment)), .OJBetter_acmsguru', function (e) {
  7610. let $this = $(this);
  7611. e.stopPropagation();
  7612. if ($this.hasClass('block_selected')) {
  7613. $this.removeClass('block_selected');
  7614. // 移除对应的按钮
  7615. $('.OJBetter_MiniTranslateButton').remove("#translateButton_selected_" + $this.attr('OJBetter_p_id'));
  7616. } else {
  7617. let id = OJB_getRandomNumber(8);
  7618. $this.attr('OJBetter_p_id', id);
  7619. $this.addClass('block_selected');
  7620. // 添加按钮
  7621. let menu = OJB_safeCreateJQElement(`<div class="OJBetter_MiniTranslateButton" id='translateButton_selected_${id}'>${translateIcon}</div>`)
  7622. .css({
  7623. left: $($this).outerWidth(true) + $($this).position().left + 10 + 'px',
  7624. });
  7625. $this.before(menu);
  7626.  
  7627. $("#translateButton_selected_" + id).click(async function () {
  7628. // 处理旧的结果
  7629. if ($this.attr('translated')) {
  7630. let result = $this.data("resultData");
  7631. if (OJBetter.translation.retransAction == "0") {
  7632. result.translateDiv.close();
  7633. } else {
  7634. result.translateDiv.foldMainDiv();
  7635. }
  7636. }
  7637. // 翻译
  7638. let target = $this.eq(0).clone();
  7639. let result = await blockProcessing(OJBetter.translation.choice, target, $this.eq(0), $("#translateButton_selected_" + id), false);
  7640. $this.data("resultData", result);
  7641. $this.removeClass('block_selected');
  7642. // 移除对应的按钮
  7643. $('.OJBetter_MiniTranslateButton').remove("#translateButton_selected_" + id);
  7644. $this.attr('translated', '1'); // 标记已翻译
  7645. });
  7646. }
  7647. });
  7648. }
  7649.  
  7650. /**
  7651. * 添加MD/复制/翻译按钮
  7652. */
  7653. async function addConversionButton() {
  7654. // TODO 7
  7655. let promises = []; // 用于收集所有的 Promise
  7656.  
  7657. // 基本添加
  7658. if (!OJBetter.typeOfPage.is_homepage) {
  7659. $('section').each((i, e) => {
  7660. let id = "_" + OJB_getRandomNumber(8);
  7661. let panel = addButtonPanel(e, id, "this_level");
  7662. promises.push(addButtonWithHTML2MD(panel.viewButton, e, id, "this_level"));
  7663. promises.push(addButtonWithCopy(panel.copyButton, e, id, "this_level"));
  7664. promises.push(addButtonWithTranslation(panel.translateButton, e, id, "this_level"));
  7665. });
  7666. }
  7667.  
  7668. // 添加按钮到题解部分
  7669. if (OJBetter.typeOfPage.isEditorial) {
  7670. let contestNavTabs = $("#contest-nav-tabs");
  7671. let nextElement = contestNavTabs.next();
  7672. let id = "_editorial_1_" + OJB_getRandomNumber(8);
  7673. let panel = addButtonPanel(nextElement, id, "this_level");
  7674. panel.panel.css('width', '100%'); // 加个样式,不然不显示
  7675. promises.push(addButtonWithHTML2MD(panel.viewButton, nextElement, id, "this_level"));
  7676. promises.push(addButtonWithCopy(panel.copyButton, nextElement, id, "this_level"));
  7677. promises.push(addButtonWithTranslation(panel.translateButton, nextElement, id, "this_level"));
  7678. }
  7679.  
  7680. // 添加按钮到折叠块部分
  7681. $('details').each((i, e) => {
  7682. // 自定义测试样例折叠块不添加
  7683. if ($(e).attr('id') !== "customTestBlock") {
  7684. let id = "_details_" + OJB_getRandomNumber(8);
  7685. let panel = addButtonPanel(e, id, "child_level");
  7686. promises.push(addButtonWithHTML2MD(panel.viewButton, e, id, "child_level"));
  7687. promises.push(addButtonWithCopy(panel.copyButton, e, id, "child_level"));
  7688. promises.push(addButtonWithTranslation(panel.translateButton, e, id, "child_level"));
  7689. }
  7690. });
  7691.  
  7692. // 添加到contest-statement部分
  7693. $('#contest-statement').each((i, e) => {
  7694. let id = "_contest-statement_" + OJB_getRandomNumber(8);
  7695. let panel = addButtonPanel(e, id, "this_level");
  7696. promises.push(addButtonWithHTML2MD(panel.viewButton, e, id, "this_level"));
  7697. promises.push(addButtonWithCopy(panel.copyButton, e, id, "this_level"));
  7698. promises.push(addButtonWithTranslation(panel.translateButton, e, id, "this_level"));
  7699. });
  7700.  
  7701. // 添加到blog-post部分
  7702. $('.blog-post').each((i, e) => {
  7703. let id = "_blog-post_" + OJB_getRandomNumber(8);
  7704. let panel = addButtonPanel(e, id, "this_level");
  7705. promises.push(addButtonWithHTML2MD(panel.viewButton, e, id, "this_level"));
  7706. promises.push(addButtonWithCopy(panel.copyButton, e, id, "this_level"));
  7707. promises.push(addButtonWithTranslation(panel.translateButton, e, id, "this_level"));
  7708. });
  7709.  
  7710. return Promise.all(promises).catch(error => {
  7711. console.error("One or more of the Add Button operations failed: ", error);
  7712. });
  7713. };
  7714.  
  7715. /**
  7716. * 等待LaTeX渲染队列全部完成
  7717. * @returns {Promise} 完成渲染
  7718. */
  7719. function waitForMathJaxIdle() {
  7720. return true;
  7721. // return new Promise((resolve, reject) => {
  7722. // // 检查MathJax对象是否存在
  7723. // const checkMathJaxExists = () => {
  7724. // if (typeof MathJax === 'undefined') {
  7725. // // 如果MathJax不存在,稍后再次检查
  7726. // OJB_delay(100).then(checkMathJaxExists);
  7727. // } else {
  7728. // // MathJax存在,开始监视渲染队列
  7729. // startMonitoringQueue();
  7730. // }
  7731. // };
  7732.  
  7733. // // 开始监视MathJax渲染队列
  7734. // const startMonitoringQueue = () => {
  7735. // const intervalId = setInterval(() => {
  7736. // const queue = MathJax.Hub.queue;
  7737. // if (queue.pending === 0 && queue.running === 0) {
  7738. // clearInterval(intervalId);
  7739. // resolve();
  7740. // }
  7741. // }, 100);
  7742. // };
  7743.  
  7744. // // 开始检查MathJax对象
  7745. // checkMathJaxExists();
  7746. // });
  7747. }
  7748.  
  7749. /**
  7750. * 翻译结果面板
  7751. */
  7752. class TranslateDiv {
  7753. /**
  7754. * 构造函数
  7755. * @param {string} id 指定翻译框的id
  7756. */
  7757. constructor(id) {
  7758. this.id = id;
  7759. this.div = $('<div>').attr('id', id).addClass('translateDiv bounce-in');
  7760. if (!OJBetter.typeOfPage.is_completeProblemset) {
  7761. this.div.addClass('input-output-copier');
  7762. }
  7763. this.panelDiv = $('<div>').addClass('translate-problem-statement-panel');
  7764. this.div.append(this.panelDiv);
  7765.  
  7766. // 主要信息
  7767. this.mainDiv = $('<div>').addClass('translate-problem-statement');
  7768. this.span = $('<span>');
  7769. this.mainDiv.append(this.span);
  7770. this.div.append(this.mainDiv);
  7771. this.mainDivState = {
  7772. current: 'transHTML',
  7773. transHTML: '',
  7774. rawDataHTML: ''
  7775. };
  7776.  
  7777. // 顶栏信息
  7778. this.topText = $('<div>').addClass('topText');
  7779. this.panelDiv.append(this.topText);
  7780.  
  7781. // 右侧
  7782. this.rightDiv = $('<div>').css('display', 'flex');
  7783. this.panelDiv.append(this.rightDiv);
  7784. this.debugButton = OJB_safeCreateJQElement(`
  7785. <button class='ojb_btn ojb_btn_popover top'>
  7786. <i class="iconfont">&#xe641;</i>
  7787. <span class="popover_content">${i18next.t('rawData.normal', { ns: 'button' })}</span>
  7788. </button>`).hide();
  7789. this.rightDiv.append(this.debugButton);
  7790. this.queryBalanceButton = OJB_safeCreateJQElement(`
  7791. <button class='ojb_btn ojb_btn_popover top'>
  7792. <i class="iconfont">&#xe6ae;</i>
  7793. <span class="popover_content">${i18next.t('queryBalance.normal', { ns: 'button' })}</span>
  7794. </button>`).hide();
  7795. this.rightDiv.append(this.queryBalanceButton);
  7796. this.copyButton = OJB_safeCreateJQElement(`
  7797. <button class='ojb_btn ojb_btn_popover top'>
  7798. <i class="iconfont">&#xe608;</i>
  7799. <span class="popover_content">${i18next.t('copy.normal', { ns: 'button' })}</span>
  7800. </button>`);
  7801. this.rightDiv.append(this.copyButton);
  7802. this.upButton = OJB_safeCreateJQElement(`
  7803. <button class='ojb_btn ojb_btn_popover top'>
  7804. <i class="iconfont">&#xe601;</i>
  7805. <span class="popover_content">${i18next.t('fold.normal', { ns: 'button' })}</span>
  7806. </button>`);
  7807. this.rightDiv.append(this.upButton);
  7808. this.closeButton = OJB_safeCreateJQElement(`
  7809. <button class='ojb_btn ojb_btn_popover top'>
  7810. <i class="iconfont">&#xe614;</i>
  7811. <span class="popover_content">${i18next.t('close.normal', { ns: 'button' })}</span>
  7812. </button>`);
  7813. this.rightDiv.append(this.closeButton);
  7814. }
  7815.  
  7816. /**
  7817. * 获取翻译框
  7818. * @returns {JQuery<HTMLElement>} 返回翻译框
  7819. */
  7820. getDiv() {
  7821. return this.div;
  7822. }
  7823.  
  7824. /**
  7825. * 设置翻译框顶部的文本
  7826. * @param {string} text 翻译框顶部的文本
  7827. */
  7828. setTopText(text) {
  7829. this.div.attr("data-topText", text);
  7830. this.topText.text(text);
  7831. }
  7832.  
  7833. /**
  7834. * 获取翻译框顶部的文本
  7835. * @returns {string} 返回翻译框顶部的文本
  7836. */
  7837. getTopText() {
  7838. return this.topText.text();
  7839. }
  7840.  
  7841. /**
  7842. * 渲染一个元素内的LaTeX公式
  7843. * @param {*} element
  7844. */
  7845. renderLaTeX(element) {
  7846. const latexRenderOptions = {
  7847. delimiters: [
  7848. { left: "$$", right: "$$", display: true },
  7849. { left: "\$$", right: "\\$$", display: true },
  7850. { left: "$", right: "$", display: false },
  7851. { left: "\$$", right: "\\$$", display: false }
  7852. ]
  7853. };
  7854.  
  7855. if (typeof renderMathInElement === 'function') {
  7856. renderMathInElement(element, latexRenderOptions);
  7857. }
  7858. }
  7859.  
  7860. /**
  7861. * 更新翻译框内容
  7862. * @param {string} text 文本内容
  7863. * @param {boolean} is_escapeHTML 是否转义HTML标签,为true则HTML标签将作为普通文本处理,默认为true
  7864. * @param {boolean} is_renderLaTeX 是否渲染LaTeX,为true则会渲染LaTeX,默认为true
  7865. */
  7866. updateTranslateDiv(text, is_escapeHTML = true, is_renderLaTeX = true,) {
  7867. // 渲染MarkDown
  7868. let md = window.markdownit({
  7869. html: !is_escapeHTML,
  7870. });
  7871. if (!text) text = "";
  7872. let html = md.render(text);
  7873. this.mainDiv.html(html);
  7874.  
  7875. // 渲染Latex
  7876. if (is_renderLaTeX) {
  7877. // MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.mainDiv.get(0)]);
  7878. this.renderLaTeX(this.mainDiv.get(0));
  7879. }
  7880. // 渲染代码块中的公式 (AtCoder)
  7881. this.mainDiv.find('pre code').each((index, element) => {
  7882. const codeText = $(element).text();
  7883. const latexPattern = /\$\$(\\.|[^\$])*?\$\$|\$(\\.|[^\$])*?\$/;
  7884. if (latexPattern.test(codeText)) {
  7885. this.renderLaTeX(element);
  7886. }
  7887. });
  7888. }
  7889.  
  7890. /**
  7891. * 关闭元素
  7892. */
  7893. close() {
  7894. this.closeButton.click();
  7895. }
  7896.  
  7897. /**
  7898. * 注册收起按钮事件
  7899. */
  7900. registerUpButtonEvent() {
  7901. this.upButton.on("click", () => {
  7902. // 如果没有reverse类,说明是展开状态
  7903. if (!this.upButton.hasClass("reverse")) {
  7904. // 执行收起操作
  7905. this.upButton.addClass("reverse");
  7906. this.upButton.setButtonState('initial', i18next.t('fold.unfold', { ns: 'button' }));
  7907. OJB_toggleCollapseExpand(this.mainDiv.get(0));
  7908. } else {
  7909. // 执行展开操作
  7910. this.upButton.removeClass("reverse");
  7911. this.upButton.setButtonState('initial', i18next.t('fold.normal', { ns: 'button' }));
  7912. OJB_toggleCollapseExpand(this.mainDiv.get(0));
  7913. }
  7914. });
  7915. }
  7916.  
  7917. /**
  7918. * 注册关闭按钮事件
  7919. */
  7920. registerCloseButtonEvent() {
  7921. this.closeButton.on("click", () => {
  7922. $(this.div).remove();
  7923. $(this.panelDiv).remove();
  7924. if (OJBetter.typeOfPage.is_problem && OJBetter.translation.memory.enabled) {
  7925. OJBetter.translation.memory.ttTree.rmTransResultMap(this.id); // 移除ttTree中的数据
  7926. OJBetter.translation.memory.ttTree.refreshNode("#task-statement");
  7927. updateTransDBData(OJBetter.translation.memory.ttTree.getNodeData(), OJBetter.translation.memory.ttTree.getTransResultMap()); // 更新DB中的数据
  7928. }
  7929. });
  7930. }
  7931.  
  7932. /**
  7933. * 注册复制按钮事件
  7934. * @param {string} text 复制的文本
  7935. */
  7936. registerCopyButtonEvent(text) {
  7937. this.copyButton.on("click", () => {
  7938. GM_setClipboard(text);
  7939. this.copyButton.setButtonState('success', i18next.t('copy.copied', { ns: 'button' }));
  7940. // 复制提示
  7941. setTimeout(() => {
  7942. this.copyButton.setButtonState('initial', i18next.t('copy.normal', { ns: 'button' }));
  7943. }, 2000);
  7944. });
  7945. }
  7946.  
  7947. /**
  7948. * 禁用复制按钮
  7949. */
  7950. disableCopyButton() {
  7951. this.copyButton.css({ 'fill': '#ccc' });
  7952. this.copyButton.off("click");
  7953. }
  7954.  
  7955. /**
  7956. * 设置面板为error状态
  7957. */
  7958. setError() {
  7959. this.div.addClass('error');
  7960. this.panelDiv.addClass('error');
  7961. this.mainDiv.addClass('error');
  7962. }
  7963.  
  7964. /**
  7965. * 设置原始数据数据
  7966. * @param {Object} Object 原始数据
  7967. */
  7968. setRawData(Object) {
  7969. this.mainDivState.rawDataHTML = $("<pre>").text(JSON.stringify(Object, null, 4)).get(0);
  7970. if (this.mainDivState.current === 'rawDataHTML') {
  7971. this.renderMainDiv();
  7972. }
  7973. }
  7974.  
  7975. /**
  7976. * 切换结果面板与原始数据面板
  7977. */
  7978. switchMainDiv() {
  7979. // 在切换之前,保存当前内容的状态
  7980. this.mainDivState[this.mainDivState.current] = this.mainDiv.html();
  7981. // 切换当前状态
  7982. this.debugButton.setButtonState(this.mainDivState.current === 'transHTML' ? 'enabled' : 'initial');
  7983. this.mainDivState.current = this.mainDivState.current === 'transHTML' ? 'rawDataHTML' : 'transHTML';
  7984. // 渲染新的当前状态
  7985. this.renderMainDiv();
  7986. }
  7987.  
  7988. // 渲染当前内容到 mainDiv
  7989. renderMainDiv() {
  7990. requestAnimationFrame(() => {
  7991. this.mainDiv.html(this.mainDivState[this.mainDivState.current]);
  7992. });
  7993. }
  7994.  
  7995. /**
  7996. * 注册debug按钮事件
  7997. */
  7998. registerDebugButtonEvent() {
  7999. this.debugButton.on("click", () => {
  8000. this.switchMainDiv();
  8001. });
  8002. }
  8003.  
  8004. /**
  8005. * 显示debug按钮
  8006. */
  8007. showDebugButton() {
  8008. this.debugButton.show();
  8009. this.registerDebugButtonEvent();
  8010. }
  8011.  
  8012. /**
  8013. * 注册查询余额按钮事件
  8014. * @param {function} callback 查询回调函数
  8015. */
  8016. registerQueryBalanceButtonEvent(callback) {
  8017. this.queryBalanceButton.on("click", async () => {
  8018. this.queryBalanceButton.setButtonState('loading', i18next.t('queryBalance.loading', { ns: 'button' }));
  8019. try {
  8020. const balance = await callback();
  8021. this.queryBalanceButton.setButtonState('success', `${i18next.t('queryBalance.success', { ns: 'button' })} ${balance}`);
  8022. } catch (error) {
  8023. this.queryBalanceButton.setButtonState('error', `${i18next.t('queryBalance.error', { ns: 'button' })} ${error.message}`);
  8024. }
  8025. });
  8026. }
  8027.  
  8028. /**
  8029. * 显示余额查询按钮
  8030. * @param {string} server 服务名称
  8031. */
  8032. showQueryBalanceButton(server) {
  8033. if (server == 'deepl') {
  8034. const quotaConfig = OJBetter.deepl.config.quota;
  8035. if (quotaConfig.url && quotaConfig.surplus && quotaConfig.header) {
  8036. this.queryBalanceButton.show();
  8037. this.registerQueryBalanceButtonEvent(() => {
  8038. return queryServerBalance(OJBetter.deepl.config.quota);
  8039. });
  8040. }
  8041. } else if (server == 'openai') {
  8042. const quotaConfig = OJBetter.chatgpt.config.quota;
  8043. if (quotaConfig.url && quotaConfig.surplus && quotaConfig.header) {
  8044. this.queryBalanceButton.show();
  8045. this.registerQueryBalanceButtonEvent(() => {
  8046. return queryServerBalance(OJBetter.chatgpt.config.quota);
  8047. });
  8048. }
  8049. }
  8050. }
  8051. }
  8052.  
  8053. // 元素关系树
  8054. class ElementsTree {
  8055. constructor(elements) {
  8056. this.node = [];
  8057. this.transResultMap = {};
  8058. this.index = 0;
  8059. // this.tagNames = ["DIV", "P", "UL", "LI"]
  8060. this.tagNames = ["DIV", "P", "UL", "LI", "SECTION", "SPAN"]
  8061. this.init($(elements));
  8062. }
  8063.  
  8064. // Iterate through all elements, because there may be multiple ttypography
  8065. init(elements) {
  8066. elements.each((i, e) => {
  8067. this.node.push({}); // add one element
  8068. this.index = 0; // reset index
  8069. this.create(i, $(e));
  8070. });
  8071. }
  8072.  
  8073. // 刷新关系树
  8074. refreshNode(elements) {
  8075. this.node = [];
  8076. this.index = 0;
  8077. this.init($(elements));
  8078. }
  8079.  
  8080. // 创建节点间的关系树
  8081. create(i_, element) {
  8082. var prev = null;
  8083. var node = this.node[i_];
  8084. element.children().each((i, e) => {
  8085. // only add element with tagNames
  8086. if (this.tagNames.includes($(e).prop("tagName"))) {
  8087. prev = this.addNode(i_, prev, e);
  8088. }
  8089. // recursively child element
  8090. if ($(e).children().length > 0 && prev !== null) {
  8091. node[prev].firstChild = this.index;
  8092. this.create(i_, $(e));
  8093. }
  8094. });
  8095. }
  8096.  
  8097. // 向树中添加一个节点
  8098. addNode(i_, prev, e) {
  8099. let node = this.node[i_];
  8100. node[this.index] = {
  8101. prev: prev,
  8102. next: null,
  8103. firstChild: null,
  8104. type: $(e).prop("tagName"),
  8105. isTranslateDiv: $(e).hasClass("translateDiv"),
  8106. topText: $(e).attr("data-topText"),
  8107. id: $(e).attr("id"),
  8108. };
  8109.  
  8110. if (prev !== null) {
  8111. node[prev].next = this.index;
  8112. }
  8113.  
  8114. prev = this.index;
  8115.  
  8116. this.index++;
  8117. return prev;
  8118. }
  8119.  
  8120. getNodeData() {
  8121. return this.node;
  8122. }
  8123.  
  8124. setNodeData(node) {
  8125. this.node = node;
  8126. }
  8127.  
  8128. getTransResultMap() {
  8129. return this.transResultMap;
  8130. }
  8131.  
  8132. setTransResultMap(transResultMap) {
  8133. this.transResultMap = transResultMap;
  8134. }
  8135.  
  8136. rmTransResultMap(id) {
  8137. delete this.transResultMap[id];
  8138. }
  8139.  
  8140. addTransResultMap(id, text) {
  8141. this.transResultMap[id] = text;
  8142. }
  8143.  
  8144. getTranslateDivNum(ttTree) {
  8145. var num = 0;
  8146. for (var i in ttTree) {
  8147. if (ttTree[i].isTranslateDiv) {
  8148. num++;
  8149. }
  8150. }
  8151. return num;
  8152. }
  8153.  
  8154. // 恢复目标元素中的translateDiv
  8155. recover(elements) {
  8156. elements.each((i, e) => {
  8157. var ttTreeNode = this.node[i];
  8158. var missingTranslateDivs = this.getTranslateDivNum(ttTreeNode);
  8159. if (missingTranslateDivs > 0) {
  8160. this.recoverOneElement($(e), ttTreeNode);
  8161. }
  8162. });
  8163. }
  8164.  
  8165. recoverOneElement(element, ttTreeNode) {
  8166. this.recoverOneFork(element.children().eq(0), ttTreeNode, 0);
  8167. }
  8168.  
  8169. // 恢复一个分支
  8170. recoverOneFork(pElement, ttTreeNode, index) {
  8171. do {
  8172. // only recover element with tagNames
  8173. if (!this.tagNames.includes(pElement.prop("tagName"))) {
  8174. if (pElement.next().length > 0) {
  8175. pElement = pElement.next();
  8176. } else {
  8177. return;
  8178. }
  8179. }
  8180. if (!ttTreeNode[index] || pElement.prop("tagName") !== ttTreeNode[index].type) {
  8181. // console.warn(`元素不存在或类型不同, 元素结构可能已经发生了变化: \nindex: ${index}`, pElement);
  8182. return;
  8183. } else {
  8184. // recursively child element
  8185. var node = ttTreeNode[index];
  8186. if (node.firstChild !== null) {
  8187. this.recoverOneFork(
  8188. pElement.children().eq(0),
  8189. ttTreeNode,
  8190. node.firstChild
  8191. );
  8192. }
  8193. // check if next node is translateDiv
  8194. if (node.next !== null) {
  8195. index = node.next;
  8196.  
  8197. var ne_node = ttTreeNode[index];
  8198. if (ne_node.isTranslateDiv) {
  8199. var id = ne_node.id;
  8200. var topText = ne_node.topText;
  8201. var text = this.transResultMap[id];
  8202. // create element after pElement
  8203. this.reCreateTransDiv(pElement, id, text, topText);
  8204. }
  8205. pElement = pElement.next(); // go to next element
  8206. }
  8207. }
  8208. } while (node.next !== null);
  8209. }
  8210.  
  8211. // 重新创建translateDiv
  8212. reCreateTransDiv(pElement, id, translatedText, topText) {
  8213. const translateDiv = new TranslateDiv(id);
  8214. pElement.after(translateDiv.getDiv());
  8215. translateDiv.setTopText(topText);
  8216. translateDiv.registerUpButtonEvent();
  8217. translateDiv.registerCloseButtonEvent();
  8218. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  8219. translateDiv.registerCopyButtonEvent(translatedText);
  8220. } else {
  8221. translateDiv.disableCopyButton();
  8222. }
  8223. translateDiv.updateTranslateDiv(translatedText, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru));
  8224. // 标记已翻译并添加到翻译按钮的结果栈中
  8225. let transButton = pElement.prev('.html2md-panel').find('.translateButton');
  8226. if (transButton.length == 0) {
  8227. // 如果没有找到,则应该是得在父元素中找到
  8228. transButton = pElement.parent().prev('.html2md-panel').find('.translateButton');
  8229. }
  8230. transButton.pushResultToTransButton({
  8231. translateDiv: translateDiv,
  8232. status: 0
  8233. });
  8234. transButton.setTransButtonState('success');
  8235. }
  8236. }
  8237.  
  8238. // 更新TransDB中的翻译数据
  8239. async function updateTransDBData(nodeDate, transResultMap) {
  8240. var url = window.location.href.replace(/#/, "");
  8241. try {
  8242. await OJBetter.common.database.translateData.put({ url, transResultMap, nodeDate });
  8243. return 'translateData saved successfully';
  8244. } catch (error) {
  8245. throw new Error(`Failed to save translateData: ${error}`);
  8246. }
  8247. }
  8248.  
  8249. // 获取TransDB中保存的翻译数据
  8250. async function getTransDBData() {
  8251. var url = window.location.href.replace(/#/, "");
  8252. try {
  8253. const result = await OJBetter.common.database.translateData.get(url);
  8254. return result;
  8255. } catch (error) {
  8256. throw new Error(`Failed to get translateData: ${error}`);
  8257. }
  8258. }
  8259.  
  8260. /**
  8261. * 翻译结果恢复功能初始化
  8262. * @returns
  8263. */
  8264. async function initTransResultsRecover() {
  8265. OJBetter.translation.memory.ttTree = new ElementsTree("#task-statement"); // 初始化当前页面#task-statement元素的结构树
  8266. let result = await getTransDBData();
  8267. if (!result) return;
  8268. OJBetter.translation.memory.ttTree.setNodeData(result.nodeDate);
  8269. OJBetter.translation.memory.ttTree.setTransResultMap(result.transResultMap);
  8270. OJBetter.translation.memory.ttTree.recover($("#task-statement"));
  8271. }
  8272.  
  8273. /**
  8274. * 自动翻译
  8275. */
  8276. async function initTransWhenViewable() {
  8277. await waitForMathJaxIdle();
  8278.  
  8279. // const elements = $('.ttypography, .comments').find('.translateButton');
  8280. const elements = $('#task-statement').find('.translateButton');
  8281. const observers = [];
  8282.  
  8283. // Use a single Intersection Observer for all elements
  8284. const observer = new IntersectionObserver((entries, obs) => {
  8285. entries.forEach((entry) => {
  8286. if (entry.isIntersecting) {
  8287. const button = $(entry.target);
  8288. const state = button.getButtonState();
  8289. const notAutoTranslate = button.getNotAutoTranslate();
  8290. // Check if the button meets the criteria
  8291. if (state === 'normal' && !notAutoTranslate) {
  8292. let trans = OJBetter.translation.choice;
  8293.  
  8294. if (OJBetter.translation.auto.mixTrans.enabled && button.IsCommentButton() && OJBetter.translation.auto.mixTrans.servers.length > 0) {
  8295. const randomIndex = Math.floor(Math.random() * OJBetter.translation.auto.mixTrans.servers.length);
  8296. trans = OJBetter.translation.auto.mixTrans.servers[randomIndex];
  8297. }
  8298. button.data("translatedItBy")(trans);
  8299. }
  8300.  
  8301. // Stop observing the element
  8302. obs.unobserve(entry.target);
  8303. }
  8304. });
  8305. });
  8306.  
  8307. // Observe each element
  8308. elements.each((i, e) => {
  8309. observer.observe(e);
  8310. });
  8311.  
  8312. // Store the observer in case you need to disconnect it later
  8313. observers.push(observer);
  8314. }
  8315.  
  8316. /**
  8317. * 翻译返回结果结构体
  8318. * @typedef {Object} TranslateResult
  8319. * @property {string} status 翻译状态
  8320. * @property {TranslateDiv} translateDiv 翻译结果面板
  8321. * @property {TransRawData} rawData 原始翻译数据
  8322. */
  8323.  
  8324. /**
  8325. * 翻译主方法
  8326. * @param {string} text 待翻译文本
  8327. * @param {HTMLElement} element_node 元素节点
  8328. * @param {Boolean} is_comment 是否为评论区文本
  8329. * @param {string} overrideTrans 覆盖全局翻译服务设定
  8330. * @returns {TranslateResult} 翻译结果对象
  8331. */
  8332. async function translateProblemStatement(text, element_node, is_comment, overrideTrans) {
  8333. /** @type {number} 翻译结果的ID*/
  8334. const id = OJB_getRandomNumber(8);
  8335. /** @type {TextBlockReplacer} 文本块替换/恢复实例*/
  8336. const textBlockReplacer = new TextBlockReplacer();
  8337. /** @type {string} 翻译结果文本*/
  8338. let translatedText = "";
  8339.  
  8340. /** @type {string} 当前实际应用的翻译服务 */
  8341. const realTransServer = overrideTrans ||
  8342. (is_comment && OJBetter.translation.comment.choice != "0" ?
  8343. OJBetter.translation.comment.choice :
  8344. OJBetter.translation.choice);
  8345.  
  8346. /** @type {TranslateResult} 翻译结果对象 */
  8347. const translateResult = {
  8348. status: "ok",
  8349. rawData: {
  8350. done: false
  8351. }
  8352. }
  8353.  
  8354. /**
  8355. * LaTeX替换
  8356. * @param {string} text 待翻译文本
  8357. * @returns {string} 处理后的文本
  8358. */
  8359. const replaceLatex = function (text) {
  8360. if (OJBetter.typeOfPage.is_oldLatex) {
  8361. const regex = /<span\s+class="tex-span">.*?<\/span>/gi;
  8362. text = textBlockReplacer.replace(text, regex);
  8363. text = text.replace(/<p>(.*?)<\/p>/g, "$1\n\n"); // <p/>标签换为换行
  8364. } else if (OJBetter.typeOfPage.is_acmsguru) {
  8365. const regex = /<i>.*?<\/i>|<sub>.*?<\/sub>|<sup>.*?<\/sup>|<pre>.*?<\/pre>/gi;
  8366. text = textBlockReplacer.replace(text, regex);
  8367. } else if (realTransServer != "openai") {
  8368. // 使用GPT翻译时不必替换latex公式
  8369. const regex = /\$\$(\\.|[^\$])*?\$\$|\$(\\.|[^\$])*?\$/g;
  8370. text = textBlockReplacer.replace(text, regex);
  8371.  
  8372. // 替换行间代码块```
  8373. const regex2 = /```[\s\S]*?```/g;
  8374. text = textBlockReplacer.replace(text, regex2);
  8375. }
  8376. return text;
  8377. }
  8378.  
  8379. /**
  8380. * LaTeX恢复
  8381. * @param {string} text 已翻译的文本
  8382. * @returns {string} 恢复后的文本
  8383. */
  8384. const recoverLatex = function (text) {
  8385. // 两个公式之间加个空格,防止有些LaTeX解析器解析错误
  8386. let resultText = text
  8387. .replace(/】【/g, '】 【')
  8388. .replace(/\]\[/g, '] [')
  8389. .replace(/\}\{/g, '} {');
  8390.  
  8391. if (OJBetter.typeOfPage.is_oldLatex) {
  8392. resultText = resultText.replace(/(.+?)(\n\n|$)/g, "<p>$1</p>"); // 换行符还原为<p/>标签
  8393. resultText = textBlockReplacer.recover(resultText);
  8394. } else if (OJBetter.typeOfPage.is_acmsguru) {
  8395. resultText = textBlockReplacer.recover(resultText);
  8396. } else if (realTransServer != "openai") {
  8397. resultText = textBlockReplacer.recover(resultText);
  8398. }
  8399. return resultText;
  8400. }
  8401.  
  8402. /**
  8403. * 格式化翻译结果
  8404. * @param {string} text
  8405. * @returns {string} 处理后的翻译结果
  8406. */
  8407. const formatText = function (text) {
  8408. // 转义LaTex中的特殊符号
  8409. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  8410.  
  8411. // 先替换掉行间代码块
  8412. const replacer = new TextBlockReplacer();
  8413. text = replacer.replace(text, /```[\s\S]*?```/g);
  8414.  
  8415. // 处理LaTeX公式
  8416. const escapeRules = [
  8417. { pattern: /(?<!\\)>(?!\s)/g, replacement: " &gt; " }, // >符号
  8418. { pattern: /(?<!\\)</g, replacement: " &lt; " }, // <符号
  8419. { pattern: /(?<!\\)\*/g, replacement: " &#42; " }, // *符号
  8420. { pattern: /(?<!\\)_/g, replacement: " &#95; " }, // _符号
  8421. { pattern: /(?<!\\)\\\\(?=\s)/g, replacement: "\\\\\\\\" }, // \\符号
  8422. { pattern: /(?<!\\)\\(?![\\a-zA-Z0-9])/g, replacement: "\\\\" }, // \符号
  8423. ];
  8424.  
  8425. let latexMatches = [...text.matchAll(/\$\$([\s\S]*?)\$\$|\$(.*?)\$|\$([\s\S]*?)\$/g)];
  8426. for (const match of latexMatches) {
  8427. const matchedText = match[0];
  8428. let escapedText = matchedText;
  8429.  
  8430. for (const rule of escapeRules) {
  8431. escapedText = escapedText.replaceAll(rule.pattern, rule.replacement);
  8432. }
  8433. escapedText = escapedText.replace(/\$\$/g, "$$$$$$$$");// $$符号(因为后面需要作为replacement,双倍消耗)
  8434. text = text.replace(matchedText, escapedText);
  8435. }
  8436.  
  8437. // 恢复行间代码块
  8438. text = replacer.recover(text);
  8439. }
  8440.  
  8441. // // 使符合mathjx的转换语法
  8442. // const mathjaxRuleMap = [
  8443. // { pattern: /\$/g, replacement: "$$$$$$" }, // $$ 行间
  8444. // ];
  8445. // mathjaxRuleMap.forEach(({ pattern, replacement }) => {
  8446. // text = text.replace(pattern, replacement);
  8447. // });
  8448.  
  8449. // markdown修正
  8450. const mdRuleMap = [
  8451. { pattern: /(\s_[\u4e00-\u9fa5]+_)([\u4e00-\u9fa5]+)/g, replacement: "$1 $2" }, // 斜体
  8452. { pattern: /(_[\u4e00-\u9fa5]+_\s)([\u4e00-\u9fa5]+)/g, replacement: " $1$2" },
  8453. { pattern: /(_[\u4e00-\u9fa5]+_)([\u4e00-\u9fa5]+)/g, replacement: " $1 $2" },
  8454. { pattern: /(([\s\S]*?))/g, replacement: "($1)" }, // 中文()
  8455. // { pattern: /:/g, replacement: ":" }, // 中文:
  8456. { pattern: /\*\* (.*?) \*\*/g, replacement: "\*\*$1\*\*" } // 加粗
  8457. ];
  8458. mdRuleMap.forEach(({ pattern, replacement }) => {
  8459. text = text.replace(pattern, replacement);
  8460. });
  8461.  
  8462. return text;
  8463. }
  8464.  
  8465. // 创建翻译结果元素并放在element_node的后面
  8466. translateResult.translateDiv = new TranslateDiv(id);
  8467. $(element_node).after(translateResult.translateDiv.getDiv());
  8468.  
  8469. // 顶栏左侧信息
  8470. translateResult.translateDiv.setTopText(i18next.t('servers.' + realTransServer, { ns: 'translator' }) +
  8471. i18next.t('translateDiv.topTextSuffix', { ns: 'translator' }));
  8472.  
  8473. // 注册按钮
  8474. translateResult.translateDiv.registerUpButtonEvent();
  8475. translateResult.translateDiv.registerCloseButtonEvent();
  8476. if (OJBetter.translation.choice == 'openai' || OJBetter.translation.choice == 'deepl') {
  8477. translateResult.translateDiv.showQueryBalanceButton(OJBetter.translation.choice); // 显示额度查询
  8478. }
  8479.  
  8480. // 翻译内容是否可能为代码片段
  8481. if (isEmptyText(text)) {
  8482. const shouldContinue = await OJB_createDialog(
  8483. i18next.t('isEmptyText.title', { ns: 'dialog' }),
  8484. i18next.t('isEmptyText.content', { ns: 'dialog' }),
  8485. [
  8486. i18next.t('isEmptyText.buttons.0', { ns: 'dialog' }),
  8487. i18next.t('isEmptyText.buttons.1', { ns: 'dialog' })
  8488. ],
  8489. true
  8490. );
  8491. if (shouldContinue) {
  8492. translateResult.status = "skip";
  8493. return translateResult;
  8494. }
  8495. }
  8496.  
  8497. // 替换latex公式
  8498. text = replaceLatex(text);
  8499.  
  8500. // 过滤**号
  8501. if (OJBetter.translation.filterTextWithoutEmphasis && GM_getValue("translation") !== "openai") { // TODO
  8502. text = text.replace(/\*\*/g, "");
  8503. }
  8504.  
  8505. // 字符数上限
  8506. const translationLimits = {
  8507. deepl: 5000,
  8508. iflyrec: 2000,
  8509. youdao: 600,
  8510. google: 5000,
  8511. caiyun: 5000
  8512. };
  8513. if (translationLimits.hasOwnProperty(realTransServer) && text.length > translationLimits[realTransServer]) {
  8514. let textLength = translationLimits[realTransServer];
  8515. let realTextLength = text.length;
  8516. const shouldContinue = await OJB_createDialog(
  8517. i18next.t('transTextLimits.title', { ns: 'dialog' }),
  8518. i18next.t('transTextLimits.content', { ns: 'dialog', textLength: textLength, realTextLength: realTextLength }),
  8519. [
  8520. i18next.t('transTextLimits.buttons.0', { ns: 'dialog' }),
  8521. i18next.t('transTextLimits.buttons.1', { ns: 'dialog' })
  8522. ],
  8523. true
  8524. ); // 字数超限确认
  8525. if (shouldContinue) {
  8526. translateResult.status = "skip";
  8527. return translateResult;
  8528. }
  8529. }
  8530.  
  8531. /**
  8532. * 调用各个翻译服务
  8533. * @param {string} transServer 翻译服务
  8534. * @returns {TransRawData} 原始翻译数据
  8535. */
  8536. async function translate(transServer) {
  8537. const is_renderLaTeX = !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru);
  8538. const servername = i18next.t('servers.' + realTransServer, { ns: 'translator' });
  8539. /** @type {TransRawData} 原始翻译数据*/
  8540. let rawData = {};
  8541. try {
  8542. if (transServer == "deepl") {
  8543. if (OJBetter.deepl.config.type == 'free') {
  8544. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8545. rawData = await translate_deepl(text);
  8546. } else if (OJBetter.deepl.config.type == 'api') {
  8547. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.deeplApi', { ns: 'translator', deepl_configName: OJBetter.deepl.config.name })}`, is_renderLaTeX);
  8548. if (OJBetter.deepl.config.apiGenre == 'deeplx') {
  8549. rawData = await translate_deeplx(text);
  8550. } else {
  8551. if (OJBetter.deepl.enableEmphasisProtection) text = convertBoldMarkdownToHTML(text);
  8552. if (OJBetter.deepl.enableLinkProtection) text = convertLinksMarkdownToHTML(text);
  8553. if (OJBetter.deepl.config.apiGenre == 'api-free') {
  8554. rawData = await translate_deepl_api_free(text);
  8555. } else if (OJBetter.deepl.config.apiGenre == 'api-pro') {
  8556. rawData = await translate_deepl_api_pro(text);
  8557. }
  8558. if (OJBetter.deepl.enableEmphasisProtection) rawData.text = convertBoldHTMLToMarkdown(rawData.text);
  8559. if (OJBetter.deepl.enableLinkProtection) rawData.text = convertLinksHTMLToMarkdown(rawData.text);
  8560. }
  8561. }
  8562. } else if (transServer == "iflyrec") {
  8563. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8564. rawData = await translate_iflyrec(text);
  8565. } else if (transServer == "youdao") {
  8566. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8567. rawData = await translate_youdao_mobile(text);
  8568. } else if (transServer == "google") {
  8569. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8570. rawData = await translate_gg(text);
  8571. } else if (transServer == "caiyun") {
  8572. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8573. rawData = await translate_caiyun(text);
  8574. } else if (transServer == "openai") {
  8575. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.openai', { ns: 'translator', openai_name: OJBetter.chatgpt.config.name })}${!OJBetter.chatgpt.isStream
  8576. ? i18next.t('transingTip.openai_isStream', { ns: 'translator' }) : ""}`,
  8577. is_renderLaTeX);
  8578. if (OJBetter.chatgpt.isStream) {
  8579. // 流式传输
  8580. rawData = await translate_openai_stream(text, translateResult.translateDiv);
  8581. } else {
  8582. // 普通模式
  8583. rawData = await translate_openai(text);
  8584. }
  8585. }
  8586. translatedText = rawData.text;
  8587. if (!rawData.done) {
  8588. translateResult.status = "error";
  8589. }
  8590. } catch (e) {
  8591. translateResult.status = "error";
  8592. rawData.message = i18next.t('error.unexpected', { ns: 'translator' });
  8593. console.warn(e);
  8594. }
  8595. return rawData;
  8596. }
  8597. translateResult.rawData = await translate(realTransServer);
  8598.  
  8599. if (translateResult.status == "error") {
  8600. translateResult.translateDiv.updateTranslateDiv(translateResult.rawData.message);
  8601. return translateResult;
  8602. }
  8603.  
  8604. // 还原latex公式
  8605. translatedText = recoverLatex(translatedText);
  8606.  
  8607. // 注册结果复制按钮
  8608. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  8609. translateResult.translateDiv.registerCopyButtonEvent(translatedText);
  8610. } else {
  8611. translateResult.translateDiv.disableCopyButton();
  8612. }
  8613.  
  8614. // 翻译结果格式化
  8615. translatedText = formatText(translatedText);
  8616.  
  8617. // 保存翻译结果
  8618. if ((OJBetter.typeOfPage.is_problem || OJBetter.typeOfPage.is_completeProblemset) && OJBetter.translation.memory.enabled) {
  8619. // OJBetter.translation.memory.ttTree.refreshNode(".ttypography"); // 刷新当前页面.ttypography元素的结构树实例
  8620. OJBetter.translation.memory.ttTree.refreshNode("#task-statement"); // 刷新当前页面.ttypography元素的结构树实例
  8621. OJBetter.translation.memory.ttTree.addTransResultMap(id, translatedText);
  8622. updateTransDBData(OJBetter.translation.memory.ttTree.getNodeData(), OJBetter.translation.memory.ttTree.getTransResultMap()); // 更新翻译结果到transDB
  8623. }
  8624.  
  8625. // 翻译结果面板更新
  8626. translateResult.translateDiv.updateTranslateDiv(translatedText, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru));
  8627.  
  8628. return translateResult;
  8629. }
  8630.  
  8631. //弹窗翻译
  8632. function alertZh() {
  8633. // var _alert = window.alert;
  8634. // window.alert = async function (msg) {
  8635. // _alert(msg + "\n=========翻译=========\n" + await translate_deepl(msg));
  8636. // return true;
  8637. // }
  8638. };
  8639.  
  8640. /**
  8641. * 折叠块展开
  8642. */
  8643. function ExpandFoldingblocks() {
  8644. $('.spoiler').addClass('spoiler-open');
  8645. $('.spoiler-content').attr('style', '');
  8646. };
  8647.  
  8648. /**
  8649. * 折叠块渲染优化
  8650. */
  8651. function RenderPerfOpt() {
  8652. GM_addStyle(`
  8653. .spoiler-content {
  8654. contain: layout style;
  8655. }
  8656. `);
  8657. }
  8658.  
  8659. /**
  8660. * 下拉选择框性能优化
  8661. */
  8662. async function SelectElementPerfOpt() {
  8663. // TODO 10
  8664. // 加载库资源
  8665. await OJB_LoadJS("https://aowuucdn.oss-accelerate.aliyuncs.com/js/selectpage.min.js");
  8666. /**
  8667. * 将一个<select>元素转换为SelectPage控件
  8668. * @param {HTMLElement|string} selector - 要转换的<select>元素或其选择器
  8669. */
  8670. const OJB_transformSelectToSelectPage = (selector) => {
  8671. const $select = $(selector);
  8672. if ($select.length === 0 || !$select.is('select')) {
  8673. console.error('Invalid select element provided.');
  8674. return;
  8675. }
  8676.  
  8677. // 隐藏原生的<select>元素
  8678. $select.hide();
  8679.  
  8680. // 创建一个新的<input>元素用于SelectPage控件
  8681. const $inputForSelectPage = $('<input>', {
  8682. type: 'text',
  8683. class: 'selectpage-input',
  8684. autocomplete: 'off'
  8685. });
  8686. $select.after($inputForSelectPage);
  8687.  
  8688. // 准备SelectPage所需的数据格式
  8689. const data = $select.find('option').map((_, option) => ({
  8690. id: option.value,
  8691. text: option.text
  8692. })).get();
  8693.  
  8694. // 初始化SelectPage
  8695. $inputForSelectPage.selectPage({
  8696. showField: 'text',
  8697. keyField: 'id',
  8698. data,
  8699. lang: 'en',
  8700. // 当选中一个选项时,更新隐藏的<select>元素的值
  8701. eSelect: (data) => {
  8702. $select.val(data.id).trigger('change');
  8703. },
  8704. // 初始化时根据<select>的当前值设置SelectPage
  8705. initRecord: $select.val()
  8706. });
  8707. };
  8708.  
  8709. // 遍历页面上的所有select
  8710. $('select').each((_, select) => {
  8711. // 选项大于500才优化
  8712. if ($(select).find('option').length > 500) {
  8713. OJB_transformSelectToSelectPage(select);
  8714. }
  8715. });
  8716. }
  8717.  
  8718. /**
  8719. * 题目页相关链接栏
  8720. */
  8721. class ProblemPageLinkbar {
  8722. constructor() {
  8723. this.containerElement = this.createToolbar();
  8724. this.commandInvoker = new CommandInvoker();
  8725. }
  8726.  
  8727. /**
  8728. * 创建工具栏
  8729. */
  8730. createToolbar() {
  8731. // const toolbarElement = $("<div>").attr("id", "problemToolbar").insertBefore($(".problemindexholder"));
  8732. const toolbarElement = $("<div>").attr("id", "problemToolbar").insertBefore($(".h2"));
  8733. return new DOMContainer(toolbarElement);
  8734. }
  8735.  
  8736. /**
  8737. * 添加按钮
  8738. * @param {string} id 按钮id
  8739. * @param {string} url 按钮链接
  8740. * @param {string} text 按钮文字
  8741. * @param {JQueryObject} icon 按钮图标
  8742. * @param {string} iconHeight 图标高度
  8743. * @returns {object} 按钮对象
  8744. */
  8745. addLinkButton(id, url, text, icon = $('<div>'), iconHeight = "22px") {
  8746. const linkElement = $("<a>")
  8747. .attr("href", url)
  8748. .attr("target", "_blank")
  8749. .addClass("ojb_btn")
  8750. .attr("id", id);
  8751.  
  8752. linkElement.append(icon);
  8753. icon.css("height", iconHeight);
  8754.  
  8755. const textSpan = $("<span>").html(text);
  8756. linkElement.append(textSpan);
  8757.  
  8758. this.commandInvoker.execute(new AddElementCommand(this.containerElement, linkElement));
  8759. return {
  8760. element: linkElement,
  8761. text: textSpan,
  8762. icon: icon
  8763. };
  8764. }
  8765.  
  8766. /**
  8767. * 更新链接
  8768. * @param {object} button 按钮对象
  8769. * @param {string} url 按钮链接
  8770. */
  8771. updateUrl(button, url) {
  8772. button.element.attr("href", url);
  8773. }
  8774.  
  8775. /**
  8776. * 更新文字
  8777. * @param {object} button 按钮对象
  8778. * @param {string} text 按钮文字
  8779. */
  8780. updateText(button, text) {
  8781. button.text.html(text);
  8782. }
  8783.  
  8784. /**
  8785. * 设置文字为粗体
  8786. * @param {object} button 按钮对象
  8787. */
  8788. setBold(button) {
  8789. button.text.css("font-weight", "bold");
  8790. }
  8791.  
  8792. /**
  8793. * 更新图标
  8794. * @param {object} button 按钮对象
  8795. * @param {JQueryObject} icon 按钮图标
  8796. * @param {string} iconHeight 图标高度
  8797. */
  8798. updateIcon(button, icon, iconHeight = "16px") {
  8799. button.icon.remove();
  8800. button.text.prepend(icon);
  8801. icon.css("height", iconHeight);
  8802. button.icon = icon;
  8803. }
  8804.  
  8805. /**
  8806. * 添加类
  8807. * @param {object} button 按钮对象
  8808. * @param {string} className 类名
  8809. */
  8810. addClass(button, className) {
  8811. button.element.addClass(className);
  8812. }
  8813.  
  8814. /**
  8815. * 禁用链接按钮
  8816. * @param {object} button 按钮对象
  8817. */
  8818. disableButton(button) {
  8819. button.element.addClass("disabled");
  8820. }
  8821.  
  8822. /**
  8823. * 启用链接按钮
  8824. * @param {object} button 按钮对象
  8825. */
  8826. enableButton(button) {
  8827. button.element.removeClass("disabled");
  8828. }
  8829. }
  8830.  
  8831. /**
  8832. * 获取题目的id
  8833. * @param {String} url 题目的链接
  8834. * @returns 题目的id,形如2000A
  8835. */
  8836. function getProblemId(url) {
  8837. const regex = /\/contests\/([A-Za-z\d]+)\/tasks\/([A-Za-z\d\_]+)/;
  8838. const matchResult = url.match(regex);
  8839. return matchResult && matchResult.length >= 3
  8840. ? `${matchResult[2]}`
  8841. : '';
  8842. };
  8843.  
  8844. /**
  8845. * 跳转到洛谷
  8846. * @param {ProblemPageLinkbar} problemToolbar
  8847. */
  8848. async function CF2luogu(problemToolbar) {
  8849. const url = window.location.href;
  8850. const problemId = getProblemId(url);
  8851. const luoguButton = problemToolbar.addLinkButton(
  8852. "luoguButton",
  8853. "https://www.luogu.com.cn/",
  8854. i18next.t('state.loading', { ns: 'button' }),
  8855. $("<img>").attr("src", "https://cdn.luogu.com.cn/fe/logo.png")
  8856. );
  8857.  
  8858. const checkLinkExistence = async (url) => {
  8859. return OJB_promiseRetryWrapper(async () => {
  8860. const response = await OJB_GMRequest({
  8861. method: "GET",
  8862. url
  8863. });
  8864. return !response.responseText.match(/出错了/g);
  8865. }, {
  8866. maxRetries: 3,
  8867. retryInterval: 1000
  8868. });
  8869. };
  8870.  
  8871. const LuoguUrl = `https://www.luogu.com.cn/problem/AT_${problemId}`;
  8872. try {
  8873. const result = await checkLinkExistence(LuoguUrl);
  8874. if (problemId && result) {
  8875. problemToolbar.updateText(luoguButton, "");
  8876. problemToolbar.updateUrl(luoguButton, LuoguUrl);
  8877. } else {
  8878. problemToolbar.updateText(luoguButton, i18next.t('state.404', { ns: 'button' }));
  8879. problemToolbar.disableButton(luoguButton);
  8880. }
  8881. } catch (error) {
  8882. if (error instanceof OJB_GMError && error.type == "error") {
  8883. problemToolbar.updateText(luoguButton, i18next.t('state.netError', { ns: 'button' }));
  8884. problemToolbar.disableButton(luoguButton);
  8885. }
  8886. }
  8887. }
  8888.  
  8889. /**
  8890. * 跳转到 Virtual Judge
  8891. * @param {ProblemPageLinkbar} problemToolbar
  8892. */
  8893. async function CF2vjudge(problemToolbar) {
  8894. const url = window.location.href;
  8895. const problemId = getProblemId(url);
  8896. const vjudgeButton = problemToolbar.addLinkButton(
  8897. "vjudgeButton",
  8898. "https://vjudge.net/",
  8899. i18next.t('state.loading', { ns: 'button' }),
  8900. $("<img>").attr("src", "https://aowuucdn.oss-accelerate.aliyuncs.com/vjudge.ico")
  8901. );
  8902.  
  8903. const checkLinkExistence = async (url) => {
  8904. return OJB_promiseRetryWrapper(async () => {
  8905. const response = await OJB_GMRequest({
  8906. method: "HEAD",
  8907. url: url,
  8908. });
  8909. if (response.status >= 200 && response.status < 300) return true;
  8910. else if (response.status == 404) return false;
  8911. else throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  8912. }, {
  8913. maxRetries: 3,
  8914. retryInterval: 1000
  8915. });
  8916. };
  8917.  
  8918. const VjudgeUrl = `https://vjudge.net/problem/AtCoder-${problemId}`;
  8919. try {
  8920. const result = await checkLinkExistence(VjudgeUrl);
  8921. if (problemId && result) {
  8922. problemToolbar.updateText(vjudgeButton, "VJudge");
  8923. problemToolbar.updateUrl(vjudgeButton, VjudgeUrl);
  8924. } else {
  8925. problemToolbar.updateText(vjudgeButton, i18next.t('state.404', { ns: 'button' }));
  8926. problemToolbar.disableButton(vjudgeButton);
  8927. }
  8928. } catch (error) {
  8929. if (error instanceof OJB_GMError && error.type == "error") {
  8930. problemToolbar.updateText(vjudgeButton, i18next.t('state.netError', { ns: 'button' }));
  8931. problemToolbar.disableButton(vjudgeButton);
  8932. }
  8933. }
  8934. }
  8935.  
  8936. // RatingClass
  8937. const ratingClassMap = {
  8938. NaN: "rating_by_clist_colorNaN",
  8939. 0: "rating_by_clist_color0",
  8940. 1200: "rating_by_clist_color1",
  8941. 1400: "rating_by_clist_color2",
  8942. 1600: "rating_by_clist_color3",
  8943. 1900: "rating_by_clist_color4",
  8944. 2100: "rating_by_clist_color5",
  8945. 2300: "rating_by_clist_color6",
  8946. 2400: "rating_by_clist_color7",
  8947. 2600: "rating_by_clist_color8",
  8948. 3000: "rating_by_clist_color9"
  8949. };
  8950. const cssMap = {
  8951. "rating_by_clist_colorNaN": "#cccccc",
  8952. "rating_by_clist_color0": "#808080",
  8953. "rating_by_clist_color1": "#73e473",
  8954. "rating_by_clist_color2": "#77ddbb",
  8955. "rating_by_clist_color3": "#aaaaff",
  8956. "rating_by_clist_color4": "#ff88ff",
  8957. "rating_by_clist_color5": "#ffcc88",
  8958. "rating_by_clist_color6": "#ffbb55",
  8959. "rating_by_clist_color7": "#ff7777",
  8960. "rating_by_clist_color8": "#ff3333",
  8961. "rating_by_clist_color9": "#aa0000"
  8962. };
  8963. // TODO 7
  8964. /**
  8965. * clist 访问有效性检查
  8966. * @param {boolean} onlyCookie 是否只检查Cookie
  8967. * @returns {Promise<boolean>} 是否有效
  8968. */
  8969. async function validateClistConnection(onlyCookie = false) {
  8970. const clistApiUrl = "https://clist.by:443/api/v4/contest/?limit=1&resource_id=1";
  8971. const requestOptions = {
  8972. method: "GET",
  8973. url: clistApiUrl,
  8974. timeout: 5000,
  8975. };
  8976.  
  8977. // 尝试发送请求
  8978. async function tryRequest(options) {
  8979. try {
  8980. const response = await OJB_GMRequest(options);
  8981. if (response.status === 200) {
  8982. return { ok: true };
  8983. } else if (response.status === 401) {
  8984. throw new Error('unauthorized');
  8985. } else if (response.status === 404) {
  8986. throw new Error('not_found');
  8987. } else {
  8988. throw new Error('other_error');
  8989. }
  8990. } catch (error) {
  8991. console.warn(`Error accessing clist.by: ${error.message}`);
  8992. return { ok: false, error: error.message };
  8993. }
  8994. }
  8995.  
  8996. // 尝试携带Key发送请求
  8997. let result = await tryRequest(requestOptions);
  8998. if (!onlyCookie && !result.ok) {
  8999. requestOptions.headers = { "Authorization": OJBetter.clist.authorization };
  9000. result = await tryRequest(requestOptions);
  9001. }
  9002.  
  9003. // 根据结果显示错误信息
  9004. if (!result.ok) {
  9005. let errorType = result.error;
  9006. const loadingMessage = new LoadingMessage();
  9007. let state;
  9008. if (errorType === 'not_found') {
  9009. state = i18next.t('error.clist.404', { ns: 'alert' });
  9010. } else if (errorType === 'unauthorized') {
  9011. state = i18next.t('error.clist.cookie', { ns: 'alert' });
  9012. } else {
  9013. state = i18next.t('error.clist.other', { ns: 'alert' });
  9014. }
  9015. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${state}`, 'error');
  9016. }
  9017. return result.ok;
  9018. }
  9019.  
  9020. /**
  9021. * 创建Rating相关css
  9022. * @param {boolean} [hasBorder=true] 是否有边框
  9023. */
  9024. function creatRatingCss(hasBorder = true) {
  9025. const defaultBorderColor = '#dcdfe6';
  9026. let dynamicCss = "";
  9027. let hoverSelector = OJBetter.clist.ratingHidden ? ":hover" : "";
  9028. for (let cssClass in cssMap) {
  9029. dynamicCss += `a.${cssClass}${hoverSelector}, a.${cssClass}${hoverSelector}:link {\n`;
  9030. let borderColor = hasBorder ? cssMap[cssClass] : defaultBorderColor;
  9031. dynamicCss += ` color: ${cssMap[cssClass]} ${OJBetter.clist.ratingHidden ? "!important" : ""};\n`;
  9032. dynamicCss += `}\n`;
  9033. }
  9034. GM_addStyle(dynamicCss);
  9035. if (OJBetter.clist.ratingHidden) {
  9036. GM_addStyle(`
  9037. #clistButton {
  9038. color: #ffffff00;
  9039. }
  9040. `);
  9041. }
  9042. }
  9043.  
  9044. /**
  9045. * 模拟clist网页访问获取rating
  9046. * @param {string} problem 题目名称
  9047. * @param {string} problem_url 题目链接
  9048. * @param {string} contest 比赛名称
  9049. * @returns {Promise<{rating: number, problem: string}>} 题目难度
  9050. */
  9051. async function getRatingFromHTML(problem, problem_url, contest = null) {
  9052. // 去除题目名称中的括号,以及首尾的空白符
  9053. problem = problem.replace(/\([\s\S]*?\)/g, '').trim();
  9054.  
  9055. return OJB_promiseRetryWrapper(async () => {
  9056. const queryString = `search=${encodeURIComponent(problem)}&resource=1`;
  9057. const response = await OJB_GMRequest({
  9058. method: 'GET',
  9059. url: `https://clist.by/problems/?${queryString}`,
  9060. });
  9061.  
  9062. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9063. const html = response.responseText;
  9064. const cleanedHtml = html.replace(/src=(.|\s)*?"/g, '');
  9065. const parser = new DOMParser();
  9066. const doc = parser.parseFromString(cleanedHtml, 'text/html');
  9067. const trs = doc.querySelectorAll('table tbody tr');
  9068.  
  9069. for (let tr of trs) {
  9070. const rating = tr.querySelector('.problem-rating-column').textContent.trim();
  9071. const link = OJB_cleanLink(tr.querySelector('.problem-name-column a:nth-of-type(2)')?.href);
  9072.  
  9073. if (link === problem_url || link === problem_url + '/') {
  9074. return {
  9075. rating: parseInt(rating),
  9076. problem: problem
  9077. };
  9078. } else if (contest !== null) {
  9079. const contestTitles = [...tr.querySelectorAll('.problem-name-column .pull-right a[title], .problem-name-column .pull-right span[title]')].map(el => el.title);
  9080. if (contestTitles.includes(contest)) {
  9081. return {
  9082. rating: parseInt(rating),
  9083. problem: problem
  9084. };
  9085. }
  9086. }
  9087. }
  9088. console.warn(`No data found for the question: ${problem}`);
  9089. }, {
  9090. maxRetries: 3,
  9091. retryInterval: 500
  9092. });
  9093. }
  9094.  
  9095. /**
  9096. * 从clist API获取题目的rating
  9097. * @param {string} problem_name 题目名
  9098. * @param {string} problem_url 题目链接
  9099. * @returns {Promise<number>} 题目rating
  9100. *
  9101. * 使用两个Map对象来存储和快速访问题目信息:
  9102. * - problemsMap: 通过题目的URL作为键来存储题目信息。
  9103. * - nameMap: 通过题目的名称作为键来存储题目信息。
  9104. *
  9105. * 每个题目信息是一个对象,包含以下属性:
  9106. * @typedef {Object} ProblemInfo
  9107. * @property {string} name 题目名称
  9108. * @property {string} url 题目URL
  9109. * @property {number} rating 题目评分,如果没有评分信息则为NaN
  9110. */
  9111. async function getRatingFromApi_problem(problem_name, problem_url) {
  9112. return OJB_promiseRetryWrapper(async () => {
  9113. const response = await OJB_GMRequest({
  9114. method: "GET",
  9115. // url: `https://clist.by:443/api/v4/problem/?name=${encodeURIComponent(problem_name)}&resource__regex=codeforces.com`,
  9116. url: `https://clist.by:443/api/v4/problem/?url__regex=${encodeURIComponent(problem_name)}&resource__regex=atcoder.jp`,
  9117. headers: { "Authorization": OJBetter.clist.authorization }
  9118. });
  9119.  
  9120. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9121. let data = JSON.parse(response.responseText);
  9122. /**
  9123. * 使用题目的URL作为键来存储题目信息。
  9124. * @type {Map<string, ProblemInfo>}
  9125. */
  9126. let problemsMap = new Map();
  9127.  
  9128. /**
  9129. * 使用题目的名称作为键来存储题目信息。
  9130. * @type {Map<string, ProblemInfo>}
  9131. */
  9132. let nameMap = new Map();
  9133.  
  9134. data.objects.forEach(problem => {
  9135. /** @type {ProblemInfo} 题目信息*/
  9136. let problemInfo = {
  9137. name: problem.name,
  9138. url: problem.url,
  9139. rating: problem.rating ? problem.rating : NaN
  9140. };
  9141. problemsMap.set(OJB_cleanLink(problem.url), problemInfo);
  9142. nameMap.set(problem.name, problemInfo);
  9143. });
  9144.  
  9145. if (problemsMap.has(problem_url)) {
  9146. return problemsMap.get(problem_url).rating;
  9147. } else if (nameMap.has(problem_name)) {
  9148. return nameMap.get(problem_name).rating;
  9149. } else {
  9150. console.warn('Problem not found in the response');
  9151. }
  9152. }, {
  9153. maxRetries: 5,
  9154. retryInterval: 1000
  9155. });
  9156. }
  9157.  
  9158. /**
  9159. * 获取字符串中的关键词列表
  9160. * @param {string} text 字符串文本
  9161. * @returns {array<string>} 返回关键词列表
  9162. */
  9163. function getKeywords(text) {
  9164. // 定义要过滤掉的高频词
  9165. const highFrequencyWords = ['Educational', 'Codeforces', 'Round', 'Div'];
  9166.  
  9167. // 使用正则表达式替换掉特殊符号(保留空格以便分词)
  9168. const sanitizedText = text.replace(/[^\w\s]|_/g, '').replace(/\s+/g, ' ');
  9169.  
  9170. // 将字符串拆分为单词数组
  9171. const words = sanitizedText.split(' ');
  9172.  
  9173. // 过滤掉高频词和空字符串
  9174. const filteredWords = words.filter(word => {
  9175. return word && highFrequencyWords.indexOf(word) === -1;
  9176. });
  9177.  
  9178. // 返回关键词列表
  9179. return filteredWords;
  9180. }
  9181.  
  9182. /**
  9183. * 根据关键词从 Clist API 中获取实际比赛名称
  9184. * @param {string} contestName 比赛名
  9185. * @param {string} contestUrl 比赛链接
  9186. * @returns {string|null} 该比赛在Clist中的实际名字
  9187. */
  9188. async function getContestNameFromApi(contestName, contestUrl) {
  9189. return OJB_promiseRetryWrapper(async () => {
  9190. const options = {
  9191. method: "GET",
  9192. // url: `https://clist.by:443/api/v4/contest/?resource_id=1&event__regex=${encodeURIComponent(contestName)}`,
  9193. url: `https://clist.by:443/api/v4/contest/?resource_id=93&event__regex=${encodeURIComponent(contestName)}`,
  9194. headers: {
  9195. "Authorization": OJBetter.clist.authorization
  9196. }
  9197. };
  9198.  
  9199. let response = await OJB_GMRequest(options);
  9200.  
  9201. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9202.  
  9203. let data = JSON.parse(response.responseText);
  9204. let objects = data.objects;
  9205.  
  9206. if (objects.length > 0) {
  9207. for (const contest of objects) {
  9208. // const href = contest.href.replace(/\/contests\//i, '/contest/'); // 链接可能是contests而不是contest,换回来
  9209. const href = contest.href;
  9210. if (OJB_cleanLink(href) == contestUrl) {
  9211. return contest.event;
  9212. }
  9213. }
  9214. }
  9215. return null;
  9216. }, {
  9217. maxRetries: 5,
  9218. retryInterval: 1000
  9219. });
  9220. }
  9221.  
  9222. /**
  9223. * 获取在clist中的实际比赛名称
  9224. * @param {string} contestName 待搜索的比赛名称
  9225. * @param {string} contestUrl 比赛的url
  9226. * @returns {Promise<string|null>} 在clist中的实际比赛名称,如果没有找到,则返回null
  9227. */
  9228. async function getActualContestName(contestName, contestUrl) {
  9229. // 首先尝试使用完整的比赛名称进行搜索
  9230. let actualContestName = await getContestNameFromApi(contestName, contestUrl);
  9231. if (actualContestName) {
  9232. return actualContestName;
  9233. }
  9234.  
  9235. // 如果使用完整名称没有找到,则尝试使用关键词进行搜索
  9236. const keywords = getKeywords(contestName);
  9237. const maxKeywordAttempts = 1; // 最多尝试到第几个关键词(因为Clist API有频率限制)
  9238. for (let i = 0; i < Math.min(keywords.length, maxKeywordAttempts); i++) {
  9239. actualContestName = await getContestNameFromApi(keywords[i], contestUrl);
  9240. if (actualContestName) {
  9241. return actualContestName;
  9242. }
  9243. }
  9244.  
  9245. // 如果全部尝试后仍没有找到,返回null
  9246. return null;
  9247. }
  9248.  
  9249. /**
  9250. * 从clist API获取比赛题目集的rating
  9251. * @param {string} contestName 比赛名
  9252. * @returns {Promise<Map<string, number>>} 题目rating
  9253. */
  9254. async function getRatingFromApi_contest(contestName, contestUrl) {
  9255. const actualContestName = await getActualContestName(contestName, contestUrl);
  9256. return OJB_promiseRetryWrapper(async () => {
  9257. const options = {
  9258. method: "GET",
  9259. url: `https://clist.by:443/api/v4/contest/?resource_id=93&with_problems=true&event=${encodeURIComponent(actualContestName)}`,
  9260. headers: {
  9261. "Authorization": OJBetter.clist.authorization
  9262. }
  9263. };
  9264.  
  9265. let response = await OJB_GMRequest(options);
  9266.  
  9267. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9268.  
  9269. let data = JSON.parse(response.responseText);
  9270. let objects = data.objects;
  9271. let problemsMap = new Map();
  9272.  
  9273. if (objects.length > 0 && objects[0].problems) {
  9274. objects[0].problems.forEach(problem => {
  9275. problemsMap.set(OJB_cleanLink(problem.url), problem.rating ? problem.rating : NaN);
  9276. });
  9277. }
  9278.  
  9279. return problemsMap;
  9280. }, {
  9281. maxRetries: 5,
  9282. retryInterval: 1000
  9283. });
  9284. }
  9285.  
  9286. /**
  9287. * 根据rating获取对应的颜色class名
  9288. * @param {number} rating 题目rating
  9289. * @returns {string} 颜色class名
  9290. */
  9291. function getClassNameByRating(rating) {
  9292. let className = "rating_by_clist_color9";
  9293. if (Number.isNaN(rating)) {
  9294. className = "rating_by_clist_colorNaN";
  9295. } else {
  9296. let keys = Object.keys(ratingClassMap);
  9297. for (let i = 0; i < keys.length; i++) {
  9298. if (rating < keys[i]) {
  9299. className = ratingClassMap[keys[i - 1]];
  9300. break;
  9301. }
  9302. }
  9303. }
  9304. return className;
  9305. }
  9306.  
  9307. /**
  9308. * problem题目页显示Rating
  9309. * @param {ProblemPageLinkbar} problemToolbar
  9310. * @returns {Promise<void>}
  9311. */
  9312. async function showRatingByClist_problem(problemToolbar) {
  9313. // 题目名
  9314. // const problem = $('.header .title').eq(0).text().replace(/[\s\S]*?. /, '');
  9315. // if (OJBetter.typeOfPage.is_acmsguru) problem = $('h4').eq(0).text().replace(/[\s\S]*?. /, '');
  9316. const url = window.location.href;
  9317. const problemId = getProblemId(url);
  9318.  
  9319. // 创建Rating按钮元素
  9320. creatRatingCss(false);
  9321. // TODO
  9322. // const clistButton = problemToolbar.addLinkButton(
  9323. // 'clistButton',
  9324. // `https://clist.by/problems/?search=${problem}&resource=1`,
  9325. // i18next.t('state.wait', { ns: 'button' }),
  9326. // $("<img>").attr("src", "https://clist.by/static/img/logo-48.png"),
  9327. // "15px"
  9328. // );
  9329. const clistButton = problemToolbar.addLinkButton(
  9330. 'clistButton',
  9331. `https://clist.by/problems/?search=${problemId}&resource=93`,
  9332. i18next.t('state.wait', { ns: 'button' }),
  9333. $("<img>").attr("src", "https://clist.by/static/img/logo-48.png"),
  9334. "15px"
  9335. );
  9336.  
  9337. // 检测clist连接
  9338. if (!await validateClistConnection()) {
  9339. problemToolbar.updateText(clistButton, i18next.t('state.netError', { ns: 'button' }));
  9340. return;
  9341. }
  9342.  
  9343. // 题目链接
  9344. let problem_url = window.location.href;
  9345. if (problem_url.includes('/contest/')) {
  9346. problem_url = problem_url.replace(/\/contest\/(\d+)\/problem\/(\w+)[^\w]*/, '/contest/$1/problem/$2');
  9347. } else {
  9348. problem_url = problem_url.replace(/\/problemset\/problem\/(\d+)\/(\w+)/, '/contest/$1/problem/$2');
  9349. }
  9350. if (OJBetter.typeOfPage.is_mSite) problem_url = problem_url.replace(/\/\/(\w+).codeforces.com/, '//codeforces.com'); // 轻量站
  9351.  
  9352. // 比赛名
  9353. // let contest = $('#sidebar').children().first().find('.rtable th').first().text();
  9354.  
  9355. // rating
  9356. problemToolbar.updateText(clistButton, i18next.t('state.loading', { ns: 'button' }));
  9357. let rating = await getRatingFromApi_problem(problemId, problem_url);
  9358. if (rating) {
  9359. let className = getClassNameByRating(rating);
  9360. problemToolbar.updateText(clistButton, rating);
  9361. problemToolbar.setBold(clistButton);
  9362. problemToolbar.addClass(clistButton, className);
  9363. } else {
  9364. problemToolbar.updateText(clistButton, i18next.t('state.404', { ns: 'button' }));
  9365. problemToolbar.disableButton(clistButton);
  9366. }
  9367. }
  9368.  
  9369. /**
  9370. * contest页显示Rating
  9371. * @returns {Promise<void>}
  9372. */
  9373. async function showRatingByClist_contest() {
  9374. // 创建Rating显示框
  9375. creatRatingCss();
  9376. let ratingBadges = {};
  9377. // $('.datatable .id.left').each(function () {
  9378. // let href = 'https://codeforces.com' + $(this).find('a').attr('href');
  9379. // let badge = OJB_safeCreateJQElement(`<a id="clistButton" class="ratingBadge">${i18next.t('state.wait', { ns: 'button' })}</a>`);
  9380. // $(this).find('a').after(badge);
  9381. // ratingBadges[href] = badge;
  9382. // });
  9383. $('table tbody tr').each(function () {
  9384. let href = 'https://atcoder.jp' + $(this).find('a').attr('href');
  9385. let badge = OJB_safeCreateJQElement(`<a id="clistButton" class="ratingBadge">${i18next.t('state.wait', { ns: 'button' })}</a>`);
  9386. $(this).find('a:first').after(badge);
  9387. ratingBadges[href] = badge;
  9388. });
  9389.  
  9390. // 检测clist连接
  9391. if (!await validateClistConnection()) {
  9392. for (let href in ratingBadges) {
  9393. ratingBadges[href].text('error').addClass('ratingBadge_error');
  9394. }
  9395. return;
  9396. }
  9397.  
  9398. // 显示loading
  9399. for (let href in ratingBadges) {
  9400. ratingBadges[href].text(i18next.t('state.loading', { ns: 'button' })).addClass('ratingBadge_loading');
  9401. }
  9402.  
  9403. // 获取Rating
  9404. // let contestName = $('#sidebar').children().first().find('.rtable th').first().text();
  9405. let contestName = window.location.href.match(/\/contests\/[^\/]*?(\d+)\/tasks/)?.[1];
  9406. // let contestUrl = OJB_cleanLink(window.location.href);
  9407. let contestUrl = OJB_cleanLink(window.location.href.replace(/\/tasks\/?.*$/, ''));
  9408. try {
  9409. let problemsMap = await getRatingFromApi_contest(contestName, contestUrl);
  9410.  
  9411. // 填充数据
  9412. for (let href in ratingBadges) {
  9413. if (problemsMap.has(href)) {
  9414. let rating = problemsMap.get(href);
  9415. let className = getClassNameByRating(rating);
  9416. ratingBadges[href].text(rating).addClass(className);
  9417. } else {
  9418. ratingBadges[href].text(i18next.t('state.404', { ns: 'button' })).addClass('ratingBadge_no');
  9419. }
  9420. }
  9421. } catch (error) {
  9422. // 填充数据
  9423. for (let href in ratingBadges) {
  9424. ratingBadges[href].text(i18next.t('state.netError', { ns: 'button' })).addClass('ratingBadge_no');
  9425. }
  9426. console.warn(error);
  9427. }
  9428. }
  9429.  
  9430. /**
  9431. * problemset页显示Rating
  9432. * @returns {Promise<void>}
  9433. */
  9434. async function showRatingByClist_problemset() {
  9435. creatRatingCss();
  9436. let ratingBadges = [];
  9437. const $problems = $('.problems');
  9438. const $trs = $problems.find('tbody tr:gt(0)');
  9439.  
  9440. // 先创建Rating显示框,并将关系存进数组ratingBadges
  9441. for (let i = 0; i < $trs.length; i++) {
  9442. const $tds = $($trs[i]).find('td');
  9443. const $firstDiv = $($tds[1]).find('div:first');
  9444. let problem = $firstDiv.text();
  9445. let problem_url = $firstDiv.find('a').attr('href');
  9446. problem_url = problem_url.replace(/^\/problemset\/problem\/(\d+)\/(\w+)/, 'https://codeforces.com/contest/$1/problem/$2');
  9447.  
  9448. const ratingBadge = OJB_safeCreateJQElement(`<a id="clistButton" class="ratingBadge"></a>`);
  9449. const rating = OJB_safeCreateJQElement(`<span class="rating">${i18next.t('state.wait', { ns: 'button' })}</span>`);
  9450. ratingBadge.append(rating);
  9451. $($tds[0]).find('a').after(ratingBadge);
  9452. ratingBadges.push({ ratingBadge, rating, problem, problem_url });
  9453. }
  9454.  
  9455. // 检测clist连接
  9456. if (!await validateClistConnection()) {
  9457. for (let i = 0; i < rating.length; i++) {
  9458. ratingBadges[i].rating.text(i18next.t('state.netError', { ns: 'button' }));
  9459. }
  9460. return;
  9461. }
  9462.  
  9463. // 每次只获取3个rating
  9464. for (let i = 0; i < ratingBadges.length; i += 3) {
  9465. const promises = [];
  9466. const endIndex = Math.min(i + 3, ratingBadges.length);
  9467.  
  9468. for (let j = i; j < endIndex; j++) {
  9469. const ratingBadge = ratingBadges[j];
  9470. // 显示请求中
  9471. ratingBadge.rating.text(i18next.t('state.loading', { ns: 'button' }));
  9472. promises.push(getRatingFromHTML(ratingBadge.problem, ratingBadge.problem_url).catch(error => console.warn(error)));
  9473. }
  9474.  
  9475. const results = await Promise.all(promises);
  9476.  
  9477. for (let j = i; j < endIndex; j++) {
  9478. const result = results[j - i];
  9479. const ratingBadge = ratingBadges[j];
  9480. if (result) {
  9481. let className = getClassNameByRating(result.rating);
  9482. ratingBadge.ratingBadge.addClass(className);
  9483. ratingBadge.rating.text(result.rating);
  9484. } else {
  9485. ratingBadge.rating.text(i18next.t('state.404', { ns: 'button' }));
  9486. }
  9487. }
  9488. }
  9489. }
  9490.  
  9491. /**
  9492. * 存放编辑器语言select的值与Monaco语言对应关系的map.
  9493. * @type {Object.<string, string>}
  9494. */
  9495. // const value_monacoLanguageMap = {
  9496. // "4": "pascal", "6": "php", "7": "python", "9": "csharp", "13": "perl", "20": "scala", "31": "python",
  9497. // "32": "go", "34": "javascript", "36": "java", "40": "python", "41": "python", "43": "cpp",
  9498. // "50": "cpp", "51": "pascal", "52": "cpp", "54": "cpp", "55": "javascript", "59": "cpp", "60": "java",
  9499. // "61": "cpp", "65": "csharp", "67": "ruby", "70": "python", "73": "cpp", "74": "java", "75": "rust",
  9500. // "77": "kotlin", "79": "csharp", "80": "cpp", "83": "kotlin", "87": "java"
  9501. // };
  9502. const value_monacoLanguageMap = {
  9503. "5001": "cpp", "5002": "go", "5003": "csharp", "5004": "kotlin", "5005": "java",
  9504. "5006": "nim", "5007": "text", "5008": "text", "5009": "javascript", "5010": "javascript",
  9505. "5011": "r", "5012": "d", "5013": "d", "5014": "swift", "5015": "dart",
  9506. "5016": "php", "5017": "cpp", "5018": "ruby", "5019": "crystal", "5020": "text",
  9507. "5021": "fsharp", "5022": "julia", "5023": "sh", "5024": "text", "5025": "haskell",
  9508. "5026": "fortran", "5027": "lua", "5028": "cpp", "5029": "lisp", "5030": "cobol",
  9509. "5031": "cpp", "5032": "sh", "5033": "python", "5034": "sh", "5035": "text",
  9510. "5036": "text", "5037": "perl", "5038": "sh", "5039": "text", "5040": "text",
  9511. "5041": "pascal", "5042": "csharp", "5043": "lua", "5044": "prolog", "5045": "sh",
  9512. "5046": "scheme", "5047": "scala", "5048": "vbscript", "5049": "text", "5050": "clojure",
  9513. "5051": "erlang", "5052": "typescript", "5053": "cpp", "5054": "rust", "5055": "python",
  9514. "5056": "scala", "5057": "text", "5058": "typescript", "5059": "ocaml", "5060": "raku",
  9515. "5061": "text", "5062": "lisp", "5063": "python", "5064": "clojure", "5065": "text",
  9516. "5066": "text", "5067": "text", "5068": "ada", "5069": "text", "5070": "text",
  9517. "5071": "clojure", "5072": "cpp", "5073": "cpp", "5074": "text", "5075": "lisp",
  9518. "5076": "text", "5077": "d", "5078": "python", "5079": "text", "5080": "text",
  9519. "5081": "ocaml", "5082": "python", "5083": "matlab", "5084": "haxe", "5085": "elixir",
  9520. "5086": "text", "5087": "text", "5088": "lisp", "5089": "text", "5090": "cobol"
  9521. };
  9522.  
  9523. /**
  9524. * 更新代码提交页的HTML
  9525. * @param {string} submitUrl 提交页面的URL
  9526. * @param {string} cacheKey 本地缓存的键名
  9527. * @returns {Promise<jQuery<HTMLElement>>} 返回 jQuery 包装的 HTML 元素
  9528. */
  9529. async function CloneOriginalHTML(submitUrl, cacheKey) {
  9530. return OJB_promiseRetryWrapper(async () => {
  9531. const response = await OJB_GMRequest({
  9532. method: 'GET',
  9533. url: submitUrl
  9534. });
  9535. const html = response.responseText;
  9536. const parser = new DOMParser();
  9537. const doc = parser.parseFromString(html, 'text/html');
  9538. const cloneHTML = $(doc.body).html();
  9539. localStorage.setItem(cacheKey, html);
  9540. return $(cloneHTML);
  9541. }, {
  9542. maxRetries: 5,
  9543. retryInterval: 1000,
  9544. errorHandler: (err) => {
  9545. console.error('A network error occurred while retrieving the HTML for the code submission page.', submitUrl);
  9546. }
  9547. });
  9548. }
  9549.  
  9550. /**
  9551. * 获取代码提交页的HTML元素
  9552. * @param {string} submitUrl
  9553. * @returns {Promise<jQuery>}
  9554. */
  9555. async function getSubmitHTML(submitUrl) {
  9556. const cacheKey = 'OJBetter_CloneOriginalHTML';
  9557. const cookieKey = 'OJBetter_CloneOriginalHTML_time';
  9558. if (OJB_getCookie(cookieKey) === '1') {
  9559. // 存在缓存
  9560. CloneOriginalHTML(submitUrl, cacheKey);
  9561. // 校验
  9562. let cloneHTML = $(localStorage.getItem(cacheKey));
  9563. if (cloneHTML.find('form.submit-form').length > 0) {
  9564. return cloneHTML;
  9565. } else {
  9566. // 存在错误,更新缓存
  9567. console.warn(`Cache error detected!\nattempting to update, cache destination submitUrl:\n${submitUrl}`);
  9568. return await CloneOriginalHTML(submitUrl, cacheKey);
  9569. }
  9570.  
  9571. } else {
  9572. // 没有缓存,更新
  9573. document.cookie = `${cookieKey}=1; path=/`;
  9574. return await CloneOriginalHTML(submitUrl, cacheKey);
  9575. }
  9576. }
  9577.  
  9578. // 代码自动保存
  9579. async function saveCode(url, code) {
  9580. try {
  9581. await OJBetter.common.database.editorCode.put({ url, code });
  9582. return 'Code saved successfully';
  9583. } catch (error) {
  9584. throw new Error('Failed to save code');
  9585. }
  9586. }
  9587.  
  9588. async function getCode(url) {
  9589. try {
  9590. const result = await OJBetter.common.database.editorCode.get(url);
  9591. return result ? result.code : null;
  9592. } catch (error) {
  9593. throw new Error('Failed to get code');
  9594. }
  9595. }
  9596.  
  9597. // 创建代码编辑调试表单元素
  9598. // async function createCodeEditorForm(submitUrl, cloneHTML) {
  9599. async function createCodeEditorForm(submitUrl) {
  9600. // 表单
  9601. let formDiv = $('<form method="post" id="OJBetter_SubmitForm" class="input-output-copier"></form>');
  9602. // $('.ttypography').after(formDiv);
  9603. $('#task-statement').after(formDiv);
  9604. // formDiv.attr('action', submitUrl + "?csrf_token=" + OJBetter.common.at_csrf_token);
  9605. formDiv.attr('action', submitUrl);
  9606. formDiv.attr('method', 'POST');
  9607.  
  9608. // 顶部区域
  9609. let topDiv = OJB_safeCreateJQElement(`<div class="topDiv"></div>`);
  9610. let selectLang = $('#select-lang').clone(); // 语言选择
  9611. // selectLang.css({ 'margin': '10px 0px' }).attr('id', 'programTypeId');
  9612. topDiv.append(selectLang);
  9613. let topRightDiv = OJB_safeCreateJQElement(`<div class="topRightDiv"></div>`);
  9614. topDiv.append(topRightDiv);
  9615. formDiv.append(topDiv);
  9616.  
  9617. // 问题选择/编号
  9618. // let selectProblem = $('<input name="submittedProblemIndex" style="display:none;"></input>');
  9619. // let problemCode;
  9620. // if (OJBetter.typeOfPage.is_acmsguru) {
  9621. // problemCode = $('h4').eq(0).text();
  9622. // let matchResult = problemCode.match(/([A-Z0-9]+)/);
  9623. // problemCode = matchResult[0];
  9624. // } else if (OJBetter.typeOfPage.is_problemset_problem) {
  9625. // let match = window.location.href.match(/\/problem\/([0-9]+?)\/([A-Z0-9]+?)(?!=[A-Z0-9])/);
  9626. // problemCode = match[1] + match[2];
  9627. // selectProblem.attr('name', 'submittedProblemCode');
  9628. // } else {
  9629. // problemCode = $('.header .title').eq(0).text();
  9630. // let matchResult = problemCode.match(/([A-Z0-9]+)/);
  9631. // problemCode = matchResult[0];
  9632. // }
  9633. // selectProblem.val(problemCode);
  9634. let selectProblem = $('input[name="data.TaskScreenName"]').clone();
  9635. formDiv.append(selectProblem);
  9636.  
  9637. // 隐藏的代码记录
  9638. // let sourceDiv = $('<textarea id="sourceCodeTextarea" name="source" style="display: none;"></textarea>');
  9639. let sourceDiv = $('<textarea id="plain-textarea" name="sourceCode" style="display: none;"></textarea>');
  9640. formDiv.append(sourceDiv);
  9641.  
  9642. // 隐藏的crsf token
  9643. let csrfDiv = $(`<input type="hidden" name="csrf_token" value=${OJBetter.common.at_csrf_token}>`);
  9644. formDiv.append(csrfDiv);
  9645.  
  9646. // 代码编辑器
  9647. let editorDiv = $('<div id="OJBetter_editor"></div>');
  9648. formDiv.append(editorDiv);
  9649.  
  9650. // monaco
  9651. let monaco = $('<div id="OJBetter_monaco"></div>');
  9652. editorDiv.append(monaco);
  9653.  
  9654. // 自定义调试
  9655. let customTestDiv = OJB_safeCreateJQElement(`
  9656. <details id="customTestBlock">
  9657. <summary >${i18next.t('customTestBlock.title', { ns: 'codeEditor' })}</summary>
  9658. <div id="customTests" style="min-height: 30px;"></div>
  9659. <div id="control" style="display:flex;">
  9660. <div style="display: flex;margin: 5px;">
  9661. <input type="checkbox" id="onlyCustomTest"}><label for="onlyCustomTest">
  9662. ${i18next.t('customTestBlock.onlyCustom', { ns: 'codeEditor' })}
  9663. </label>
  9664. </div>
  9665. <div style="display: flex;margin: 5px;">
  9666. <input type="checkbox" id="DontShowDiff"}>
  9667. <label for="DontShowDiff">
  9668. ${i18next.t('customTestBlock.DontShowDiff', { ns: 'codeEditor' })}
  9669. </label>
  9670. </div>
  9671. <button type="button" id="addCustomTest">${i18next.t('customTestBlock.add', { ns: 'codeEditor' })}</button>
  9672. </div>
  9673. </details>
  9674. `)
  9675. formDiv.append(customTestDiv);
  9676.  
  9677. // 调试/提交
  9678. let submitDiv = $('<div id="OJBetter_submitDiv"></div>');
  9679. let CompilerArgsInput = $('<input type="text" id="CompilerArgsInput">');
  9680. submitDiv.append(CompilerArgsInput);
  9681.  
  9682. let runButton = OJB_safeCreateJQElement(`
  9683. <button type="button" id="RunTestButton" class="ojb_btn ojb_btn_popover top">
  9684. <i class="iconfont">&#xe6c1;</i>
  9685. <span class="popover_content">${i18next.t('runTestButton.initial', { ns: 'codeEditor' })}</span>
  9686. </button>
  9687. `);
  9688. let submitButton = OJB_safeCreateJQElement(`
  9689. <button id="SubmitButton" class="ojb_btn ojb_btn_popover top" type="submit">
  9690. <i class="iconfont">&#xe633;</i>
  9691. <span class="popover_content">${i18next.t('submitButton', { ns: 'codeEditor' })}</span>
  9692. </button>
  9693. `);
  9694. if (OJBetter.monaco.setting.submitButtonPosition == "bottom") {
  9695. // 添加测试/提交按钮到底部
  9696. submitDiv.append(runButton);
  9697. submitDiv.append(submitButton);
  9698. }
  9699.  
  9700. formDiv.append(submitDiv);
  9701. let CompilerSetting = OJB_safeCreateJQElement(`
  9702. <div id="CompilerSetting"></div>
  9703. `);
  9704. formDiv.append(CompilerSetting);
  9705. let statePanel = OJB_safeCreateJQElement(`
  9706. <div id="statePanel"></div>
  9707. `);
  9708. formDiv.append(statePanel);
  9709.  
  9710. //==================================
  9711. // 去除原有的编辑器
  9712. //==================================
  9713. $('.form-code-submit').remove();
  9714.  
  9715. let from = {
  9716. formDiv: formDiv,
  9717. selectLang: selectLang.find('select:first'),
  9718. topRightDiv: topRightDiv,
  9719. sourceDiv: sourceDiv,
  9720. editorDiv: editorDiv,
  9721. monaco: monaco,
  9722. runButton: runButton,
  9723. submitButton: submitButton,
  9724. submitDiv: submitDiv,
  9725. CompilerSetting: CompilerSetting,
  9726. statePanel: statePanel
  9727. };
  9728. return from;
  9729. }
  9730.  
  9731. // 解析ace格式的补全规则(acwing)
  9732. function parseAceCompleter(rules, range) {
  9733. const suggestions = [];
  9734. if (rules && rules.templates && rules.templates.items) {
  9735. const items = rules.templates.items;
  9736. for (let i = 0; i < items.length; i++) {
  9737. const item = items[i];
  9738. const parts = item.caption.split(' ');
  9739. for (let i = 0; i < parts.length; i++) {
  9740. if (item.value.startsWith(parts[i])) {
  9741. item.value = item.value.replace(parts[i], parts.slice(0, i + 1).join(' '));
  9742. break;
  9743. }
  9744. }
  9745. const completionItem = {
  9746. label: item.caption,
  9747. kind: monaco.languages.CompletionItemKind.Function,
  9748. insertText: item.value,
  9749. range: range
  9750. };
  9751. suggestions.push(completionItem);
  9752. }
  9753. }
  9754. return { suggestions };
  9755. }
  9756.  
  9757. // 解析monaco格式的补全规则
  9758. function parseMonacoCompleter(rules, range) {
  9759. const suggestions_ = [];
  9760. if (rules && rules.suggestions) {
  9761. const suggestion = rules.suggestions;
  9762. for (let i = 0; i < rules.suggestions.length; i++) {
  9763. const item = suggestion[i];
  9764. const completionItem = {
  9765. ...item,
  9766. range: range
  9767. };
  9768. suggestions_.push(completionItem);
  9769. }
  9770. }
  9771. return { suggestions: suggestions_ };
  9772. }
  9773.  
  9774. /**
  9775. * 创建monaco编辑器的一个实例
  9776. */
  9777. async function createMonacoEditor(language, form, support) {
  9778. // 判断monacoLoader是否加载完毕
  9779. await OJB_waitUntilTrue(() => OJBetter.monaco.loaderOnload);
  9780.  
  9781. /**
  9782. * 通用参数
  9783. */
  9784. var id = 0; // 协议中的id标识
  9785. var workspace = language + "_workspace";
  9786. var rootUri = OJBetter.monaco.lsp.workUri + "/" + workspace;
  9787. // 文件名
  9788. var InstanceID = OJB_getRandomNumber(8).toString();
  9789. var filename = language == "java" ? "hello/src/" + InstanceID : InstanceID;
  9790. // 后缀名
  9791. var fileExtension =
  9792. language === "cpp"
  9793. ? ".cpp"
  9794. : language === "python"
  9795. ? ".py"
  9796. : language === "java"
  9797. ? ".java"
  9798. : "";
  9799. var uri = rootUri + "/" + filename + fileExtension;
  9800. var initialized = false; // 是否已初始化
  9801. var serverInfo; // 服务器返回的支持信息
  9802. var model; // model
  9803. var OJBetter_monaco = {};
  9804. window.OJBetter_monaco = OJBetter_monaco; // 全局方法
  9805.  
  9806. /**
  9807. * 一些工具函数
  9808. */
  9809. // 将lsp格式的rang转换为Monaco格式
  9810. OJBetter_monaco.lspRangeToMonacoRange = function (range) {
  9811. const { start, end } = range;
  9812. return new monaco.Range(
  9813. start.line + 1,
  9814. start.character + 1,
  9815. end.line + 1,
  9816. end.character + 1
  9817. );
  9818. };
  9819. // 将Monaco格式的rang转为lsp格式
  9820. OJBetter_monaco.MonacoRangeTolspRange = function (range) {
  9821. return {
  9822. start: {
  9823. line: range.startLineNumber - 1,
  9824. character: range.startColumn - 1,
  9825. },
  9826. end: {
  9827. line: range.endLineNumber - 1,
  9828. character: range.endColumn - 1,
  9829. },
  9830. };
  9831. };
  9832. // 将Monaco格式的position转为lsp格式的
  9833. OJBetter_monaco.MonacoPositionTolspPosition = function (position) {
  9834. return {
  9835. line: position.lineNumber - 1,
  9836. character: position.column - 1,
  9837. };
  9838. };
  9839. // 将Monaco格式的severity转为lsp格式的
  9840. OJBetter_monaco.MonacoSeverityTolspSeverity = function (severity) {
  9841. switch (severity) {
  9842. case 8:
  9843. return 1;
  9844. case 1:
  9845. return 4;
  9846. case 2:
  9847. return 3;
  9848. case 4:
  9849. return 2;
  9850. default:
  9851. return severity;
  9852. }
  9853. };
  9854. // 将lsp格式的severity转为Monaco格式的
  9855. OJBetter_monaco.lspSeverityToMonacoSeverity = function (severity) {
  9856. switch (severity) {
  9857. case 1:
  9858. return 8;
  9859. case 4:
  9860. return 1;
  9861. case 3:
  9862. return 2;
  9863. case 2:
  9864. return 4;
  9865. default:
  9866. return severity;
  9867. }
  9868. };
  9869. // 收集Monaco数据中的rang数据
  9870. OJBetter_monaco.CollectRange = function (item) {
  9871. return {
  9872. startLineNumber: item.startLineNumber,
  9873. startColumn: item.startColumn,
  9874. endLineNumber: item.endLineNumber,
  9875. endColumn: item.endColumn,
  9876. };
  9877. };
  9878. // 收集Monaco position数据中的rang数据
  9879. OJBetter_monaco.CollectRangeByPosition = function (item) {
  9880. var word = model.getWordUntilPosition(item);
  9881. return {
  9882. startLineNumber: item.lineNumber,
  9883. endLineNumber: item.lineNumber,
  9884. startColumn: word.startColumn,
  9885. endColumn: word.endColumn,
  9886. };
  9887. };
  9888. // 将lsp格式的Edit转换为Monaco格式
  9889. OJBetter_monaco.lspEditToMonacoEdit = function (edit) {
  9890. const edits = [];
  9891.  
  9892. if (language == "python") {
  9893. for (const item1 of edit.documentChanges) {
  9894. for (const item2 of item1.edits) {
  9895. const newElement = {
  9896. textEdit: {
  9897. range: OJBetter_monaco.lspRangeToMonacoRange(item2.range),
  9898. text: item2.newText,
  9899. },
  9900. resource: monaco.Uri.parse(item1.textDocument.uri),
  9901. versionId: model.getVersionId(),
  9902. };
  9903. edits.push(newElement);
  9904. }
  9905. }
  9906. } else if (language == "java") {
  9907. for (const item1 in edit.changes) {
  9908. edit.changes[item1].forEach((item2) => {
  9909. const newElement = {
  9910. textEdit: {
  9911. range: OJBetter_monaco.lspRangeToMonacoRange(item2.range),
  9912. text: item2.newText,
  9913. },
  9914. resource: uri,
  9915. versionId: model.getVersionId(),
  9916. };
  9917. edits.push(newElement);
  9918. });
  9919. }
  9920. } else {
  9921. for (const key in edit.changes) {
  9922. const arr = edit.changes[key];
  9923. for (const item of arr) {
  9924. const newElement = {
  9925. textEdit: {
  9926. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  9927. text: item.newText,
  9928. },
  9929. resource: monaco.Uri.parse(key),
  9930. versionId: model.getVersionId(),
  9931. };
  9932. edits.push(newElement);
  9933. }
  9934. }
  9935. }
  9936. return { edits: edits };
  9937. };
  9938.  
  9939. /**
  9940. * 实例化一个editor
  9941. */
  9942. uri = monaco.Uri.file(uri);
  9943. model = monaco.editor.createModel('', language, uri);
  9944. OJBetter.monaco.editor = monaco.editor.create(document.getElementById("OJBetter_monaco"), {
  9945. model: model,
  9946. rootUri: rootUri,
  9947. fontSize: 15,
  9948. tabSize: 4,
  9949. theme: OJBetter.basic.darkMode == "dark" ? "vs-dark" : "vs",
  9950. bracketPairColorization: {
  9951. enabled: true,
  9952. independentColorPoolPerBracketType: true,
  9953. },
  9954. automaticLayout: true,
  9955. lineNumbersMinChars: 3,
  9956. matchOnWordStartOnly: false,
  9957. wordWrap: "on",
  9958. wrappingIndent: "same",
  9959. glyphMargin: true,
  9960. formatOnType: true,
  9961. scrollbar: {
  9962. verticalScrollbarSize: 10,
  9963. horizontalScrollbarSize: 10,
  9964. alwaysConsumeMouseWheel: OJBetter.monaco.setting.alwaysConsumeMouseWheel
  9965. },
  9966. suggest: {
  9967. selectionMode: 'never' // 代码建议不自动选择
  9968. }
  9969. });
  9970.  
  9971. /**
  9972. * 添加快捷功能
  9973. */
  9974. (OJBetter_monaco.addShortCuts = async () => {
  9975. // 从配置信息更新字体大小
  9976. OJBetter.monaco.editor.updateOptions({ fontSize: parseInt(OJBetter.monaco.setting.fontsize) });
  9977.  
  9978. // 调整字体大小
  9979. let changeSize = OJB_safeCreateJQElement(`
  9980. <div class="ojb_btn ojb_btn_popover top">
  9981. <input type="number" id="fontSizeInput" value="${OJBetter.monaco.setting.fontsize}">
  9982. <span class="popover_content">${i18next.t('fontSizeInput', { ns: 'codeEditor' })}</span>
  9983. </div>`)
  9984. form.topRightDiv.append(changeSize);
  9985. changeSize.find('input#fontSizeInput').on('input', function () {
  9986. var size = $(this).val();
  9987. OJBetter.monaco.editor.updateOptions({ fontSize: parseInt(size) });
  9988. GM_setValue('editorFontSize', size);
  9989. });
  9990.  
  9991. // 全屏按钮
  9992. let fullscreenButton = OJB_safeCreateJQElement(`
  9993. <button type="button" class="ojb_btn ojb_btn_popover top">
  9994. <i class="iconfont">&#xe606;</i>
  9995. <span class="popover_content">${i18next.t('fullscreenButton', { ns: 'codeEditor' })}</span>
  9996. </button>
  9997. `);
  9998. form.topRightDiv.append(fullscreenButton);
  9999. fullscreenButton.on('click', enterFullscreen);
  10000.  
  10001. // 固定到底部按钮
  10002. let fixToBottomButton = OJB_safeCreateJQElement(`
  10003. <button type="button" class="ojb_btn ojb_btn_popover top">
  10004. <i class="iconfont">&#xe607;</i>
  10005. <span class="popover_content">${i18next.t('fixToBottomButton', { ns: 'codeEditor' })}</span>
  10006. </button>
  10007. `);
  10008. form.topRightDiv.append(fixToBottomButton);
  10009. fixToBottomButton.on('click', fixToBottom);
  10010.  
  10011. // 固定到右侧按钮
  10012. let fixToRightButton = OJB_safeCreateJQElement(`
  10013. <button type="button" class="ojb_btn ojb_btn_popover top">
  10014. <i class="iconfont">&#xe605;</i>
  10015. <span class="popover_content">${i18next.t('fixToRightButton', { ns: 'codeEditor' })}</span>
  10016. </button>
  10017. `);
  10018. // form.topRightDiv.append(fixToRightButton);
  10019. fixToRightButton.on('click', fixToRight);
  10020.  
  10021. // 添加测试/提交按钮到顶部
  10022. if (OJBetter.monaco.setting.submitButtonPosition == "top") {
  10023. form.topRightDiv.append(form.runButton);
  10024. form.topRightDiv.append(form.submitButton);
  10025. }
  10026.  
  10027. // 选择记忆
  10028. if (!OJBetter.monaco.setting.position_initialized) {
  10029. OJBetter.monaco.setting.position_initialized = true; // 标记是否已经初始化过
  10030. if (OJBetter.monaco.setting.position == "full") {
  10031. fullscreenButton.click();
  10032. } else if (OJBetter.monaco.setting.position == "bottom") {
  10033. fixToBottomButton.click();
  10034. } else if (OJBetter.monaco.setting.position == "right") {
  10035. fixToRightButton.click();
  10036. }
  10037. }
  10038.  
  10039. // 禁用按钮
  10040. function disableButtons() {
  10041. fullscreenButton.prop("disabled", true);
  10042. fixToBottomButton.prop("disabled", true);
  10043. fixToRightButton.prop("disabled", true);
  10044. }
  10045.  
  10046. // 启用按钮
  10047. function enableButtons() {
  10048. fullscreenButton.prop("disabled", false);
  10049. fixToBottomButton.prop("disabled", false);
  10050. fixToRightButton.prop("disabled", false);
  10051. }
  10052.  
  10053. // 进入全屏
  10054. function enterFullscreen() {
  10055. let editor = $('#OJBetter_editor');
  10056. editor.addClass('fullscreen');
  10057.  
  10058. // 取消按钮
  10059. let cancelButton = OJB_safeCreateJQElement(`
  10060. <button type="button" class="ojb_btn ojb_btn_popover top primary exit_button_bottom">
  10061. <i class="iconfont">&#xe60b;</i>
  10062. <span class="popover_content">${i18next.t('exitFullscreenButton', { ns: 'codeEditor' })}</span>
  10063. </button>
  10064. `).on('click', () => exitFullscreen(cancelButton));
  10065. $('body').append(cancelButton);
  10066.  
  10067. disableButtons();
  10068. GM_setValue("monacoEditor_position", "full");
  10069. }
  10070.  
  10071. // 退出全屏
  10072. const exitFullscreen = (cancelButton) => {
  10073. let editor = $('#OJBetter_editor');
  10074. editor.removeClass('fullscreen');
  10075. cancelButton.remove();
  10076. enableButtons();
  10077. GM_setValue("monacoEditor_position", "initial");
  10078. };
  10079.  
  10080. // 固定到底部
  10081. function fixToBottom() {
  10082. let editor = $('#OJBetter_editor');
  10083. editor.addClass('bottom');
  10084.  
  10085. let halfHeight = $(window).height() * 0.5;
  10086. let blankSpace = $('<div>', {
  10087. 'class': 'blank-space',
  10088. 'style': 'height: ' + (halfHeight + 30) + 'px;'
  10089. });
  10090. $('body').append(blankSpace);
  10091.  
  10092. let cancelButton = OJB_safeCreateJQElement(`
  10093. <button type="button" class="ojb_btn ojb_btn_popover top enabled exit_button_bottom">
  10094. <i class="iconfont">&#xe625;</i>
  10095. <span class="popover_content">${i18next.t('cancelFixButton', { ns: 'codeEditor' })}</span>
  10096. </button>
  10097. `).on('click', () => cancelFixingToBottom(cancelButton, blankSpace));
  10098. $('body').append(cancelButton);
  10099.  
  10100. disableButtons();
  10101. GM_setValue("monacoEditor_position", "bottom");
  10102. }
  10103.  
  10104. // 取消固定到底部
  10105. const cancelFixingToBottom = (cancelButton, blankSpace) => {
  10106. let editor = $('#OJBetter_editor');
  10107. editor.removeClass('bottom');
  10108. cancelButton.remove();
  10109. blankSpace.remove();
  10110. enableButtons();
  10111. GM_setValue("monacoEditor_position", "initial");
  10112. };
  10113.  
  10114. // 固定到右侧边栏
  10115. function fixToRight() {
  10116. const sidebar = $('#sidebar').hide();
  10117.  
  10118. // 添加样式
  10119. const styleElement = GM_addStyle(`
  10120. #body {
  10121. min-width: 50vw;
  10122. max-width: 50vw;
  10123. max-height: 100vh;
  10124. overflow-x: hidden;
  10125. overflow-y: auto;
  10126. padding: 1rem;
  10127. box-sizing: border-box;
  10128. }
  10129. body {
  10130. margin: 0px;
  10131. }
  10132. .content-with-sidebar {
  10133. margin-right: 0px !important;
  10134. }
  10135. .menu-list li {
  10136. margin-right: 0.5em;
  10137. }
  10138. .menu-list li a {
  10139. font-size: 1.4rem;
  10140. }
  10141. #OJBetter_editor{
  10142. height: 100vh;
  10143. width: 50vw;
  10144. }
  10145. `);
  10146.  
  10147. // 包装一层div
  10148. $('#body').wrap('<div id="right-side-wrapper" style="display:flex; max-width: 100vw; overflow: hidden;"></div>');
  10149. const blankSpace = $('<div>').appendTo('#right-side-wrapper');
  10150.  
  10151. // 移动编辑器
  10152. const editor = $('#OJBetter_editor').prependTo(blankSpace).addClass('right-side');
  10153.  
  10154. // 取消按钮
  10155. const cancelButton = OJB_safeCreateJQElement(`
  10156. <button type="button" class="ojb_btn ojb_btn_popover top enabled exit_button_bottom">
  10157. <i class="iconfont">&#xe625;</i>
  10158. <span class="popover_content">${i18next.t('cancelFixButton', { ns: 'codeEditor' })}</span>
  10159. </button>
  10160. `).on('click', () => cancelFixingToRight(sidebar, styleElement, editor, cancelButton)).appendTo('body');
  10161.  
  10162. disableButtons();
  10163. GM_setValue("monacoEditor_position", "right");
  10164.  
  10165. // 补丁:修复固定到右侧导致的样例元素.sample-test相关代码重复执行的问题(具体原因未查)
  10166. $('.sample-test').find('.title').each((i, e) => {
  10167. if ($(e).find('.input-output-copier').length > 1) {
  10168. $(e).find('.input-output-copier').first().remove();
  10169. }
  10170. });
  10171. darkModeStyleAdjustment();
  10172. }
  10173.  
  10174. const cancelFixingToRight = (sidebar, styleElement, editor, cancelButton) => {
  10175. sidebar.show();
  10176. // 移回来
  10177. editor.insertAfter(form.sourceDiv).removeClass('right-side');
  10178.  
  10179. // 移除包装
  10180. $('#body').unwrap();
  10181. cancelButton.remove();
  10182. styleElement.remove(); // 移除添加的样式
  10183.  
  10184. enableButtons();
  10185. GM_setValue("monacoEditor_position", "initial");
  10186. }
  10187.  
  10188. // 代码同步与保存
  10189. var nowUrl = window.location.href;
  10190. nowUrl = nowUrl.replace(/#/, ""); // 当页面存在更改时url会多出一个#,去掉
  10191. const code = await getCode(nowUrl);
  10192. if (code) {
  10193. OJBetter.monaco.editor.setValue(code); // 恢复代码
  10194. form.sourceDiv.val(code);
  10195. }
  10196. OJBetter.monaco.editor.onDidChangeModelContent(async () => {
  10197. // 将monaco editor的内容同步到sourceDiv
  10198. const code = OJBetter.monaco.editor.getValue();
  10199. form.sourceDiv.val(code);
  10200. await saveCode(nowUrl, code);
  10201. });
  10202. })();
  10203.  
  10204. /**
  10205. * 注册本地自动补全
  10206. */
  10207. (OJBetter_monaco.RegisterLocalComplet = async () => {
  10208. // 补全器注册函数
  10209. function registMyCompletionItemProvider(language, genre, rule) {
  10210. if (genre == "monaco") {
  10211. monaco.languages.registerCompletionItemProvider(language, {
  10212. provideCompletionItems: function (model, position) {
  10213. return parseMonacoCompleter(rule, OJBetter_monaco.CollectRangeByPosition(position));
  10214. }
  10215. })
  10216. } else if (genre == "ace") {
  10217. monaco.languages.registerCompletionItemProvider(language, {
  10218. provideCompletionItems: function (model, position) {
  10219. return parseAceCompleter(rule, OJBetter_monaco.CollectRangeByPosition(position));
  10220. }
  10221. })
  10222. }
  10223. }
  10224.  
  10225. // 注册acwing cpp 模板
  10226. if (language == "cpp" && OJBetter.monaco.complet.cppCodeTemplate) {
  10227. try {
  10228. var acwing_cpp_code_completer = JSON.parse(GM_getResourceText("acwing_cpp_code_completer"));
  10229. registMyCompletionItemProvider('cpp', 'ace', acwing_cpp_code_completer);
  10230. } catch (error) {
  10231. console.error("Error registering acwing cpp template:", error);
  10232. }
  10233. }
  10234.  
  10235. // 注册自定义的补全
  10236. let complet_length = OJBetter.monaco.complet.customConfig.configurations.length;
  10237. if (complet_length > 0) {
  10238. for (let i = 0; i < complet_length; i++) {
  10239. let item = OJBetter.monaco.complet.customConfig.configurations[i];
  10240. if (item.isChoose && item.language == language) {
  10241. try {
  10242. let rule = await OJB_getExternalJSON(item.jsonUrl);
  10243. registMyCompletionItemProvider(item.language, item.genre, rule);
  10244. } catch (error) {
  10245. console.error(`Error registering custom completer for ${item.language}:`, error);
  10246. }
  10247. }
  10248. }
  10249. }
  10250. })();
  10251.  
  10252. if (!support || !OJBetter.monaco.lsp.enabled) { return; } // 如果不支持lsp,则到此为止
  10253.  
  10254. /**
  10255. * LSP连接状态指示
  10256. */
  10257. const lspStateButton = OJB_safeCreateJQElement(`
  10258. <div id="lspStateDiv" class="ojb_btn ojb_btn_popover top loading">
  10259. <i class="iconfont">&#xe658;</i>
  10260. <span class="popover_content">${i18next.t('lsp.connect', { ns: 'codeEditor' })}</span>
  10261. </div>
  10262. `).on('click', () => {
  10263. OJB_showModal(LSPLogDiv);
  10264. LSPLogDiv.show();
  10265. });
  10266. form.topRightDiv.prepend(lspStateButton);
  10267.  
  10268. const LSPLogDiv = OJB_safeCreateJQElement(`
  10269. <dialog id="LSPLog" style="display: none;">
  10270. <button class="ojb_btn">${i18next.t('close', { ns: 'common' })}</button>
  10271. <div id="LSPLogList" style="overflow: auto;"></div>
  10272. <dialog>`);
  10273. $('body').append(LSPLogDiv);
  10274.  
  10275. const LSPLogList = $('<ul></ul>');
  10276. $('#LSPLogList').append(LSPLogList);
  10277.  
  10278. const closeButton = LSPLogDiv.find('button');
  10279. closeButton.on('click', function () {
  10280. OJB_closeModal(LSPLogDiv);
  10281. });
  10282.  
  10283. /**
  10284. * 推送新的消息到LSP日志中
  10285. * @param {'error' | 'warn' | 'info'} status
  10286. * @param {string} msg
  10287. * @param {boolean} data
  10288. */
  10289. function pushLSPLogMessage(status, msg, data) {
  10290. var li = $('<li>').text('[' + new Date().toLocaleString() + '] ' + msg);
  10291. if (status === 'error') {
  10292. li.attr('style', 'color: #f44336;');
  10293. } else if (status === 'warn') {
  10294. li.attr('style', 'color: #ff9800;');
  10295. } else if (status === 'info') {
  10296. li.attr('style', 'color: #616161;');
  10297. }
  10298. if (data) {
  10299. var jsonText = JSON.stringify(data, null, 2);
  10300. var details = $('<details>');
  10301. var summary = $('<summary>').text('Data');
  10302. var pre = $('<pre>').text(jsonText);
  10303. details.append(summary, pre);
  10304. li.append(details);
  10305. }
  10306. LSPLogList.append(li);
  10307. }
  10308.  
  10309. /**
  10310. * 添加状态底栏
  10311. */
  10312. var statusBar = $('<div id="OJBetter_statusBar">');
  10313. form.editorDiv.append(statusBar);
  10314.  
  10315. /**
  10316. * languageSocket
  10317. */
  10318. var url = OJBetter.monaco.lsp.socketUrl;
  10319. var languageSocket = new WebSocket(url + language);
  10320. OJBetter.monaco.lsp.socket.push(languageSocket);
  10321. var languageSocketState = false;
  10322. var responseHandlers = new Map(); // 映射表,需要等待返回数据的请求 -> 对应的事件触发函数
  10323.  
  10324. languageSocket.onopen = () => {
  10325. languageSocketState = true;
  10326. lspStateButton.setButtonPopover(i18next.t('lsp.waitingAnswer', { ns: 'codeEditor' }));
  10327. pushLSPLogMessage("info", `languageSocket ${i18next.t('lsp.socket.open', { ns: 'logMessage' })}`);
  10328. };
  10329. languageSocket.onmessage = (event) => {
  10330. const message = JSON.parse(event.data);
  10331. if (message.id === 0 && message.result) {
  10332. // 初始化完成
  10333. lspStateButton.setButtonState('success', i18next.t('lsp.connected', { ns: 'codeEditor' }));
  10334. pushLSPLogMessage("info", `Initialization ${i18next.t('lsp.state.finished', { ns: 'logMessage' })}`);
  10335. serverInfo = message.result; // 存下服务器支持信息
  10336. OJBetter_monaco.openDocRequest(); // 打开文档
  10337. if (!OJBetter.monaco.setting.language.includes(language)) {
  10338. OJBetter.monaco.setting.language.push(language);
  10339. OJBetter_monaco.RegistrationAfterInit(); // 注册语言及功能
  10340. } else {
  10341. location.reload(); // 这里有问题,先贴个补丁
  10342. }
  10343. OJBetter_monaco.PassiveReceiveHandler(); // 注册被动接收函数
  10344. } else if (message.id === 0 && message.error) {
  10345. pushLSPLogMessage("warn", `Initialization ${i18next.t('lsp.state.error', { ns: 'logMessage' })}`);
  10346. } else if (message.id !== undefined && responseHandlers.has(message.id)) {
  10347. // 如果收到带有id字段的消息,则回传给对应的事件触发函数
  10348. const handler = responseHandlers.get(message.id);
  10349. if (handler) {
  10350. handler(message);
  10351. responseHandlers.delete(message.id); // 删除已处理的事件触发函数
  10352. }
  10353. } else if (message.method == "textDocument/publishDiagnostics") {
  10354. // 接收代码诊断推送
  10355. OJBetter_monaco.updateMarkers(message);
  10356. } else if (message.method == "workspace/applyEdit") {
  10357. // 应用服务器推送的更改
  10358. OJBetter_monaco.applyEdit(message);
  10359. }
  10360. };
  10361. languageSocket.onerror = (error) => {
  10362. pushLSPLogMessage("error", `languageSocket ${i18next.t('lsp.state.error', { ns: 'logMessage' })}`, error);
  10363. console.warn(`Error connecting to languageSocket: ${error}`)
  10364. };
  10365. languageSocket.onclose = (event) => {
  10366. languageSocketState = false;
  10367. lspStateButton.setButtonState('error', i18next.t('lsp.error', { ns: 'codeEditor' }));
  10368. pushLSPLogMessage("warn", `languageSocket ${i18next.t('lsp.socket.close', { ns: 'logMessage' })}`);
  10369. };
  10370.  
  10371. /**
  10372. * 等待LanguageSocketState
  10373. */
  10374. async function waitForLanguageSocketState() {
  10375. return new Promise((resolve) => {
  10376. const checkInitialized = () => {
  10377. if (languageSocketState) {
  10378. resolve();
  10379. } else {
  10380. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  10381. }
  10382. };
  10383. checkInitialized();
  10384. });
  10385. }
  10386.  
  10387. // 等待lsp响应初始化结果
  10388. async function waitForInitialized() {
  10389. return new Promise((resolve) => {
  10390. const checkInitialized = () => {
  10391. if (initialized) {
  10392. resolve();
  10393. } else {
  10394. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  10395. }
  10396. };
  10397. checkInitialized();
  10398. });
  10399. }
  10400.  
  10401. /**
  10402. * 与languageSocket通信的包装方法
  10403. */
  10404. async function sendMessage(data, requiresResponse, callback) {
  10405. if (!initialized) {
  10406. await waitForInitialized(); // 等待initialized为真
  10407. }
  10408. if (requiresResponse) {
  10409. responseHandlers.set(data.id, callback) // 将事件触发函数与id关联起来
  10410. }
  10411. if (!languageSocketState) await waitForLanguageSocketState();
  10412. languageSocket.send(JSON.stringify(data));
  10413. }
  10414. // 发送消息并等待返回结果
  10415. function fetchData(params, callback) {
  10416. sendMessage(params, true, callback);
  10417. }
  10418. // 发送消息,不需要等待返回结果
  10419. function sendData(data) {
  10420. sendMessage(data, false);
  10421. }
  10422.  
  10423. /**
  10424. * 代码文件更新fileWebSocket
  10425. */
  10426. var fileWebSocket = new WebSocket(url + "file");
  10427. var fileWebSocketState = false;
  10428. OJBetter.monaco.lsp.socket.push(fileWebSocket);
  10429. fileWebSocket.onopen = () => {
  10430. fileWebSocketState = true;
  10431. pushLSPLogMessage("info", `fileWebSocket ${i18next.t('lsp.socket.open', { ns: 'logMessage' })}`);
  10432. };
  10433. fileWebSocket.onclose = (ev) => {
  10434. fileWebSocketState = false;
  10435. pushLSPLogMessage("warn", `fileWebSocket ${i18next.t('lsp.socket.close', { ns: 'logMessage' })}`, ev);
  10436. };
  10437. fileWebSocket.onmessage = (ev) => {
  10438. let message = JSON.parse(ev.data);
  10439. if (message.result !== "ok")
  10440. pushLSPLogMessage("error", `update file failed: ${ev}`);
  10441. };
  10442. fileWebSocket.onerror = (error) => {
  10443. console.warn(`Error connecting to fileWebSocket: ${error}`);
  10444. };
  10445. async function updateFile(workspace, filename, fileExtension, code) {
  10446. async function waitForfileWebSocketState() {
  10447. return new Promise((resolve) => {
  10448. const checkInitialized = () => {
  10449. if (fileWebSocketState) {
  10450. resolve();
  10451. } else {
  10452. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  10453. }
  10454. };
  10455. checkInitialized();
  10456. });
  10457. }
  10458. if (!fileWebSocketState) await waitForfileWebSocketState();
  10459. fileWebSocket.send(
  10460. JSON.stringify({
  10461. type: "update",
  10462. workspace,
  10463. filename,
  10464. fileExtension,
  10465. code,
  10466. })
  10467. );
  10468. }
  10469.  
  10470. /**
  10471. * 发送初始化请求
  10472. */
  10473. OJBetter_monaco.Initialize = () => {
  10474. //初始化initialize
  10475. const capabilities = {
  10476. workspace: {
  10477. applyEdit: true,
  10478. },
  10479. textDocument: {
  10480. publishDiagnostics: {
  10481. relatedInformation: true,
  10482. versionSupport: false,
  10483. tagSupport: {
  10484. valueSet: [1, 2],
  10485. },
  10486. codeDescriptionSupport: true,
  10487. },
  10488. completion: {
  10489. contextSupport: true,
  10490. completionItem: {
  10491. snippetSupport: true,
  10492. commitCharactersSupport: true,
  10493. documentationFormat: ["markdown", "plaintext"],
  10494. deprecatedSupport: true,
  10495. preselectSupport: true,
  10496. tagSupport: {
  10497. valueSet: [1],
  10498. },
  10499. insertReplaceSupport: true,
  10500. resolveSupport: {
  10501. properties: [
  10502. "documentation",
  10503. "detail",
  10504. "additionalTextEdits",
  10505. ],
  10506. },
  10507. insertTextModeSupport: {
  10508. valueSet: [1, 2],
  10509. },
  10510. },
  10511. },
  10512. hover: {
  10513. dynamicRegistration: true,
  10514. contentFormat: ["markdown", "plaintext"],
  10515. },
  10516. signatureHelp: {
  10517. signatureInformation: {
  10518. documentationFormat: ["markdown", "plaintext"],
  10519. parameterInformation: {
  10520. labelOffsetSupport: true,
  10521. },
  10522. activeParameterSupport: true,
  10523. },
  10524. contextSupport: true,
  10525. },
  10526. definition: {
  10527. dynamicRegistration: true,
  10528. linkSupport: true,
  10529. },
  10530. references: {
  10531. dynamicRegistration: true,
  10532. },
  10533. documentHighlight: {
  10534. dynamicRegistration: true,
  10535. },
  10536. codeAction: {
  10537. codeActionLiteralSupport: {
  10538. codeActionKind: {
  10539. valueSet:
  10540. language == "java"
  10541. ? []
  10542. : [
  10543. "",
  10544. "quickfix",
  10545. "refactor",
  10546. "refactor.extract",
  10547. "refactor.inline",
  10548. "refactor.rewrite",
  10549. "source",
  10550. "source.organizeImports",
  10551. ],
  10552. },
  10553. },
  10554. },
  10555. rename: {
  10556. dynamicRegistration: true,
  10557. prepareSupport: true,
  10558. prepareSupportDefaultBehavior: 1,
  10559. honorsChangeAnnotations: true,
  10560. },
  10561. documentLink: {
  10562. tooltipSupport: true,
  10563. },
  10564. typeDefinition: {
  10565. dynamicRegistration: true,
  10566. linkSupport: true,
  10567. },
  10568. implementation: {
  10569. dynamicRegistration: true,
  10570. linkSupport: true,
  10571. },
  10572. colorProvider: {
  10573. dynamicRegistration: true,
  10574. },
  10575. foldingRange: {
  10576. dynamicRegistration: true,
  10577. rangeLimit: 5000,
  10578. lineFoldingOnly: true,
  10579. },
  10580. declaration: {
  10581. dynamicRegistration: true,
  10582. linkSupport: true,
  10583. },
  10584. semanticTokens: {
  10585. dynamicRegistration: true,
  10586. tokenTypes: [
  10587. "namespace",
  10588. "type",
  10589. "class",
  10590. "enum",
  10591. "interface",
  10592. "struct",
  10593. "typeParameter",
  10594. "parameter",
  10595. "variable",
  10596. "property",
  10597. "enumMember",
  10598. "event",
  10599. "function",
  10600. "method",
  10601. "macro",
  10602. "keyword",
  10603. "modifier",
  10604. "comment",
  10605. "string",
  10606. "number",
  10607. "regexp",
  10608. "operator",
  10609. ],
  10610. tokenModifiers: [
  10611. "declaration",
  10612. "definition",
  10613. "readonly",
  10614. "static",
  10615. "deprecated",
  10616. "abstract",
  10617. "async",
  10618. "modification",
  10619. "documentation",
  10620. "defaultLibrary",
  10621. ],
  10622. formats: ["relative"],
  10623. requests: {
  10624. range: true,
  10625. full: {
  10626. delta: true,
  10627. },
  10628. },
  10629. multilineTokenSupport: false,
  10630. overlappingTokenSupport: false,
  10631. },
  10632. callHierarchy: {
  10633. dynamicRegistration: true,
  10634. },
  10635. },
  10636. window: {
  10637. showMessage: {
  10638. messageActionItem: {
  10639. additionalPropertiesSupport: true,
  10640. },
  10641. },
  10642. showDocument: {
  10643. support: true,
  10644. },
  10645. workDoneProgress: true,
  10646. },
  10647. general: {
  10648. regularExpressions: {
  10649. engine: "ECMAScript",
  10650. version: "ES2020",
  10651. },
  10652. markdown: {
  10653. parser: "marked",
  10654. version: "1.1.0",
  10655. },
  10656. },
  10657. };
  10658.  
  10659. const initializeRequest = {
  10660. id: id++,
  10661. jsonrpc: "2.0",
  10662. method: "initialize",
  10663. params: {
  10664. processId: null,
  10665. clientInfo: {
  10666. name: "CFMonaco" + InstanceID,
  10667. },
  10668. locale: "zh-CN",
  10669. rootPath: null,
  10670. rootUri: null,
  10671. capabilities: capabilities,
  10672. trace: "off",
  10673. workspaceFolders: [
  10674. {
  10675. uri:
  10676. "file:///" + OJBetter.monaco.lsp.workUri + workspace,
  10677. name:
  10678. "file:///" + OJBetter.monaco.lsp.workUri + workspace,
  10679. },
  10680. ],
  10681. },
  10682. };
  10683. languageSocket.send(JSON.stringify(initializeRequest));
  10684.  
  10685. // 打开文档函数
  10686. OJBetter_monaco.openDocRequest = function () {
  10687. const initializ = {
  10688. jsonrpc: "2.0",
  10689. method: "initialized",
  10690. params: {},
  10691. };
  10692. languageSocket.send(JSON.stringify(initializ));
  10693. const openDocRequest = {
  10694. jsonrpc: "2.0",
  10695. method: "textDocument/didOpen",
  10696. params: {
  10697. textDocument: {
  10698. uri: model.uri.toString(),
  10699. languageId: language,
  10700. version: model.getVersionId(),
  10701. text: model.getValue(),
  10702. },
  10703. },
  10704. };
  10705. languageSocket.send(JSON.stringify(openDocRequest));
  10706. initialized = true; // 初始化完成,这里确认逻辑待完善
  10707. };
  10708.  
  10709. // 初始化更新文件
  10710. updateFile(workspace, filename, fileExtension, model.getValue());
  10711. }
  10712.  
  10713. /**
  10714. * 注册语言及功能
  10715. */
  10716. OJBetter_monaco.RegistrationAfterInit = () => {
  10717. // 注册语言
  10718. monaco.languages.register({ id: language });
  10719.  
  10720. // 注册"Command"
  10721. (function registerCommand() {
  10722. serverInfo.capabilities.executeCommandProvider.commands.forEach(
  10723. (item) => {
  10724. pushLSPLogMessage("info", `${i18next.t('lsp.server.regist', { ns: 'logMessage' })}`, item);
  10725. monaco.editor.registerCommand(item, (accessor, ...args) => {
  10726. sendData({
  10727. jsonrpc: "2.0",
  10728. id: id++,
  10729. method: "workspace/executeCommand",
  10730. params: {
  10731. command: item,
  10732. arguments: args,
  10733. },
  10734. });
  10735. });
  10736. }
  10737. );
  10738. })();
  10739.  
  10740. // 注册"增量更新"
  10741. model.onDidChangeContent((event) => {
  10742. updateFile(workspace, filename, fileExtension, model.getValue()); // 更新文件
  10743. const changeDocRequest = {
  10744. jsonrpc: "2.0",
  10745. method: "textDocument/didChange",
  10746. params: {
  10747. textDocument: {
  10748. uri: model.uri.toString(),
  10749. version: model.getVersionId(),
  10750. },
  10751. contentChanges: event.changes.map((change) => ({
  10752. range: OJBetter_monaco.MonacoRangeTolspRange(change.range),
  10753. rangeLength: change.rangeLength,
  10754. text: change.text,
  10755. })),
  10756. },
  10757. };
  10758. sendData(changeDocRequest);
  10759. });
  10760.  
  10761. //注册"自动补全"
  10762. monaco.languages.registerCompletionItemProvider(language, {
  10763. provideCompletionItems: (model, position, context) => {
  10764. const request = {
  10765. jsonrpc: "2.0",
  10766. id: id++,
  10767. method: "textDocument/completion",
  10768. params: {
  10769. textDocument: {
  10770. uri: model.uri.toString(),
  10771. },
  10772. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  10773. context: {
  10774. triggerKind: context.triggerKind + 1, // 这里要+1,两边的定义不一样。。。
  10775. triggerCharacter: context.triggerCharacter,
  10776. },
  10777. },
  10778. };
  10779. return new Promise((resolve, reject) => {
  10780. fetchData(request, (response) => {
  10781. const result = response.result;
  10782. pushLSPLogMessage("info", `completion ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10783. if (!result) return resolve(null);
  10784. const CompletionItems = {
  10785. suggestions: result.items.map(
  10786. ({
  10787. label,
  10788. kind,
  10789. filterText,
  10790. insertText,
  10791. insertTextFormat,
  10792. sortText,
  10793. textEdit,
  10794. documentation,
  10795. additionalTextEdits,
  10796. }) => ({
  10797. additionalTextEdits: additionalTextEdits
  10798. ? additionalTextEdits.map(({ newText, range }) => ({
  10799. text: newText,
  10800. range: OJBetter_monaco.lspRangeToMonacoRange(range),
  10801. }))
  10802. : [],
  10803. documentation: documentation ? documentation.value : "",
  10804. filterText,
  10805. insertText: insertText ? insertText : textEdit.newText,
  10806. insertTextRules:
  10807. insertTextFormat === 2
  10808. ? monaco.languages.CompletionItemInsertTextRule
  10809. .InsertAsSnippet
  10810. : monaco.languages.CompletionItemInsertTextRule
  10811. .KeepWhitespace,
  10812. kind,
  10813. label,
  10814. sortText,
  10815. range: textEdit
  10816. ? textEdit.range
  10817. ? OJBetter_monaco.lspRangeToMonacoRange(textEdit.range)
  10818. : OJBetter_monaco.lspRangeToMonacoRange(textEdit.insert)
  10819. : null,
  10820. })
  10821. ),
  10822. };
  10823. pushLSPLogMessage("info", `completion ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, CompletionItems);
  10824. resolve(CompletionItems);
  10825. });
  10826. });
  10827. },
  10828. });
  10829.  
  10830. // 注册"代码修复"
  10831. monaco.languages.registerCodeActionProvider(language, {
  10832. provideCodeActions: (model, range, context) => {
  10833. const request = {
  10834. id: id++,
  10835. jsonrpc: "2.0",
  10836. method: "textDocument/codeAction",
  10837. params: {
  10838. textDocument: {
  10839. uri: model.uri.toString(),
  10840. },
  10841. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  10842. context: {
  10843. diagnostics: context.markers.map((item) => ({
  10844. range: OJBetter_monaco.MonacoRangeTolspRange({
  10845. startLineNumber: item.startLineNumber,
  10846. startColumn: item.startColumn,
  10847. endLineNumber: item.endLineNumber,
  10848. endColumn: item.endColumn,
  10849. }),
  10850. severity: OJBetter_monaco.MonacoSeverityTolspSeverity(
  10851. item.severity
  10852. ),
  10853. code: item.code,
  10854. source: item.source,
  10855. message: item.message,
  10856. tags: item.tags,
  10857. relatedInformation: item.relatedInformation
  10858. ? item.relatedInformation.map((item) => ({
  10859. location: {
  10860. uri: item.resource.toString(),
  10861. range: OJBetter_monaco.MonacoRangeTolspRange({
  10862. startLineNumber: item.startLineNumber,
  10863. startColumn: item.startColumn,
  10864. endLineNumber: item.endLineNumber,
  10865. endColumn: item.endColumn,
  10866. }),
  10867. },
  10868. message: item.message,
  10869. }))
  10870. : null,
  10871. })),
  10872. only: context.only ? [context.only] : [],
  10873. triggerKind: context.trigger,
  10874. },
  10875. },
  10876. };
  10877.  
  10878. return new Promise((resolve, reject) => {
  10879. fetchData(request, (response) => {
  10880. const result = response.result;
  10881. pushLSPLogMessage("info", `codeAction ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10882. if (!result) return resolve(null);
  10883. const codeAction = {
  10884. actions: result.map((item) => ({
  10885. title: item.title,
  10886. kind: item.kind ? item.kind : "quickfix",
  10887. command: item.command
  10888. ? item.command.command
  10889. ? {
  10890. id: item.command.command,
  10891. arguments: item.command.arguments,
  10892. title: item.command.title,
  10893. }
  10894. : null
  10895. : null,
  10896. diagnostics: item.diagnostics
  10897. ? item.diagnostics.map((item) => ({
  10898. code: item.code,
  10899. message: item.message,
  10900. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  10901. severity: OJBetter_monaco.lspSeverityToMonacoSeverity(
  10902. item.severity
  10903. ),
  10904. source: item.source,
  10905. }))
  10906. : null,
  10907. edit: item.edit
  10908. ? OJBetter_monaco.lspEditToMonacoEdit(item.edit)
  10909. : item.arguments
  10910. ? {
  10911. edits: item.arguments.flatMap(
  10912. (item1) => OJBetter_monaco.lspEditToMonacoEdit(item1).edits
  10913. ),
  10914. }
  10915. : null,
  10916. })),
  10917. dispose: () => { },
  10918. };
  10919. pushLSPLogMessage("info", `codeAction ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, codeAction);
  10920.  
  10921. resolve(codeAction);
  10922. });
  10923. });
  10924. },
  10925. });
  10926.  
  10927. // 注册"hover提示"
  10928. monaco.languages.registerHoverProvider(language, {
  10929. provideHover: (model, position) => {
  10930. const request = {
  10931. jsonrpc: "2.0",
  10932. id: id++,
  10933. method: "textDocument/hover",
  10934. params: {
  10935. textDocument: {
  10936. uri: model.uri.toString(),
  10937. },
  10938. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  10939. },
  10940. };
  10941.  
  10942. return new Promise((resolve, reject) => {
  10943. fetchData(request, (response) => {
  10944. pushLSPLogMessage("info", `Hover ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10945. const result = response.result;
  10946.  
  10947. if (!result) return resolve(null);
  10948. const Hover = {
  10949. range: result.range
  10950. ? OJBetter_monaco.lspRangeToMonacoRange(result.range)
  10951. : new monaco.Range(
  10952. position.lineNumber,
  10953. position.column,
  10954. position.lineNumber,
  10955. position.column
  10956. ),
  10957. contents: Array.isArray(result.contents)
  10958. ? result.contents.map((item) => ({
  10959. value: item.value ? item.value : item,
  10960. }))
  10961. : [
  10962. {
  10963. value: result.contents.value,
  10964. },
  10965. ],
  10966. };
  10967. pushLSPLogMessage("info", `Hover ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, Hover);
  10968. resolve(Hover);
  10969. });
  10970. });
  10971. },
  10972. });
  10973.  
  10974. // 注册"inlay提示"
  10975. if (language == "cpp" || language == "java")
  10976. monaco.languages.registerInlayHintsProvider(language, {
  10977. provideInlayHints: (model, range, token) => {
  10978. return new Promise((resolve, reject) => {
  10979. const request = {
  10980. jsonrpc: "2.0",
  10981. id: id++,
  10982. method: "textDocument/inlayHint",
  10983. params: {
  10984. textDocument: {
  10985. uri: model.uri.toString(),
  10986. },
  10987. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  10988. },
  10989. };
  10990.  
  10991. fetchData(request, (response) => {
  10992. const result = response.result;
  10993. pushLSPLogMessage("info", `Inlay Hints ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10994.  
  10995. if (!result) return resolve(null);
  10996.  
  10997. const inlayHints = {
  10998. hints: result.map((item) => {
  10999. return {
  11000. kind: item.kind,
  11001. label: item.label,
  11002. paddingLeft: item.paddingLeft,
  11003. paddingRight: item.paddingRight,
  11004. position: {
  11005. lineNumber: item.position.line + 1,
  11006. column: item.position.character + 1,
  11007. },
  11008. };
  11009. }),
  11010. };
  11011. pushLSPLogMessage("info", `Inlay Hints ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, inlayHints);
  11012.  
  11013. resolve(inlayHints);
  11014. });
  11015. });
  11016. },
  11017. });
  11018.  
  11019. // 注册"转到定义"
  11020. monaco.languages.registerDefinitionProvider(language, {
  11021. provideDefinition: (model, position) => {
  11022. const request = {
  11023. jsonrpc: "2.0",
  11024. id: id++,
  11025. method: "textDocument/definition",
  11026. params: {
  11027. textDocument: {
  11028. uri: model.uri.toString(),
  11029. },
  11030. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11031. },
  11032. };
  11033.  
  11034. return new Promise((resolve, reject) => {
  11035. fetchData(request, (response) => {
  11036. const result = response.result;
  11037. pushLSPLogMessage("info", `definition ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11038.  
  11039. if (result.length == 0) return resolve(null);
  11040. const definition = result.map((item) => ({
  11041. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11042. uri: monaco.Uri.parse(item.uri), //
  11043. }));
  11044. pushLSPLogMessage("info", `definition ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, definition);
  11045.  
  11046. resolve(definition);
  11047. });
  11048. });
  11049.  
  11050. return null; // 如果没有内容,则返回null
  11051. },
  11052. });
  11053.  
  11054. // 注册"转到引用"
  11055. monaco.languages.registerReferenceProvider(language, {
  11056. provideReferences: (model, position, context) => {
  11057. const request = {
  11058. jsonrpc: "2.0",
  11059. id: id++,
  11060. method: "textDocument/references",
  11061. params: {
  11062. context: context,
  11063. textDocument: {
  11064. uri: model.uri.toString(),
  11065. },
  11066. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11067. },
  11068. };
  11069.  
  11070. return new Promise((resolve, reject) => {
  11071. fetchData(request, (response) => {
  11072. const result = response.result;
  11073. pushLSPLogMessage("info", `references ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11074.  
  11075. if (result.length == 0) return resolve([]);
  11076.  
  11077. const references = result.map((item) => ({
  11078. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11079. uri: monaco.Uri.parse(item.uri), //
  11080. }));
  11081. pushLSPLogMessage("info", `references ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, references);
  11082. resolve(references);
  11083. });
  11084. });
  11085. return []; // 如果没有内容,则返回空数组
  11086. },
  11087. });
  11088.  
  11089. // 注册"符号引用点击高亮"
  11090. monaco.languages.registerDocumentHighlightProvider(language, {
  11091. provideDocumentHighlights: (model, position) => {
  11092. const request = {
  11093. jsonrpc: "2.0",
  11094. id: id++,
  11095. method: "textDocument/documentHighlight",
  11096. params: {
  11097. textDocument: {
  11098. uri: model.uri.toString(),
  11099. },
  11100. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11101. },
  11102. };
  11103.  
  11104. return new Promise((resolve, reject) => {
  11105. fetchData(request, (response) => {
  11106. const result = response.result;
  11107. pushLSPLogMessage("info", `documentHighlight ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11108.  
  11109. if (!result || result.length == 0) return resolve([]);
  11110. const highlights = result.map((item) => ({
  11111. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11112. kind: item.kind,
  11113. }));
  11114. pushLSPLogMessage("info",
  11115. `documentHighlight ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`,
  11116. highlights
  11117. );
  11118.  
  11119. resolve(highlights);
  11120. });
  11121. });
  11122. return []; // 如果没有内容,则返回空数组
  11123. },
  11124. });
  11125.  
  11126. // 注册"文件链接"
  11127. if (language == "cpp" || language == "java")
  11128. monaco.languages.registerLinkProvider(language, {
  11129. provideLinks: (model) => {
  11130. const request = {
  11131. jsonrpc: "2.0",
  11132. id: id++,
  11133. method: "textDocument/documentLink",
  11134. params: {
  11135. textDocument: {
  11136. uri: model.uri.toString(),
  11137. },
  11138. },
  11139. };
  11140.  
  11141. return new Promise((resolve, reject) => {
  11142. fetchData(request, (response) => {
  11143. const result = response.result;
  11144. pushLSPLogMessage("info", `DocumentLink ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11145.  
  11146. if (!result) return resolve(null);
  11147. const links = {
  11148. links: result.map((item) => ({
  11149. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11150. url: item.target.toString(),
  11151. tooltip: item.tooltip ? item.tooltip : null,
  11152. })),
  11153. };
  11154. pushLSPLogMessage("info", `DocumentLink ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, links);
  11155. resolve(links);
  11156. });
  11157. });
  11158. },
  11159. });
  11160.  
  11161. // 注册"格式化"
  11162. monaco.languages.registerDocumentFormattingEditProvider(language, {
  11163. provideDocumentFormattingEdits: (model, options, token) => {
  11164. const request = {
  11165. jsonrpc: "2.0",
  11166. id: id++,
  11167. method: "textDocument/formatting",
  11168. params: {
  11169. textDocument: {
  11170. uri: model.uri.toString(),
  11171. },
  11172. options: options,
  11173. },
  11174. };
  11175.  
  11176. return new Promise((resolve, reject) => {
  11177. fetchData(request, (response) => {
  11178. const result = response.result;
  11179. pushLSPLogMessage("info", `formatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11180.  
  11181. const TextEdit = result.map((edit) => ({
  11182. range: OJBetter_monaco.lspRangeToMonacoRange(edit.range),
  11183. text: edit.newText,
  11184. }));
  11185. pushLSPLogMessage("info", `formatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, TextEdit);
  11186. resolve(TextEdit);
  11187. });
  11188. });
  11189. },
  11190. });
  11191.  
  11192. // 注册"部分格式化"
  11193. monaco.languages.registerDocumentRangeFormattingEditProvider(language, {
  11194. provideDocumentRangeFormattingEdits: (model, range, options) => {
  11195. const request = {
  11196. jsonrpc: "2.0",
  11197. id: id++,
  11198. method: "textDocument/rangeFormatting",
  11199. params: {
  11200. textDocument: {
  11201. uri: model.uri.toString(),
  11202. },
  11203. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  11204. options,
  11205. },
  11206. };
  11207.  
  11208. return new Promise((resolve, reject) => {
  11209. fetchData(request, (response) => {
  11210. const result = response.result;
  11211. pushLSPLogMessage("info", `rangeFormatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11212.  
  11213. if (!result || result.length == 0) return resolve([]);
  11214. const edits = result.map((item) => ({
  11215. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11216. text: item.newText,
  11217. }));
  11218. pushLSPLogMessage("info", `rangeFormatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, edits);
  11219. resolve(edits);
  11220. });
  11221. });
  11222. },
  11223. });
  11224.  
  11225. // 注册"重命名"
  11226. monaco.languages.registerRenameProvider(language, {
  11227. provideRenameEdits: (model, position, newName, token) => {
  11228. const request = {
  11229. jsonrpc: "2.0",
  11230. id: id++,
  11231. method: "textDocument/rename",
  11232. params: {
  11233. textDocument: {
  11234. uri: model.uri.toString(),
  11235. },
  11236. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11237. newName: newName,
  11238. },
  11239. };
  11240.  
  11241. return new Promise((resolve, reject) => {
  11242. fetchData(request, (response) => {
  11243. const result = response.result;
  11244. pushLSPLogMessage("info", `rename ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11245.  
  11246. const rename = OJBetter_monaco.lspEditToMonacoEdit(result);
  11247. pushLSPLogMessage("info", `rename ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, rename);
  11248. resolve(rename);
  11249. });
  11250. });
  11251. },
  11252. });
  11253.  
  11254. // 注册"折叠范围分析"
  11255. monaco.languages.registerFoldingRangeProvider(language, {
  11256. provideFoldingRanges: (model) => {
  11257. const request = {
  11258. jsonrpc: "2.0",
  11259. id: id++,
  11260. method: "textDocument/foldingRange",
  11261. params: {
  11262. textDocument: {
  11263. uri: model.uri.toString(),
  11264. },
  11265. },
  11266. };
  11267.  
  11268. return new Promise((resolve, reject) => {
  11269. fetchData(request, (response) => {
  11270. const result = response.result;
  11271. pushLSPLogMessage("info", `FoldingRange ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11272.  
  11273. if (!result) return resolve([]);
  11274. const foldingRanges = result.map((item) => ({
  11275. start: item.startLine + 1,
  11276. end: item.endLine + 1,
  11277. kind: monaco.languages.FoldingRangeKind.fromValue(item.kind),
  11278. }));
  11279. pushLSPLogMessage("info", `FoldingRange ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, foldingRanges);
  11280. resolve(foldingRanges);
  11281. });
  11282. });
  11283. },
  11284. });
  11285.  
  11286. // 注册"方法签名提示"
  11287. monaco.languages.registerSignatureHelpProvider(language, {
  11288. signatureHelpTriggerCharacters:
  11289. serverInfo.capabilities.signatureHelpProvider.triggerCharacters,
  11290. provideSignatureHelp: (model, position, token, context) => {
  11291. const request = {
  11292. jsonrpc: "2.0",
  11293. id: id++,
  11294. method: "textDocument/signatureHelp",
  11295. params: {
  11296. textDocument: {
  11297. uri: model.uri.toString(),
  11298. },
  11299. position: {
  11300. line: position.lineNumber - 1,
  11301. character: position.column - 1,
  11302. },
  11303. context: {
  11304. triggerKind: context.triggerKind,
  11305. triggerCharacter: context.triggerCharacter,
  11306. isRetrigger: context.isRetrigger,
  11307. activeSignatureHelp: context.activeSignatureHelp,
  11308. },
  11309. },
  11310. };
  11311.  
  11312. return new Promise((resolve, reject) => {
  11313. fetchData(request, (response) => {
  11314. const result = response.result;
  11315.  
  11316. pushLSPLogMessage("info", `signatureHelp ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11317.  
  11318. if (!result) return resolve(null);
  11319. const SignatureHelpResult = {
  11320. value: {
  11321. activeParameter: result.activeParameter,
  11322. activeSignature: result.activeSignature,
  11323. signatures: result.signatures,
  11324. },
  11325. dispose: () => { },
  11326. };
  11327.  
  11328. pushLSPLogMessage("info",
  11329. `signatureHelp ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`,
  11330. SignatureHelpResult
  11331. );
  11332. resolve(SignatureHelpResult);
  11333. });
  11334. });
  11335. },
  11336. });
  11337.  
  11338. // 注册"渐进式自动格式化" 如果server有这个
  11339. if (serverInfo.capabilities.documentOnTypeFormattingProvider)
  11340. monaco.languages.registerOnTypeFormattingEditProvider(language, {
  11341. autoFormatTriggerCharacters: [
  11342. serverInfo.capabilities.documentOnTypeFormattingProvider
  11343. .firstTriggerCharacter,
  11344. ],
  11345. provideOnTypeFormattingEdits: (model, position, ch, options) => {
  11346. const request = {
  11347. jsonrpc: "2.0",
  11348. id: id++,
  11349. method: "textDocument/onTypeFormatting",
  11350. params: {
  11351. textDocument: {
  11352. uri: model.uri.toString(),
  11353. },
  11354. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11355. ch,
  11356. options,
  11357. },
  11358. };
  11359.  
  11360. return new Promise((resolve, reject) => {
  11361. fetchData(request, (response) => {
  11362. const result = response.result;
  11363. pushLSPLogMessage("info", `onTypeFormatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11364.  
  11365. if (!result || result.length == 0) return resolve([]);
  11366.  
  11367. const edits = result.map((item) => ({
  11368. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11369. text: item.newText,
  11370. }));
  11371. pushLSPLogMessage("info", `onTypeFormatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, edits);
  11372. resolve(edits);
  11373. });
  11374. });
  11375. },
  11376. });
  11377. };
  11378.  
  11379. /**
  11380. * 被动式接收处理
  11381. */
  11382. OJBetter_monaco.PassiveReceiveHandler = () => {
  11383.  
  11384. // "实时代码诊断"
  11385. OJBetter_monaco.updateMarkers = function (message) {
  11386. const params = message.params;
  11387. pushLSPLogMessage("info", `Markers ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, message);
  11388.  
  11389. if (!params) return;
  11390. const markers = params.diagnostics.map((item1) => ({
  11391. code: item1.code,
  11392. message: item1.message,
  11393. ...OJBetter_monaco.lspRangeToMonacoRange(item1.range),
  11394. relatedInformation: item1.relatedInformation
  11395. ? item1.relatedInformation.map((item2) => ({
  11396. ...(item2.location.range
  11397. ? OJBetter_monaco.lspRangeToMonacoRange(item2.location.range)
  11398. : OJBetter_monaco.lspRangeToMonacoRange(item2.location)),
  11399. message: item2.message,
  11400. resource: monaco.Uri.parse(item2.location.uri),
  11401. }))
  11402. : null,
  11403. severity: OJBetter_monaco.lspSeverityToMonacoSeverity(item1.severity),
  11404. source: item1.source,
  11405. }));
  11406.  
  11407. pushLSPLogMessage("info", `Markers ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, markers);
  11408. monaco.editor.setModelMarkers(model, "eslint", markers);
  11409.  
  11410. // 更新状态底栏信息
  11411. const nowMarks = monaco.editor.getModelMarkers();
  11412. warningCount = 0;
  11413. errorCount = 0;
  11414. for (const marker of nowMarks) {
  11415. if (marker.severity === monaco.MarkerSeverity.Warning) {
  11416. warningCount++;
  11417. } else if (marker.severity === monaco.MarkerSeverity.Error) {
  11418. errorCount++;
  11419. }
  11420. }
  11421. $('#OJBetter_statusBar').text(`Warnings: ${warningCount}, Errors: ${errorCount}`);
  11422. };
  11423.  
  11424. // "应用服务器推送的更改"(代码修复)
  11425. OJBetter_monaco.applyEdit = function (message) {
  11426. const params = message.params;
  11427. pushLSPLogMessage("info", `applyEdit ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, message);
  11428.  
  11429. if (!params) return;
  11430. const operations = Object.values(params.edit.changes)
  11431. .flat()
  11432. .map((item) => ({
  11433. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11434. text: item.newText,
  11435. }));
  11436.  
  11437. pushLSPLogMessage("info", `applyEdit ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, operations);
  11438. model.pushEditOperations([], operations, () => null); // 入栈编辑操作
  11439. };
  11440. }
  11441.  
  11442. if (!languageSocketState) await waitForLanguageSocketState();
  11443. OJBetter_monaco.Initialize();
  11444. }
  11445.  
  11446. // 语言更改
  11447. function changeMonacoLanguage(form) {
  11448. let nowSelect = form.selectLang.val();
  11449.  
  11450. // 这里是因为在Chrome上Select2会莫名其妙触发一次不会改变值的change事件,而在其他浏览器中没有,所以贴个补丁
  11451. if (nowSelect === OJBetter.monaco.nowLangSelect) return;
  11452. else OJBetter.monaco.nowLangSelect = nowSelect;
  11453.  
  11454. // 记忆更改
  11455. GM_setValue('compilerSelection', nowSelect);
  11456. // 销毁旧的编辑器
  11457. try {
  11458. if (OJBetter.monaco.editor) OJBetter.monaco.editor.dispose();
  11459. } catch (error) {
  11460. console.warn("Encountered an error while attempting to delete the old editor, but it should not affect your regular usage.", error)
  11461. }
  11462. // 关闭旧的socket
  11463. OJBetter.monaco.lsp.socket.forEach(socket => {
  11464. socket.close();
  11465. });
  11466. // 移除相关元素
  11467. form.topRightDiv.empty();
  11468. $('#LSPLog').remove();
  11469. $('#OJBetter_statusBar').remove();
  11470. // 创建新的编辑器
  11471. if (nowSelect in value_monacoLanguageMap) {
  11472. let language = value_monacoLanguageMap[nowSelect];
  11473. if (language == "python" || language == "cpp") {
  11474. createMonacoEditor(language, form, true);
  11475. } else {
  11476. createMonacoEditor(language, form, false);
  11477. }
  11478. } else {
  11479. createMonacoEditor(null, form, false);
  11480. }
  11481. // 更新在线编译器参数
  11482. changeCompilerArgs(nowSelect);
  11483. }
  11484.  
  11485. // 收集样例数据
  11486. function getTestData() {
  11487. let testData = {};
  11488.  
  11489. /**
  11490. * 从pre中获取文本信息
  11491. * @param {JQuery<HTMLElement>} node 元素
  11492. * @returns {string} 文本信息
  11493. */
  11494. function getTextFromPre(node) {
  11495. let text;
  11496. if (node.find("br").length > 0) {
  11497. text = node.html().replace(/<br>/g, "\n"); // <br>作换行符的情况
  11498. } else {
  11499. text = node.text();
  11500. }
  11501. return text;
  11502. }
  11503.  
  11504. // $('.input').each(function (index) {
  11505. // var inputText = '';
  11506. // if ($(this).find('pre').find('div').length > 0) {
  11507. // $(this).find('pre').find('div').each(function () {
  11508. // inputText += getTextFromPre($(this)) + '\n';
  11509. // });
  11510. // } else {
  11511. // inputText = getTextFromPre($(this).find('pre'));
  11512. // }
  11513. // var outputText = '';
  11514. // if ($('.output').eq(index).find('pre').find('div').length > 0) {
  11515. // $('.output').eq(index).find('pre').find('div').each(function () {
  11516. // inputText += getTextFromPre($(this)) + '\n';
  11517. // });
  11518. // } else {
  11519. // outputText = getTextFromPre($('.output').eq(index).find('pre'));
  11520. // }
  11521.  
  11522. // testData[index + 1] = {
  11523. // input: inputText.trim(),
  11524. // output: outputText.trim()
  11525. // };
  11526. // });
  11527.  
  11528. // 需要过滤重复的样例(因为有日文英文两个页面,样例元素重复了两遍)
  11529. let uniqueTestData = [];
  11530. const filteredPreElements = $('pre').clone().filter((index, element) => element.id.startsWith('pre-sample'));
  11531. for (let i = 0; i < filteredPreElements.length; i += 2) {
  11532. if (i + 1 < filteredPreElements.length) {
  11533. const inputElement = $(filteredPreElements[i]);
  11534. const outputElement = $(filteredPreElements[i + 1]);
  11535.  
  11536. const inputText = getTextFromPre(inputElement).trim();
  11537. const outputText = getTextFromPre(outputElement).trim();
  11538.  
  11539. // 检查是否已经存在相同的样例
  11540. let isDuplicate = uniqueTestData.some(testCase => testCase.input === inputText && testCase.output === outputText);
  11541. if (!isDuplicate) {
  11542. uniqueTestData.push({
  11543. input: inputText,
  11544. output: outputText
  11545. });
  11546. }
  11547. }
  11548. }
  11549.  
  11550. // 把唯一的测试数据赋值给testData,保持原有的索引风格
  11551. uniqueTestData.forEach((testCase, index) => {
  11552. testData[index + 1] = testCase;
  11553. });
  11554.  
  11555. return testData;
  11556. }
  11557.  
  11558. // 初始化自定义测试数据面板
  11559. function CustomTestInit() {
  11560. const url = window.location.href;
  11561.  
  11562. restoreText();
  11563.  
  11564. // 添加
  11565. $('#addCustomTest').click(function () {
  11566. var sampleDiv = $('<div class="sampleDiv">');
  11567. var inputTextarea = $('<p style="padding: 0px 5px;">input</p><textarea class="dynamicTextarea inputTextarea"></textarea>');
  11568. var outputTextarea = $('<p style="padding: 0px 5px;">output</p><textarea class="dynamicTextarea outputTextarea"></textarea>');
  11569. var deleteCustomTest = OJB_safeCreateJQElement(`<button type="button" class="deleteCustomTest">${closeIcon}</button>`);
  11570. sampleDiv.append(deleteCustomTest);
  11571. sampleDiv.append(inputTextarea);
  11572. sampleDiv.append(outputTextarea);
  11573. $('#customTests').append(sampleDiv);
  11574. });
  11575.  
  11576. // 实时保存文本内容到 IndexedDB 中
  11577. $(document).on('input', '.inputTextarea, .outputTextarea', function () {
  11578. OJBetter.common.database.transaction('rw', OJBetter.common.database.samplesData, function () {
  11579. var objectStore = OJBetter.common.database.samplesData;
  11580. var samples = {
  11581. url: url,
  11582. samples: []
  11583. };
  11584. var index = 0;
  11585. $('.sampleDiv').each(function () {
  11586. var $sampleDiv = $(this);
  11587. var inputTextarea = $sampleDiv.find('.inputTextarea');
  11588. var outputTextarea = $sampleDiv.find('.outputTextarea');
  11589. $sampleDiv.attr('data-index', index);
  11590. inputTextarea.attr('id', 'input' + index);
  11591. outputTextarea.attr('id', 'output' + index);
  11592. var sample = {
  11593. id: index,
  11594. input: inputTextarea.val(),
  11595. output: outputTextarea.val()
  11596. };
  11597. samples.samples.push(sample);
  11598. index++;
  11599. });
  11600. objectStore.put(samples);
  11601. });
  11602. });
  11603.  
  11604. // 删除
  11605. $(document).on('click', '.deleteCustomTest', function () {
  11606. var $sampleDiv = $(this).closest('.sampleDiv');
  11607. OJBetter.common.database.transaction('rw', OJBetter.common.database.samplesData, function () {
  11608. var objectStore = OJBetter.common.database.samplesData;
  11609. var index = parseInt($sampleDiv.attr('data-index'));
  11610. if (!isNaN(index)) {
  11611. objectStore.get(url).then(row => {
  11612. let samples = row.samples;
  11613. samples.splice(index, 1); // 移除第index个元素
  11614. objectStore.put({
  11615. url: url,
  11616. samples: samples
  11617. });
  11618. })
  11619. }
  11620. $sampleDiv.remove();
  11621. });
  11622. });
  11623.  
  11624. // 恢复保存的内容
  11625. function restoreText() {
  11626. OJBetter.common.database.transaction('r', OJBetter.common.database.samplesData, function () {
  11627. return OJBetter.common.database.samplesData.get(url);
  11628. }).then(function (data) {
  11629. if (data.samples && data.samples.length > 0) {
  11630. data.samples.forEach(function (item, index) {
  11631. var sampleDiv = $('<div class="sampleDiv">');
  11632. var inputTextarea = OJB_safeCreateJQElement(`<p style="padding: 0px 5px;">input</p><textarea id="input${index}" class="dynamicTextarea inputTextarea"></textarea>`);
  11633. var outputTextarea = OJB_safeCreateJQElement(`<p style="padding: 0px 5px;">output</p><textarea id="output${index}" class="dynamicTextarea outputTextarea"></textarea>`);
  11634. var deleteCustomTest = OJB_safeCreateJQElement(`<button type="button" class="deleteCustomTest">${closeIcon}</button>`);
  11635.  
  11636. inputTextarea.val(item.input);
  11637. outputTextarea.val(item.output);
  11638.  
  11639. sampleDiv.append(deleteCustomTest);
  11640. sampleDiv.append(inputTextarea);
  11641. sampleDiv.append(outputTextarea);
  11642. sampleDiv.attr('data-index', index)
  11643. $('#customTests').append(sampleDiv);
  11644. });
  11645. }
  11646. });
  11647. }
  11648. }
  11649.  
  11650. // 获取自定义测试数据
  11651. function getCustomTestData() {
  11652. const url = window.location.href;
  11653.  
  11654. return new Promise(function (resolve) {
  11655. var customTestData = {};
  11656. OJBetter.common.database.transaction('r', OJBetter.common.database.samplesData, function () {
  11657. return OJBetter.common.database.samplesData.get(url);
  11658. }).then(function (data) {
  11659. if (!data) resolve(customTestData);
  11660. if (data.samples && data.samples.length > 0) {
  11661. data.samples.forEach(function (item, index) {
  11662. customTestData[index + 1] = {
  11663. input: item.input,
  11664. output: item.output
  11665. };
  11666. });
  11667. }
  11668. resolve(customTestData);
  11669. });
  11670. });
  11671. }
  11672.  
  11673. // codeforces编译器参数列表
  11674. let officialLanguage = "";
  11675. function officialCompilerArgsChange(nowSelect) {
  11676. officialLanguage = nowSelect;
  11677. $('#CompilerArgsInput').prop("disabled", true);
  11678. }
  11679.  
  11680. // codeforces编译器通信
  11681. async function officialCompiler(code, input) {
  11682. // const data = new FormData();
  11683. // data.append('csrf_token', OJBetter.common.cf_csrf_token);
  11684. const data = new URLSearchParams();
  11685. data.append('csrf_token', OJBetter.common.at_csrf_token);
  11686. // data.append('source', code);
  11687. // data.append('tabSize', '4');
  11688. // data.append('programTypeId', officialLanguage);
  11689. data.append('data.LanguageId', officialLanguage);
  11690. data.append('input', input);
  11691. // data.append('output', '');
  11692. // data.append('communityCode', '');
  11693. // data.append('action', 'submitSourceCode');
  11694. // data.append('programTypeId', officialLanguage);
  11695. data.append('sourceCode', code);
  11696.  
  11697. const requestOptions = {
  11698. method: 'POST',
  11699. // url: `${OJBetter.common.hostAddress}/data/customtest`,
  11700. url: `${OJBetter.common.hostAddress}/contests/arc172/custom_test/submit/json`,
  11701. data: data,
  11702. headers: {
  11703. // 'X-Csrf-Token': OJBetter.common.cf_csrf_token
  11704. 'Content-Type': 'application/x-www-form-urlencoded'
  11705. }
  11706. };
  11707.  
  11708. const result = {
  11709. Errors: '',
  11710. Result: '',
  11711. Stats: ''
  11712. };
  11713.  
  11714. try {
  11715. const submitResponse = await OJB_GMRequest(requestOptions);
  11716. // if (submitResponse.status !== 200 || !submitResponse.response) {
  11717. // result.Errors = `${i18next.t('compiler.official.pushError', { ns: 'codeEditor' })}`;
  11718. // return result;
  11719. // }
  11720. if (submitResponse.status !== 200) {
  11721. result.Errors = `${i18next.t('compiler.official.pushError', { ns: 'codeEditor' })}`;
  11722. return result;
  11723. }
  11724.  
  11725. // const submitResult = JSON.parse(submitResponse.response);
  11726. // const customTestSubmitId = submitResult.customTestSubmitId;
  11727.  
  11728. const verdictResponse = await OJB_promiseRetryWrapper(
  11729. getOfficialCompilerVerdict,
  11730. {
  11731. maxRetries: 10,
  11732. retryInterval: 500
  11733. },
  11734. // customTestSubmitId
  11735. );
  11736. return verdictResponse;
  11737. } catch (error) {
  11738. result.Errors = error.message;
  11739. return result;
  11740. }
  11741. }
  11742.  
  11743. // 获取codeforces编译器的执行结果
  11744. // async function getOfficialCompilerVerdict(customTestSubmitId) {
  11745. async function getOfficialCompilerVerdict() {
  11746. // const newdata = new FormData();
  11747. // newdata.append('csrf_token', OJBetter.common.cf_csrf_token);
  11748. // newdata.append('action', 'getVerdict');
  11749. // newdata.append('customTestSubmitId', customTestSubmitId);
  11750.  
  11751. // const requestOptions = {
  11752. // method: 'POST',
  11753. // url: `${OJBetter.common.hostAddress}/data/customtest`,
  11754. // data: newdata,
  11755. // headers: {
  11756. // 'X-Csrf-Token': OJBetter.common.cf_csrf_token
  11757. // }
  11758. // };
  11759. const requestOptions = {
  11760. method: 'GET',
  11761. url: `https://atcoder.jp/contests/arc172/custom_test/json?reload=true`,
  11762. };
  11763.  
  11764. const responseDetails = await OJB_GMRequest(requestOptions);
  11765. if (responseDetails.status !== 200 || !responseDetails.response) {
  11766. throw new Error(`${i18next.t('compiler.official.getResultError', { ns: 'codeEditor' })}`);
  11767. }
  11768.  
  11769. const response = JSON.parse(responseDetails.response);
  11770. // if (!response.stat) {
  11771. // throw new Error('Verdict not ready, retrying...');
  11772. // }
  11773. if (response.Interval) {
  11774. throw new Error('Verdict not ready, retrying...');
  11775. }
  11776.  
  11777. // return {
  11778. // Errors: response.verdict === "OK" ? null : response.verdict + '<br>' + response.output,
  11779. // Result: response.output.replace(/\r\n/g, "\n"),
  11780. // Stats: `Status: ${response.stat}`
  11781. // };
  11782. return {
  11783. Errors: response.Result.ExitCode === "0" ? null : response.Stderr,
  11784. Result: response.Stdout.replace(/\r\n/g, "\n"),
  11785. Stats: `Status: ExitCode: ${response.Result.ExitCode} Exec Time: ${response.Result.TimeConsumption} ms Memory: ${response.Result.MemoryConsumption} KB`
  11786. };
  11787. }
  11788.  
  11789. // rextester编译器参数列表
  11790. let rextesterLanguage = "";
  11791. function rextesterCompilerArgsChange(nowSelect) {
  11792. // let LanguageChoiceList = {
  11793. // "4": "9", "6": "8", "7": "5", "9": "1", "13": "13", "19": "42", "20": "21", "28": "30", "31": "24", "32": "20",
  11794. // "34": "17", "36": "4", "43": "6", "45": "7", "46": "4", "50": "7", "51": "9", "52": "27", "54": "7", "55": "23", "60": "4",
  11795. // "61": "7", "65": "1", "67": "12", "70": "5", "73": "7", "74": "4", "75": "46", "77": "43", "79": "1", "80": "27", "83": "43", "87": "4"
  11796. // }
  11797. let LanguageChoiceList = {
  11798. "5001": "7", "5002": "20", "5003": "1", "5005": "4", "5009": "17", "5016": "8", "5037": "13", "5041": "9",
  11799. "5047": "21", "5055": "5", "5058": "17", "5063": "5", "5078": "5", "5082": "5"
  11800. }
  11801.  
  11802. let CompilerArgsList = {
  11803. "6": "-Wall -std=gnu99 -O2 -o a.out source_file.c",
  11804. "7": "-Wall -std=c++14 -O2 -o a.out source_file.cpp",
  11805. "20": "-o a.out source_file.go",
  11806. "27": "-Wall -std=c++14 -stdlib=libc++ -O2 -o a.out source_file.cpp",
  11807. "30": "source_file.d -ofa.out"
  11808. }
  11809. if (nowSelect in LanguageChoiceList) {
  11810. $('#RunTestButton').prop("disabled", false);
  11811. rextesterLanguage = LanguageChoiceList[nowSelect];
  11812. } else {
  11813. $('#RunTestButton').prop("disabled", true);
  11814. }
  11815. if (rextesterLanguage in CompilerArgsList) {
  11816. const rextesterCompilerArgs = CompilerArgsList[rextesterLanguage];
  11817. $('#CompilerArgsInput').val(rextesterCompilerArgs);
  11818. } else {
  11819. $('#CompilerArgsInput').val("");
  11820. }
  11821. }
  11822.  
  11823. // rextester编译器通信
  11824. async function rextesterCompiler(code, input) {
  11825. try {
  11826. const result = await OJB_promiseRetryWrapper(rextesterCompilerRequest, {
  11827. maxRetries: 5,
  11828. retryInterval: 500,
  11829. errorHandler: (err) => ({ Errors: err.message, Result: '', Stats: '' })
  11830. }, code, input);
  11831. return result;
  11832. } catch (error) {
  11833. return { Errors: error.message, Result: '', Stats: '' };
  11834. }
  11835. }
  11836.  
  11837. // rextester编译器请求方法
  11838. async function rextesterCompilerRequest(code, input) {
  11839. const data = new FormData();
  11840. data.append('LanguageChoiceWrapper', rextesterLanguage);
  11841. data.append('EditorChoiceWrapper', '1');
  11842. data.append('LayoutChoiceWrapper', '1');
  11843. data.append('Program', code);
  11844. data.append('CompilerArgs', $('#CompilerArgsInput').val());
  11845. data.append('Input', input);
  11846. data.append('ShowWarnings', 'false');
  11847. data.append('IsInEditMode', 'false');
  11848. data.append('IsLive', 'false');
  11849.  
  11850. const responseDetails = await OJB_GMRequest({
  11851. method: 'POST',
  11852. url: 'https://rextester.com/rundotnet/Run',
  11853. data: data
  11854. });
  11855.  
  11856. if (responseDetails.status !== 200 || !responseDetails.response) {
  11857. throw new Error(`${i18next.t('compiler.common.error', { ns: 'codeEditor' })}`);
  11858. }
  11859.  
  11860. const response = JSON.parse(responseDetails.response);
  11861. return {
  11862. Errors: response.Errors || '',
  11863. Result: response.Result || '',
  11864. Stats: response.Stats || ''
  11865. };
  11866. }
  11867.  
  11868. // wandbox编译器参数列表
  11869. var wandboxlist = JSON.parse(GM_getResourceText("wandboxlist"));
  11870. function wandboxCompilerArgsChange(nowSelect) {
  11871. // let LanguageChoiceList = {
  11872. // "6": "PHP", "7": "Python", "9": "C#", "12": "Haskell", "13": "Perl", "19": "OCaml",
  11873. // "20": "Scala", "28": "D", "31": "Python", "32": "Go", "34": "JavaScript", "36": "Java", "40": "Python", "41": "Python",
  11874. // "43": "C++", "50": "C++", "51": "Pascal", "52": "C++", "54": "C++", "60": "Java", "61": "C++", "65": "C#", "67": "Ruby",
  11875. // "70": "Python", "73": "C++", "74": "Java", "75": "Rust", "79": "C#", "80": "C++", "87": "Java"
  11876. // }
  11877. let LanguageChoiceList = {
  11878. "5001": "C++", "5002": "Go", "5003": "C#", "5005": "Java", "5009": "JavaScript", "5010": "JavaScript",
  11879. "5012": "D", "5013": "D", "5016": "PHP", "5017": "C++", "5018": "Ruby", "5025": "Haskell",
  11880. "5027": "Lua", "5028": "C++", "5031": "C++", "5037": "Perl", "5041": "Pascal", "5042": "C#",
  11881. "5043": "Lua", "5047": "Scala", "5055": "Python", "5056": "Scala", "5059": "OCaml", "5062": "Lisp",
  11882. "5063": "Python", "5077": "D", "5078": "Python", "5081": "OCaml", "5082": "Python"
  11883. }
  11884.  
  11885. // 移除旧的
  11886. $('#CompilerChange').remove();
  11887.  
  11888. if (nowSelect in LanguageChoiceList) {
  11889. $('#RunTestButton').prop("disabled", false);
  11890. const Languagefiltered = wandboxlist.filter(obj => obj.language === LanguageChoiceList[nowSelect]);
  11891.  
  11892. // 创建编译器下拉框
  11893. let CompilerChange = $('<select id="CompilerChange" style="width: 100%;"></select>');
  11894.  
  11895. $('#CompilerSetting').show().append(CompilerChange);
  11896. for (let i = 0; i < Languagefiltered.length; i++) {
  11897. let Compiler = Languagefiltered[i];
  11898. let op = $("<option></option>")
  11899. .val(Compiler.name)
  11900. .text(Compiler["display-name"] + " " + Compiler.version);
  11901. $("#CompilerChange").append(op);
  11902. }
  11903.  
  11904. // 编译器参数刷新
  11905. function refreshCompilerArgs() {
  11906. var flags = '';
  11907. $("#CompilerBox").find("*").each(function () {
  11908. if ($(this).is("input[type='checkbox']")) {
  11909. let flag = $(this).prop("checked") ? $(this).val() : '';
  11910. flags += flag + (flag ? ' ' : '');
  11911. } else if ($(this).is("select") || $(this).is("input") || $(this).is("textarea")) {
  11912. let flag = $(this).val();
  11913. flags += flag + (flag ? ' ' : '');
  11914. }
  11915. });
  11916. $("#CompilerArgsInput").val(flags);
  11917. $("#CompilerArgsInput").prop("readonly", true); // 只读
  11918. }
  11919.  
  11920. // 编译器切换监听
  11921. CompilerChange.change(function () {
  11922. let selectedName = CompilerChange.val();
  11923. let Compiler = Languagefiltered.find(
  11924. (obj) => obj.name === selectedName
  11925. );
  11926.  
  11927. $("#CompilerArgsInput").val(); // 初始化编译器输入框
  11928.  
  11929. $("#CompilerBox").remove();
  11930. let div = $("<div id='CompilerBox'></div>");
  11931.  
  11932. let display_compile_command = OJB_safeCreateJQElement(`<input id='${Compiler.name}' value='${Compiler['display-compile-command']}' style="display:none;"}></input>`);
  11933. div.append(display_compile_command);
  11934.  
  11935. let switches = Compiler.switches;
  11936. for (let i = 0; i < switches.length; i++) {
  11937. let switche = switches[i];
  11938.  
  11939. if (switche.type == "single") {
  11940. let single = OJB_safeCreateJQElement(`
  11941. <div>
  11942. <input type='checkbox' id='${switche.name}' value='${switche['display-flags']}' ${switche.default ? 'checked' : ''}></input>
  11943. <label for='${switche.name}'>${switche['display-name']}</label>
  11944. </div>
  11945. `);
  11946. div.append(single);
  11947. single.find("input").change(function () {
  11948. refreshCompilerArgs();
  11949. });
  11950. } else if (switche.type == "select") {
  11951. let select = OJB_safeCreateJQElement(`<select id='${switche.name}'></select>`);
  11952. select.data('previousValue', switche.options[0]['display-flags']);
  11953. div.append(select);
  11954. for (let i = 0; i < switche.options.length; i++) {
  11955. let option = switche.options[i];
  11956. let op = $("<option></option>")
  11957. .val(option['display-flags'])
  11958. .text(option['display-name']);
  11959. select.append(op);
  11960. }
  11961. select.change(function () {
  11962. refreshCompilerArgs();
  11963. });
  11964. }
  11965. }
  11966.  
  11967. if (Compiler['compiler-option-raw'] == true) {
  11968. let textarea = OJB_safeCreateJQElement(`<textarea id="compiler_option_raw" placeholder="Raw compiler options" style="resize: vertical;"></textarea>`);
  11969. div.append(textarea);
  11970. textarea.on('input', function () {
  11971. refreshCompilerArgs();
  11972. });
  11973. }
  11974. if (Compiler['runtime-option-raw'] == true) {
  11975. let textarea = OJB_safeCreateJQElement(`<textarea id="runtime_option_raw" placeholder="Raw runtime options" style="resize: vertical;"></textarea>`);
  11976. div.append(textarea);
  11977. textarea.on('input', function () {
  11978. refreshCompilerArgs();
  11979. });
  11980. }
  11981. $("#CompilerSetting").append(div);
  11982.  
  11983. refreshCompilerArgs(); // 初始化
  11984. });
  11985.  
  11986. CompilerChange.trigger("change"); // 初始化
  11987. } else {
  11988. $('#RunTestButton').prop("disabled", true);
  11989. }
  11990. }
  11991.  
  11992. // wandbox编译器通信
  11993. async function wandboxCompiler(code, input) {
  11994. try {
  11995. const result = await OJB_promiseRetryWrapper(wandboxCompilerRequest, {
  11996. maxRetries: 5,
  11997. retryInterval: 500,
  11998. errorHandler: (err) => ({ Errors: err.message, Result: '', Stats: '' })
  11999. }, code, input);
  12000. return result;
  12001. } catch (error) {
  12002. return { Errors: error.message, Result: '', Stats: '' };
  12003. }
  12004. }
  12005.  
  12006. // wandbox编译器请求方法
  12007. async function wandboxCompilerRequest(code, input) {
  12008. const data = {
  12009. code: code,
  12010. codes: [],
  12011. compiler: $('#CompilerChange').val().replace($('#compiler_option_raw').val(), '').replace($('#runtime_option_raw').val(), ''),
  12012. 'compiler-option-raw': $('#compiler_option_raw').val(),
  12013. 'runtime-option-raw': $('#runtime_option_raw').val(),
  12014. options: $("#CompilerArgsInput").val(),
  12015. description: '',
  12016. stdin: input,
  12017. title: ''
  12018. };
  12019.  
  12020. const responseDetails = await OJB_GMRequest({
  12021. method: 'POST',
  12022. url: 'https://wandbox.org/api/compile.json',
  12023. data: JSON.stringify(data),
  12024. headers: {
  12025. 'Content-Type': 'application/json'
  12026. }
  12027. });
  12028.  
  12029. if (responseDetails.status !== 200 || !responseDetails.response) {
  12030. throw new Error(`${i18next.t('compiler.common.error', { ns: 'codeEditor' })}`);
  12031. }
  12032.  
  12033. const response = JSON.parse(responseDetails.response);
  12034. return {
  12035. Errors: response.compiler_error === "" ? response.signal : response.compiler_error,
  12036. Result: response.program_output || '',
  12037. Stats: response.status === "0" ? "Finish" : "Error"
  12038. };
  12039. }
  12040.  
  12041. // 更改编译器参数
  12042. function changeCompilerArgs(nowSelect) {
  12043. if (OJBetter.monaco.onlineCompilerChoice == "official") {
  12044. officialCompilerArgsChange(nowSelect);
  12045. } else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
  12046. rextesterCompilerArgsChange(nowSelect);
  12047. } else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
  12048. wandboxCompilerArgsChange(nowSelect);
  12049. }
  12050. }
  12051.  
  12052. // 在线编译器通信
  12053. async function onlineCompilerConnect(code, input) {
  12054. if (OJBetter.monaco.onlineCompilerChoice == "official") {
  12055. return await officialCompiler(code, input);
  12056. } else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
  12057. return await rextesterCompiler(code, input);
  12058. } else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
  12059. return await wandboxCompiler(code, input);
  12060. }
  12061. }
  12062.  
  12063. // 差异对比
  12064. function codeDiff(expectedText, actualText) {
  12065. // 将文本按行拆分
  12066. const expectedLines = expectedText ? expectedText.split('\n') : [];
  12067. const actualLines = actualText ? actualText.split('\n') : [];
  12068.  
  12069. const output = document.createElement('div');
  12070.  
  12071. const createLineElement = (lineNo, contentElement) => {
  12072. const lineDiv = document.createElement('div');
  12073. lineDiv.className = 'diffLine';
  12074.  
  12075. const lineNoDiv = document.createElement('div');
  12076. lineNoDiv.className = 'lineNo';
  12077. lineNoDiv.textContent = lineNo;
  12078.  
  12079. lineDiv.appendChild(lineNoDiv);
  12080. lineDiv.appendChild(contentElement);
  12081.  
  12082. return lineDiv;
  12083. };
  12084.  
  12085. const createContentElement = (isEquals = true, expected = null, removed = null) => {
  12086. const contentDiv = document.createElement('div');
  12087. contentDiv.className = 'lineContent';
  12088.  
  12089. if (isEquals) {
  12090. const span = document.createElement('span');
  12091. span.textContent = expected;
  12092. contentDiv.appendChild(span);
  12093. } else {
  12094. if (removed != null) {
  12095. const removedSpan = document.createElement('span');
  12096. removedSpan.className = 'removed';
  12097. removedSpan.textContent = removed;
  12098. contentDiv.appendChild(removedSpan);
  12099. }
  12100. if (expected != null) {
  12101. const addedSpan = document.createElement('span');
  12102. addedSpan.className = 'added';
  12103. addedSpan.textContent = expected;
  12104. contentDiv.appendChild(addedSpan);
  12105. }
  12106. }
  12107.  
  12108. return contentDiv;
  12109. };
  12110.  
  12111. let index = 1;
  12112.  
  12113. expectedLines.forEach((expectedLine, i) => {
  12114. const actualLine = actualLines[i];
  12115. if (actualLine === undefined) {
  12116. output.appendChild(createLineElement(index++, createContentElement(false, expectedLine)));
  12117. } else if (expectedLine === actualLine) {
  12118. output.appendChild(createLineElement(index++, createContentElement(true, expectedLine)));
  12119. } else {
  12120. output.appendChild(createLineElement(index++, createContentElement(false, expectedLine, actualLine)));
  12121. }
  12122. });
  12123.  
  12124. // 处理多余的 actualLines
  12125. for (let i = expectedLines.length; i < actualLines.length; i++) {
  12126. output.appendChild(createLineElement(index++, createContentElement(false, null, actualLines[i])));
  12127. }
  12128.  
  12129. return output.innerHTML;
  12130. }
  12131.  
  12132. // 内容类型常量
  12133. const TestCaseContentType = {
  12134. TERMINAL: 'terminal',
  12135. DIFF: 'diff',
  12136. NO_DIFF: 'no_diff',
  12137. SUCCESS: 'success'
  12138. };
  12139.  
  12140. // 样例测试状态类
  12141. class TestCaseStatus {
  12142. constructor(item, prefix) {
  12143. this.testCase = OJB_safeCreateJQElement(`<div class="test-case"></div>`);
  12144. this.item = item;
  12145. this.prefix = prefix;
  12146. this.titleElement = OJB_safeCreateJQElement(`<div class="test-case-title">${this.prefix} ${this.item}</div>`);
  12147. this.statusElement = OJB_safeCreateJQElement(`<div class="test-case-status"></div>`);
  12148. this.contentElement = OJB_safeCreateJQElement(`<div class="test-case-content"></div>`);
  12149. this.judgeElement = OJB_safeCreateJQElement(`<div class="test-case-judge"></div>`);
  12150. this.testCase.append(this.titleElement, this.statusElement, this.contentElement, this.judgeElement);
  12151. $('#statePanel').append(this.testCase);
  12152. this.setStatus('Queued', 'queued');
  12153. }
  12154.  
  12155. setTitle(title) {
  12156. this.titleElement.text(title);
  12157. }
  12158.  
  12159. setStatus(text, status) {
  12160. this.statusElement.text(text).removeClass('queued running success error').addClass(status);
  12161. }
  12162.  
  12163. setContent(content, type) {
  12164. // 如果内容类型为SUCCESS,隐藏内容元素并提前返回
  12165. if (type === TestCaseContentType.SUCCESS) {
  12166. this.contentElement.hide();
  12167. return;
  12168. }
  12169.  
  12170. // 根据内容类型创建内容元素
  12171. const createContentElementByType = (content, type) => {
  12172. let contentElement;
  12173. switch (type) {
  12174. case TestCaseContentType.TERMINAL:
  12175. // 为TERMINAL类型创建一个新的终端容器
  12176. contentElement = OJB_safeCreateJQElement(`<div class="terminal-container" style="overflow: auto;"></div>`);
  12177. break;
  12178. case TestCaseContentType.DIFF:
  12179. case TestCaseContentType.NO_DIFF:
  12180. // 为DIFF和NO_DIFF类型创建相应的内容元素,并添加差异说明
  12181. const className = type === TestCaseContentType.DIFF ? "output_diff" : "output_no_diff";
  12182. contentElement = OJB_safeCreateJQElement(`<pre class="${className}">${content}</pre>`);
  12183. appendDiffNote();
  12184. break;
  12185. default:
  12186. throw new Error("Unsupported content type.");
  12187. }
  12188. return contentElement;
  12189. };
  12190.  
  12191. // 初始化终端
  12192. const initializeTerminal = (content, contentElement) => {
  12193. const term = new Terminal({ rows: 10, cols: 150 });
  12194. term.setOption('theme', { background: '#2d2e2c' });
  12195. term.setOption('convertEol', true); // 将换行符\n转换为\r\n
  12196. term.write(content);
  12197. term.open(contentElement.get(0));
  12198. };
  12199.  
  12200. // 添加差异说明
  12201. const appendDiffNote = () => {
  12202. const diffNote = OJB_safeCreateJQElement(`<div class="diff_note">${i18next.t('resultBlock.diffNote', { ns: 'codeEditor' })}</div>`);
  12203. this.testCase.append(diffNote);
  12204. };
  12205.  
  12206. // 创建并追加内容元素
  12207. const contentElement = createContentElementByType(content, type);
  12208. this.contentElement.append(contentElement);
  12209.  
  12210. // 如果内容类型为TERMINAL,初始化并打开终端
  12211. if (type === TestCaseContentType.TERMINAL) {
  12212. initializeTerminal(content, contentElement);
  12213. }
  12214. }
  12215.  
  12216. setJudge(judge) {
  12217. this.judgeElement.text(judge);
  12218. }
  12219. }
  12220.  
  12221. // 样例测试函数
  12222. async function runCode(event, runButton, sourceDiv, submitDiv) {
  12223. event.preventDefault();
  12224. const statePanel = $('#statePanel').show().empty();
  12225. const testData = getTestData();
  12226. const customTestData = await getCustomTestData();
  12227. const totalTests = Object.keys(customTestData).length + Object.keys(testData).length;
  12228.  
  12229. let passedTests = 0;
  12230. let failedTests = 0;
  12231. let hasError = false;
  12232.  
  12233. // 定义一个对象队列,包括创建的样例块实例和对应的样例数据
  12234. const queue = [];
  12235.  
  12236. // 先生成各个样例的块,并显示排队中,将创建的各个对象存到队列中,以便后面更新
  12237. for (const [item, data] of Object.entries(customTestData)) {
  12238. const testCase = new TestCaseStatus(item, i18next.t('resultBlock.title.custom', { ns: 'codeEditor' }));
  12239. queue.push({ testCase, data });
  12240. }
  12241.  
  12242. if (!$('#onlyCustomTest').prop('checked')) {
  12243. for (const [item, data] of Object.entries(testData)) {
  12244. const testCase = new TestCaseStatus(item, i18next.t('resultBlock.title.sample', { ns: 'codeEditor' }));
  12245. queue.push({ testCase, data });
  12246. }
  12247. }
  12248.  
  12249. // 测试函数
  12250. const runTest = async (testCase, data, index) => {
  12251. runButton.setButtonState('running', `${index}/${totalTests}`);
  12252.  
  12253. testCase.setStatus('Running', 'running');
  12254. const result = await onlineCompilerConnect(sourceDiv.val(), data.input);
  12255.  
  12256. if (result.Errors) {
  12257. testCase.setStatus('Compilation error or Time limit', 'error');
  12258. testCase.setContent(result.Errors, TestCaseContentType.TERMINAL);
  12259. hasError = true;
  12260. } else if (result.Result.trim() === data.output.trim()) {
  12261. testCase.setStatus('Accepted', 'success');
  12262. testCase.setContent('The output is correct.', TestCaseContentType.SUCCESS);
  12263. passedTests++;
  12264. } else {
  12265. testCase.setStatus('Wrong Answer', 'error');
  12266. const diffContent = $('#DontShowDiff').prop('checked') ? result.Result.trim() : codeDiff(data.output.trim(), result.Result.trim());
  12267. const contentType = $('#DontShowDiff').prop('checked') ? TestCaseContentType.NO_DIFF : TestCaseContentType.DIFF;
  12268. testCase.setContent(diffContent, contentType);
  12269. failedTests++;
  12270. }
  12271.  
  12272. const judgeStats = `${i18next.t('resultBlock.state', { ns: 'codeEditor' })}${result.Stats}`;
  12273. testCase.setJudge(judgeStats);
  12274.  
  12275. await OJB_delay(500); // 等待500毫秒
  12276. };
  12277.  
  12278. // 对队列中的对象进行测试
  12279. for (let i = 0; i < queue.length; i++) {
  12280. const { testCase, data } = queue[i];
  12281. await runTest(testCase, data, i + 1);
  12282. }
  12283.  
  12284. // 测试完成后更新按钮状态
  12285. if (hasError) {
  12286. runButton.setButtonState('error', i18next.t('runTestButton.error', { ns: 'codeEditor' }));
  12287. } else if (failedTests > 0) {
  12288. runButton.setButtonState('error', `${passedTests}/${totalTests} ` + i18next.t('runTestButton.partial', { ns: 'codeEditor' }));
  12289. } else {
  12290. runButton.setButtonState('success', i18next.t('runTestButton.success', { ns: 'codeEditor' }));
  12291. }
  12292. }
  12293.  
  12294. /**
  12295. * 添加题目页代码编辑器
  12296. * @returns
  12297. */
  12298. async function addProblemPageCodeEditor() {
  12299. // if (typeof ace === 'undefined') {
  12300. // const loadingMessage = new LoadingMessage();
  12301. // loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('error.codeEditor.load', { ns: 'alert' })}`, 'error');
  12302. // return; // 因为Codeforces设定的是未登录时不能访问提交页,也不会加载ace库
  12303. // }
  12304.  
  12305. // 获取提交页链接
  12306. const href = window.location.href;
  12307. let submitUrl = OJBetter.common.hostAddress + $('.form-code-submit').attr('action');
  12308. // if (/\/problemset\//.test(href)) {
  12309. // // problemset
  12310. // submitUrl = OJBetter.common.hostAddress + '/problemset/submit';
  12311. // } else if (/\/gym\//.test(href)) {
  12312. // // gym 题目
  12313. // submitUrl = OJBetter.common.hostAddress + '/gym/' + ((href) => {
  12314. // const regex = /\/gym\/(?<num>[0-9a-zA-Z]*?)\/problem\//;
  12315. // const match = href.match(regex);
  12316. // return match && match.groups.num;
  12317. // })(href) + '/submit';
  12318. // } else if (OJBetter.typeOfPage.is_acmsguru) {
  12319. // // acmsguru 题目
  12320. // submitUrl = href.replace(/\/problemsets[A-Za-z0-9\/#]*/, "/problemsets/acmsguru/submit");
  12321. // } else {
  12322. // submitUrl = href.replace(/\/problem[A-Za-z0-9\/#]*/, "/submit");
  12323. // }
  12324.  
  12325. // // 获取提交页HTML
  12326. // let cloneHTML = await getSubmitHTML(submitUrl);
  12327.  
  12328. // 创建
  12329. // let form = await createCodeEditorForm(submitUrl, cloneHTML);
  12330. let form = await createCodeEditorForm(submitUrl);
  12331. let selectLang = form.selectLang;
  12332. let submitButton = form.submitButton;
  12333. let runButton = form.runButton;
  12334.  
  12335. // 初始化
  12336. CustomTestInit(); // 自定义测试数据面板
  12337. selectLang.val(OJBetter.monaco.compilerSelection); // 恢复上一次的语言选择
  12338.  
  12339. // 设置语言选择change事件监听器
  12340. selectLang.on('change', () => {
  12341. changeMonacoLanguage(form); // 编辑器语言切换监听
  12342. });
  12343. changeMonacoLanguage(form);
  12344.  
  12345. // 样例测试
  12346. runButton.on('click', (event) => runCode(event, runButton, form.sourceDiv, form.submitDiv))
  12347. .setHoverRedo();
  12348.  
  12349. // 提交
  12350. submitButton.on('click', async function (event) {
  12351. event.preventDefault();
  12352. if (OJBetter.monaco.setting.isCodeSubmitDoubleConfirm) {
  12353. const submit = await OJB_createDialog(
  12354. i18next.t('submitCode.title', { ns: 'dialog' }),
  12355. i18next.t('submitCode.content', { ns: 'dialog' }),
  12356. [
  12357. i18next.t('submitCode.buttons.0', { ns: 'dialog' }),
  12358. i18next.t('submitCode.buttons.1', { ns: 'dialog' })
  12359. ]
  12360. ); //提交确认
  12361. if (submit) {
  12362. submitButton.after(`<img class="OJBetter_loding" src="//codeforces.org/s/84141/images/ajax-loading-24x24.gif">`);
  12363. $('#OJBetter_SubmitForm').submit();
  12364. } else {
  12365. submitButton.addClass('disabled');
  12366. setTimeout(function () {
  12367. submitButton.removeClass('disabled');
  12368. }, 300);
  12369. }
  12370. } else {
  12371. $('#OJBetter_SubmitForm').submit();
  12372. }
  12373. });
  12374. }
  12375.  
  12376. /**
  12377. * 获取翻译服务目标语言的对应代码
  12378. * @param {string} serverName 服务名称
  12379. * @returns {string} 目标语言,如果没有对应代码则返回中文
  12380. */
  12381. function getTargetLanguage(serverName) {
  12382. let targetLanguage = OJBetter.supportList.translationSupport[serverName][OJBetter.translation.targetLang];
  12383. if (targetLanguage) return targetLanguage;
  12384. else return OJBetter.supportList.translationSupport[serverName]['zh'];
  12385. }
  12386.  
  12387. /**
  12388. * 将文本中Markdown格式的加粗**转换成HTML格式。
  12389. * @param {string} text 文本
  12390. * @returns {string} 替换后的字符串
  12391. */
  12392. function convertBoldMarkdownToHTML(text) {
  12393. return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
  12394. }
  12395.  
  12396. /**
  12397. * 将文本中Markdown格式的链接文本转换成HTML格式。
  12398. * @param {string} text 文本
  12399. * @returns {string} 替换后的字符串
  12400. */
  12401. function convertLinksMarkdownToHTML(text) {
  12402. return text.replace(/(?<!!)\[(.*?)\]\(([^"]*?)("(.*?)")*\)/g, '<a href="$2" title="$4">$1</a>');
  12403. }
  12404.  
  12405. /**
  12406. * 将HTML格式的加粗文本转换回Markdown格式。
  12407. * @param {string} text 文本
  12408. * @returns {string} 替换后的字符串
  12409. */
  12410. function convertBoldHTMLToMarkdown(text) {
  12411. return text.replace(/<strong>(.*?)<\/strong>/g, '**$1**');
  12412. }
  12413.  
  12414. /**
  12415. * 将HTML格式的链接文本转换回Markdown格式。
  12416. * @param {string} html - 包含HTML链接标签<a>的字符串。
  12417. * @returns {string} 转换后的字符串,其中HTML链接标签被替换为Markdown的链接语法。
  12418. */
  12419. function convertLinksHTMLToMarkdown(html) {
  12420. return html.replace(/<a href="([^"]*)"( title="([^"]*)")*>([^<]+)<\/a>/g, '[$4]($1 "$3")');
  12421. }
  12422.  
  12423. /**
  12424. * DeepL翻译
  12425. * @param {string} raw 原文
  12426. * @returns {Promise<TransRawData>} 翻译结果对象
  12427. */
  12428. async function translate_deepl(raw) {
  12429. const id = (Math.floor(Math.random() * 99999) + 100000) * 1000;
  12430. const data = {
  12431. jsonrpc: '2.0',
  12432. method: 'LMT_handle_texts',
  12433. id,
  12434. params: {
  12435. splitting: 'newlines',
  12436. lang: {
  12437. source_lang_user_selected: 'auto',
  12438. target_lang: getTargetLanguage('deepl'),
  12439. },
  12440. texts: [{
  12441. text: raw,
  12442. requestAlternatives: 3
  12443. }],
  12444. timestamp: getTimeStamp(raw.split('i').length - 1)
  12445. }
  12446. }
  12447. let postData = JSON.stringify(data);
  12448. if ((id + 5) % 29 === 0 || (id + 3) % 13 === 0) {
  12449. postData = postData.replace('"method":"', '"method" : "');
  12450. } else {
  12451. postData = postData.replace('"method":"', '"method": "');
  12452. }
  12453. const options = {
  12454. method: 'POST',
  12455. url: 'https://www2.deepl.com/jsonrpc',
  12456. data: postData,
  12457. headers: {
  12458. 'Content-Type': 'application/json',
  12459. 'Host': 'www2.deepl.com',
  12460. 'Origin': 'https://www.deepl.com',
  12461. 'Referer': 'https://www.deepl.com/',
  12462. },
  12463. anonymous: true,
  12464. nocache: true,
  12465. }
  12466.  
  12467. return await BaseTranslate(options, res => JSON.parse(res)?.result?.texts?.[0]?.text || res, res => {
  12468. const resObj = {
  12469. status: true,
  12470. message: 'ok'
  12471. };
  12472. if (res.includes('"message":"Too many requests"')) {
  12473. resObj.status = false;
  12474. resObj.message = i18next.t('error.deepl429', { ns: 'translator' }); // Too many requests 提示
  12475. return resObj;
  12476. };
  12477. return resObj;
  12478. });
  12479. }
  12480.  
  12481. /**
  12482. * 使用 DeepL Free API 进行翻译
  12483. * @param {string} raw 原文
  12484. * @returns {Promise<TransRawData>} 翻译结果对象
  12485. */
  12486. async function translate_deepl_api_free(raw) {
  12487. const data = JSON.stringify({
  12488. text: [raw],
  12489. target_lang: getTargetLanguage('deepl'),
  12490. split_sentences: '1',
  12491. ...(OJBetter.deepl.enableEmphasisProtection || OJBetter.deepl.enableLinkProtection ? { tag_handling: 'html' } : {}),
  12492. ...Object.assign({}, ...OJBetter.deepl.config.data)
  12493. });
  12494.  
  12495. const options = {
  12496. method: "POST",
  12497. url: OJBetter.deepl.config.proxy || "https://api-free.deepl.com/v2/translate",
  12498. headers: {
  12499. "Authorization": `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
  12500. "Content-Type": "application/json",
  12501. ...Object.assign({}, ...OJBetter.deepl.config.header)
  12502. },
  12503. data: data,
  12504. onload: response => response.responseText,
  12505. onerror: error => console.error(error)
  12506. };
  12507.  
  12508. return await BaseTranslate(options, res => JSON.parse(res).translations[0].text);
  12509. }
  12510.  
  12511. /**
  12512. * 使用 DeepL Pro API 进行翻译
  12513. * @param {string} raw 原文
  12514. * @returns {Promise<TransRawData>} 翻译结果对象
  12515. */
  12516. async function translate_deepl_api_pro(raw) {
  12517. const data = JSON.stringify({
  12518. text: [raw],
  12519. target_lang: getTargetLanguage('deepl'),
  12520. split_sentences: '1',
  12521. ...(OJBetter.deepl.enableEmphasisProtection || OJBetter.deepl.enableLinkProtection ? { tag_handling: 'html' } : {}),
  12522. ...Object.assign({}, ...OJBetter.deepl.config.data)
  12523. });
  12524.  
  12525. const options = {
  12526. method: "POST",
  12527. url: OJBetter.deepl.config.proxy || "https://api.deepl.com/v2/translate",
  12528. headers: {
  12529. "Authorization": `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
  12530. "Content-Type": "application/json",
  12531. ...Object.assign({}, ...OJBetter.deepl.config.header)
  12532. },
  12533. data: data,
  12534. onload: response => response.responseText,
  12535. onerror: error => console.error(error)
  12536. };
  12537.  
  12538. return await BaseTranslate(options, res => JSON.parse(res).translations[0].text);
  12539. }
  12540.  
  12541. /**
  12542. * 使用 DeepLX 进行翻译
  12543. * @param {String} text 原文
  12544. * @returns {Promise<TransRawData>} 翻译结果对象
  12545. */
  12546. async function translate_deeplx(text) {
  12547. const options = {
  12548. method: "POST",
  12549. url: OJBetter.deepl.config.proxy || 'https://api.deeplx.org/translate',
  12550. data: JSON.stringify({
  12551. "text": text,
  12552. "source_lang": "EN",
  12553. "target_lang": getTargetLanguage('deepl'),
  12554. }),
  12555. headers: {
  12556. 'Content-Type': 'application/json',
  12557. ...(OJBetter.deepl.config.key ? { Authorization: `Bearer ${OJBetter.deepl.config.key}` } : {})
  12558. },
  12559. responseType: "json",
  12560. };
  12561.  
  12562. return await BaseTranslate(options, res => {
  12563. const parsedResponse = JSON.parse(res);
  12564. if (parsedResponse.code === 200 && parsedResponse.data) {
  12565. return parsedResponse.data;
  12566. } else {
  12567. throw new Error('Translation failed or invalid response format.');
  12568. }
  12569. });
  12570. }
  12571.  
  12572. function getTimeStamp(iCount) {
  12573. const ts = Date.now();
  12574. if (iCount !== 0) {
  12575. iCount = iCount + 1;
  12576. return ts - (ts % iCount) + iCount;
  12577. } else {
  12578. return ts;
  12579. }
  12580. }
  12581.  
  12582. /**
  12583. * 讯飞听见翻译
  12584. * @param {String} text 要翻译的文本
  12585. * @returns {Promise<TransRawData>} 翻译结果对象
  12586. */
  12587. async function translate_iflyrec(text) {
  12588. const options = {
  12589. method: "POST",
  12590. url: 'https://www.iflyrec.com/TranslationService/v1/textTranslation',
  12591. data: JSON.stringify({
  12592. "from": "2",
  12593. "to": getTargetLanguage('iflyrec'),
  12594. "contents": [{
  12595. "text": text,
  12596. "frontBlankLine": 0
  12597. }]
  12598. }),
  12599. anonymous: true,
  12600. headers: {
  12601. 'Content-Type': 'application/json',
  12602. 'Origin': 'https://www.iflyrec.com',
  12603. },
  12604. responseType: "json",
  12605. };
  12606. return await BaseTranslate(options, res => JSON.parse(res).biz[0].translateResult.replace(/\\n/g, "\n\n"));
  12607. }
  12608.  
  12609. /**
  12610. * 有道翻译
  12611. * @param {string} raw 原文
  12612. * @returns {Promise<TransRawData>} 翻译结果对象
  12613. */
  12614. async function translate_youdao_mobile(raw) {
  12615. const options = {
  12616. method: "POST",
  12617. url: 'http://m.youdao.com/translate',
  12618. data: "inputtext=" + encodeURIComponent(raw) + "&type=" + getTargetLanguage('youdao'),
  12619. anonymous: true,
  12620. headers: {
  12621. "Content-Type": "application/x-www-form-urlencoded",
  12622. 'Host': 'm.youdao.com',
  12623. 'Origin': 'http://m.youdao.com',
  12624. 'Referer': 'http://m.youdao.com/translate',
  12625. }
  12626. }
  12627. return await BaseTranslate(options,
  12628. res => {
  12629. const array = /id="translateResult">\s*?<li>([\s\S]*?)<\/li>\s*?<\/ul/.exec(res);
  12630. if (array && array.length > 1) {
  12631. return array[1];
  12632. } else {
  12633. return res;
  12634. }
  12635. },
  12636. res => {
  12637. const resObj = {
  12638. status: true,
  12639. message: 'ok'
  12640. };
  12641. if (res.includes('<title>413 Request Entity Too Large</title>')) {
  12642. resObj.status = false;
  12643. resObj.message = i18next.t('error.youdao413', { ns: 'translator' }); // Request Entity Too Large 提示
  12644. return resObj;
  12645. };
  12646. return resObj;
  12647. })
  12648. }
  12649.  
  12650. /**
  12651. * google翻译
  12652. * @param {string} raw 原文
  12653. * @returns {Promise<TransRawData>} 翻译结果对象
  12654. */
  12655. async function translate_gg(raw) {
  12656. const params = `tl=${getTargetLanguage('google')}&q=${encodeURIComponent(raw)}`;
  12657. const options = {
  12658. method: "GET",
  12659. url: `https://translate.google.com/m?${params}`,
  12660. }
  12661. return await BaseTranslate(options,
  12662. res => $(res).filter('.result-container').text() || $(res).find('.result-container').text());
  12663. }
  12664.  
  12665. /**
  12666. * 彩云翻译
  12667. * @param {string} raw 原文
  12668. * @returns {Promise<TransRawData>} 翻译结果对象
  12669. */
  12670. async function translate_caiyun(raw) {
  12671. const source = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";
  12672. const dic = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"].reduce((dic, current, index) => { dic[current] = source[index]; return dic }, {});
  12673. const browser_id = CryptoJS.MD5(Math.random().toString()).toString();
  12674. const caiyun_jwt = await (async () => {
  12675. const options = {
  12676. method: "POST",
  12677. url: 'https://api.interpreter.caiyunai.com/v1/user/jwt/generate',
  12678. headers: {
  12679. "content-type": "application/json",
  12680. "x-authorization": "token:qgemv4jr1y38jyq6vhvi",
  12681. "origin": "https://fanyi.caiyunapp.com",
  12682. },
  12683. data: JSON.stringify({ browser_id }),
  12684. }
  12685. const res = await OJB_GMRequest(options);
  12686. return JSON.parse(res.responseText).jwt;
  12687. })();
  12688.  
  12689. // 解码
  12690. const decodeUnicode = str => {
  12691. const decoder = new TextDecoder();
  12692. const data = Uint8Array.from(atob(str), c => c.charCodeAt(0));
  12693. return decoder.decode(data);
  12694. };
  12695. const decoder = line => decodeUnicode([...line].map(i => dic[i] || i).join(""));
  12696.  
  12697. const options = {
  12698. method: "POST",
  12699. url: 'https://api.interpreter.caiyunai.com/v1/translator',
  12700. data: JSON.stringify({
  12701. "source": raw.split('\n'),
  12702. "browser_id": browser_id,
  12703. "trans_type": getTargetLanguage('caiyun'),
  12704. "request_id": "web_fanyi",
  12705. "media": "text",
  12706. "os_type": "web",
  12707. "dict": true,
  12708. "cached": true,
  12709. "replaced": true,
  12710. "style": "formal",
  12711. "model": "",
  12712. "detect": true,
  12713. }),
  12714. headers: {
  12715. "content-type": "application/json;charset=UTF-8",
  12716. "x-authorization": "token:qgemv4jr1y38jyq6vhvi",
  12717. "t-authorization": caiyun_jwt
  12718. }
  12719. }
  12720. return await BaseTranslate(options, res => JSON.parse(res).target.map(decoder).join('\n'))
  12721. }
  12722.  
  12723. /**
  12724. * ChatGPT
  12725. * @param {string} raw 原文
  12726. * @returns {Promise<TransRawData>} 翻译结果对象
  12727. */
  12728. async function translate_openai(raw) {
  12729. const modelDefault = 'gpt-3.5-turbo';
  12730. const lang = getTargetLanguage('openai');
  12731. const prompt = `
  12732. As a professional English translator, your task is to accurately translate a segment of an algorithm programming competition question into ${lang}.
  12733. The translation should use professional terms and maintain the text format, including ${OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru
  12734. ? "keeping the LaTeX equations unchanged."
  12735. : "keeping the brackets【】, HTML tags, and their content unchanged."
  12736. }
  12737. After translation, please ensure that the ${lang} version conforms to normal expression habits.
  12738. What I need is a carefully polished ${lang} translation of my question segment, The segment to be translated is as follows: "
  12739. ${raw}
  12740. "`;
  12741. const data = {
  12742. model: OJBetter.chatgpt.config.model || modelDefault,
  12743. messages: [{
  12744. role: "assistant",
  12745. content: prompt
  12746. }],
  12747. temperature: 0.7,
  12748. ...Object.assign({}, ...OJBetter.chatgpt.config.data)
  12749. }
  12750. const options = {
  12751. method: "POST",
  12752. url: OJBetter.chatgpt.config.proxy || 'https://api.openai.com/v1/chat/completions',
  12753. data: JSON.stringify(data),
  12754. responseType: 'json',
  12755. headers: {
  12756. 'Content-Type': 'application/json',
  12757. 'Authorization': 'Bearer ' + OJBetter.chatgpt.config.key,
  12758. ...Object.assign({}, ...OJBetter.chatgpt.config.header)
  12759. }
  12760. }
  12761. return await BaseTranslate(options,
  12762. res => res,
  12763. undefined,
  12764. response => response.response.choices[0].message.content);
  12765. }
  12766.  
  12767. /**
  12768. * ChatGPT 流式传输
  12769. * @param {string} raw 原文
  12770. * @param {TranslateDiv} translateDiv 翻译结果面板
  12771. * @returns {Promise<TransRawData>} 翻译结果对象
  12772. */
  12773. async function translate_openai_stream(raw, translateDiv) {
  12774. const result = {
  12775. done: true,
  12776. checkPassed: null,
  12777. response: null,
  12778. responseText: null,
  12779. text: "",
  12780. error: null,
  12781. message: null
  12782. };
  12783. const helpText = i18next.t('error.basic', { ns: 'translator' }); // 基本帮助提示信息
  12784. try {
  12785. for await (const delta of openai_stream(raw)) {
  12786. result.text += delta;
  12787. // 翻译结果面板更新
  12788. translateDiv.updateTranslateDiv(result.text, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru), false);
  12789. }
  12790. return result;
  12791. } catch (err) {
  12792. console.warn(err);
  12793. result.error = {
  12794. message: err.message || null,
  12795. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  12796. enumerable: err,
  12797. source: 'openai_stream'
  12798. };
  12799. result.message = `${i18next.t('error.GMRequest', { ns: 'translator' })}${helpText}`;
  12800. }
  12801.  
  12802. return result;
  12803. }
  12804.  
  12805. /**
  12806. * 流式传输
  12807. * @param {string} raw 原文
  12808. * @returns {AsyncGenerator<string>} 返回 AsyncGenerator
  12809. */
  12810. async function* openai_stream(raw) {
  12811. const modelDefault = 'gpt-3.5-turbo';
  12812. const lang = getTargetLanguage('openai');
  12813. const prompt = `
  12814. I hope you can act as a professional English translator to help me translate a segment of an algorithm programming competition question into ${lang}.
  12815. During the translation process, I would like you to use more professional terms and maintain the text format, ${OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru
  12816. ? "keeping the LaTeX equations unchanged."
  12817. : "keeping the brackets【】, HTML tags, and their content unchanged."
  12818. }
  12819. After completing the translation, please polish the ${lang} version to ensure it conforms to normal expression habits.
  12820. What I need is a carefully polished ${lang} translation of my question segment, which is as follows:
  12821. "
  12822. ${raw}
  12823. "`;
  12824. const data = {
  12825. model: OJBetter.chatgpt.config.model || modelDefault,
  12826. messages: [{
  12827. role: "assistant",
  12828. content: prompt
  12829. }],
  12830. temperature: 0.7,
  12831. stream: true,
  12832. ...Object.assign({}, ...OJBetter.chatgpt.config.data)
  12833. }
  12834. const options = {
  12835. method: "POST",
  12836. url: OJBetter.chatgpt.config.proxy || 'https://api.openai.com/v1/chat/completions',
  12837. data: JSON.stringify(data),
  12838. responseType: 'stream',
  12839. headers: {
  12840. 'Content-Type': 'application/json',
  12841. 'Authorization': 'Bearer ' + OJBetter.chatgpt.config.key,
  12842. ...Object.assign({}, ...OJBetter.chatgpt.config.header)
  12843. }
  12844. }
  12845. const response = await OJB_GMRequest(options, true);
  12846. const reader = response.response.getReader();
  12847. const decoder = new TextDecoder();
  12848. let buffer = ''; // 用于累积数据片段的缓冲区
  12849.  
  12850. while (true) {
  12851. const { done, value } = await reader.read();
  12852. if (done) break;
  12853. buffer += decoder.decode(value, { stream: true }); // 将新的数据片段追加到缓冲区
  12854. let lines = buffer.split("\n\n"); // 处理累积的数据
  12855.  
  12856. // 缓冲区的最后一行可能还未完整接收,保留在缓冲区中,-1
  12857. for (let i = 0; i < lines.length - 1; i++) {
  12858. let line = lines[i];
  12859. line = line.substring(5); // 移除 'data:' 前缀
  12860. if (line.includes('[DONE]')) {
  12861. return; // End
  12862. }
  12863. try {
  12864. let data = JSON.parse(line);
  12865. let delta = data['choices'][0]['delta'];
  12866. let content = delta['content'] ? delta['content'] : "";
  12867. yield content; // 传递数据给调用者
  12868. } catch (error) {
  12869. console.warn(`Error parsing JSON: ${error}\n\nError data: ${line}`);
  12870. }
  12871. }
  12872.  
  12873. // 保留最后一行在缓冲区中
  12874. buffer = lines.slice(-1);
  12875. }
  12876.  
  12877. return buffer;
  12878. }
  12879.  
  12880. /**
  12881. * @typedef {Object} CheckResponseResult
  12882. * @property {boolean} status 检查是否通过
  12883. * @property {string} message 检查失败时的消息
  12884. */
  12885.  
  12886. /**
  12887. * @typedef {Object} ErrorResponse
  12888. * @property {Object} message 错误消息
  12889. * @property {Object} stack 错误堆栈
  12890. * @property {Object} enumerable 可枚举的错误属性
  12891. * @property {string} source 错误来源
  12892. */
  12893.  
  12894. /**
  12895. * @typedef {Object} TransRawData
  12896. * @property {boolean} done 操作是否完成
  12897. * @property {CheckResponseResult|null} checkPassed 检查是否通过的结果
  12898. * @property {Object|null} response 响应对象
  12899. * @property {string|null} text 处理后的文本
  12900. * @property {ErrorResponse} error 错误列表
  12901. * @property {string|null} message 可能的消息
  12902. */
  12903.  
  12904. /**
  12905. * 通用翻译函数
  12906. * @param {Object} options GM_xmlhttpRequest 的参数
  12907. * @param {Function} processer 响应再处理函数,它接收响应文本,并应返回处理后的文本。
  12908. * @param {Function} checkResponse 检查文本是否符合预期的函数,它接收文本,并返回一个Object,包含状态和信息。默认为返回 { status: true, message: 'ok' }
  12909. * @param {Function} getResponseText 重写响应文本获取函数,它接收response,并返回响应文本。 默认为 response.responseText
  12910. * @returns {Promise<TransRawData>} 返回 Promise,其解析值为翻译结果对象
  12911. */
  12912. async function BaseTranslate(options, processer, checkResponse = () => { return { status: true, message: 'ok' } }, getResponseText = (response) => response.responseText) {
  12913. const result = {
  12914. done: false,
  12915. checkPassed: null,
  12916. response: null,
  12917. responseText: null,
  12918. text: "",
  12919. error: null,
  12920. message: null
  12921. };
  12922. const helpText = i18next.t('error.basic', { ns: 'translator' }); // 基本帮助提示信息
  12923. const toDo = async () => {
  12924. try {
  12925. result.response = await OJB_GMRequest(options);
  12926. result.responseText = result.response.responseText;
  12927. result.text = getResponseText(result.response);
  12928. } catch (err) {
  12929. console.warn(err);
  12930. result.error = {
  12931. message: err.message || null,
  12932. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  12933. enumerable: err,
  12934. source: 'GMRequest'
  12935. };
  12936. result.message = `${i18next.t('error.GMRequest', { ns: 'translator' })}${helpText}`;
  12937. throw result;
  12938. }
  12939. try {
  12940. result.text = processer(result.text);
  12941. } catch (err) {
  12942. console.warn(err);
  12943. result.error = {
  12944. message: err.message || null,
  12945. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  12946. enumerable: err,
  12947. source: 'processer'
  12948. };
  12949. result.message = `${i18next.t('error.processer', { ns: 'translator' })}${helpText}`;
  12950. throw result;
  12951. }
  12952. try {
  12953. result.checkPassed = checkResponse(result.text);
  12954. if (result.checkPassed.status) result.done = true;
  12955. else result.message = result.checkPassed.message;
  12956. return result;
  12957. } catch (err) {
  12958. console.warn(err);
  12959. result.error = {
  12960. message: err.message || null,
  12961. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  12962. enumerable: err,
  12963. source: 'checkResponse'
  12964. };
  12965. result.message = `${i18next.t('error.checkResponse', { ns: 'translator' })}${helpText}`;
  12966. throw result;
  12967. }
  12968. };
  12969.  
  12970. return await OJB_promiseRetryWrapper(toDo, {
  12971. maxRetries: 3,
  12972. errorHandler: (err, maxRetries, attemptsLeft) => {
  12973. const detailedError = {
  12974. maxRetries: maxRetries,
  12975. attemptsLeft: attemptsLeft,
  12976. ...err
  12977. };
  12978. return detailedError;
  12979. }
  12980. });
  12981. }
  12982.  
  12983. /**
  12984. * 查询服务余额
  12985. * @param {Object} quotaConfig - 配额配置对象
  12986. * @returns {Promise} 返回包含余额信息的 Promise
  12987. */
  12988. async function queryServerBalance(quotaConfig) {
  12989. // 确保传入了有效的配置对象
  12990. if (!quotaConfig || !quotaConfig.url) {
  12991. return Promise.reject(new Error('Quota configuration is missing.'));
  12992. }
  12993.  
  12994. // 准备请求选项
  12995. const requestOptions = {
  12996. method: quotaConfig.method || 'GET',
  12997. url: quotaConfig.url,
  12998. headers: {
  12999. ...Object.assign({}, ...quotaConfig.header)
  13000. },
  13001. data: JSON.stringify({ ...Object.assign({}, ...quotaConfig.data) })
  13002. };
  13003.  
  13004. // 发送请求并返回 Promise
  13005. return OJB_GMRequest(requestOptions).then(response => {
  13006. try {
  13007. const responseData = JSON.parse(response.responseText);
  13008. // 从响应数据中提取余额
  13009. const surplusPath = quotaConfig.surplus;
  13010. const surplusValue = OJB_evaluatePathOrExpression(responseData, surplusPath);
  13011. return surplusValue;
  13012. } catch (error) {
  13013. return Promise.reject(new Error('Failed to parse balance response.'));
  13014. }
  13015. }).catch(error => {
  13016. console.warn('Error querying balance:', error);
  13017. return Promise.reject(error);
  13018. });
  13019. }
  13020.  
  13021. /**
  13022. * 确认 jQuery 已加载
  13023. * @param {number} retryDelay 重试延迟(毫秒)
  13024. * @returns {Promise<void>}
  13025. */
  13026. async function ensureJQueryIsLoaded(retryDelay = 50) {
  13027. while (typeof jQuery === 'undefined') {
  13028. console.warn(`JQuery is not loaded. Retry after ${retryDelay} ms.`);
  13029. await OJB_delay(retryDelay);
  13030. retryDelay = Math.min(retryDelay * 2, 2000);
  13031. }
  13032. }
  13033.  
  13034. /**
  13035. * 加载必须的函数
  13036. * @returns {Promise} 加载提示信息
  13037. */
  13038. async function loadRequiredFunctions() {
  13039. await initVar();// 初始化全局变量
  13040. return Promise.allSettled([
  13041. initDB(), // 连接数据库
  13042. initI18next(), // i18next初始化
  13043. initButtonFunc(), // 加载按钮相关函数
  13044. initHTML2MarkDown(), // 初始化html2markdown转换器
  13045. checkScriptVersion(), // 更新检查
  13046. // ...(OJBetter.typeOfPage.is_acmsguru ? [acmsguruReblock()] : []) // 为acmsguru题面重新划分div
  13047. ]);
  13048. }
  13049.  
  13050. /**
  13051. * DOM加载后即可执行
  13052. */
  13053. function initOnDOMReady() {
  13054. showAnnounce(); // 显示公告
  13055. showWarnMessage(); // 显示警告消息
  13056. initSettingsPanel(); // 加载设置按钮面板
  13057. initMonacoEditor(); // 初始化monaco编辑器资源
  13058. localizeWebsite(); // 网站本地化替换
  13059. addDependencyStyles(); // 添加一些依赖库的样式
  13060. addI18nStyles(); // 添加包含i18n内容的样式
  13061. // if (OJBetter.basic.expandFoldingblocks) ExpandFoldingblocks(); // 折叠块展开
  13062. // if (OJBetter.basic.renderPerfOpt) RenderPerfOpt(); // 折叠块渲染优化
  13063. // if (OJBetter.basic.selectElementPerfOpt) SelectElementPerfOpt(); // 下拉选择框性能优化
  13064. if (OJBetter.typeOfPage.is_problem) {
  13065. const problemPageLinkbar = new ProblemPageLinkbar(); // 创建题目页相关链接栏
  13066. if (OJBetter.basic.showCF2vjudge) CF2vjudge(problemPageLinkbar); // 跳转到Vjudge按钮
  13067. if (OJBetter.basic.showJumpToLuogu) CF2luogu(problemPageLinkbar); // 跳转到洛谷按钮
  13068. if (OJBetter.clist.enabled.problem) showRatingByClist_problem(problemPageLinkbar); // problem页显示Rating
  13069. }
  13070. if (OJBetter.typeOfPage.is_contest) {
  13071. if (OJBetter.clist.enabled.contest) showRatingByClist_contest(); // contest页显示Rating
  13072. }
  13073. // if (OJBetter.typeOfPage.is_problemset) {
  13074. // if (OJBetter.clist.enabled.problemset) showRatingByClist_problemset(); // problemset页显示Rating
  13075. // }
  13076. if (OJBetter.typeOfPage.is_problem && OJBetter.monaco.enableOnProblemPage) {
  13077. addProblemPageCodeEditor(); // 添加题目页代码编辑器
  13078. }
  13079. }
  13080.  
  13081. /**
  13082. * 需要在页面资源完全加载后执行的函数
  13083. */
  13084. function onResourcesReady(loadingMessage) {
  13085. if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('loadFunc', { ns: 'alert' })}`);
  13086. initializeInParallel(loadingMessage);
  13087. initializeSequentially(loadingMessage);
  13088. }
  13089.  
  13090. /**
  13091. * 可以异步并行的函数
  13092. */
  13093. function initializeInParallel(loadingMessage) {
  13094. if (OJBetter.basic.darkMode == "dark") darkModeStyleAdjustment(); // 黑暗模式额外的处理事件
  13095. // if (OJBetter.basic.commentPaging) CommentPagination(); // 评论区分页
  13096. if (OJBetter.translation.comment.transMode == "2") multiChoiceTranslation(); // 选段翻译支持
  13097. if (OJBetter.monaco.beautifyPreBlocks) beautifyPreBlocksWithMonaco(); // 美化Pre代码块
  13098. }
  13099.  
  13100. /**
  13101. * 必须按序执行的函数
  13102. */
  13103. async function initializeSequentially(loadingMessage) {
  13104. await addConversionButton(); // 添加MD/复制/翻译按钮
  13105. if ((OJBetter.typeOfPage.is_problem || OJBetter.typeOfPage.is_completeProblemset) && OJBetter.translation.memory.enabled) {
  13106. await initTransResultsRecover(); // 翻译结果恢复功能初始化
  13107. }
  13108. // if (OJBetter.translation.auto.enabled) {
  13109. // await initTransWhenViewable(); // 自动翻译
  13110. // }
  13111. // if (OJBetter.basic.standingsRecolor && OJBetter.typeOfPage.is_cfStandings) {
  13112. // await recolorStandings(); // cf赛制榜单重新着色
  13113. // }
  13114. if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('loadSuccess', { ns: 'alert' })}`, 'success', 3000);
  13115. }
  13116.  
  13117. /**
  13118. * 主方法
  13119. */
  13120. async function main() {
  13121. await ensureJQueryIsLoaded(); // 等待jQuery加载
  13122. const loadingMessage = new LoadingMessage();
  13123. await loadRequiredFunctions(); // 加载必须的函数
  13124. initOnDOMReady(); // DOM加载后即可执行的函数
  13125. if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('onload', { ns: 'alert' })}`);
  13126.  
  13127. // 检查页面资源是否已经完全加载
  13128. if (OJBetter.state.notWaiteLoaded) {
  13129. onResourcesReady(loadingMessage);
  13130. } else {
  13131. if (document.readyState === 'complete') {
  13132. onResourcesReady(loadingMessage);
  13133. } else {
  13134. window.addEventListener('load', () => onResourcesReady(loadingMessage));
  13135. }
  13136. }
  13137. };
  13138.  
  13139. // ------------------------------
  13140. // 脚本加载入口
  13141. if (document.readyState === 'loading') {
  13142. document.addEventListener("DOMContentLoaded", main);
  13143. } else {
  13144. main(); // 如果DOMContentLoaded已经触发,立即执行
  13145. }
  13146. // ------------------------------
  13147.  
  13148. // ------------------------------
  13149. // 配置自动迁移代码(将在10个小版本后移除-1.19)
  13150. // ------------------------------
  13151.  
  13152. if (GM_getValue("openai_key") || GM_getValue("api2d_key")) {
  13153. const newConfig = { "choice": -1, "configurations": [] };
  13154. if (GM_getValue("openai_key")) {
  13155. let config1 = {
  13156. "note": "我的配置1",
  13157. "model": GM_getValue("openai_model"),
  13158. "key": GM_getValue("openai_key"),
  13159. "proxy": GM_getValue("openai_proxy"),
  13160. "_header": "",
  13161. "_data": ""
  13162. }
  13163. if (GM_getValue("translation") === "openai") newConfig.choice = 0;
  13164. newConfig.configurations.push(config1);
  13165. }
  13166. if (GM_getValue("api2d_key")) {
  13167. let config2 = {
  13168. "note": "api2d",
  13169. "model": GM_getValue("api2d_model"),
  13170. "key": GM_getValue("api2d_key"),
  13171. "proxy": GM_getValue("api2d_request_entry") + '/v1/chat/completions',
  13172. "_header": GM_getValue("x_api2d_no_cache") ? "" : " x-api2d-no-cache : 1",
  13173. "_data": ""
  13174. }
  13175. if (GM_getValue("translation") === "api2d") {
  13176. if (GM_getValue("openai_key")) newConfig.choice = 1;
  13177. else newConfig.choice = 0;
  13178. }
  13179. newConfig.configurations.push(config2);
  13180. }
  13181. GM_setValue("chatgpt-config", newConfig);
  13182. const keysToDelete = ["openai_key", "openai_model", "openai_proxy", "api2d_key", "api2d_model", "api2d_request_entry", "x_api2d_no_cache", "showOpneAiAdvanced"];
  13183. keysToDelete.forEach(key => {
  13184. if (GM_getValue(key) != undefined) GM_deleteValue(key);
  13185. });
  13186. if (GM_getValue("translation") === "api2d") GM_setValue("translation", "openai");
  13187. location.reload();
  13188. }
  13189.  
  13190.  
  13191. // ------------------------------
  13192. // 配置自动迁移代码(将在10个小版本后移除-1.23)
  13193. // ------------------------------
  13194.  
  13195. {
  13196. let bottomZh_CN = GM_getValue("bottomZh_CN");
  13197. if (bottomZh_CN !== undefined) {
  13198. if (bottomZh_CN == true) {
  13199. GM_setValue("localizationLanguage", "zh");
  13200. } else {
  13201. GM_setValue("localizationLanguage", "initial");
  13202. }
  13203. GM_deleteValue("bottomZh_CN");
  13204. location.reload();
  13205. }
  13206. }
  13207. {
  13208. let config = GM_getValue("chatgpt-config");
  13209. if (config && config !== undefined) {
  13210. let index = parseInt(config.choice, 10);
  13211. if (index == -1) config.choice = "";
  13212. else config.choice = config.configurations[index].note;
  13213. config.configurations.forEach(function (item) {
  13214. item.name = item.note;
  13215. delete item.note;
  13216. });
  13217. GM_deleteValue("chatgpt-config");
  13218. GM_setValue("chatgpt_config", config);
  13219. location.reload();
  13220. }
  13221. }
  13222.  
  13223. // ------------------------------
  13224. // 配置自动迁移代码(将在10个小版本后移除-1.24)
  13225. // ------------------------------
  13226.  
  13227. {
  13228. let config = GM_getValue("compilerSelection");
  13229. if (config !== undefined) {
  13230. if (config === "61") {
  13231. GM_setValue("compilerSelection", "5001");
  13232. location.reload();
  13233. }
  13234. }
  13235. }