Atcoder Better!

Atcoder界面汉化、题目翻译、markdown视图、一键复制题目、跳转到洛谷

נכון ליום 15-03-2024. ראה הגרסה האחרונה.

  1. // ==UserScript==
  2. // @name Atcoder Better!
  3. // @namespace https://greatest.deepsurf.us/users/747162
  4. // @version 1.13.1
  5. // @description Atcoder界面汉化、题目翻译、markdown视图、一键复制题目、跳转到洛谷
  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. // @license GPL3
  54. // @compatible Chrome
  55. // @compatible Firefox
  56. // @compatible Edge
  57. // @incompatible safari
  58. // @supportURL https://github.com/beijixiaohu/OJBetter/issues
  59. // @name:zh-TW Atcoder Better!
  60. // @name:en Atcoder Better!
  61. // @name:de Atcoder Better!
  62. // @name:fr Atcoder Better!
  63. // @name:ko Atcoder Better!
  64. // @name:pt Atcoder Better!
  65. // @name:ja Atcoder Better!
  66. // @name:es Atcoder Better!
  67. // @name:it Atcoder Better!
  68. // @name:hi Atcoder Better!
  69. // @description 一个适用于 AtCoder 的 Tampermonkey 脚本,增强功能与界面。
  70. // @description:zh-TW 一個適用於 AtCoder 的 Tampermonkey 腳本,增強功能與界面。
  71. // @description:en A Tampermonkey script for AtCoder that enhances functionality and interface.
  72. // @description:de Ein Tampermonkey-Skript für AtCoder, das Funktionalität und Benutzeroberfläche verbessert.
  73. // @description:fr Un script Tampermonkey pour AtCoder qui améliore les fonctionnalités et l'interface.
  74. // @description:ko AtCoder를 위한 Tampermonkey 스크립트로 기능과 인터페이스를 개선합니다.
  75. // @description:pt Um script Tampermonkey para AtCoder que aprimora a funcionalidade e a interface.
  76. // @description:ja AtCoder用のTampermonkeyスクリプトで機能とインターフェースを強化します。
  77. // @description:es Un script Tampermonkey para AtCoder que mejora la funcionalidad y la interfaz.
  78. // @description:it Uno script Tampermonkey per AtCoder che migliora la funzionalità e l'interfaccia.
  79. // @description:hi AtCoder के लिए एक Tampermonkey स्क्रिप्ट जो कार्यक्षमता और इंटरफ़ेस को बेहतर बनाता है।
  80. // ==/UserScript==
  81.  
  82. /**
  83. * @namespace OJBetter
  84. * @desc 主命名空间
  85. */
  86. const OJBetter = {};
  87.  
  88. /**
  89. * @namespace state
  90. * @desc 描述脚本的当前状态。
  91. * @memberof OJBetter
  92. */
  93. OJBetter.state = {
  94. /** @type {string} 脚本名*/
  95. name: GM_info.script.name,
  96. /** @type {string} 格式化后的脚本名*/
  97. formatName: undefined,
  98. /** @type {string} 版本号*/
  99. version: GM_info.script.version,
  100. /** @type {boolean?} 是否跳过页面加载等待 */
  101. notWaiteLoaded: undefined,
  102. /** @type {string} 最后公告版本,用于标识版本更新完成提示 */
  103. lastAnnounceVer: undefined,
  104. /** @type {string} 最后读取的有效公告版本 */
  105. lastReadAnnounceVer: undefined,
  106. /** @type {number} 当前已打开的模态对话框数量*/
  107. openDialogCount: 0
  108. };
  109.  
  110. /**
  111. * @namespace common
  112. * @desc 通用设置和属性。
  113. * @memberof OJBetter
  114. */
  115. OJBetter.common = {
  116. /** @type {string} 网站的主机地址 */
  117. hostAddress: location.origin,
  118. /** @type {string?} Atcoder的CSRF令牌 */
  119. at_csrf_token: undefined,
  120. /** @type {Array?} 任务队列 */
  121. taskQueue: undefined,
  122. /** @type {object} OJBetter数据库连接实例*/
  123. database: undefined
  124. };
  125.  
  126. /**
  127. * @namespace basic
  128. * @desc 基本的用户界面设置。
  129. * @memberof OJBetter
  130. */
  131. OJBetter.basic = {
  132. /** @type {string} 黑暗模式设置 */
  133. darkMode: undefined,
  134. /** @type {boolean?} 是否展开折叠块 */
  135. expandFoldingblocks: undefined,
  136. /** @type {boolean?} 是否开启折叠块渲染性能优化 */
  137. renderPerfOpt: undefined,
  138. /** @type {boolean?} 评论区分页 */
  139. commentPaging: undefined,
  140. /** @type {boolean?} 显示跳转到Luogu按钮 */
  141. showJumpToLuogu: undefined,
  142. /** @type {boolean?} 显示跳转到Virtual Judge按钮 */
  143. showCF2vjudge: undefined,
  144. /** @type {boolean?} 比赛排行榜重新着色 */
  145. standingsRecolor: undefined
  146. };
  147.  
  148. /**
  149. * @namespace typeOfPage
  150. * @desc 页面类型判断。
  151. * @memberof OJBetter
  152. */
  153. OJBetter.typeOfPage = {
  154. /** @type {boolean?} 是否是轻量站 */
  155. is_mSite: false,
  156. /** @type {boolean?} 是否是acmsguru页面 */
  157. is_acmsguru: false,
  158. /** @type {boolean?} 是否是旧版LaTeX页面 */
  159. is_oldLatex: false,
  160. /** @type {boolean?} 是否是题目集页面 */
  161. is_contest: undefined,
  162. /** @type {boolean?} 是否是题目页面 */
  163. is_problem: undefined,
  164. /** @type {boolean?} 是否是完整的问题集页面 */
  165. is_completeProblemset: false,
  166. /** @type {boolean?} 是否是问题集中的问题页面 */
  167. is_problemset_problem: false,
  168. /** @type {boolean?} 是否是问题集页面 */
  169. is_problemset: false,
  170. /** @type {boolean?} 是否是Codeforces排名页面 */
  171. is_cfStandings: false,
  172. /** @type {boolean?} 是否是提交页面 */
  173. is_submitPage: false,
  174. /** @type {boolean?} 是否是主页 */
  175. is_homepage: undefined,
  176. /** @type {boolean?} 是否选择的是英语页面 */
  177. isEnglishLanguage: undefined,
  178. };
  179.  
  180. /**
  181. * @namespace localization
  182. * @desc 本地化设置。
  183. * @memberof OJBetter
  184. */
  185. OJBetter.localization = {
  186. /** @type {string?} 网站语言 */
  187. websiteLang: undefined,
  188. /** @type {string?} 脚本语言 */
  189. scriptLang: undefined
  190. };
  191.  
  192. /**
  193. * @namespace translation
  194. * @desc 翻译设置。
  195. * @memberof OJBetter
  196. */
  197. OJBetter.translation = {
  198. /** @type {string?} 翻译服务选择 */
  199. choice: undefined,
  200. /** @type {string?} 目标语言 */
  201. targetLang: undefined,
  202. comment: {
  203. /** @type {string?} 评论翻译服务选择 */
  204. choice: undefined,
  205. /** @type {string?} 评论翻译模式 */
  206. transMode: undefined
  207. },
  208. auto: {
  209. /** @type {boolean?} 自动翻译开关 */
  210. enabled: undefined,
  211. /** @type {number?} 短文本长度限制 */
  212. shortTextLength: undefined,
  213. mixTrans: {
  214. /** @type {boolean?} 混合翻译开关 */
  215. enabled: undefined,
  216. /** @type {Array?} 混合翻译服务列表 */
  217. servers: undefined
  218. }
  219. },
  220. memory: {
  221. /** @type {boolean?} 翻译记忆开关 */
  222. enabled: undefined,
  223. /** @type {Object?} 翻译记忆树 */
  224. ttTree: undefined
  225. },
  226. /** @type {string?} 重翻译时的行为 */
  227. retransAction: undefined,
  228. /** @type {number?} 等待时间 */
  229. waitTime: undefined,
  230. /** @type {boolean?} 替换符 */
  231. replaceSymbol: undefined,
  232. /** @type {boolean?} 过滤文本中的*号 */
  233. filterTextWithoutEmphasis: undefined
  234. };
  235.  
  236. /**
  237. * @namespace clist
  238. * @desc Clist相关设置。
  239. * @memberof OJBetter
  240. */
  241. OJBetter.clist = {
  242. enabled: {
  243. /** @type {boolean?} 比赛页面开关 */
  244. contest: undefined,
  245. /** @type {boolean?} 问题页面开关 */
  246. problem: undefined,
  247. /** @type {boolean?} 问题集页面开关 */
  248. problemset: undefined
  249. },
  250. /** @type {boolean?} Rating数据防剧透 */
  251. ratingHidden: undefined,
  252. /** @type {string?} Clist key */
  253. authorization: undefined
  254. };
  255.  
  256. /**
  257. * @namespace monaco
  258. * @desc Monaco编辑器配置。
  259. * @memberof OJBetter
  260. */
  261. OJBetter.monaco = {
  262. /** @type {boolean?} 在问题页面上启用Monaco编辑器 */
  263. enableOnProblemPage: undefined,
  264. /** @type {boolean?} 美化pre代码块 */
  265. beautifyPreBlocks: undefined,
  266. /** @type {boolean} Monaco编辑器加载完成标志 */
  267. loaderOnload: false,
  268. lsp: {
  269. /** @type {Array?} LSP套接字数组 */
  270. socket: [],
  271. /** @type {boolean?} 是否启用LSP */
  272. enabled: undefined,
  273. /** @type {string?} 工作路径 */
  274. workUri: undefined,
  275. /** @type {string?} 套接字URL */
  276. socketUrl: undefined
  277. },
  278. complet: {
  279. /** @type {boolean?} 是否启用C++代码补全模板 */
  280. cppCodeTemplate: undefined,
  281. /** @type {Object?} 自定义配置 */
  282. customConfig: undefined
  283. },
  284. /** @type {Object?} Monaco编辑器实例 */
  285. editor: null,
  286. /** @type {string?} 在线编译器选择 */
  287. onlineCompilerChoice: undefined,
  288. /** @type {string?} 编译器选择 */
  289. compilerSelection: undefined,
  290. setting: {
  291. /** @type {Array?} 语言设置数组 */
  292. language: [],
  293. /** @type {string?} 位置 */
  294. position: undefined,
  295. /** @type {boolean} 位置初始化标志 */
  296. position_initialized: false,
  297. /** @type {number?} 字体大小 */
  298. fontsize: undefined,
  299. /** @type {boolean?} 鼠标滚动锁定 */
  300. alwaysConsumeMouseWheel: undefined,
  301. /** @type {boolean?} 提交代码二次确认 */
  302. isCodeSubmitDoubleConfirm: undefined,
  303. /** @type {string?} 提交按钮位置 */
  304. submitButtonPosition: undefined
  305. }
  306. };
  307.  
  308. /**
  309. * @namespace deepl
  310. * @desc DeepL翻译服务配置。
  311. * @memberof OJBetter
  312. */
  313. OJBetter.deepl = {
  314. /** @type {Object?} DeepL配置对象 */
  315. configs: undefined,
  316. config: {
  317. /** @type {string?} 类型 */
  318. type: undefined,
  319. /** @type {string?} 名称 */
  320. name: undefined,
  321. /** @type {string?} API类型 */
  322. apiGenre: undefined,
  323. /** @type {string?} API密钥 */
  324. key: undefined,
  325. /** @type {string?} 代理 */
  326. proxy: undefined,
  327. /** @type {Object?} 额外请求头 */
  328. header: undefined,
  329. /** @type {Object?} 额外请求数据 */
  330. data: undefined,
  331. quota: {
  332. /** @type {string?} 余额URL */
  333. url: undefined,
  334. /** @type {string?} 余额请求方法 */
  335. method: undefined,
  336. /** @type {Object?} 余额请求头 */
  337. header: undefined,
  338. /** @type {Object?} 余额请求数据 */
  339. data: undefined,
  340. /** @type {number?} 剩余配额 */
  341. surplus: undefined
  342. }
  343. },
  344. /** @type {boolean?} 启用重点保护 */
  345. enableEmphasisProtection: undefined,
  346. /** @type {boolean?} 启用链接保护 */
  347. enableLinkProtection: undefined
  348. };
  349.  
  350. /**
  351. * @namespace chatgpt
  352. * @desc ChatGPT服务配置。
  353. * @memberof OJBetter
  354. */
  355. OJBetter.chatgpt = {
  356. /** @type {Object?} ChatGPT配置对象 */
  357. configs: undefined,
  358. config: {
  359. /** @type {string?} 名称 */
  360. name: undefined,
  361. /** @type {string?} 模型 */
  362. model: undefined,
  363. /** @type {string?} API密钥 */
  364. key: undefined,
  365. /** @type {string?} 代理 */
  366. proxy: undefined,
  367. /** @type {Object?} 额外请求头 */
  368. header: undefined,
  369. /** @type {Object?} 额外请求数据 */
  370. data: undefined,
  371. quota: {
  372. /** @type {string?} 余额URL */
  373. url: undefined,
  374. /** @type {string?} 余额请求方法 */
  375. method: undefined,
  376. /** @type {Object?} 余额请求头 */
  377. header: undefined,
  378. /** @type {Object?} 余额请求数据 */
  379. data: undefined,
  380. /** @type {number?} 剩余配额 */
  381. surplus: undefined
  382. }
  383. },
  384. /** @type {boolean?} 是否为流式传输 */
  385. isStream: undefined
  386. };
  387.  
  388. /**
  389. * @namespace preference
  390. * @desc 偏好设置
  391. * @memberof OJBetter
  392. */
  393. OJBetter.preference = {
  394. /** @type {boolean?} 是否显示加载动画 */
  395. showLoading: undefined,
  396. /** @type {boolean?} 是否显示悬停目标区域 */
  397. hoverTargetAreaDisplay: undefined,
  398. /** @type {string?} 按钮图标大小 */
  399. iconButtonSize: undefined,
  400. };
  401.  
  402. /**
  403. * @namespace about
  404. * @desc 关于页信息
  405. * @memberof OJBetter
  406. */
  407. OJBetter.about = {
  408. /** @type {string?} 更新通道 */
  409. updateChannel: undefined,
  410. /** @type {string?} 更新源 */
  411. updateSource: undefined
  412. };
  413.  
  414. /**
  415. * @namespace supportList
  416. * @desc 支持列表
  417. * @memberof OJBetter
  418. */
  419. OJBetter.supportList = {
  420. /** @type {object} 翻译支持列表和对应语言代码*/
  421. translationSupport: {
  422. 'deepl': { 'zh': 'ZH', 'de': 'DE', 'fr': 'FR', 'ko': 'KO', 'pt': 'PT', 'ja': 'JA', 'es': 'ES', 'it': 'IT' },
  423. 'iflyrec': { 'zh': '1' },
  424. 'youdao': { 'zh': 'AUTO' },
  425. 'google': { 'zh': 'zh-CN', 'zh-Hant': 'zh-TW', 'de': 'de', 'fr': 'fr', 'ko': 'ko', 'pt': 'pt', 'ja': 'ja', 'es': 'es', 'it': 'it', 'hi': 'hi' },
  426. 'caiyun': { 'zh': 'auto2zh', 'ja': 'auto2ja', 'ko': 'auto2ko', 'es': 'auto2es', 'fr': 'auto2fr' },
  427. 'openai': { 'zh': 'Chinese', 'zh-Hant': 'Traditional Chinese', 'de': 'German', 'fr': 'French', 'ko': 'Korean', 'pt': 'Portuguese', 'ja': 'Japanese', 'es': 'Spanish', 'it': 'Italian', 'hi': 'Hindi' }
  428. },
  429. /** @type {object} 更新源支持列表*/
  430. updateSourceSupportList: {
  431. 'greasyfork': {
  432. 'release': true,
  433. 'dev': false
  434. },
  435. 'github': {
  436. 'release': true,
  437. 'dev': true
  438. },
  439. 'aliyunoss': {
  440. 'release': true,
  441. 'dev': true
  442. }
  443. }
  444. }
  445.  
  446. // ------------------------------
  447. // 一些工具函数
  448. // ------------------------------
  449.  
  450. /**
  451. * 安全地创建jQuery元素
  452. * @description 通过jQuery创建HTML字符串时,如果字符串以空格开头,在某些Jquery版本中会发生错误,过滤空格以安全的创建元素。
  453. * @param {string} htmlString - HTML字符串。
  454. * @returns jQuery对象
  455. */
  456. const OJB_safeCreateJQElement = function (htmlString) {
  457. return $(htmlString.replace(/^\s+/, ""));
  458. }
  459.  
  460.  
  461. /**
  462. * 将数字或者字符串解析为数字。
  463. * @memberof OJBetter.common
  464. * @param {string} val 要解析的字符串
  465. * @param {boolean} [strict=false] 是否进行严格类型检查
  466. * @returns {number} 解析结果
  467. * @throws {Error} 如果解析失败,则抛出错误
  468. */
  469. const OJB_parseNumber = (val, strict = false) => {
  470. const num = Number(val);
  471. if (isNaN(num) || (strict && val.toString() !== num.toString())) {
  472. throw new Error('Invalid number');
  473. }
  474. return num;
  475. };
  476.  
  477. /**
  478. * 将字符串解析为布尔值
  479. * @param {string} val - 要解析的字符串
  480. * @param {boolean} strict - 是否进行严格类型检查
  481. * @returns {boolean} - 解析结果
  482. * @throws {Error} - 如果解析失败,则抛出错误
  483. */
  484. const OJB_parseBoolean = (val, strict) => {
  485. if (strict) {
  486. if (val === true || val === false) return val;
  487. throw new Error('Invalid boolean');
  488. }
  489. return val === 'true' ? true : val === 'false' ? false : val;
  490. };
  491.  
  492. /**
  493. * 将字符串解析为对象
  494. * @param {string} val - 要解析的字符串
  495. * @returns {Object} - 解析结果
  496. * @throws {Error} - 如果解析失败,则抛出错误
  497. */
  498. const OJB_parseObject = val => {
  499. try {
  500. return JSON.parse(val);
  501. } catch {
  502. throw new Error('Invalid JSON');
  503. }
  504. };
  505.  
  506. /**
  507. * 将字符串解析为键值对数组
  508. * @param {string} val - 要解析的字符串
  509. * @returns {Object[]} - 解析结果
  510. * @throws {Error} - 如果解析失败,则抛出错误
  511. */
  512. const OJB_parseLinePairArray = val => {
  513. if (typeof val !== 'string' || val.trim() === '') return [];
  514. return val.split("\n").filter(line => line.trim() !== '').map(line => {
  515. const indexOfFirstColon = line.indexOf(":");
  516. if (indexOfFirstColon === -1) throw new Error('Invalid LinePairArray format: ":" is missing');
  517. const key = line.substring(0, indexOfFirstColon).trim();
  518. const value = line.substring(indexOfFirstColon + 1).trim();
  519. return { [key]: value };
  520. });
  521. };
  522.  
  523. /**
  524. * 移除文本中的HTML标签
  525. * @param {string} text - 包含HTML标签的文本
  526. * @returns {string} - 移除HTML标签后的文本
  527. */
  528. const OJB_removeHTMLTags = function (text) {
  529. return text.replace(/<\/?[a-zA-Z]+("[^"]*"|'[^']*'|[^'">])*>/g, '');
  530. }
  531.  
  532. /**
  533. * 获取对象中指定路径表达式的值
  534. * @param {Object} obj - 要计算的对象
  535. * @param {string} pathOrExpression - 要计算的路径表达式
  536. * @returns {any} - 计算结果
  537. * @example
  538. * const obj = {
  539. * "a": {
  540. * "b": 1
  541. * },
  542. * "c": 2
  543. * };
  544. * evaluatePathOrExpression(obj, "a.b"); // 1
  545. * evaluatePathOrExpression(obj, "a.b + c"); // 3
  546. * evaluatePathOrExpression(obj, "a.b + a.c"); // 1
  547. */
  548. function OJB_evaluatePathOrExpression(obj, pathOrExpression) {
  549. const hasOperator = /[\+\-\*\/]/.test(pathOrExpression);
  550. const getPathValue = (obj, path) => {
  551. return path.split('.').reduce((acc, part) => {
  552. return acc !== undefined && acc !== null && acc.hasOwnProperty(part) ? acc[part] : undefined;
  553. }, obj);
  554. };
  555. const evaluateExpression = (obj, expression) => {
  556. const tokens = expression.split(/([\+\-\*\/])/).map(token => token.trim());
  557. const values = tokens.map(token => {
  558. if (/[\+\-\*\/]/.test(token)) {
  559. return token;
  560. } else {
  561. const value = getPathValue(obj, token);
  562. return value !== undefined ? value : 0;
  563. }
  564. });
  565. const evaluatedExpression = values.join(' ');
  566. try {
  567. return Function(`'use strict'; return (${evaluatedExpression});`)();
  568. } catch (e) {
  569. console.error('Expression evaluation error:', e);
  570. return undefined;
  571. }
  572. };
  573. return hasOperator ? evaluateExpression(obj, pathOrExpression) : getPathValue(obj, pathOrExpression);
  574. }
  575.  
  576. /**
  577. * 获取 GM 存储的值并根据类型进行处理
  578. * @param {string} key - 要检索的值的键。
  579. * @param {any} defaultValue - 如果值未找到,则返回的默认值。
  580. * @param {Object} [options={}] - 配置选项对象。
  581. * @param {string} [options.type='string'] - 期望的值的类型。可选值:'string', 'number', 'boolean', 'object', 'array', 'linePairArray'。
  582. * @param {boolean} [options.strict=false] - 用于数字和布尔类型,表示是否进行严格类型检查。
  583. * @param {string} [options.pathOrExpression=''] - 用于对象或数组类型,表示路径表达式或获取元素的索引。
  584. * @returns {any} - 检索到的值。
  585. */
  586. const OJB_getGMValue = (key, defaultValue, { type = 'string', strict = false, pathOrExpression = '' } = {}) => {
  587. let value = GM_getValue(key);
  588. if (value === undefined || value === null || value === "") {
  589. GM_setValue?.(key, defaultValue);
  590. return defaultValue;
  591. }
  592.  
  593. const parsers = {
  594. string: val => val,
  595. number: (val) => OJB_parseNumber(val, strict),
  596. boolean: (val) => OJB_parseBoolean(val, strict),
  597. object: OJB_parseObject,
  598. array: OJB_parseObject,
  599. linePairArray: OJB_parseLinePairArray
  600. };
  601.  
  602. if (!(type in parsers)) {
  603. console.error(`Unsupported type: ${type}`);
  604. return defaultValue;
  605. }
  606.  
  607. try {
  608. value = parsers[type](value);
  609. } catch (e) {
  610. console.error('Error:', e.message);
  611. return defaultValue;
  612. }
  613.  
  614. // The pathOrExpression processing is not applicable to linePairArray type
  615. if ((type === 'object' || type === 'array') && pathOrExpression) {
  616. const evaluated = OJB_evaluatePathOrExpression(value, pathOrExpression);
  617. if (evaluated === undefined) {
  618. console.error('Path or expression evaluation returned undefined');
  619. return defaultValue;
  620. }
  621. value = evaluated;
  622. }
  623.  
  624. return value;
  625. };
  626.  
  627. /**
  628. * 版本号比较方法
  629. * @param {string} version1 版本号1
  630. * @param {string} version2 版本号2
  631. * @returns {number} -1: version1 < version2, 0: version1 = version2, 1: version1 > version2
  632. */
  633. const OJB_compareVersions = function (version1 = "0", version2 = "0") {
  634. const v1Array = version1.split(".").map(Number);
  635. const v2Array = version2.split(".").map(Number);
  636. const length = Math.max(v1Array.length, v2Array.length);
  637. for (let i = 0; i < length; i++) {
  638. const diff = (v1Array[i] || 0) - (v2Array[i] || 0);
  639. if (diff) return Math.sign(diff);
  640. }
  641. return 0;
  642. }
  643.  
  644. /**
  645. * 获取上一个主版本号
  646. * @param {string} currentVersion 当前版本号
  647. * @returns {string} 上一个主版本号
  648. */
  649. const OJB_getPreviousVersion = function (currentVersion) {
  650. const versionArray = currentVersion.split(".").map(Number);
  651. let lastNonZeroIndex = versionArray.length - 1;
  652. while (lastNonZeroIndex >= 0 && versionArray[lastNonZeroIndex] === 0) {
  653. lastNonZeroIndex--;
  654. }
  655. if (lastNonZeroIndex >= 0) {
  656. versionArray[lastNonZeroIndex]--;
  657. for (let i = lastNonZeroIndex + 1; i < versionArray.length; i++) {
  658. versionArray[i] = 0;
  659. }
  660. }
  661. return versionArray.join(".");
  662. };
  663.  
  664. /**
  665. * 在指定根节点下观察指定选择器的元素,当元素存在时,执行回调函数
  666. * @param {Object} options - 配置对象
  667. * @param {string} options.selector - CSS选择器文本
  668. * @param {Function} options.callback - 回调函数,接收变动的节点作为参数
  669. * @param {Boolean} [options.triggerOnExist=true] - 如果为true,元素已存在时立即触发一次回调
  670. * @param {Element} [options.root=document.body] - 在哪个根节点下监听变化
  671. * @param {Boolean} [options.subtree=false] - 是否监听子树变化(即非直接子元素)
  672. */
  673. function OJB_observeElement({
  674. selector,
  675. callback,
  676. triggerOnExist = true,
  677. root = document.body,
  678. subtree = false
  679. }) {
  680. // 尝试获取选择器指定的元素
  681. const targetNode = root.querySelector(selector);
  682.  
  683. if (targetNode) {
  684. // 如果元素已存在,直接开始观察
  685. observeAndReport(targetNode, callback);
  686. // 如果triggerOnExist为true,则立即触发一次回调
  687. if (triggerOnExist) {
  688. callback(targetNode);
  689. }
  690. } else {
  691. // 如果元素不存在,监听DOM变化直到该元素被添加
  692. const observer = new MutationObserver((mutations) => {
  693. mutations.forEach((mutation) => {
  694. mutation.addedNodes.forEach((node) => {
  695. if (node.nodeType === Node.ELEMENT_NODE && node.matches(selector)) {
  696. observeAndReport(node, callback);
  697. if (triggerOnExist) {
  698. callback(node);
  699. }
  700. observer.disconnect(); // 停止监听
  701. }
  702. });
  703. });
  704. });
  705.  
  706. observer.observe(root, { childList: true, subtree, attributes: false });
  707. }
  708.  
  709. function observeAndReport(node, callback) {
  710. const childObserver = new MutationObserver((mutations) => {
  711. mutations.forEach((mutation) => {
  712. if (childList) {
  713. mutation.addedNodes.forEach((addedNode) => {
  714. if (addedNode.nodeType === Node.ELEMENT_NODE) {
  715. callback(addedNode); // 执行回调函数
  716. }
  717. });
  718. }
  719. if (attributes && mutation.type === 'attributes') {
  720. callback(mutation.target); // 执行回调函数
  721. }
  722. });
  723. });
  724.  
  725. childObserver.observe(node, { childList: true, subtree: true, attributes: false });
  726. }
  727. }
  728.  
  729. /**
  730. * 初始化全局变量
  731. */
  732. async function initVar() {
  733. const { hostname, href } = window.location;
  734. OJBetter.state.formatName = (() => OJBetter.state.name
  735. .toLowerCase()
  736. .replace(/\s+/g, '-')
  737. .replace(/[^a-z0-9-]/g, ''))();
  738. OJBetter.state.lastAnnounceVer = OJB_getGMValue("lastAnnounceVer", "0");
  739. OJBetter.state.lastReadAnnounceVer = OJB_getGMValue("lastReadAnnounceVer", "0");
  740. OJBetter.typeOfPage.is_contest = /\/contests\/[^\/]+\/tasks\/?$/.test(href);
  741. OJBetter.typeOfPage.is_problem = href.includes('/tasks/');
  742. OJBetter.typeOfPage.is_homepage = (href === 'https://atcoder.jp/' || href === 'https://atcoder.jp/?lang=ja');
  743. OJBetter.localization.websiteLang = OJB_getGMValue("localizationLanguage", "zh");
  744. OJBetter.localization.scriptLang = OJB_getGMValue("scriptL10nLanguage", "zh");
  745. OJBetter.basic.renderPerfOpt = OJB_getGMValue("renderPerfOpt", false);
  746. OJBetter.basic.commentPaging = OJB_getGMValue("commentPaging", true);
  747. OJBetter.basic.showJumpToLuogu = OJB_getGMValue("showJumpToLuogu", true);
  748. OJBetter.basic.showCF2vjudge = OJB_getGMValue("showCF2vjudge", true);
  749. OJBetter.basic.standingsRecolor = OJB_getGMValue("standingsRecolor", true);
  750. OJBetter.state.notWaiteLoaded = OJB_getGMValue("notWaiteLoaded", false);
  751. OJBetter.translation.targetLang = OJB_getGMValue("transTargetLang", "zh");
  752. OJBetter.translation.choice = OJB_getGMValue("translation", "deepl");
  753. OJBetter.translation.comment.transMode = OJB_getGMValue("commentTranslationMode", "0");
  754. OJBetter.translation.comment.choice = OJB_getGMValue("commentTranslationChoice", "0");
  755. OJBetter.translation.memory.enabled = OJB_getGMValue("memoryTranslateHistory", true);
  756. OJBetter.translation.auto.enabled = OJB_getGMValue("autoTranslation", false);
  757. OJBetter.translation.auto.shortTextLength = OJB_getGMValue("shortTextLength", "2000");
  758. OJBetter.translation.retransAction = OJB_getGMValue("retransAction", "0");
  759. OJBetter.translation.waitTime = OJB_getGMValue("transWaitTime", "200");
  760. OJBetter.translation.auto.mixTrans.enabled = OJB_getGMValue("allowMixTrans", true);
  761. OJBetter.translation.auto.mixTrans.servers = OJB_getGMValue("mixedTranslation", ['deepl', 'iflyrec', 'youdao', 'caiyun']);
  762. OJBetter.common.taskQueue = new TaskQueue();
  763. OJBetter.translation.replaceSymbol = OJB_getGMValue("replaceSymbol", "2");
  764. OJBetter.translation.filterTextWithoutEmphasis = OJB_getGMValue("filterTextWithoutEmphasis", false);
  765. OJBetter.clist.enabled.contest = OJB_getGMValue("showClistRating_contest", false);
  766. OJBetter.clist.enabled.problem = OJB_getGMValue("showClistRating_problem", false);
  767. OJBetter.clist.enabled.problemset = OJB_getGMValue("showClistRating_problemset", false);
  768. OJBetter.clist.ratingHidden = OJB_getGMValue("RatingHidden", false);
  769. OJBetter.clist.authorization = OJB_getGMValue("clist_Authorization", "");
  770. //deepl
  771. OJBetter.deepl.config.type = OJB_getGMValue("deepl_type", "free");
  772. OJBetter.deepl.configs = OJB_getGMValue("deepl_config", {
  773. "choice": "",
  774. "configurations": []
  775. });
  776. if (OJBetter.deepl.configs.choice !== "" && OJBetter.deepl.configs.configurations.length !== 0) {
  777. const choice = OJBetter.deepl.configs.choice;
  778. const configuration = OJBetter.deepl.configs.configurations.find(obj => obj.name === choice);;
  779. if (configuration == undefined) {
  780. let existingConfig = GM_getValue('deepl_config');
  781. existingConfig.choice = "";
  782. GM_setValue('deepl_config', existingConfig);
  783. location.reload();
  784. }
  785. OJBetter.deepl.config.name = configuration.name;
  786. OJBetter.deepl.config.apiGenre = configuration.apiGenre;
  787. OJBetter.deepl.config.key = configuration.key;
  788. OJBetter.deepl.config.proxy = configuration.proxy;
  789. OJBetter.deepl.config.header = OJB_parseLinePairArray(configuration._header);
  790. OJBetter.deepl.config.data = OJB_parseLinePairArray(configuration._data);
  791. OJBetter.deepl.config.quota.url = configuration.quota_url;
  792. OJBetter.deepl.config.quota.method = configuration.quota_method;
  793. OJBetter.deepl.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
  794. OJBetter.deepl.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
  795. OJBetter.deepl.config.quota.surplus = configuration.quota_surplus;
  796. }
  797. OJBetter.deepl.enableEmphasisProtection = OJB_getGMValue("enableEmphasisProtection", true);
  798. OJBetter.deepl.enableLinkProtection = OJB_getGMValue("enableLinkProtection", true);
  799. //openai
  800. OJBetter.chatgpt.isStream = OJB_getGMValue("openai_isStream", true);
  801. OJBetter.chatgpt.configs = OJB_getGMValue("chatgpt_config", {
  802. "choice": "",
  803. "configurations": []
  804. });
  805. if (OJBetter.chatgpt.configs.choice !== "" && OJBetter.chatgpt.configs.configurations.length !== 0) {
  806. const choice = OJBetter.chatgpt.configs.choice;
  807. const configuration = OJBetter.chatgpt.configs.configurations.find(obj => obj.name === choice);;
  808. if (configuration == undefined) {
  809. let existingConfig = GM_getValue('chatgpt_config');
  810. existingConfig.choice = "";
  811. GM_setValue('chatgpt_config', existingConfig);
  812. location.reload();
  813. }
  814. OJBetter.chatgpt.config.name = configuration.name;
  815. OJBetter.chatgpt.config.model = configuration.model;
  816. OJBetter.chatgpt.config.key = configuration.key;
  817. OJBetter.chatgpt.config.proxy = configuration.proxy;
  818. OJBetter.chatgpt.config.header = OJB_parseLinePairArray(configuration._header);
  819. OJBetter.chatgpt.config.data = OJB_parseLinePairArray(configuration._data);
  820. OJBetter.chatgpt.config.quota.url = configuration.quota_url;
  821. OJBetter.chatgpt.config.quota.method = configuration.quota_method;
  822. OJBetter.chatgpt.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
  823. OJBetter.chatgpt.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
  824. OJBetter.chatgpt.config.quota.surplus = configuration.quota_surplus;
  825. }
  826. // 编辑器
  827. // if (!OJBetter.typeOfPage.is_mSite) OJBetter.common.cf_csrf_token = Codeforces.getCsrfToken();
  828. // else OJBetter.common.cf_csrf_token = "";
  829. OJBetter.common.at_csrf_token = csrfToken;
  830. OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "61");
  831. OJBetter.monaco.setting.fontsize = OJB_getGMValue("editorFontSize", "15");
  832. OJBetter.monaco.enableOnProblemPage = OJB_getGMValue("problemPageCodeEditor", true);
  833. OJBetter.monaco.beautifyPreBlocks = OJB_getGMValue("beautifyPreBlocks", true);
  834. OJBetter.monaco.complet.cppCodeTemplate = OJB_getGMValue("cppCodeTemplateComplete", true);
  835. OJBetter.monaco.onlineCompilerChoice = OJB_getGMValue("onlineCompilerChoice", "official");
  836. OJBetter.monaco.setting.isCodeSubmitDoubleConfirm = OJB_getGMValue("isCodeSubmitConfirm", true);
  837. OJBetter.monaco.setting.alwaysConsumeMouseWheel = OJB_getGMValue("alwaysConsumeMouseWheel", true);
  838. OJBetter.monaco.setting.submitButtonPosition = OJB_getGMValue("submitButtonPosition", "bottom");
  839. //自定义补全
  840. OJBetter.monaco.complet.customConfig = OJB_getGMValue("Complet_config", {
  841. "choice": -1,
  842. "configurations": []
  843. });
  844. /**
  845. * 加载monaco编辑器资源
  846. */
  847. OJBetter.monaco.lsp.enabled = OJB_getGMValue("useLSP", false);
  848. OJBetter.monaco.setting.position = OJB_getGMValue("monacoEditor_position", "initial");
  849. OJBetter.monaco.lsp.workUri = OJB_getGMValue("OJBetter_Bridge_WorkUri", "C:/OJBetter_Bridge");
  850. OJBetter.monaco.lsp.socketUrl = OJB_getGMValue("OJBetter_Bridge_SocketUrl", "ws://127.0.0.1:2323/");
  851. if (OJBetter.monaco.enableOnProblemPage || OJBetter.monaco.beautifyPreBlocks) {
  852. let monacoLoader = document.createElement("script");
  853. monacoLoader.src = "https://cdn.staticfile.net/monaco-editor/0.44.0/min/vs/loader.min.js";
  854. document.head.prepend(monacoLoader);
  855. monacoLoader.onload = () => {
  856. require.config({
  857. paths: { vs: "https://cdn.staticfile.net/monaco-editor/0.44.0/min/vs" },
  858. "vs/nls": { availableLanguages: { "*": "zh-cn" } },
  859. });
  860. require(["vs/editor/editor.main"], () => {
  861. OJBetter.monaco.loaderOnload = true;
  862. });
  863. }
  864. }
  865. OJBetter.preference.showLoading = OJB_getGMValue("showLoading", true);
  866. OJBetter.preference.hoverTargetAreaDisplay = OJB_getGMValue("hoverTargetAreaDisplay", false);
  867. OJBetter.basic.expandFoldingblocks = OJB_getGMValue("expandFoldingblocks", true);
  868. OJBetter.preference.iconButtonSize = OJB_getGMValue("iconButtonSize", "16");
  869. OJBetter.about.updateChannel = OJB_getGMValue("updateChannel", "release");
  870. OJBetter.about.updateSource = OJB_getGMValue("updateSource", "greasyfork");
  871. }
  872.  
  873. /**
  874. * 显示警告消息
  875. */
  876. function showWarnMessage() {
  877. if (OJBetter.typeOfPage.is_oldLatex) {
  878. const loadingMessage = new LoadingMessage();
  879. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_oldLatex', { ns: 'alert' })}`, 'warning');
  880. }
  881. if (OJBetter.typeOfPage.is_acmsguru) {
  882. const loadingMessage = new LoadingMessage();
  883. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_acmsguru', { ns: 'alert' })}`, 'warning');
  884. }
  885. if (OJBetter.translation.comment.transMode == "1") {
  886. const loadingMessage = new LoadingMessage();
  887. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_segment', { ns: 'alert' })}`, 'warning');
  888. }
  889. if (OJBetter.translation.comment.transMode == "2") {
  890. const loadingMessage = new LoadingMessage();
  891. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_select', { ns: 'alert' })}`, 'warning');
  892. }
  893. if (OJBetter.typeOfPage.is_submitPage && OJBetter.monaco.enableOnProblemPage) {
  894. const loadingMessage = new LoadingMessage();
  895. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_submitPage', { ns: 'alert' })}`, 'warning');
  896. }
  897. }
  898.  
  899. // 常量
  900. 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>';
  901. 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>`;
  902. 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
  903. 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>`;
  904. 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" />
  905. <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" />
  906. <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>`;
  907.  
  908. /**
  909. * 连接数据库
  910. */
  911. async function initDB() {
  912. OJBetter.common.database = new Dexie('OJBetterDB');
  913. OJBetter.common.database.version(3).stores({
  914. samplesData: '&url',
  915. editorCode: '&url',
  916. translateData: '&url',
  917. localizeSubsData: '&lang'
  918. });
  919.  
  920. // 等待数据库打开
  921. await OJBetter.common.database.open();
  922. }
  923.  
  924. /**
  925. * 清空数据库
  926. */
  927. async function clearDatabase() {
  928. const isConfirmed = await OJB_createDialog(
  929. i18next.t('isClearDatabase.title', { ns: 'dialog' }),
  930. i18next.t('isClearDatabase.content', { ns: 'dialog' }),
  931. [
  932. i18next.t('isClearDatabase.buttons.0', { ns: 'dialog' }),
  933. i18next.t('isClearDatabase.buttons.1', { ns: 'dialog' })
  934. ]
  935. );
  936. if (!isConfirmed) {
  937. try {
  938. // 开启一个读写事务,包含数据库中的所有表
  939. await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
  940. // 遍历所有表
  941. for (const table of OJBetter.common.database.tables) {
  942. // 清空当前表
  943. await table.clear();
  944. }
  945. });
  946. console.log("All tables in the database have been cleared.");
  947. alert("All tables in the database have been cleared.");
  948. } catch (error) {
  949. console.error("Error clearing the database:", error);
  950. }
  951. }
  952. }
  953.  
  954. /**
  955. * 导出数据库
  956. * @returns {Promise<string>} 数据库的JSON字符串
  957. */
  958. async function exportDatabase() {
  959. try {
  960. // 创建一个存储数据的对象
  961. const exportData = {};
  962. // 获取数据库中所有表的名称
  963. const tableNames = OJBetter.common.database.tables.map(table => table.name);
  964.  
  965. // 遍历每一个表,获取数据
  966. for (const tableName of tableNames) {
  967. const tableData = await OJBetter.common.database.table(tableName).toArray();
  968. exportData[tableName] = tableData;
  969. }
  970.  
  971. // 将数据对象转换为JSON字符串
  972. const jsonData = JSON.stringify(exportData, null, 4);
  973. return jsonData;
  974. } catch (error) {
  975. console.error("Error exporting database:", error);
  976. }
  977. }
  978.  
  979. /**
  980. * 导入数据库
  981. * @param {string} jsonData 数据库的JSON字符串
  982. */
  983. async function importDatabase(jsonData) {
  984. const isConfirmed = await OJB_createDialog(
  985. i18next.t('isImportDatabase.title', { ns: 'dialog' }),
  986. i18next.t('isImportDatabase.content', { ns: 'dialog' }),
  987. [
  988. i18next.t('isImportDatabase.buttons.0', { ns: 'dialog' }),
  989. i18next.t('isImportDatabase.buttons.1', { ns: 'dialog' })
  990. ]
  991. );
  992. if (!isConfirmed) {
  993. try {
  994. // 将JSON字符串解析为对象
  995. const importData = JSON.parse(jsonData);
  996.  
  997. // 开启一个事务,并清空现有数据
  998. await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
  999. // 清空所有表的数据
  1000. for (const tableName of OJBetter.common.database.tables.map(table => table.name)) {
  1001. await OJBetter.common.database.table(tableName).clear();
  1002. }
  1003.  
  1004. // 插入新数据
  1005. for (const [tableName, rows] of Object.entries(importData)) {
  1006. await OJBetter.common.database.table(tableName).bulkAdd(rows);
  1007. }
  1008. });
  1009. alert("Data imported successfully");
  1010. } catch (error) {
  1011. console.error("Error importing database:", error);
  1012. }
  1013. }
  1014. }
  1015.  
  1016. /**
  1017. * 将数据下载为文件
  1018. * @param {string} data 数据
  1019. * @param {string} filename 文件名,默认为'export.json'
  1020. * @param {string} fileType 文件MIME类型,默认为'application/json'
  1021. * @returns {void}
  1022. */
  1023. function downloadDataAsFile(data, filename = 'export.json', fileType = 'application/json') {
  1024. // 创建一个blob对象,指定文件类型
  1025. const blob = new Blob([data], { type: fileType });
  1026. const url = URL.createObjectURL(blob);
  1027.  
  1028. // 创建一个隐藏的a标签,模拟点击进行下载
  1029. const a = document.createElement('a');
  1030. a.href = url;
  1031. a.download = filename;
  1032. document.body.appendChild(a);
  1033. a.click();
  1034.  
  1035. // 清理
  1036. document.body.removeChild(a);
  1037. URL.revokeObjectURL(url);
  1038. }
  1039.  
  1040.  
  1041. /**
  1042. * 从文件中读取数据
  1043. * @param {Function} callback 回调函数
  1044. * @returns {void}
  1045. */
  1046. function readFileInput(callback) {
  1047. const fileInput = document.createElement('input');
  1048. fileInput.type = 'file';
  1049. fileInput.accept = '.json';
  1050. fileInput.style.display = 'none'; // 隐藏input元素
  1051.  
  1052. fileInput.onchange = (e) => {
  1053. const file = e.target.files[0];
  1054. if (file) {
  1055. const reader = new FileReader();
  1056. reader.onload = (e) => {
  1057. const fileContent = e.target.result;
  1058. if (callback && typeof callback === 'function') {
  1059. callback(fileContent); // 调用回调函数,传入文件内容
  1060. }
  1061. };
  1062. reader.readAsText(file);
  1063. }
  1064. };
  1065.  
  1066. document.body.appendChild(fileInput);
  1067. fileInput.click();
  1068. document.body.removeChild(fileInput);
  1069. }
  1070.  
  1071. /**
  1072. * 删除所有设置
  1073. */
  1074. async function deleteAllConfigSettings() {
  1075. const isConfirmed = await OJB_createDialog(
  1076. i18next.t('isDeleteAllConfigSettings.title', { ns: 'dialog' }),
  1077. i18next.t('isDeleteAllConfigSettings.content', { ns: 'dialog' }),
  1078. [
  1079. i18next.t('isDeleteAllConfigSettings.buttons.0', { ns: 'dialog' }),
  1080. i18next.t('isDeleteAllConfigSettings.buttons.1', { ns: 'dialog' })
  1081. ]
  1082. );
  1083. if (!isConfirmed) {
  1084. const keys = GM_listValues();
  1085.  
  1086. keys.forEach(key => {
  1087. GM_deleteValue(key);
  1088. });
  1089.  
  1090. alert('All settings have been deleted.');
  1091. window.location.reload();
  1092. }
  1093. }
  1094.  
  1095. /**
  1096. * 导出设置到JSON
  1097. * @returns {string} JSON字符串
  1098. */
  1099. function exportSettingsToJSON() {
  1100. const keys = GM_listValues();
  1101. let settings = {};
  1102.  
  1103. keys.forEach(key => {
  1104. settings[key] = GM_getValue(key);
  1105. });
  1106.  
  1107. return JSON.stringify(settings, null, 4);
  1108. }
  1109.  
  1110. /**
  1111. * 从JSON导入设置
  1112. * @param {string} jsonData JSON字符串
  1113. * @returns {void}
  1114. */
  1115. async function importSettingsFromJSON(jsonData) {
  1116. const isConfirmed = await OJB_createDialog(
  1117. i18next.t('isImportSettings.title', { ns: 'dialog' }),
  1118. i18next.t('isImportSettings.content', { ns: 'dialog' }),
  1119. [
  1120. i18next.t('isImportSettings.buttons.0', { ns: 'dialog' }),
  1121. i18next.t('isImportSettings.buttons.1', { ns: 'dialog' })
  1122. ]
  1123. );
  1124. if (!isConfirmed) {
  1125. let settings;
  1126. try {
  1127. settings = JSON.parse(jsonData);
  1128. } catch (e) {
  1129. console.error('JSON parsing error:', e);
  1130. return;
  1131. }
  1132.  
  1133. Object.keys(settings).forEach(key => {
  1134. GM_setValue(key, settings[key]);
  1135. });
  1136.  
  1137. alert('Settings imported successfully!');
  1138. window.location.reload();
  1139. }
  1140. }
  1141.  
  1142. /**
  1143. * 加载元素本地化语言数据
  1144. * @param {JQuery} element jQuery元素
  1145. * @param {number} [retries=10] 重试次数
  1146. * @param {number} [interval=50] 重试间隔
  1147. */
  1148. function elementLocalize(element, retries = 10, interval = 50) {
  1149. if ($.isFunction(element.localize)) {
  1150. element.localize();
  1151. } else if (retries > 0) {
  1152. setTimeout(elementLocalize, interval, element, retries - 1, interval);
  1153. } else {
  1154. console.error('Unable to localize', element);
  1155. }
  1156. }
  1157.  
  1158. // 切换系统黑暗监听
  1159. const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
  1160. const changeEventListeners = [];
  1161. function handleColorSchemeChange(event) {
  1162. event.matches ? $('html').attr('data-theme', 'dark') : $('html').attr('data-theme', 'light');
  1163. if (!event.matches) {
  1164. var originalColor = $(this).data("original-color");
  1165. $(this).css("background-color", originalColor);
  1166. if (OJBetter.monaco.editor) {
  1167. monaco.editor.setTheme('vs');
  1168. }
  1169. } else {
  1170. if (OJBetter.monaco.editor) {
  1171. monaco.editor.setTheme('vs-dark');
  1172. }
  1173. }
  1174. }
  1175.  
  1176. // 黑暗模式
  1177. (function setDark() {
  1178. // 初始化
  1179. function setDarkTheme() {
  1180. const htmlElement = document.querySelector('html');
  1181. if (htmlElement) {
  1182. htmlElement.setAttribute('data-theme', 'dark');
  1183. } else {
  1184. setTimeout(setDarkTheme, 100);
  1185. }
  1186. }
  1187. OJBetter.basic.darkMode = OJB_getGMValue("darkMode", "follow")
  1188. if (OJBetter.basic.darkMode == "dark") {
  1189. setDarkTheme();
  1190. } else if (OJBetter.basic.darkMode == "follow") {
  1191. // 添加事件监听器
  1192. changeEventListeners.push(handleColorSchemeChange);
  1193. mediaQueryList.addEventListener('change', handleColorSchemeChange);
  1194.  
  1195. if (window.matchMedia('(prefers-color-scheme: dark)').matches) setDarkTheme();
  1196. }
  1197.  
  1198. // 定义全局变量
  1199. GM_addStyle(`
  1200. /* 黑暗支持 */
  1201. html[data-theme=dark]:root {
  1202. color-scheme: light dark;
  1203. }
  1204. /* 颜色 */
  1205. :root {
  1206. /* 文字颜色 */
  1207. --ojb-color-text-primary: #a0adb9; /* 主要文字颜色 */
  1208. --ojb-color-text-secondary: #9AA4B1; /* 次要文字颜色 */
  1209. --ojb-color-text-tertiary: #9BA5B2; /* 第三级文字颜色 */
  1210. --ojb-color-text-success: #43A047; /* 成功状态文字颜色 */
  1211. --ojb-color-text-highlight: #cbd6e2; /* 高亮文字颜色 */
  1212. --ojb-color-text-disabled: #506778; /* 禁用状态文字颜色 */
  1213. --ojb-color-text-icon-success: #2e7d32; /* 成功状态图标颜色 */
  1214. --ojb-color-text-link: #4b8eda; /* 链接颜色 */
  1215.  
  1216. /* 背景颜色 */
  1217. --ojb-color-bg-primary: #22272e; /* 主背景颜色 */
  1218. --ojb-color-bg-secondary: #2d333b; /* 次级背景颜色 */
  1219. --ojb-color-bg-disabled: #24292e; /* 禁用元素背景颜色 */
  1220.  
  1221. /* 边框颜色 */
  1222. --ojb-color-border-primary: #48535F; /* 主要边框颜色 */
  1223. --ojb-color-border-disabled: #404950; /* 禁用状态边框颜色 */
  1224. --ojb-color-border-dashed-hover: #03A9F4; /* 虚线边框悬浮颜色 */
  1225. --ojb-color-border-radio-checked: #326154; /* 选中的单选框边框颜色 */
  1226.  
  1227. /* 阴影颜色 */
  1228. --ojb-shadow-standard: 0px 0px 0.5px 0.5px #3A4048; /* 标准阴影 */
  1229. --ojb-shadow-menu-modal: 0px 0px 0px 4px #2d333b; /* 菜单和模态框阴影 */
  1230.  
  1231. /* 区域遮罩颜色 */
  1232. --ojb-overlay-background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px); /* 区域遮罩背景 */
  1233.  
  1234. /* 文字阴影 */
  1235. --ojb-text-shadow-icon: 1px 1px 0px #2d333b, 1px -1px 0px #2d333b, -1px -1px 0px #2d333b, -1px 1px 0px #2d333b; /* 图标文字阴影 */
  1236. }
  1237. /* 边框样式 */
  1238. :root {
  1239. /* 边框样式 */
  1240. --ojb-border-width: 1px; /* 边框宽度 */
  1241. --ojb-border-style-solid: solid; /* 实线样式 */
  1242. --ojb-border-style-dashed: dashed; /* 虚线样式 */
  1243. --ojb-border-radius-small: 4px; /* 小圆角 */
  1244. --ojb-border-radius-medium: 8px; /* 中圆角 */
  1245. --ojb-border-radius-large: 12px; /* 大圆角 */
  1246. /* 组合边框样式 */
  1247. --ojb-border-solid-primary: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-primary); /* 主要实线边框 */
  1248. --ojb-border-dashed: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-primary); /* 主要虚线边框 */
  1249. --ojb-border-dashed-hover: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-dashed-hover); /* 悬浮虚线边框 */
  1250. --ojb-border-solid-disabled: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-disabled); /* 禁用状态实线边框 */
  1251. }
  1252. `);
  1253.  
  1254. // OJBetter界面样式
  1255. GM_addStyle(`
  1256. /* 主要文字颜色 */
  1257. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1258. html[data-theme=dark] .alert-warning, html[data-theme=dark] .markItUpEditor,
  1259. html[data-theme=dark] .translate-problem-statement, html[data-theme=dark] .OJBetter_setting_menu,
  1260. html[data-theme=dark] .help_tip .tip_text,
  1261. html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
  1262. html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
  1263. html[data-theme=dark] #items-per-page, html[data-theme=dark] #pagBar,
  1264. html[data-theme=dark] .OJBetter_setting_sidebar li a:link,
  1265. html[data-theme=dark] .popup .content{
  1266. color: var(--ojb-color-text-primary);
  1267. }
  1268. /* 次要文字颜色 */
  1269. html[data-theme=dark] .ojb_btn:hover, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_statusBar,
  1270. html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #addCustomTest,
  1271. html[data-theme=dark] #customTestBlock, html[data-theme=dark] .OJBetter_setting_list.alert_info{
  1272. color: var(--ojb-color-text-secondary);
  1273. }
  1274. /* 文字颜色3 */
  1275. html[data-theme=dark] .ojb_btn{
  1276. color: var(--ojb-color-text-tertiary);
  1277. }
  1278. /* 文字颜色 浅绿 */
  1279. html[data-theme=dark] #SubmitButton{
  1280. color: var(--ojb-color-text-success);
  1281. }
  1282. /* 禁止文字颜色 */
  1283. html[data-theme=dark] .ojb_btn[disabled]{
  1284. color: var(--ojb-color-text-disabled);
  1285. }
  1286. /* 主要背景层次 */
  1287. html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] li#add_button:hover,
  1288. html[data-theme=dark] .ojb_btn:hover,
  1289. html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
  1290. html[data-theme=dark] #OJBetter_SubmitForm input,
  1291. html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
  1292. html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
  1293. html[data-theme=dark] .OJBetter_setting_sidebar li a.active, html[data-theme=dark] .OJBetter_setting_sidebar li,
  1294. html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-track, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-track,
  1295. html[data-theme=dark] .OJBetter_modal, html[data-theme=dark] .OJBetter_modal button:hover,
  1296. html[data-theme=dark] .popup .content,
  1297. html[data-theme=dark] .config_bar_list, html[data-theme=dark] #LSPLog, html[data-theme=dark] #OJBetter_SubmitForm,
  1298. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
  1299. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before,
  1300. html[data-theme=dark] .OJBetter_setting_menu a, html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button:hover,
  1301. html[data-theme=dark] .OJBetter_setting_menu select{
  1302. background-color: var(--ojb-color-bg-primary);
  1303. background-image: none;
  1304. }
  1305. /* 次要背景层次 */
  1306. html[data-theme=dark] .ojb_btn,
  1307. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1308. html[data-theme=dark] .alert-warning, html[data-theme=dark] .SumoSelect>.optWrapper>.options li.opt:hover,
  1309. html[data-theme=dark] .translate-problem-statement-panel,
  1310. html[data-theme=dark] .translate-problem-statement,
  1311. html[data-theme=dark] .OJBetter_setting_list,
  1312. html[data-theme=dark] .OJBetter_setting_menu hr,
  1313. html[data-theme=dark] .OJBetter_setting_sidebar li a,
  1314. html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-thumb, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-thumb,
  1315. html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] .test-for-popup pre,
  1316. html[data-theme=dark] .popup .content pre, html[data-theme=dark] .popup .content pre code,
  1317. html[data-theme=dark] ul.config_bar_ul::-webkit-scrollbar-thumb, html[data-theme=dark] #OJBetter_statusBar,
  1318. html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] .sampleDiv,
  1319. html[data-theme=dark] #addCustomTest, html[data-theme=dark] #LSPLog li:nth-child(odd),
  1320. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before,
  1321. html[data-theme=dark] .config::before, html[data-theme=dark] .config li.tempConfig_add_button:hover,
  1322. html[data-theme=dark] .OJBetter_setting_menu details, html[data-theme=dark] #config_bar_menu,
  1323. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button,
  1324. html[data-theme=dark] .OJBetter_setting_menu .badge, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton{
  1325. background-color: var(--ojb-color-bg-secondary);
  1326. }
  1327. /* 禁止背景层次 */
  1328. html[data-theme=dark] .ojb_btn[disabled]{
  1329. background-color: var(--ojb-color-bg-disabled);
  1330. }
  1331. /* 实线边框颜色-圆角 */
  1332. html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
  1333. html[data-theme=dark] .alert-warning, html[data-theme=dark] .translate-problem-statement{
  1334. border: var(--ojb-border-solid-primary);
  1335. border-radius: 2px;
  1336. }
  1337. /* 实线边框颜色-无圆角 */
  1338. html[data-theme=dark] .ojb_btn,
  1339. html[data-theme=dark] .OJBetter_setting_list, html[data-theme=dark] .config_bar_list,
  1340. html[data-theme=dark] label.config_bar_ul_li_text,
  1341. html[data-theme=dark] .OJBetter_setting_sidebar li, html[data-theme=dark] .OJBetter_setting_menu select,
  1342. html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_SubmitForm select,
  1343. html[data-theme=dark] #OJBetter_editor, html[data-theme=dark] #OJBetter_statusBar,
  1344. html[data-theme=dark] #OJBetter_SubmitForm #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #customTestBlock,
  1345. html[data-theme=dark] #OJBetter_SubmitForm #addCustomTest, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton,
  1346. html[data-theme=dark] .OJBetter_setting_menu input,
  1347. html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
  1348. html[data-theme=dark] .OJBetter_setting_menu textarea,
  1349. html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea,
  1350. html[data-theme=dark] #CompilerSetting select, html[data-theme=dark] #CompilerSetting textarea, html[data-theme=dark] #CompilerBox,
  1351. html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
  1352. html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] .config::before,
  1353. html[data-theme=dark] #statePanel, html[data-theme=dark] .test-case, html[data-theme=dark] .OJBetter_setting_menu .badge{
  1354. border: var(--ojb-border-solid-primary);
  1355. }
  1356. html[data-theme=dark] #customTestBlock #customTests{
  1357. border-top: var(--ojb-border-solid-primary);
  1358. }
  1359. html[data-theme=dark] .OJBetter_setting_sidebar {
  1360. border-right: var(--ojb-border-solid-primary);
  1361. }
  1362. /* 实线边框-禁止 */
  1363. html[data-theme=dark] .ojb_btn[disabled]{
  1364. border: var(--ojb-border-solid-disabled);
  1365. }
  1366. /* 虚线边框 */
  1367. html[data-theme=dark] li#add_button,
  1368. html[data-theme=dark] .OJBetter_setting_menu_label_text{
  1369. border: var(--ojb-border-dashed);
  1370. }
  1371. /* 虚线边框-悬浮 */
  1372. html[data-theme=dark] li#add_button:hover{
  1373. border: var(--ojb-border-dashed-hover);
  1374. background-color: var(--ojb-color-bg-secondary);
  1375. color: var(--ojb-color-border-dashed-hover);
  1376. }
  1377. /* 无边框 */
  1378. html[data-theme=dark] .translate-problem-statement-panel .ojb_btn{
  1379. border: none;
  1380. }
  1381. /* 区域遮罩 */
  1382. html[data-theme=dark] .overlay::before {
  1383. background: var(--ojb-overlay-background);
  1384. color: var(--ojb-color-text-secondary);
  1385. text-shadow: 0px 0px 2px #000000;
  1386. }
  1387. /* 阴影 */
  1388. html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .translate-problem-statement{
  1389. box-shadow: var(--ojb-shadow-standard);
  1390. }
  1391. /* 图标按钮状态样式 */
  1392. html[data-theme=dark] .ojb_btn_popover.success i:before, html[data-theme=dark] .ojb_btn_popover.success i {
  1393. color: var(--ojb-color-text-icon-success);
  1394. }
  1395. html[data-theme=dark] .ojb_btn_popover i:before {
  1396. text-shadow: var(--ojb-text-shadow-icon);
  1397. }
  1398. /* 其他样式 */
  1399. html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .OJBetter_modal{
  1400. box-shadow: var(--ojb-shadow-menu-modal);
  1401. border: 1px solid var(--ojb-color-bg-secondary);
  1402. }
  1403. html[data-theme=dark] input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
  1404. color: var(--ojb-color-text-primary);
  1405. border: 1px solid var(--ojb-color-border-radio-checked);
  1406. }
  1407. html[data-theme=dark] .alert{
  1408. text-shadow: none;
  1409. }
  1410. `);
  1411.  
  1412. // 网站界面样式
  1413. GM_addStyle(`
  1414. /* 文字颜色1 */
  1415. html[data-theme=dark] body, html[data-theme=dark] .float-container>#main-container,
  1416. html[data-theme=dark] .panel-default>.panel-heading, html[data-theme=dark] #header a,
  1417. html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span, html[data-theme=dark] .dropdown-menu,
  1418. html[data-theme=dark] .select2-container--bootstrap .select2-selection--single .select2-selection__rendered,
  1419. html[data-theme=dark] .ace-tm .ace_gutter, html[data-theme=dark] .translate-problem-statement-panel,
  1420. html[data-theme=dark] .select2-container--bootstrap .select2-results__option--highlighted[aria-selected],
  1421. 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,
  1422. html[data-theme=dark] .m-box_inner, html[data-theme=dark] .m-list-job_item, html[data-theme=dark] .a-btn_arrow,
  1423. html[data-theme=dark] #header, html[data-theme=dark] #header .header-sub_page li a,
  1424. html[data-theme=dark] .select2-container--default .select2-selection--single .select2-selection__rendered, html[data-theme=dark] .select2-results{
  1425. color: var(--ojb-color-text-primary) !important;
  1426. }
  1427. /* 文字颜色2 */
  1428. html[data-theme=dark] pre, html[data-theme=dark] .html2mdButton, html[data-theme=dark] .btn-default, html[data-theme=dark] .btn-pre,
  1429. html[data-theme=dark] small.contest-duration, html[data-theme=dark] .select2-container--bootstrap .select2-results__option,
  1430. html[data-theme=dark] #ace_settingsmenu, #kbshortcutmenu, html[data-theme=dark] code{
  1431. color: var(--ojb-color-text-secondary) !important;
  1432. }
  1433. /* 文字颜色3 */
  1434. html[data-theme=dark] input, html[data-theme=dark] #header .header-page li a:hover{
  1435. color: var(--ojb-color-text-secondary);
  1436. }
  1437. /* 文字颜色4 */
  1438. html[data-theme=dark] .katex{
  1439. color: var(--ojb-color-text-highlight) !important;
  1440. }
  1441. /* 链接颜色 */
  1442. html[data-theme=dark] a:link {
  1443. color: var(--ojb-color-text-link);
  1444. }
  1445. html[data-theme=dark] a:visited {
  1446. color: var(--ojb-color-text-secondary);
  1447. }
  1448. /* 按钮 */
  1449. html[data-theme=dark] input:hover, html[data-theme=dark] .btn-default:hover{
  1450. background-color: var(--ojb-color-bg-primary) !important;
  1451. }
  1452. /* 背景层次1 */
  1453. html[data-theme=dark] body, html[data-theme=dark] #main-div.float-container, html[data-theme=dark] pre,
  1454. html[data-theme=dark] .html2mdButton:hover, html[data-theme=dark] .pagination>.active>a, html[data-theme=dark] .ace-tm,
  1455. html[data-theme=dark] .dropdown-menu>li>a:hover, html[data-theme=dark] .dropdown-menu>li>a:focus,
  1456. html[data-theme=dark] .dropdown-menu .divider, html[data-theme=dark] .select2-container--bootstrap .select2-selection,
  1457. html[data-theme=dark] .ace-tm .ace_gutter-active-line, html[data-theme=dark] .select2-dropdown,
  1458. html[data-theme=dark] input, html[data-theme=dark] button, html[data-theme=dark] select, html[data-theme=dark] textarea,
  1459. html[data-theme=dark] code, html[data-theme=dark] #keyvisual .keyvisual-inner:before, html[data-theme=dark] .m-box_inner,
  1460. html[data-theme=dark] .m-list-job_item, html[data-theme=dark] .select2-container--default .select2-selection--single,
  1461. html[data-theme=dark] ol.linenums, html[data-theme=dark] li.L0, html[data-theme=dark] li.L1, html[data-theme=dark] li.L2,
  1462. html[data-theme=dark] li.L3, html[data-theme=dark] li.L4, html[data-theme=dark] li.L5, html[data-theme=dark] li.L6,
  1463. html[data-theme=dark] li.L7, html[data-theme=dark] li.L8, html[data-theme=dark] li.L9{
  1464. background-color: var(--ojb-color-bg-primary) !important;
  1465. }
  1466. /* 背景层次2 */
  1467. html[data-theme=dark] .float-container>#main-container, html[data-theme=dark] #contest-nav-tabs,
  1468. html[data-theme=dark] .btn-default, html[data-theme=dark] .html2mdButton,
  1469. 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,
  1470. html[data-theme=dark] .nav>li>a:hover, html[data-theme=dark] .nav>li>a:focus, html[data-theme=dark] .panel,
  1471. html[data-theme=dark] .table-striped>tbody>tr:nth-of-type(odd), html[data-theme=dark] .insert-participant-box,
  1472. html[data-theme=dark] .btn-pre, html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-danger,
  1473. html[data-theme=dark] .alert-warning, html[data-theme=dark] .panel-default>.panel-heading,
  1474. html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span, html[data-theme=dark] .dropdown-menu,
  1475. html[data-theme=dark] .ace-tm .ace_gutter, html[data-theme=dark] .select2-container--bootstrap .select2-results__option[aria-selected=true],
  1476. html[data-theme=dark] #ace_settingsmenu, #kbshortcutmenu, html[data-theme=dark] #header .header-inner,
  1477. html[data-theme=dark] ul#config_bar_ul::-webkit-scrollbar-thumb, html[data-theme=dark] .panel-info>.panel-heading,
  1478. html[data-theme=dark] .post-footer, html[data-theme=dark] .a-btn_arrow:before,
  1479. html[data-theme=dark] .table-hover>tbody>tr:hover,
  1480. html[data-theme=dark] li.L1, html[data-theme=dark] li.L3, html[data-theme=dark] li.L5, html[data-theme=dark] li.L7,
  1481. html[data-theme=dark] li.L9{
  1482. background-color: var(--ojb-color-bg-secondary) !important;
  1483. }
  1484. /* 实线边框颜色-圆角 */
  1485. html[data-theme=dark] input{
  1486. border: var(--ojb-border-solid-primary) !important;
  1487. border-radius: 2px;
  1488. }
  1489. /* 实线边框颜色-无圆角 */
  1490. html[data-theme=dark] .btn-default, html[data-theme=dark] .html2mdButton, html[data-theme=dark] .nav-tabs>li>a:hover,
  1491. html[data-theme=dark] .nav-tabs>li.active>a, html[data-theme=dark] .nav-tabs>li.active>a:hover,
  1492. html[data-theme=dark] .nav-tabs>li.active>a:focus, html[data-theme=dark] .btn-pre, html[data-theme=dark] .btn-pre:hover,
  1493. html[data-theme=dark] pre, html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span,
  1494. 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,
  1495. 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,
  1496. 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,
  1497. html[data-theme=dark] .select2-container--bootstrap .select2-selection, html[data-theme=dark] .select2-container--default .select2-selection--single{
  1498. border: var(--ojb-border-solid-primary) !important;
  1499. }
  1500. html[data-theme=dark] hr, html[data-theme=dark] .panel-footer,
  1501. html[data-theme=dark] .table>thead>tr>th, html[data-theme=dark] .table>tbody>tr>th, html[data-theme=dark] .table>tfoot>tr>th,
  1502. html[data-theme=dark] .table>thead>tr>td, html[data-theme=dark] .table>tbody>tr>td, html[data-theme=dark] .table>tfoot>tr>td{
  1503. border-top: var(--ojb-border-solid-primary) !important;
  1504. }
  1505. html[data-theme=dark] .nav-tabs, html[data-theme=dark] .panel-info>.panel-heading, html[data-theme=dark] .panel-default>.panel-heading,
  1506. html[data-theme=dark] .a-btn_arrow{
  1507. border-bottom: var(--ojb-border-solid-primary) !important;
  1508. }
  1509. html[data-theme=dark] .table>thead>tr>th{
  1510. border-bottom: 2px solid var(--ojb-color-border-primary) !important;
  1511. }
  1512. /* 双实线边框 */
  1513. html[data-theme=dark] #header .header-inner{
  1514. border-bottom: 5px double var(--ojb-color-border-primary) !important;
  1515. }
  1516. /* 阴影 */
  1517. html[data-theme=dark] .float-container>#main-container{
  1518. box-shadow: 0px 0px 10px 5px #fff0;
  1519. }
  1520. /* 图片-亮度 */
  1521. html[data-theme=dark] img{
  1522. opacity: .75;
  1523. }
  1524. /* 反转 */
  1525. html[data-theme=dark] .ace_content, html[data-theme=dark] #header .header-logo img, html[data-theme=dark] pre code{
  1526. filter: invert(1) hue-rotate(.5turn);
  1527. }
  1528. /* 区域遮罩 */
  1529. html[data-theme=dark] .overlay {
  1530. background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px);
  1531. color: #9099a3;
  1532. text-shadow: 0px 0px 2px #000000;
  1533. }
  1534. /* 其他样式 */
  1535. 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{
  1536. border-bottom-color: transparent !important;
  1537. }
  1538. html[data-theme=dark] .collapsible-topic.collapsed .content .collapsible-topic-options:before{
  1539. background-image: linear-gradient(#22272e00, #22272e);
  1540. }
  1541. html[data-theme=dark] .alert{
  1542. text-shadow: none;
  1543. }
  1544. html[data-theme=dark] .m-box-news_post:before{
  1545. background: linear-gradient(0deg, #22272e 50%, rgba(255,255,255,0) 100%);
  1546. }
  1547. html[data-theme=dark] #header .header-sub_page li a:before, html[data-theme=dark] #header .header-page li a:before{
  1548. background-color: #9e9e9e !important;
  1549. }
  1550. html[data-theme=dark] .standings-score{
  1551. color: #2196f3;
  1552. }
  1553. html[data-theme=dark] pre code{
  1554. background-color: transparent !important;
  1555. }
  1556. html[data-theme=dark] #fixed-server-timer {
  1557. color: #000;
  1558. }
  1559. `);
  1560. })()
  1561.  
  1562. /**
  1563. * 黑暗模式额外的处理事件
  1564. */
  1565. function darkModeStyleAdjustment() {
  1566.  
  1567. }
  1568.  
  1569. /**
  1570. * 美化Pre代码块
  1571. */
  1572. async function beautifyPreBlocksWithMonaco() {
  1573. // 用于替换 <pre> 标签为 Monaco 编辑器的函数
  1574. function replacePreWithMonaco(preElement) {
  1575. const pre = $(preElement);
  1576. if (pre.hasClass('source-code-for-copy')) return; // 跳过复制块
  1577. const code = OJB_getCodeFromPre(pre.get(0));
  1578. if (!code) return;
  1579. const language = OJB_codeLangDetect(code);
  1580.  
  1581. // 创建一个用于 Monaco 编辑器的容器
  1582. const container = $('<div></div>');
  1583. const lineCount = code.split('\n').length; // 代码的行数
  1584.  
  1585. // 计算容器的高度
  1586. const calculateContainerHeight = (lineCount) => {
  1587. const lineHeight = 20; // 每行代码的高度
  1588. const minHeight = 100; // 最小高度
  1589. const maxHeight = 1000; // 最大高度
  1590. const dynamicHeight = lineCount * lineHeight;
  1591. return Math.min(Math.max(dynamicHeight, minHeight), maxHeight) + 'px';
  1592. };
  1593.  
  1594. // 应用样式
  1595. container.css({
  1596. height: calculateContainerHeight(lineCount),
  1597. width: '100%'
  1598. });
  1599. pre.replaceWith(container);
  1600.  
  1601. // 初始化 Monaco 编辑器
  1602. monaco.editor.create(container[0], {
  1603. value: code,
  1604. language: language,
  1605. readOnly: true,
  1606. tabSize: 4,
  1607. theme: OJBetter.basic.darkMode == "dark" ? "vs-dark" : "vs",
  1608. scrollbar: {
  1609. verticalScrollbarSize: 10,
  1610. horizontalScrollbarSize: 10,
  1611. alwaysConsumeMouseWheel: false
  1612. },
  1613. automaticLayout: true,
  1614. scrollBeyondLastLine: false
  1615. });
  1616. }
  1617. // 全局替换页面上所有的 <pre> 元素
  1618. $('pre').each(function () {
  1619. replacePreWithMonaco(this);
  1620. });
  1621. // 监听页面上的提交状态页面窗口的 <pre> 元素
  1622. if (OJBetter.typeOfPage.is_statePage) {
  1623. OJB_observeElement({
  1624. selector: '#facebox',
  1625. callback: (node) => {
  1626. // 如果 facebox 中存在 pre 元素,则替换它们
  1627. const preElements = $(node).find('pre');
  1628. preElements.each(function () {
  1629. replacePreWithMonaco(this);
  1630. });
  1631. }
  1632. });
  1633. }
  1634. }
  1635.  
  1636. // 样式
  1637. GM_addStyle(`
  1638. /*动画*/
  1639. @keyframes shake {
  1640. 0% { transform: translateX(-5px); }
  1641. 100% { transform: translateX(5px); }
  1642. }
  1643. @keyframes rotate {
  1644. from {
  1645. transform: rotate(0deg);
  1646. }
  1647.  
  1648. to {
  1649. transform: rotate(360deg);
  1650. }
  1651. }
  1652. @keyframes rippleout {
  1653. 0% {
  1654. box-shadow: 0 0 0 0 rgba(96, 98, 102, 0.2);
  1655. }
  1656.  
  1657. 100% {
  1658. box-shadow: 0 0 0 6px rgba(0, 0, 0, 0);
  1659. }
  1660. }
  1661. @keyframes bounce-in {
  1662. 20%,40%,60%,80%,from,to {
  1663. animation-timing-function: cubic-bezier(.215,.61,.355,1);
  1664. }
  1665.  
  1666. 0% {
  1667. opacity: 0;
  1668. transform: scale3d(.995,.995,.995);
  1669. }
  1670.  
  1671. 20% {
  1672. opacity: 1;
  1673. transform: scale3d(1.005,1.005,1.005);
  1674. }
  1675.  
  1676. 40% {
  1677. transform: scale3d(.998,.998,.998);
  1678. }
  1679.  
  1680. 60% {
  1681. transform: scale3d(1.002,1.002,1.002);
  1682. }
  1683.  
  1684. 80% {
  1685. transform: scale3d(.995,.995,.995);
  1686. }
  1687.  
  1688. to {
  1689. opacity: 1;
  1690. transform: scale3d(1,1,1);
  1691. }
  1692. }
  1693. /*iconfont图标*/
  1694. .iconfont {
  1695. font-family: "iconfont" !important;
  1696. font-size: 16px;
  1697. font-style: normal !important;
  1698. -webkit-font-smoothing: antialiased;
  1699. -moz-osx-font-smoothing: grayscale;
  1700. }
  1701. @font-face {
  1702. font-family: 'iconfont'; /* Project id 4284341 */
  1703. src: url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2') format('woff2'),
  1704. url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2.ttf') format('truetype');
  1705. }
  1706. html {
  1707. scroll-behavior: smooth;
  1708. }
  1709. :root {
  1710. --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";
  1711. }
  1712. span.mdViewContent {
  1713. white-space: pre-wrap;
  1714. }
  1715.  
  1716. /* dialog */
  1717. dialog {
  1718. margin: 0px;
  1719. }
  1720. dialog::backdrop {
  1721. background-color: rgba(0, 0, 0, 0.4);
  1722. }
  1723.  
  1724. /*题目页链接栏样式*/
  1725. #problemToolbar {
  1726. display: flex;
  1727. flex-wrap: wrap;
  1728. justify-content: flex-end;
  1729. overflow: auto;
  1730. height: 100%;
  1731. margin: 0.5em;
  1732. }
  1733.  
  1734. /*html2md面板*/
  1735. .html2md-panel {
  1736. display: flex;
  1737. justify-content: flex-end;
  1738. align-items: center;
  1739. }
  1740. .html2md-panel a {
  1741. text-decoration: none;
  1742. }
  1743. .html2md-panel > button {
  1744. margin: 5px;
  1745. }
  1746. .html2md-panel.is_simple {
  1747. position: absolute;
  1748. right: 2%;
  1749. }
  1750.  
  1751. /*通用按钮*/
  1752. .ojb_btn {
  1753. display: flex;
  1754. align-items: center;
  1755. justify-content: center;
  1756. cursor: pointer;
  1757. background-color: #ffffff;
  1758. color: #606266;
  1759. width: auto;
  1760. font-size: 13px;
  1761. border-radius: 0.3rem;
  1762. padding: 2px 5px;
  1763. margin: 0px 5px;
  1764. border: 1px solid #dcdfe6;
  1765. }
  1766. .ojb_btn[disabled] {
  1767. cursor: not-allowed !important;
  1768. color: rgb(168, 171, 178) !important;
  1769. border: 1px solid #e4e7ed;
  1770. background-color: #ffffff;
  1771. }
  1772. .ojb_btn:hover {
  1773. color: #409eff;
  1774. border-color: #409eff;
  1775. background-color: #f1f8ff;
  1776. z-index: 150;
  1777. }
  1778. .ojb_btn.primary {
  1779. color: #ffffff;
  1780. border: 1px solid #409eff;
  1781. background-color: #409eff;
  1782. }
  1783. .ojb_btn.primary:hover {
  1784. color: #ffffff;
  1785. border: 1px solid #79bbff;
  1786. background-color: #79bbff;
  1787. }
  1788. .ojb_btn.success {
  1789. color: #4caf50;
  1790. border: 1px solid #C8E6C9;
  1791. background-color: #f0f9eb;
  1792. }
  1793. .ojb_btn.warning {
  1794. color: #e6a23c;
  1795. border: 1px solid #f3d19e;
  1796. background-color: #fdf6ec;
  1797. }
  1798. .ojb_btn.error {
  1799. color: #f56c6c;
  1800. border: 1px solid #fab6b6;
  1801. background-color: #fef0f0;
  1802. }
  1803. .ojb_btn.enabled {
  1804. color: #42A5F5;
  1805. border: 1px solid #90CAF9;
  1806. background-color: #fafbff;
  1807. }
  1808. .ojb_btn.active {
  1809. animation: rippleout 0.5s ease-in-out;
  1810. }
  1811. a.ojb_btn {
  1812. text-decoration: none;
  1813. }
  1814. a.ojb_btn:link {
  1815. color: #606266;
  1816. }
  1817. a.ojb_btn span {
  1818. margin-left: 2px;
  1819. }
  1820. /*按钮图标和popover*/
  1821. .ojb_btn_popover {
  1822. display: flex;
  1823. justify-content: center;
  1824. position: relative;
  1825. outline: none;
  1826. appearance: none;
  1827. }
  1828. .ojb_btn_popover:hover span {
  1829. opacity: 1;
  1830. visibility: visible;
  1831. }
  1832. .ojb_btn_popover i:before {
  1833. position: absolute;
  1834. text-shadow: 1px 1px 0px #ffffff, 1px -1px 0px #ffffff, -1px -1px 0px #ffffff, -1px 1px 0px #ffffff;
  1835. }
  1836. .ojb_btn_popover span {
  1837. cursor: initial;
  1838. position: absolute;
  1839. left: 50%;
  1840. opacity: 0;
  1841. visibility: hidden;
  1842. padding: 4px 8px;
  1843. background-color: rgba(33, 33, 33, 0.8);
  1844. color: rgba(255, 255, 255, 0.9019607843);
  1845. font-size: 12px;
  1846. border-radius: 6px;
  1847. line-height: 1.6;
  1848. text-align: left;
  1849. white-space: nowrap;
  1850. transition: all 0.15s ease-in-out;
  1851. z-index: 999;
  1852. }
  1853. .ojb_btn_popover span:hover {
  1854. opacity: 0;
  1855. visibility: hidden;
  1856. }
  1857. .ojb_btn_popover.top:hover span {
  1858. transform: translate(-50%, 0);
  1859. }
  1860. .ojb_btn_popover.top span {
  1861. bottom: 100%;
  1862. transform: translate(-50%, -20%);
  1863. margin-bottom: 4px;
  1864. }
  1865. .ojb_btn_popover.top span:hover {
  1866. transform: translate(-50%, -20%);
  1867. }
  1868. .ojb_btn_popover.bottom:hover span {
  1869. transform: translate(-50%, 105%);
  1870. }
  1871. .ojb_btn_popover.bottom span {
  1872. bottom: -2%;
  1873. transform: translate(-50%, 100%);
  1874. margin-top: 4px;
  1875. }
  1876. .ojb_btn_popover.bottom span:hover {
  1877. transform: translate(-50%, 50%);
  1878. }
  1879. .ojb_btn_popover.loading i {
  1880. color: rgba(33, 33, 33, 0.1);
  1881. }
  1882. .ojb_btn_popover.loading i:before {
  1883. content: "\\e640";
  1884. color: rgb(168, 171, 178);
  1885. animation: rotate 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
  1886. }
  1887. .ojb_btn_popover.running i {
  1888. color: rgba(33, 33, 33, 0.1);
  1889. }
  1890. .ojb_btn_popover.running i:before {
  1891. content: "\\e600";
  1892. color: rgb(168, 171, 178);
  1893. animation: rotate 1s linear infinite;
  1894. }
  1895. .ojb_btn_popover.warning i {
  1896. color: rgba(230, 162, 61, 0.8);
  1897. }
  1898. .ojb_btn_popover.warning i:before {
  1899. content: "\\e68b";
  1900. font-size: 15px;
  1901. left: 10px;
  1902. bottom: 0%;
  1903. color: #ff9800;
  1904. }
  1905. .ojb_btn_popover.error i {
  1906. color: rgba(245, 108, 108, 0.8);
  1907. }
  1908. .ojb_btn_popover.error i:before {
  1909. content: "\\e651";
  1910. font-size: 15px;
  1911. left: 10px;
  1912. bottom: 0%;
  1913. color: #F44336;
  1914. }
  1915. .ojb_btn_popover.success i {
  1916. color: rgba(76, 175, 80, 0.9);
  1917. }
  1918. .ojb_btn_popover.success i:before {
  1919. content: "\\e61e";
  1920. font-size: 15px;
  1921. left: 10px;
  1922. bottom: 0%;
  1923. color: #4caf50;
  1924. }
  1925. .ojb_btn_popover.enabled i {
  1926. color: rgba(33, 150, 243, 0.6);
  1927. }
  1928. .ojb_btn_popover.enabled i:before {
  1929. content: "\\e6f4";
  1930. font-size: 15px;
  1931. left: 10px;
  1932. bottom: 0%;
  1933. color: #2196F3;
  1934. }
  1935. .ojb_btn_popover.redo i {
  1936. color: rgba(33, 33, 33, 0.1);
  1937. }
  1938. .ojb_btn_popover.redo i:before {
  1939. content: "\\e831";
  1940. color: #616161;
  1941. }
  1942. .ojb_btn_popover.reverse i {
  1943. transform: rotate(180deg);
  1944. }
  1945.  
  1946. /*translateDiv样式*/
  1947. .translateDiv .topText {
  1948. display: flex;
  1949. margin-left: 5px;
  1950. color: #9e9e9e;
  1951. font-size: 13px;
  1952. align-items: center;
  1953. }
  1954. .translateDiv .borderlessButton{
  1955. display: flex;
  1956. align-items: center;
  1957. margin: 2.5px 7px;
  1958. fill: #9E9E9E;
  1959. }
  1960. .translateDiv .borderlessButton:hover{
  1961. cursor: pointer;
  1962. fill: #059669;
  1963. }
  1964. .translateDiv.bounce-in {
  1965. animation: bounce-in 1s forwards;
  1966. }
  1967. html:not([data-theme='dark']) .translateDiv {
  1968. box-shadow: 0px 0px 0.5px 0.5px #defdf378;
  1969. }
  1970. .translate-problem-statement {
  1971. justify-items: start;
  1972. letter-spacing: 1.8px;
  1973. color: #059669;
  1974. background-color: #f9f9fa;
  1975. border: 1px solid #c5ebdf;
  1976. border-radius: 0rem 0rem 0.3rem 0.3rem;
  1977. padding: 5px;
  1978. margin: -5px 0px 6px 0px;
  1979. width: 100%;
  1980. box-sizing: border-box;
  1981. font-size: 13px;
  1982. }
  1983. .translate-problem-statement h3 {
  1984. font-size: 1.3em;
  1985. font-weight: 700;
  1986. }
  1987. .translate-problem-statement-panel{
  1988. display: flex;
  1989. justify-content: space-between;
  1990. background-color: #f9f9fa;
  1991. border: 1px solid #c5ebdf;
  1992. border-radius: 0.3rem;
  1993. margin: 4px 0px;
  1994. }
  1995. .translate-problem-statement-panel .ojb_btn {
  1996. background: none;
  1997. border: none;
  1998. color: #9e9e9e;
  1999. }
  2000. .translate-problem-statement-panel.error, .translate-problem-statement.error {
  2001. color: red;
  2002. border-color: red;
  2003. }
  2004. .translate-problem-statement a, .translate-problem-statement a:link {
  2005. color: #10b981;
  2006. font-weight: 600;
  2007. background: 0 0;
  2008. text-decoration: none;
  2009. }
  2010. .translate-problem-statement ol, .translate-problem-statement ul {
  2011. display: grid;
  2012. margin-inline-start: 0.8em;
  2013. margin-block-start: 0em;
  2014. margin: 0.5em 0 0 3em;
  2015. padding-inline-start: 0px;
  2016. }
  2017. .translate-problem-statement li {
  2018. display: list-item;
  2019. height: auto;
  2020. word-wrap: break-word;
  2021. }
  2022. .translate-problem-statement ol li {
  2023. list-style-type: auto;
  2024. }
  2025. .translate-problem-statement ul li {
  2026. list-style-type: disc;
  2027. }
  2028. .translate-problem-statement img {
  2029. max-width: 100.0%;
  2030. max-height: 100.0%;
  2031. }
  2032. #task-statement .translate-problem-statement .MathJax {
  2033. color: #059669!important;
  2034. }
  2035. .translate-problem-statement span.math {
  2036. margin: 0px 2.5px !important;
  2037. }
  2038. .translate-problem-statement a:hover {
  2039. background-color: #800;
  2040. color: #fff;
  2041. text-decoration: none;
  2042. }
  2043. .translate-problem-statement table {
  2044. border: 1px #ccc solid !important;
  2045. margin: 1.5em 0 !important;
  2046. color: #059669 !important;
  2047. }
  2048. .translate-problem-statement table thead th {
  2049. border: 1px #ccc solid !important;
  2050. color: #059669 !important;
  2051. }
  2052. .translate-problem-statement table td {
  2053. border-right: 1px solid #ccc;
  2054. border-top: 1px solid #ccc;
  2055. padding: 0.7143em 0.5em;
  2056. }
  2057. .translate-problem-statement table th {
  2058. padding: 0.7143em 0.5em;
  2059. }
  2060. .translate-problem-statement p:not(:first-child) {
  2061. margin: 1.5em 0 0;
  2062. }
  2063. .translate-problem-statement p {
  2064. line-height: 20px !important;
  2065. word-wrap: break-word;
  2066. font-size: 13px !important
  2067. }
  2068. .problem-statement p:last-child {
  2069. margin-bottom: 0px !important;
  2070. }
  2071.  
  2072. /*设置按钮*/
  2073. header .enter-or-register-box, header .languages {
  2074. position: absolute;
  2075. right: 170px;
  2076. }
  2077. .ojb_btn.OJBetter_setting {
  2078. float: right;
  2079. height: 30px;
  2080. background: #60a5fa;
  2081. color: white;
  2082. margin: 10px;
  2083. border: 1px solid #60a5fa;
  2084. }
  2085. .ojb_btn.OJBetter_setting.open {
  2086. background-color: #e6e6e6;
  2087. color: #727378;
  2088. cursor: not-allowed;
  2089. }
  2090.  
  2091. /*设置面板*/
  2092. .OJBetter_setting_menu {
  2093. box-shadow: 0px 0px 0px 4px #ffffff;
  2094. position: fixed;
  2095. top: 50%;
  2096. left: 50%;
  2097. width: 600px;
  2098. min-height: 600px;
  2099. transform: translate(-50%, -50%);
  2100. border-radius: 6px;
  2101. background-color: #f0f4f9;
  2102. border-collapse: collapse;
  2103. border: 1px solid #ffffff;
  2104. color: #697e91;
  2105. font-family: var(--vp-font-family-base);
  2106. padding: 10px 20px 20px 10px;
  2107. box-sizing: content-box;
  2108. }
  2109. .OJBetter_setting_menu h3 {
  2110. margin-top: 10px;
  2111. font-size: 1.4em;
  2112. font-weight: 700;
  2113. }
  2114. .OJBetter_setting_menu h4 {
  2115. margin: 15px 0px 10px 0px;
  2116. }
  2117. .OJBetter_setting_menu h4,.OJBetter_setting_menu h5 {
  2118. font-weight: 600;
  2119. }
  2120. .OJBetter_setting_menu hr {
  2121. border: none;
  2122. height: 1px;
  2123. background-color: #ccc;
  2124. margin: 10px 0;
  2125. }
  2126. .OJBetter_setting_menu details {
  2127. padding: 10px;
  2128. margin-bottom: 5px;
  2129. background-color: #ffffff;
  2130. border-bottom: 1px solid #c9c6c696;
  2131. border-radius: 8px;
  2132. }
  2133. .OJBetter_setting_menu .badge {
  2134. border-radius: 4px;
  2135. border: 1px solid #009688;
  2136. color: #009688;
  2137. background-color: #fff;
  2138. padding: 0.5px 4px;
  2139. margin-left: 5px;
  2140. margin-right: auto;
  2141. line-height: initial;
  2142. font-weight: initial;
  2143. }
  2144. .OJBetter_setting_menu .missing {
  2145. box-shadow: inset 0px 0px 1px 1px red;
  2146. }
  2147. /* 页面切换 */
  2148. .OJBetter_setting_menu .settings-page {
  2149. display: none;
  2150. }
  2151. .OJBetter_setting_menu .settings-page.active {
  2152. display: block;
  2153. }
  2154. .OJBetter_setting_container {
  2155. display: flex;
  2156. }
  2157. .OJBetter_setting_sidebar {
  2158. flex: 0 0 auto;
  2159. min-width: 110px;
  2160. padding: 6px 10px 6px 6px;
  2161. margin: 20px 0px;
  2162. border-right: 1px solid #d4d8e9;
  2163. }
  2164. .OJBetter_setting_content {
  2165. flex-grow: 1;
  2166. margin: 20px 0px 0px 12px;
  2167. padding-right: 10px;
  2168. max-height: 580px;
  2169. overflow-y: auto;
  2170. box-sizing: border-box;
  2171. }
  2172. .OJBetter_setting_sidebar h3 {
  2173. margin-top: 0;
  2174. }
  2175. .OJBetter_setting_sidebar hr {
  2176. margin-top: 10px;
  2177. margin-bottom: 10px;
  2178. border: none;
  2179. border-top: 1px solid #DADCE0;
  2180. }
  2181. .OJBetter_setting_sidebar ul {
  2182. list-style-type: none;
  2183. margin: 0;
  2184. padding: 0;
  2185. }
  2186. .OJBetter_setting_sidebar li {
  2187. margin: 5px 0px;
  2188. background-color: #ffffff;
  2189. border: 1px solid #d4d8e9;
  2190. border-radius: 4px;
  2191. font-size: 16px;
  2192. }
  2193. .OJBetter_setting_sidebar li a {
  2194. text-decoration: none;
  2195. display: flex;
  2196. width: 100%;
  2197. font-size: 16px;
  2198. color: gray;
  2199. background-color: #ffffff;
  2200. border: none;
  2201. letter-spacing: 2px;
  2202. padding: 7px;
  2203. margin: 0px;
  2204. border-radius: 4px;
  2205. align-items: center;
  2206. -webkit-box-sizing: border-box;
  2207. -moz-box-sizing: border-box;
  2208. box-sizing: border-box;
  2209. }
  2210. .OJBetter_setting_sidebar li a.active {
  2211. background-color: #eceff1c7;
  2212. }
  2213. /* 链接样式 */
  2214. .OJBetter_setting_menu a {
  2215. font-size: 13px;
  2216. color: #009688;
  2217. background-color: #E0F2F1;
  2218. border: 1px solid #009688;
  2219. border-radius: 4px;
  2220. padding: 0px 5px;
  2221. margin: 0px 5px;
  2222. text-decoration: none;
  2223. }
  2224. /* 下拉选择框 */
  2225. .OJBetter_setting_menu select {
  2226. appearance: none;
  2227. padding: 5px 10px;
  2228. margin: -5px 0px;
  2229. border-radius: 6px;
  2230. border-style: solid;
  2231. border: 1px solid #ced4da;
  2232. color: #009688;
  2233. background: #ffffff;
  2234. font-size: 15px;
  2235. }
  2236. .OJBetter_setting_menu select:focus-visible {
  2237. outline: none;
  2238. }
  2239. .OJBetter_setting_menu select option:disabled {
  2240. color: #EEEEEE;
  2241. }
  2242. /* 数值输入框 */
  2243. .OJBetter_setting_menu input[type="number"] {
  2244. width: 40px;
  2245. color: #009688;
  2246. font-size: 15px;
  2247. appearance: none;
  2248. padding: 5px 10px;
  2249. margin: -5px 3px;
  2250. border-radius: 6px;
  2251. border-style: solid;
  2252. border: 1px solid #ced4da;
  2253. }
  2254. .OJBetter_setting_menu input[type="number"]:focus-visible {
  2255. outline: none;
  2256. }
  2257. .OJBetter_setting_menu input[type="number"]::-webkit-inner-spin-button,
  2258. .OJBetter_setting_menu input[type="number"]::-webkit-outer-spin-button {
  2259. -webkit-appearance: none;
  2260. margin: 0;
  2261. }
  2262. /*设置面板-滚动条*/
  2263. .OJBetter_setting_menu::-webkit-scrollbar, .OJBetter_setting_content::-webkit-scrollbar,
  2264. .OJBetter_modal .content::-webkit-scrollbar {
  2265. width: 5px;
  2266. height: 7px;
  2267. background-color: #aaa;
  2268. }
  2269. .OJBetter_setting_menu::-webkit-scrollbar-thumb, .OJBetter_setting_content::-webkit-scrollbar-thumb,
  2270. .OJBetter_modal .content::-webkit-scrollbar-thumb {
  2271. background-clip: padding-box;
  2272. background-color: #d7d9e4;
  2273. }
  2274. .OJBetter_setting_menu::-webkit-scrollbar-track, .OJBetter_setting_content::-webkit-scrollbar-track,
  2275. .OJBetter_modal .content::-webkit-scrollbar-track {
  2276. background-color: #f1f1f1;
  2277. }
  2278. /*设置面板-关闭按钮*/
  2279. .OJBetter_setting_menu .tool-box {
  2280. position: absolute;
  2281. width: 20px;
  2282. height: 20px;
  2283. top: 3px;
  2284. right: 3px;
  2285. }
  2286. .OJBetter_setting_menu .btn-close {
  2287. width: 20px;
  2288. height: 20px;
  2289. border-radius: 50%;
  2290. border: none;
  2291. margin: 0px;
  2292. padding: 0px;
  2293. background-color: #ff000080;
  2294. transition: .15s ease all;
  2295. box-sizing: border-box;
  2296. text-align: center;
  2297. color: transparent;
  2298. }
  2299. .OJBetter_setting_menu .iconfont {
  2300. font-size: 10px;
  2301. font-weight: bolder;
  2302. }
  2303. .OJBetter_setting_menu .btn-close:hover {
  2304. color: #ffffff;
  2305. background-color: #ff0000cc;
  2306. box-shadow: 0 5px 5px 0 #00000026;
  2307. }
  2308. .OJBetter_setting_menu .btn-close:active {
  2309. color: #ffffffde;
  2310. background-color: #ff000080;
  2311. }
  2312. /*设置面板-checkbox*/
  2313. .OJBetter_setting_menu input[type=checkbox]:focus {
  2314. outline: 0px;
  2315. }
  2316. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"] {
  2317. margin: 0px;
  2318. appearance: none;
  2319. -webkit-appearance: none;
  2320. width: 40px;
  2321. height: 20px;
  2322. border: 1.5px solid #D7CCC8;
  2323. padding: 0px !important;
  2324. border-radius: 20px;
  2325. background: #efebe978;
  2326. position: relative;
  2327. box-sizing: border-box;
  2328. }
  2329. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::before {
  2330. content: "";
  2331. width: 17px;
  2332. height: 17px;
  2333. background: #D7CCC8;
  2334. border: 1.5px solid #BCAAA4;
  2335. border-radius: 50%;
  2336. position: absolute;
  2337. top: 0;
  2338. left: 0;
  2339. transform: translate(2%, 2%);
  2340. transition: all 0.3s ease-in-out;
  2341. box-sizing: border-box;
  2342. }
  2343. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::after {
  2344. 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");
  2345. position: absolute;
  2346. top: 0;
  2347. left: 24px;
  2348. }
  2349. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked {
  2350. border: 1.5px solid #C5CAE9;
  2351. background: #E8EAF6;
  2352. }
  2353. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::before {
  2354. background: #C5CAE9;
  2355. border: 1.5px solid #7986CB;
  2356. transform: translate(122%, 2%);
  2357. transition: all 0.3s ease-in-out;
  2358. }
  2359. .OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::after {
  2360. 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");
  2361. position: absolute;
  2362. top: 1.5px;
  2363. left: 4.5px;
  2364. }
  2365. .OJBetter_setting_menu .OJBetter_setting_list button {
  2366. cursor: pointer;
  2367. color: #7986cb;
  2368. background-color: #e8eaf6;
  2369. border: 1px solid #7986cb;
  2370. border-radius: 6px;
  2371. width: 100px;
  2372. margin: -5px 2px;
  2373. padding: 5px 10px;
  2374. }
  2375. .OJBetter_setting_menu .OJBetter_setting_list button:hover {
  2376. color: #e8eaf6;
  2377. background-color: #7986cb;
  2378. border: 1px solid #7986cb;
  2379. }
  2380. .OJBetter_setting_menu label, #darkMode_span, #loaded_span {
  2381. font-size: 16px;
  2382. }
  2383. .OJBetter_setting_list {
  2384. display: flex;
  2385. flex-wrap: wrap;
  2386. align-items: center;
  2387. padding: 10px;
  2388. margin: 5px 0px;
  2389. background-color: #ffffff;
  2390. border: 1px solid #c9c6c642;
  2391. border-bottom-color: #c9c6c696;
  2392. border-radius: 8px;
  2393. justify-content: space-between;
  2394. }
  2395. .OJBetter_setting_list.alert_danger {
  2396. color: #F44336;
  2397. background-color: #FFEBEE;
  2398. border: 1px solid #F44336;
  2399. margin: 10px 0px;
  2400. }
  2401. .OJBetter_setting_list.alert_warn {
  2402. color: #E65100;
  2403. background-color: #FFF3E0;
  2404. border: 1px solid #FF9800;
  2405. margin: 10px 0px;
  2406. }
  2407. .OJBetter_setting_list.alert_tip {
  2408. color: #009688;
  2409. background-color: #E0F2F1;
  2410. border: 1px solid #009688;
  2411. margin: 10px 0px;
  2412. }
  2413. .OJBetter_setting_list.alert_info {
  2414. color: #ffffff;
  2415. background-color: #009688;
  2416. margin: 10px 0px;
  2417. box-shadow: rgba(0, 0, 0, 0.06) 0px 2px 4px 0px inset;
  2418. }
  2419. .OJBetter_setting_list p:not(:last-child) {
  2420. margin-bottom: 10px;
  2421. }
  2422. .OJBetter_setting_list p:not(:first-child) {
  2423. margin-top: 10px;
  2424. }
  2425. /*设置面板-checkboxs*/
  2426. .OJBetter_setting_menu .OJBetter_checkboxs {
  2427. flex-basis: 100%;
  2428. display: flex;
  2429. padding: 8px;
  2430. margin: 10px 0px 0px 0px;
  2431. border-bottom: 1px solid #c9c6c696;
  2432. border-radius: 8px;
  2433. border: 1px solid #c5cae9;
  2434. background-color: #f0f8ff;
  2435. }
  2436. .OJBetter_setting_menu .OJBetter_checkboxs label {
  2437. font-size: 13px;
  2438. margin: 0px 6px 0px 3px;
  2439. }
  2440. .OJBetter_setting_menu .OJBetter_checkboxs input[type=checkbox]:checked+label{
  2441. color: #7986cb;
  2442. }
  2443. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"] {
  2444. border: none;
  2445. width: 16px;
  2446. height: 16px;
  2447. }
  2448. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before{
  2449. background: #ffffff;
  2450. transform: none;
  2451. width: 16px;
  2452. height: 16px;
  2453. }
  2454. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked {
  2455. background: none;
  2456. border: none;
  2457. }
  2458. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before {
  2459. border: 1.5px solid #95a2de;
  2460. background: #e8eaf6;
  2461. transform: none;
  2462. }
  2463. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::after {
  2464. 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");
  2465. top: 0px;
  2466. left: 3.5px;
  2467. }
  2468. .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:disabled+label {
  2469. color: #BDBDBD;
  2470. }
  2471. /*设置面板-radio*/
  2472. .OJBetter_setting_menu label {
  2473. display: block;
  2474. font-weight: initial;
  2475. list-style-type: none;
  2476. padding-inline-start: 0px;
  2477. overflow-x: auto;
  2478. max-width: 100%;
  2479. margin: 3px 0px;
  2480. overflow-x: visible;
  2481. }
  2482. .OJBetter_setting_menu_label_text {
  2483. display: flex;
  2484. border: 1px dashed #00aeeccc;
  2485. height: 35px;
  2486. width: 100%;
  2487. color: #6e6e6e;
  2488. font-weight: 300;
  2489. font-size: 14px;
  2490. letter-spacing: 2px;
  2491. padding: 7px;
  2492. margin-bottom: 4px;
  2493. align-items: center;
  2494. -webkit-box-sizing: border-box;
  2495. -moz-box-sizing: border-box;
  2496. box-sizing: border-box;
  2497. }
  2498. input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
  2499. background: #41e49930;
  2500. border: 1px solid green;
  2501. color: green;
  2502. text-shadow: 0px 0px 0.5px green;
  2503. }
  2504. input[type="radio"]:disabled+.OJBetter_setting_menu_label_text {
  2505. background: #fafafa00;
  2506. border: 1px solid #e0e0e07a;
  2507. color: #e0e0e0;
  2508. }
  2509. .OJBetter_setting_menu label input[type="radio"], .OJBetter_contextmenu label input[type="radio"]{
  2510. appearance: none;
  2511. list-style: none;
  2512. padding: 0px !important;
  2513. margin: 0px;
  2514. clip: rect(0 0 0 0);
  2515. -webkit-clip-path: inset(100%);
  2516. clip-path: inset(100%);
  2517. height: 1px;
  2518. overflow: hidden;
  2519. position: absolute;
  2520. white-space: nowrap;
  2521. width: 1px;
  2522. }
  2523. /*设置面板-文本输入框*/
  2524. .OJBetter_setting_menu input[type="text"] {
  2525. display: block;
  2526. height: 25px !important;
  2527. width: 100%;
  2528. background-color: #ffffff;
  2529. color: #727378;
  2530. font-size: 12px;
  2531. border-radius: 0.3rem;
  2532. padding: 1px 5px !important;
  2533. box-sizing: border-box;
  2534. margin: 5px 0px 5px 0px;
  2535. border: 1px solid #00aeeccc;
  2536. box-shadow: 0 0 1px #0000004d;
  2537. }
  2538. .OJBetter_setting_menu .OJBetter_setting_list input[type="text"] {
  2539. margin-left: 5px;
  2540. }
  2541. .OJBetter_setting_menu input[type="text"]:focus-visible{
  2542. border-style: solid;
  2543. border-color: #3f51b5;
  2544. outline: none;
  2545. }
  2546. .OJBetter_setting_menu_config_box {
  2547. width: 100%;
  2548. display: grid;
  2549. margin-top: 5px;
  2550. -webkit-box-sizing: border-box;
  2551. -moz-box-sizing: border-box;
  2552. box-sizing: border-box;
  2553. }
  2554. .OJBetter_setting_menu input::placeholder {
  2555. color: #727378;
  2556. }
  2557. .OJBetter_setting_menu input.no_default::placeholder{
  2558. color: #BDBDBD;
  2559. }
  2560. .OJBetter_setting_menu input.is_null::placeholder{
  2561. color: red;
  2562. border-width: 1.5px;
  2563. }
  2564. .OJBetter_setting_menu input.is_null{
  2565. border-color: red;
  2566. }
  2567. .OJBetter_setting_menu textarea {
  2568. resize: vertical;
  2569. display: block;
  2570. width: 100%;
  2571. height: 60px;
  2572. background-color: #ffffff;
  2573. color: #727378;
  2574. font-size: 12px;
  2575. padding: 1px 5px !important;
  2576. box-sizing: border-box;
  2577. margin: 5px 0px 5px 0px;
  2578. border: 1px solid #00aeeccc;
  2579. box-shadow: 0 0 1px #0000004d;
  2580. }
  2581. .OJBetter_setting_menu textarea:focus-visible{
  2582. border-style: solid;
  2583. border-color: #3f51b5;
  2584. outline: none;
  2585. }
  2586. .OJBetter_setting_menu textarea::placeholder{
  2587. color: #BDBDBD;
  2588. font-size: 14px;
  2589. }
  2590. .OJBetter_setting_menu #tempConfig_save {
  2591. cursor: pointer;
  2592. display: inline-flex;
  2593. padding: 5px;
  2594. background-color: #1aa06d;
  2595. color: #ffffff;
  2596. font-size: 14px;
  2597. line-height: 1.5rem;
  2598. font-weight: 500;
  2599. justify-content: center;
  2600. width: 100%;
  2601. border-radius: 0.375rem;
  2602. border: none;
  2603. box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  2604. margin-top: 20px
  2605. }
  2606. .OJBetter_setting_menu button#debug_button.debug_button {
  2607. width: 18%;
  2608. }
  2609. .OJBetter_setting_menu span.tip {
  2610. color: #999;
  2611. font-size: 12px;
  2612. font-weight: 500;
  2613. padding: 5px 0px;
  2614. }
  2615. /*设置面板-tip*/
  2616. .help_tip {
  2617. margin-right: auto;
  2618. }
  2619. span.input_label {
  2620. font-size: 14px;
  2621. }
  2622. .help_tip .tip_text {
  2623. display: none;
  2624. position: absolute;
  2625. color: #697e91;
  2626. font-weight: 400;
  2627. font-size: 14px;
  2628. letter-spacing: 0px;
  2629. background-color: #ffffff;
  2630. padding: 10px;
  2631. margin: 5px 0px;
  2632. border-radius: 4px;
  2633. border: 1px solid #e4e7ed;
  2634. box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);
  2635. z-index: 100;
  2636. }
  2637. .help_tip .tip_text p {
  2638. margin-bottom: 5px;
  2639. }
  2640. .help_tip .tip_text:before {
  2641. content: "";
  2642. position: absolute;
  2643. top: -20px;
  2644. right: -10px;
  2645. bottom: -10px;
  2646. left: -10px;
  2647. z-index: -1;
  2648. }
  2649. .help-icon {
  2650. cursor: help;
  2651. width: 15px;
  2652. color: #b4b9d4;
  2653. margin-left: 5px;
  2654. margin-top: 3px;
  2655. }
  2656. .OJBetter_setting_menu .OJBetter_setting_menu_label_text .help_tip .help-icon {
  2657. color: #7fbeb2;
  2658. }
  2659. .help_tip .help-icon:hover + .tip_text, .help_tip .tip_text:hover {
  2660. display: block;
  2661. cursor: help;
  2662. width: 250px;
  2663. }
  2664. /* 版本信息 */
  2665. .OJBetter_setting_menu .versionInfo{
  2666. display: grid;
  2667. justify-items: center;
  2668. font-size: 16px;
  2669. padding: 10px;
  2670. }
  2671. .OJBetter_setting_menu .versionInfo>* {
  2672. margin: 10px 0px;
  2673. }
  2674.  
  2675. /* 配置管理面板 */
  2676. .config{
  2677. width: 100%;
  2678. margin: 10px 0px;
  2679. }
  2680. .config li.tempConfig_add_button {
  2681. cursor: pointer;
  2682. height: 40px;
  2683. border: 1px dashed #BDBDBD;
  2684. border-radius: 8px;
  2685. background-color: #fcfbfb36;
  2686. color: #bdbdbd;
  2687. font-size: 14px;
  2688. align-items: center;
  2689. justify-content: center;
  2690. }
  2691. .config li.tempConfig_add_button:hover {
  2692. border: 1px dashed #03A9F4;
  2693. background-color: #d7f0fb8c;
  2694. color: #03A9F4;
  2695. }
  2696. .config .config_bar_list {
  2697. display: flex;
  2698. width: 100%;
  2699. padding-bottom: 2px;
  2700. border: 1px solid #c5cae9;
  2701. background-color: #f0f8ff;
  2702. box-sizing: border-box;
  2703. border-radius: 0px 0px 8px 8px;
  2704. }
  2705. .config .config_bar_list input[type="radio"] {
  2706. appearance: none;
  2707. width: 0;
  2708. height: 0;
  2709. overflow: hidden;
  2710. }
  2711. .config .config_bar_list input[type="radio"] {
  2712. margin: 0px;
  2713. }
  2714. .config .config_bar_list input[type=radio]:focus {
  2715. outline: 0px;
  2716. }
  2717. .config .config_bar_ul_li_text {
  2718. display: flex;
  2719. align-items: center;
  2720. justify-content: center;
  2721. max-width: 100%;
  2722. height: 40px;
  2723. overflow-x: auto;
  2724. font-size: 14px;
  2725. font-weight: 400;
  2726. margin: 0px 4px;
  2727. padding: 3px;
  2728. border: 1px solid #dedede;
  2729. border-radius: 10px;
  2730. box-shadow: 0px 2px 4px 0px rgba(0,0,0,.05);
  2731. box-sizing: border-box;
  2732. }
  2733. .config .config_bar_ul li button {
  2734. background-color: #e6e6e6;
  2735. color: #727378;
  2736. height: 23px;
  2737. font-size: 14px;
  2738. border-radius: 0.3rem;
  2739. padding: 1px 5px;
  2740. margin: 5px;
  2741. border: none;
  2742. box-shadow: 0 0 1px #0000004d;
  2743. }
  2744. .config .config_bar_ul {
  2745. display: flex;
  2746. align-items: center;
  2747. list-style-type: none;
  2748. padding-inline-start: 0px;
  2749. overflow-x: auto;
  2750. max-width: 100%;
  2751. margin: 0px;
  2752. padding: 5px;
  2753. }
  2754. .config .config_bar_ul li {
  2755. width: 80px;
  2756. display: grid;
  2757. margin: 4px 4px;
  2758. min-width: 100px;
  2759. box-sizing: border-box;
  2760. }
  2761. .config .config_bar_ul_li_text:hover {
  2762. background-color: #eae4dc24;
  2763. }
  2764. input[type="radio"]:checked + .config_bar_ul_li_text {
  2765. background: #41b3e430;
  2766. border: 1px solid #5e7ce0;
  2767. color: #5e7ce0;
  2768. }
  2769. .config .config_bar_ul::-webkit-scrollbar {
  2770. width: 5px;
  2771. height: 4px;
  2772. }
  2773. .config .config_bar_ul::-webkit-scrollbar-thumb {
  2774. background-clip: padding-box;
  2775. background-color: #d7d9e4;
  2776. border-radius: 8px;
  2777. }
  2778. .config .config_bar_ul::-webkit-scrollbar-button:start:decrement {
  2779. width: 4px;
  2780. background-color: transparent;
  2781. }
  2782. .config .config_bar_ul::-webkit-scrollbar-button:end:increment {
  2783. width: 4px;
  2784. background-color: transparent;
  2785. }
  2786. .config .config_bar_ul::-webkit-scrollbar-track {
  2787. border-radius: 5px;
  2788. }
  2789. .config .config_bar_ul_li_text::-webkit-scrollbar {
  2790. width: 5px;
  2791. height: 7px;
  2792. background-color: #aaa;
  2793. }
  2794. .config .config_bar_ul_li_text::-webkit-scrollbar-thumb {
  2795. background-clip: padding-box;
  2796. background-color: #d7d9e4;
  2797. }
  2798. .config .config_bar_ul_li_text::-webkit-scrollbar-track {
  2799. background-color: #f1f1f1;
  2800. }
  2801. .config .config_bar_list_add_div {
  2802. display: flex;
  2803. height: 40px;
  2804. margin: 4px 2px;
  2805. }
  2806.  
  2807. /* 修改菜单 */
  2808. #config_bar_menu {
  2809. z-index: 400;
  2810. position: fixed;
  2811. width: 60px;
  2812. background: #ffffff;
  2813. box-shadow: 1px 1px 4px 0px #0000004d;
  2814. border: 0px solid rgba(0,0,0,0.04);
  2815. border-radius: 4px;
  2816. padding: 8px 0;
  2817. }
  2818. .config_bar_menu_item {
  2819. cursor: pointer;
  2820. padding: 2px 6px;
  2821. display: flex;
  2822. justify-content: center;
  2823. align-items: center;
  2824. height: 32px;
  2825. font-size: 14px;
  2826. font-weight: 500;
  2827. box-shadow: inset 0px 0px 0px 0px #8bb2d9;
  2828. }
  2829. #config_bar_menu_edit:hover {
  2830. background-color: #00aeec;
  2831. color: white;
  2832. }
  2833. #config_bar_menu_delete:hover {
  2834. background-color: #FF5722;
  2835. color: white;
  2836. }
  2837.  
  2838. /* 配置编辑页面 */
  2839. #config_edit_menu {
  2840. z-index: 300;
  2841. width: 450px;
  2842. }
  2843.  
  2844. /* 黑暗模式选项按钮 */
  2845. .dark-mode-selection {
  2846. display: flex;
  2847. justify-content: center;
  2848. align-items: center;
  2849. max-width: 350px;
  2850. -webkit-user-select: none;
  2851. -moz-user-select: none;
  2852. -ms-user-select: none;
  2853. user-select: none;
  2854. }
  2855. .dark-mode-selection label {
  2856. margin: 8px 0px 8px 8px;
  2857. }
  2858. .dark-mode-selection > * {
  2859. margin: 6px;
  2860. }
  2861. .dark-mode-selection .OJBetter_setting_menu_label_text {
  2862. border-radius: 8px;
  2863. margin-bottom: 0px;
  2864. }
  2865.  
  2866. /*确认弹窗*/
  2867. .OJBetter_modal {
  2868. z-index: 600;
  2869. display: grid;
  2870. position: fixed;
  2871. top: 50%;
  2872. left: 50%;
  2873. transform: translate(-50%, -50%);
  2874. font-size: 12px;
  2875. font-family: var(--vp-font-family-base);
  2876. width: max-content;
  2877. padding: 10px 20px;
  2878. box-shadow: 0px 0px 0px 4px #ffffff;
  2879. border-radius: 6px;
  2880. background-color: #f0f4f9;
  2881. border-collapse: collapse;
  2882. border: 1px solid #ffffff;
  2883. color: #697e91;
  2884. }
  2885. .OJBetter_modal h2 {
  2886. font-size: 1.6em;
  2887. font-weight: 700;
  2888. }
  2889. .OJBetter_modal .content{
  2890. white-space: nowrap;
  2891. max-height: 500px;
  2892. overflow-y: auto;
  2893. }
  2894. .OJBetter_modal .buttons{
  2895. display: flex;
  2896. padding-top: 15px;
  2897. }
  2898. .OJBetter_modal button {
  2899. display: inline-flex;
  2900. justify-content: center;
  2901. align-items: center;
  2902. line-height: 1;
  2903. white-space: nowrap;
  2904. cursor: pointer;
  2905. text-align: center;
  2906. box-sizing: border-box;
  2907. outline: none;
  2908. transition: .1s;
  2909. user-select: none;
  2910. vertical-align: middle;
  2911. -webkit-appearance: none;
  2912. height: 24px;
  2913. padding: 5px 11px;
  2914. margin-right: 15px;
  2915. font-size: 12px;
  2916. border-radius: 4px;
  2917. color: #ffffff;
  2918. background: #009688;
  2919. border-color: #009688;
  2920. border: none;
  2921. }
  2922. .OJBetter_modal button.secondary{
  2923. background-color:#4DB6AC;
  2924. }
  2925. .OJBetter_modal button:hover{
  2926. background-color:#4DB6AC;
  2927. }
  2928. .OJBetter_modal button.secondary:hover {
  2929. background-color: #80CBC4;
  2930. }
  2931. .OJBetter_modal .help-icon {
  2932. margin: 0px 8px 0px 0px;
  2933. height: 1em;
  2934. width: 1em;
  2935. line-height: 1em;
  2936. display: inline-flex;
  2937. justify-content: center;
  2938. align-items: center;
  2939. position: relative;
  2940. fill: currentColor;
  2941. font-size: inherit;
  2942. }
  2943. .OJBetter_modal p {
  2944. margin: 5px 0px;
  2945. }
  2946.  
  2947. /* 右键菜单 */
  2948. .OJBetter_contextmenu {
  2949. z-index: 500;
  2950. display: grid;
  2951. position: absolute;
  2952. background-color: #f0f4f9;
  2953. border-collapse: collapse;
  2954. color: #697e91;
  2955. font-family: var(--vp-font-family-base);
  2956. overflow: hidden;
  2957. box-sizing: content-box;
  2958. box-shadow: 0px 0px 0px 2px #eddbdb4d;
  2959. }
  2960. .OJBetter_contextmenu label {
  2961. margin: 0px;
  2962. }
  2963. input[type="radio"]:checked+.OJBetter_contextmenu_label_text {
  2964. background: #41e49930;
  2965. border: 1px solid green;
  2966. color: green;
  2967. font-weight: 500;
  2968. }
  2969. .OJBetter_contextmenu_label_text {
  2970. display: flex;
  2971. border: 1px dashed #80cbc4;
  2972. height: 26px;
  2973. width: 100%;
  2974. color: gray;
  2975. font-size: 13px;
  2976. font-weight: initial;
  2977. padding: 4px;
  2978. align-items: center;
  2979. -webkit-box-sizing: border-box;
  2980. -moz-box-sizing: border-box;
  2981. box-sizing: border-box;
  2982. }
  2983. .OJBetter_contextmenu_label_text:hover {
  2984. color: #F44336;
  2985. border: 1px dashed #009688;
  2986. background-color: #ffebcd;
  2987. }
  2988.  
  2989. /* RatingByClist */
  2990. .ratingBadge, html[data-theme=dark] button.ratingBadge{
  2991. display: block;
  2992. font-weight: 700;
  2993. font-size: 11px;
  2994. margin-top: 5px;
  2995. padding: 2px;
  2996. border-radius: 4px;
  2997. color: #B0BEC5;
  2998. border: 1px solid #cccccc66;
  2999. }
  3000.  
  3001. /* 多选翻译 */
  3002. .block_selected{
  3003. box-shadow: 0px 0px 0px 1px #FF9800;
  3004. outline: none;
  3005. }
  3006.  
  3007. /* 悬浮菜单 */
  3008. .OJBetter_MiniTranslateButton {
  3009. z-index: 100;
  3010. display: grid;
  3011. position: absolute;
  3012. border-collapse: collapse;
  3013. fill: #F57C00;
  3014. background-color: #FFF3E0;
  3015. overflow: hidden;
  3016. box-sizing: content-box;
  3017. box-shadow: 0px 0px 0px 2px #FFE0B2;
  3018. border-radius: 100%;
  3019. }
  3020. .OJBetter_MiniTranslateButton:hover {
  3021. cursor: pointer;
  3022. box-shadow: 0px 0px 0px 2px #FFB74D;
  3023. }
  3024.  
  3025. /* acmsguru划分块 */
  3026. .OJBetter_acmsguru {
  3027. margin: 0 0 1em!important;
  3028. }
  3029.  
  3030. /* 代码提交表单 */
  3031. #OJBetter_SubmitForm.input-output-copier:hover {
  3032. background-color: #ffffff00;
  3033. }
  3034. #OJBetter_SubmitForm input[type="number"] {
  3035. width: 40px;
  3036. color: #009688;
  3037. appearance: none;
  3038. border-radius: 6px;
  3039. border-style: solid;
  3040. border: none;
  3041. background-color: #ffffff00;
  3042. }
  3043. #OJBetter_SubmitForm :focus-visible {
  3044. outline: none;
  3045. }
  3046. #OJBetter_SubmitForm .topDiv {
  3047. height: 50px;
  3048. display: flex;
  3049. align-items: center;
  3050. justify-content: space-between;
  3051. padding: 10px 0px;
  3052. box-sizing: border-box;
  3053. }
  3054. #OJBetter_SubmitForm .topDiv .topRightDiv {
  3055. height: 100%;
  3056. display: flex;
  3057. flex-wrap: wrap;
  3058. gap: 0px;
  3059. }
  3060. #OJBetter_SubmitForm input[type="checkbox"], #OJBetter_SubmitForm label {
  3061. margin: 0px;
  3062. font-weight: initial;
  3063. }
  3064. #OJBetter_SubmitForm #fontSizeInput {
  3065. border: none;
  3066. background-color: #ffffff00;
  3067. }
  3068.  
  3069. /* 顶部区域 */
  3070. #OJBetter_SubmitForm .topRightDiv>* {
  3071. height: 100%;
  3072. box-sizing: border-box;
  3073. }
  3074. #OJBetter_SubmitForm .topRightDiv>button{
  3075. padding: 0px 8px;
  3076. }
  3077. #OJBetter_SubmitForm .topRightDiv {
  3078. display: flex;
  3079. flex-wrap: wrap;
  3080. gap: 0px;
  3081. align-items: center;
  3082. }
  3083.  
  3084. /* LSP连接Log */
  3085. #LSPLog{
  3086. width: 500px;
  3087. height: 500px;
  3088. position: fixed;
  3089. top: 50%;
  3090. left: 50%;
  3091. padding: 10px;
  3092. transform: translate(-50%, -50%);
  3093. border: 1px solid;
  3094. z-index: 200;
  3095. background-color: #ffffff;
  3096. }
  3097. #LSPLog button{
  3098. position: fixed;
  3099. top: 10px;
  3100. right: 10px;
  3101. z-index: 200;
  3102. }
  3103. #LSPLog #LSPLogList{
  3104. width: 500px;
  3105. height: 500px;
  3106. overflow: auto;
  3107. color: #424242;
  3108. }
  3109. #LSPLog li:nth-child(odd){
  3110. background-color: #f5f5f5;
  3111. }
  3112. #LSPLog details{
  3113. padding: 2px;
  3114. }
  3115.  
  3116. /* 代码编辑器 */
  3117. #OJBetter_editor{
  3118. box-sizing: border-box;
  3119. height: 600px;
  3120. border: 1px solid #d3d3d3;
  3121. width: 100%;
  3122. resize: vertical;
  3123. display: flex;
  3124. flex-direction: column;
  3125. }
  3126. #OJBetter_editor.fullscreen{
  3127. position: fixed;
  3128. top: 0;
  3129. left: 0;
  3130. width: 100%;
  3131. height: 100vh;
  3132. z-index: 2000;
  3133. }
  3134. #OJBetter_editor.bottom{
  3135. position: fixed;
  3136. bottom: 0;
  3137. left: 0;
  3138. width: 100%;
  3139. height: 50vh;
  3140. z-index: 2000;
  3141. }
  3142. .ojb_btn.exit_button_bottom {
  3143. position: fixed;
  3144. bottom: 30px;
  3145. right: 15px;
  3146. z-index: 2000;
  3147. height: 28px;
  3148. }
  3149.  
  3150. /* monaco */
  3151. #OJBetter_monaco {
  3152. flex: 1;
  3153. min-height: 0;
  3154. width: 100%;
  3155. }
  3156. #OJBetter_monaco .highlight {
  3157. border: 1px solid #ffffff00;
  3158. background-color: #ffffff00!important
  3159. }
  3160. .monaco-hover hr {
  3161. margin: 4px -8px 4px !important;
  3162. }
  3163.  
  3164. /* 状态底栏 */
  3165. #OJBetter_statusBar{
  3166. height: 22px;
  3167. font-size: 12px;
  3168. color: #757575;
  3169. border: 1px solid #d3d3d3;
  3170. background-color: #f8f8f8;
  3171. padding: 3px;
  3172. box-sizing: border-box;
  3173. }
  3174.  
  3175. /* 提交 */
  3176. #OJBetter_submitDiv{
  3177. display: flex;
  3178. padding-top: 15px;
  3179. height: 50px;
  3180. box-sizing: border-box;
  3181. }
  3182. #OJBetter_submitDiv >* {
  3183. border-radius: 6px;
  3184. }
  3185. #OJBetter_submitDiv > button {
  3186. height: 100%;
  3187. aspect-ratio: 1 / 1;
  3188. }
  3189. #SubmitButton {
  3190. color: #fff;
  3191. background-color: #209978;
  3192. border-color: #17795E;
  3193. }
  3194. #SubmitButton:hover {
  3195. background-color: #17795e;
  3196. }
  3197. #SubmitButton.disabled {
  3198. background-color: red;
  3199. animation: shake 0.07s infinite alternate;
  3200. }
  3201. #programTypeId{
  3202. height: 100%;
  3203. padding: 5px 10px;
  3204. border-radius: 6px;
  3205. border-style: solid;
  3206. border: 1px solid #ced4da;
  3207. color: #212529;
  3208. }
  3209.  
  3210. /* 调试 */
  3211. .OJBetter_loding{
  3212. padding: 6px 0px 0px 5px;
  3213. height: 22px;
  3214. }
  3215. #CompilerArgsInput{
  3216. flex-grow: 1;
  3217. width: 100%;
  3218. height: 100%;
  3219. margin-bottom: 10px;
  3220. padding: 5px 10px;
  3221. border-radius: 6px;
  3222. box-sizing: border-box;
  3223. border: 1px solid #ccc;
  3224. box-shadow: inset 0px 1px 1px rgba(0,0,0,.075);
  3225. }
  3226. #CompilerArgsInput[disabled] {
  3227. cursor: not-allowed;
  3228. }
  3229. #CompilerSetting{
  3230. font-size: 14px;
  3231. margin-top: 10px;
  3232. display: none;
  3233. }
  3234. #CompilerSetting select, #CompilerSetting textarea{
  3235. padding: 4px 10px;
  3236. border-radius: 6px;
  3237. border-style: solid;
  3238. border: 1px solid #ced4da;
  3239. color: #212529;
  3240. }
  3241. #CompilerBox{
  3242. display: grid;
  3243. margin-top: 10px;
  3244. border: #d0d7de solid 1px;
  3245. border-radius: 6px;
  3246. }
  3247. #CompilerBox > * {
  3248. margin: 5px;
  3249. }
  3250.  
  3251. /* 自定义样例 */
  3252. #customTestBlock {
  3253. margin-top: 10px;
  3254. font-size: 14px;
  3255. color: #616161;
  3256. border: 1px solid #d3d3d3;
  3257. box-sizing: border-box;
  3258. position: relative;
  3259. }
  3260. #customTestBlock #customTests{
  3261. border-top: 1px solid #d3d3d3;
  3262. margin: 0px 0px 40px 0px;
  3263. }
  3264. #customTestBlock summary {
  3265. cursor: pointer;
  3266. padding: 10px;
  3267. }
  3268. #customTestBlock textarea {
  3269. resize: vertical;
  3270. }
  3271. .sampleDiv {
  3272. color: #727378;
  3273. background-color: #FAFAFA;
  3274. padding: 5px;
  3275. margin-bottom: 10px;
  3276. box-shadow: inset 0 0 1px #0000004d;
  3277. position: relative;
  3278. }
  3279. .dynamicTextarea {
  3280. width: 98%;
  3281. height: 120px;
  3282. margin: 10px 5px;
  3283. border: 1px solid #E0E0E0;
  3284. }
  3285. .deleteCustomTest {
  3286. cursor: pointer;
  3287. position: absolute;
  3288. top: 5px;
  3289. right: 5px;
  3290. display: flex;
  3291. fill: #9E9E9E;
  3292. padding: 2px 2px;
  3293. border-radius: 4px;
  3294. border: 1px solid #ffffff00;
  3295. background-color: #ffffff00;
  3296. align-items: center;
  3297. }
  3298. .deleteCustomTest:hover {
  3299. fill: #EF5350;
  3300. border: 1px solid #ef9a9a;
  3301. background-color: #FFEBEE;
  3302. }
  3303. #addCustomTest {
  3304. cursor: pointer;
  3305. position: absolute;
  3306. bottom: 5px;
  3307. right: 5px;
  3308. padding: 3px 10px;
  3309. color: #795548;
  3310. border: 1px solid #ccc;
  3311. border-radius: 4px;
  3312. background-color: #FAFAFA;
  3313. }
  3314. #addCustomTest:hover {
  3315. background-color: #f5f5f5;
  3316. }
  3317.  
  3318. /* 调试结果 */
  3319. #statePanel{
  3320. display: none;
  3321. padding: 5px;
  3322. margin-top: 10px;
  3323. border: 1px solid #ddd;
  3324. border-radius: 4px;
  3325. }
  3326. .test-case {
  3327. padding: 10px;
  3328. border: 1px solid #ddd;
  3329. border-radius: 4px;
  3330. }
  3331. .test-case:not(:first-child){
  3332. margin-top: 5px;
  3333. }
  3334. .test-case > * {
  3335. margin: 5px 0px;
  3336. }
  3337. .test-case > :first-child {
  3338. margin-top: 0px;
  3339. }
  3340. .test-case > :last-child {
  3341. margin-bottom: 0px;
  3342. }
  3343. .test-case-title, .test-case-status {
  3344. font-size: 16px;
  3345. display: inline;
  3346. }
  3347. .test-case-status{
  3348. margin-left: 5px;
  3349. }
  3350. .test-case-status.error{
  3351. color: red;
  3352. }
  3353. .test-case-status.success{
  3354. color: #449d44;
  3355. }
  3356. .test-case-judge {
  3357. font-size: 13px;
  3358. }
  3359.  
  3360. /* 差异对比 */
  3361. .output_diff {
  3362. color: #5d4037;
  3363. margin: 5px 0px;
  3364. display: grid;
  3365. border: 1px solid #bcaaa4;
  3366. font-size: 13px;
  3367. font-family: Consolas, "Lucida Console", "Andale Mono", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
  3368. overflow: auto;
  3369. }
  3370. .output_diff .added {
  3371. background-color: #c8f7c5;
  3372. user-select: none;
  3373. }
  3374. .output_diff .removed {
  3375. background-color: #f7c5c5;
  3376. }
  3377. .output_diff .diffLine {
  3378. display: flex;
  3379. }
  3380. .output_diff .diffLine:nth-child(odd) {
  3381. background-color: #f5f5f5;
  3382. }
  3383. .lineNo {
  3384. display: flex;
  3385. align-items: center;
  3386. justify-content: center;
  3387. width: 17px;
  3388. color: #BDBDBD;
  3389. font-size: 10px;
  3390. border-right: 1px solid;
  3391. user-select: none;
  3392. }
  3393. .lineContent {
  3394. display: grid;
  3395. width: 100%;
  3396. }
  3397. .lineContent>span {
  3398. height: 16px;
  3399. padding-left: 3px;
  3400. }
  3401. .output_no_diff {
  3402. padding: 5px;
  3403. border: 1px solid #ddd;
  3404. }
  3405. .diff_note {
  3406. font-size: 10px;
  3407. }
  3408.  
  3409. /* 移动设备 */
  3410. @media (max-device-width: 450px) {
  3411. .ojb_btn{
  3412. height: 2em;
  3413. font-size: 1.2em;
  3414. }
  3415. .ojb_btn.OJBetter_setting{
  3416. height: 2.5em;
  3417. font-size: 1em;
  3418. }
  3419. .OJBetter_setting_menu{
  3420. width: 90%;
  3421. }
  3422. .OJBetter_setting_menu label, #darkMode_span, #loaded_span, .OJBetter_setting_menu_label_text,
  3423. .OJBetter_setting_sidebar li{
  3424. font-size: 1em;
  3425. }
  3426. .translate-problem-statement{
  3427. font-size: 1.2em;
  3428. }
  3429. .OJBetter_modal{
  3430. font-size: 1.5em;
  3431. }
  3432. .OJBetter_setting_list, .translate-problem-statement{
  3433. padding: 0.5em;
  3434. }
  3435. .OJBetter_setting_menu_label_text{
  3436. height: 2.5em;
  3437. padding: 0.5em;
  3438. }
  3439. #pagBar #jump-input, #pagBar #items-per-page, .OJBetter_modal button{
  3440. height: 2.5em;
  3441. font-size: 1em;
  3442. }
  3443. .translate-problem-statement p, .translate-problem-statement ul li{
  3444. line-height: 1.5em !important;
  3445. }
  3446. .OJBetter_contextmenu_label_text{
  3447. height: 3em;
  3448. font-size: 1em;
  3449. }
  3450. }
  3451.  
  3452. /* 覆盖网站原本的样式 */
  3453. div#select-lang {
  3454. padding: 0px;
  3455. }
  3456. `);
  3457.  
  3458. /**
  3459. * 添加一些依赖库和条件加载的css样式
  3460. */
  3461. function addDependencyStyles() {
  3462. GM_addStyle(GM_getResourceText("xtermcss"));
  3463. // 自定义图标大小
  3464. GM_addStyle(`
  3465. .iconfont {
  3466. font-size: ${OJBetter.preference.iconButtonSize}px;
  3467. }
  3468. `);
  3469. }
  3470.  
  3471. /**
  3472. * 添加包含i18n内容的css样式
  3473. */
  3474. function addI18nStyles() {
  3475. GM_addStyle(`
  3476. /* 加载鼠标悬浮覆盖层css */
  3477. .overlay::before {
  3478. content: '';
  3479. position: absolute;
  3480. top: 0;
  3481. left: 0;
  3482. width: 100%;
  3483. height: 100%;
  3484. 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);
  3485. z-index: 100;
  3486. }
  3487. .overlay::after {
  3488. content: '${i18next.t('targetArea', { ns: 'common' })}';
  3489. position: absolute;
  3490. top: 50%;
  3491. left: 50%;
  3492. transform: translate(-50%, -50%);
  3493. color: #00695C;
  3494. font-size: 16px;
  3495. font-weight: bold;
  3496. z-index: 100;
  3497. }
  3498.  
  3499. .config::before {
  3500. content: "${i18next.t('common.configManageTitle', { ns: 'settings' })}";
  3501. display: block;
  3502. height: 20px;
  3503. background-color: #f0f8ff;
  3504. border: 1px solid #c5cae9;
  3505. border-bottom: 0px;
  3506. line-height: 20px;
  3507. padding: 2px 10px;
  3508. border-radius: 8px 8px 0px 0px;
  3509. box-sizing: content-box;
  3510. }
  3511. .config.missing::before {
  3512. content: "${i18next.t('common.missing.radio', { ns: 'settings' })}";
  3513. background-color: #fef0f0;
  3514. color: #f56c6c;
  3515. border: 1px solid #fab6b6;
  3516. }
  3517. `);
  3518. }
  3519.  
  3520. // ------------------------------
  3521. // 一些工具类
  3522. // ------------------------------
  3523.  
  3524.  
  3525. /**
  3526. * 自定义错误类,以区分不同的错误类型
  3527. */
  3528. class OJB_GMError extends Error {
  3529. constructor(type, message, originalError) {
  3530. super(message);
  3531. this.name = 'GMError';
  3532. this.type = type;
  3533. this.stack = originalError.stack;
  3534. Object.assign(this, originalError);
  3535. }
  3536. }
  3537.  
  3538. /**
  3539. * 文本块替换/恢复类
  3540. */
  3541. class TextBlockReplacer {
  3542. constructor() {
  3543. /** @type {string[]} 匹配项 */
  3544. this.matches = [];
  3545. /** @type {Map<string, string>} 待还原项 */
  3546. this.replacements = new Map();
  3547. /** @type {Map<string, string>} 暂时未找到的待还原项 */
  3548. this.tempReplacements = new Map();
  3549. /** @type {string} 替换符号 */
  3550. this.replaceSymbol = OJBetter.translation.replaceSymbol;
  3551. }
  3552.  
  3553. /**
  3554. * 替换文本
  3555. * @param {string} text 原文本
  3556. * @param {RegExp} regex 匹配规则
  3557. * @returns {string} 替换后的文本
  3558. */
  3559. replace(text, regex) {
  3560. this.matches = text.match(regex) || [];
  3561. try {
  3562. for (let i = 0; i < this.matches.length; i++) {
  3563. const match = this.matches[i];
  3564. const id = OJB_getRandomNumber(8);
  3565. let replacement = '';
  3566. switch (this.replaceSymbol) {
  3567. case "1":
  3568. replacement = `【${id}】`;
  3569. break;
  3570. case "2":
  3571. replacement = `{${id}}`;
  3572. break;
  3573. case "3":
  3574. replacement = `[${id}]`;
  3575. break;
  3576. default:
  3577. replacement = `【${id}】`;
  3578. break;
  3579. }
  3580. text = text.replace(match, replacement);
  3581. this.replacements.set(id, match);
  3582. }
  3583. } catch (e) { }
  3584. return text;
  3585. }
  3586.  
  3587.  
  3588. /**
  3589. * 恢复替换的文本
  3590. * @param {string} text 还原前的文本
  3591. * @returns {string} 还原后的文本
  3592. */
  3593. recover(text) {
  3594. let textCopy = text;
  3595.  
  3596. /**
  3597. * 替换文本
  3598. * @param {string} replacement 替换的文本
  3599. * @param {string} regexPattern 匹配规则
  3600. * @returns {void}
  3601. */
  3602. const replaceText = (replacement, regexPattern) => {
  3603. const latexMatch = '(?<latex_block>\\$\\$(\\\\.|[^\\$])*?\\$\\$)|(?<latex_inline>\\$(\\\\.|[^\\$])*?\\$)|';
  3604. const regex = new RegExp(latexMatch + regexPattern, 'g');
  3605. textCopy = textCopy.replace(regex, (match, ...args) => {
  3606. // LaTeX中的不替换
  3607. const groups = args[args.length - 1]; // groups是replace方法的最后一个参数
  3608. if (groups.latex_block || groups.latex_inline) return match;
  3609. // 没有空格则加一个
  3610. const offset = args[args.length - 3]; // offset是replace方法的倒数第三个参数
  3611. let leftSpace = "", rightSpace = "";
  3612. if (!/\s/.test(textCopy[offset - 1])) leftSpace = " ";
  3613. if (!/\s/.test(textCopy[offset + match.length])) rightSpace = " ";
  3614. return leftSpace + replacement + rightSpace;
  3615. });
  3616. };
  3617.  
  3618. /**
  3619. * 尝试还原
  3620. * @param {string} replacement 替换的文本
  3621. * @param {string} id 替换的 id
  3622. * @returns {boolean} 是否替换成功
  3623. */
  3624. const tryRecover = (replacement, id) => {
  3625. // 尝试还原,如果还原成功,则从 replacements 中删除
  3626. const originalText = textCopy;
  3627. replaceText(replacement, `【\\s*${id}\\s*】|\\[\\s*${id}\\s*\\]|{\\s*${id}\\s*}`); // 替换符完整匹配(考虑了多出空格的情况)
  3628. replaceText(replacement, `【\\s*${id}(?![】\\d])|(?<![【\\d])${id}\\s*】|\\[\\s*${id}(?![\\]\\d])|(?<![\\[\\d])${id}\\s*\\]|{\\s*${id}(?![}\\d])|(?<![{\\d])${id}\\s*}`); // 替换符部分匹配
  3629.  
  3630. if (textCopy === originalText) {
  3631. // 如果文本没有变化,说明没有找到,加入到 tempReplacements
  3632. this.tempReplacements.set(id, replacement);
  3633. return false;
  3634. } else {
  3635. // 如果文本变化了,说明找到并成功替换,则删除
  3636. this.replacements.delete(id);
  3637. this.tempReplacements.delete(id);
  3638. return true;
  3639. }
  3640. }
  3641.  
  3642. // 处理 replacements 中的项
  3643. this.replacements.forEach((replacement, id) => {
  3644. tryRecover(replacement, id);
  3645. });
  3646.  
  3647. // 处理 tempReplacements 中的项
  3648. while (this.tempReplacements.size > 0) {
  3649. let found = false;
  3650. this.tempReplacements.forEach((replacement, id) => {
  3651. found = tryRecover(replacement, id) || found;
  3652. });
  3653. if (!found) break; // 如果这一轮没有找到任何项,终止循环
  3654. }
  3655.  
  3656. // 如果 tempReplacements 还有未找到的项
  3657. if (this.tempReplacements.size > 0) {
  3658. console.warn("There are still some replacements not found:", this.tempReplacements);
  3659. }
  3660.  
  3661. return textCopy;
  3662. }
  3663. }
  3664.  
  3665. // ------------------------------
  3666. // 一些工具函数
  3667. // ------------------------------
  3668.  
  3669. /**
  3670. * 延迟函数
  3671. * @param {number} ms 延迟时间(毫秒)
  3672. * @returns {Promise<void>}
  3673. */
  3674. function OJB_delay(ms) {
  3675. return new Promise(resolve => setTimeout(resolve, ms));
  3676. }
  3677.  
  3678. /**
  3679. * 格式化链接格式
  3680. * @param {string} url 链接字符串
  3681. * @returns {string} 清理后的链接字符串
  3682. */
  3683. function OJB_cleanLink(url) {
  3684. // 替换'http://'为'https://'
  3685. let cleanUrl = url.replace(/^http:\/\//i, 'https://');
  3686.  
  3687. // 移除末尾的斜杠
  3688. cleanUrl = cleanUrl.replace(/\/$/, '');
  3689.  
  3690. return cleanUrl;
  3691. }
  3692.  
  3693. /**
  3694. * 深度比较两个对象或数组是否完全相等。
  3695. * @param {any} a - 第一个比较对象。
  3696. * @param {any} b - 第二个比较对象。
  3697. * @returns {boolean} - 如果两个对象或数组深度相等,则返回true,否则返回false。
  3698. */
  3699. function OJB_deepEquals(a, b) {
  3700. if (a === b) return true;
  3701. if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false;
  3702. const keysA = Object.keys(a);
  3703. const keysB = Object.keys(b);
  3704. if (keysA.length !== keysB.length) return false;
  3705. for (let key of keysA) {
  3706. if (!b.hasOwnProperty(key)) return false;
  3707. if (!OJB_deepEquals(a[key], b[key])) return false;
  3708. }
  3709. return true;
  3710. }
  3711.  
  3712. /**
  3713. * 用于封装需要重试的异步函数
  3714. * @param {Function} task 需要封装的异步函数
  3715. * @param {Object} options 配置项
  3716. * @param {Number} options.maxRetries 重试次数,默认为 5
  3717. * @param {Number} options.retryInterval 重试时间间隔,默认为 0 毫秒
  3718. * @param {Function} options.errorHandler 错误处理函数,默认为抛出错误
  3719. * @param {...any} args task 函数的参数
  3720. * @returns {Promise} 返回 Promise
  3721. */
  3722. async function OJB_promiseRetryWrapper(task, {
  3723. maxRetries = 5,
  3724. retryInterval = 0,
  3725. errorHandler = (err) => { throw err }
  3726. } = {}, ...args) {
  3727. let attemptsLeft = maxRetries;
  3728. while (attemptsLeft--) {
  3729. try {
  3730. return await task(...args);
  3731. } catch (err) {
  3732. if (attemptsLeft <= 0) {
  3733. return errorHandler(err, maxRetries, attemptsLeft);
  3734. }
  3735. if (retryInterval > 0) {
  3736. await OJB_delay(retryInterval);
  3737. }
  3738. }
  3739. }
  3740. }
  3741.  
  3742. /**
  3743. * GM_xmlhttpRequest 的 Promise 封装
  3744. * @param {Object} options GM_xmlhttpRequest 的参数
  3745. * @param {Boolean} isStream 是否为流式请求
  3746. * @returns {Promise<OJB_GMError>} 返回 Promise
  3747. */
  3748. function OJB_GMRequest(options, isStream = false) {
  3749. return new Promise((resolve, reject) => {
  3750. GM_xmlhttpRequest({
  3751. ...options,
  3752. ...(isStream ? {
  3753. onloadstart: resolve
  3754. } : {
  3755. onload: resolve
  3756. }),
  3757. onerror: (error) => reject(new OJB_GMError('error', 'An error occurred during the request.', error)),
  3758. ontimeout: (error) => reject(new OJB_GMError('timeout', 'The request timed out.', error)),
  3759. onabort: (error) => reject(new OJB_GMError('abort', 'The request was aborted.', error)),
  3760. });
  3761. });
  3762. }
  3763.  
  3764. /**
  3765. * 获取cookie
  3766. * @param {string} name cookie名称
  3767. * @returns {string} cookie值
  3768. */
  3769. function OJB_getCookie(name) {
  3770. const cookies = document.cookie.split(";");
  3771. for (let i = 0; i < cookies.length; i++) {
  3772. const cookie = cookies[i].trim();
  3773. const [cookieName, cookieValue] = cookie.split("=");
  3774.  
  3775. if (cookieName === name) {
  3776. return decodeURIComponent(cookieValue);
  3777. }
  3778. }
  3779. return "";
  3780. }
  3781.  
  3782. /**
  3783. * 检查是否仍在同一浏览器会话中
  3784. * @param {string} sessionKey - 会话键名,用于标识会话
  3785. * @returns {boolean} - 如果在当前会话中之前已经设置过这个键,则返回true,否则返回false
  3786. */
  3787. function OJB_isSameBrowserSession(sessionKey) {
  3788. const fullCookieName = `OJB_Session_${sessionKey}`;
  3789. const sessionValue = OJB_getCookie(fullCookieName);
  3790. if (sessionValue === "") {
  3791. document.cookie = `${fullCookieName}=true; path=/`;
  3792. return false;
  3793. }
  3794. return true;
  3795. }
  3796.  
  3797. /**
  3798. * 随机数生成
  3799. * @param {number} numDigits 位数
  3800. * @returns {number}
  3801. */
  3802. function OJB_getRandomNumber(numDigits) {
  3803. let min = Math.pow(10, numDigits - 1);
  3804. let max = Math.pow(10, numDigits) - 1;
  3805. return Math.floor(Math.random() * (max - min + 1)) + min;
  3806. }
  3807.  
  3808. /**
  3809. * 防抖函数
  3810. * @param {Function} callback 回调函数
  3811. * @returns {Function}
  3812. */
  3813. function OJB_debounce(callback) {
  3814. let timer;
  3815. let immediateExecuted = false;
  3816. const delay = 500;
  3817. return function () {
  3818. clearTimeout(timer);
  3819. if (!immediateExecuted) { callback.call(this); immediateExecuted = true; }
  3820. timer = setTimeout(() => { immediateExecuted = false; }, delay);
  3821. };
  3822. }
  3823.  
  3824. /**
  3825. * 为元素添加鼠标拖拽支持
  3826. * @param {JQuery<HTMLElement>} element 要添加拖拽支持的元素
  3827. * @returns {void}
  3828. */
  3829. function OJB_addDraggable(element) {
  3830. let isDragging = false;
  3831. let x, y, l, t, nl, nt;
  3832. let isSpecialMouseDown = false; // 选取某些元素时不拖动
  3833.  
  3834. element.on('mousedown', function (e) {
  3835. isSpecialMouseDown = $(e.target).is('label, p, input, textarea, span, select, details, summary');
  3836. if (isSpecialMouseDown) return;
  3837.  
  3838. isDragging = true;
  3839. x = e.clientX;
  3840. y = e.clientY;
  3841. l = element.offset().left - $(window).scrollLeft();
  3842. t = element.offset().top - $(window).scrollTop();
  3843.  
  3844. element.css({ left: l + 'px', top: t + 'px', transform: 'none' });
  3845.  
  3846. $(document).on("mousemove", drag);
  3847. $(document).on("mouseup", stopDrag);
  3848. element.css('cursor', 'all-scroll');
  3849. });
  3850.  
  3851. const drag = (e) => {
  3852. if (!isDragging) return;
  3853. // 不执行拖动操作
  3854. if ($(e.target).is('label, p, input, textarea, span') || isSpecialMouseDown && !$(e.target).is('input, textarea')) return;
  3855. e.preventDefault();
  3856.  
  3857. const nx = e.clientX;
  3858. const ny = e.clientY;
  3859. nl = nx - (x - l);
  3860. nt = ny - (y - t);
  3861. element.css({ transform: `translate(${nx - x}px, ${ny - y}px)` });
  3862. };
  3863.  
  3864. const stopDrag = () => {
  3865. isDragging = false;
  3866. isSpecialMouseDown = false;
  3867. element.css('cursor', 'default');
  3868.  
  3869. // 在停止拖拽后,设置元素的left和top,并还原transform
  3870. element.css({ left: nl + 'px', top: nt + 'px', transform: 'none' });
  3871. $(document).off("mousemove", drag);
  3872. $(document).off("mouseup", stopDrag);
  3873. };
  3874. }
  3875.  
  3876. /**
  3877. * 切换元素的折叠/展开过渡动画
  3878. * @param {HTMLElement} element
  3879. */
  3880. function OJB_toggleCollapseExpand(element) {
  3881. // 设置transitionend事件监听器的函数
  3882. const setTransitionListener = (listener) => {
  3883. const listenerName = `transitionEndListener${Date.now()}`;
  3884. window[listenerName] = listener;
  3885. element.addEventListener('transitionend', listener);
  3886. element.setAttribute('data-transition-end-listener', listenerName);
  3887. };
  3888.  
  3889. // 移除事件监听器的函数
  3890. const removeTransitionListener = () => {
  3891. const transitionEndListenerName = element.getAttribute('data-transition-end-listener');
  3892. if (transitionEndListenerName) {
  3893. element.removeEventListener('transitionend', window[transitionEndListenerName]);
  3894. element.removeAttribute('data-transition-end-listener');
  3895. }
  3896. };
  3897.  
  3898. const collapsed = element.getAttribute('data-collapsed') === 'true';
  3899. const sectionHeight = element.scrollHeight;
  3900.  
  3901. // 移除事件监听器
  3902. removeTransitionListener();
  3903.  
  3904. // 设置初始样式
  3905. element.style.overflow = 'hidden';
  3906. element.style.transition = 'height 0.3s ease-out 0s';
  3907. element.style.height = collapsed ? `0px` : `${sectionHeight}px`;
  3908. element.style.opacity = collapsed ? '' : '1';
  3909.  
  3910. // 需要立即开始动画
  3911. requestAnimationFrame(() => {
  3912. // 设置结束样式
  3913. element.style.height = collapsed ? `${sectionHeight}px` : `0px`;
  3914. });
  3915.  
  3916. const transitionEndListener = (event) => {
  3917. if (event.propertyName === 'height') {
  3918. if (collapsed) {
  3919. // 展开后的设置
  3920. element.style.height = '';
  3921. element.style.overflow = '';
  3922. } else {
  3923. // 折叠后的设置
  3924. element.style.opacity = '0';
  3925. }
  3926. removeTransitionListener();
  3927. }
  3928. };
  3929.  
  3930. setTransitionListener(transitionEndListener);
  3931.  
  3932. // 更新data-collapsed属性
  3933. element.setAttribute('data-collapsed', collapsed ? 'false' : 'true');
  3934. }
  3935.  
  3936. /**
  3937. * 获取外部JSON并转换为Object
  3938. * @param {string} url JSON Url
  3939. * @param {boolean} [nacache=true] 是否不使用缓存
  3940. * @returns {Promise<Object>} JSON Object
  3941. */
  3942. async function OJB_getExternalJSON(url, nacache = true) {
  3943. const response = await OJB_GMRequest({
  3944. method: "GET",
  3945. url: url,
  3946. nocache: nacache
  3947. });
  3948. try {
  3949. return JSON.parse(response.responseText);
  3950. } catch (e) {
  3951. throw new Error(`JSON parse error\n${e}`);
  3952. }
  3953. }
  3954.  
  3955. /**
  3956. * 创建确认对话框dialog
  3957. * @param {string} title 标题
  3958. * @param {string} content 内容
  3959. * @param {string[]} buttons 按钮 (取消 确定) 可以为null
  3960. * @param {boolean} renderMarkdown 是否使用markdown渲染文本
  3961. * @returns {Promise<boolean>} 用户点击了确定按钮返回true, 否则返回false
  3962. */
  3963. function OJB_createDialog(title, content, buttons, renderMarkdown = false) {
  3964. return new Promise(resolve => {
  3965. let contentHtml = content;
  3966.  
  3967. if (renderMarkdown) {
  3968. const md = window.markdownit();
  3969. contentHtml = md.render(content);
  3970. }
  3971.  
  3972. const dialog = OJB_safeCreateJQElement(`
  3973. <dialog class="OJBetter_modal">
  3974. <h2>${title}</h2>
  3975. <div class="content">${contentHtml}</div>
  3976. </dialog>
  3977. `);
  3978. const buttonbox = OJB_safeCreateJQElement(`<div class="buttons"></div>`);
  3979. const cancelButton = OJB_safeCreateJQElement(`<button class="cancelButton">${buttons[0]}</button>`)
  3980. .addClass("secondary");
  3981. const continueButton = OJB_safeCreateJQElement(`<button class="continueButton">${buttons[1]}</button>`);
  3982. if (buttons[0] !== null) buttonbox.append(cancelButton);
  3983. if (buttons[1] !== null) buttonbox.append(continueButton);
  3984. dialog.append(buttonbox);
  3985. $('body').append(dialog);
  3986.  
  3987. OJB_showModal(dialog);
  3988. OJB_addDraggable(dialog);
  3989.  
  3990. continueButton.click(function () {
  3991. OJB_closeAndRemoveModal(dialog);
  3992. resolve(true);
  3993. });
  3994.  
  3995. cancelButton.click(function () {
  3996. OJB_closeAndRemoveModal(dialog);
  3997. resolve(false);
  3998. });
  3999. });
  4000. }
  4001.  
  4002. /**
  4003. * 显示模态对话框并阻止页面滚动,同时考虑滚动条宽度变化和原始marginRight
  4004. * @param {JQuery<HTMLElement>} element
  4005. */
  4006. function OJB_showModal(element) {
  4007. const dialog = element.get(0);
  4008. dialog.showModal();
  4009. OJBetter.state.openDialogCount++;
  4010.  
  4011. if (OJBetter.state.openDialogCount === 1) {
  4012. const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
  4013. // 获取原始的html marginRight,考虑到可能的非数字值,比如auto
  4014. const originalMarginRight = window.getComputedStyle(document.documentElement).marginRight;
  4015. const marginRightValue = parseFloat(originalMarginRight) || 0; // 将非数字值转换为0
  4016.  
  4017. if (scrollbarWidth > 0) {
  4018. // 保存原始的marginRight,并设置新的值以补偿滚动条宽度
  4019. document.documentElement.style.setProperty('--original-margin-right', originalMarginRight);
  4020. document.documentElement.style.marginRight = `${marginRightValue + scrollbarWidth}px`;
  4021. }
  4022.  
  4023. // 保存原始的overflow样式
  4024. document.documentElement.setAttribute('data-original-overflow', document.documentElement.style.overflow);
  4025. document.documentElement.style.overflow = 'hidden';
  4026. }
  4027.  
  4028. const allowScrollIfNeeded = () => {
  4029. OJBetter.state.openDialogCount--;
  4030. if (OJBetter.state.openDialogCount === 0) {
  4031. // 恢复原始的html marginRight和overflow样式
  4032. const originalMarginRight = document.documentElement.style.getPropertyValue('--original-margin-right');
  4033. document.documentElement.style.marginRight = originalMarginRight;
  4034. document.documentElement.style.removeProperty('--original-margin-right');
  4035.  
  4036. const originalOverflow = document.documentElement.getAttribute('data-original-overflow');
  4037. document.documentElement.style.overflow = originalOverflow;
  4038. document.documentElement.removeAttribute('data-original-overflow');
  4039. }
  4040. };
  4041.  
  4042. dialog.addEventListener('close', allowScrollIfNeeded);
  4043. }
  4044.  
  4045. /**
  4046. * 关闭并移除模态对话框
  4047. * @param {JQuery<HTMLElement>} element
  4048. */
  4049. function OJB_closeAndRemoveModal(element) {
  4050. const dialog = element.get(0);
  4051. dialog.close();
  4052. dialog.remove();
  4053. }
  4054.  
  4055. /**
  4056. * 关闭并移除模态对话框
  4057. * @param {JQuery<HTMLElement>} element
  4058. */
  4059. function OJB_closeModal(element) {
  4060. const dialog = element.get(0);
  4061. dialog.close();
  4062. }
  4063.  
  4064. /**
  4065. * 清除i18next的缓存数据并刷新
  4066. */
  4067. function clearI18nextCache() {
  4068. Object.keys(localStorage)
  4069. .filter(key => key.startsWith('i18next_res_'))
  4070. .forEach(key => localStorage.removeItem(key));
  4071. window.location.reload();
  4072. }
  4073.  
  4074. /**
  4075. * 从Pre代码块中获取原始代码
  4076. * @param {HTMLElement} element pre代码块元素
  4077. * @returns {string|null} 代码文本
  4078. */
  4079. function OJB_getCodeFromPre(element) {
  4080. /**
  4081. * 从Ace格式化的代码块中获取原始代码
  4082. * @param {HTMLElement} element pre代码块元素
  4083. * @returns {string} 代码文本
  4084. */
  4085. const getCodeFromAcePre = function (element) {
  4086. const editor = ace.edit(element);
  4087. return editor.getValue();
  4088. }
  4089.  
  4090. /**
  4091. * 从Pretty格式化的代码块中获取原始代码-1
  4092. * 代码直接存放在 pre 元素中
  4093. * @param {HTMLElement} element pre代码块元素
  4094. * @returns {string} 代码文本
  4095. */
  4096. const getCodeFromPrettyPre = function (element) {
  4097. return Array.from(element.querySelectorAll('li')).map(function (li) {
  4098. return li.textContent;
  4099. }).join('\n');
  4100. }
  4101.  
  4102. /**
  4103. * 从Pretty格式化的代码块中获取原始代码-2
  4104. * 代码存放在子元素 code 中
  4105. * @param {HTMLElement} element pre代码块元素
  4106. * @returns {string} 代码文本
  4107. */
  4108. const getCodeFromPreChild = function (element) {
  4109. const code = element.querySelector("code.prettyprint");
  4110. if (code.classList.contains("linenums")) {
  4111. return getCodeFromPrettyPre(element);
  4112. } else {
  4113. return element.querySelector("code.prettyprint").textContent;
  4114. }
  4115. }
  4116.  
  4117. if (element.id === "submission-code") {
  4118. return getCodeFromAcePre(element);
  4119. } else if (element.classList.contains("prettyprint")) {
  4120. return getCodeFromPrettyPre(element);
  4121. } else if (element.querySelector("code.prettyprint")) {
  4122. return getCodeFromPreChild(element);
  4123. } else {
  4124. return null;
  4125. }
  4126. }
  4127.  
  4128. /**
  4129. * 判断代码的语言
  4130. * @param {string} code 代码文本
  4131. * @returns {string} 可能的语言
  4132. */
  4133. function OJB_codeLangDetect(code) {
  4134. result = hljs.highlightAuto(code);
  4135. return result.language;
  4136. }
  4137.  
  4138. /**
  4139. * 更新检查
  4140. */
  4141. async function checkScriptVersion() {
  4142. try {
  4143. const versionResponse = await OJB_GMRequest({
  4144. method: "GET",
  4145. url: "https://aowuucdn.oss-accelerate.aliyuncs.com/script/versions.json",
  4146. timeout: 10 * 1e3,
  4147. nocache: true
  4148. });
  4149. const versionData = JSON.parse(versionResponse.responseText);
  4150. const { [OJBetter.state.formatName]: { dev: version_dev, release: version_release } } = versionData;
  4151. const baseUrls = {
  4152. // greasyfork: 'https://update.greatest.deepsurf.us/scripts/465777/Codeforces%20Better%21.user.js',
  4153. greasyfork: 'https://update.greatest.deepsurf.us/scripts/471106/Atcoder%20Better%21.user.js',
  4154. github: `https://github.com/beijixiaohu/OJBetter/raw/main/script/${OJBetter.about.updateChannel}/${OJBetter.state.formatName}.user.js`,
  4155. aliyunoss: `https://aowuucdn.oss-accelerate.aliyuncs.com/script/${OJBetter.about.updateChannel}/${OJBetter.state.formatName}.user.js`
  4156. };
  4157. /** @type {string} 更新跳转url */
  4158. const updateUrl = baseUrls[OJBetter.about.updateSource];
  4159. /** @type {string} 是否暂时跳过cookie */
  4160. const skipUpdate = OJB_getCookie("skipUpdate");
  4161. /** @type {string} 当前更新频道的最新版本 */
  4162. const version = OJBetter.about.updateChannel == "release" ? version_release : version_dev;
  4163. if (OJB_compareVersions(version, OJBetter.state.version) === 1 && skipUpdate !== "true") {
  4164. const updateConfirmed = await OJB_createDialog(
  4165. i18next.t('update.title', { ns: 'dialog', scriptName: OJBetter.state.name }),
  4166. i18next.t('update.content', { ns: 'dialog', oldVersion: OJBetter.state.version, newVersion: version }),
  4167. [
  4168. i18next.t('update.buttons.0', { ns: 'dialog' }),
  4169. i18next.t('update.buttons.1', { ns: 'dialog' })
  4170. ],
  4171. true
  4172. );
  4173.  
  4174. if (updateConfirmed) {
  4175. window.location.href = updateUrl;
  4176. } else {
  4177. document.cookie = "skipUpdate=true; path=/";
  4178. }
  4179. }
  4180. } catch (error) {
  4181. console.error("Update check failed: ", error);
  4182. }
  4183. }
  4184.  
  4185. /**
  4186. * 公告
  4187. */
  4188. async function showAnnounce() {
  4189. /** @type {string} 最新公告版本*/
  4190. const lastAnnounceVer = i18next.t('lastVersion', { ns: 'announce' });
  4191. if (OJB_compareVersions(OJBetter.state.version, OJBetter.state.lastAnnounceVer) === 1) {
  4192. const title = `🎉${i18next.t('announce.title', { ns: 'dialog' })} ${OJBetter.state.version}`;
  4193. /** @type {Boolean} 是否是新的公告 */
  4194. const isNewAnnounceVer = OJB_compareVersions(lastAnnounceVer, OJBetter.state.lastReadAnnounceVer) === 1;
  4195. /** @type {Boolean} 是否展示新的公告(高于当前版本的测试公告不展示) */
  4196. const showNewAnnounceVer = OJB_compareVersions(lastAnnounceVer, OJBetter.state.version) !== 1;
  4197. /**
  4198. * 获取最后三个公告的内容
  4199. * @param {string} lastAnnounceVer
  4200. * @returns {string} 公告内容
  4201. */
  4202. const getLastThreeAnnounceContent = function (lastAnnounceVer) {
  4203. let content = "";
  4204. for (let i = 0; i < 3; i++) {
  4205. content += `### ${lastAnnounceVer}\n\n`;
  4206. content += i18next.t(`${lastAnnounceVer}`, { ns: 'announce' });
  4207. content += "\n\n";
  4208. lastAnnounceVer = OJB_getPreviousVersion(lastAnnounceVer);
  4209. }
  4210. return content;
  4211. };
  4212.  
  4213. const content = (() => {
  4214. if (isNewAnnounceVer && showNewAnnounceVer) {
  4215. return `${i18next.t('announce.prefix', { ns: 'dialog' })}\n\n${getLastThreeAnnounceContent(lastAnnounceVer)}`;
  4216. } else {
  4217. return i18next.t('announce.divContent', { ns: 'dialog' });
  4218. }
  4219. })();
  4220. const ok = await OJB_createDialog(
  4221. title,
  4222. content,
  4223. [
  4224. null,
  4225. i18next.t('announce.buttons.0', { ns: 'dialog' })
  4226. ],
  4227. true
  4228. ); //跳过折叠块确认
  4229. if (ok) {
  4230. if (isNewAnnounceVer && showNewAnnounceVer) {
  4231. GM_setValue('lastReadAnnounceVer', lastAnnounceVer);
  4232. }
  4233. GM_setValue('lastAnnounceVer', OJBetter.state.version);
  4234. }
  4235. }
  4236. };
  4237.  
  4238. /**
  4239. * 页面顶部提示信息alert类
  4240. */
  4241. class LoadingMessage {
  4242. constructor() {
  4243. this._statusElement = null;
  4244. this._isDisplayed = false;
  4245. this.init();
  4246. }
  4247.  
  4248. /**
  4249. * 初始化加载提示信息
  4250. */
  4251. init() {
  4252. this._statusElement = this.createStatusElement();
  4253. this.insertStatusElement();
  4254. }
  4255.  
  4256. /**
  4257. * 创建提示信息元素
  4258. */
  4259. createStatusElement() {
  4260. const statusElement = $("<div></div>").addClass("alert OJBetter_alert")
  4261. .css({
  4262. "margin": "1em",
  4263. "text-align": "center",
  4264. "position": "relative"
  4265. }).hide();
  4266. return statusElement;
  4267. }
  4268.  
  4269. /**
  4270. * 插入提示信息
  4271. * @returns {void}
  4272. */
  4273. insertStatusElement() {
  4274. // (OJBetter.typeOfPage.is_mSite ? $("header") : $(".menu-box:first").next()).after(this._statusElement);
  4275. $("#main-container").prepend(this._statusElement);
  4276. }
  4277.  
  4278. /**
  4279. * 显示提示信息
  4280. */
  4281. showStatus() {
  4282. this._statusElement.show();
  4283. this._isDisplayed = true;
  4284. }
  4285.  
  4286. /**
  4287. * 隐藏提示信息
  4288. */
  4289. hideStatus() {
  4290. this._statusElement.fadeOut(500);
  4291. this._isDisplayed = false;
  4292. }
  4293.  
  4294. /**
  4295. * 移除提示信息
  4296. */
  4297. removeStatus() {
  4298. this._statusElement.remove();
  4299. this._isDisplayed = false;
  4300. }
  4301.  
  4302. /**
  4303. * 更新提示信息
  4304. * @param {string} text 提示信息文本
  4305. * @param {string} type 提示信息类型,可选值:info, success, warning, error
  4306. * @param {number} timeout 提示信息显示的持续时间(毫秒), 默认为无限长
  4307. */
  4308. updateStatus(text, type = 'info', timeout = Infinity, isMarkdown = false) {
  4309. if (isMarkdown) {
  4310. let md = window.markdownit({
  4311. html: !is_escapeHTML,
  4312. });
  4313. text = md.render(text);
  4314. }
  4315. this._statusElement.html(text).removeClass("alert-info alert-success alert-warning alert-error").addClass(`alert-${type}`);
  4316. if (!this._isDisplayed) {
  4317. this.showStatus();
  4318. }
  4319. if (timeout !== Infinity) {
  4320. setTimeout(() => {
  4321. this.hideStatus();
  4322. }, timeout);
  4323. }
  4324. }
  4325. }
  4326.  
  4327. /**
  4328. * 获取网站本地化的数据
  4329. * @param {*} localizationLanguage 本地化语言
  4330. * @returns {Promise<Object>} 本地化数据
  4331. */
  4332. async function getLocalizeWebsiteJson(localizationLanguage) {
  4333. let data = await OJBetter.common.database.localizeSubsData.get(localizationLanguage);
  4334. let url = localizationLanguage === "zh" ?
  4335. `https://aowuucdn.oss-accelerate.aliyuncs.com/resources/subs/${OJBetter.state.formatName}.json` :
  4336. `https://aowuucdn.oss-accelerate.aliyuncs.com/i18n/${localizationLanguage}/resources/subs/${OJBetter.state.formatName}.json`;
  4337. if (data) data = data.data;
  4338. if (!data) {
  4339. // 如果本地没有数据,从远端获取并保存
  4340. data = await OJB_getExternalJSON(url);
  4341. await OJBetter.common.database.localizeSubsData.put({ lang: localizationLanguage, data: data });
  4342. } else {
  4343. // 如果本地有数据,检查是否已经在当前会话中尝试过更新
  4344. const sessionKey = `ojb_updateL10nWebsiteJson_${localizationLanguage}`;
  4345. if (!OJB_isSameBrowserSession(sessionKey)) {
  4346. // 如果尚未更新,则在后台更新
  4347. (async () => {
  4348. try {
  4349. const newData = await OJB_getExternalJSON(url);
  4350. await OJBetter.common.database.localizeSubsData.put({ lang: localizationLanguage, data: newData });
  4351. console.log("Website local data has been refreshed!");
  4352. } catch (error) {
  4353. console.error('Failed to update localization data:', error);
  4354. }
  4355. })();
  4356. }
  4357. }
  4358. return data;
  4359. }
  4360.  
  4361. /**
  4362. * 网站本地化替换
  4363. * @returns
  4364. */
  4365. async function localizeWebsite() {
  4366. if (OJBetter.localization.websiteLang === "initial") return;
  4367.  
  4368. // 设置网页语言
  4369. var htmlTag = document.getElementsByTagName("html")[0];
  4370. htmlTag.setAttribute("lang", OJBetter.localization.websiteLang);
  4371.  
  4372. // 获取网站本地化的数据
  4373. var subs = await getLocalizeWebsiteJson(OJBetter.localization.websiteLang);
  4374.  
  4375. /**
  4376. * 文本节点遍历替换
  4377. * @param {JQuery} $nodes jQuery对象
  4378. * @param {Object} textReplaceRules 文本替换规则对象
  4379. */
  4380. function traverseTextNodes($nodes, textReplaceRules) {
  4381. if (!$nodes) return;
  4382.  
  4383. $nodes.each(function () {
  4384. let node = this;
  4385. if (node.nodeType === Node.TEXT_NODE) {
  4386. Object.keys(textReplaceRules).forEach(match => {
  4387. try {
  4388. const replace = textReplaceRules[match];
  4389. const regex = new RegExp(match, 'g');
  4390. node.textContent = node.textContent.replace(regex, replace);
  4391. } catch (error) {
  4392. console.error(`Error processing text replacement for match: ${match}`, error);
  4393. }
  4394. });
  4395. } else {
  4396. $(node).contents().each(function () {
  4397. traverseTextNodes($(this), textReplaceRules);
  4398. });
  4399. }
  4400. });
  4401. }
  4402.  
  4403. /**
  4404. * value替换
  4405. * @param {JQuery} $nodes jQuery对象
  4406. * @param {Object} valueReplaceRules 值替换规则对象
  4407. */
  4408. function traverseValueNodes($nodes, valueReplaceRules) {
  4409. if (!$nodes) return;
  4410.  
  4411. $nodes.each(function () {
  4412. let $node = $(this);
  4413. if ($node.is('[value]')) {
  4414. Object.keys(valueReplaceRules).forEach(match => {
  4415. const replace = valueReplaceRules[match];
  4416. const regex = new RegExp(match, 'g');
  4417. let currentValue = $node.val();
  4418. let newValue = currentValue.replace(regex, replace);
  4419. $node.val(newValue);
  4420. });
  4421. } else {
  4422. $node.children().each(function () {
  4423. traverseValueNodes($(this), valueReplaceRules);
  4424. });
  4425. }
  4426. });
  4427. }
  4428.  
  4429. /**
  4430. * 严格的文本节点遍历替换
  4431. * @param {JQuery} $node jQuery对象
  4432. * @param {Object} textReplaceRules 文本替换规则对象
  4433. */
  4434. function strictTraverseTextNodes($nodes, textReplaceRules) {
  4435. if (!$nodes) return;
  4436.  
  4437. $nodes.each(function () {
  4438. let $node = $(this);
  4439. if ($node.nodeType === Node.TEXT_NODE) {
  4440. const trimmedNodeText = $node.textContent.trim();
  4441. Object.keys(textReplaceRules).forEach(match => {
  4442. if (trimmedNodeText === match) {
  4443. $node.textContent = textReplaceRules[match];
  4444. }
  4445. });
  4446. } else {
  4447. $($node).contents().each(function () {
  4448. strictTraverseTextNodes($(this), textReplaceRules);
  4449. });
  4450. }
  4451. });
  4452. }
  4453.  
  4454. /**
  4455. * 应用文本替换
  4456. */
  4457. let commonReplacements = subs.commonReplacements;
  4458. Object.entries(commonReplacements).forEach(([key, value]) => {
  4459. const classSelectors = Array.isArray(value.class) ? value.class : [value.class]; // 兼容,class的值可以为数组或者字符串
  4460. classSelectors.forEach(classSelector => {
  4461. if (value.isStrict) {
  4462. strictTraverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules);
  4463. } else {
  4464. traverseTextNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules);
  4465. }
  4466. });
  4467. });
  4468.  
  4469. // 测试
  4470. {
  4471. // var translations = {
  4472. //
  4473. // };
  4474. // traverseTextNodes($('xxx'), translations);
  4475. };
  4476.  
  4477. /**
  4478. * 应用value替换
  4479. */
  4480. let InputValueReplacements = subs.InputValueReplacements;
  4481. Object.entries(InputValueReplacements).forEach(([key, value]) => {
  4482. const classSelectors = Array.isArray(value.class) ? value.class : [value.class];
  4483. classSelectors.forEach(classSelector => {
  4484. traverseValueNodes(OJB_safeCreateJQElement(`${classSelector}`), value.rules);
  4485. });
  4486. });
  4487.  
  4488. // // 杂项
  4489. // (function () {
  4490. // // 选项汉化input[type="radio"]
  4491. // var translations = {
  4492. // "as individual participant": "个人",
  4493. // "as a team member": "作为一个团队成员",
  4494. // };
  4495. // $('input[type="radio"]').each(function () {
  4496. // var tag = $(this).parent().contents().filter(function () {
  4497. // return this.nodeType === Node.TEXT_NODE;
  4498. // });
  4499. // for (var i = 0; i < tag.length; i++) {
  4500. // var text = tag[i].textContent.trim();
  4501. // if (translations.hasOwnProperty(text)) {
  4502. // $(this).addClass(text);
  4503. // tag[i].replaceWith(translations[text]);
  4504. // break;
  4505. // }
  4506. // }
  4507. // });
  4508. // })();
  4509. // (function () {
  4510. // var translations = {
  4511. // "(standard input\/output)": "标准输入/输出",
  4512. // };
  4513. // $("div.notice").each(function () {
  4514. // var tag = $(this).children().eq(0).text();
  4515. // for (var property in translations) {
  4516. // if (tag.match(property)) {
  4517. // $(this).children().eq(0).text(translations[property]);
  4518. // break;
  4519. // }
  4520. // }
  4521. // });
  4522. // })();
  4523.  
  4524. // // 轻量站特殊
  4525. // if (OJBetter.typeOfPage.is_mSite) {
  4526. // traverseTextNodes($('nav'), commonReplacements['.second-level-menu']['rules']);
  4527. // }
  4528. // if (OJBetter.typeOfPage.is_mSite) {
  4529. // (function () {
  4530. // var translations = {
  4531. // "Announcements": "公告",
  4532. // "Submissions": "提交记录",
  4533. // "Contests": "比赛",
  4534. // };
  4535. // $(".caption").each(function () {
  4536. // var optionValue = $(this).text();
  4537. // if (translations[optionValue]) {
  4538. // $(this).text(translations[optionValue]);
  4539. // }
  4540. // });
  4541. // })();
  4542. // }
  4543. };
  4544.  
  4545. /**
  4546. * i18next初始化
  4547. */
  4548. async function initI18next() {
  4549. return new Promise((resolve, reject) => {
  4550. i18next
  4551. .use(i18nextChainedBackend)
  4552. .init({
  4553. lng: OJBetter.localization.scriptLang,
  4554. ns: ['common', 'settings', 'config', 'dialog', 'alert', 'translator',
  4555. 'button', 'codeEditor', 'comments', 'announce', 'logMessage'], // 命名空间列表
  4556. defaultNS: 'settings',
  4557. fallbackLng: ['zh', OJBetter.translation.targetLang],
  4558. load: 'currentOnly',
  4559. debug: false,
  4560. backend: {
  4561. backends: [
  4562. i18nextLocalStorageBackend,
  4563. i18nextHttpBackend
  4564. ],
  4565. backendOptions: [{
  4566. prefix: 'i18next_res_',
  4567. expirationTime: 7 * 24 * 60 * 60 * 1000,
  4568. defaultVersion: `v${OJBetter.state.version}`,
  4569. store: typeof window !== 'undefined' ? window.localStorage : null
  4570. }, {
  4571. /* options for secondary backend */
  4572. loadPath: (lng, ns) => {
  4573. if (lng[0] === 'zh' || lng[0] === 'zh-Hans') {
  4574. return `https://aowuucdn.oss-accelerate.aliyuncs.com/resources/locales/${OJBetter.state.formatName}/${ns}.json`;
  4575. }
  4576. return `https://aowuucdn.oss-accelerate.aliyuncs.com/i18n/${lng}/resources/locales/${OJBetter.state.formatName}/${ns}.json`;
  4577. }
  4578. }]
  4579. }
  4580. }, (err, t) => {
  4581. if (err) {
  4582. reject(err);
  4583. } else {
  4584. jqueryI18next.init(i18next, $, {
  4585. useOptionsAttr: true
  4586. });
  4587. resolve(t);
  4588. }
  4589. });
  4590. });
  4591. };
  4592.  
  4593. /**
  4594. * 抽象命令类
  4595. */
  4596. class Command {
  4597. execute() { }
  4598. undo() { }
  4599. }
  4600.  
  4601. /**
  4602. * 命令调用者
  4603. */
  4604. class CommandInvoker {
  4605. constructor() {
  4606. this.history = [];
  4607. }
  4608.  
  4609. /**
  4610. * 执行命令
  4611. * @param {Command} command 命令对象
  4612. */
  4613. execute(command) {
  4614. this.history.push(command);
  4615. command.execute();
  4616. }
  4617.  
  4618. /**
  4619. * 撤销命令
  4620. */
  4621. undo() {
  4622. const command = this.history.pop();
  4623. if (command) {
  4624. command.undo();
  4625. }
  4626. }
  4627. }
  4628.  
  4629. /**
  4630. * 接收者
  4631. */
  4632. class DOMContainer {
  4633. /**
  4634. * @param {JQueryObject} element 容器对象
  4635. */
  4636. constructor(element) {
  4637. this.containerElement = element;
  4638. }
  4639.  
  4640. /**
  4641. * 添加元素
  4642. * @param {JQueryObject} element 元素对象
  4643. * @returns {JQueryObject} 添加的元素对象
  4644. */
  4645. add(element) {
  4646. this.containerElement.append(element);
  4647. return this.containerElement.children().last();
  4648. }
  4649.  
  4650. /**
  4651. * 删除元素
  4652. * @param {JQueryObject} element 元素对象
  4653. */
  4654. remove(element) {
  4655. $(element).remove();
  4656. }
  4657. }
  4658.  
  4659. /**
  4660. * 具体命令类:添加元素
  4661. */
  4662. class AddElementCommand extends Command {
  4663. /**
  4664. * @param {DOMContainer} receiver 接收者
  4665. * @param {JQueryObject} element 元素对象
  4666. */
  4667. constructor(receiver, element) {
  4668. super();
  4669. this.receiver = receiver;
  4670. this.element = element;
  4671. this.addedElement = null;
  4672. }
  4673.  
  4674. execute() {
  4675. this.addedElement = this.receiver.add(this.element);
  4676. }
  4677.  
  4678. undo() {
  4679. if (this.addedElement) {
  4680. this.receiver.remove(this.addedElement);
  4681. }
  4682. }
  4683. }
  4684.  
  4685. /**
  4686. * 具体命令类:删除元素
  4687. */
  4688. class RemoveElementCommand extends Command {
  4689. /**
  4690. * @param {DOMContainer} receiver 接收者
  4691. * @param {JQueryObject} element 元素对象
  4692. */
  4693. constructor(receiver, element) {
  4694. super();
  4695. this.receiver = receiver;
  4696. this.element = element;
  4697. this.parent = $(element).parent();
  4698. this.nextSibling = $(element).next();
  4699. }
  4700.  
  4701. execute() {
  4702. this.receiver.remove(this.element);
  4703. }
  4704.  
  4705. undo() {
  4706. if (this.nextSibling.length > 0) {
  4707. $(this.element).insertBefore(this.nextSibling);
  4708. } else {
  4709. this.parent.append(this.element);
  4710. }
  4711. }
  4712. }
  4713.  
  4714. /**
  4715. * 验证器
  4716. */
  4717. class Validator {
  4718. /**
  4719. * 表单必填项空值校验
  4720. */
  4721. static required(structure) {
  4722. let config = {};
  4723. let allFieldsValid = true;
  4724. for (const key in structure) {
  4725. let value = key.type == 'checkbox' ?
  4726. $(key).prop("checked") : $(key).val();
  4727.  
  4728. config[structure[key].value] = value;
  4729.  
  4730. if (value || structure[key].require === false) {
  4731. $(key).removeClass('is_null');
  4732. } else {
  4733. $(key).addClass('is_null');
  4734. allFieldsValid = false;
  4735. }
  4736. }
  4737. return {
  4738. valid: allFieldsValid,
  4739. config: config
  4740. };
  4741. }
  4742.  
  4743. /**
  4744. * 表单合法性校验
  4745. */
  4746. static checkKeyValuePairs(structure, config) {
  4747. let errorKeys = [];
  4748. let allFieldsValid = true;
  4749.  
  4750. for (const key in structure) {
  4751. const { check, value } = structure[key];
  4752. const fieldValue = config[value];
  4753.  
  4754. // 如果字段没有值或校验类型不匹配,则跳过当前迭代
  4755. if (!fieldValue) continue;
  4756.  
  4757. let isValid = true;
  4758. switch (check) {
  4759. case 'keyValuePairs':
  4760. isValid = Validator.keyValuePairs(fieldValue);
  4761. break;
  4762. case 'dotSeparatedPath':
  4763. isValid = Validator.validateDotSeparatedPath(fieldValue);
  4764. break;
  4765. default:
  4766. // 没有匹配的校验类型
  4767. continue;
  4768. }
  4769.  
  4770. Validator.toggleErrorDisplay(key, isValid);
  4771. if (!isValid) {
  4772. allFieldsValid = false;
  4773. errorKeys.push(key);
  4774. }
  4775. }
  4776.  
  4777. return {
  4778. valid: allFieldsValid,
  4779. errorKeys: errorKeys
  4780. };
  4781. }
  4782.  
  4783. /**
  4784. * 切换错误信息的显示和隐藏
  4785. * @param {string} key - 字段的键
  4786. * @param {boolean} isValid - 字段值是否有效
  4787. */
  4788. static toggleErrorDisplay(key, isValid) {
  4789. const errorMessage = i18next.t('common.unValid', { ns: 'settings' });
  4790. const $errorSpan = $(key).prev('span.text-error');
  4791. if (!isValid) {
  4792. if (!$errorSpan.length) {
  4793. $(key).before(`<span class="text-error" style="color: red;">${errorMessage}</span>`);
  4794. }
  4795. } else {
  4796. $errorSpan.remove();
  4797. }
  4798. }
  4799.  
  4800. /**
  4801. * 键值对合法性校验
  4802. * @param {string} value
  4803. * @returns {boolean}
  4804. */
  4805. static keyValuePairs(value) {
  4806. const keyValuePairs = value.split('\n');
  4807. // 允许值中包含空格和冒号
  4808. const regex = /^[a-zA-Z0-9_-]+\s*:\s*.+$/;
  4809. return keyValuePairs.every(pair => regex.test(pair));
  4810. }
  4811.  
  4812.  
  4813. /**
  4814. * 点分隔符路径格式校验,允许加减运算
  4815. * @param {string} path
  4816. * @returns {boolean}
  4817. */
  4818. static validateDotSeparatedPath(path) {
  4819. // 正则表达式允许标识符之间有点号,标识符可以包含加减运算
  4820. const regex = /^([a-zA-Z0-9_-]+(\s*[\+\-]\s*[a-zA-Z0-9_-]+)*\.)*([a-zA-Z0-9_-]+(\s*[\+\-]\s*[a-zA-Z0-9_-]+)*)$/;
  4821. return regex.test(path);
  4822. }
  4823. }
  4824.  
  4825. /**
  4826. * 配置管理
  4827. */
  4828. class ConfigManager {
  4829. /**
  4830. * @param {HTMLElement} element - 挂载容器
  4831. * @param {string} prefix - 前缀
  4832. * @param {object} tempConfig - 配置内容
  4833. * @param {object} structure - 配置结构
  4834. * @param {object} configHTML - 配置编辑页面HTML
  4835. * @param {boolean} allowChoice - 是否允许选择列表项
  4836. */
  4837. constructor(element, prefix, tempConfig, structure, configHTML, allowChoice = true) {
  4838. /** @param 设置面板DIV */
  4839. this.settingMenuDiv = $('#OJBetter_setting_menu');
  4840. this.element = $(element);
  4841. this.prefix = prefix;
  4842. this.tempConfig = tempConfig;
  4843. this.structure = structure;
  4844. this.configHTML = configHTML;
  4845. this.allowChoice = allowChoice;
  4846.  
  4847. this.controlTip = null;
  4848. this.config_bar_list = null;
  4849. this.config_bar_ul = null;
  4850. this.config_add_button = null;
  4851. this.menu = null;
  4852. this.editItem = null;
  4853. this.deleteItem = null;
  4854.  
  4855. // 绑定方法
  4856. this.onAdd = this.onAdd.bind(this);
  4857. this.onEdit = this.onEdit.bind(this);
  4858. this.onDelete = this.onDelete.bind(this);
  4859. this.createListItemElement = this.createListItemElement.bind(this);
  4860.  
  4861. this.lastItemId = 0; // 列表中当前最后一个元素的id号
  4862. this.init();
  4863. }
  4864.  
  4865. init() {
  4866. this.createControlBar();
  4867. this.createContextMenu();
  4868. this.renderList();
  4869. }
  4870.  
  4871. /**
  4872. * 创建控制栏
  4873. */
  4874. createControlBar() {
  4875. this.controlTip = OJB_safeCreateJQElement(`<div id='${this.prefix}configControlTip' style='color:red;'></div>`);
  4876. this.config_bar_list = OJB_safeCreateJQElement(`<div class='config_bar_list' id='${this.prefix}config_bar_list'></div>`);
  4877. this.config_bar_ul = OJB_safeCreateJQElement(`<ul class='config_bar_ul' id='${this.prefix}config_bar_ul'></ul>`);
  4878. this.element.append(this.controlTip);
  4879. this.element.append(this.config_bar_list);
  4880. this.config_bar_list.append(this.config_bar_ul);
  4881. }
  4882.  
  4883. /**
  4884. * 创建右键菜单
  4885. */
  4886. createContextMenu() {
  4887. const menu = OJB_safeCreateJQElement(`<div id='config_bar_menu' style='display: none;'></div>`);
  4888. const editItem = OJB_safeCreateJQElement(`
  4889. <div class='config_bar_menu_item' id='config_bar_menu_edit'>
  4890. ${i18next.t('contextMenu.edit', { ns: 'translator' })}
  4891. </div>`);
  4892. const deleteItem = OJB_safeCreateJQElement(`
  4893. <div class='config_bar_menu_item' id='config_bar_menu_delete'>
  4894. ${i18next.t('contextMenu.delete', { ns: 'translator' })}
  4895. </div>`);
  4896. menu.append(editItem);
  4897. menu.append(deleteItem);
  4898. this.editItem = editItem;
  4899. this.deleteItem = deleteItem;
  4900. this.menu = menu;
  4901. this.settingMenuDiv.append(menu);
  4902. }
  4903.  
  4904. /**
  4905. * 关闭右键菜单
  4906. */
  4907. closeContextMenu() {
  4908. this.menu.css({ display: "none" });
  4909. }
  4910.  
  4911. /**
  4912. * 创建列表项
  4913. * @param {string} text - 列表项文本
  4914. * @returns {HTMLElement} - 列表项
  4915. */
  4916. createListItemElement(text) {
  4917. const id = OJB_getRandomNumber(4);
  4918. const li = $("<li></li>");
  4919. const radio = OJB_safeCreateJQElement(`<input type='radio' name='${this.prefix}config_item'></input>`)
  4920. .attr("value", text)
  4921. .attr("id", id)
  4922. .attr("prev_id", this.lastItemId)
  4923. .appendTo(li);
  4924. if (!this.allowChoice) {
  4925. radio.prop("disabled", true);
  4926. }
  4927. const label = OJB_safeCreateJQElement(`<label for='${id}' class='config_bar_ul_li_text'>${text}</label>`).appendTo(li);
  4928.  
  4929.  
  4930. this.lastItemId = id;
  4931.  
  4932. // 添加右键菜单
  4933. li.on("contextmenu", (event) => {
  4934. event.preventDefault();
  4935. this.menu.css({
  4936. display: "block",
  4937. left: event.pageX, top: event.pageY
  4938. });
  4939.  
  4940. const deleteItem = this.deleteItem;
  4941. const editItem = this.editItem;
  4942.  
  4943. // 移除旧事件
  4944. deleteItem.off("click");
  4945. editItem.off("click");
  4946.  
  4947. // 获取 li 在 ul 中的索引
  4948. const index = li.index();
  4949.  
  4950. deleteItem.on("click", () => this.onDelete(index, li));
  4951. editItem.on("click", () => this.onEdit(index, li));
  4952.  
  4953. $(document).one("click", (event) => {
  4954. if (!this.menu.get(0).contains(event.target)) {
  4955. this.closeContextMenu();
  4956. deleteItem.off("click", () => this.onDelete);
  4957. editItem.off("click", () => this.onEdit);
  4958. }
  4959. });
  4960. });
  4961.  
  4962. return li;
  4963. }
  4964.  
  4965. /**
  4966. * 渲染配置列表
  4967. */
  4968. renderList() {
  4969. const list = this.config_bar_ul;
  4970. list.empty(); // 清空
  4971. this.tempConfig.configurations.forEach((item) => {
  4972. list.append(this.createListItemElement(item['name']));
  4973. });
  4974.  
  4975. // 添加按钮
  4976. let addButton = OJB_safeCreateJQElement(`<li id='${this.prefix}add_button' class="tempConfig_add_button">
  4977. <span>+ ${i18next.t('add', { ns: 'common' })}</span>
  4978. </li>`);
  4979. this.config_add_button = addButton;
  4980. list.append(addButton);
  4981. addButton.on("click", this.onAdd);
  4982. }
  4983.  
  4984. /**
  4985. * 添加配置项
  4986. */
  4987. onAdd() {
  4988. const configMenu = this.createConfigHTML();
  4989. const structure = this.structure;
  4990.  
  4991. configMenu.on("click", "#tempConfig_save", () => {
  4992.  
  4993. // 检查必填字段
  4994. const { valid, config } = Validator.required(structure);
  4995. if (!valid) return;
  4996.  
  4997. // 检查键值对
  4998. const { valid: checkOk, errorKey } = Validator.checkKeyValuePairs(structure, config);
  4999. if (!checkOk) return;
  5000.  
  5001. this.tempConfig.configurations.push(config);
  5002.  
  5003. this.createListItemElement(config.name).insertBefore(this.config_add_button);
  5004.  
  5005. configMenu.remove();
  5006. });
  5007.  
  5008. configMenu.on("click", ".btn-close", () => {
  5009. configMenu.remove();
  5010. });
  5011. }
  5012.  
  5013. /**
  5014. * 修改配置项
  5015. * @param {number} index - 配置项索引
  5016. * @param {HTMLElement} li - 配置项
  5017. * @returns {void}
  5018. */
  5019. onEdit(index, li) {
  5020. const configMenu = this.createConfigHTML();
  5021. const structure = this.structure;
  5022.  
  5023. this.closeContextMenu();
  5024.  
  5025. // 填充表单
  5026. for (const [key, { value, type }] of Object.entries(this.structure)) {
  5027. const configValue = this.tempConfig.configurations[index][value];
  5028. const $element = $(key);
  5029. if (type === 'checkbox') {
  5030. $element.prop("checked", configValue);
  5031. } else {
  5032. $element.val(configValue);
  5033. }
  5034. }
  5035.  
  5036. configMenu.on("click", "#tempConfig_save", () => {
  5037. // 检查必填字段
  5038. const { valid, config } = Validator.required(structure);
  5039. if (!valid) return;
  5040.  
  5041. // 检查键值对
  5042. const { valid: checkOk, errorKey } = Validator.checkKeyValuePairs(structure, config);
  5043. if (!checkOk) return;
  5044.  
  5045. // 更新配置
  5046. this.tempConfig.configurations[index] = config;
  5047. li.find('label').text(config.name);
  5048.  
  5049. OJB_closeAndRemoveModal(configMenu);
  5050. });
  5051.  
  5052. configMenu.on("click", ".btn-close", () => {
  5053. OJB_closeAndRemoveModal(configMenu);
  5054. });
  5055. }
  5056.  
  5057. /**
  5058. * 删除配置项
  5059. * @param {number} index - 配置项索引
  5060. * @param {HTMLElement} li - 配置项
  5061. * @returns {void}
  5062. */
  5063. onDelete(index, li) {
  5064. this.closeContextMenu();
  5065. this.tempConfig.configurations.splice(index, 1);
  5066. li.remove();
  5067. }
  5068.  
  5069. /**
  5070. * 创建配置编辑页面
  5071. * @returns {JQuery<HTMLElement>} 返回配置编辑页面
  5072. */
  5073. createConfigHTML() {
  5074. const configMenu = OJB_safeCreateJQElement(this.configHTML);
  5075. this.settingMenuDiv.after(configMenu);
  5076. OJB_showModal(configMenu);
  5077. OJB_addDraggable(configMenu);
  5078. elementLocalize(configMenu);
  5079. return configMenu;
  5080. }
  5081.  
  5082. /**
  5083. * 获取配置内容
  5084. * @returns {object} - 配置内容
  5085. */
  5086. getTempConfig() {
  5087. return this.tempConfig;
  5088. }
  5089.  
  5090. /**
  5091. * 注册列表项选中改变监听
  5092. */
  5093. registerChoiceChange() {
  5094. this.config_bar_ul.on("change", "input[type='radio']", (event) => {
  5095. const value = event.target.value;
  5096. this.tempConfig.choice = value;
  5097. });
  5098. }
  5099. }
  5100.  
  5101. const OJBetter_setting_sidebar_HTML = `
  5102. <div class="OJBetter_setting_sidebar">
  5103. <ul>
  5104. <li><a href="#basic-settings" id="sidebar-basic-settings" class="active" data-i18n="settings:sidebar.basic"></a></li>
  5105. <li><a href="#l10n_settings" id="sidebar-l10n_settings" data-i18n="settings:sidebar.localization"></a></li>
  5106. <li><a href="#translation-settings" id="sidebar-translation-settings" data-i18n="settings:sidebar.translation"></a></li>
  5107. <li><a href="#clist_rating-settings" id="sidebar-clist_rating-settings" data-i18n="settings:sidebar.clist"></a></li>
  5108. <li><a href="#code_editor-settings" id="sidebar-code_editor-settings" data-i18n="settings:sidebar.monaco"></a></li>
  5109. <li><a href="#preference-settings" id="sidebar-preference-settings" data-i18n="settings:sidebar.preference"></a></li>
  5110. <li><a href="#dev-settings" id="sidebar-dev-settings" data-i18n="settings:sidebar.dev"></a></li>
  5111. <li><a href="#about-settings" id="sidebar-about-settings" data-i18n="settings:sidebar.about"></a></li>
  5112. </ul>
  5113. </div>
  5114. `;
  5115.  
  5116. const basic_settings_HTML = `
  5117. <div id="basic-settings" class="settings-page active">
  5118. <h3 data-i18n="settings:basic.title"></h3>
  5119. <hr>
  5120. <div class='OJBetter_setting_list' style="padding: 0px 10px;">
  5121. <span id="darkMode_span" data-i18n="settings:basic.darkMode.name"></span>
  5122. <div class="dark-mode-selection">
  5123. <label>
  5124. <input class="radio-input" type="radio" name="darkMode" value="dark" />
  5125. <span class="OJBetter_setting_menu_label_text"
  5126. data-i18n="settings:basic.darkMode.options.dark"></span>
  5127. <span class="radio-icon"> </span>
  5128. </label>
  5129. <label>
  5130. <input checked="" class="radio-input" type="radio" name="darkMode" value="light" />
  5131. <span class="OJBetter_setting_menu_label_text"
  5132. data-i18n="settings:basic.darkMode.options.light"></span>
  5133. <span class="radio-icon"> </span>
  5134. </label>
  5135. <label>
  5136. <input class="radio-input" type="radio" name="darkMode" value="follow" />
  5137. <span class="OJBetter_setting_menu_label_text"
  5138. data-i18n="settings:basic.darkMode.options.system"></span>
  5139. <span class="radio-icon"> </span>
  5140. </label>
  5141. </div>
  5142. </div>
  5143. <div class='OJBetter_setting_list' style="display:none;">
  5144. <label for="expandFoldingblocks" data-i18n="settings:basic.expandBlocks"></label>
  5145. <input type="checkbox" id="expandFoldingblocks" name="expandFoldingblocks">
  5146. </div>
  5147. <div class='OJBetter_setting_list' style="display:none;">
  5148. <label for="renderPerfOpt" data-i18n="settings:basic.renderOptimization.label"></label>
  5149. <div class="help_tip">
  5150. ${helpCircleHTML}
  5151. <div class="tip_text" data-i18n="[html]settings:basic.renderOptimization.helpText"></div>
  5152. </div>
  5153. <input type="checkbox" id="renderPerfOpt" name="renderPerfOpt">
  5154. </div>
  5155. <div class='OJBetter_setting_list' style="display:none;">
  5156. <label for="commentPaging" data-i18n="settings:basic.paging.label"></label>
  5157. <div class="help_tip">
  5158. ${helpCircleHTML}
  5159. <div class="tip_text" data-i18n="[html]settings:basic.paging.helpText"></div>
  5160. </div>
  5161. <input type="checkbox" id="commentPaging" name="commentPaging">
  5162. </div>
  5163. <div class='OJBetter_setting_list'>
  5164. <label for="showJumpToLuogu" data-i18n="settings:basic.luoguJump.label"></label>
  5165. <div class="help_tip">
  5166. ${helpCircleHTML}
  5167. <div class="tip_text" data-i18n="[html]settings:basic.luoguJump.helpText"></div>
  5168. </div>
  5169. <input type="checkbox" id="showJumpToLuogu" name="showJumpToLuogu">
  5170. </div>
  5171. <div class='OJBetter_setting_list'>
  5172. <label for="showCF2vjudge" data-i18n="settings:basic.vjudgeJump.label"></label>
  5173. <div class="help_tip">
  5174. ${helpCircleHTML}
  5175. <div class="tip_text" data-i18n="[html]settings:basic.vjudgeJump.helpText"></div>
  5176. </div>
  5177. <input type="checkbox" id="showCF2vjudge" name="showCF2vjudge">
  5178. </div>
  5179. <div class='OJBetter_setting_list' style="display:none;">
  5180. <label for="standingsRecolor" data-i18n="settings:basic.recolor.label"></label>
  5181. <div class="help_tip">
  5182. ${helpCircleHTML}
  5183. <div class="tip_text" data-i18n="[html]settings:basic.recolor.helpText"></div>
  5184. </div>
  5185. <input type="checkbox" id="standingsRecolor" name="standingsRecolor">
  5186. </div>
  5187. </div>
  5188. `;
  5189.  
  5190. const l10n_settings_HTML = `
  5191. <div id="l10n_settings" class="settings-page">
  5192. <h3 data-i18n="settings:localization.title"></h3>
  5193. <hr>
  5194. <div class='OJBetter_setting_list'>
  5195. <label for="scriptL10nLanguage" style="display: flex;" data-i18n="settings:localization.scriptLanguageLabel"></label>
  5196. <select id="scriptL10nLanguage" name="scriptL10nLanguage">
  5197. <option value="zh">简体中文</option>
  5198. <option value="zh-Hant">繁體中文</option>
  5199. <option value="en">English</option>
  5200. <option value="de">Deutsch</option>
  5201. <option value="fr">Français</option>
  5202. <option value="ko">한국어</option>
  5203. <option value="pt">Português</option>
  5204. <option value="ja">日本語</option>
  5205. <option value="es">Español</option>
  5206. <option value="it">Italiano</option>
  5207. <option value="hi">हिन्दी</option>
  5208. </select>
  5209. </div>
  5210. <div class='OJBetter_setting_list'>
  5211. <label for="localizationLanguage" style="display: flex;" data-i18n="settings:localization.websiteLanguageLabel"></label>
  5212. <select id="localizationLanguage" name="localizationLanguage">
  5213. <option value="initial">——</option>
  5214. <option value="zh">简体中文</option>
  5215. <option value="zh-Hant">繁體中文</option>
  5216. <option value="de">Deutsch</option>
  5217. <option value="fr">Français</option>
  5218. <option value="ko">한국어</option>
  5219. <option value="pt">Português</option>
  5220. <option value="ja">日本語</option>
  5221. <option value="es">Español</option>
  5222. <option value="it">Italiano</option>
  5223. <option value="hi">हिन्दी</option>
  5224. </select>
  5225. </div>
  5226. <div class='OJBetter_setting_list alert_tip'>
  5227. <div data-i18n="[html]settings:localization.notice.1"></div>
  5228. </div>
  5229. <div class='OJBetter_setting_list alert_tip'>
  5230. <div data-i18n="[html]settings:localization.notice.2"></div>
  5231. </div>
  5232. </div>
  5233. `;
  5234.  
  5235. const translation_settings_HTML = `
  5236. <div id="translation-settings" class="settings-page">
  5237. <h3 data-i18n="settings:translation.title"></h3>
  5238. <hr>
  5239. <h4 data-i18n="settings:translation.options.title"></h4>
  5240. <div class='OJBetter_setting_list'>
  5241. <label for="transTargetLang" style="display: flex;" data-i18n="settings:translation.preference.target.title"></label>
  5242. <div class="help_tip">
  5243. ${helpCircleHTML}
  5244. <div class="tip_text" data-i18n="[html]settings:translation.preference.target.helpText"></div>
  5245. </div>
  5246. <select id="transTargetLang" name="transTargetLang">
  5247. <option value="zh">简体中文</option>
  5248. <option value="zh-Hant">繁體中文</option>
  5249. <option value="de">Deutsch</option>
  5250. <option value="fr">Français</option>
  5251. <option value="ko">한국어</option>
  5252. <option value="pt">Português</option>
  5253. <option value="ja">日本語</option>
  5254. <option value="es">Español</option>
  5255. <option value="it">Italiano</option>
  5256. <option value="hi">हिन्दी</option>
  5257. </select>
  5258. </div>
  5259. <div id="translationServices">
  5260. <label>
  5261. <input type='radio' name='translation' value='deepl'>
  5262. <span class='OJBetter_setting_menu_label_text'
  5263. data-i18n="settings:translation.options.services.deepl"></span>
  5264. </label>
  5265. <label>
  5266. <input type='radio' name='translation' value='iflyrec'>
  5267. <span class='OJBetter_setting_menu_label_text'
  5268. data-i18n="settings:translation.options.services.iflyrec"></span>
  5269. </label>
  5270. <label>
  5271. <input type='radio' name='translation' value='youdao'>
  5272. <span class='OJBetter_setting_menu_label_text'
  5273. data-i18n="settings:translation.options.services.youdao"></span>
  5274. </label>
  5275. <label>
  5276. <input type='radio' name='translation' value='google'>
  5277. <span class='OJBetter_setting_menu_label_text'
  5278. data-i18n="settings:translation.options.services.google"></span>
  5279. </label>
  5280. <label>
  5281. <input type='radio' name='translation' value='caiyun'>
  5282. <span class='OJBetter_setting_menu_label_text'
  5283. data-i18n="settings:translation.options.services.caiyun"></span>
  5284. </label>
  5285. <label>
  5286. <input type='radio' name='translation' value='openai'>
  5287. <span class='OJBetter_setting_menu_label_text'
  5288. data-i18n="settings:translation.options.services.openai.name">
  5289. <div class="help_tip">
  5290. ${helpCircleHTML}
  5291. <div class="tip_text"
  5292. data-i18n="[html]settings:translation.options.services.openai.helpText"></div>
  5293. </div>
  5294. </span>
  5295. </label>
  5296. </div>
  5297. <hr>
  5298. <h4>DeepL</h4>
  5299. <div class='OJBetter_setting_list'>
  5300. <label for="deepl_type" style="display: flex;" data-i18n="settings:translation.deepl.mode.title"></label>
  5301. <div class="help_tip">
  5302. ${helpCircleHTML}
  5303. <div class="tip_text" data-i18n="[html]settings:translation.deepl.mode.helpText"></div>
  5304. </div>
  5305. <select id="deepl_type" name="deepl_type">
  5306. <option value="free" data-i18n="settings:translation.deepl.mode.select.free"></option>
  5307. <option value="api" data-i18n="settings:translation.deepl.mode.select.api"></option>
  5308. </select>
  5309. </div>
  5310. <div id="deepl_config" class="config"></div>
  5311. <div class='OJBetter_setting_list'>
  5312. <label for="enableEmphasisProtection" data-i18n="settings:translation.deepl.enableEmphasisProtection.title"></label>
  5313. <div class="help_tip" style="margin-right: initial;">
  5314. ${helpCircleHTML}
  5315. <div class="tip_text" data-i18n="[html]settings:translation.deepl.enableEmphasisProtection.helpText"></div>
  5316. </div>
  5317. <div class="badge">Official API Only</div>
  5318. <input type="checkbox" id="enableEmphasisProtection" name="enableEmphasisProtection">
  5319. </div>
  5320. <div class='OJBetter_setting_list'>
  5321. <label for="enableLinkProtection" data-i18n="settings:translation.deepl.enableLinkProtection.title"></label>
  5322. <div class="help_tip" style="margin-right: initial;">
  5323. ${helpCircleHTML}
  5324. <div class="tip_text" data-i18n="[html]settings:translation.deepl.enableLinkProtection.helpText"></div>
  5325. </div>
  5326. <div class="badge">Official API Only</div>
  5327. <input type="checkbox" id="enableLinkProtection" name="enableLinkProtection">
  5328. </div>
  5329. <hr>
  5330. <h4>ChatGPT</h4>
  5331. <div id="chatgpt_config" class="config"></div>
  5332. <div class='OJBetter_setting_list'>
  5333. <label for="openai_isStream" data-i18n="settings:translation.chatgpt.isStream.name"></label>
  5334. <div class="help_tip">
  5335. ${helpCircleHTML}
  5336. <div class="tip_text" data-i18n="[html]settings:translation.chatgpt.isStream.helpText"></div>
  5337. </div>
  5338. <input type="checkbox" id="openai_isStream" name="openai_isStream">
  5339. </div>
  5340. <hr>
  5341. <h4 data-i18n="settings:translation.preference.title"></h4>
  5342. <div class='OJBetter_setting_list'>
  5343. <label for="comment_translation_choice" style="display: flex;"
  5344. data-i18n="settings:translation.preference.comment_translation_choice.title">
  5345. </label>
  5346. <select id="comment_translation_choice" name="comment_translation_choice">
  5347. <option value="0" data-i18n="settings:translation.preference.comment_translation_choice.services.follow"></option>
  5348. <option value="deepl" data-i18n="settings:translation.preference.comment_translation_choice.services.deepl"></option>
  5349. <option value="iflyrec" data-i18n="settings:translation.preference.comment_translation_choice.services.iflyrec"></option>
  5350. <option value="youdao" data-i18n="settings:translation.preference.comment_translation_choice.services.youdao"></option>
  5351. <option value="google" data-i18n="settings:translation.preference.comment_translation_choice.services.google"></option>
  5352. <option value="caiyun" data-i18n="settings:translation.preference.comment_translation_choice.services.caiyun"></option>
  5353. <option value="openai" data-i18n="settings:translation.preference.comment_translation_choice.services.openai"></option>
  5354. </select>
  5355. </div>
  5356. <hr>
  5357.  
  5358. <div style="display:none;">
  5359. <h4 data-i18n="settings:translation.autoTranslation.title"></h4>
  5360. <div class='OJBetter_setting_list'>
  5361. <label for="autoTranslation" data-i18n="settings:translation.autoTranslation.enable"></label>
  5362. <div class="help_tip">
  5363. ${helpCircleHTML}
  5364. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.helpText"></div>
  5365. </div>
  5366. <input type="checkbox" id="autoTranslation" name="autoTranslation">
  5367. </div>
  5368. <div class='OJBetter_setting_list'>
  5369. <label for='shortTextLength'>
  5370. <div style="display: flex;align-items: center;"
  5371. data-i18n="settings:translation.autoTranslation.shortTextLength.name"></div>
  5372. </label>
  5373. <div class="help_tip">
  5374. ${helpCircleHTML}
  5375. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.shortTextLength.helpText">
  5376. </div>
  5377. </div>
  5378. <input type='number' id='shortTextLength' class='no_default' require=true data-i18n="[placeholder]settings:translation.autoTranslation.shortTextLength.placeholder">
  5379. <span data-i18n="settings:translation.autoTranslation.shortTextLength.end"></span>
  5380. </div>
  5381. <div class='OJBetter_setting_list'>
  5382. <label for="allowMixTrans" data-i18n="settings:translation.autoTranslation.allowMixTrans.name"></label>
  5383. <div class="help_tip">
  5384. ${helpCircleHTML}
  5385. <div class="tip_text" data-i18n="[html]settings:translation.autoTranslation.allowMixTrans.helpText">
  5386. </div>
  5387. </div>
  5388. <input type="checkbox" id="allowMixTrans" name="allowMixTrans">
  5389. <div class='OJBetter_checkboxs'>
  5390. <input type="checkbox" id="deepl" name="mixedTranslation" value="deepl">
  5391. <label for="deepl" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.deepl"></label>
  5392. <input type="checkbox" id="iflyrec" name="mixedTranslation" value="iflyrec">
  5393. <label for="iflyrec" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.iflyrec"></label>
  5394. <input type="checkbox" id="youdao" name="mixedTranslation" value="youdao">
  5395. <label for="youdao" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.youdao"></label>
  5396. <input type="checkbox" id="google" name="mixedTranslation" value="google">
  5397. <label for="google" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.google">Google</label>
  5398. <input type="checkbox" id="caiyun" name="mixedTranslation" value="caiyun">
  5399. <label for="caiyun" data-i18n="settings:translation.autoTranslation.allowMixTrans.checkboxs.caiyun"></label>
  5400. </div>
  5401. </div>
  5402. <hr>
  5403. </div>
  5404.  
  5405. <h4 data-i18n="settings:translation.advanced.name"></h4>
  5406. <div class='OJBetter_setting_list'>
  5407. <label for="comment_translation_mode" style="display: flex;" data-i18n="settings:translation.advanced.mode.name"></label>
  5408. <div class="help_tip">
  5409. ${helpCircleHTML}
  5410. <div class="tip_text" data-i18n="[html]settings:translation.advanced.mode.helpText"></div>
  5411. </div>
  5412. <select id="comment_translation_mode" name="comment_translation_mode">
  5413. <option value="0" data-i18n="settings:translation.advanced.mode.options.0"></option>
  5414. <option value="1" data-i18n="settings:translation.advanced.mode.options.1"></option>
  5415. <option value="2" data-i18n="settings:translation.advanced.mode.options.2"></option>
  5416. </select>
  5417. </div>
  5418. <div class='OJBetter_setting_list'>
  5419. <label for="memoryTranslateHistory" data-i18n="settings:translation.advanced.memory.name"></label>
  5420. <div class="help_tip">
  5421. ${helpCircleHTML}
  5422. <div class="tip_text" data-i18n="[html]settings:translation.advanced.memory.helpText"></div>
  5423. </div>
  5424. <input type="checkbox" id="memoryTranslateHistory" name="memoryTranslateHistory">
  5425. </div>
  5426. <div class='OJBetter_setting_list'>
  5427. <label for="translation_retransAction" style="display: flex;" data-i18n="settings:translation.advanced.retrans.name"></label>
  5428. <div class="help_tip">
  5429. ${helpCircleHTML}
  5430. <div class="tip_text" data-i18n="[html]settings:translation.advanced.retrans.helpText"></div>
  5431. </div>
  5432. <select id="translation_retransAction" name="translation_retransAction">
  5433. <option value=0 data-i18n="settings:translation.advanced.retrans.options.0"></option>
  5434. <option value=1 data-i18n="settings:translation.advanced.retrans.options.1"></option>
  5435. </select>
  5436. </div>
  5437. <div class='OJBetter_setting_list'>
  5438. <label for='transWaitTime'>
  5439. <div style="display: flex;align-items: center;" data-i18n="settings:translation.advanced.transWaitTime.name"></div>
  5440. </label>
  5441. <div class="help_tip">
  5442. ${helpCircleHTML}
  5443. <div class="tip_text" data-i18n="[html]settings:translation.advanced.transWaitTime.helpText"></div>
  5444. </div>
  5445. <input type='number' id='transWaitTime' class='no_default' require=true data-i18n="[placeholder]settings:translation.advanced.transWaitTime.placeholder">
  5446. <span data-i18n="settings:translation.advanced.transWaitTime.end"></span>
  5447. </div>
  5448. <div class='OJBetter_setting_list'>
  5449. <label for="translation_replaceSymbol" style="display: flex;" data-i18n="settings:translation.advanced.replaceSymbol.name"></label>
  5450. <div class="help_tip">
  5451. ${helpCircleHTML}
  5452. <div class="tip_text" data-i18n="[html]settings:translation.advanced.replaceSymbol.helpText"></div>
  5453. </div>
  5454. <select id="translation_replaceSymbol" name="translation_replaceSymbol">
  5455. <option value=2 data-i18n="settings:translation.advanced.replaceSymbol.options.2"></option>
  5456. <option value=1 data-i18n="settings:translation.advanced.replaceSymbol.options.1"></option>
  5457. <option value=3 data-i18n="settings:translation.advanced.replaceSymbol.options.3"></option>
  5458. </select>
  5459. </div>
  5460. <div class='OJBetter_setting_list'>
  5461. <label for="filterTextWithoutEmphasis" data-i18n="settings:translation.advanced.filterTextWithoutEmphasis.name"></label>
  5462. <div class="help_tip">
  5463. ${helpCircleHTML}
  5464. <div class="tip_text" data-i18n="[html]settings:translation.advanced.filterTextWithoutEmphasis.helpText"></div>
  5465. </div>
  5466. <input type="checkbox" id="filterTextWithoutEmphasis" name="filterTextWithoutEmphasis">
  5467. </div>
  5468. </div>
  5469. `;
  5470.  
  5471. const clist_rating_settings_HTML = `
  5472. <div id="clist_rating-settings" class="settings-page">
  5473. <h3 data-i18n="settings:clist.title"></h3>
  5474. <hr>
  5475. <h4 data-i18n="settings:clist.basics.name"></h4>
  5476. <div class='OJBetter_setting_list alert_tip'>
  5477. <div>
  5478. <p data-i18n="[html]settings:clist.basics.notice"></p>
  5479. </div>
  5480. </div>
  5481. <div class='OJBetter_setting_list'>
  5482. <label for='clist_Authorization'>
  5483. <div style="display: flex;align-items: center;">
  5484. <span class="input_label" data-i18n="settings:clist.basics.key.title"></span>
  5485. </div>
  5486. </label>
  5487. <div class="help_tip">
  5488. ${helpCircleHTML}
  5489. <div class="tip_text" data-i18n="[html]settings:clist.basics.key.helpText"></div>
  5490. </div>
  5491. <input type='text' id='clist_Authorization' class='no_default' required="true"
  5492. data-i18n="[placeholder]settings:clist.basics.key.keyPlaceholder">
  5493. </div>
  5494. <hr>
  5495. <h4 data-i18n="settings:clist.displayRating.title"></h4>
  5496. <div class='OJBetter_setting_list'>
  5497. <label for="showClistRating_contest"><span data-i18n="settings:clist.displayRating.contest.name"></span></label>
  5498. <input type="checkbox" id="showClistRating_contest" name="showClistRating_contest">
  5499. </div>
  5500. <div class='OJBetter_setting_list'>
  5501. <label for="showClistRating_problem"><span data-i18n="settings:clist.displayRating.problem.name"></span></label>
  5502. <input type="checkbox" id="showClistRating_problem" name="showClistRating_problem">
  5503. </div>
  5504. <div class='OJBetter_setting_list' style='display:none;'>
  5505. <label for="showClistRating_problemset"><span data-i18n="settings:clist.displayRating.problemset.name"></span></label>
  5506. <input type="checkbox" id="showClistRating_problemset" name="showClistRating_problemset">
  5507. </div>
  5508. <hr>
  5509. <div class='OJBetter_setting_list'>
  5510. <label for="RatingHidden"><span data-i18n="settings:clist.spoilerProtection.title"></span></label>
  5511. <div class="help_tip">
  5512. ${helpCircleHTML}
  5513. <div class="tip_text" data-i18n="[html]settings:clist.spoilerProtection.helpText"></div>
  5514. </div>
  5515. <input type="checkbox" id="RatingHidden" name="RatingHidden">
  5516. </div>
  5517. </div>
  5518. `;
  5519.  
  5520. const code_editor_settings_HTML = `
  5521. <div id="code_editor-settings" class="settings-page">
  5522. <h3 data-i18n="settings:codeEditor.title"></h3>
  5523. <hr>
  5524. <h4 data-i18n="settings:codeEditor.basics"></h4>
  5525. <div class='OJBetter_setting_list'>
  5526. <label for="problemPageCodeEditor"><span
  5527. data-i18n="settings:codeEditor.problemPageCodeEditor.label"></span></label>
  5528. <div class="help_tip">
  5529. ${helpCircleHTML}
  5530. <div class="tip_text" data-i18n="settings:codeEditor.problemPageCodeEditor.helpText"></div>
  5531. </div>
  5532. <input type="checkbox" id="problemPageCodeEditor" name="problemPageCodeEditor">
  5533. </div>
  5534. <div class='OJBetter_setting_list'>
  5535. <label for="beautifyPreBlocks"><span
  5536. data-i18n="settings:codeEditor.beautifyPreBlocks.label"></span></label>
  5537. <div class="help_tip">
  5538. ${helpCircleHTML}
  5539. <div class="tip_text" data-i18n="settings:codeEditor.beautifyPreBlocks.helpText"></div>
  5540. </div>
  5541. <input type="checkbox" id="beautifyPreBlocks" name="beautifyPreBlocks">
  5542. </div>
  5543. <hr>
  5544. <h4 data-i18n="settings:codeEditor.preferences.title"></h4>
  5545. <div class='OJBetter_setting_list'>
  5546. <label for="isCodeSubmitConfirm"><span
  5547. data-i18n="settings:codeEditor.preferences.isCodeSubmitConfirm.label"></span></label>
  5548. <div class="help_tip">
  5549. ${helpCircleHTML}
  5550. <div class="tip_text" data-i18n="settings:codeEditor.preferences.isCodeSubmitConfirm.helpText"></div>
  5551. </div>
  5552. <input type="checkbox" id="isCodeSubmitConfirm" name="isCodeSubmitConfirm">
  5553. </div>
  5554. <div class='OJBetter_setting_list'>
  5555. <label for="alwaysConsumeMouseWheel"><span
  5556. data-i18n="settings:codeEditor.preferences.alwaysConsumeMouseWheel.label"></span></label>
  5557. <div class="help_tip">
  5558. ${helpCircleHTML}
  5559. <div class="tip_text" data-i18n="settings:codeEditor.preferences.alwaysConsumeMouseWheel.helpText"></div>
  5560. </div>
  5561. <input type="checkbox" id="alwaysConsumeMouseWheel" name="alwaysConsumeMouseWheel">
  5562. </div>
  5563. <div class='OJBetter_setting_list'>
  5564. <label for="submitButtonPosition"><span
  5565. data-i18n="settings:codeEditor.preferences.submitButtonPosition.label"></span></label>
  5566. <div class="help_tip">
  5567. ${helpCircleHTML}
  5568. <div class="tip_text" data-i18n="settings:codeEditor.preferences.submitButtonPosition.helpText"></div>
  5569. </div>
  5570. <select id="submitButtonPosition" name="submitButtonPosition">
  5571. <option value="bottom" data-i18n="settings:codeEditor.preferences.submitButtonPosition.options.bottom"></option>
  5572. <option value="top" data-i18n="settings:codeEditor.preferences.submitButtonPosition.options.top"></option>
  5573. </select>
  5574. </div>
  5575. <hr>
  5576. <h4 data-i18n="settings:codeEditor.onlineCodeExecution.title"></h4>
  5577. <label>
  5578. <input type='radio' name='compiler' value='official'>
  5579. <span class='OJBetter_setting_menu_label_text'
  5580. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.codeforces"></span>
  5581. </label>
  5582. <label>
  5583. <input type='radio' name='compiler' value='wandbox'>
  5584. <span class='OJBetter_setting_menu_label_text'
  5585. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.wandbox"></span>
  5586. </label>
  5587. <label>
  5588. <input type='radio' name='compiler' value='rextester'>
  5589. <span class='OJBetter_setting_menu_label_text'
  5590. data-i18n="settings:codeEditor.onlineCodeExecution.compilerOptions.rextester"></span>
  5591. </label>
  5592. <hr>
  5593. <h4 data-i18n="settings:codeEditor.lsp.title"></h4>
  5594. <div class='OJBetter_setting_list'>
  5595. <label for="useLSP"><span data-i18n="settings:codeEditor.lsp.useLSP.label"></span></label>
  5596. <div class="help_tip">
  5597. ${helpCircleHTML}
  5598. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.useLSP.helpText"></div>
  5599. </div>
  5600. <input type="checkbox" id="useLSP" name="useLSP">
  5601. </div>
  5602. <div class='OJBetter_setting_list'>
  5603. <label for='OJBetter_Bridge_WorkUri'>
  5604. <div style="display: flex;align-items: center;">
  5605. <span class="input_label" data-i18n="settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.label"></span>
  5606. </div>
  5607. </label>
  5608. <div class="help_tip">
  5609. ${helpCircleHTML}
  5610. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.helpText"></div>
  5611. </div>
  5612. <input type='text' id='OJBetter_Bridge_WorkUri' class='no_default'
  5613. require=true data-i18n="[placeholder]settings:codeEditor.lsp.OJBetter_Bridge_WorkUri.placeholder">
  5614. </div>
  5615. <div class='OJBetter_setting_list'>
  5616. <label for='OJBetter_Bridge_SocketUrl'>
  5617. <div style="display: flex;align-items: center;">
  5618. <span class="input_label"
  5619. data-i18n="settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.label"></span>
  5620. </div>
  5621. </label>
  5622. <div class="help_tip">
  5623. ${helpCircleHTML}
  5624. <div class="tip_text" data-i18n="[html]settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.helpText"></div>
  5625. </div>
  5626. <input type='text' id='OJBetter_Bridge_SocketUrl' class='no_default'
  5627. require=true data-i18n="[placeholder]settings:codeEditor.lsp.OJBetter_Bridge_SocketUrl.placeholder">
  5628. </div>
  5629. <hr>
  5630. <h4 data-i18n="settings:codeEditor.staticCompletionEnhancement.title"></h4>
  5631. <div class='OJBetter_setting_list'>
  5632. <label for="cppCodeTemplateComplete"><span
  5633. data-i18n="settings:codeEditor.staticCompletionEnhancement.cppCodeTemplateComplete.label"></span></label>
  5634. <div class="help_tip">
  5635. ${helpCircleHTML}
  5636. <div class="tip_text" data-i18n="[html]settings:codeEditor.staticCompletionEnhancement.cppCodeTemplateComplete.helpText"></div>
  5637. </div>
  5638. <input type="checkbox" id="cppCodeTemplateComplete" name="cppCodeTemplateComplete">
  5639. </div>
  5640. <hr>
  5641. <h5 data-i18n="settings:codeEditor.staticCompletionEnhancement.customization"></h5>
  5642. <div class='OJBetter_setting_list alert_warn'>
  5643. <div>
  5644. <p data-i18n="settings:codeEditor.staticCompletionEnhancement.performanceWarning"></p>
  5645. </div>
  5646. </div>
  5647. <div id="Complet_config" class="config"></div>
  5648. </div>
  5649. `;
  5650.  
  5651. const preference_settings_HTML = `
  5652. <div id="preference-settings" class="settings-page">
  5653. <h3 data-i18n="settings:preference.title"></h3>
  5654. <hr>
  5655. <div class='OJBetter_setting_list'>
  5656. <label for="showLoading" data-i18n="settings:preference.loadingInfo.label"></label>
  5657. <div class="help_tip">
  5658. ${helpCircleHTML}
  5659. <div class="tip_text" data-i18n="[html]settings:preference.loadingInfo.helpText" data-i18n-options='{ "scriptName": "${OJBetter.state.name}" }'></div>
  5660. </div>
  5661. <input type="checkbox" id="showLoading" name="showLoading">
  5662. </div>
  5663. <div class='OJBetter_setting_list'>
  5664. <label for="hoverTargetAreaDisplay" data-i18n="settings:preference.targetArea.label"></label>
  5665. <div class="help_tip">
  5666. ${helpCircleHTML}
  5667. <div class="tip_text" data-i18n="[html]settings:preference.targetArea.helpText"></div>
  5668. </div>
  5669. <input type="checkbox" id="hoverTargetAreaDisplay" name="hoverTargetAreaDisplay">
  5670. </div>
  5671. <div class='OJBetter_setting_list'>
  5672. <label for='iconButtonSize'>
  5673. <div style="display: flex;align-items: center;" data-i18n="settings:preference.iconButtonSize.title"></div>
  5674. </label>
  5675. <div class="help_tip">
  5676. ${helpCircleHTML}
  5677. <div class="tip_text" data-i18n="[html]settings:preference.iconButtonSize.helpText"></div>
  5678. </div>
  5679. <input type='number' id='iconButtonSize' class='no_default' require=true data-i18n="[placeholder]settings:preference.iconButtonSize.placeholder">
  5680. <span>px</span>
  5681. </div>
  5682. </div>
  5683. `;
  5684.  
  5685. const dev_settings_HTML = `
  5686. <div id="dev-settings" class="settings-page">
  5687. <h3 data-i18n="settings:dev.title"></h3>
  5688. <hr>
  5689. <div class='OJBetter_setting_list alert_danger'>
  5690. <div>
  5691. <p data-i18n="[html]settings:dev.notice"></p>
  5692. </div>
  5693. </div>
  5694. <hr>
  5695. <h5 data-i18n="settings:dev.load.title"></h5>
  5696. <div class='OJBetter_setting_list'>
  5697. <label for="notWaiteLoaded"><span data-i18n="settings:dev.load.notWaiteLoaded.label"></span></label>
  5698. <div class="help_tip">
  5699. ${helpCircleHTML}
  5700. <div class="tip_text" data-i18n="[html]settings:dev.load.notWaiteLoaded.helpText"></div>
  5701. </div>
  5702. <input type="checkbox" id="notWaiteLoaded" name="notWaiteLoaded">
  5703. </div>
  5704. <hr>
  5705. <h5 data-i18n="settings:dev.l10n.title"></h5>
  5706. <div class='OJBetter_setting_list'>
  5707. <label><span data-i18n="settings:dev.l10n.refreshScrpitCache.label"></span></label>
  5708. <div class="help_tip">
  5709. ${helpCircleHTML}
  5710. <div class="tip_text" data-i18n="[html]settings:dev.l10n.refreshScrpitCache.helpText"></div>
  5711. </div>
  5712. <button type="button" id="l10n_refreshScrpitCacheButton" name="l10n_refreshScrpitCacheButton" data-i18n="settings:dev.l10n.refreshScrpitCache.button"></button>
  5713. </div>
  5714. <hr>
  5715. <h5 data-i18n="settings:dev.indexedDB.title"></h5>
  5716. <div class='OJBetter_setting_list'>
  5717. <label><span data-i18n="settings:dev.indexedDB.clear.label"></span></label>
  5718. <div class="help_tip">
  5719. ${helpCircleHTML}
  5720. <div class="tip_text" data-i18n="[html]settings:dev.indexedDB.clear.helpText"></div>
  5721. </div>
  5722. <button type="button" id="indexedDB_clearButton" name="indexedDB_clearButton" data-i18n="settings:dev.indexedDB.clear.button"></button>
  5723. </div>
  5724. <div class='OJBetter_setting_list'>
  5725. <label><span data-i18n="settings:dev.indexedDB.inputOrExport.label"></span></label>
  5726. <div class="help_tip">
  5727. ${helpCircleHTML}
  5728. <div class="tip_text" data-i18n="[html]settings:dev.indexedDB.inputOrExport.helpText"></div>
  5729. </div>
  5730. <button type="button" id="indexedDB_exportButton" name="indexedDB_exportButton" data-i18n="settings:dev.indexedDB.inputOrExport.export"></button>
  5731. <button type="button" id="indexedDB_importButton" name="indexedDB_importButton" data-i18n="settings:dev.indexedDB.inputOrExport.import"></button>
  5732. </div>
  5733. <hr>
  5734. <h5 data-i18n="settings:dev.configuration.title"></h5>
  5735. <div class='OJBetter_setting_list'>
  5736. <label><span data-i18n="settings:dev.configuration.clear.label"></span></label>
  5737. <div class="help_tip">
  5738. ${helpCircleHTML}
  5739. <div class="tip_text" data-i18n="[html]settings:dev.configuration.clear.helpText"></div>
  5740. </div>
  5741. <button type="button" id="configuration_clearButton" name="configuration_clearButton" data-i18n="settings:dev.configuration.clear.button"></button>
  5742. </div>
  5743. <div class='OJBetter_setting_list'>
  5744. <label><span data-i18n="settings:dev.configuration.inputOrExport.label"></span></label>
  5745. <div class="help_tip">
  5746. ${helpCircleHTML}
  5747. <div class="tip_text" data-i18n="[html]settings:dev.configuration.inputOrExport.helpText"></div>
  5748. </div>
  5749. <button type="button" id="configuration_exportButton" name="configuration_exportButton" data-i18n="settings:dev.configuration.inputOrExport.export"></button>
  5750. <button type="button" id="configuration_importButton" name="configuration_importButton" data-i18n="settings:dev.configuration.inputOrExport.import"></button>
  5751. </div>
  5752. </div>
  5753. `;
  5754.  
  5755. const about_settings_HTML = `
  5756. <div id="about-settings" class="settings-page">
  5757. <h3 data-i18n="settings:about.title"></h3>
  5758. <hr>
  5759. <div class='versionInfo'>
  5760. <p>${OJBetter.state.name}</p>
  5761. <p><span data-i18n="settings:about.version"></span><span id="nowVersion">${OJBetter.state.version}</span></p>
  5762. <p> @北极小狐 <a target="_blank" href="https://github.com/beijixiaohu/OJBetter">Github</a>
  5763. <a target="_blank" href="https://greatest.deepsurf.us/zh-CN/scripts/465777">GreasyFork</a></p>
  5764. </div>
  5765. <hr>
  5766. <h5 data-i18n="settings:about.update.title"></h5>
  5767. <div id="thanksforDevChannelNotice" class='OJBetter_setting_list alert_info'>
  5768. <div data-i18n="[html]settings:about.update.thanksforDevChannelNotice"} data-i18n-options='{ "scriptName": "${OJBetter.state.name}" }' ></div>
  5769. </div>
  5770. <div class='OJBetter_setting_list'>
  5771. <label for="updateChannel"><span data-i18n="settings:about.update.channel.label"></span></label>
  5772. <div class="help_tip">
  5773. ${helpCircleHTML}
  5774. <div class="tip_text" data-i18n="[html]settings:about.update.channel.helpText"></div>
  5775. </div>
  5776. <select id="updateChannel" name="updateChannel">
  5777. <option value="release" data-i18n="settings:about.update.channel.options.release"></option>
  5778. <option value="dev" data-i18n="settings:about.update.channel.options.dev"></option>
  5779. </select>
  5780. </div>
  5781. <div class='OJBetter_setting_list'>
  5782. <label for="updateSource"><span data-i18n="settings:about.update.source.label"></span></label>
  5783. <div class="help_tip">
  5784. ${helpCircleHTML}
  5785. <div class="tip_text" data-i18n="[html]settings:about.update.source.helpText"></div>
  5786. </div>
  5787. <select id="updateSource" name="updateSource">
  5788. <option value="greasyfork" data-i18n="settings:about.update.source.options.greasyfork"></option>
  5789. <option value="github" data-i18n="settings:about.update.source.options.github"></option>
  5790. <option value="aliyunoss" data-i18n="settings:about.update.source.options.aliyunoss"></option>
  5791. </select>
  5792. </div>
  5793. </div>
  5794. `;
  5795.  
  5796. const OJBetter_setting_content_HTML = `
  5797. <div class="OJBetter_setting_content">
  5798. ${basic_settings_HTML}
  5799. ${l10n_settings_HTML}
  5800. ${translation_settings_HTML}
  5801. ${clist_rating_settings_HTML}
  5802. ${code_editor_settings_HTML}
  5803. ${preference_settings_HTML}
  5804. ${dev_settings_HTML}
  5805. ${about_settings_HTML}
  5806. </div>
  5807. `;
  5808.  
  5809. // 设置界面HTML
  5810. const OJBetterSettingMenu_HTML = `
  5811. <dialog class='OJBetter_setting_menu' id='OJBetter_setting_menu'>
  5812. <div class="tool-box">
  5813. <button class='ojb_btn ojb_btn_popover top btn-close'>
  5814. <i class="iconfont">&#xe614;</i>
  5815. </button>
  5816. </div>
  5817. <div class="OJBetter_setting_container">
  5818. ${OJBetter_setting_sidebar_HTML}
  5819. ${OJBetter_setting_content_HTML}
  5820. </div>
  5821. </dialog>
  5822. `;
  5823.  
  5824. const apiCustomConfigHTML = (prefix) => {
  5825. return `
  5826. <div class="OJBetter_setting_list">
  5827. <label for='${prefix}_header'>
  5828. <div style="display: flex;align-items: center;">
  5829. <span class="input_label" data-i18n="config:common.advanced.header.label"></span>
  5830. <div class="help_tip">
  5831. ${helpCircleHTML}
  5832. <div class="tip_text" data-i18n="[html]config:common.advanced.header.tipText"></div>
  5833. </div>
  5834. </div>
  5835. </label>
  5836. <textarea id="${prefix}_header" placeholder='' require = false data-i18n="[placeholder]config:common.advanced.header.placeholder"></textarea>
  5837. </div>
  5838. <div class="OJBetter_setting_list">
  5839. <label for='${prefix}_data'>
  5840. <div style="display: flex;align-items: center;">
  5841. <span class="input_label" data-i18n="config:common.advanced.data.label"></span>
  5842. <div class="help_tip">
  5843. ${helpCircleHTML}
  5844. <div class="tip_text" data-i18n="[html]config:common.advanced.data.tipText"></div>
  5845. </div>
  5846. </div>
  5847. </label>
  5848. <textarea id="${prefix}_data" placeholder='' require = false data-i18n="[placeholder]config:common.advanced.data.placeholder"></textarea>
  5849. </div>
  5850. `;
  5851. };
  5852.  
  5853. const apiQuotaConfigHTML = (prefix) => {
  5854. return `
  5855. <div class="OJBetter_setting_list">
  5856. <label for='${prefix}_quota_url'>
  5857. <div style="display: flex;align-items: center;">
  5858. <span class="input_label" data-i18n="config:common.quota.url.label"></span>
  5859. <div class="help_tip">
  5860. ${helpCircleHTML}
  5861. <div class="tip_text" data-i18n="[html]config:common.quota.url.tipText"></div>
  5862. </div>
  5863. </div>
  5864. </label>
  5865. <input type='text' id='${prefix}_quota_url' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:common.quota.url.placeholder">
  5866. </div>
  5867. <div class="OJBetter_setting_list">
  5868. <label for="${prefix}_quota_method" style="display: flex;" data-i18n="config:common.quota.method.label"></label>
  5869. <div class="help_tip">
  5870. ${helpCircleHTML}
  5871. <div class="tip_text" data-i18n="[html]config:common.quota.method.tipText"></div>
  5872. </div>
  5873. <select id="${prefix}_quota_method" name="${prefix}_quota_method">
  5874. <option value="get">GET</option>
  5875. <option value="post">POST</option>
  5876. </select>
  5877. </div>
  5878. <div class="OJBetter_setting_list">
  5879. <label for='${prefix}_quota_header'>
  5880. <div style="display: flex;align-items: center;">
  5881. <span class="input_label" data-i18n="config:common.quota.header.label"></span>
  5882. <div class="help_tip">
  5883. ${helpCircleHTML}
  5884. <div class="tip_text" data-i18n="[html]config:common.quota.header.tipText"></div>
  5885. </div>
  5886. </div>
  5887. </label>
  5888. <textarea id="${prefix}_quota_header" placeholder='' require = false data-i18n="[placeholder]config:common.quota.header.placeholder"></textarea>
  5889. </div>
  5890. <div class="OJBetter_setting_list">
  5891. <label for='${prefix}_quota_data'>
  5892. <div style="display: flex;align-items: center;">
  5893. <span class="input_label" data-i18n="config:common.quota.data.label"></span>
  5894. <div class="help_tip">
  5895. ${helpCircleHTML}
  5896. <div class="tip_text" data-i18n="[html]config:common.quota.data.tipText"></div>
  5897. </div>
  5898. </div>
  5899. </label>
  5900. <textarea id="${prefix}_quota_data" placeholder='' require = false data-i18n="[placeholder]config:common.quota.data.placeholder"></textarea>
  5901. </div>
  5902. <div class="OJBetter_setting_list">
  5903. <div style="display: flex;align-items: center;">
  5904. <span class="input_label" data-i18n="config:common.quota.surplus.label"></span>
  5905. <div class="help_tip">
  5906. ${helpCircleHTML}
  5907. <div class="tip_text" data-i18n="[html]config:common.quota.surplus.tipText"></div>
  5908. </div>
  5909. </div>
  5910. </label>
  5911. <input type='text' id='${prefix}_quota_surplus' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:common.quota.surplus.placeholder">
  5912. </div>
  5913. `;
  5914. }
  5915.  
  5916. const deeplConfigEditHTML = `
  5917. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  5918. <div class='OJBetter_setting_content'>
  5919. <div class="tool-box">
  5920. <button class='ojb_btn ojb_btn_popover top btn-close'>
  5921. <i class="iconfont">&#xe614;</i>
  5922. </button>
  5923. </div>
  5924. <h4 data-i18n="config:deepl.title"></h4>
  5925. <h5 data-i18n="config:deepl.basic.title"></h5>
  5926. <hr>
  5927. <div class="OJBetter_setting_list">
  5928. <label for='name'>
  5929. <span class="input_label" data-i18n="config:deepl.basic.name.label"></span>
  5930. </label>
  5931. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:deepl.basic.name.placeholder">
  5932. </div>
  5933. <div class='OJBetter_setting_list'>
  5934. <label for="deepl_apiGenre" style="display: flex;" data-i18n="config:deepl.genre.label"></label>
  5935. <div class="help_tip">
  5936. ${helpCircleHTML}
  5937. <div class="tip_text" data-i18n="[html]config:deepl.genre.tipText"></div>
  5938. </div>
  5939. <select id="deepl_apiGenre" name="deepl_apiGenre">
  5940. <option value="api-free">api-free</option>
  5941. <option value="api-pro">api-pro</option>
  5942. <option value="deeplx">deeplx</option>
  5943. </select>
  5944. </div>
  5945. <div class="OJBetter_setting_list">
  5946. <label for='deepl_key'>
  5947. <div style="display: flex;align-items: center;">
  5948. <span class="input_label" data-i18n="config:deepl.basic.key.label"></span>
  5949. <div class="help_tip">
  5950. ${helpCircleHTML}
  5951. <div class="tip_text" data-i18n="[html]config:deepl.basic.key.tipText"></div>
  5952. </div>
  5953. </div>
  5954. </label>
  5955. <input type='text' id='deepl_key' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:deepl.basic.key.placeholder">
  5956. </div>
  5957. <div class="OJBetter_setting_list">
  5958. <label for='deepl_proxy'>
  5959. <div style="display: flex;align-items: center;">
  5960. <span class="input_label" data-i18n="config:deepl.basic.proxy.label">Proxy API:</span>
  5961. <div class="help_tip">
  5962. ${helpCircleHTML}
  5963. <div class="tip_text" data-i18n="[html]config:deepl.basic.proxy.tipText"></div>
  5964. </div>
  5965. </div>
  5966. </label>
  5967. <input type='text' id='deepl_proxy' placeholder='' require = false>
  5968. </div>
  5969. <hr>
  5970. <details>
  5971. <summary data-i18n="config:common.advanced.title"></summary>
  5972. ${apiCustomConfigHTML('deepl')}
  5973. </details>
  5974. <details>
  5975. <summary data-i18n="config:common.quota.title"></summary>
  5976. ${apiQuotaConfigHTML('deepl')}
  5977. </details>
  5978. <button id='tempConfig_save' data-i18n="common:save"></button>
  5979. </div>
  5980. </dialog>
  5981. `;
  5982.  
  5983. const chatgptConfigEditHTML = `
  5984. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  5985. <div class='OJBetter_setting_content'>
  5986. <div class="tool-box">
  5987. <button class='ojb_btn ojb_btn_popover top btn-close'>
  5988. <i class="iconfont">&#xe614;</i>
  5989. </button>
  5990. </div>
  5991. <h4 data-i18n="config:chatgpt.title"></h4>
  5992. <h5 data-i18n="config:chatgpt.basic.title"></h5>
  5993. <hr>
  5994. <div class="OJBetter_setting_list">
  5995. <label for='name'>
  5996. <span class="input_label" data-i18n="config:chatgpt.basic.name.label"></span>
  5997. </label>
  5998. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:chatgpt.basic.name.placeholder">
  5999. </div>
  6000. <div class="OJBetter_setting_list">
  6001. <label for='chatgpt_model'>
  6002. <div style="display: flex;align-items: center;">
  6003. <span class="input_label" data-i18n="[html]config:chatgpt.basic.model.label"></span>
  6004. <div class="help_tip">
  6005. ${helpCircleHTML}
  6006. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.model.tipText"></div>
  6007. </div>
  6008. </div>
  6009. </label>
  6010. <input type='text' id='chatgpt_model' placeholder='gpt-3.5-turbo' require = false>
  6011. </div>
  6012. <div class="OJBetter_setting_list">
  6013. <label for='chatgpt_key'>
  6014. <div style="display: flex;align-items: center;">
  6015. <span class="input_label" data-i18n="config:chatgpt.basic.key.label"></span>
  6016. <div class="help_tip">
  6017. ${helpCircleHTML}
  6018. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.key.tipText"></div>
  6019. </div>
  6020. </div>
  6021. </label>
  6022. <input type='text' id='chatgpt_key' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:chatgpt.basic.key.placeholder">
  6023. </div>
  6024. <div class="OJBetter_setting_list">
  6025. <label for='chatgpt_proxy'>
  6026. <div style="display: flex;align-items: center;">
  6027. <span class="input_label" data-i18n="config:chatgpt.basic.proxy.label">Proxy API:</span>
  6028. <div class="help_tip">
  6029. ${helpCircleHTML}
  6030. <div class="tip_text" data-i18n="[html]config:chatgpt.basic.proxy.tipText"></div>
  6031. </div>
  6032. </div>
  6033. </label>
  6034. <input type='text' id='chatgpt_proxy' placeholder='https://api.openai.com/v1/chat/completions' require = false>
  6035. </div>
  6036. <hr>
  6037. <details>
  6038. <summary data-i18n="config:common.advanced.title"></summary>
  6039. ${apiCustomConfigHTML('chatgpt')}
  6040. </details>
  6041. <details>
  6042. <summary data-i18n="config:common.quota.title"></summary>
  6043. ${apiQuotaConfigHTML('chatgpt')}
  6044. </details>
  6045. <button id='tempConfig_save' data-i18n="common:save"></button>
  6046. </div>
  6047. </dialog>
  6048. `;
  6049.  
  6050. const CompletConfigEditHTML = `
  6051. <dialog class='OJBetter_setting_menu' id='config_edit_menu'>
  6052. <div class='OJBetter_setting_content'>
  6053. <div class="tool-box">
  6054. <button class='ojb_btn ojb_btn_popover top btn-close'>
  6055. <i class="iconfont">&#xe614;</i>
  6056. </button>
  6057. </div>
  6058. <h4 data-i18n="config:complet.title"></h4>
  6059. <hr>
  6060. <div class="OJBetter_setting_list">
  6061. <label for='name'>
  6062. <span class="input_label" data-i18n="config:complet.name.label"></span>
  6063. </label>
  6064. <input type='text' id='name' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:complet.name.placeholder">
  6065. </div>
  6066. <div class='OJBetter_setting_list'>
  6067. <label for="complet_isChoose"><span id="loaded_span" data-i18n="config:complet.choose.label"></span></label>
  6068. <input type="checkbox" id="complet_isChoose" name="complet_isChoose" require = false>
  6069. </div>
  6070. <div class='OJBetter_setting_list'>
  6071. <label for="complet_genre" style="display: flex;" data-i18n="config:complet.genre.label"></label>
  6072. <div class="help_tip">
  6073. ${helpCircleHTML}
  6074. <div class="tip_text" data-i18n="[html]config:complet.genre.tipText"></div>
  6075. </div>
  6076. <select id="complet_genre" name="complet_genre">
  6077. <option value="monaco">monaco</option>
  6078. <option value="ace">ace</option>
  6079. </select>
  6080. </div>
  6081. <div class='OJBetter_setting_list'>
  6082. <label for="complet_language" style="display: flex;" data-i18n="config:complet.language.label"></label>
  6083. <select id="complet_language" name="complet_language">
  6084. <option value="cpp">cpp</option>
  6085. <option value="python">python</option>
  6086. <option value="java">java</option>
  6087. <option value="c">c</option>
  6088. </select>
  6089. </div>
  6090. <div class="OJBetter_setting_list">
  6091. <label for='complet_jsonUrl'>
  6092. <div style="display: flex;align-items: center;">
  6093. <span class="input_label">JSON URL:</span>
  6094. <div class="help_tip">
  6095. ${helpCircleHTML}
  6096. <div class="tip_text" data-i18n="[html]config:complet.jsonurl.tipText"></div>
  6097. </div>
  6098. </div>
  6099. </label>
  6100. <div class='OJBetter_setting_list alert_warn' data-i18n="[html]config:complet.jsonurl.alert"></div>
  6101. <input type='text' id='complet_jsonUrl' class='no_default' placeholder='' require = true data-i18n="[placeholder]config:complet.jsonurl.placeholder">
  6102. </div>
  6103. <button id='tempConfig_save' data-i18n="common:save"></button>
  6104. </div>
  6105. </dialog>
  6106. `;
  6107.  
  6108. /**
  6109. * 加载设置按钮面板
  6110. */
  6111. async function initSettingsPanel() {
  6112. /**
  6113. * 添加右上角设置按钮
  6114. * @param {string} location 位置选择器
  6115. * @param {string} method 插入方法
  6116. */
  6117. function insertOJBetterSettingButton(location, method) {
  6118. $(location)[method](`<button class='ojb_btn OJBetter_setting'>
  6119. ${OJBetter.state.name} ${i18next.t('settings', { ns: 'common' })}</button>`);
  6120. }
  6121.  
  6122. /**
  6123. * ============================================
  6124. * 该网站插入设置按钮的位置和方式
  6125. */
  6126. if (OJBetter.typeOfPage.isEnglishLanguage) {
  6127. insertOJBetterSettingButton("#navbar-collapse > ul:nth-child(2) > li:last-child", "after");
  6128. } else {
  6129. if ($('.header-mypage').length > 0) insertOJBetterSettingButton(".header-mypage", "after");
  6130. else insertOJBetterSettingButton("#navbar-collapse > ul:nth-child(2) > li:last-child", "after");
  6131. }
  6132. /**
  6133. * ============================================
  6134. */
  6135.  
  6136. const $settingBtns = $(".OJBetter_setting");
  6137. $settingBtns.click(() => {
  6138. $settingBtns.prop("disabled", true).addClass("open");
  6139.  
  6140. // 设置面板div
  6141. const settingMenu = OJB_safeCreateJQElement(OJBetterSettingMenu_HTML);
  6142. $("body").append(settingMenu);
  6143.  
  6144. elementLocalize(settingMenu); // 加载i18n
  6145. OJB_showModal(settingMenu);
  6146. OJB_addDraggable($('#OJBetter_setting_menu')); // 窗口支持拖拽
  6147.  
  6148. // help帮助悬浮窗位置更新
  6149. $(document).on('mouseenter', '.help-icon', function (event) {
  6150. var menuOffset = $('.OJBetter_setting_menu:last').offset();
  6151. var mouseX = event.pageX - menuOffset.left;
  6152. var mouseY = event.pageY - menuOffset.top;
  6153.  
  6154. $('.tip_text').css({
  6155. 'top': mouseY + 'px',
  6156. 'left': mouseX + 'px'
  6157. });
  6158. });
  6159.  
  6160. // 选项卡切换
  6161. $('.OJBetter_setting_sidebar a').click(function (event) {
  6162. event.preventDefault();
  6163. $('.OJBetter_setting_sidebar a').removeClass('active');
  6164. $('.settings-page').removeClass('active');
  6165. $(this).addClass('active');
  6166. const targetPageId = $(this).attr('href').substring(1);
  6167. $('#' + targetPageId).addClass('active');
  6168. });
  6169.  
  6170. /**
  6171. * 更新单选按钮组的可用状态
  6172. * @param {string} selector 单选按钮组的选择器
  6173. * @param {string} targetLanguage 目标语言
  6174. * @param {Object} translationSupport 翻译支持的语言对应表
  6175. */
  6176. const updateRadioButtonsAvailability = (selector, targetLanguage) => {
  6177. Object.entries(OJBetter.supportList.translationSupport).forEach(([service, languages]) => {
  6178. const radioButton = $(selector).find(`input[value="${service}"]`);
  6179. const isEnabled = languages[targetLanguage];
  6180. $(radioButton).prop('disabled', !isEnabled);
  6181. if (!isEnabled) {
  6182. $(radioButton).prop('checked', false);
  6183. }
  6184. });
  6185. };
  6186.  
  6187. /**
  6188. * 检查下拉框选中项是否有效,若无效则清空
  6189. * @param {string} selector 下拉框的选择器
  6190. */
  6191. const validateSelectOption = (selector) => {
  6192. const selectedValue = $(selector).val();
  6193. if (!selectedValue) {
  6194. $(selector).val('');
  6195. }
  6196. };
  6197.  
  6198. /**
  6199. * 更新翻译目标语言下拉框的可用状态
  6200. * @param {string} selector 下拉框的选择器
  6201. * @param {string} targetLanguage 目标语言
  6202. * @param {Object} translationSupport 翻译支持的语言对应表
  6203. */
  6204. const updateSelectOptionsAvailability = (selector, targetLanguage) => {
  6205. $(selector).children('option').each(function () {
  6206. const optionValue = $(this).val();
  6207. const isEnabled = OJBetter.supportList.translationSupport[optionValue] ? OJBetter.supportList.translationSupport[optionValue][targetLanguage] : true;
  6208. $(this).prop('disabled', !isEnabled);
  6209. });
  6210. validateSelectOption(selector);
  6211. };
  6212.  
  6213. /**
  6214. * 更新翻译服务复选框的可用状态
  6215. * @param {string} selector 复选框的选择器
  6216. * @param {string} targetLanguage 目标语言
  6217. * @param {Object} translationSupport 翻译支持的语言对应表
  6218. */
  6219. const updateCheckboxesAvailability = (selector, targetLanguage) => {
  6220. $(selector).children('input').each(function () {
  6221. const checkboxValue = $(this).val();
  6222. const isEnabled = OJBetter.supportList.translationSupport[checkboxValue][targetLanguage];
  6223. $(this).prop('disabled', !isEnabled);
  6224. if (!isEnabled) {
  6225. $(this).prop('checked', false);
  6226. }
  6227. });
  6228. };
  6229.  
  6230. /**
  6231. * 更新更新源下拉框的可用状态
  6232. * @param {string} selector 下拉框的选择器
  6233. * @param {string} targetLanguage 目标语言
  6234. * @param {Object} translationSupport 翻译支持的语言对应表
  6235. */
  6236. const updateUpdateSourceSelectOptionsAvailability = (selector, updateChannel) => {
  6237. $(selector).children('option').each(function () {
  6238. const optionValue = $(this).val();
  6239. const isEnabled = OJBetter.supportList.updateSourceSupportList[optionValue][updateChannel];
  6240. $(this).prop('disabled', !isEnabled);
  6241. });
  6242. validateSelectOption(selector);
  6243. };
  6244.  
  6245. /**
  6246. * 创建配置结构
  6247. * @param {string} type - 该字段的在表单中的类型
  6248. * @param {string} value - 在配置中的键值
  6249. * @param {boolean} require - 是否是表单的必填项
  6250. * @param {string} [check=""] check - 调用的合法性检查
  6251. */
  6252. function createStructure(type, value, require, check = "") {
  6253. return { type, value, require, check };
  6254. }
  6255.  
  6256. // deepl配置
  6257. const deeplStructure = {
  6258. '#name': createStructure('text', 'name', true),
  6259. '#deepl_apiGenre': createStructure('text', 'apiGenre', true),
  6260. '#deepl_key': createStructure('text', 'key', false),
  6261. '#deepl_proxy': createStructure('text', 'proxy', false),
  6262. '#deepl_header': createStructure('text', '_header', false, 'keyValuePairs'),
  6263. '#deepl_data': createStructure('text', '_data', false, 'keyValuePairs'),
  6264. '#deepl_quota_url': createStructure('text', 'quota_url', false),
  6265. '#deepl_quota_method': createStructure('text', 'quota_method', false),
  6266. '#deepl_quota_header': createStructure('text', 'quota_header', false, 'keyValuePairs'),
  6267. '#deepl_quota_data': createStructure('text', 'quota_data', false, 'keyValuePairs'),
  6268. '#deepl_quota_surplus': createStructure('text', 'quota_surplus', false, 'dotSeparatedPath'),
  6269. };
  6270. let tempConfig_deepl = GM_getValue('deepl_config'); // 获取配置信息
  6271. const configManager_deepl = new ConfigManager('#deepl_config', 'deepl_config_', tempConfig_deepl, deeplStructure, deeplConfigEditHTML);
  6272. configManager_deepl.registerChoiceChange();
  6273.  
  6274. // chatgpt配置
  6275. const chatgptStructure = {
  6276. '#name': createStructure('text', 'name', true),
  6277. '#chatgpt_model': createStructure('text', 'model', false),
  6278. '#chatgpt_key': createStructure('text', 'key', true),
  6279. '#chatgpt_proxy': createStructure('text', 'proxy', false),
  6280. '#chatgpt_header': createStructure('text', '_header', false, 'keyValuePairs'),
  6281. '#chatgpt_data': createStructure('text', '_data', false, 'keyValuePairs'),
  6282. '#chatgpt_quota_url': createStructure('text', 'quota_url', false),
  6283. '#chatgpt_quota_header': createStructure('text', 'quota_header', false, 'keyValuePairs'),
  6284. '#chatgpt_quota_data': createStructure('text', 'quota_data', false, 'keyValuePairs'),
  6285. '#chatgpt_quota_surplus': createStructure('text', 'quota_surplus', false, 'dotSeparatedPath'),
  6286. '#chatgpt_quota_method': createStructure('text', 'quota_method', false),
  6287. };
  6288. let tempConfig_chatgpt = GM_getValue('chatgpt_config'); // 获取配置信息
  6289. const configManager_chatgpt = new ConfigManager('#chatgpt_config', 'chatgpt_config_', tempConfig_chatgpt, chatgptStructure, chatgptConfigEditHTML);
  6290. configManager_chatgpt.registerChoiceChange();
  6291.  
  6292. // Complet配置
  6293. const CompletStructure = {
  6294. '#name': createStructure('text', 'name', true),
  6295. '#complet_isChoose': createStructure('checkbox', 'isChoose', true),
  6296. '#complet_genre': createStructure('text', 'genre', true),
  6297. '#complet_language': createStructure('text', 'language', true),
  6298. '#complet_jsonUrl': createStructure('text', 'jsonUrl', true)
  6299. };
  6300. let tempConfig_Complet = GM_getValue('Complet_config'); // 获取配置信息
  6301. const configManager_complet = new ConfigManager('#Complet_config', 'complet_config_', tempConfig_Complet, CompletStructure, CompletConfigEditHTML, false);
  6302.  
  6303. // 状态更新
  6304. $("input[name='darkMode'][value='" + OJBetter.basic.darkMode + "']").prop("checked", true);
  6305. $("#showLoading").prop("checked", GM_getValue("showLoading") === true);
  6306. $("#expandFoldingblocks").prop("checked", GM_getValue("expandFoldingblocks") === true);
  6307. $("#renderPerfOpt").prop("checked", GM_getValue("renderPerfOpt") === true);
  6308. $("#commentPaging").prop("checked", GM_getValue("commentPaging") === true);
  6309. $("#standingsRecolor").prop("checked", GM_getValue("standingsRecolor") === true);
  6310. $("#showJumpToLuogu").prop("checked", GM_getValue("showJumpToLuogu") === true);
  6311. $("#showCF2vjudge").prop("checked", GM_getValue("showCF2vjudge") === true);
  6312. $("#hoverTargetAreaDisplay").prop("checked", GM_getValue("hoverTargetAreaDisplay") === true);
  6313. $("#showClistRating_contest").prop("checked", GM_getValue("showClistRating_contest") === true);
  6314. $("#showClistRating_problemset").prop("checked", GM_getValue("showClistRating_problemset") === true);
  6315. $("#showClistRating_problem").prop("checked", GM_getValue("showClistRating_problem") === true);
  6316. $("#RatingHidden").prop("checked", GM_getValue("RatingHidden") === true);
  6317. $('#scriptL10nLanguage').val(GM_getValue("scriptL10nLanguage"));
  6318. $('#localizationLanguage').val(GM_getValue("localizationLanguage"));
  6319. $("input[name='translation'][value='" + OJBetter.translation.choice + "']").prop("checked", true);
  6320. $("input[name='translation']").css("color", "gray");
  6321. $('#deepl_type').val(GM_getValue("deepl_type"));
  6322. $("#deepl_config_config_bar_ul").find(`input[name='deepl_config_config_item'][value='${tempConfig_deepl.choice}']`).prop("checked", true);
  6323. $('#enableEmphasisProtection').prop("checked", GM_getValue("enableEmphasisProtection") === true);
  6324. $('#enableLinkProtection').prop("checked", GM_getValue("enableLinkProtection") === true);
  6325. $("#chatgpt_config_config_bar_ul").find(`input[name='chatgpt_config_config_item'][value='${tempConfig_chatgpt.choice}']`).prop("checked", true);
  6326. $("#openai_isStream").prop("checked", GM_getValue("openai_isStream") === true);
  6327. $('#comment_translation_choice').val(GM_getValue("commentTranslationChoice"));
  6328. $('#iconButtonSize').val(GM_getValue("iconButtonSize"));
  6329. $("#autoTranslation").prop("checked", GM_getValue("autoTranslation") === true);
  6330. $('#shortTextLength').val(GM_getValue("shortTextLength"));
  6331. $("#allowMixTrans").prop("checked", GM_getValue("allowMixTrans") === true);
  6332. $('.OJBetter_checkboxs').find('input[type="checkbox"][name="mixedTranslation"]').each(function () {
  6333. if (OJBetter.translation.auto.mixTrans.servers.indexOf($(this).val()) > -1) {
  6334. $(this).prop('checked', true);
  6335. }
  6336. });
  6337. // 翻译目标语言下拉框
  6338. $('#transTargetLang').change(function () {
  6339. var selectedLang = $(this).val();
  6340. updateRadioButtonsAvailability('#translationServices', selectedLang);
  6341. updateSelectOptionsAvailability('#comment_translation_choice', selectedLang);
  6342. updateCheckboxesAvailability('.OJBetter_checkboxs', selectedLang);
  6343. });
  6344. $('#transTargetLang').val(GM_getValue("transTargetLang"));
  6345. $('#transTargetLang').change();
  6346. //
  6347. $('#comment_translation_mode').val(GM_getValue("commentTranslationMode"));
  6348. $("#memoryTranslateHistory").prop("checked", GM_getValue("memoryTranslateHistory") === true);
  6349. $('#transWaitTime').val(GM_getValue("transWaitTime"));
  6350. $('#translation_replaceSymbol').val(GM_getValue("replaceSymbol"));
  6351. $("#filterTextWithoutEmphasis").prop("checked", GM_getValue("filterTextWithoutEmphasis") === true);
  6352. $('#translation_retransAction').val(GM_getValue("retransAction"));
  6353. $("#clist_Authorization").val(GM_getValue("clist_Authorization"));
  6354. $("#problemPageCodeEditor").prop("checked", GM_getValue("problemPageCodeEditor") === true);
  6355. $("#beautifyPreBlocks").prop("checked", GM_getValue("beautifyPreBlocks") === true);
  6356. $("#isCodeSubmitConfirm").prop("checked", GM_getValue("isCodeSubmitConfirm") === true);
  6357. $("#alwaysConsumeMouseWheel").prop("checked", GM_getValue("alwaysConsumeMouseWheel") === true);
  6358. $("#submitButtonPosition").val(GM_getValue("submitButtonPosition"));
  6359. $("#cppCodeTemplateComplete").prop("checked", GM_getValue("cppCodeTemplateComplete") === true);
  6360. $("#useLSP").prop("checked", GM_getValue("useLSP") === true);
  6361. $("#OJBetter_Bridge_WorkUri").val(GM_getValue("OJBetter_Bridge_WorkUri"));
  6362. $("#OJBetter_Bridge_SocketUrl").val(GM_getValue("OJBetter_Bridge_SocketUrl"));
  6363. $("input[name='compiler'][value='" + OJBetter.monaco.onlineCompilerChoice + "']").prop("checked", true);
  6364. $("input[name='compiler']").css("color", "gray");
  6365. // 调试
  6366. $("#notWaiteLoaded").prop("checked", GM_getValue("notWaiteLoaded") === true);
  6367. $('#l10n_refreshScrpitCacheButton').click(clearI18nextCache);
  6368. $('#indexedDB_clearButton').click(async () => { await clearDatabase(); });
  6369. $('#indexedDB_exportButton').click(async () => { downloadDataAsFile(await exportDatabase(), 'database_export.json') });
  6370. $('#indexedDB_importButton').click(() => { readFileInput(async (fileContent) => { await importDatabase(fileContent); }); });
  6371. $('#configuration_clearButton').click(deleteAllConfigSettings);
  6372. $('#configuration_exportButton').click(() => { downloadDataAsFile(exportSettingsToJSON(), 'configuration_export.json') });
  6373. $('#configuration_importButton').click(() => { readFileInput((fileContent) => { importSettingsFromJSON(fileContent); }) });
  6374. // 关于
  6375. $('#updateChannel').val(GM_getValue("updateChannel"));
  6376. $('#updateSource').val(GM_getValue("updateSource"));
  6377. $('#updateChannel').change(function () {
  6378. var selectedLang = $(this).val();
  6379. updateUpdateSourceSelectOptionsAvailability('#updateSource', selectedLang);
  6380. if (selectedLang == "dev") $('#thanksforDevChannelNotice').show();
  6381. else $('#thanksforDevChannelNotice').hide();
  6382. });
  6383. $('#updateChannel').change();
  6384.  
  6385. // 关闭
  6386. const $settingMenu = $(".OJBetter_setting_menu");
  6387. $settingMenu.on("click", ".btn-close", async () => {
  6388. // 设置的数据
  6389. const settings = {
  6390. darkMode: $("input[name='darkMode']:checked").val(),
  6391. showLoading: $("#showLoading").prop("checked"),
  6392. hoverTargetAreaDisplay: $("#hoverTargetAreaDisplay").prop("checked"),
  6393. expandFoldingblocks: $("#expandFoldingblocks").prop("checked"),
  6394. renderPerfOpt: $("#renderPerfOpt").prop("checked"),
  6395. commentPaging: $("#commentPaging").prop("checked"),
  6396. standingsRecolor: $("#standingsRecolor").prop("checked"),
  6397. showJumpToLuogu: $("#showJumpToLuogu").prop("checked"),
  6398. showCF2vjudge: $("#showCF2vjudge").prop("checked"),
  6399. scriptL10nLanguage: $('#scriptL10nLanguage').val(),
  6400. localizationLanguage: $('#localizationLanguage').val(),
  6401. transTargetLang: $('#transTargetLang').val(),
  6402. translation: $("input[name='translation']:checked").val(),
  6403. deepl_type: $('#deepl_type').val(),
  6404. enableEmphasisProtection: $("#enableEmphasisProtection").prop("checked"),
  6405. enableLinkProtection: $("#enableLinkProtection").prop("checked"),
  6406. openai_isStream: $("#openai_isStream").prop("checked"),
  6407. commentTranslationChoice: $('#comment_translation_choice').val(),
  6408. iconButtonSize: $('#iconButtonSize').val(),
  6409. autoTranslation: $("#autoTranslation").prop("checked"),
  6410. shortTextLength: $('#shortTextLength').val(),
  6411. allowMixTrans: $("#allowMixTrans").prop("checked"),
  6412. mixedTranslation: (() => {
  6413. let mixedTranslation = [];
  6414. $('.OJBetter_checkboxs').find('input[type="checkbox"][name="mixedTranslation"]').each(function () {
  6415. if ($(this).is(":checked")) {
  6416. mixedTranslation.push($(this).val());
  6417. }
  6418. });
  6419. return mixedTranslation;
  6420. })(),
  6421. commentTranslationMode: $('#comment_translation_mode').val(),
  6422. memoryTranslateHistory: $('#memoryTranslateHistory').prop("checked"),
  6423. transWaitTime: $('#transWaitTime').val(),
  6424. replaceSymbol: $('#translation_replaceSymbol').val(),
  6425. filterTextWithoutEmphasis: $('#filterTextWithoutEmphasis').prop("checked"),
  6426. retransAction: $('#translation_retransAction').val(),
  6427. showClistRating_contest: $('#showClistRating_contest').prop("checked"),
  6428. showClistRating_problemset: $('#showClistRating_problemset').prop("checked"),
  6429. showClistRating_problem: $('#showClistRating_problem').prop("checked"),
  6430. RatingHidden: $('#RatingHidden').prop("checked"),
  6431. clist_Authorization: $('#clist_Authorization').val(),
  6432. problemPageCodeEditor: $("#problemPageCodeEditor").prop("checked"),
  6433. beautifyPreBlocks: $("#beautifyPreBlocks").prop("checked"),
  6434. isCodeSubmitConfirm: $("#isCodeSubmitConfirm").prop("checked"),
  6435. alwaysConsumeMouseWheel: $("#alwaysConsumeMouseWheel").prop("checked"),
  6436. submitButtonPosition: $('#submitButtonPosition').val(),
  6437. cppCodeTemplateComplete: $("#cppCodeTemplateComplete").prop("checked"),
  6438. useLSP: $("#useLSP").prop("checked"),
  6439. OJBetter_Bridge_WorkUri: $('#OJBetter_Bridge_WorkUri').val().replace(/\\/g, '/').replace(/\/$/, ''),
  6440. OJBetter_Bridge_SocketUrl: $('#OJBetter_Bridge_SocketUrl').val(),
  6441. onlineCompilerChoice: $("input[name='compiler']:checked").val(),
  6442. notWaiteLoaded: $("#notWaiteLoaded").prop("checked"),
  6443. updateChannel: $('#updateChannel').val(),
  6444. updateSource: $('#updateSource').val()
  6445. };
  6446. // tempConfigs的数据
  6447. const tempConfigs = {
  6448. 'deepl_config': configManager_deepl.getTempConfig(),
  6449. 'chatgpt_config': configManager_chatgpt.getTempConfig(),
  6450. 'Complet_config': configManager_complet.getTempConfig()
  6451. }
  6452.  
  6453. // 判断是否改变
  6454. let changes = {};
  6455. const combinedConfigs = Object.assign({}, settings, tempConfigs); // 合并settings和tempConfigs对象
  6456. for (const [key, value] of Object.entries(combinedConfigs)) {
  6457. const storedValue = GM_getValue(key);
  6458. if (!OJB_deepEquals(value, storedValue)) {
  6459. changes[key] = { oldValue: storedValue, newValue: value };
  6460. }
  6461. }
  6462.  
  6463. // 如果changes对象不为空,则有变化
  6464. if (Object.keys(changes).length > 0) {
  6465. console.log("Changes detected:", changes);
  6466. const shouldSave = await OJB_createDialog(
  6467. i18next.t('saveSetting.title', { ns: 'dialog' }),
  6468. i18next.t('saveSetting.content', { ns: 'dialog' }),
  6469. [
  6470. i18next.t('saveSetting.buttons.0', { ns: 'dialog' }),
  6471. i18next.t('saveSetting.buttons.1', { ns: 'dialog' })
  6472. ]
  6473. ); // 配置改变保存确认
  6474. if (shouldSave) {
  6475. // 数据校验
  6476. // TODO
  6477. if (settings.deepl_type !== 'free') {
  6478. let selectedIndex = $('input[name="deepl_config_config_item"]:checked').length > 0;
  6479. if (!selectedIndex) {
  6480. $('.deepl_config a').removeClass('active');
  6481. $('.settings-page').removeClass('active');
  6482. $('#sidebar-translation-settings').addClass('active');
  6483. $('#translation-settings').addClass('active');
  6484.  
  6485. $('#deepl_config').addClass('missing');
  6486. return;
  6487. } else {
  6488. $('#deepl_config').removeClass('missing');
  6489. }
  6490. }
  6491. if (settings.translation === "openai") {
  6492. let selectedIndex = $('input[name="chatgpt_config_config_item"]:checked').length > 0;
  6493. if (!selectedIndex) {
  6494. $('.chatgpt_config a').removeClass('active');
  6495. $('.settings-page').removeClass('active');
  6496. $('#sidebar-translation-settings').addClass('active');
  6497. $('#translation-settings').addClass('active');
  6498.  
  6499. $('#chatgpt_config').addClass('missing');
  6500. return;
  6501. } else {
  6502. $('#chatgpt_config').removeClass('missing');
  6503. }
  6504. }
  6505. {
  6506. let selectedIndex = $('input[name="translation"]:checked').length > 0;
  6507. if (!selectedIndex) {
  6508. $('.OJBetter_setting_sidebar a').removeClass('active');
  6509. $('.settings-page').removeClass('active');
  6510. $('#sidebar-translation-settings').addClass('active');
  6511. $('#translation-settings').addClass('active');
  6512.  
  6513. $('#translationServices').addClass('missing');
  6514. return;
  6515. } else {
  6516. $('#translationServices').removeClass('missing');
  6517. }
  6518. }
  6519.  
  6520. // 保存数据
  6521. let refreshPage = false; // 是否需要刷新页面
  6522. for (const [key, value] of Object.entries(settings)) {
  6523. if (!refreshPage && !(key == 'translation' || key == 'darkMode' || key == 'commentTranslationChoice')) {
  6524. if (GM_getValue(key) != value) refreshPage = true;
  6525. }
  6526. GM_setValue(key, value);
  6527. }
  6528. for (const [key, value] of Object.entries(tempConfigs)) {
  6529. if (!refreshPage && (JSON.stringify(GM_getValue(key)) != JSON.stringify(value))) refreshPage = true;
  6530. GM_setValue(key, value);
  6531. }
  6532.  
  6533. if (refreshPage) location.reload();
  6534. else {
  6535. // 切换黑暗模式
  6536. if (OJBetter.basic.darkMode != settings.darkMode) {
  6537. OJBetter.basic.darkMode = settings.darkMode;
  6538. // 移除旧的事件监听器
  6539. changeEventListeners.forEach(listener => {
  6540. mediaQueryList.removeEventListener('change', listener);
  6541. });
  6542.  
  6543. if (OJBetter.basic.darkMode == "follow") {
  6544. changeEventListeners.push(handleColorSchemeChange);
  6545. mediaQueryList.addEventListener('change', handleColorSchemeChange);
  6546. $('html').removeAttr('data-theme');
  6547. } else if (OJBetter.basic.darkMode == "dark") {
  6548. $('html').attr('data-theme', 'dark');
  6549. if (OJBetter.monaco.editor) {
  6550. monaco.editor.setTheme('vs-dark');
  6551. }
  6552. } else {
  6553. $('html').attr('data-theme', 'light');
  6554. if (OJBetter.monaco.editor) {
  6555. monaco.editor.setTheme('vs');
  6556. }
  6557. // 移除旧的事件监听器
  6558. const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
  6559. window.matchMedia('(prefers-color-scheme: dark)');
  6560. }
  6561. }
  6562. // 更新配置信息
  6563. OJBetter.translation.choice = settings.translation;
  6564. OJBetter.translation.comment.choice = settings.commentTranslationChoice;
  6565. }
  6566. }
  6567. }
  6568. OJB_closeAndRemoveModal(settingMenu);
  6569. $settingBtns.prop("disabled", false).removeClass("open");
  6570. });
  6571. });
  6572. };
  6573.  
  6574. // html2markdown转换/处理规则
  6575. const turndownService = new TurndownService({ bulletListMarker: '-' });
  6576.  
  6577. // 保留原始
  6578. turndownService.keep(['del']);
  6579.  
  6580. turndownService.addRule('removeByClass', {
  6581. filter: function (node) {
  6582. return node.classList.contains('html2md-panel') ||
  6583. node.classList.contains('div-btn-copy') ||
  6584. node.classList.contains('btn-copy') ||
  6585. node.classList.contains('overlay')
  6586. },
  6587. replacement: function () {
  6588. return '';
  6589. }
  6590. });
  6591.  
  6592. // inline math
  6593. turndownService.addRule('inline-math', {
  6594. filter: function (node, options) {
  6595. return node.tagName.toLowerCase() == "span" && node.className == "katex";
  6596. },
  6597. replacement: function (content, node) {
  6598. var latex = $(node).find('annotation').text();
  6599. latex = latex.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  6600. return "$" + latex + "$";
  6601. }
  6602. });
  6603.  
  6604. // block math
  6605. turndownService.addRule('block-math', {
  6606. filter: function (node, options) {
  6607. return node.tagName.toLowerCase() == "span" && node.className == "katex-display";
  6608. },
  6609. replacement: function (content, node) {
  6610. var latex = $(node).find('annotation').text();
  6611. latex = latex.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  6612. return "\n$$\n" + latex + "\n$$\n";
  6613. }
  6614. });
  6615.  
  6616. // pre
  6617. turndownService.addRule('pre', {
  6618. filter: function (node, options) {
  6619. return node.tagName.toLowerCase() == "pre";
  6620. },
  6621. replacement: function (content, node) {
  6622. return "```\n" + content + "```\n";
  6623. }
  6624. });
  6625.  
  6626. // bordertable
  6627. turndownService.addRule('bordertable', {
  6628. filter: 'table',
  6629. replacement: function (content, node) {
  6630. if (node.classList.contains('table')) {
  6631. var output = [],
  6632. thead = '',
  6633. trs = node.querySelectorAll('tr');
  6634. if (trs.length > 0) {
  6635. var ths = trs[0].querySelectorAll('th, td');
  6636. if (ths.length > 0) {
  6637. thead = '| ' + Array.from(ths).map(th => turndownService.turndown(th.innerHTML.trim())).join(' | ') + ' |\n'
  6638. thead += '| ' + Array.from(ths).map(() => ' --- ').join('|') + ' |\n';
  6639. }
  6640. }
  6641. var rows = node.querySelectorAll('tr');
  6642. Array.from(rows).forEach(function (row, i) {
  6643. if (i > 0) {
  6644. var cells = row.querySelectorAll('td,th');
  6645. var trow = '| ' + Array.from(cells).map(cell => turndownService.turndown(cell.innerHTML.trim())).join(' | ') + ' |';
  6646. output.push(trow);
  6647. }
  6648. });
  6649. return thead + output.join('\n');
  6650. } else {
  6651. return content;
  6652. }
  6653. }
  6654. });
  6655.  
  6656. /**
  6657. * 任务队列
  6658. */
  6659. class TaskQueue {
  6660. constructor() {
  6661. this.taskQueues = {};
  6662. this.isProcessing = {}; // 处理状态
  6663. this.delays = {}; // 等待时间(毫秒)
  6664. }
  6665.  
  6666. getDelay(type) {
  6667. if (type === 'openai') {
  6668. return 0;
  6669. } else {
  6670. return OJBetter.translation.waitTime;
  6671. }
  6672. }
  6673.  
  6674. /**
  6675. * 添加任务
  6676. * @param {string} type 任务类型
  6677. * @param {function} fn 任务函数
  6678. * @param {boolean} isNonQueueTask 是否为非队列任务
  6679. */
  6680. addTask(type, fn, isNonQueueTask = false) {
  6681. if (!this.taskQueues[type]) {
  6682. this.taskQueues[type] = [];
  6683. }
  6684.  
  6685. if (isNonQueueTask) {
  6686. fn();
  6687. } else {
  6688. this.taskQueues[type].push(fn);
  6689.  
  6690. if (!this.isProcessing[type]) {
  6691. this.processQueue(type);
  6692. }
  6693. }
  6694. }
  6695.  
  6696. async processQueue(type) {
  6697. this.isProcessing[type] = true;
  6698.  
  6699. while (this.taskQueues[type].length > 0) {
  6700. const task = this.taskQueues[type].shift();
  6701. await task();
  6702.  
  6703. if (this.taskQueues[type].length > 0) {
  6704. await this.wait(this.getDelay(type));
  6705. }
  6706. }
  6707.  
  6708. this.isProcessing[type] = false;
  6709. }
  6710.  
  6711. wait(delay) {
  6712. return new Promise(resolve => {
  6713. setTimeout(resolve, delay);
  6714. });
  6715. }
  6716. }
  6717.  
  6718. /**
  6719. * 检测文本是否可能为代码片段
  6720. * @param {string} text 待检测的文本
  6721. * @returns {boolean} 是否可能为代码片段
  6722. */
  6723. function isLikelyCodeSnippet(text) {
  6724. // 过滤文本中可能的HTML标签
  6725. text = OJB_removeHTMLTags(text);
  6726.  
  6727. // 移除LaTeX公式部分
  6728. const cleanedText = text.replace(/(\$\$?[\s\S]*?\$\$?)/g, '');
  6729.  
  6730. // 代码的关键字
  6731. const keywords = [
  6732. 'int', 'float', 'return', 'if', 'else', 'while', 'for', 'switch', 'case', 'break', 'continue',
  6733. 'class', 'public', 'private', 'protected', 'void', 'static', 'const', 'enum', 'struct',
  6734. 'char', 'double', 'long', 'include', 'def', 'import', 'from', 'as', 'elif', 'try', 'except',
  6735. 'raise', 'with', 'lambda', 'print'
  6736. ];
  6737. // 代码的特殊字符
  6738. const codeChars = [';', '{', '}', '>', '<', '<<', '>>', '=', '+', '-',
  6739. '&', '|', ':', '\'\'\'', '\"\"\"', '->'];
  6740.  
  6741. // 普通文本的标点符号
  6742. const textChars = ['.', ',', '?', '!', ':', '"', "'"];
  6743.  
  6744. // 关键字的数量
  6745. const keywordCount = keywords.reduce((count, keyword) => {
  6746. const regex = new RegExp("\\b" + keyword + "\\b", 'gi');
  6747. return count + (cleanedText.match(regex) || []).length;
  6748. }, 0);
  6749.  
  6750. // 代码的特殊字符的数量
  6751. const codeCharCount = codeChars.reduce((count, char) => {
  6752. const regex = new RegExp("\\" + char, 'g');
  6753. return count + (cleanedText.match(regex) || []).length;
  6754. }, 0);
  6755.  
  6756. // 普通文本字符的数量
  6757. const textCharCount = textChars.reduce((count, char) => {
  6758. const regex = new RegExp("\\" + char, 'g');
  6759. return count + (cleanedText.match(regex) || []).length;
  6760. }, 0);
  6761.  
  6762. // 如果代码关键字数量或者代码的特殊字符数量显著高于普通文本标点符号数量,或者存在Python缩进,则可能是代码
  6763. if (keywordCount > textCharCount * 2 || codeCharCount > textCharCount * 2) {
  6764. console.log("keywordCount:", keywordCount, "codeCharCount:", codeCharCount, "textCharCount:", textCharCount);
  6765. return true;
  6766. }
  6767.  
  6768. return false;
  6769. }
  6770.  
  6771. /**
  6772. * 加载按钮相关函数
  6773. */
  6774. async function initButtonFunc() {
  6775. // 鼠标悬浮时为目标元素区域添加一个覆盖层
  6776. $.fn.addHoverOverlay = function (target) {
  6777. let position = $(target).css('position');
  6778. let display = $(target).css('display');
  6779.  
  6780. this.hover(() => {
  6781. $(target)
  6782. .addClass('overlay')
  6783. .css('position', 'relative');
  6784. if (display == "inline" || display == "contents") {
  6785. $(target).css('display', 'block');
  6786. }
  6787. }, () => {
  6788. $(target)
  6789. .removeClass('overlay')
  6790. .css('position', position);
  6791. if (display == "inline" || display == "contents") {
  6792. $(target).css('display', display);
  6793. }
  6794. })
  6795. }
  6796.  
  6797. /**
  6798. * 为按钮设置图标
  6799. * @param {string} icon 图标
  6800. * @returns {JQuery<HTMLElement>} 按钮
  6801. */
  6802. $.fn.setButtonIcon = function (icon) {
  6803. let i = this.find("i");
  6804. if (i.length != 0 && i.hasClass("iconfont")) {
  6805. i.html(icon);
  6806. } else {
  6807. i = OJB_safeCreateJQElement(`<i>${icon}</i>`);
  6808. this.prepend(i);
  6809. }
  6810. return this;
  6811. }
  6812.  
  6813. /**
  6814. * 设置按钮为加载等待状态
  6815. */
  6816. $.fn.setButtonLoading = function () {
  6817. this.addClass("loading");
  6818. this.prop("disabled", true);
  6819. return this;
  6820. }
  6821.  
  6822. /**
  6823. * 解除按钮的加载等待状态
  6824. */
  6825. $.fn.setButtonLoaded = function () {
  6826. this.removeClass("loading");
  6827. this.prop("disabled", false);
  6828. return this;
  6829. }
  6830.  
  6831. /**
  6832. * 为按钮设置popover提示文本
  6833. * @param {string} text 文本
  6834. * @returns {JQuery<HTMLElement>} 按钮
  6835. */
  6836. $.fn.setButtonPopover = function (text) {
  6837. // find if has popover_content class element
  6838. let popover_content = this.find(".popover_content");
  6839. if (popover_content.length != 0) {
  6840. popover_content.text(text);
  6841. } else {
  6842. popover_content = OJB_safeCreateJQElement(`<span class="popover_content">${text}</span>`);
  6843. this.append(popover_content);
  6844. }
  6845. return this;
  6846. }
  6847.  
  6848. /**
  6849. * 获取MarkDown
  6850. * @returns {string} MarkDown
  6851. */
  6852. $.fn.getMarkdown = function () {
  6853. const markdown = this.data('markdown');
  6854. if (markdown === undefined) {
  6855. const htmlContent = this.html();
  6856. const newMarkdown = turndownService.turndown(htmlContent);
  6857. this.data('markdown', newMarkdown);
  6858. return newMarkdown;
  6859. }
  6860. return markdown;
  6861. }
  6862.  
  6863. // 设置按钮状态
  6864. $.fn.setButtonState = function (state, popoverText = null, disabled = false) {
  6865. this.data('buttonState', state)
  6866. .prop('disabled', disabled)
  6867. .css('cursor', disabled ? 'not-allowed' : 'pointer')
  6868. .removeClass('running success enabled error loading redo');
  6869. if (popoverText) this.setButtonPopover(popoverText);
  6870.  
  6871. if (state !== 'initial') this.addClass(state);
  6872. return this;
  6873. };
  6874.  
  6875. // 为按钮添加鼠标悬浮重试
  6876. $.fn.setHoverRedo = function () {
  6877. this.hover(() => {
  6878. prevState = this.getButtonState();
  6879. if (prevState !== "normal" && prevState !== "running") {
  6880. this.setButtonState('redo');
  6881. }
  6882. }, () => {
  6883. const currentState = this.getButtonState();
  6884. if (prevState !== "normal" && ["normal", "redo"].includes(currentState)) {
  6885. this.setButtonState(prevState);
  6886. prevState = null;
  6887. }
  6888. });
  6889. };
  6890.  
  6891. // 获取按钮状态
  6892. $.fn.getButtonState = function () {
  6893. return this.data('buttonState') || 'normal';
  6894. };
  6895.  
  6896. // 设置翻译按钮状态
  6897. $.fn.setTransButtonState = function (state, text = null) {
  6898. const popoverText = text || i18next.t(`trans.${state}`, { ns: 'button' });
  6899. const disabled = state === 'running' || state === 'loading';
  6900. this.setButtonState(state, popoverText, disabled);
  6901. return this;
  6902. };
  6903.  
  6904. // 存翻译结果
  6905. $.fn.pushResultToTransButton = function (result) {
  6906. let resultStack = this.data('resultStack');
  6907. if (!resultStack) resultStack = [];
  6908. resultStack.push(result);
  6909. this.data('resultStack', resultStack);
  6910. }
  6911.  
  6912. // 获取翻译结果
  6913. $.fn.getResultFromTransButton = function () {
  6914. return this.data('resultStack');
  6915. }
  6916.  
  6917. // 标记为不自动翻译
  6918. $.fn.setNotAutoTranslate = function () {
  6919. this.data('notAutoTranslate', true);
  6920. }
  6921.  
  6922. // 获取是否为不自动翻译
  6923. $.fn.getNotAutoTranslate = function () {
  6924. return this.data('notAutoTranslate');
  6925. }
  6926.  
  6927. // 判断是否已经翻译
  6928. $.fn.IsTranslated = function () {
  6929. if (this.hasAttr('translated')) {
  6930. return true;
  6931. } else {
  6932. return false;
  6933. }
  6934. }
  6935.  
  6936. // 判断是否为评论区按钮
  6937. $.fn.IsCommentButton = function () {
  6938. let isCommentButton = this.data('isCommentButton');
  6939. if (isCommentButton == undefined) {
  6940. this.parents('.comments').length > 0 ? isCommentButton = true : isCommentButton = false;
  6941. this.data('isCommentButton', isCommentButton);
  6942. }
  6943. return isCommentButton;
  6944. }
  6945.  
  6946. // 按钮点击效果
  6947. $(document).on('mousedown', '.ojb_btn', function () {
  6948. $(this).addClass('active').on('animationend', () => $(this).removeClass('active'));
  6949. });
  6950. }
  6951.  
  6952. /**
  6953. * 添加题目markdown转换/复制/翻译按钮面板
  6954. * @param {HTMLElement} element 需要添加按钮面板的元素
  6955. * @param {string} suffix 按钮面板id后缀
  6956. * @param {string} type 按钮面板添加位置
  6957. * @param {boolean} is_simple 是否是简单模式
  6958. * @returns {object} 返回按钮面板元素
  6959. */
  6960. function addButtonPanel(element, suffix, type, is_simple = false) {
  6961. let text;
  6962. if (OJBetter.translation.comment.transMode == "1") text = i18next.t('trans.segment', { ns: 'button' });
  6963. else if (OJBetter.translation.comment.transMode == "2") text = i18next.t('trans.select', { ns: 'button' });
  6964. else text = i18next.t('trans.normal', { ns: 'button' });
  6965.  
  6966. let panel = OJB_safeCreateJQElement(`<div class='html2md-panel input-output-copier ${is_simple ? 'is_simple' : ''}'></div>`);
  6967. let viewButton = OJB_safeCreateJQElement(`
  6968. <button class='ojb_btn ojb_btn_popover top' id='html2md-view${suffix}'>
  6969. <i class="iconfont">&#xe7e5;</i>
  6970. <span class="popover_content">${i18next.t('md.normal', { ns: 'button' })}</span>
  6971. </button>`);
  6972. let copyButton = OJB_safeCreateJQElement(`
  6973. <button class='ojb_btn ojb_btn_popover top' id='html2md-cb${suffix}'>
  6974. <i class="iconfont">&#xe608;</i>
  6975. <span class="popover_content">${i18next.t('copy.normal', { ns: 'button' })}</span>
  6976. </button>`);
  6977. let translateButton = OJB_safeCreateJQElement(`
  6978. <button class='ojb_btn translateButton ojb_btn_popover top' id='translateButton${suffix}'>
  6979. <i class="iconfont">&#xe6be;</i>
  6980. <span class="popover_content">${text}</span>
  6981. </button>`);
  6982. if (!is_simple) panel.append(viewButton);
  6983. if (!is_simple) panel.append(copyButton);
  6984. panel.append(translateButton);
  6985. if (type === "this_level") {
  6986. $(element).before(panel);
  6987. } else if (type === "child_level") {
  6988. $(element).prepend(panel);
  6989. }
  6990.  
  6991. return {
  6992. panel: panel,
  6993. viewButton: viewButton,
  6994. copyButton: copyButton,
  6995. translateButton: translateButton
  6996. }
  6997. }
  6998.  
  6999. /**
  7000. * 添加MD视图按钮
  7001. * @param {JQuery<HTMLElement>} button 按钮
  7002. * @param {JQuery<HTMLElement>} element 目标元素
  7003. * @param {string} suffix id后缀
  7004. * @param {string} type 类型
  7005. * @returns {void}
  7006. */
  7007. async function addButtonWithHTML2MD(button, element, suffix, type) {
  7008. /**
  7009. * 改变按钮状态
  7010. * @param {string} state 状态
  7011. */
  7012. function changeButtonState(state) {
  7013. if (state == "loading") {
  7014. button.setButtonLoading();
  7015. button.setButtonPopover(i18next.t('state.waitMathJax', { ns: 'button' }));
  7016. } else if (state == "loaded") {
  7017. button.setButtonLoaded();
  7018. button.setButtonPopover(i18next.t('md.normal', { ns: 'button' }));
  7019. } else if (state == "normal") {
  7020. button.removeClass("enabled");
  7021. button.setButtonPopover(i18next.t('md.normal', { ns: 'button' }));
  7022. } else if (state == "mdView") {
  7023. button.addClass("enabled");
  7024. button.setButtonPopover(i18next.t('md.reduction', { ns: 'button' }));
  7025. } else if (state == "disabled") {
  7026. button.prop("disabled", true);
  7027. button.setButtonPopover(i18next.t('md.disabled', { ns: 'button' }));
  7028. }
  7029. }
  7030.  
  7031. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7032. changeButtonState("disabled");
  7033. return;
  7034. } else {
  7035. changeButtonState("loading");
  7036. await waitForMathJaxIdle();
  7037. changeButtonState("loaded");
  7038. }
  7039.  
  7040. button.click(OJB_debounce(function () {
  7041. var target = $(element).get(0);
  7042.  
  7043. /**
  7044. * 检查是否是MarkDown视图
  7045. * @returns {boolean} 是否是MarkDown视图
  7046. */
  7047. function checkViewmd() {
  7048. if ($(element).attr("viewmd") === "true") {
  7049. return true;
  7050. } else {
  7051. return false;
  7052. }
  7053. }
  7054.  
  7055. /**
  7056. * 设置是否是MarkDown视图
  7057. * @param {boolean} value 是否是MarkDown视图
  7058. * @returns {void}
  7059. */
  7060. function setViewmd(value) {
  7061. $(element).attr("viewmd", value);
  7062. if (value) {
  7063. changeButtonState("mdView");
  7064. } else {
  7065. changeButtonState("normal");
  7066. }
  7067. }
  7068.  
  7069. if (checkViewmd()) {
  7070. setViewmd(false);
  7071. $(element).next(".mdViewContent").remove();
  7072. $(element).show();
  7073. } else {
  7074. setViewmd(true);
  7075. var markdown = $(element).getMarkdown();
  7076. var mdViewContent = OJB_safeCreateJQElement(`<span class="mdViewContent" style="width:auto; height:auto;">${markdown}</span>`);
  7077. $(element).after(mdViewContent);
  7078. $(element).hide();
  7079. }
  7080. }));
  7081.  
  7082. if (OJBetter.preference.hoverTargetAreaDisplay && !OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  7083. button.addHoverOverlay($(element));
  7084. }
  7085. }
  7086.  
  7087. /**
  7088. * 添加复制按钮
  7089. * @param {JQuery<HTMLElement>} button 按钮
  7090. * @param {JQuery<HTMLElement>} element 目标元素
  7091. * @param {string} suffix 后缀
  7092. * @param {string} type 类型
  7093. */
  7094. async function addButtonWithCopy(button, element, suffix, type) {
  7095. /**
  7096. * 改变按钮状态
  7097. * @param {string} state 状态
  7098. */
  7099. function changeButtonState(state) {
  7100. if (state == "loading") {
  7101. button.setButtonLoading();
  7102. button.setButtonPopover(i18next.t('state.waitMathJax', { ns: 'button' }));
  7103. } else if (state == "loaded") {
  7104. button.setButtonLoaded();
  7105. button.setButtonPopover(i18next.t('copy.normal', { ns: 'button' }));
  7106. } else if (state == "normal") {
  7107. button.setButtonPopover(i18next.t('copy.normal', { ns: 'button' }));
  7108. } else if (state == "copied") {
  7109. button.setButtonPopover(i18next.t('copy.copied', { ns: 'button' }));
  7110. } else if (state == "disabled") {
  7111. button.prop("disabled", true);
  7112. button.setButtonPopover(i18next.t('copy.disabled', { ns: 'button' }));
  7113. }
  7114. }
  7115.  
  7116. // 等待MathJax队列完成
  7117. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7118. changeButtonState("disabled");
  7119. return;
  7120. } else {
  7121. changeButtonState("loading");
  7122. await waitForMathJaxIdle();
  7123. changeButtonState("loaded");
  7124. }
  7125.  
  7126. button.click(OJB_debounce(function () {
  7127. var target = $(element).get(0);
  7128.  
  7129. var markdown = $(element).getMarkdown();
  7130.  
  7131. GM_setClipboard(markdown);
  7132.  
  7133. $(this).addClass("success");
  7134. changeButtonState("copied");
  7135.  
  7136.  
  7137. // 更新复制按钮文本
  7138. setTimeout(() => {
  7139. $(this).removeClass("success");
  7140. changeButtonState("normal")
  7141. }, 2000);
  7142. }));
  7143.  
  7144. if (OJBetter.preference.hoverTargetAreaDisplay && !OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  7145. button.addHoverOverlay($(element));
  7146. }
  7147. }
  7148.  
  7149. /**
  7150. * 添加翻译按钮
  7151. * @param {JQuery<HTMLElement>} button 按钮
  7152. * @param {JQuery<HTMLElement>} element 目标元素
  7153. * @param {string} suffix 后缀
  7154. * @param {string} type 类型
  7155. * @param {boolean} is_comment 是否是评论
  7156. */
  7157. async function addButtonWithTranslation(button, element, suffix, type, is_comment = false) {
  7158. // 添加可指定翻译服务的方法调用
  7159. button.data("translatedItBy", function (translation) {
  7160. button.setTransButtonState('running', i18next.t('trans.wait', { ns: 'button' }));
  7161. OJBetter.common.taskQueue.addTask(translation, () => transTask(button, element, type, is_comment, translation), translation == 'openai');
  7162. });
  7163.  
  7164. // 等待MathJax队列完成
  7165. button.setButtonLoading();
  7166. await waitForMathJaxIdle();
  7167. button.setButtonLoaded();
  7168.  
  7169. // 标记目标文本区域不自动翻译
  7170. {
  7171. let text;
  7172. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7173. text = $(element).html();
  7174. } else {
  7175. text = $(element).getMarkdown();
  7176. }
  7177. let length = text.length;
  7178. if (length > OJBetter.translation.auto.shortTextLength || isLikelyCodeSnippet(text)) {
  7179. button.setNotAutoTranslate();
  7180. }
  7181. // button.after(`<span>${length}</span>`); // 显示字符数
  7182. }
  7183.  
  7184. button.click(OJB_debounce(async function () {
  7185. // 重新翻译
  7186. let resultStack = $(this).getResultFromTransButton();
  7187. if (resultStack) {
  7188. let pElements = $(element).find("p.block_selected:not(li p), li.block_selected");
  7189. for (let item of resultStack) {
  7190. if (OJBetter.translation.retransAction == "0") {
  7191. // 选段翻译不直接移除旧结果
  7192. if (OJBetter.translation.comment.transMode == "2") {
  7193. // 只移除即将要翻译的段的结果
  7194. if (pElements.is(item.translateDiv.getDiv().prev())) {
  7195. item.translateDiv.close();
  7196. }
  7197. } else {
  7198. item.translateDiv.close();
  7199. $($(element)).find(".translate-problem-statement, .translate-problem-statement-panel").remove();
  7200. }
  7201. } else {
  7202. item.translateDiv.foldMainDiv();
  7203. }
  7204. }
  7205. }
  7206.  
  7207. // 翻译
  7208. button.setTransButtonState('running', i18next.t('trans.wait', { ns: 'button' }));
  7209. OJBetter.common.taskQueue.addTask(OJBetter.translation.choice, () => transTask(button, element, type, is_comment), OJBetter.translation.choice == 'openai');
  7210. }));
  7211.  
  7212. // 重新翻译提示
  7213. let prevState;
  7214. button.hover(() => {
  7215. prevState = button.getButtonState();
  7216. if (prevState !== "normal" && prevState !== "running") {
  7217. button.setTransButtonState('redo');
  7218. }
  7219. }, () => {
  7220. const currentState = button.getButtonState();
  7221. if (prevState !== "normal" && ["normal", "redo"].includes(currentState)) {
  7222. button.setTransButtonState(prevState);
  7223. prevState = null;
  7224. }
  7225. });
  7226.  
  7227. // 目标区域指示
  7228. if (OJBetter.preference.hoverTargetAreaDisplay) {
  7229. button.addHoverOverlay($(element));
  7230. }
  7231.  
  7232. // 翻译右键切换菜单
  7233. $(document).on('contextmenu', '#translateButton' + suffix, function (e) {
  7234. e.preventDefault();
  7235.  
  7236. // 是否为评论的翻译
  7237. let is_comment = button.IsCommentButton();
  7238.  
  7239. // 移除旧的
  7240. if (!$(e.target).closest('.OJBetter_contextmenu').length) {
  7241. $('.OJBetter_contextmenu').remove();
  7242. }
  7243.  
  7244. var menu = $('<div class="OJBetter_contextmenu"></div>');
  7245. var translations = [
  7246. { value: 'deepl', name: i18next.t('translation.options.services.deepl', { ns: 'settings' }) },
  7247. { value: 'iflyrec', name: i18next.t('translation.options.services.iflyrec', { ns: 'settings' }) },
  7248. { value: 'youdao', name: i18next.t('translation.options.services.youdao', { ns: 'settings' }) },
  7249. { value: 'google', name: i18next.t('translation.options.services.google', { ns: 'settings' }) },
  7250. { value: 'caiyun', name: i18next.t('translation.options.services.caiyun', { ns: 'settings' }) },
  7251. { value: 'openai', name: i18next.t('translation.options.services.openai.name', { ns: 'settings' }) }
  7252. ];
  7253.  
  7254. // Function to check if the service supports the target language
  7255. function supportsTargetLanguage(service, targetLang) {
  7256. return OJBetter.supportList.translationSupport[service] && OJBetter.supportList.translationSupport[service][targetLang] !== undefined;
  7257. }
  7258.  
  7259. if (is_comment) {
  7260. var label = OJB_safeCreateJQElement(`<label><input type="radio" name="translation" value="0">
  7261. <span class="OJBetter_contextmenu_label_text">
  7262. ${i18next.t('translation.preference.comment_translation_choice.services.follow', { ns: 'settings' })}
  7263. </span></label>`);
  7264. menu.append(label);
  7265. }
  7266. translations.forEach(function (translation) {
  7267. if (supportsTargetLanguage(translation.value, OJBetter.translation.targetLang)) {
  7268. var label = OJB_safeCreateJQElement(`<label><input type="radio" name="translation" value="${translation.value}">
  7269. <span class="OJBetter_contextmenu_label_text">${translation.name}</span></label>`);
  7270. menu.append(label);
  7271. }
  7272. });
  7273.  
  7274. // 初始化
  7275. if (is_comment) {
  7276. menu.find(`input[name="translation"][value="${OJBetter.translation.comment.choice}"]`).prop('checked', true);
  7277. } else {
  7278. menu.find(`input[name="translation"][value="${OJBetter.translation.choice}"]`).prop('checked', true);
  7279. }
  7280. menu.css({
  7281. top: e.pageY + 'px',
  7282. left: e.pageX + 'px'
  7283. }).appendTo('body');
  7284.  
  7285. $(document).one('change', 'input[name="translation"]', function () {
  7286. if (is_comment) {
  7287. OJBetter.translation.comment.choice = $('input[name="translation"]:checked').val();
  7288. GM_setValue("commentTranslationChoice", OJBetter.translation.comment.choice);
  7289. } else {
  7290. OJBetter.translation.choice = $('input[name="translation"]:checked').val();
  7291. GM_setValue("translation", OJBetter.translation.choice);
  7292. }
  7293. $('.OJBetter_contextmenu').remove();
  7294. });
  7295.  
  7296. // 点击区域外关闭菜单
  7297. function handleClick(event) {
  7298. if (!$(event.target).closest('.OJBetter_contextmenu').length) {
  7299. $('.OJBetter_contextmenu').remove();
  7300. $(document).off('change', 'input[name="translation"]');
  7301. } else {
  7302. $(document).one('click', handleClick);
  7303. }
  7304. }
  7305. $(document).one('click', handleClick);
  7306. });
  7307. }
  7308.  
  7309. /**
  7310. * 创建翻译任务
  7311. * @param {JQuery<HTMLElement>} button 按钮
  7312. * @param {HTMLElement} element 目标元素
  7313. * @param {string} type 类型
  7314. * @param {boolean} is_comment 是否是评论
  7315. * @param {string} overrideTrans 覆盖全局翻译服务设定
  7316. */
  7317. async function transTask(button, element, type, is_comment, overrideTrans) {
  7318. /** @type {HTMLElement} 目标元素 */
  7319. let target;
  7320. /**
  7321. * 错误计数数据结构
  7322. * @typedef {Object} count
  7323. * @property {number} errerNum 错误数量
  7324. * @property {number} skipNum 跳过数量
  7325. */
  7326. const count = {
  7327. errerNum: 0,
  7328. skipNum: 0
  7329. };
  7330. if (OJBetter.translation.comment.transMode == "1") {
  7331. // 分段翻译
  7332. let pElements = $(element).find("p:not(li p), li, .OJBetter_acmsguru");
  7333. for (let i = 0; i < pElements.length; i++) {
  7334. target = $(pElements[i]).eq(0).clone();
  7335. element_node = pElements[i];
  7336. await process(button, target, element_node, type, is_comment, count, overrideTrans);
  7337. }
  7338. } else if (OJBetter.translation.comment.transMode == "2") {
  7339. // 选段翻译
  7340. let pElements = $(element).find("p.block_selected:not(li p), li.block_selected, .OJBetter_acmsguru");
  7341. for (let i = 0; i < pElements.length; i++) {
  7342. target = $(pElements[i]).eq(0).clone();
  7343. element_node = pElements[i];
  7344. await process(button, target, element_node, type, is_comment, count, overrideTrans);
  7345. }
  7346. $(element).find("p.block_selected:not(li p), li.block_selected").removeClass('block_selected');
  7347. } else {
  7348. // 普通翻译
  7349. target = $(element).eq(0).clone();
  7350. if (type === "child_level") $(target).children(':first').remove();
  7351. element_node = $($(element)).get(0);
  7352. await process(button, target, element_node, type, is_comment, count, overrideTrans);
  7353. }
  7354.  
  7355. // 翻译完成
  7356. if (!count.errerNum && !count.skipNum) {
  7357. button.setTransButtonState('success');
  7358. }
  7359. }
  7360.  
  7361. /**
  7362. * 翻译处理
  7363. * @param {JQuery<HTMLElement>} button 按钮
  7364. * @param {HTMLElement} target 目标元素
  7365. * @param {HTMLElement} element_node 目标节点
  7366. * @param {string} type 类型
  7367. * @param {boolean} is_comment 是否是评论
  7368. * @param {string} overrideTrans 覆盖全局翻译服务设定
  7369. */
  7370. async function process(button, target, element_node, type, is_comment, count, overrideTrans) {
  7371. if (type === "child_level") {
  7372. let div = $("<div>");
  7373. $(element_node).append(div);
  7374. element_node = div.get(0);
  7375. }
  7376.  
  7377. //是否跳过折叠块
  7378. if ($(target).find('.spoiler').length > 0) {
  7379. const shouldSkip = await OJB_createDialog(
  7380. i18next.t('skipFold.title', { ns: 'dialog' }),
  7381. i18next.t('skipFold.content', { ns: 'dialog' }),
  7382. [
  7383. i18next.t('skipFold.buttons.0', { ns: 'dialog' }),
  7384. i18next.t('skipFold.buttons.1', { ns: 'dialog' })
  7385. ],
  7386. true
  7387. ); //跳过折叠块确认
  7388. if (shouldSkip) {
  7389. $(target).find('.spoiler').remove();
  7390. } else {
  7391. $(target).find('.html2md-panel').remove();
  7392. }
  7393. }
  7394.  
  7395. // 等待并获取结果
  7396. button.setTransButtonState('running');
  7397. const result = await blockProcessing(button, target, element_node, is_comment, overrideTrans);
  7398. button.pushResultToTransButton(result);
  7399.  
  7400. if (result.status == "error") count.errerNum += 1;
  7401. else if (result.status == "skip") count.skipNum += 1;
  7402. $(target).remove();
  7403. }
  7404.  
  7405. /**
  7406. * 块处理
  7407. * @param {JQuery<HTMLElement>} button
  7408. * @param {HTMLElement} target 目标元素
  7409. * @param {HTMLElement} element_node 目标节点
  7410. * @param {boolean} is_comment 是否是评论
  7411. * @param {string} overrideTrans 覆盖全局翻译服务设定
  7412. * @returns {TranslateResult} 翻译结果对象
  7413. */
  7414. async function blockProcessing(button, target, element_node, is_comment, overrideTrans) {
  7415. if (OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru) {
  7416. target.markdown = $(target).html();
  7417. } else if (!target.markdown) {
  7418. target.markdown = turndownService.turndown($(target).html());
  7419. }
  7420.  
  7421. const result = await translateProblemStatement(target.markdown, element_node, is_comment, overrideTrans);
  7422. if (result.status == "skip") {
  7423. button.setTransButtonState('error', i18next.t('trans.tooLong', { ns: 'button' }));
  7424. result.translateDiv.close();
  7425. } else if (result.status == "error" || !result.rawData.done) {
  7426. result.translateDiv.setError();
  7427. result.translateDiv.setRawData(result.rawData);
  7428. result.translateDiv.showDebugButton();
  7429. button.setTransButtonState('error', i18next.t('trans.error', { ns: 'button' }));
  7430. $(target).remove();
  7431. }
  7432. return result;
  7433. }
  7434.  
  7435. /**
  7436. * 选段翻译支持
  7437. */
  7438. async function multiChoiceTranslation() {
  7439. GM_addStyle(`
  7440. .topic .content #task-statement {
  7441. overflow: initial;
  7442. }
  7443. `);
  7444.  
  7445. $(document).on('click', 'p, li:not(:has(.comment)), .OJBetter_acmsguru', function (e) {
  7446. let $this = $(this);
  7447. e.stopPropagation();
  7448. if ($this.hasClass('block_selected')) {
  7449. $this.removeClass('block_selected');
  7450. // 移除对应的按钮
  7451. $('.OJBetter_MiniTranslateButton').remove("#translateButton_selected_" + $this.attr('OJBetter_p_id'));
  7452. } else {
  7453. let id = OJB_getRandomNumber(8);
  7454. $this.attr('OJBetter_p_id', id);
  7455. $this.addClass('block_selected');
  7456. // 添加按钮
  7457. let menu = OJB_safeCreateJQElement(`<div class="OJBetter_MiniTranslateButton" id='translateButton_selected_${id}'>${translateIcon}</div>`)
  7458. .css({
  7459. left: $($this).outerWidth(true) + $($this).position().left + 10 + 'px',
  7460. });
  7461. $this.before(menu);
  7462.  
  7463. $("#translateButton_selected_" + id).click(async function () {
  7464. // 处理旧的结果
  7465. if ($this.attr('translated')) {
  7466. let result = $this.data("resultData");
  7467. if (OJBetter.translation.retransAction == "0") {
  7468. result.translateDiv.close();
  7469. } else {
  7470. result.translateDiv.foldMainDiv();
  7471. }
  7472. }
  7473. // 翻译
  7474. let target = $this.eq(0).clone();
  7475. let result = await blockProcessing(OJBetter.translation.choice, target, $this.eq(0), $("#translateButton_selected_" + id), false);
  7476. $this.data("resultData", result);
  7477. $this.removeClass('block_selected');
  7478. // 移除对应的按钮
  7479. $('.OJBetter_MiniTranslateButton').remove("#translateButton_selected_" + id);
  7480. $this.attr('translated', '1'); // 标记已翻译
  7481. });
  7482. }
  7483. });
  7484. }
  7485.  
  7486. /**
  7487. * 添加MD/复制/翻译按钮
  7488. */
  7489. async function addConversionButton() {
  7490. // 基本添加
  7491. if (!OJBetter.typeOfPage.is_homepage) {
  7492. $('section').each(function () {
  7493. let id = "_" + OJB_getRandomNumber(8);
  7494. let panel = addButtonPanel(this, id, "this_level");
  7495. addButtonWithHTML2MD(panel.viewButton, this, id, "this_level");
  7496. addButtonWithCopy(panel.copyButton, this, id, "this_level");
  7497. addButtonWithTranslation(panel.translateButton, this, id, "this_level");
  7498. });
  7499. }
  7500.  
  7501. // 添加按钮到题解部分
  7502. if (window.location.href.includes("editorial")) {
  7503. let contestNavTabs = $("#contest-nav-tabs");
  7504. let nextElement = contestNavTabs.next();
  7505. let id = "_editorial_" + OJB_getRandomNumber(8);
  7506. let panel = addButtonPanel(nextElement, id, "child_level");
  7507. addButtonWithHTML2MD(panel.viewButton, nextElement, id, "child_level");
  7508. addButtonWithCopy(panel.copyButton, nextElement, id, "child_level");
  7509. addButtonWithTranslation(panel.translateButton, nextElement, id, "child_level");
  7510. }
  7511. if (window.location.href.includes("editorial")) {
  7512. let contestNavTabs = $("#contest-nav-tabs");
  7513. let nextElement = contestNavTabs.next().children().eq(-2);
  7514. let id = "_editorial_" + OJB_getRandomNumber(8);
  7515. let panel = addButtonPanel(nextElement, id, "child_level");
  7516. addButtonWithHTML2MD(panel.viewButton, nextElement, id, "child_level");
  7517. addButtonWithCopy(panel.copyButton, nextElement, id, "child_level");
  7518. addButtonWithTranslation(panel.translateButton, nextElement, id, "child_level");
  7519. }
  7520.  
  7521. // 添加按钮到折叠块部分
  7522. $('details').each(function () {
  7523. // 自定义测试样例折叠块不添加
  7524. if ($(this).attr('id') !== "customTestBlock") {
  7525. let id = "_details_" + OJB_getRandomNumber(8);
  7526. let panel = addButtonPanel(this, id, "child_level");
  7527. addButtonWithHTML2MD(panel.viewButton, this, id, "child_level");
  7528. addButtonWithCopy(panel.copyButton, this, id, "child_level");
  7529. addButtonWithTranslation(panel.translateButton, this, id, "child_level");
  7530. }
  7531. });
  7532.  
  7533. // 添加到contest-statement部分
  7534. $('#contest-statement').each(function () {
  7535. let id = "_contest-statement_" + OJB_getRandomNumber(8);
  7536. let panel = addButtonPanel(this, id, "this_level");
  7537. addButtonWithHTML2MD(panel.viewButton, this, id, "this_level");
  7538. addButtonWithCopy(panel.copyButton, this, id, "this_level");
  7539. addButtonWithTranslation(panel.translateButton, this, id, "this_level");
  7540. });
  7541.  
  7542. // 添加到blog-post部分
  7543. $('.blog-post').each(function () {
  7544. let id = "_blog-post_" + OJB_getRandomNumber(8);
  7545. let panel = addButtonPanel(this, id, "this_level");
  7546. addButtonWithHTML2MD(panel.viewButton, this, id, "this_level");
  7547. addButtonWithCopy(panel.copyButton, this, id, "this_level");
  7548. addButtonWithTranslation(panel.translateButton, this, id, "this_level");
  7549. });
  7550. };
  7551.  
  7552. /**
  7553. * 等待LaTeX渲染队列全部完成
  7554. * @returns {Promise} 完成渲染
  7555. */
  7556. function waitForMathJaxIdle() {
  7557. return true;
  7558. // return new Promise((resolve, reject) => {
  7559. // // 检查MathJax对象是否存在
  7560. // const checkMathJaxExists = () => {
  7561. // if (typeof MathJax === 'undefined') {
  7562. // // 如果MathJax不存在,稍后再次检查
  7563. // OJB_delay(100).then(checkMathJaxExists);
  7564. // } else {
  7565. // // MathJax存在,开始监视渲染队列
  7566. // startMonitoringQueue();
  7567. // }
  7568. // };
  7569.  
  7570. // // 开始监视MathJax渲染队列
  7571. // const startMonitoringQueue = () => {
  7572. // const intervalId = setInterval(() => {
  7573. // const queue = MathJax.Hub.queue;
  7574. // if (queue.pending === 0 && queue.running === 0) {
  7575. // clearInterval(intervalId);
  7576. // resolve();
  7577. // }
  7578. // }, 100);
  7579. // };
  7580.  
  7581. // // 开始检查MathJax对象
  7582. // checkMathJaxExists();
  7583. // });
  7584. }
  7585.  
  7586. /**
  7587. * 翻译结果面板
  7588. */
  7589. class TranslateDiv {
  7590. /**
  7591. * 构造函数
  7592. * @param {string} id 指定翻译框的id
  7593. */
  7594. constructor(id) {
  7595. this.id = id;
  7596. this.div = $('<div>').attr('id', id).addClass('translateDiv bounce-in');
  7597. if (!OJBetter.typeOfPage.is_completeProblemset) {
  7598. this.div.addClass('input-output-copier');
  7599. }
  7600. this.panelDiv = $('<div>').addClass('translate-problem-statement-panel');
  7601. this.div.append(this.panelDiv);
  7602.  
  7603. // 主要信息
  7604. this.mainDiv = $('<div>').addClass('translate-problem-statement');
  7605. this.span = $('<span>');
  7606. this.mainDiv.append(this.span);
  7607. this.div.append(this.mainDiv);
  7608. this.mainDivState = {
  7609. current: 'transHTML',
  7610. transHTML: '',
  7611. rawDataHTML: ''
  7612. };
  7613.  
  7614. // 顶栏信息
  7615. this.topText = $('<div>').addClass('topText');
  7616. this.panelDiv.append(this.topText);
  7617.  
  7618. // 右侧
  7619. this.rightDiv = $('<div>').css('display', 'flex');
  7620. this.panelDiv.append(this.rightDiv);
  7621. this.debugButton = OJB_safeCreateJQElement(`
  7622. <button class='ojb_btn ojb_btn_popover top'>
  7623. <i class="iconfont">&#xe641;</i>
  7624. <span class="popover_content">${i18next.t('rawData.normal', { ns: 'button' })}</span>
  7625. </button>`).hide();
  7626. this.rightDiv.append(this.debugButton);
  7627. this.queryBalanceButton = OJB_safeCreateJQElement(`
  7628. <button class='ojb_btn ojb_btn_popover top'>
  7629. <i class="iconfont">&#xe6ae;</i>
  7630. <span class="popover_content">${i18next.t('queryBalance.normal', { ns: 'button' })}</span>
  7631. </button>`).hide();
  7632. this.rightDiv.append(this.queryBalanceButton);
  7633. this.copyButton = OJB_safeCreateJQElement(`
  7634. <button class='ojb_btn ojb_btn_popover top'>
  7635. <i class="iconfont">&#xe608;</i>
  7636. <span class="popover_content">${i18next.t('copy.normal', { ns: 'button' })}</span>
  7637. </button>`);
  7638. this.rightDiv.append(this.copyButton);
  7639. this.upButton = OJB_safeCreateJQElement(`
  7640. <button class='ojb_btn ojb_btn_popover top'>
  7641. <i class="iconfont">&#xe601;</i>
  7642. <span class="popover_content">${i18next.t('fold.normal', { ns: 'button' })}</span>
  7643. </button>`);
  7644. this.rightDiv.append(this.upButton);
  7645. this.closeButton = OJB_safeCreateJQElement(`
  7646. <button class='ojb_btn ojb_btn_popover top'>
  7647. <i class="iconfont">&#xe614;</i>
  7648. <span class="popover_content">${i18next.t('close.normal', { ns: 'button' })}</span>
  7649. </button>`);
  7650. this.rightDiv.append(this.closeButton);
  7651. }
  7652.  
  7653. /**
  7654. * 获取翻译框
  7655. * @returns {JQuery<HTMLElement>} 返回翻译框
  7656. */
  7657. getDiv() {
  7658. return this.div;
  7659. }
  7660.  
  7661. /**
  7662. * 设置翻译框顶部的文本
  7663. * @param {string} text 翻译框顶部的文本
  7664. */
  7665. setTopText(text) {
  7666. this.div.attr("data-topText", text);
  7667. this.topText.text(text);
  7668. }
  7669.  
  7670. /**
  7671. * 获取翻译框顶部的文本
  7672. * @returns {string} 返回翻译框顶部的文本
  7673. */
  7674. getTopText() {
  7675. return this.topText.text();
  7676. }
  7677.  
  7678. /**
  7679. * 渲染一个元素内的LaTeX公式
  7680. * @param {*} element
  7681. */
  7682. renderLaTeX(element) {
  7683. const latexRenderOptions = {
  7684. delimiters: [
  7685. { left: "$$", right: "$$", display: true },
  7686. { left: "\$$", right: "\\$$", display: true },
  7687. { left: "$", right: "$", display: false },
  7688. { left: "\$$", right: "\\$$", display: false }
  7689. ]
  7690. };
  7691.  
  7692. if (typeof renderMathInElement === 'function') {
  7693. renderMathInElement(element, latexRenderOptions);
  7694. }
  7695. }
  7696.  
  7697. /**
  7698. * 更新翻译框内容
  7699. * @param {string} text 文本内容
  7700. * @param {boolean} is_escapeHTML 是否转义HTML标签,为true则HTML标签将作为普通文本处理,默认为true
  7701. * @param {boolean} is_renderLaTeX 是否渲染LaTeX,为true则会渲染LaTeX,默认为true
  7702. */
  7703. updateTranslateDiv(text, is_escapeHTML = true, is_renderLaTeX = true,) {
  7704. // 渲染MarkDown
  7705. let md = window.markdownit({
  7706. html: !is_escapeHTML,
  7707. });
  7708. if (!text) text = "";
  7709. let html = md.render(text);
  7710. this.mainDiv.html(html);
  7711.  
  7712. // 渲染Latex
  7713. if (is_renderLaTeX) {
  7714. // MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.mainDiv.get(0)]);
  7715. this.renderLaTeX(this.mainDiv.get(0));
  7716. }
  7717. // 渲染代码块中的公式 (AtCoder)
  7718. this.mainDiv.find('pre code').each((index, element) => {
  7719. const codeText = $(element).text();
  7720. const latexPattern = /\$\$(\\.|[^\$])*?\$\$|\$(\\.|[^\$])*?\$/;
  7721. if (latexPattern.test(codeText)) {
  7722. this.renderLaTeX(element);
  7723. }
  7724. });
  7725. }
  7726.  
  7727. /**
  7728. * 关闭元素
  7729. */
  7730. close() {
  7731. this.closeButton.click();
  7732. }
  7733.  
  7734. /**
  7735. * 注册收起按钮事件
  7736. */
  7737. registerUpButtonEvent() {
  7738. this.upButton.on("click", () => {
  7739. // 如果没有reverse类,说明是展开状态
  7740. if (!this.upButton.hasClass("reverse")) {
  7741. // 执行收起操作
  7742. this.upButton.addClass("reverse");
  7743. this.upButton.setButtonState('initial', i18next.t('fold.unfold', { ns: 'button' }));
  7744. OJB_toggleCollapseExpand(this.mainDiv.get(0));
  7745. } else {
  7746. // 执行展开操作
  7747. this.upButton.removeClass("reverse");
  7748. this.upButton.setButtonState('initial', i18next.t('fold.normal', { ns: 'button' }));
  7749. OJB_toggleCollapseExpand(this.mainDiv.get(0));
  7750. }
  7751. });
  7752. }
  7753.  
  7754. /**
  7755. * 注册关闭按钮事件
  7756. */
  7757. registerCloseButtonEvent() {
  7758. this.closeButton.on("click", () => {
  7759. $(this.div).remove();
  7760. $(this.panelDiv).remove();
  7761. if (OJBetter.typeOfPage.is_problem && OJBetter.translation.memory.enabled) {
  7762. OJBetter.translation.memory.ttTree.rmTransResultMap(this.id); // 移除ttTree中的数据
  7763. OJBetter.translation.memory.ttTree.refreshNode("#task-statement");
  7764. updateTransDBData(OJBetter.translation.memory.ttTree.getNodeData(), OJBetter.translation.memory.ttTree.getTransResultMap()); // 更新DB中的数据
  7765. }
  7766. });
  7767. }
  7768.  
  7769. /**
  7770. * 注册复制按钮事件
  7771. * @param {string} text 复制的文本
  7772. */
  7773. registerCopyButtonEvent(text) {
  7774. this.copyButton.on("click", () => {
  7775. GM_setClipboard(text);
  7776. this.copyButton.setButtonState('success', i18next.t('copy.copied', { ns: 'button' }));
  7777. // 复制提示
  7778. setTimeout(() => {
  7779. this.copyButton.setButtonState('initial', i18next.t('copy.normal', { ns: 'button' }));
  7780. }, 2000);
  7781. });
  7782. }
  7783.  
  7784. /**
  7785. * 禁用复制按钮
  7786. */
  7787. disableCopyButton() {
  7788. this.copyButton.css({ 'fill': '#ccc' });
  7789. this.copyButton.off("click");
  7790. }
  7791.  
  7792. /**
  7793. * 设置面板为error状态
  7794. */
  7795. setError() {
  7796. this.div.addClass('error');
  7797. this.panelDiv.addClass('error');
  7798. this.mainDiv.addClass('error');
  7799. }
  7800.  
  7801. /**
  7802. * 设置原始数据数据
  7803. * @param {Object} Object 原始数据
  7804. */
  7805. setRawData(Object) {
  7806. this.mainDivState.rawDataHTML = $("<pre>").text(JSON.stringify(Object, null, 4)).get(0);
  7807. if (this.mainDivState.current === 'rawDataHTML') {
  7808. this.renderMainDiv();
  7809. }
  7810. }
  7811.  
  7812. /**
  7813. * 切换结果面板与原始数据面板
  7814. */
  7815. switchMainDiv() {
  7816. // 在切换之前,保存当前内容的状态
  7817. this.mainDivState[this.mainDivState.current] = this.mainDiv.html();
  7818. // 切换当前状态
  7819. this.debugButton.setButtonState(this.mainDivState.current === 'transHTML' ? 'enabled' : 'initial');
  7820. this.mainDivState.current = this.mainDivState.current === 'transHTML' ? 'rawDataHTML' : 'transHTML';
  7821. // 渲染新的当前状态
  7822. this.renderMainDiv();
  7823. }
  7824.  
  7825. // 渲染当前内容到 mainDiv
  7826. renderMainDiv() {
  7827. requestAnimationFrame(() => {
  7828. this.mainDiv.html(this.mainDivState[this.mainDivState.current]);
  7829. });
  7830. }
  7831.  
  7832. /**
  7833. * 注册debug按钮事件
  7834. */
  7835. registerDebugButtonEvent() {
  7836. this.debugButton.on("click", () => {
  7837. this.switchMainDiv();
  7838. });
  7839. }
  7840.  
  7841. /**
  7842. * 显示debug按钮
  7843. */
  7844. showDebugButton() {
  7845. this.debugButton.show();
  7846. this.registerDebugButtonEvent();
  7847. }
  7848.  
  7849. /**
  7850. * 注册查询余额按钮事件
  7851. * @param {function} callback 查询回调函数
  7852. */
  7853. registerQueryBalanceButtonEvent(callback) {
  7854. this.queryBalanceButton.on("click", async () => {
  7855. this.queryBalanceButton.setButtonState('loading', i18next.t('queryBalance.loading', { ns: 'button' }));
  7856. try {
  7857. const balance = await callback();
  7858. this.queryBalanceButton.setButtonState('success', `${i18next.t('queryBalance.success', { ns: 'button' })} ${balance}`);
  7859. } catch (error) {
  7860. this.queryBalanceButton.setButtonState('error', `${i18next.t('queryBalance.error', { ns: 'button' })} ${error.message}`);
  7861. }
  7862. });
  7863. }
  7864.  
  7865. /**
  7866. * 显示余额查询按钮
  7867. * @param {string} server 服务名称
  7868. */
  7869. showQueryBalanceButton(server) {
  7870. if (server == 'deepl') {
  7871. const quotaConfig = OJBetter.deepl.config.quota;
  7872. if (quotaConfig.url && quotaConfig.surplus && quotaConfig.header) {
  7873. this.queryBalanceButton.show();
  7874. this.registerQueryBalanceButtonEvent(() => {
  7875. return queryServerBalance(OJBetter.deepl.config.quota);
  7876. });
  7877. }
  7878. } else if (server == 'openai') {
  7879. const quotaConfig = OJBetter.chatgpt.config.quota;
  7880. if (quotaConfig.url && quotaConfig.surplus && quotaConfig.header) {
  7881. this.queryBalanceButton.show();
  7882. this.registerQueryBalanceButtonEvent(() => {
  7883. return queryServerBalance(OJBetter.chatgpt.config.quota);
  7884. });
  7885. }
  7886. }
  7887. }
  7888. }
  7889.  
  7890. // 元素关系树
  7891. class ElementsTree {
  7892. constructor(elements) {
  7893. this.node = [];
  7894. this.transResultMap = {};
  7895. this.index = 0;
  7896. // this.tagNames = ["DIV", "P", "UL", "LI"]
  7897. this.tagNames = ["DIV", "P", "UL", "LI", "SECTION", "SPAN"]
  7898. this.init($(elements));
  7899. }
  7900.  
  7901. // Iterate through all elements, because there may be multiple ttypography
  7902. init(elements) {
  7903. elements.each((i, e) => {
  7904. this.node.push({}); // add one element
  7905. this.index = 0; // reset index
  7906. this.create(i, $(e));
  7907. });
  7908. }
  7909.  
  7910. // 刷新关系树
  7911. refreshNode(elements) {
  7912. this.node = [];
  7913. this.index = 0;
  7914. this.init($(elements));
  7915. }
  7916.  
  7917. // 创建节点间的关系树
  7918. create(i_, element) {
  7919. var prev = null;
  7920. var node = this.node[i_];
  7921. element.children().each((i, e) => {
  7922. // only add element with tagNames
  7923. if (this.tagNames.includes($(e).prop("tagName"))) {
  7924. prev = this.addNode(i_, prev, e);
  7925. }
  7926. // recursively child element
  7927. if ($(e).children().length > 0 && prev !== null) {
  7928. node[prev].firstChild = this.index;
  7929. this.create(i_, $(e));
  7930. }
  7931. });
  7932. }
  7933.  
  7934. // 向树中添加一个节点
  7935. addNode(i_, prev, e) {
  7936. let node = this.node[i_];
  7937. node[this.index] = {
  7938. prev: prev,
  7939. next: null,
  7940. firstChild: null,
  7941. type: $(e).prop("tagName"),
  7942. isTranslateDiv: $(e).hasClass("translateDiv"),
  7943. topText: $(e).attr("data-topText"),
  7944. id: $(e).attr("id"),
  7945. };
  7946.  
  7947. if (prev !== null) {
  7948. node[prev].next = this.index;
  7949. }
  7950.  
  7951. prev = this.index;
  7952.  
  7953. this.index++;
  7954. return prev;
  7955. }
  7956.  
  7957. getNodeData() {
  7958. return this.node;
  7959. }
  7960.  
  7961. setNodeData(node) {
  7962. this.node = node;
  7963. }
  7964.  
  7965. getTransResultMap() {
  7966. return this.transResultMap;
  7967. }
  7968.  
  7969. setTransResultMap(transResultMap) {
  7970. this.transResultMap = transResultMap;
  7971. }
  7972.  
  7973. rmTransResultMap(id) {
  7974. delete this.transResultMap[id];
  7975. }
  7976.  
  7977. addTransResultMap(id, text) {
  7978. this.transResultMap[id] = text;
  7979. }
  7980.  
  7981. getTranslateDivNum(ttTree) {
  7982. var num = 0;
  7983. for (var i in ttTree) {
  7984. if (ttTree[i].isTranslateDiv) {
  7985. num++;
  7986. }
  7987. }
  7988. return num;
  7989. }
  7990.  
  7991. // 恢复目标元素中的translateDiv
  7992. recover(elements) {
  7993. elements.each((i, e) => {
  7994. var ttTreeNode = this.node[i];
  7995. var missingTranslateDivs = this.getTranslateDivNum(ttTreeNode);
  7996. if (missingTranslateDivs > 0) {
  7997. this.recoverOneElement($(e), ttTreeNode);
  7998. }
  7999. });
  8000. }
  8001.  
  8002. recoverOneElement(element, ttTreeNode) {
  8003. this.recoverOneFork(element.children().eq(0), ttTreeNode, 0);
  8004. }
  8005.  
  8006. // 恢复一个分支
  8007. recoverOneFork(pElement, ttTreeNode, index) {
  8008. do {
  8009. // only recover element with tagNames
  8010. if (!this.tagNames.includes(pElement.prop("tagName"))) {
  8011. if (pElement.next().length > 0) {
  8012. pElement = pElement.next();
  8013. } else {
  8014. return;
  8015. }
  8016. }
  8017. if (!ttTreeNode[index] || pElement.prop("tagName") !== ttTreeNode[index].type) {
  8018. // console.warn(`元素不存在或类型不同, 元素结构可能已经发生了变化: \nindex: ${index}`, pElement);
  8019. return;
  8020. } else {
  8021. // recursively child element
  8022. var node = ttTreeNode[index];
  8023. if (node.firstChild !== null) {
  8024. this.recoverOneFork(
  8025. pElement.children().eq(0),
  8026. ttTreeNode,
  8027. node.firstChild
  8028. );
  8029. }
  8030. // check if next node is translateDiv
  8031. if (node.next !== null) {
  8032. index = node.next;
  8033.  
  8034. var ne_node = ttTreeNode[index];
  8035. if (ne_node.isTranslateDiv) {
  8036. var id = ne_node.id;
  8037. var topText = ne_node.topText;
  8038. var text = this.transResultMap[id];
  8039. // create element after pElement
  8040. this.reCreateTransDiv(pElement, id, text, topText);
  8041. }
  8042. pElement = pElement.next(); // go to next element
  8043. }
  8044. }
  8045. } while (node.next !== null);
  8046. }
  8047.  
  8048. // 重新创建translateDiv
  8049. reCreateTransDiv(pElement, id, translatedText, topText) {
  8050. const translateDiv = new TranslateDiv(id);
  8051. pElement.after(translateDiv.getDiv());
  8052. translateDiv.setTopText(topText);
  8053. translateDiv.registerUpButtonEvent();
  8054. translateDiv.registerCloseButtonEvent();
  8055. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  8056. translateDiv.registerCopyButtonEvent(translatedText);
  8057. } else {
  8058. translateDiv.disableCopyButton();
  8059. }
  8060. translateDiv.updateTranslateDiv(translatedText, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru));
  8061. // 标记已翻译并添加到翻译按钮的结果栈中
  8062. let transButton = pElement.prev('.html2md-panel').find('.translateButton');
  8063. if (transButton.length == 0) {
  8064. // 如果没有找到,则应该是得在父元素中找到
  8065. transButton = pElement.parent().prev('.html2md-panel').find('.translateButton');
  8066. }
  8067. transButton.pushResultToTransButton({
  8068. translateDiv: translateDiv,
  8069. status: 0
  8070. });
  8071. transButton.setTransButtonState('success');
  8072. }
  8073. }
  8074.  
  8075. // 更新TransDB中的翻译数据
  8076. async function updateTransDBData(nodeDate, transResultMap) {
  8077. var url = window.location.href.replace(/#/, "");
  8078. try {
  8079. await OJBetter.common.database.translateData.put({ url, transResultMap, nodeDate });
  8080. return 'translateData saved successfully';
  8081. } catch (error) {
  8082. throw new Error(`Failed to save translateData: ${error}`);
  8083. }
  8084. }
  8085.  
  8086. // 获取TransDB中保存的翻译数据
  8087. async function getTransDBData() {
  8088. var url = window.location.href.replace(/#/, "");
  8089. try {
  8090. const result = await OJBetter.common.database.translateData.get(url);
  8091. return result;
  8092. } catch (error) {
  8093. throw new Error(`Failed to get translateData: ${error}`);
  8094. }
  8095. }
  8096.  
  8097. /**
  8098. * 翻译结果恢复功能初始化
  8099. * @returns
  8100. */
  8101. async function initTransResultsRecover() {
  8102. OJBetter.translation.memory.ttTree = new ElementsTree("#task-statement"); // 初始化当前页面#task-statement元素的结构树
  8103. let result = await getTransDBData();
  8104. if (!result) return;
  8105. OJBetter.translation.memory.ttTree.setNodeData(result.nodeDate);
  8106. OJBetter.translation.memory.ttTree.setTransResultMap(result.transResultMap);
  8107. OJBetter.translation.memory.ttTree.recover($("#task-statement"));
  8108. }
  8109.  
  8110. /**
  8111. * 自动翻译
  8112. */
  8113. async function initTransWhenViewable() {
  8114. await waitForMathJaxIdle();
  8115.  
  8116. // const elements = $('.ttypography, .comments').find('.translateButton');
  8117. const elements = $('#task-statement').find('.translateButton');
  8118. const observers = [];
  8119.  
  8120. // Use a single Intersection Observer for all elements
  8121. const observer = new IntersectionObserver((entries, obs) => {
  8122. entries.forEach((entry) => {
  8123. if (entry.isIntersecting) {
  8124. const button = $(entry.target);
  8125. const state = button.getButtonState();
  8126. const notAutoTranslate = button.getNotAutoTranslate();
  8127. // Check if the button meets the criteria
  8128. if (state === 'normal' && !notAutoTranslate) {
  8129. let trans = OJBetter.translation.choice;
  8130.  
  8131. if (OJBetter.translation.auto.mixTrans.enabled && button.IsCommentButton() && OJBetter.translation.auto.mixTrans.servers.length > 0) {
  8132. const randomIndex = Math.floor(Math.random() * OJBetter.translation.auto.mixTrans.servers.length);
  8133. trans = OJBetter.translation.auto.mixTrans.servers[randomIndex];
  8134. }
  8135. button.data("translatedItBy")(trans);
  8136. }
  8137.  
  8138. // Stop observing the element
  8139. obs.unobserve(entry.target);
  8140. }
  8141. });
  8142. });
  8143.  
  8144. // Observe each element
  8145. elements.each((i, e) => {
  8146. observer.observe(e);
  8147. });
  8148.  
  8149. // Store the observer in case you need to disconnect it later
  8150. observers.push(observer);
  8151. }
  8152.  
  8153. /**
  8154. * 翻译返回结果结构体
  8155. * @typedef {Object} TranslateResult
  8156. * @property {string} status 翻译状态
  8157. * @property {TranslateDiv} translateDiv 翻译结果面板
  8158. * @property {TransRawData} rawData 原始翻译数据
  8159. */
  8160.  
  8161. /**
  8162. * 翻译主方法
  8163. * @param {string} text 待翻译文本
  8164. * @param {HTMLElement} element_node 元素节点
  8165. * @param {Boolean} is_comment 是否为评论区文本
  8166. * @param {string} overrideTrans 覆盖全局翻译服务设定
  8167. * @returns {TranslateResult} 翻译结果对象
  8168. */
  8169. async function translateProblemStatement(text, element_node, is_comment, overrideTrans) {
  8170. /** @type {number} 翻译结果的ID*/
  8171. const id = OJB_getRandomNumber(8);
  8172. /** @type {TextBlockReplacer} 文本块替换/恢复实例*/
  8173. const textBlockReplacer = new TextBlockReplacer();
  8174. /** @type {string} 翻译结果文本*/
  8175. let translatedText = "";
  8176.  
  8177. /** @type {string} 当前实际应用的翻译服务 */
  8178. const realTransServer = overrideTrans ||
  8179. (is_comment && OJBetter.translation.comment.choice != "0" ?
  8180. OJBetter.translation.comment.choice :
  8181. OJBetter.translation.choice);
  8182.  
  8183. /** @type {TranslateResult} 翻译结果对象 */
  8184. const translateResult = {
  8185. status: "ok",
  8186. rawData: {
  8187. done: false
  8188. }
  8189. }
  8190.  
  8191. /**
  8192. * LaTeX替换
  8193. * @param {string} text 待翻译文本
  8194. * @returns {string} 处理后的文本
  8195. */
  8196. const replaceLatex = function (text) {
  8197. if (OJBetter.typeOfPage.is_oldLatex) {
  8198. const regex = /<span\s+class="tex-span">.*?<\/span>/gi;
  8199. text = textBlockReplacer.replace(text, regex);
  8200. text = text.replace(/<p>(.*?)<\/p>/g, "$1\n\n"); // <p/>标签换为换行
  8201. } else if (OJBetter.typeOfPage.is_acmsguru) {
  8202. const regex = /<i>.*?<\/i>|<sub>.*?<\/sub>|<sup>.*?<\/sup>|<pre>.*?<\/pre>/gi;
  8203. text = textBlockReplacer.replace(text, regex);
  8204. } else if (realTransServer != "openai") {
  8205. // 使用GPT翻译时不必替换latex公式
  8206. const regex = /\$\$(\\.|[^\$])*?\$\$|\$(\\.|[^\$])*?\$/g;
  8207. text = textBlockReplacer.replace(text, regex);
  8208.  
  8209. // 替换行间代码块```
  8210. const regex2 = /```[\s\S]*?```/g;
  8211. text = textBlockReplacer.replace(text, regex2);
  8212. }
  8213. return text;
  8214. }
  8215.  
  8216. /**
  8217. * LaTeX恢复
  8218. * @param {string} text 已翻译的文本
  8219. * @returns {string} 恢复后的文本
  8220. */
  8221. const recoverLatex = function (text) {
  8222. // 两个公式之间加个空格,防止有些LaTeX解析器解析错误
  8223. let resultText = text
  8224. .replace(/】【/g, '】 【')
  8225. .replace(/\]\[/g, '] [')
  8226. .replace(/\}\{/g, '} {');
  8227.  
  8228. if (OJBetter.typeOfPage.is_oldLatex) {
  8229. resultText = resultText.replace(/(.+?)(\n\n|$)/g, "<p>$1</p>"); // 换行符还原为<p/>标签
  8230. resultText = textBlockReplacer.recover(resultText);
  8231. } else if (OJBetter.typeOfPage.is_acmsguru) {
  8232. resultText = textBlockReplacer.recover(resultText);
  8233. } else if (realTransServer != "openai") {
  8234. resultText = textBlockReplacer.recover(resultText);
  8235. }
  8236. return resultText;
  8237. }
  8238.  
  8239. /**
  8240. * 格式化翻译结果
  8241. * @param {string} text
  8242. * @returns {string} 处理后的翻译结果
  8243. */
  8244. const formatText = function (text) {
  8245. // 转义LaTex中的特殊符号
  8246. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  8247.  
  8248. // 先替换掉行间代码块
  8249. const replacer = new TextBlockReplacer();
  8250. text = replacer.replace(text, /```[\s\S]*?```/g);
  8251.  
  8252. // 处理LaTeX公式
  8253. const escapeRules = [
  8254. { pattern: /(?<!\\)>(?!\s)/g, replacement: " &gt; " }, // >符号
  8255. { pattern: /(?<!\\)</g, replacement: " &lt; " }, // <符号
  8256. { pattern: /(?<!\\)\*/g, replacement: " &#42; " }, // *符号
  8257. { pattern: /(?<!\\)_/g, replacement: " &#95; " }, // _符号
  8258. { pattern: /(?<!\\)\\\\(?=\s)/g, replacement: "\\\\\\\\" }, // \\符号
  8259. { pattern: /(?<!\\)\\(?![\\a-zA-Z0-9])/g, replacement: "\\\\" }, // \符号
  8260. ];
  8261.  
  8262. let latexMatches = [...text.matchAll(/\$\$([\s\S]*?)\$\$|\$(.*?)\$|\$([\s\S]*?)\$/g)];
  8263. for (const match of latexMatches) {
  8264. const matchedText = match[0];
  8265. let escapedText = matchedText;
  8266.  
  8267. for (const rule of escapeRules) {
  8268. escapedText = escapedText.replaceAll(rule.pattern, rule.replacement);
  8269. }
  8270. escapedText = escapedText.replace(/\$\$/g, "$$$$$$$$");// $$符号(因为后面需要作为replacement,双倍消耗)
  8271. text = text.replace(matchedText, escapedText);
  8272. }
  8273.  
  8274. // 恢复行间代码块
  8275. text = replacer.recover(text);
  8276. }
  8277.  
  8278. // // 使符合mathjx的转换语法
  8279. // const mathjaxRuleMap = [
  8280. // { pattern: /\$/g, replacement: "$$$$$$" }, // $$ 行间
  8281. // ];
  8282. // mathjaxRuleMap.forEach(({ pattern, replacement }) => {
  8283. // text = text.replace(pattern, replacement);
  8284. // });
  8285.  
  8286. // markdown修正
  8287. const mdRuleMap = [
  8288. { pattern: /(\s_[\u4e00-\u9fa5]+_)([\u4e00-\u9fa5]+)/g, replacement: "$1 $2" }, // 斜体
  8289. { pattern: /(_[\u4e00-\u9fa5]+_\s)([\u4e00-\u9fa5]+)/g, replacement: " $1$2" },
  8290. { pattern: /(_[\u4e00-\u9fa5]+_)([\u4e00-\u9fa5]+)/g, replacement: " $1 $2" },
  8291. { pattern: /(([\s\S]*?))/g, replacement: "($1)" }, // 中文()
  8292. // { pattern: /:/g, replacement: ":" }, // 中文:
  8293. { pattern: /\*\* (.*?) \*\*/g, replacement: "\*\*$1\*\*" } // 加粗
  8294. ];
  8295. mdRuleMap.forEach(({ pattern, replacement }) => {
  8296. text = text.replace(pattern, replacement);
  8297. });
  8298.  
  8299. return text;
  8300. }
  8301.  
  8302. // 创建翻译结果元素并放在element_node的后面
  8303. translateResult.translateDiv = new TranslateDiv(id);
  8304. $(element_node).after(translateResult.translateDiv.getDiv());
  8305.  
  8306. // 顶栏左侧信息
  8307. translateResult.translateDiv.setTopText(i18next.t('servers.' + realTransServer, { ns: 'translator' }) +
  8308. i18next.t('translateDiv.topTextSuffix', { ns: 'translator' }));
  8309.  
  8310. // 注册按钮
  8311. translateResult.translateDiv.registerUpButtonEvent();
  8312. translateResult.translateDiv.registerCloseButtonEvent();
  8313. if (OJBetter.translation.choice == 'openai' || OJBetter.translation.choice == 'deepl') {
  8314. translateResult.translateDiv.showQueryBalanceButton(OJBetter.translation.choice); // 显示额度查询
  8315. }
  8316.  
  8317. // 翻译内容是否可能为代码片段
  8318. if (isLikelyCodeSnippet(text)) {
  8319. const shouldContinue = await OJB_createDialog(
  8320. i18next.t('isLikelyCodeSnippet.title', { ns: 'dialog' }),
  8321. i18next.t('isLikelyCodeSnippet.content', { ns: 'dialog' }),
  8322. [
  8323. i18next.t('isLikelyCodeSnippet.buttons.0', { ns: 'dialog' }),
  8324. i18next.t('isLikelyCodeSnippet.buttons.1', { ns: 'dialog' })
  8325. ],
  8326. true
  8327. );
  8328. if (shouldContinue) {
  8329. translateResult.status = "skip";
  8330. return translateResult;
  8331. }
  8332. }
  8333.  
  8334. // 替换latex公式
  8335. text = replaceLatex(text);
  8336.  
  8337. // 过滤**号
  8338. if (OJBetter.translation.filterTextWithoutEmphasis && GM_getValue("translation") !== "openai") { // TODO
  8339. text = text.replace(/\*\*/g, "");
  8340. }
  8341.  
  8342. // 字符数上限
  8343. const translationLimits = {
  8344. deepl: 5000,
  8345. iflyrec: 2000,
  8346. youdao: 600,
  8347. google: 5000,
  8348. caiyun: 5000
  8349. };
  8350. if (translationLimits.hasOwnProperty(realTransServer) && text.length > translationLimits[realTransServer]) {
  8351. let textLength = translationLimits[realTransServer];
  8352. let realTextLength = text.length;
  8353. const shouldContinue = await OJB_createDialog(
  8354. i18next.t('transTextLimits.title', { ns: 'dialog' }),
  8355. i18next.t('transTextLimits.content', { ns: 'dialog', textLength: textLength, realTextLength: realTextLength }),
  8356. [
  8357. i18next.t('transTextLimits.buttons.0', { ns: 'dialog' }),
  8358. i18next.t('transTextLimits.buttons.1', { ns: 'dialog' })
  8359. ],
  8360. true
  8361. ); // 字数超限确认
  8362. if (shouldContinue) {
  8363. translateResult.status = "skip";
  8364. return translateResult;
  8365. }
  8366. }
  8367.  
  8368. /**
  8369. * 调用各个翻译服务
  8370. * @param {string} transServer 翻译服务
  8371. * @returns {TransRawData} 原始翻译数据
  8372. */
  8373. async function translate(transServer) {
  8374. const is_renderLaTeX = !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru);
  8375. const servername = i18next.t('servers.' + realTransServer, { ns: 'translator' });
  8376. /** @type {TransRawData} 原始翻译数据*/
  8377. let rawData = {};
  8378. try {
  8379. if (transServer == "deepl") {
  8380. if (OJBetter.deepl.config.type == 'free') {
  8381. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8382. rawData = await translate_deepl(text);
  8383. } else if (OJBetter.deepl.config.type == 'api') {
  8384. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.deeplApi', { ns: 'translator', deepl_configName: OJBetter.deepl.config.name })}`, is_renderLaTeX);
  8385. if (OJBetter.deepl.config.apiGenre == 'deeplx') {
  8386. rawData = await translate_deeplx(text);
  8387. } else {
  8388. if (OJBetter.deepl.enableEmphasisProtection) text = convertBoldMarkdownToHTML(text);
  8389. if (OJBetter.deepl.enableLinkProtection) text = convertLinksMarkdownToHTML(text);
  8390. if (OJBetter.deepl.config.apiGenre == 'api-free') {
  8391. rawData = await translate_deepl_api_free(text);
  8392. } else if (OJBetter.deepl.config.apiGenre == 'api-pro') {
  8393. rawData = await translate_deepl_api_pro(text);
  8394. }
  8395. if (OJBetter.deepl.enableEmphasisProtection) rawData.text = convertBoldHTMLToMarkdown(rawData.text);
  8396. if (OJBetter.deepl.enableLinkProtection) rawData.text = convertLinksHTMLToMarkdown(rawData.text);
  8397. }
  8398. }
  8399. } else if (transServer == "iflyrec") {
  8400. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8401. rawData = await translate_iflyrec(text);
  8402. } else if (transServer == "youdao") {
  8403. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8404. rawData = await translate_youdao_mobile(text);
  8405. } else if (transServer == "google") {
  8406. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8407. rawData = await translate_gg(text);
  8408. } else if (transServer == "caiyun") {
  8409. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.basic', { ns: 'translator', server: servername })}`, is_renderLaTeX);
  8410. rawData = await translate_caiyun(text);
  8411. } else if (transServer == "openai") {
  8412. translateResult.translateDiv.updateTranslateDiv(`${i18next.t('transingTip.openai', { ns: 'translator', openai_name: OJBetter.chatgpt.config.name })}${!OJBetter.chatgpt.isStream
  8413. ? i18next.t('transingTip.openai_isStream', { ns: 'translator' }) : ""}`,
  8414. is_renderLaTeX);
  8415. if (OJBetter.chatgpt.isStream) {
  8416. // 流式传输
  8417. rawData = await translate_openai_stream(text, translateResult.translateDiv);
  8418. } else {
  8419. // 普通模式
  8420. rawData = await translate_openai(text);
  8421. }
  8422. }
  8423. translatedText = rawData.text;
  8424. if (!rawData.done) {
  8425. translateResult.status = "error";
  8426. }
  8427. } catch (e) {
  8428. translateResult.status = "error";
  8429. rawData.message = i18next.t('error.unexpected', { ns: 'translator' });
  8430. console.warn(e);
  8431. }
  8432. return rawData;
  8433. }
  8434. translateResult.rawData = await translate(realTransServer);
  8435.  
  8436. if (translateResult.status == "error") {
  8437. translateResult.translateDiv.updateTranslateDiv(translateResult.rawData.message);
  8438. return translateResult;
  8439. }
  8440.  
  8441. // 还原latex公式
  8442. translatedText = recoverLatex(translatedText);
  8443.  
  8444. // 注册结果复制按钮
  8445. if (!OJBetter.typeOfPage.is_oldLatex && !OJBetter.typeOfPage.is_acmsguru) {
  8446. translateResult.translateDiv.registerCopyButtonEvent(translatedText);
  8447. } else {
  8448. translateResult.translateDiv.disableCopyButton();
  8449. }
  8450.  
  8451. // 翻译结果格式化
  8452. translatedText = formatText(translatedText);
  8453.  
  8454. // 保存翻译结果
  8455. if ((OJBetter.typeOfPage.is_problem || OJBetter.typeOfPage.is_completeProblemset) && OJBetter.translation.memory.enabled) {
  8456. // OJBetter.translation.memory.ttTree.refreshNode(".ttypography"); // 刷新当前页面.ttypography元素的结构树实例
  8457. OJBetter.translation.memory.ttTree.refreshNode("#task-statement"); // 刷新当前页面.ttypography元素的结构树实例
  8458. OJBetter.translation.memory.ttTree.addTransResultMap(id, translatedText);
  8459. updateTransDBData(OJBetter.translation.memory.ttTree.getNodeData(), OJBetter.translation.memory.ttTree.getTransResultMap()); // 更新翻译结果到transDB
  8460. }
  8461.  
  8462. // 翻译结果面板更新
  8463. translateResult.translateDiv.updateTranslateDiv(translatedText, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru));
  8464.  
  8465. return translateResult;
  8466. }
  8467.  
  8468. /**
  8469. * 题目页相关链接栏
  8470. */
  8471. class ProblemPageLinkbar {
  8472. constructor() {
  8473. this.containerElement = this.createToolbar();
  8474. this.commandInvoker = new CommandInvoker();
  8475. }
  8476.  
  8477. /**
  8478. * 创建工具栏
  8479. */
  8480. createToolbar() {
  8481. // const toolbarElement = $("<div>").attr("id", "problemToolbar").insertBefore($(".problemindexholder"));
  8482. const toolbarElement = $("<div>").attr("id", "problemToolbar").insertBefore($(".h2"));
  8483. return new DOMContainer(toolbarElement);
  8484. }
  8485.  
  8486. /**
  8487. * 添加按钮
  8488. * @param {string} id 按钮id
  8489. * @param {string} url 按钮链接
  8490. * @param {string} text 按钮文字
  8491. * @param {JQueryObject} icon 按钮图标
  8492. * @param {string} iconHeight 图标高度
  8493. * @returns {object} 按钮对象
  8494. */
  8495. addLinkButton(id, url, text, icon = $('<div>'), iconHeight = "22px") {
  8496. const linkElement = $("<a>")
  8497. .attr("href", url)
  8498. .attr("target", "_blank")
  8499. .addClass("ojb_btn")
  8500. .attr("id", id);
  8501.  
  8502. linkElement.append(icon);
  8503. icon.css("height", iconHeight);
  8504.  
  8505. const textSpan = $("<span>").html(text);
  8506. linkElement.append(textSpan);
  8507.  
  8508. this.commandInvoker.execute(new AddElementCommand(this.containerElement, linkElement));
  8509. return {
  8510. element: linkElement,
  8511. text: textSpan,
  8512. icon: icon
  8513. };
  8514. }
  8515.  
  8516. /**
  8517. * 更新链接
  8518. * @param {object} button 按钮对象
  8519. * @param {string} url 按钮链接
  8520. */
  8521. updateUrl(button, url) {
  8522. button.element.attr("href", url);
  8523. }
  8524.  
  8525. /**
  8526. * 更新文字
  8527. * @param {object} button 按钮对象
  8528. * @param {string} text 按钮文字
  8529. */
  8530. updateText(button, text) {
  8531. button.text.html(text);
  8532. }
  8533.  
  8534. /**
  8535. * 设置文字为粗体
  8536. * @param {object} button 按钮对象
  8537. */
  8538. setBold(button) {
  8539. button.text.css("font-weight", "bold");
  8540. }
  8541.  
  8542. /**
  8543. * 更新图标
  8544. * @param {object} button 按钮对象
  8545. * @param {JQueryObject} icon 按钮图标
  8546. * @param {string} iconHeight 图标高度
  8547. */
  8548. updateIcon(button, icon, iconHeight = "16px") {
  8549. button.icon.remove();
  8550. button.text.prepend(icon);
  8551. icon.css("height", iconHeight);
  8552. button.icon = icon;
  8553. }
  8554.  
  8555. /**
  8556. * 添加类
  8557. * @param {object} button 按钮对象
  8558. * @param {string} className 类名
  8559. */
  8560. addClass(button, className) {
  8561. button.element.addClass(className);
  8562. }
  8563.  
  8564. /**
  8565. * 禁用链接按钮
  8566. * @param {object} button 按钮对象
  8567. */
  8568. disableButton(button) {
  8569. button.element.addClass("disabled");
  8570. }
  8571.  
  8572. /**
  8573. * 启用链接按钮
  8574. * @param {object} button 按钮对象
  8575. */
  8576. enableButton(button) {
  8577. button.element.removeClass("disabled");
  8578. }
  8579. }
  8580.  
  8581. /**
  8582. * 获取题目的id
  8583. * @param {String} url 题目的链接
  8584. * @returns 题目的id,形如2000A
  8585. */
  8586. function getProblemId(url) {
  8587. const regex = /\/contests\/([A-Za-z\d]+)\/tasks\/([A-Za-z\d\_]+)/;
  8588. const matchResult = url.match(regex);
  8589. return matchResult && matchResult.length >= 3
  8590. ? `${matchResult[2]}`
  8591. : '';
  8592. };
  8593.  
  8594. /**
  8595. * 跳转到洛谷
  8596. * @param {ProblemPageLinkbar} problemToolbar
  8597. */
  8598. async function CF2luogu(problemToolbar) {
  8599. const url = window.location.href;
  8600. const problemId = getProblemId(url);
  8601. const luoguButton = problemToolbar.addLinkButton(
  8602. "luoguButton",
  8603. "https://www.luogu.com.cn/",
  8604. i18next.t('state.loading', { ns: 'button' }),
  8605. $("<img>").attr("src", "https://cdn.luogu.com.cn/fe/logo.png")
  8606. );
  8607.  
  8608. const checkLinkExistence = async (url) => {
  8609. return OJB_promiseRetryWrapper(async () => {
  8610. const response = await OJB_GMRequest({
  8611. method: "GET",
  8612. url
  8613. });
  8614. return !response.responseText.match(/出错了/g);
  8615. }, {
  8616. maxRetries: 3,
  8617. retryInterval: 1000
  8618. });
  8619. };
  8620.  
  8621. const LuoguUrl = `https://www.luogu.com.cn/problem/AT_${problemId}`;
  8622. try {
  8623. const result = await checkLinkExistence(LuoguUrl);
  8624. if (problemId && result) {
  8625. problemToolbar.updateText(luoguButton, "");
  8626. problemToolbar.updateUrl(luoguButton, LuoguUrl);
  8627. } else {
  8628. problemToolbar.updateText(luoguButton, i18next.t('state.404', { ns: 'button' }));
  8629. problemToolbar.disableButton(luoguButton);
  8630. }
  8631. } catch (error) {
  8632. if (error instanceof OJB_GMError && error.type == "error") {
  8633. problemToolbar.updateText(luoguButton, i18next.t('state.netError', { ns: 'button' }));
  8634. problemToolbar.disableButton(luoguButton);
  8635. }
  8636. }
  8637. }
  8638.  
  8639. /**
  8640. * 跳转到 Virtual Judge
  8641. * @param {ProblemPageLinkbar} problemToolbar
  8642. */
  8643. async function CF2vjudge(problemToolbar) {
  8644. const url = window.location.href;
  8645. const problemId = getProblemId(url);
  8646. const vjudgeButton = problemToolbar.addLinkButton(
  8647. "vjudgeButton",
  8648. "https://vjudge.net/",
  8649. i18next.t('state.loading', { ns: 'button' }),
  8650. $("<img>").attr("src", "https://aowuucdn.oss-accelerate.aliyuncs.com/vjudge.ico")
  8651. );
  8652.  
  8653. const checkLinkExistence = async (url) => {
  8654. return OJB_promiseRetryWrapper(async () => {
  8655. const response = await OJB_GMRequest({
  8656. method: "HEAD",
  8657. url: url,
  8658. });
  8659. if (response.status >= 200 && response.status < 300) return true;
  8660. else if (response.status == 404) return false;
  8661. else throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  8662. }, {
  8663. maxRetries: 3,
  8664. retryInterval: 1000
  8665. });
  8666. };
  8667.  
  8668. const VjudgeUrl = `https://vjudge.net/problem/AtCoder-${problemId}`;
  8669. try {
  8670. const result = await checkLinkExistence(VjudgeUrl);
  8671. if (problemId && result) {
  8672. problemToolbar.updateText(vjudgeButton, "VJudge");
  8673. problemToolbar.updateUrl(vjudgeButton, VjudgeUrl);
  8674. } else {
  8675. problemToolbar.updateText(vjudgeButton, i18next.t('state.404', { ns: 'button' }));
  8676. problemToolbar.disableButton(vjudgeButton);
  8677. }
  8678. } catch (error) {
  8679. if (error instanceof OJB_GMError && error.type == "error") {
  8680. problemToolbar.updateText(vjudgeButton, i18next.t('state.netError', { ns: 'button' }));
  8681. problemToolbar.disableButton(vjudgeButton);
  8682. }
  8683. }
  8684. }
  8685.  
  8686. // RatingClass
  8687. const ratingClassMap = {
  8688. NaN: "rating_by_clist_colorNaN",
  8689. 0: "rating_by_clist_color0",
  8690. 1200: "rating_by_clist_color1",
  8691. 1400: "rating_by_clist_color2",
  8692. 1600: "rating_by_clist_color3",
  8693. 1900: "rating_by_clist_color4",
  8694. 2100: "rating_by_clist_color5",
  8695. 2300: "rating_by_clist_color6",
  8696. 2400: "rating_by_clist_color7",
  8697. 2600: "rating_by_clist_color8",
  8698. 3000: "rating_by_clist_color9"
  8699. };
  8700. const cssMap = {
  8701. "rating_by_clist_colorNaN": "#cccccc",
  8702. "rating_by_clist_color0": "#808080",
  8703. "rating_by_clist_color1": "#73e473",
  8704. "rating_by_clist_color2": "#77ddbb",
  8705. "rating_by_clist_color3": "#aaaaff",
  8706. "rating_by_clist_color4": "#ff88ff",
  8707. "rating_by_clist_color5": "#ffcc88",
  8708. "rating_by_clist_color6": "#ffbb55",
  8709. "rating_by_clist_color7": "#ff7777",
  8710. "rating_by_clist_color8": "#ff3333",
  8711. "rating_by_clist_color9": "#aa0000"
  8712. };
  8713. // TODO 7
  8714. /**
  8715. * clist 访问有效性检查
  8716. * @param {boolean} onlyCookie 是否只检查Cookie
  8717. * @returns {Promise<boolean>} 是否有效
  8718. */
  8719. async function validateClistConnection(onlyCookie = false) {
  8720. const clistApiUrl = "https://clist.by:443/api/v4/contest/?limit=1&resource_id=1";
  8721. const requestOptions = {
  8722. method: "GET",
  8723. url: clistApiUrl,
  8724. timeout: 5000,
  8725. };
  8726.  
  8727. // 尝试发送请求
  8728. async function tryRequest(options) {
  8729. try {
  8730. const response = await OJB_GMRequest(options);
  8731. if (response.status === 200) {
  8732. return { ok: true };
  8733. } else if (response.status === 401) {
  8734. throw new Error('unauthorized');
  8735. } else if (response.status === 404) {
  8736. throw new Error('not_found');
  8737. } else {
  8738. throw new Error('other_error');
  8739. }
  8740. } catch (error) {
  8741. console.warn(`Error accessing clist.by: ${error.message}`);
  8742. return { ok: false, error: error.message };
  8743. }
  8744. }
  8745.  
  8746. // 尝试携带Key发送请求
  8747. let result = await tryRequest(requestOptions);
  8748. if (!onlyCookie && !result.ok) {
  8749. requestOptions.headers = { "Authorization": OJBetter.clist.authorization };
  8750. result = await tryRequest(requestOptions);
  8751. }
  8752.  
  8753. // 根据结果显示错误信息
  8754. if (!result.ok) {
  8755. let errorType = result.error;
  8756. const loadingMessage = new LoadingMessage();
  8757. let state;
  8758. if (errorType === 'not_found') {
  8759. state = i18next.t('error.clist.404', { ns: 'alert' });
  8760. } else if (errorType === 'unauthorized') {
  8761. state = i18next.t('error.clist.cookie', { ns: 'alert' });
  8762. } else {
  8763. state = i18next.t('error.clist.other', { ns: 'alert' });
  8764. }
  8765. loadingMessage.updateStatus(`${OJBetter.state.name} —— ${state}`, 'error');
  8766. }
  8767. return result.ok;
  8768. }
  8769.  
  8770. /**
  8771. * 创建Rating相关css
  8772. * @param {boolean} [hasBorder=true] 是否有边框
  8773. */
  8774. function creatRatingCss(hasBorder = true) {
  8775. const defaultBorderColor = '#dcdfe6';
  8776. let dynamicCss = "";
  8777. let hoverSelector = OJBetter.clist.ratingHidden ? ":hover" : "";
  8778. for (let cssClass in cssMap) {
  8779. dynamicCss += `a.${cssClass}${hoverSelector}, a.${cssClass}${hoverSelector}:link {\n`;
  8780. let borderColor = hasBorder ? cssMap[cssClass] : defaultBorderColor;
  8781. dynamicCss += ` color: ${cssMap[cssClass]} ${OJBetter.clist.ratingHidden ? "!important" : ""};\n`;
  8782. dynamicCss += `}\n`;
  8783. }
  8784. GM_addStyle(dynamicCss);
  8785. if (OJBetter.clist.ratingHidden) {
  8786. GM_addStyle(`
  8787. #clistButton {
  8788. color: #ffffff00;
  8789. }
  8790. `);
  8791. }
  8792. }
  8793.  
  8794. /**
  8795. * 模拟clist网页访问获取rating
  8796. * @param {string} problem 题目名称
  8797. * @param {string} problem_url 题目链接
  8798. * @param {string} contest 比赛名称
  8799. * @returns {Promise<{rating: number, problem: string}>} 题目难度
  8800. */
  8801. async function getRatingFromHTML(problem, problem_url, contest = null) {
  8802. // 去除题目名称中的括号,以及首尾的空白符
  8803. problem = problem.replace(/\([\s\S]*?\)/g, '').trim();
  8804.  
  8805. return OJB_promiseRetryWrapper(async () => {
  8806. const queryString = `search=${encodeURIComponent(problem)}&resource=1`;
  8807. const response = await OJB_GMRequest({
  8808. method: 'GET',
  8809. url: `https://clist.by/problems/?${queryString}`,
  8810. });
  8811.  
  8812. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  8813. const html = response.responseText;
  8814. const cleanedHtml = html.replace(/src=(.|\s)*?"/g, '');
  8815. const parser = new DOMParser();
  8816. const doc = parser.parseFromString(cleanedHtml, 'text/html');
  8817. const trs = doc.querySelectorAll('table tbody tr');
  8818.  
  8819. for (let tr of trs) {
  8820. const rating = tr.querySelector('.problem-rating-column').textContent.trim();
  8821. const linkElement = tr.querySelector('.problem-name-column a:nth-of-type(2)');
  8822. let link = linkElement ? OJB_cleanLink(linkElement) : null;
  8823.  
  8824. if (link === problem_url || link === problem_url + '/') {
  8825. return {
  8826. rating: parseInt(rating),
  8827. problem: problem
  8828. };
  8829. } else if (contest !== null) {
  8830. const contestTitles = [...tr.querySelectorAll('.problem-name-column .pull-right a[title], .problem-name-column .pull-right span[title]')].map(el => el.title);
  8831. if (contestTitles.includes(contest)) {
  8832. return {
  8833. rating: parseInt(rating),
  8834. problem: problem
  8835. };
  8836. }
  8837. }
  8838. }
  8839. console.warn(`No data found for the question: ${problem}`);
  8840. }, {
  8841. maxRetries: 3,
  8842. retryInterval: 500
  8843. });
  8844. }
  8845.  
  8846. /**
  8847. * 从clist API获取题目的rating
  8848. * @param {string} problem_name 题目名
  8849. * @param {string} problem_url 题目链接
  8850. * @returns {Promise<number>} 题目rating
  8851. *
  8852. * 使用两个Map对象来存储和快速访问题目信息:
  8853. * - problemsMap: 通过题目的URL作为键来存储题目信息。
  8854. * - nameMap: 通过题目的名称作为键来存储题目信息。
  8855. *
  8856. * 每个题目信息是一个对象,包含以下属性:
  8857. * @typedef {Object} ProblemInfo
  8858. * @property {string} name 题目名称
  8859. * @property {string} url 题目URL
  8860. * @property {number} rating 题目评分,如果没有评分信息则为NaN
  8861. */
  8862. async function getRatingFromApi_problem(problem_name, problem_url) {
  8863. return OJB_promiseRetryWrapper(async () => {
  8864. const response = await OJB_GMRequest({
  8865. method: "GET",
  8866. // url: `https://clist.by:443/api/v4/problem/?name=${encodeURIComponent(problem_name)}&resource__regex=codeforces.com`,
  8867. url: `https://clist.by:443/api/v4/problem/?url__regex=${encodeURIComponent(problem_name)}&resource__regex=atcoder.jp`,
  8868. headers: { "Authorization": OJBetter.clist.authorization }
  8869. });
  8870.  
  8871. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  8872. let data = JSON.parse(response.responseText);
  8873. /**
  8874. * 使用题目的URL作为键来存储题目信息。
  8875. * @type {Map<string, ProblemInfo>}
  8876. */
  8877. let problemsMap = new Map();
  8878.  
  8879. /**
  8880. * 使用题目的名称作为键来存储题目信息。
  8881. * @type {Map<string, ProblemInfo>}
  8882. */
  8883. let nameMap = new Map();
  8884.  
  8885. data.objects.forEach(problem => {
  8886. /** @type {ProblemInfo} 题目信息*/
  8887. let problemInfo = {
  8888. name: problem.name,
  8889. url: problem.url,
  8890. rating: problem.rating ? problem.rating : NaN
  8891. };
  8892. problemsMap.set(OJB_cleanLink(problem.url), problemInfo);
  8893. nameMap.set(problem.name, problemInfo);
  8894. });
  8895.  
  8896. if (problemsMap.has(problem_url)) {
  8897. return problemsMap.get(problem_url).rating;
  8898. } else if (nameMap.has(problem_name)) {
  8899. return nameMap.get(problem_name).rating;
  8900. } else {
  8901. console.warn('Problem not found in the response');
  8902. }
  8903. }, {
  8904. maxRetries: 5,
  8905. retryInterval: 1000
  8906. });
  8907. }
  8908.  
  8909. /**
  8910. * 获取字符串中的关键词列表
  8911. * @param {string} text 字符串文本
  8912. * @returns {array<string>} 返回关键词列表
  8913. */
  8914. function getKeywords(text) {
  8915. // 定义要过滤掉的高频词
  8916. const highFrequencyWords = ['Educational', 'Codeforces', 'Round', 'Div'];
  8917.  
  8918. // 使用正则表达式替换掉特殊符号(保留空格以便分词)
  8919. const sanitizedText = text.replace(/[^\w\s]|_/g, '').replace(/\s+/g, ' ');
  8920.  
  8921. // 将字符串拆分为单词数组
  8922. const words = sanitizedText.split(' ');
  8923.  
  8924. // 过滤掉高频词和空字符串
  8925. const filteredWords = words.filter(word => {
  8926. return word && highFrequencyWords.indexOf(word) === -1;
  8927. });
  8928.  
  8929. // 返回关键词列表
  8930. return filteredWords;
  8931. }
  8932.  
  8933. /**
  8934. * 根据关键词从 Clist API 中获取实际比赛名称
  8935. * @param {string} contestName 比赛名
  8936. * @param {string} contestUrl 比赛链接
  8937. * @returns {string|null} 该比赛在Clist中的实际名字
  8938. */
  8939. async function getContestNameFromApi(contestName, contestUrl) {
  8940. return OJB_promiseRetryWrapper(async () => {
  8941. const options = {
  8942. method: "GET",
  8943. // url: `https://clist.by:443/api/v4/contest/?resource_id=1&event__regex=${encodeURIComponent(contestName)}`,
  8944. url: `https://clist.by:443/api/v4/contest/?resource_id=93&event__regex=${encodeURIComponent(contestName)}`,
  8945. headers: {
  8946. "Authorization": OJBetter.clist.authorization
  8947. }
  8948. };
  8949.  
  8950. let response = await OJB_GMRequest(options);
  8951.  
  8952. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  8953.  
  8954. let data = JSON.parse(response.responseText);
  8955. let objects = data.objects;
  8956.  
  8957. if (objects.length > 0) {
  8958. for (const contest of objects) {
  8959. // const href = contest.href.replace(/\/contests\//i, '/contest/'); // 链接可能是contests而不是contest,换回来
  8960. const href = contest.href;
  8961. if (OJB_cleanLink(href) == contestUrl) {
  8962. return contest.event;
  8963. }
  8964. }
  8965. }
  8966. return null;
  8967. }, {
  8968. maxRetries: 5,
  8969. retryInterval: 1000
  8970. });
  8971. }
  8972.  
  8973. /**
  8974. * 获取在clist中的实际比赛名称
  8975. * @param {string} contestName 待搜索的比赛名称
  8976. * @param {string} contestUrl 比赛的url
  8977. * @returns {Promise<string|null>} 在clist中的实际比赛名称,如果没有找到,则返回null
  8978. */
  8979. async function getActualContestName(contestName, contestUrl) {
  8980. // 首先尝试使用完整的比赛名称进行搜索
  8981. let actualContestName = await getContestNameFromApi(contestName, contestUrl);
  8982. if (actualContestName) {
  8983. return actualContestName;
  8984. }
  8985.  
  8986. // 如果使用完整名称没有找到,则尝试使用关键词进行搜索
  8987. const keywords = getKeywords(contestName);
  8988. const maxKeywordAttempts = 1; // 最多尝试到第几个关键词(因为Clist API有频率限制)
  8989. for (let i = 0; i < Math.min(keywords.length, maxKeywordAttempts); i++) {
  8990. actualContestName = await getContestNameFromApi(keywords[i], contestUrl);
  8991. if (actualContestName) {
  8992. return actualContestName;
  8993. }
  8994. }
  8995.  
  8996. // 如果全部尝试后仍没有找到,返回null
  8997. return null;
  8998. }
  8999.  
  9000. /**
  9001. * 从clist API获取比赛题目集的rating
  9002. * @param {string} contestName 比赛名
  9003. * @returns {Promise<Map<string, number>>} 题目rating
  9004. */
  9005. async function getRatingFromApi_contest(contestName, contestUrl) {
  9006. const actualContestName = await getActualContestName(contestName, contestUrl);
  9007. return OJB_promiseRetryWrapper(async () => {
  9008. const options = {
  9009. method: "GET",
  9010. url: `https://clist.by:443/api/v4/contest/?resource_id=93&with_problems=true&event=${encodeURIComponent(actualContestName)}`,
  9011. headers: {
  9012. "Authorization": OJBetter.clist.authorization
  9013. }
  9014. };
  9015.  
  9016. let response = await OJB_GMRequest(options);
  9017.  
  9018. if (!response.responseText) throw new OJB_GMError('network', 'An unknown network error occurred!', response);
  9019.  
  9020. let data = JSON.parse(response.responseText);
  9021. let objects = data.objects;
  9022. let problemsMap = new Map();
  9023.  
  9024. if (objects.length > 0 && objects[0].problems) {
  9025. objects[0].problems.forEach(problem => {
  9026. problemsMap.set(OJB_cleanLink(problem.url), problem.rating ? problem.rating : NaN);
  9027. });
  9028. }
  9029.  
  9030. return problemsMap;
  9031. }, {
  9032. maxRetries: 5,
  9033. retryInterval: 1000
  9034. });
  9035. }
  9036.  
  9037. /**
  9038. * 根据rating获取对应的颜色class名
  9039. * @param {number} rating 题目rating
  9040. * @returns {string} 颜色class名
  9041. */
  9042. function getClassNameByRating(rating) {
  9043. let className = "rating_by_clist_color9";
  9044. if (Number.isNaN(rating)) {
  9045. className = "rating_by_clist_colorNaN";
  9046. } else {
  9047. let keys = Object.keys(ratingClassMap);
  9048. for (let i = 0; i < keys.length; i++) {
  9049. if (rating < keys[i]) {
  9050. className = ratingClassMap[keys[i - 1]];
  9051. break;
  9052. }
  9053. }
  9054. }
  9055. return className;
  9056. }
  9057.  
  9058. /**
  9059. * problem题目页显示Rating
  9060. * @param {ProblemPageLinkbar} problemToolbar
  9061. * @returns {Promise<void>}
  9062. */
  9063. async function showRatingByClist_problem(problemToolbar) {
  9064. // 题目名
  9065. // const problem = $('.header .title').eq(0).text().replace(/[\s\S]*?. /, '');
  9066. // if (OJBetter.typeOfPage.is_acmsguru) problem = $('h4').eq(0).text().replace(/[\s\S]*?. /, '');
  9067. const url = window.location.href;
  9068. const problemId = getProblemId(url);
  9069.  
  9070. // 创建Rating按钮元素
  9071. creatRatingCss(false);
  9072. // TODO
  9073. // const clistButton = problemToolbar.addLinkButton(
  9074. // 'clistButton',
  9075. // `https://clist.by/problems/?search=${problem}&resource=1`,
  9076. // i18next.t('state.wait', { ns: 'button' }),
  9077. // $("<img>").attr("src", "https://clist.by/static/img/logo-48.png"),
  9078. // "15px"
  9079. // );
  9080. const clistButton = problemToolbar.addLinkButton(
  9081. 'clistButton',
  9082. `https://clist.by/problems/?search=${problemId}&resource=93`,
  9083. i18next.t('state.wait', { ns: 'button' }),
  9084. $("<img>").attr("src", "https://clist.by/static/img/logo-48.png"),
  9085. "15px"
  9086. );
  9087.  
  9088. // 检测clist连接
  9089. if (!await validateClistConnection()) {
  9090. problemToolbar.updateText(clistButton, i18next.t('state.netError', { ns: 'button' }));
  9091. return;
  9092. }
  9093.  
  9094. // 题目链接
  9095. let problem_url = window.location.href;
  9096. if (problem_url.includes('/contest/')) {
  9097. problem_url = problem_url.replace(/\/contest\/(\d+)\/problem\/(\w+)[^\w]*/, '/contest/$1/problem/$2');
  9098. } else {
  9099. problem_url = problem_url.replace(/\/problemset\/problem\/(\d+)\/(\w+)/, '/contest/$1/problem/$2');
  9100. }
  9101. if (OJBetter.typeOfPage.is_mSite) problem_url = problem_url.replace(/\/\/(\w+).codeforces.com/, '//codeforces.com'); // 轻量站
  9102.  
  9103. // 比赛名
  9104. // let contest = $('#sidebar').children().first().find('.rtable th').first().text();
  9105.  
  9106. // rating
  9107. problemToolbar.updateText(clistButton, i18next.t('state.loading', { ns: 'button' }));
  9108. let rating = await getRatingFromApi_problem(problemId, problem_url);
  9109. if (rating) {
  9110. let className = getClassNameByRating(rating);
  9111. problemToolbar.updateText(clistButton, rating);
  9112. problemToolbar.setBold(clistButton);
  9113. problemToolbar.addClass(clistButton, className);
  9114. } else {
  9115. problemToolbar.updateText(clistButton, i18next.t('state.404', { ns: 'button' }));
  9116. problemToolbar.disableButton(clistButton);
  9117. }
  9118. }
  9119.  
  9120. /**
  9121. * contest页显示Rating
  9122. * @returns {Promise<void>}
  9123. */
  9124. async function showRatingByClist_contest() {
  9125. // 创建Rating显示框
  9126. creatRatingCss();
  9127. let ratingBadges = {};
  9128. // $('.datatable .id.left').each(function () {
  9129. // let href = 'https://codeforces.com' + $(this).find('a').attr('href');
  9130. // let badge = OJB_safeCreateJQElement(`<a id="clistButton" class="ratingBadge">${i18next.t('state.wait', { ns: 'button' })}</a>`);
  9131. // $(this).find('a').after(badge);
  9132. // ratingBadges[href] = badge;
  9133. // });
  9134. $('table tbody tr').each(function () {
  9135. let href = 'https://atcoder.jp' + $(this).find('a').attr('href');
  9136. let badge = OJB_safeCreateJQElement(`<a id="clistButton" class="ratingBadge">${i18next.t('state.wait', { ns: 'button' })}</a>`);
  9137. $(this).find('a:first').after(badge);
  9138. ratingBadges[href] = badge;
  9139. });
  9140.  
  9141. // 检测clist连接
  9142. if (!await validateClistConnection()) {
  9143. for (let href in ratingBadges) {
  9144. ratingBadges[href].text('error').addClass('ratingBadge_error');
  9145. }
  9146. return;
  9147. }
  9148.  
  9149. // 显示loading
  9150. for (let href in ratingBadges) {
  9151. ratingBadges[href].text(i18next.t('state.loading', { ns: 'button' })).addClass('ratingBadge_loading');
  9152. }
  9153.  
  9154. // 获取Rating
  9155. // let contestName = $('#sidebar').children().first().find('.rtable th').first().text();
  9156. let contestName = window.location.href.match(/\/contests\/[^\/]*?(\d+)\/tasks/)?.[1];
  9157. // let contestUrl = OJB_cleanLink(window.location.href);
  9158. let contestUrl = OJB_cleanLink(window.location.href.replace(/\/tasks\/?.*$/, ''));
  9159. try {
  9160. let problemsMap = await getRatingFromApi_contest(contestName, contestUrl);
  9161.  
  9162. // 填充数据
  9163. for (let href in ratingBadges) {
  9164. if (problemsMap.has(href)) {
  9165. let rating = problemsMap.get(href);
  9166. let className = getClassNameByRating(rating);
  9167. ratingBadges[href].text(rating).addClass(className);
  9168. } else {
  9169. ratingBadges[href].text(i18next.t('state.404', { ns: 'button' })).addClass('ratingBadge_no');
  9170. }
  9171. }
  9172. } catch (error) {
  9173. // 填充数据
  9174. for (let href in ratingBadges) {
  9175. ratingBadges[href].text(i18next.t('state.netError', { ns: 'button' })).addClass('ratingBadge_no');
  9176. }
  9177. console.warn(error);
  9178. }
  9179. }
  9180.  
  9181. /**
  9182. * problemset页显示Rating
  9183. * @returns {Promise<void>}
  9184. */
  9185. async function showRatingByClist_problemset() {
  9186. creatRatingCss();
  9187. let ratingBadges = [];
  9188. const $problems = $('.problems');
  9189. const $trs = $problems.find('tbody tr:gt(0)');
  9190.  
  9191. // 先创建Rating显示框,并将关系存进数组ratingBadges
  9192. for (let i = 0; i < $trs.length; i++) {
  9193. const $tds = $($trs[i]).find('td');
  9194. const $firstDiv = $($tds[1]).find('div:first');
  9195. let problem = $firstDiv.text();
  9196. let problem_url = $firstDiv.find('a').attr('href');
  9197. problem_url = problem_url.replace(/^\/problemset\/problem\/(\d+)\/(\w+)/, 'https://codeforces.com/contest/$1/problem/$2');
  9198.  
  9199. const ratingBadge = OJB_safeCreateJQElement(`<a id="clistButton" class="ratingBadge"></a>`);
  9200. const rating = OJB_safeCreateJQElement(`<span class="rating">${i18next.t('state.wait', { ns: 'button' })}</span>`);
  9201. ratingBadge.append(rating);
  9202. $($tds[0]).find('a').after(ratingBadge);
  9203. ratingBadges.push({ ratingBadge, rating, problem, problem_url });
  9204. }
  9205.  
  9206. // 检测clist连接
  9207. if (!await validateClistConnection()) {
  9208. for (let i = 0; i < rating.length; i++) {
  9209. ratingBadges[i].rating.text(i18next.t('state.netError', { ns: 'button' }));
  9210. }
  9211. return;
  9212. }
  9213.  
  9214. // 每次只获取3个rating
  9215. for (let i = 0; i < ratingBadges.length; i += 3) {
  9216. const promises = [];
  9217. const endIndex = Math.min(i + 3, ratingBadges.length);
  9218.  
  9219. for (let j = i; j < endIndex; j++) {
  9220. const ratingBadge = ratingBadges[j];
  9221. // 显示请求中
  9222. ratingBadge.rating.text(i18next.t('state.loading', { ns: 'button' }));
  9223. promises.push(getRatingFromHTML(ratingBadge.problem, ratingBadge.problem_url).catch(error => console.warn(error)));
  9224. }
  9225.  
  9226. const results = await Promise.all(promises);
  9227.  
  9228. for (let j = i; j < endIndex; j++) {
  9229. const result = results[j - i];
  9230. const ratingBadge = ratingBadges[j];
  9231. if (result) {
  9232. let className = getClassNameByRating(result.rating);
  9233. ratingBadge.ratingBadge.addClass(className);
  9234. ratingBadge.rating.text(result.rating);
  9235. } else {
  9236. ratingBadge.rating.text(i18next.t('state.404', { ns: 'button' }));
  9237. }
  9238. }
  9239. }
  9240. }
  9241.  
  9242. /**
  9243. * 存放编辑器语言select的值与Monaco语言对应关系的map.
  9244. * @type {Object.<string, string>}
  9245. */
  9246. // const value_monacoLanguageMap = {
  9247. // "4": "pascal", "6": "php", "7": "python", "9": "csharp", "13": "perl", "20": "scala", "31": "python",
  9248. // "32": "go", "34": "javascript", "36": "java", "40": "python", "41": "python", "43": "cpp",
  9249. // "50": "cpp", "51": "pascal", "52": "cpp", "54": "cpp", "55": "javascript", "59": "cpp", "60": "java",
  9250. // "61": "cpp", "65": "csharp", "67": "ruby", "70": "python", "73": "cpp", "74": "java", "75": "rust",
  9251. // "77": "kotlin", "79": "csharp", "80": "cpp", "83": "kotlin", "87": "java"
  9252. // };
  9253. const value_monacoLanguageMap = {
  9254. "5001": "cpp", "5002": "go", "5003": "csharp", "5004": "kotlin", "5005": "java",
  9255. "5006": "nim", "5007": "text", "5008": "text", "5009": "javascript", "5010": "javascript",
  9256. "5011": "r", "5012": "d", "5013": "d", "5014": "swift", "5015": "dart",
  9257. "5016": "php", "5017": "cpp", "5018": "ruby", "5019": "crystal", "5020": "text",
  9258. "5021": "fsharp", "5022": "julia", "5023": "sh", "5024": "text", "5025": "haskell",
  9259. "5026": "fortran", "5027": "lua", "5028": "cpp", "5029": "lisp", "5030": "cobol",
  9260. "5031": "cpp", "5032": "sh", "5033": "python", "5034": "sh", "5035": "text",
  9261. "5036": "text", "5037": "perl", "5038": "sh", "5039": "text", "5040": "text",
  9262. "5041": "pascal", "5042": "csharp", "5043": "lua", "5044": "prolog", "5045": "sh",
  9263. "5046": "scheme", "5047": "scala", "5048": "vbscript", "5049": "text", "5050": "clojure",
  9264. "5051": "erlang", "5052": "typescript", "5053": "cpp", "5054": "rust", "5055": "python",
  9265. "5056": "scala", "5057": "text", "5058": "typescript", "5059": "ocaml", "5060": "raku",
  9266. "5061": "text", "5062": "lisp", "5063": "python", "5064": "clojure", "5065": "text",
  9267. "5066": "text", "5067": "text", "5068": "ada", "5069": "text", "5070": "text",
  9268. "5071": "clojure", "5072": "cpp", "5073": "cpp", "5074": "text", "5075": "lisp",
  9269. "5076": "text", "5077": "d", "5078": "python", "5079": "text", "5080": "text",
  9270. "5081": "ocaml", "5082": "python", "5083": "matlab", "5084": "haxe", "5085": "elixir",
  9271. "5086": "text", "5087": "text", "5088": "lisp", "5089": "text", "5090": "cobol"
  9272. };
  9273.  
  9274. /**
  9275. * 更新代码提交页的HTML
  9276. * @param {string} submitUrl 提交页面的URL
  9277. * @param {string} cacheKey 本地缓存的键名
  9278. * @returns {Promise<jQuery<HTMLElement>>} 返回 jQuery 包装的 HTML 元素
  9279. */
  9280. async function CloneOriginalHTML(submitUrl, cacheKey) {
  9281. return OJB_promiseRetryWrapper(async () => {
  9282. const response = await OJB_GMRequest({
  9283. method: 'GET',
  9284. url: submitUrl
  9285. });
  9286. const html = response.responseText;
  9287. const parser = new DOMParser();
  9288. const doc = parser.parseFromString(html, 'text/html');
  9289. const cloneHTML = $(doc.body).html();
  9290. localStorage.setItem(cacheKey, html);
  9291. return $(cloneHTML);
  9292. }, {
  9293. maxRetries: 5,
  9294. retryInterval: 1000,
  9295. errorHandler: (err) => {
  9296. console.error('A network error occurred while retrieving the HTML for the code submission page.', submitUrl);
  9297. }
  9298. });
  9299. }
  9300.  
  9301. /**
  9302. * 获取代码提交页的HTML元素
  9303. * @param {string} submitUrl
  9304. * @returns {Promise<jQuery>}
  9305. */
  9306. async function getSubmitHTML(submitUrl) {
  9307. const cacheKey = 'OJBetter_CloneOriginalHTML';
  9308. const cookieKey = 'OJBetter_CloneOriginalHTML_time';
  9309. if (OJB_getCookie(cookieKey) === '1') {
  9310. // 存在缓存
  9311. CloneOriginalHTML(submitUrl, cacheKey);
  9312. // 校验
  9313. let cloneHTML = $(localStorage.getItem(cacheKey));
  9314. if (cloneHTML.find('form.submit-form').length > 0) {
  9315. return cloneHTML;
  9316. } else {
  9317. // 存在错误,更新缓存
  9318. console.warn(`Cache error detected!\nattempting to update, cache destination submitUrl:\n${submitUrl}`);
  9319. return await CloneOriginalHTML(submitUrl, cacheKey);
  9320. }
  9321.  
  9322. } else {
  9323. // 没有缓存,更新
  9324. document.cookie = `${cookieKey}=1; path=/`;
  9325. return await CloneOriginalHTML(submitUrl, cacheKey);
  9326. }
  9327. }
  9328.  
  9329. // 代码自动保存
  9330. async function saveCode(url, code) {
  9331. try {
  9332. await OJBetter.common.database.editorCode.put({ url, code });
  9333. return 'Code saved successfully';
  9334. } catch (error) {
  9335. throw new Error('Failed to save code');
  9336. }
  9337. }
  9338.  
  9339. async function getCode(url) {
  9340. try {
  9341. const result = await OJBetter.common.database.editorCode.get(url);
  9342. return result ? result.code : null;
  9343. } catch (error) {
  9344. throw new Error('Failed to get code');
  9345. }
  9346. }
  9347.  
  9348. // 创建代码编辑调试表单元素
  9349. // async function createCodeEditorForm(submitUrl, cloneHTML) {
  9350. async function createCodeEditorForm(submitUrl) {
  9351. // 表单
  9352. let formDiv = $('<form method="post" id="OJBetter_SubmitForm" class="input-output-copier"></form>');
  9353. // $('.ttypography').after(formDiv);
  9354. $('#task-statement').after(formDiv);
  9355. // formDiv.attr('action', submitUrl + "?csrf_token=" + OJBetter.common.at_csrf_token);
  9356. formDiv.attr('action', submitUrl);
  9357. formDiv.attr('method', 'POST');
  9358.  
  9359. // 顶部区域
  9360. let topDiv = OJB_safeCreateJQElement(`<div class="topDiv"></div>`);
  9361. let selectLang = $('#select-lang').clone(); // 语言选择
  9362. // selectLang.css({ 'margin': '10px 0px' }).attr('id', 'programTypeId');
  9363. topDiv.append(selectLang);
  9364. let topRightDiv = OJB_safeCreateJQElement(`<div class="topRightDiv"></div>`);
  9365. topDiv.append(topRightDiv);
  9366. formDiv.append(topDiv);
  9367.  
  9368. // 问题选择/编号
  9369. // let selectProblem = $('<input name="submittedProblemIndex" style="display:none;"></input>');
  9370. // let problemCode;
  9371. // if (OJBetter.typeOfPage.is_acmsguru) {
  9372. // problemCode = $('h4').eq(0).text();
  9373. // let matchResult = problemCode.match(/([A-Z0-9]+)/);
  9374. // problemCode = matchResult[0];
  9375. // } else if (OJBetter.typeOfPage.is_problemset_problem) {
  9376. // let match = window.location.href.match(/\/problem\/([0-9]+?)\/([A-Z0-9]+?)(?!=[A-Z0-9])/);
  9377. // problemCode = match[1] + match[2];
  9378. // selectProblem.attr('name', 'submittedProblemCode');
  9379. // } else {
  9380. // problemCode = $('.header .title').eq(0).text();
  9381. // let matchResult = problemCode.match(/([A-Z0-9]+)/);
  9382. // problemCode = matchResult[0];
  9383. // }
  9384. // selectProblem.val(problemCode);
  9385. let selectProblem = $('input[name="data.TaskScreenName"]').clone();
  9386. formDiv.append(selectProblem);
  9387.  
  9388. // 隐藏的代码记录
  9389. // let sourceDiv = $('<textarea id="sourceCodeTextarea" name="source" style="display: none;"></textarea>');
  9390. let sourceDiv = $('<textarea id="plain-textarea" name="sourceCode" style="display: none;"></textarea>');
  9391. formDiv.append(sourceDiv);
  9392.  
  9393. // 隐藏的crsf token
  9394. let csrfDiv = $(`<input type="hidden" name="csrf_token" value=${OJBetter.common.at_csrf_token}>`);
  9395. formDiv.append(csrfDiv);
  9396.  
  9397. // 代码编辑器
  9398. let editorDiv = $('<div id="OJBetter_editor"></div>');
  9399. formDiv.append(editorDiv);
  9400.  
  9401. // monaco
  9402. let monaco = $('<div id="OJBetter_monaco"></div>');
  9403. editorDiv.append(monaco);
  9404.  
  9405. // 自定义调试
  9406. let customTestDiv = OJB_safeCreateJQElement(`
  9407. <details id="customTestBlock">
  9408. <summary >${i18next.t('customTestBlock.title', { ns: 'codeEditor' })}</summary>
  9409. <div id="customTests" style="min-height: 30px;"></div>
  9410. <div id="control" style="display:flex;">
  9411. <div style="display: flex;margin: 5px;">
  9412. <input type="checkbox" id="onlyCustomTest"}><label for="onlyCustomTest">
  9413. ${i18next.t('customTestBlock.onlyCustom', { ns: 'codeEditor' })}
  9414. </label>
  9415. </div>
  9416. <div style="display: flex;margin: 5px;">
  9417. <input type="checkbox" id="DontShowDiff"}>
  9418. <label for="DontShowDiff">
  9419. ${i18next.t('customTestBlock.DontShowDiff', { ns: 'codeEditor' })}
  9420. </label>
  9421. </div>
  9422. <button type="button" id="addCustomTest">${i18next.t('customTestBlock.add', { ns: 'codeEditor' })}</button>
  9423. </div>
  9424. </details>
  9425. `)
  9426. formDiv.append(customTestDiv);
  9427.  
  9428. // 调试/提交
  9429. let submitDiv = $('<div id="OJBetter_submitDiv"></div>');
  9430. let CompilerArgsInput = $('<input type="text" id="CompilerArgsInput">');
  9431. submitDiv.append(CompilerArgsInput);
  9432.  
  9433. let runButton = OJB_safeCreateJQElement(`
  9434. <button type="button" id="RunTestButton" class="ojb_btn ojb_btn_popover top">
  9435. <i class="iconfont">&#xe6c1;</i>
  9436. <span class="popover_content">${i18next.t('runTestButton.initial', { ns: 'codeEditor' })}</span>
  9437. </button>
  9438. `);
  9439. let submitButton = OJB_safeCreateJQElement(`
  9440. <button id="SubmitButton" class="ojb_btn ojb_btn_popover top" type="submit">
  9441. <i class="iconfont">&#xe633;</i>
  9442. <span class="popover_content">${i18next.t('submitButton', { ns: 'codeEditor' })}</span>
  9443. </button>
  9444. `);
  9445. if (OJBetter.monaco.setting.submitButtonPosition == "bottom") {
  9446. // 添加测试/提交按钮到底部
  9447. submitDiv.append(runButton);
  9448. submitDiv.append(submitButton);
  9449. }
  9450.  
  9451. formDiv.append(submitDiv);
  9452. let CompilerSetting = OJB_safeCreateJQElement(`
  9453. <div id="CompilerSetting"></div>
  9454. `);
  9455. formDiv.append(CompilerSetting);
  9456. let statePanel = OJB_safeCreateJQElement(`
  9457. <div id="statePanel"></div>
  9458. `);
  9459. formDiv.append(statePanel);
  9460.  
  9461. //==================================
  9462. // 去除原有的编辑器
  9463. //==================================
  9464. $('.form-code-submit').remove();
  9465.  
  9466. let from = {
  9467. formDiv: formDiv,
  9468. selectLang: selectLang.find('select:first'),
  9469. topRightDiv: topRightDiv,
  9470. sourceDiv: sourceDiv,
  9471. editorDiv: editorDiv,
  9472. monaco: monaco,
  9473. runButton: runButton,
  9474. submitButton: submitButton,
  9475. submitDiv: submitDiv,
  9476. CompilerSetting: CompilerSetting,
  9477. statePanel: statePanel
  9478. };
  9479. return from;
  9480. }
  9481.  
  9482. // 解析ace格式的补全规则(acwing)
  9483. function parseAceCompleter(rules, range) {
  9484. const suggestions = [];
  9485. if (rules && rules.templates && rules.templates.items) {
  9486. const items = rules.templates.items;
  9487. for (let i = 0; i < items.length; i++) {
  9488. const item = items[i];
  9489. const parts = item.caption.split(' ');
  9490. for (let i = 0; i < parts.length; i++) {
  9491. if (item.value.startsWith(parts[i])) {
  9492. item.value = item.value.replace(parts[i], parts.slice(0, i + 1).join(' '));
  9493. break;
  9494. }
  9495. }
  9496. const completionItem = {
  9497. label: item.caption,
  9498. kind: monaco.languages.CompletionItemKind.Function,
  9499. insertText: item.value,
  9500. range: range
  9501. };
  9502. suggestions.push(completionItem);
  9503. }
  9504. }
  9505. return { suggestions };
  9506. }
  9507.  
  9508. // 解析monaco格式的补全规则
  9509. function parseMonacoCompleter(rules, range) {
  9510. const suggestions_ = [];
  9511. if (rules && rules.suggestions) {
  9512. const suggestion = rules.suggestions;
  9513. for (let i = 0; i < rules.suggestions.length; i++) {
  9514. const item = suggestion[i];
  9515. const completionItem = {
  9516. ...item,
  9517. range: range
  9518. };
  9519. suggestions_.push(completionItem);
  9520. }
  9521. }
  9522. return { suggestions: suggestions_ };
  9523. }
  9524.  
  9525. /**
  9526. * 创建monaco编辑器的一个实例
  9527. */
  9528. async function createMonacoEditor(language, form, support) {
  9529. // 判断monacoLoader是否加载完毕
  9530. async function waitForMonacoLoaderOnload() {
  9531. return new Promise((resolve) => {
  9532. const checkInitialized = () => {
  9533. if (OJBetter.monaco.loaderOnload) {
  9534. resolve();
  9535. } else {
  9536. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  9537. }
  9538. };
  9539. checkInitialized();
  9540. });
  9541. }
  9542. if (!OJBetter.monaco.loaderOnload) await waitForMonacoLoaderOnload();
  9543.  
  9544. /**
  9545. * 通用参数
  9546. */
  9547. var id = 0; // 协议中的id标识
  9548. var workspace = language + "_workspace";
  9549. var rootUri = OJBetter.monaco.lsp.workUri + "/" + workspace;
  9550. // 文件名
  9551. var InstanceID = OJB_getRandomNumber(8).toString();
  9552. var filename = language == "java" ? "hello/src/" + InstanceID : InstanceID;
  9553. // 后缀名
  9554. var fileExtension =
  9555. language === "cpp"
  9556. ? ".cpp"
  9557. : language === "python"
  9558. ? ".py"
  9559. : language === "java"
  9560. ? ".java"
  9561. : "";
  9562. var uri = rootUri + "/" + filename + fileExtension;
  9563. var initialized = false; // 是否已初始化
  9564. var serverInfo; // 服务器返回的支持信息
  9565. var model; // model
  9566. var OJBetter_monaco = {};
  9567. window.OJBetter_monaco = OJBetter_monaco; // 全局方法
  9568.  
  9569. /**
  9570. * 一些工具函数
  9571. */
  9572. // 将lsp格式的rang转换为Monaco格式
  9573. OJBetter_monaco.lspRangeToMonacoRange = function (range) {
  9574. const { start, end } = range;
  9575. return new monaco.Range(
  9576. start.line + 1,
  9577. start.character + 1,
  9578. end.line + 1,
  9579. end.character + 1
  9580. );
  9581. };
  9582. // 将Monaco格式的rang转为lsp格式
  9583. OJBetter_monaco.MonacoRangeTolspRange = function (range) {
  9584. return {
  9585. start: {
  9586. line: range.startLineNumber - 1,
  9587. character: range.startColumn - 1,
  9588. },
  9589. end: {
  9590. line: range.endLineNumber - 1,
  9591. character: range.endColumn - 1,
  9592. },
  9593. };
  9594. };
  9595. // 将Monaco格式的position转为lsp格式的
  9596. OJBetter_monaco.MonacoPositionTolspPosition = function (position) {
  9597. return {
  9598. line: position.lineNumber - 1,
  9599. character: position.column - 1,
  9600. };
  9601. };
  9602. // 将Monaco格式的severity转为lsp格式的
  9603. OJBetter_monaco.MonacoSeverityTolspSeverity = function (severity) {
  9604. switch (severity) {
  9605. case 8:
  9606. return 1;
  9607. case 1:
  9608. return 4;
  9609. case 2:
  9610. return 3;
  9611. case 4:
  9612. return 2;
  9613. default:
  9614. return severity;
  9615. }
  9616. };
  9617. // 将lsp格式的severity转为Monaco格式的
  9618. OJBetter_monaco.lspSeverityToMonacoSeverity = function (severity) {
  9619. switch (severity) {
  9620. case 1:
  9621. return 8;
  9622. case 4:
  9623. return 1;
  9624. case 3:
  9625. return 2;
  9626. case 2:
  9627. return 4;
  9628. default:
  9629. return severity;
  9630. }
  9631. };
  9632. // 收集Monaco数据中的rang数据
  9633. OJBetter_monaco.CollectRange = function (item) {
  9634. return {
  9635. startLineNumber: item.startLineNumber,
  9636. startColumn: item.startColumn,
  9637. endLineNumber: item.endLineNumber,
  9638. endColumn: item.endColumn,
  9639. };
  9640. };
  9641. // 收集Monaco position数据中的rang数据
  9642. OJBetter_monaco.CollectRangeByPosition = function (item) {
  9643. var word = model.getWordUntilPosition(item);
  9644. return {
  9645. startLineNumber: item.lineNumber,
  9646. endLineNumber: item.lineNumber,
  9647. startColumn: word.startColumn,
  9648. endColumn: word.endColumn,
  9649. };
  9650. };
  9651. // 将lsp格式的Edit转换为Monaco格式
  9652. OJBetter_monaco.lspEditToMonacoEdit = function (edit) {
  9653. const edits = [];
  9654.  
  9655. if (language == "python") {
  9656. for (const item1 of edit.documentChanges) {
  9657. for (const item2 of item1.edits) {
  9658. const newElement = {
  9659. textEdit: {
  9660. range: OJBetter_monaco.lspRangeToMonacoRange(item2.range),
  9661. text: item2.newText,
  9662. },
  9663. resource: monaco.Uri.parse(item1.textDocument.uri),
  9664. versionId: model.getVersionId(),
  9665. };
  9666. edits.push(newElement);
  9667. }
  9668. }
  9669. } else if (language == "java") {
  9670. for (const item1 in edit.changes) {
  9671. edit.changes[item1].forEach((item2) => {
  9672. const newElement = {
  9673. textEdit: {
  9674. range: OJBetter_monaco.lspRangeToMonacoRange(item2.range),
  9675. text: item2.newText,
  9676. },
  9677. resource: uri,
  9678. versionId: model.getVersionId(),
  9679. };
  9680. edits.push(newElement);
  9681. });
  9682. }
  9683. } else {
  9684. for (const key in edit.changes) {
  9685. const arr = edit.changes[key];
  9686. for (const item of arr) {
  9687. const newElement = {
  9688. textEdit: {
  9689. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  9690. text: item.newText,
  9691. },
  9692. resource: monaco.Uri.parse(key),
  9693. versionId: model.getVersionId(),
  9694. };
  9695. edits.push(newElement);
  9696. }
  9697. }
  9698. }
  9699. return { edits: edits };
  9700. };
  9701.  
  9702. /**
  9703. * 实例化一个editor
  9704. */
  9705. uri = monaco.Uri.file(uri);
  9706. model = monaco.editor.createModel('', language, uri);
  9707. OJBetter.monaco.editor = monaco.editor.create(document.getElementById("OJBetter_monaco"), {
  9708. model: model,
  9709. rootUri: rootUri,
  9710. fontSize: 15,
  9711. tabSize: 4,
  9712. theme: OJBetter.basic.darkMode == "dark" ? "vs-dark" : "vs",
  9713. bracketPairColorization: {
  9714. enabled: true,
  9715. independentColorPoolPerBracketType: true,
  9716. },
  9717. automaticLayout: true,
  9718. lineNumbersMinChars: 3,
  9719. matchOnWordStartOnly: false,
  9720. wordWrap: "on",
  9721. wrappingIndent: "same",
  9722. glyphMargin: true,
  9723. formatOnType: true,
  9724. scrollbar: {
  9725. verticalScrollbarSize: 10,
  9726. horizontalScrollbarSize: 10,
  9727. alwaysConsumeMouseWheel: OJBetter.monaco.setting.alwaysConsumeMouseWheel
  9728. },
  9729. suggest: {
  9730. selectionMode: 'never' // 代码建议不自动选择
  9731. }
  9732. });
  9733.  
  9734. /**
  9735. * 添加快捷功能
  9736. */
  9737. (OJBetter_monaco.addShortCuts = async () => {
  9738. // 从配置信息更新字体大小
  9739. OJBetter.monaco.editor.updateOptions({ fontSize: parseInt(OJBetter.monaco.setting.fontsize) });
  9740.  
  9741. // 调整字体大小
  9742. let changeSize = OJB_safeCreateJQElement(`
  9743. <div class="ojb_btn ojb_btn_popover top">
  9744. <input type="number" id="fontSizeInput" value="${OJBetter.monaco.setting.fontsize}">
  9745. <span class="popover_content">${i18next.t('fontSizeInput', { ns: 'codeEditor' })}</span>
  9746. </div>`)
  9747. form.topRightDiv.append(changeSize);
  9748. changeSize.find('input#fontSizeInput').on('input', function () {
  9749. var size = $(this).val();
  9750. OJBetter.monaco.editor.updateOptions({ fontSize: parseInt(size) });
  9751. GM_setValue('editorFontSize', size);
  9752. });
  9753.  
  9754. // 全屏按钮
  9755. let fullscreenButton = OJB_safeCreateJQElement(`
  9756. <button type="button" class="ojb_btn ojb_btn_popover top">
  9757. <i class="iconfont">&#xe606;</i>
  9758. <span class="popover_content">${i18next.t('fullscreenButton', { ns: 'codeEditor' })}</span>
  9759. </button>
  9760. `);
  9761. form.topRightDiv.append(fullscreenButton);
  9762. fullscreenButton.on('click', enterFullscreen);
  9763.  
  9764. // 固定到底部按钮
  9765. let fixToBottomButton = OJB_safeCreateJQElement(`
  9766. <button type="button" class="ojb_btn ojb_btn_popover top">
  9767. <i class="iconfont">&#xe607;</i>
  9768. <span class="popover_content">${i18next.t('fixToBottomButton', { ns: 'codeEditor' })}</span>
  9769. </button>
  9770. `);
  9771. form.topRightDiv.append(fixToBottomButton);
  9772. fixToBottomButton.on('click', fixToBottom);
  9773.  
  9774. // 固定到右侧按钮
  9775. let fixToRightButton = OJB_safeCreateJQElement(`
  9776. <button type="button" class="ojb_btn ojb_btn_popover top">
  9777. <i class="iconfont">&#xe605;</i>
  9778. <span class="popover_content">${i18next.t('fixToRightButton', { ns: 'codeEditor' })}</span>
  9779. </button>
  9780. `);
  9781. // form.topRightDiv.append(fixToRightButton);
  9782. fixToRightButton.on('click', fixToRight);
  9783.  
  9784. // 添加测试/提交按钮到顶部
  9785. if (OJBetter.monaco.setting.submitButtonPosition == "top") {
  9786. form.topRightDiv.append(form.runButton);
  9787. form.topRightDiv.append(form.submitButton);
  9788. }
  9789.  
  9790. // 选择记忆
  9791. if (!OJBetter.monaco.setting.position_initialized) {
  9792. OJBetter.monaco.setting.position_initialized = true; // 标记是否已经初始化过
  9793. if (OJBetter.monaco.setting.position == "full") {
  9794. fullscreenButton.click();
  9795. } else if (OJBetter.monaco.setting.position == "bottom") {
  9796. fixToBottomButton.click();
  9797. } else if (OJBetter.monaco.setting.position == "right") {
  9798. fixToRightButton.click();
  9799. }
  9800. }
  9801.  
  9802. // 禁用按钮
  9803. function disableButtons() {
  9804. fullscreenButton.prop("disabled", true);
  9805. fixToBottomButton.prop("disabled", true);
  9806. fixToRightButton.prop("disabled", true);
  9807. }
  9808.  
  9809. // 启用按钮
  9810. function enableButtons() {
  9811. fullscreenButton.prop("disabled", false);
  9812. fixToBottomButton.prop("disabled", false);
  9813. fixToRightButton.prop("disabled", false);
  9814. }
  9815.  
  9816. // 进入全屏
  9817. function enterFullscreen() {
  9818. let editor = $('#OJBetter_editor');
  9819. editor.addClass('fullscreen');
  9820.  
  9821. // 取消按钮
  9822. let cancelButton = OJB_safeCreateJQElement(`
  9823. <button type="button" class="ojb_btn ojb_btn_popover top primary exit_button_bottom">
  9824. <i class="iconfont">&#xe60b;</i>
  9825. <span class="popover_content">${i18next.t('exitFullscreenButton', { ns: 'codeEditor' })}</span>
  9826. </button>
  9827. `).on('click', () => exitFullscreen(cancelButton));
  9828. $('body').append(cancelButton);
  9829.  
  9830. disableButtons();
  9831. GM_setValue("monacoEditor_position", "full");
  9832. }
  9833.  
  9834. // 退出全屏
  9835. const exitFullscreen = (cancelButton) => {
  9836. let editor = $('#OJBetter_editor');
  9837. editor.removeClass('fullscreen');
  9838. cancelButton.remove();
  9839. enableButtons();
  9840. GM_setValue("monacoEditor_position", "initial");
  9841. };
  9842.  
  9843. // 固定到底部
  9844. function fixToBottom() {
  9845. let editor = $('#OJBetter_editor');
  9846. editor.addClass('bottom');
  9847.  
  9848. let halfHeight = $(window).height() * 0.5;
  9849. let blankSpace = $('<div>', {
  9850. 'class': 'blank-space',
  9851. 'style': 'height: ' + (halfHeight + 30) + 'px;'
  9852. });
  9853. $('body').append(blankSpace);
  9854.  
  9855. let cancelButton = OJB_safeCreateJQElement(`
  9856. <button type="button" class="ojb_btn ojb_btn_popover top enabled exit_button_bottom">
  9857. <i class="iconfont">&#xe625;</i>
  9858. <span class="popover_content">${i18next.t('cancelFixButton', { ns: 'codeEditor' })}</span>
  9859. </button>
  9860. `).on('click', () => cancelFixingToBottom(cancelButton, blankSpace));
  9861. $('body').append(cancelButton);
  9862.  
  9863. disableButtons();
  9864. GM_setValue("monacoEditor_position", "bottom");
  9865. }
  9866.  
  9867. // 取消固定到底部
  9868. const cancelFixingToBottom = (cancelButton, blankSpace) => {
  9869. let editor = $('#OJBetter_editor');
  9870. editor.removeClass('bottom');
  9871. cancelButton.remove();
  9872. blankSpace.remove();
  9873. enableButtons();
  9874. GM_setValue("monacoEditor_position", "initial");
  9875. };
  9876.  
  9877. // 固定到右侧边栏
  9878. function fixToRight() {
  9879. const sidebar = $('#sidebar').hide();
  9880.  
  9881. // 添加样式
  9882. const styleElement = GM_addStyle(`
  9883. #body {
  9884. min-width: 50vw;
  9885. max-width: 50vw;
  9886. max-height: 100vh;
  9887. overflow-x: hidden;
  9888. overflow-y: auto;
  9889. padding: 1rem;
  9890. box-sizing: border-box;
  9891. }
  9892. body {
  9893. margin: 0px;
  9894. }
  9895. .content-with-sidebar {
  9896. margin-right: 0px !important;
  9897. }
  9898. .menu-list li {
  9899. margin-right: 0.5em;
  9900. }
  9901. .menu-list li a {
  9902. font-size: 1.4rem;
  9903. }
  9904. #OJBetter_editor{
  9905. height: 100vh;
  9906. width: 50vw;
  9907. }
  9908. `);
  9909.  
  9910. // 包装一层div
  9911. $('#body').wrap('<div id="right-side-wrapper" style="display:flex; max-width: 100vw; overflow: hidden;"></div>');
  9912. const blankSpace = $('<div>').appendTo('#right-side-wrapper');
  9913.  
  9914. // 移动编辑器
  9915. const editor = $('#OJBetter_editor').prependTo(blankSpace).addClass('right-side');
  9916.  
  9917. // 取消按钮
  9918. const cancelButton = OJB_safeCreateJQElement(`
  9919. <button type="button" class="ojb_btn ojb_btn_popover top enabled exit_button_bottom">
  9920. <i class="iconfont">&#xe625;</i>
  9921. <span class="popover_content">${i18next.t('cancelFixButton', { ns: 'codeEditor' })}</span>
  9922. </button>
  9923. `).on('click', () => cancelFixingToRight(sidebar, styleElement, editor, cancelButton)).appendTo('body');
  9924.  
  9925. disableButtons();
  9926. GM_setValue("monacoEditor_position", "right");
  9927.  
  9928. // 补丁:修复固定到右侧导致的样例元素.sample-test相关代码重复执行的问题(具体原因未查)
  9929. $('.sample-test').find('.title').each((i, e) => {
  9930. if ($(e).find('.input-output-copier').length > 1) {
  9931. $(e).find('.input-output-copier').first().remove();
  9932. }
  9933. });
  9934. darkModeStyleAdjustment();
  9935. }
  9936.  
  9937. const cancelFixingToRight = (sidebar, styleElement, editor, cancelButton) => {
  9938. sidebar.show();
  9939. // 移回来
  9940. editor.insertAfter(form.sourceDiv).removeClass('right-side');
  9941.  
  9942. // 移除包装
  9943. $('#body').unwrap();
  9944. cancelButton.remove();
  9945. styleElement.remove(); // 移除添加的样式
  9946.  
  9947. enableButtons();
  9948. GM_setValue("monacoEditor_position", "initial");
  9949. }
  9950.  
  9951. // 代码同步与保存
  9952. var nowUrl = window.location.href;
  9953. nowUrl = nowUrl.replace(/#/, ""); // 当页面存在更改时url会多出一个#,去掉
  9954. const code = await getCode(nowUrl);
  9955. if (code) {
  9956. OJBetter.monaco.editor.setValue(code); // 恢复代码
  9957. form.sourceDiv.val(code);
  9958. }
  9959. OJBetter.monaco.editor.onDidChangeModelContent(async () => {
  9960. // 将monaco editor的内容同步到sourceDiv
  9961. const code = OJBetter.monaco.editor.getValue();
  9962. form.sourceDiv.val(code);
  9963. await saveCode(nowUrl, code);
  9964. });
  9965. })();
  9966.  
  9967. /**
  9968. * 注册本地自动补全
  9969. */
  9970. (OJBetter_monaco.RegisterLocalComplet = async () => {
  9971. // 补全器注册函数
  9972. function registMyCompletionItemProvider(language, genre, rule) {
  9973. if (genre == "monaco") {
  9974. monaco.languages.registerCompletionItemProvider(language, {
  9975. provideCompletionItems: function (model, position) {
  9976. return parseMonacoCompleter(rule, OJBetter_monaco.CollectRangeByPosition(position));
  9977. }
  9978. })
  9979. } else if (genre == "ace") {
  9980. monaco.languages.registerCompletionItemProvider(language, {
  9981. provideCompletionItems: function (model, position) {
  9982. return parseAceCompleter(rule, OJBetter_monaco.CollectRangeByPosition(position));
  9983. }
  9984. })
  9985. }
  9986. }
  9987.  
  9988. // 注册acwing cpp 模板
  9989. if (language == "cpp" && OJBetter.monaco.complet.cppCodeTemplate) {
  9990. try {
  9991. var acwing_cpp_code_completer = JSON.parse(GM_getResourceText("acwing_cpp_code_completer"));
  9992. registMyCompletionItemProvider('cpp', 'ace', acwing_cpp_code_completer);
  9993. } catch (error) {
  9994. console.error("Error registering acwing cpp template:", error);
  9995. }
  9996. }
  9997.  
  9998. // 注册自定义的补全
  9999. let complet_length = OJBetter.monaco.complet.customConfig.configurations.length;
  10000. if (complet_length > 0) {
  10001. for (let i = 0; i < complet_length; i++) {
  10002. let item = OJBetter.monaco.complet.customConfig.configurations[i];
  10003. if (item.isChoose && item.language == language) {
  10004. try {
  10005. let rule = await OJB_getExternalJSON(item.jsonUrl);
  10006. registMyCompletionItemProvider(item.language, item.genre, rule);
  10007. } catch (error) {
  10008. console.error(`Error registering custom completer for ${item.language}:`, error);
  10009. }
  10010. }
  10011. }
  10012. }
  10013. })();
  10014.  
  10015. if (!support || !OJBetter.monaco.lsp.enabled) { return; } // 如果不支持lsp,则到此为止
  10016.  
  10017. /**
  10018. * LSP连接状态指示
  10019. */
  10020. const lspStateButton = OJB_safeCreateJQElement(`
  10021. <div id="lspStateDiv" class="ojb_btn ojb_btn_popover top loading">
  10022. <i class="iconfont">&#xe658;</i>
  10023. <span class="popover_content">${i18next.t('lsp.connect', { ns: 'codeEditor' })}</span>
  10024. </div>
  10025. `).on('click', () => {
  10026. OJB_showModal(LSPLogDiv);
  10027. LSPLogDiv.show();
  10028. });
  10029. form.topRightDiv.prepend(lspStateButton);
  10030.  
  10031. const LSPLogDiv = OJB_safeCreateJQElement(`
  10032. <dialog id="LSPLog" style="display: none;">
  10033. <button class="ojb_btn">${i18next.t('close', { ns: 'common' })}</button>
  10034. <div id="LSPLogList" style="overflow: auto;"></div>
  10035. <dialog>`);
  10036. $('body').append(LSPLogDiv);
  10037.  
  10038. const LSPLogList = $('<ul></ul>');
  10039. $('#LSPLogList').append(LSPLogList);
  10040.  
  10041. const closeButton = LSPLogDiv.find('button');
  10042. closeButton.on('click', function () {
  10043. OJB_closeModal(LSPLogDiv);
  10044. });
  10045.  
  10046. /**
  10047. * 推送新的消息到LSP日志中
  10048. * @param {'error' | 'warn' | 'info'} status
  10049. * @param {string} msg
  10050. * @param {boolean} data
  10051. */
  10052. function pushLSPLogMessage(status, msg, data) {
  10053. var li = $('<li>').text('[' + new Date().toLocaleString() + '] ' + msg);
  10054. if (status === 'error') {
  10055. li.attr('style', 'color: #f44336;');
  10056. } else if (status === 'warn') {
  10057. li.attr('style', 'color: #ff9800;');
  10058. } else if (status === 'info') {
  10059. li.attr('style', 'color: #616161;');
  10060. }
  10061. if (data) {
  10062. var jsonText = JSON.stringify(data, null, 2);
  10063. var details = $('<details>');
  10064. var summary = $('<summary>').text('Data');
  10065. var pre = $('<pre>').text(jsonText);
  10066. details.append(summary, pre);
  10067. li.append(details);
  10068. }
  10069. LSPLogList.append(li);
  10070. }
  10071.  
  10072. /**
  10073. * 添加状态底栏
  10074. */
  10075. var statusBar = $('<div id="OJBetter_statusBar">');
  10076. form.editorDiv.append(statusBar);
  10077.  
  10078. /**
  10079. * languageSocket
  10080. */
  10081. var url = OJBetter.monaco.lsp.socketUrl;
  10082. var languageSocket = new WebSocket(url + language);
  10083. OJBetter.monaco.lsp.socket.push(languageSocket);
  10084. var languageSocketState = false;
  10085. var responseHandlers = new Map(); // 映射表,需要等待返回数据的请求 -> 对应的事件触发函数
  10086.  
  10087. languageSocket.onopen = () => {
  10088. languageSocketState = true;
  10089. lspStateButton.setButtonPopover(i18next.t('lsp.waitingAnswer', { ns: 'codeEditor' }));
  10090. pushLSPLogMessage("info", `languageSocket ${i18next.t('lsp.socket.open', { ns: 'logMessage' })}`);
  10091. };
  10092. languageSocket.onmessage = (event) => {
  10093. const message = JSON.parse(event.data);
  10094. if (message.id === 0 && message.result) {
  10095. // 初始化完成
  10096. lspStateButton.setButtonState('success', i18next.t('lsp.connected', { ns: 'codeEditor' }));
  10097. pushLSPLogMessage("info", `Initialization ${i18next.t('lsp.state.finished', { ns: 'logMessage' })}`);
  10098. serverInfo = message.result; // 存下服务器支持信息
  10099. OJBetter_monaco.openDocRequest(); // 打开文档
  10100. if (!OJBetter.monaco.setting.language.includes(language)) {
  10101. OJBetter.monaco.setting.language.push(language);
  10102. OJBetter_monaco.RegistrationAfterInit(); // 注册语言及功能
  10103. } else {
  10104. location.reload(); // 这里有问题,先贴个补丁
  10105. }
  10106. OJBetter_monaco.PassiveReceiveHandler(); // 注册被动接收函数
  10107. } else if (message.id === 0 && message.error) {
  10108. pushLSPLogMessage("warn", `Initialization ${i18next.t('lsp.state.error', { ns: 'logMessage' })}`);
  10109. } else if (message.id !== undefined && responseHandlers.has(message.id)) {
  10110. // 如果收到带有id字段的消息,则回传给对应的事件触发函数
  10111. const handler = responseHandlers.get(message.id);
  10112. if (handler) {
  10113. handler(message);
  10114. responseHandlers.delete(message.id); // 删除已处理的事件触发函数
  10115. }
  10116. } else if (message.method == "textDocument/publishDiagnostics") {
  10117. // 接收代码诊断推送
  10118. OJBetter_monaco.updateMarkers(message);
  10119. } else if (message.method == "workspace/applyEdit") {
  10120. // 应用服务器推送的更改
  10121. OJBetter_monaco.applyEdit(message);
  10122. }
  10123. };
  10124. languageSocket.onerror = (error) => {
  10125. pushLSPLogMessage("error", `languageSocket ${i18next.t('lsp.state.error', { ns: 'logMessage' })}`, error);
  10126. console.warn(`Error connecting to languageSocket: ${error}`)
  10127. };
  10128. languageSocket.onclose = (event) => {
  10129. languageSocketState = false;
  10130. lspStateButton.setButtonState('error', i18next.t('lsp.error', { ns: 'codeEditor' }));
  10131. pushLSPLogMessage("warn", `languageSocket ${i18next.t('lsp.socket.close', { ns: 'logMessage' })}`);
  10132. };
  10133.  
  10134. /**
  10135. * 等待LanguageSocketState
  10136. */
  10137. async function waitForLanguageSocketState() {
  10138. return new Promise((resolve) => {
  10139. const checkInitialized = () => {
  10140. if (languageSocketState) {
  10141. resolve();
  10142. } else {
  10143. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  10144. }
  10145. };
  10146. checkInitialized();
  10147. });
  10148. }
  10149.  
  10150. // 等待lsp响应初始化结果
  10151. async function waitForInitialized() {
  10152. return new Promise((resolve) => {
  10153. const checkInitialized = () => {
  10154. if (initialized) {
  10155. resolve();
  10156. } else {
  10157. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  10158. }
  10159. };
  10160. checkInitialized();
  10161. });
  10162. }
  10163.  
  10164. /**
  10165. * 与languageSocket通信的包装方法
  10166. */
  10167. async function sendMessage(data, requiresResponse, callback) {
  10168. if (!initialized) {
  10169. await waitForInitialized(); // 等待initialized为真
  10170. }
  10171. if (requiresResponse) {
  10172. responseHandlers.set(data.id, callback) // 将事件触发函数与id关联起来
  10173. }
  10174. if (!languageSocketState) await waitForLanguageSocketState();
  10175. languageSocket.send(JSON.stringify(data));
  10176. }
  10177. // 发送消息并等待返回结果
  10178. function fetchData(params, callback) {
  10179. sendMessage(params, true, callback);
  10180. }
  10181. // 发送消息,不需要等待返回结果
  10182. function sendData(data) {
  10183. sendMessage(data, false);
  10184. }
  10185.  
  10186. /**
  10187. * 代码文件更新fileWebSocket
  10188. */
  10189. var fileWebSocket = new WebSocket(url + "file");
  10190. var fileWebSocketState = false;
  10191. OJBetter.monaco.lsp.socket.push(fileWebSocket);
  10192. fileWebSocket.onopen = () => {
  10193. fileWebSocketState = true;
  10194. pushLSPLogMessage("info", `fileWebSocket ${i18next.t('lsp.socket.open', { ns: 'logMessage' })}`);
  10195. };
  10196. fileWebSocket.onclose = (ev) => {
  10197. fileWebSocketState = false;
  10198. pushLSPLogMessage("warn", `fileWebSocket ${i18next.t('lsp.socket.close', { ns: 'logMessage' })}`, ev);
  10199. };
  10200. fileWebSocket.onmessage = (ev) => {
  10201. let message = JSON.parse(ev.data);
  10202. if (message.result !== "ok")
  10203. pushLSPLogMessage("error", `update file failed: ${ev}`);
  10204. };
  10205. fileWebSocket.onerror = (error) => {
  10206. console.warn(`Error connecting to fileWebSocket: ${error}`);
  10207. };
  10208. async function updateFile(workspace, filename, fileExtension, code) {
  10209. async function waitForfileWebSocketState() {
  10210. return new Promise((resolve) => {
  10211. const checkInitialized = () => {
  10212. if (fileWebSocketState) {
  10213. resolve();
  10214. } else {
  10215. setTimeout(checkInitialized, 100); // 每100毫秒检查一次initialized的值
  10216. }
  10217. };
  10218. checkInitialized();
  10219. });
  10220. }
  10221. if (!fileWebSocketState) await waitForfileWebSocketState();
  10222. fileWebSocket.send(
  10223. JSON.stringify({
  10224. type: "update",
  10225. workspace,
  10226. filename,
  10227. fileExtension,
  10228. code,
  10229. })
  10230. );
  10231. }
  10232.  
  10233. /**
  10234. * 发送初始化请求
  10235. */
  10236. OJBetter_monaco.Initialize = () => {
  10237. //初始化initialize
  10238. const capabilities = {
  10239. workspace: {
  10240. applyEdit: true,
  10241. },
  10242. textDocument: {
  10243. publishDiagnostics: {
  10244. relatedInformation: true,
  10245. versionSupport: false,
  10246. tagSupport: {
  10247. valueSet: [1, 2],
  10248. },
  10249. codeDescriptionSupport: true,
  10250. },
  10251. completion: {
  10252. contextSupport: true,
  10253. completionItem: {
  10254. snippetSupport: true,
  10255. commitCharactersSupport: true,
  10256. documentationFormat: ["markdown", "plaintext"],
  10257. deprecatedSupport: true,
  10258. preselectSupport: true,
  10259. tagSupport: {
  10260. valueSet: [1],
  10261. },
  10262. insertReplaceSupport: true,
  10263. resolveSupport: {
  10264. properties: [
  10265. "documentation",
  10266. "detail",
  10267. "additionalTextEdits",
  10268. ],
  10269. },
  10270. insertTextModeSupport: {
  10271. valueSet: [1, 2],
  10272. },
  10273. },
  10274. },
  10275. hover: {
  10276. dynamicRegistration: true,
  10277. contentFormat: ["markdown", "plaintext"],
  10278. },
  10279. signatureHelp: {
  10280. signatureInformation: {
  10281. documentationFormat: ["markdown", "plaintext"],
  10282. parameterInformation: {
  10283. labelOffsetSupport: true,
  10284. },
  10285. activeParameterSupport: true,
  10286. },
  10287. contextSupport: true,
  10288. },
  10289. definition: {
  10290. dynamicRegistration: true,
  10291. linkSupport: true,
  10292. },
  10293. references: {
  10294. dynamicRegistration: true,
  10295. },
  10296. documentHighlight: {
  10297. dynamicRegistration: true,
  10298. },
  10299. codeAction: {
  10300. codeActionLiteralSupport: {
  10301. codeActionKind: {
  10302. valueSet:
  10303. language == "java"
  10304. ? []
  10305. : [
  10306. "",
  10307. "quickfix",
  10308. "refactor",
  10309. "refactor.extract",
  10310. "refactor.inline",
  10311. "refactor.rewrite",
  10312. "source",
  10313. "source.organizeImports",
  10314. ],
  10315. },
  10316. },
  10317. },
  10318. rename: {
  10319. dynamicRegistration: true,
  10320. prepareSupport: true,
  10321. prepareSupportDefaultBehavior: 1,
  10322. honorsChangeAnnotations: true,
  10323. },
  10324. documentLink: {
  10325. tooltipSupport: true,
  10326. },
  10327. typeDefinition: {
  10328. dynamicRegistration: true,
  10329. linkSupport: true,
  10330. },
  10331. implementation: {
  10332. dynamicRegistration: true,
  10333. linkSupport: true,
  10334. },
  10335. colorProvider: {
  10336. dynamicRegistration: true,
  10337. },
  10338. foldingRange: {
  10339. dynamicRegistration: true,
  10340. rangeLimit: 5000,
  10341. lineFoldingOnly: true,
  10342. },
  10343. declaration: {
  10344. dynamicRegistration: true,
  10345. linkSupport: true,
  10346. },
  10347. semanticTokens: {
  10348. dynamicRegistration: true,
  10349. tokenTypes: [
  10350. "namespace",
  10351. "type",
  10352. "class",
  10353. "enum",
  10354. "interface",
  10355. "struct",
  10356. "typeParameter",
  10357. "parameter",
  10358. "variable",
  10359. "property",
  10360. "enumMember",
  10361. "event",
  10362. "function",
  10363. "method",
  10364. "macro",
  10365. "keyword",
  10366. "modifier",
  10367. "comment",
  10368. "string",
  10369. "number",
  10370. "regexp",
  10371. "operator",
  10372. ],
  10373. tokenModifiers: [
  10374. "declaration",
  10375. "definition",
  10376. "readonly",
  10377. "static",
  10378. "deprecated",
  10379. "abstract",
  10380. "async",
  10381. "modification",
  10382. "documentation",
  10383. "defaultLibrary",
  10384. ],
  10385. formats: ["relative"],
  10386. requests: {
  10387. range: true,
  10388. full: {
  10389. delta: true,
  10390. },
  10391. },
  10392. multilineTokenSupport: false,
  10393. overlappingTokenSupport: false,
  10394. },
  10395. callHierarchy: {
  10396. dynamicRegistration: true,
  10397. },
  10398. },
  10399. window: {
  10400. showMessage: {
  10401. messageActionItem: {
  10402. additionalPropertiesSupport: true,
  10403. },
  10404. },
  10405. showDocument: {
  10406. support: true,
  10407. },
  10408. workDoneProgress: true,
  10409. },
  10410. general: {
  10411. regularExpressions: {
  10412. engine: "ECMAScript",
  10413. version: "ES2020",
  10414. },
  10415. markdown: {
  10416. parser: "marked",
  10417. version: "1.1.0",
  10418. },
  10419. },
  10420. };
  10421.  
  10422. const initializeRequest = {
  10423. id: id++,
  10424. jsonrpc: "2.0",
  10425. method: "initialize",
  10426. params: {
  10427. processId: null,
  10428. clientInfo: {
  10429. name: "CFMonaco" + InstanceID,
  10430. },
  10431. locale: "zh-CN",
  10432. rootPath: null,
  10433. rootUri: null,
  10434. capabilities: capabilities,
  10435. trace: "off",
  10436. workspaceFolders: [
  10437. {
  10438. uri:
  10439. "file:///" + OJBetter.monaco.lsp.workUri + workspace,
  10440. name:
  10441. "file:///" + OJBetter.monaco.lsp.workUri + workspace,
  10442. },
  10443. ],
  10444. },
  10445. };
  10446. languageSocket.send(JSON.stringify(initializeRequest));
  10447.  
  10448. // 打开文档函数
  10449. OJBetter_monaco.openDocRequest = function () {
  10450. const initializ = {
  10451. jsonrpc: "2.0",
  10452. method: "initialized",
  10453. params: {},
  10454. };
  10455. languageSocket.send(JSON.stringify(initializ));
  10456. const openDocRequest = {
  10457. jsonrpc: "2.0",
  10458. method: "textDocument/didOpen",
  10459. params: {
  10460. textDocument: {
  10461. uri: model.uri.toString(),
  10462. languageId: language,
  10463. version: model.getVersionId(),
  10464. text: model.getValue(),
  10465. },
  10466. },
  10467. };
  10468. languageSocket.send(JSON.stringify(openDocRequest));
  10469. initialized = true; // 初始化完成,这里确认逻辑待完善
  10470. };
  10471.  
  10472. // 初始化更新文件
  10473. updateFile(workspace, filename, fileExtension, model.getValue());
  10474. }
  10475.  
  10476. /**
  10477. * 注册语言及功能
  10478. */
  10479. OJBetter_monaco.RegistrationAfterInit = () => {
  10480. // 注册语言
  10481. monaco.languages.register({ id: language });
  10482.  
  10483. // 注册"Command"
  10484. (function registerCommand() {
  10485. serverInfo.capabilities.executeCommandProvider.commands.forEach(
  10486. (item) => {
  10487. pushLSPLogMessage("info", `${i18next.t('lsp.server.regist', { ns: 'logMessage' })}`, item);
  10488. monaco.editor.registerCommand(item, (accessor, ...args) => {
  10489. sendData({
  10490. jsonrpc: "2.0",
  10491. id: id++,
  10492. method: "workspace/executeCommand",
  10493. params: {
  10494. command: item,
  10495. arguments: args,
  10496. },
  10497. });
  10498. });
  10499. }
  10500. );
  10501. })();
  10502.  
  10503. // 注册"增量更新"
  10504. model.onDidChangeContent((event) => {
  10505. updateFile(workspace, filename, fileExtension, model.getValue()); // 更新文件
  10506. const changeDocRequest = {
  10507. jsonrpc: "2.0",
  10508. method: "textDocument/didChange",
  10509. params: {
  10510. textDocument: {
  10511. uri: model.uri.toString(),
  10512. version: model.getVersionId(),
  10513. },
  10514. contentChanges: event.changes.map((change) => ({
  10515. range: OJBetter_monaco.MonacoRangeTolspRange(change.range),
  10516. rangeLength: change.rangeLength,
  10517. text: change.text,
  10518. })),
  10519. },
  10520. };
  10521. sendData(changeDocRequest);
  10522. });
  10523.  
  10524. //注册"自动补全"
  10525. monaco.languages.registerCompletionItemProvider(language, {
  10526. provideCompletionItems: (model, position, context) => {
  10527. const request = {
  10528. jsonrpc: "2.0",
  10529. id: id++,
  10530. method: "textDocument/completion",
  10531. params: {
  10532. textDocument: {
  10533. uri: model.uri.toString(),
  10534. },
  10535. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  10536. context: {
  10537. triggerKind: context.triggerKind + 1, // 这里要+1,两边的定义不一样。。。
  10538. triggerCharacter: context.triggerCharacter,
  10539. },
  10540. },
  10541. };
  10542. return new Promise((resolve, reject) => {
  10543. fetchData(request, (response) => {
  10544. const result = response.result;
  10545. pushLSPLogMessage("info", `completion ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10546. if (!result) return resolve(null);
  10547. const CompletionItems = {
  10548. suggestions: result.items.map(
  10549. ({
  10550. label,
  10551. kind,
  10552. filterText,
  10553. insertText,
  10554. insertTextFormat,
  10555. sortText,
  10556. textEdit,
  10557. documentation,
  10558. additionalTextEdits,
  10559. }) => ({
  10560. additionalTextEdits: additionalTextEdits
  10561. ? additionalTextEdits.map(({ newText, range }) => ({
  10562. text: newText,
  10563. range: OJBetter_monaco.lspRangeToMonacoRange(range),
  10564. }))
  10565. : [],
  10566. documentation: documentation ? documentation.value : "",
  10567. filterText,
  10568. insertText: insertText ? insertText : textEdit.newText,
  10569. insertTextRules:
  10570. insertTextFormat === 2
  10571. ? monaco.languages.CompletionItemInsertTextRule
  10572. .InsertAsSnippet
  10573. : monaco.languages.CompletionItemInsertTextRule
  10574. .KeepWhitespace,
  10575. kind,
  10576. label,
  10577. sortText,
  10578. range: textEdit
  10579. ? textEdit.range
  10580. ? OJBetter_monaco.lspRangeToMonacoRange(textEdit.range)
  10581. : OJBetter_monaco.lspRangeToMonacoRange(textEdit.insert)
  10582. : null,
  10583. })
  10584. ),
  10585. };
  10586. pushLSPLogMessage("info", `completion ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, CompletionItems);
  10587. resolve(CompletionItems);
  10588. });
  10589. });
  10590. },
  10591. });
  10592.  
  10593. // 注册"代码修复"
  10594. monaco.languages.registerCodeActionProvider(language, {
  10595. provideCodeActions: (model, range, context) => {
  10596. const request = {
  10597. id: id++,
  10598. jsonrpc: "2.0",
  10599. method: "textDocument/codeAction",
  10600. params: {
  10601. textDocument: {
  10602. uri: model.uri.toString(),
  10603. },
  10604. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  10605. context: {
  10606. diagnostics: context.markers.map((item) => ({
  10607. range: OJBetter_monaco.MonacoRangeTolspRange({
  10608. startLineNumber: item.startLineNumber,
  10609. startColumn: item.startColumn,
  10610. endLineNumber: item.endLineNumber,
  10611. endColumn: item.endColumn,
  10612. }),
  10613. severity: OJBetter_monaco.MonacoSeverityTolspSeverity(
  10614. item.severity
  10615. ),
  10616. code: item.code,
  10617. source: item.source,
  10618. message: item.message,
  10619. tags: item.tags,
  10620. relatedInformation: item.relatedInformation
  10621. ? item.relatedInformation.map((item) => ({
  10622. location: {
  10623. uri: item.resource.toString(),
  10624. range: OJBetter_monaco.MonacoRangeTolspRange({
  10625. startLineNumber: item.startLineNumber,
  10626. startColumn: item.startColumn,
  10627. endLineNumber: item.endLineNumber,
  10628. endColumn: item.endColumn,
  10629. }),
  10630. },
  10631. message: item.message,
  10632. }))
  10633. : null,
  10634. })),
  10635. only: context.only ? [context.only] : [],
  10636. triggerKind: context.trigger,
  10637. },
  10638. },
  10639. };
  10640.  
  10641. return new Promise((resolve, reject) => {
  10642. fetchData(request, (response) => {
  10643. const result = response.result;
  10644. pushLSPLogMessage("info", `codeAction ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10645. if (!result) return resolve(null);
  10646. const codeAction = {
  10647. actions: result.map((item) => ({
  10648. title: item.title,
  10649. kind: item.kind ? item.kind : "quickfix",
  10650. command: item.command
  10651. ? item.command.command
  10652. ? {
  10653. id: item.command.command,
  10654. arguments: item.command.arguments,
  10655. title: item.command.title,
  10656. }
  10657. : null
  10658. : null,
  10659. diagnostics: item.diagnostics
  10660. ? item.diagnostics.map((item) => ({
  10661. code: item.code,
  10662. message: item.message,
  10663. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  10664. severity: OJBetter_monaco.lspSeverityToMonacoSeverity(
  10665. item.severity
  10666. ),
  10667. source: item.source,
  10668. }))
  10669. : null,
  10670. edit: item.edit
  10671. ? OJBetter_monaco.lspEditToMonacoEdit(item.edit)
  10672. : item.arguments
  10673. ? {
  10674. edits: item.arguments.flatMap(
  10675. (item1) => OJBetter_monaco.lspEditToMonacoEdit(item1).edits
  10676. ),
  10677. }
  10678. : null,
  10679. })),
  10680. dispose: () => { },
  10681. };
  10682. pushLSPLogMessage("info", `codeAction ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, codeAction);
  10683.  
  10684. resolve(codeAction);
  10685. });
  10686. });
  10687. },
  10688. });
  10689.  
  10690. // 注册"hover提示"
  10691. monaco.languages.registerHoverProvider(language, {
  10692. provideHover: (model, position) => {
  10693. const request = {
  10694. jsonrpc: "2.0",
  10695. id: id++,
  10696. method: "textDocument/hover",
  10697. params: {
  10698. textDocument: {
  10699. uri: model.uri.toString(),
  10700. },
  10701. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  10702. },
  10703. };
  10704.  
  10705. return new Promise((resolve, reject) => {
  10706. fetchData(request, (response) => {
  10707. pushLSPLogMessage("info", `Hover ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10708. const result = response.result;
  10709.  
  10710. if (!result) return resolve(null);
  10711. const Hover = {
  10712. range: result.range
  10713. ? OJBetter_monaco.lspRangeToMonacoRange(result.range)
  10714. : new monaco.Range(
  10715. position.lineNumber,
  10716. position.column,
  10717. position.lineNumber,
  10718. position.column
  10719. ),
  10720. contents: Array.isArray(result.contents)
  10721. ? result.contents.map((item) => ({
  10722. value: item.value ? item.value : item,
  10723. }))
  10724. : [
  10725. {
  10726. value: result.contents.value,
  10727. },
  10728. ],
  10729. };
  10730. pushLSPLogMessage("info", `Hover ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, Hover);
  10731. resolve(Hover);
  10732. });
  10733. });
  10734. },
  10735. });
  10736.  
  10737. // 注册"inlay提示"
  10738. if (language == "cpp" || language == "java")
  10739. monaco.languages.registerInlayHintsProvider(language, {
  10740. provideInlayHints: (model, range, token) => {
  10741. return new Promise((resolve, reject) => {
  10742. const request = {
  10743. jsonrpc: "2.0",
  10744. id: id++,
  10745. method: "textDocument/inlayHint",
  10746. params: {
  10747. textDocument: {
  10748. uri: model.uri.toString(),
  10749. },
  10750. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  10751. },
  10752. };
  10753.  
  10754. fetchData(request, (response) => {
  10755. const result = response.result;
  10756. pushLSPLogMessage("info", `Inlay Hints ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10757.  
  10758. if (!result) return resolve(null);
  10759.  
  10760. const inlayHints = {
  10761. hints: result.map((item) => {
  10762. return {
  10763. kind: item.kind,
  10764. label: item.label,
  10765. paddingLeft: item.paddingLeft,
  10766. paddingRight: item.paddingRight,
  10767. position: {
  10768. lineNumber: item.position.line + 1,
  10769. column: item.position.character + 1,
  10770. },
  10771. };
  10772. }),
  10773. };
  10774. pushLSPLogMessage("info", `Inlay Hints ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, inlayHints);
  10775.  
  10776. resolve(inlayHints);
  10777. });
  10778. });
  10779. },
  10780. });
  10781.  
  10782. // 注册"转到定义"
  10783. monaco.languages.registerDefinitionProvider(language, {
  10784. provideDefinition: (model, position) => {
  10785. const request = {
  10786. jsonrpc: "2.0",
  10787. id: id++,
  10788. method: "textDocument/definition",
  10789. params: {
  10790. textDocument: {
  10791. uri: model.uri.toString(),
  10792. },
  10793. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  10794. },
  10795. };
  10796.  
  10797. return new Promise((resolve, reject) => {
  10798. fetchData(request, (response) => {
  10799. const result = response.result;
  10800. pushLSPLogMessage("info", `definition ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10801.  
  10802. if (result.length == 0) return resolve(null);
  10803. const definition = result.map((item) => ({
  10804. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  10805. uri: monaco.Uri.parse(item.uri), //
  10806. }));
  10807. pushLSPLogMessage("info", `definition ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, definition);
  10808.  
  10809. resolve(definition);
  10810. });
  10811. });
  10812.  
  10813. return null; // 如果没有内容,则返回null
  10814. },
  10815. });
  10816.  
  10817. // 注册"转到引用"
  10818. monaco.languages.registerReferenceProvider(language, {
  10819. provideReferences: (model, position, context) => {
  10820. const request = {
  10821. jsonrpc: "2.0",
  10822. id: id++,
  10823. method: "textDocument/references",
  10824. params: {
  10825. context: context,
  10826. textDocument: {
  10827. uri: model.uri.toString(),
  10828. },
  10829. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  10830. },
  10831. };
  10832.  
  10833. return new Promise((resolve, reject) => {
  10834. fetchData(request, (response) => {
  10835. const result = response.result;
  10836. pushLSPLogMessage("info", `references ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10837.  
  10838. if (result.length == 0) return resolve([]);
  10839.  
  10840. const references = result.map((item) => ({
  10841. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  10842. uri: monaco.Uri.parse(item.uri), //
  10843. }));
  10844. pushLSPLogMessage("info", `references ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, references);
  10845. resolve(references);
  10846. });
  10847. });
  10848. return []; // 如果没有内容,则返回空数组
  10849. },
  10850. });
  10851.  
  10852. // 注册"符号引用点击高亮"
  10853. monaco.languages.registerDocumentHighlightProvider(language, {
  10854. provideDocumentHighlights: (model, position) => {
  10855. const request = {
  10856. jsonrpc: "2.0",
  10857. id: id++,
  10858. method: "textDocument/documentHighlight",
  10859. params: {
  10860. textDocument: {
  10861. uri: model.uri.toString(),
  10862. },
  10863. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  10864. },
  10865. };
  10866.  
  10867. return new Promise((resolve, reject) => {
  10868. fetchData(request, (response) => {
  10869. const result = response.result;
  10870. pushLSPLogMessage("info", `documentHighlight ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10871.  
  10872. if (!result || result.length == 0) return resolve([]);
  10873. const highlights = result.map((item) => ({
  10874. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  10875. kind: item.kind,
  10876. }));
  10877. pushLSPLogMessage("info",
  10878. `documentHighlight ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`,
  10879. highlights
  10880. );
  10881.  
  10882. resolve(highlights);
  10883. });
  10884. });
  10885. return []; // 如果没有内容,则返回空数组
  10886. },
  10887. });
  10888.  
  10889. // 注册"文件链接"
  10890. if (language == "cpp" || language == "java")
  10891. monaco.languages.registerLinkProvider(language, {
  10892. provideLinks: (model) => {
  10893. const request = {
  10894. jsonrpc: "2.0",
  10895. id: id++,
  10896. method: "textDocument/documentLink",
  10897. params: {
  10898. textDocument: {
  10899. uri: model.uri.toString(),
  10900. },
  10901. },
  10902. };
  10903.  
  10904. return new Promise((resolve, reject) => {
  10905. fetchData(request, (response) => {
  10906. const result = response.result;
  10907. pushLSPLogMessage("info", `DocumentLink ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10908.  
  10909. if (!result) return resolve(null);
  10910. const links = {
  10911. links: result.map((item) => ({
  10912. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  10913. url: item.target.toString(),
  10914. tooltip: item.tooltip ? item.tooltip : null,
  10915. })),
  10916. };
  10917. pushLSPLogMessage("info", `DocumentLink ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, links);
  10918. resolve(links);
  10919. });
  10920. });
  10921. },
  10922. });
  10923.  
  10924. // 注册"格式化"
  10925. monaco.languages.registerDocumentFormattingEditProvider(language, {
  10926. provideDocumentFormattingEdits: (model, options, token) => {
  10927. const request = {
  10928. jsonrpc: "2.0",
  10929. id: id++,
  10930. method: "textDocument/formatting",
  10931. params: {
  10932. textDocument: {
  10933. uri: model.uri.toString(),
  10934. },
  10935. options: options,
  10936. },
  10937. };
  10938.  
  10939. return new Promise((resolve, reject) => {
  10940. fetchData(request, (response) => {
  10941. const result = response.result;
  10942. pushLSPLogMessage("info", `formatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10943.  
  10944. const TextEdit = result.map((edit) => ({
  10945. range: OJBetter_monaco.lspRangeToMonacoRange(edit.range),
  10946. text: edit.newText,
  10947. }));
  10948. pushLSPLogMessage("info", `formatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, TextEdit);
  10949. resolve(TextEdit);
  10950. });
  10951. });
  10952. },
  10953. });
  10954.  
  10955. // 注册"部分格式化"
  10956. monaco.languages.registerDocumentRangeFormattingEditProvider(language, {
  10957. provideDocumentRangeFormattingEdits: (model, range, options) => {
  10958. const request = {
  10959. jsonrpc: "2.0",
  10960. id: id++,
  10961. method: "textDocument/rangeFormatting",
  10962. params: {
  10963. textDocument: {
  10964. uri: model.uri.toString(),
  10965. },
  10966. range: OJBetter_monaco.MonacoRangeTolspRange(range),
  10967. options,
  10968. },
  10969. };
  10970.  
  10971. return new Promise((resolve, reject) => {
  10972. fetchData(request, (response) => {
  10973. const result = response.result;
  10974. pushLSPLogMessage("info", `rangeFormatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  10975.  
  10976. if (!result || result.length == 0) return resolve([]);
  10977. const edits = result.map((item) => ({
  10978. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  10979. text: item.newText,
  10980. }));
  10981. pushLSPLogMessage("info", `rangeFormatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, edits);
  10982. resolve(edits);
  10983. });
  10984. });
  10985. },
  10986. });
  10987.  
  10988. // 注册"重命名"
  10989. monaco.languages.registerRenameProvider(language, {
  10990. provideRenameEdits: (model, position, newName, token) => {
  10991. const request = {
  10992. jsonrpc: "2.0",
  10993. id: id++,
  10994. method: "textDocument/rename",
  10995. params: {
  10996. textDocument: {
  10997. uri: model.uri.toString(),
  10998. },
  10999. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11000. newName: newName,
  11001. },
  11002. };
  11003.  
  11004. return new Promise((resolve, reject) => {
  11005. fetchData(request, (response) => {
  11006. const result = response.result;
  11007. pushLSPLogMessage("info", `rename ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11008.  
  11009. const rename = OJBetter_monaco.lspEditToMonacoEdit(result);
  11010. pushLSPLogMessage("info", `rename ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, rename);
  11011. resolve(rename);
  11012. });
  11013. });
  11014. },
  11015. });
  11016.  
  11017. // 注册"折叠范围分析"
  11018. monaco.languages.registerFoldingRangeProvider(language, {
  11019. provideFoldingRanges: (model) => {
  11020. const request = {
  11021. jsonrpc: "2.0",
  11022. id: id++,
  11023. method: "textDocument/foldingRange",
  11024. params: {
  11025. textDocument: {
  11026. uri: model.uri.toString(),
  11027. },
  11028. },
  11029. };
  11030.  
  11031. return new Promise((resolve, reject) => {
  11032. fetchData(request, (response) => {
  11033. const result = response.result;
  11034. pushLSPLogMessage("info", `FoldingRange ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11035.  
  11036. if (!result) return resolve([]);
  11037. const foldingRanges = result.map((item) => ({
  11038. start: item.startLine + 1,
  11039. end: item.endLine + 1,
  11040. kind: monaco.languages.FoldingRangeKind.fromValue(item.kind),
  11041. }));
  11042. pushLSPLogMessage("info", `FoldingRange ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, foldingRanges);
  11043. resolve(foldingRanges);
  11044. });
  11045. });
  11046. },
  11047. });
  11048.  
  11049. // 注册"方法签名提示"
  11050. monaco.languages.registerSignatureHelpProvider(language, {
  11051. signatureHelpTriggerCharacters:
  11052. serverInfo.capabilities.signatureHelpProvider.triggerCharacters,
  11053. provideSignatureHelp: (model, position, token, context) => {
  11054. const request = {
  11055. jsonrpc: "2.0",
  11056. id: id++,
  11057. method: "textDocument/signatureHelp",
  11058. params: {
  11059. textDocument: {
  11060. uri: model.uri.toString(),
  11061. },
  11062. position: {
  11063. line: position.lineNumber - 1,
  11064. character: position.column - 1,
  11065. },
  11066. context: {
  11067. triggerKind: context.triggerKind,
  11068. triggerCharacter: context.triggerCharacter,
  11069. isRetrigger: context.isRetrigger,
  11070. activeSignatureHelp: context.activeSignatureHelp,
  11071. },
  11072. },
  11073. };
  11074.  
  11075. return new Promise((resolve, reject) => {
  11076. fetchData(request, (response) => {
  11077. const result = response.result;
  11078.  
  11079. pushLSPLogMessage("info", `signatureHelp ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11080.  
  11081. if (!result) return resolve(null);
  11082. const SignatureHelpResult = {
  11083. value: {
  11084. activeParameter: result.activeParameter,
  11085. activeSignature: result.activeSignature,
  11086. signatures: result.signatures,
  11087. },
  11088. dispose: () => { },
  11089. };
  11090.  
  11091. pushLSPLogMessage("info",
  11092. `signatureHelp ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`,
  11093. SignatureHelpResult
  11094. );
  11095. resolve(SignatureHelpResult);
  11096. });
  11097. });
  11098. },
  11099. });
  11100.  
  11101. // 注册"渐进式自动格式化" 如果server有这个
  11102. if (serverInfo.capabilities.documentOnTypeFormattingProvider)
  11103. monaco.languages.registerOnTypeFormattingEditProvider(language, {
  11104. autoFormatTriggerCharacters: [
  11105. serverInfo.capabilities.documentOnTypeFormattingProvider
  11106. .firstTriggerCharacter,
  11107. ],
  11108. provideOnTypeFormattingEdits: (model, position, ch, options) => {
  11109. const request = {
  11110. jsonrpc: "2.0",
  11111. id: id++,
  11112. method: "textDocument/onTypeFormatting",
  11113. params: {
  11114. textDocument: {
  11115. uri: model.uri.toString(),
  11116. },
  11117. position: OJBetter_monaco.MonacoPositionTolspPosition(position),
  11118. ch,
  11119. options,
  11120. },
  11121. };
  11122.  
  11123. return new Promise((resolve, reject) => {
  11124. fetchData(request, (response) => {
  11125. const result = response.result;
  11126. pushLSPLogMessage("info", `onTypeFormatting ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, response);
  11127.  
  11128. if (!result || result.length == 0) return resolve([]);
  11129.  
  11130. const edits = result.map((item) => ({
  11131. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11132. text: item.newText,
  11133. }));
  11134. pushLSPLogMessage("info", `onTypeFormatting ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, edits);
  11135. resolve(edits);
  11136. });
  11137. });
  11138. },
  11139. });
  11140. };
  11141.  
  11142. /**
  11143. * 被动式接收处理
  11144. */
  11145. OJBetter_monaco.PassiveReceiveHandler = () => {
  11146.  
  11147. // "实时代码诊断"
  11148. OJBetter_monaco.updateMarkers = function (message) {
  11149. const params = message.params;
  11150. pushLSPLogMessage("info", `Markers ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, message);
  11151.  
  11152. if (!params) return;
  11153. const markers = params.diagnostics.map((item1) => ({
  11154. code: item1.code,
  11155. message: item1.message,
  11156. ...OJBetter_monaco.lspRangeToMonacoRange(item1.range),
  11157. relatedInformation: item1.relatedInformation
  11158. ? item1.relatedInformation.map((item2) => ({
  11159. ...(item2.location.range
  11160. ? OJBetter_monaco.lspRangeToMonacoRange(item2.location.range)
  11161. : OJBetter_monaco.lspRangeToMonacoRange(item2.location)),
  11162. message: item2.message,
  11163. resource: monaco.Uri.parse(item2.location.uri),
  11164. }))
  11165. : null,
  11166. severity: OJBetter_monaco.lspSeverityToMonacoSeverity(item1.severity),
  11167. source: item1.source,
  11168. }));
  11169.  
  11170. pushLSPLogMessage("info", `Markers ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, markers);
  11171. monaco.editor.setModelMarkers(model, "eslint", markers);
  11172.  
  11173. // 更新状态底栏信息
  11174. const nowMarks = monaco.editor.getModelMarkers();
  11175. warningCount = 0;
  11176. errorCount = 0;
  11177. for (const marker of nowMarks) {
  11178. if (marker.severity === monaco.MarkerSeverity.Warning) {
  11179. warningCount++;
  11180. } else if (marker.severity === monaco.MarkerSeverity.Error) {
  11181. errorCount++;
  11182. }
  11183. }
  11184. $('#OJBetter_statusBar').text(`Warnings: ${warningCount}, Errors: ${errorCount}`);
  11185. };
  11186.  
  11187. // "应用服务器推送的更改"(代码修复)
  11188. OJBetter_monaco.applyEdit = function (message) {
  11189. const params = message.params;
  11190. pushLSPLogMessage("info", `applyEdit ${i18next.t('lsp.server.receive', { ns: 'logMessage' })}`, message);
  11191.  
  11192. if (!params) return;
  11193. const operations = Object.values(params.edit.changes)
  11194. .flat()
  11195. .map((item) => ({
  11196. range: OJBetter_monaco.lspRangeToMonacoRange(item.range),
  11197. text: item.newText,
  11198. }));
  11199.  
  11200. pushLSPLogMessage("info", `applyEdit ${i18next.t('lsp.server.transmit', { ns: 'logMessage' })}`, operations);
  11201. model.pushEditOperations([], operations, () => null); // 入栈编辑操作
  11202. };
  11203. }
  11204.  
  11205. if (!languageSocketState) await waitForLanguageSocketState();
  11206. OJBetter_monaco.Initialize();
  11207. }
  11208.  
  11209. // 语言更改
  11210. function changeMonacoLanguage(form) {
  11211. let nowSelect = form.selectLang.val();
  11212. // 记忆更改
  11213. GM_setValue('compilerSelection', nowSelect);
  11214. // 销毁旧的编辑器
  11215. try {
  11216. if (OJBetter.monaco.editor) OJBetter.monaco.editor.dispose();
  11217. } catch (error) {
  11218. console.warn("Encountered an error while attempting to delete the old editor, but it should not affect your regular usage.", error)
  11219. }
  11220. // 关闭旧的socket
  11221. OJBetter.monaco.lsp.socket.forEach(socket => {
  11222. socket.close();
  11223. });
  11224. // 移除相关元素
  11225. form.topRightDiv.empty();
  11226. $('#LSPLog').remove();
  11227. $('#OJBetter_statusBar').remove();
  11228. // 创建新的编辑器
  11229. if (nowSelect in value_monacoLanguageMap) {
  11230. let language = value_monacoLanguageMap[nowSelect];
  11231. if (language == "python" || language == "cpp") {
  11232. createMonacoEditor(language, form, true);
  11233. } else {
  11234. createMonacoEditor(language, form, false);
  11235. }
  11236. } else {
  11237. createMonacoEditor(null, false);
  11238. }
  11239. // 更新在线编译器参数
  11240. changeCompilerArgs(nowSelect);
  11241. }
  11242.  
  11243. // 收集样例数据
  11244. function getTestData() {
  11245. let testData = {};
  11246.  
  11247. /**
  11248. * 从pre中获取文本信息
  11249. * @param {JQuery<HTMLElement>} node 元素
  11250. * @returns {string} 文本信息
  11251. */
  11252. function getTextFromPre(node) {
  11253. let text;
  11254. if (node.find("br").length > 0) {
  11255. text = node.html().replace(/<br>/g, "\n"); // <br>作换行符的情况
  11256. } else {
  11257. text = node.text();
  11258. }
  11259. return text;
  11260. }
  11261.  
  11262. // $('.input').each(function (index) {
  11263. // var inputText = '';
  11264. // if ($(this).find('pre').find('div').length > 0) {
  11265. // $(this).find('pre').find('div').each(function () {
  11266. // inputText += getTextFromPre($(this)) + '\n';
  11267. // });
  11268. // } else {
  11269. // inputText = getTextFromPre($(this).find('pre'));
  11270. // }
  11271. // var outputText = '';
  11272. // if ($('.output').eq(index).find('pre').find('div').length > 0) {
  11273. // $('.output').eq(index).find('pre').find('div').each(function () {
  11274. // inputText += getTextFromPre($(this)) + '\n';
  11275. // });
  11276. // } else {
  11277. // outputText = getTextFromPre($('.output').eq(index).find('pre'));
  11278. // }
  11279.  
  11280. // testData[index + 1] = {
  11281. // input: inputText.trim(),
  11282. // output: outputText.trim()
  11283. // };
  11284. // });
  11285.  
  11286. // 需要过滤重复的样例(因为有日文英文两个页面,样例元素重复了两遍)
  11287. let uniqueTestData = [];
  11288. const filteredPreElements = $('pre').clone().filter((index, element) => element.id.startsWith('pre-sample'));
  11289. for (let i = 0; i < filteredPreElements.length; i += 2) {
  11290. if (i + 1 < filteredPreElements.length) {
  11291. const inputElement = $(filteredPreElements[i]);
  11292. const outputElement = $(filteredPreElements[i + 1]);
  11293.  
  11294. const inputText = getTextFromPre(inputElement).trim();
  11295. const outputText = getTextFromPre(outputElement).trim();
  11296.  
  11297. // 检查是否已经存在相同的样例
  11298. let isDuplicate = uniqueTestData.some(testCase => testCase.input === inputText && testCase.output === outputText);
  11299. if (!isDuplicate) {
  11300. uniqueTestData.push({
  11301. input: inputText,
  11302. output: outputText
  11303. });
  11304. }
  11305. }
  11306. }
  11307.  
  11308. // 把唯一的测试数据赋值给testData,保持原有的索引风格
  11309. uniqueTestData.forEach((testCase, index) => {
  11310. testData[index + 1] = testCase;
  11311. });
  11312.  
  11313. return testData;
  11314. }
  11315.  
  11316. // 初始化自定义测试数据面板
  11317. function CustomTestInit() {
  11318. const url = window.location.href;
  11319.  
  11320. restoreText();
  11321.  
  11322. // 添加
  11323. $('#addCustomTest').click(function () {
  11324. var sampleDiv = $('<div class="sampleDiv">');
  11325. var inputTextarea = $('<p style="padding: 0px 5px;">input</p><textarea class="dynamicTextarea inputTextarea"></textarea>');
  11326. var outputTextarea = $('<p style="padding: 0px 5px;">output</p><textarea class="dynamicTextarea outputTextarea"></textarea>');
  11327. var deleteCustomTest = OJB_safeCreateJQElement(`<button type="button" class="deleteCustomTest">${closeIcon}</button>`);
  11328. sampleDiv.append(deleteCustomTest);
  11329. sampleDiv.append(inputTextarea);
  11330. sampleDiv.append(outputTextarea);
  11331. $('#customTests').append(sampleDiv);
  11332. });
  11333.  
  11334. // 实时保存文本内容到 IndexedDB 中
  11335. $(document).on('input', '.inputTextarea, .outputTextarea', function () {
  11336. OJBetter.common.database.transaction('rw', OJBetter.common.database.samplesData, function () {
  11337. var objectStore = OJBetter.common.database.samplesData;
  11338. var samples = {
  11339. url: url,
  11340. samples: []
  11341. };
  11342. var index = 0;
  11343. $('.sampleDiv').each(function () {
  11344. var $sampleDiv = $(this);
  11345. var inputTextarea = $sampleDiv.find('.inputTextarea');
  11346. var outputTextarea = $sampleDiv.find('.outputTextarea');
  11347. $sampleDiv.attr('data-index', index);
  11348. inputTextarea.attr('id', 'input' + index);
  11349. outputTextarea.attr('id', 'output' + index);
  11350. var sample = {
  11351. id: index,
  11352. input: inputTextarea.val(),
  11353. output: outputTextarea.val()
  11354. };
  11355. samples.samples.push(sample);
  11356. index++;
  11357. });
  11358. objectStore.put(samples);
  11359. });
  11360. });
  11361.  
  11362. // 删除
  11363. $(document).on('click', '.deleteCustomTest', function () {
  11364. var $sampleDiv = $(this).closest('.sampleDiv');
  11365. OJBetter.common.database.transaction('rw', OJBetter.common.database.samplesData, function () {
  11366. var objectStore = OJBetter.common.database.samplesData;
  11367. var index = parseInt($sampleDiv.attr('data-index'));
  11368. if (!isNaN(index)) {
  11369. objectStore.get(url).then(row => {
  11370. let samples = row.samples;
  11371. samples.splice(index, 1); // 移除第index个元素
  11372. objectStore.put({
  11373. url: url,
  11374. samples: samples
  11375. });
  11376. })
  11377. }
  11378. $sampleDiv.remove();
  11379. });
  11380. });
  11381.  
  11382. // 恢复保存的内容
  11383. function restoreText() {
  11384. OJBetter.common.database.transaction('r', OJBetter.common.database.samplesData, function () {
  11385. return OJBetter.common.database.samplesData.get(url);
  11386. }).then(function (data) {
  11387. if (data.samples && data.samples.length > 0) {
  11388. data.samples.forEach(function (item, index) {
  11389. var sampleDiv = $('<div class="sampleDiv">');
  11390. var inputTextarea = OJB_safeCreateJQElement(`<p style="padding: 0px 5px;">input</p><textarea id="input${index}" class="dynamicTextarea inputTextarea"></textarea>`);
  11391. var outputTextarea = OJB_safeCreateJQElement(`<p style="padding: 0px 5px;">output</p><textarea id="output${index}" class="dynamicTextarea outputTextarea"></textarea>`);
  11392. var deleteCustomTest = OJB_safeCreateJQElement(`<button type="button" class="deleteCustomTest">${closeIcon}</button>`);
  11393.  
  11394. inputTextarea.val(item.input);
  11395. outputTextarea.val(item.output);
  11396.  
  11397. sampleDiv.append(deleteCustomTest);
  11398. sampleDiv.append(inputTextarea);
  11399. sampleDiv.append(outputTextarea);
  11400. sampleDiv.attr('data-index', index)
  11401. $('#customTests').append(sampleDiv);
  11402. });
  11403. }
  11404. });
  11405. }
  11406. }
  11407.  
  11408. // 获取自定义测试数据
  11409. function getCustomTestData() {
  11410. const url = window.location.href;
  11411.  
  11412. return new Promise(function (resolve) {
  11413. var customTestData = {};
  11414. OJBetter.common.database.transaction('r', OJBetter.common.database.samplesData, function () {
  11415. return OJBetter.common.database.samplesData.get(url);
  11416. }).then(function (data) {
  11417. if (!data) resolve(customTestData);
  11418. if (data.samples && data.samples.length > 0) {
  11419. data.samples.forEach(function (item, index) {
  11420. customTestData[index + 1] = {
  11421. input: item.input,
  11422. output: item.output
  11423. };
  11424. });
  11425. }
  11426. resolve(customTestData);
  11427. });
  11428. });
  11429. }
  11430.  
  11431. // codeforces编译器参数列表
  11432. let officialLanguage = "";
  11433. function officialCompilerArgsChange(nowSelect) {
  11434. officialLanguage = nowSelect;
  11435. $('#CompilerArgsInput').prop("disabled", true);
  11436. }
  11437.  
  11438. // codeforces编译器通信
  11439. async function officialCompiler(code, input) {
  11440. // const data = new FormData();
  11441. // data.append('csrf_token', OJBetter.common.cf_csrf_token);
  11442. const data = new URLSearchParams();
  11443. data.append('csrf_token', OJBetter.common.at_csrf_token);
  11444. // data.append('source', code);
  11445. // data.append('tabSize', '4');
  11446. // data.append('programTypeId', officialLanguage);
  11447. data.append('data.LanguageId', officialLanguage);
  11448. data.append('input', input);
  11449. // data.append('output', '');
  11450. // data.append('communityCode', '');
  11451. // data.append('action', 'submitSourceCode');
  11452. // data.append('programTypeId', officialLanguage);
  11453. data.append('sourceCode', code);
  11454.  
  11455. const requestOptions = {
  11456. method: 'POST',
  11457. // url: `${OJBetter.common.hostAddress}/data/customtest`,
  11458. url: `${OJBetter.common.hostAddress}/contests/arc172/custom_test/submit/json`,
  11459. data: data,
  11460. headers: {
  11461. // 'X-Csrf-Token': OJBetter.common.cf_csrf_token
  11462. 'Content-Type': 'application/x-www-form-urlencoded'
  11463. }
  11464. };
  11465.  
  11466. const result = {
  11467. Errors: '',
  11468. Result: '',
  11469. Stats: ''
  11470. };
  11471.  
  11472. try {
  11473. const submitResponse = await OJB_GMRequest(requestOptions);
  11474. // if (submitResponse.status !== 200 || !submitResponse.response) {
  11475. // result.Errors = `${i18next.t('compiler.official.pushError', { ns: 'codeEditor' })}`;
  11476. // return result;
  11477. // }
  11478. if (submitResponse.status !== 200) {
  11479. result.Errors = `${i18next.t('compiler.official.pushError', { ns: 'codeEditor' })}`;
  11480. return result;
  11481. }
  11482.  
  11483. // const submitResult = JSON.parse(submitResponse.response);
  11484. // const customTestSubmitId = submitResult.customTestSubmitId;
  11485.  
  11486. const verdictResponse = await OJB_promiseRetryWrapper(
  11487. getOfficialCompilerVerdict,
  11488. {
  11489. maxRetries: 10,
  11490. retryInterval: 500
  11491. },
  11492. // customTestSubmitId
  11493. );
  11494. return verdictResponse;
  11495. } catch (error) {
  11496. result.Errors = error.message;
  11497. return result;
  11498. }
  11499. }
  11500.  
  11501. // 获取codeforces编译器的执行结果
  11502. // async function getOfficialCompilerVerdict(customTestSubmitId) {
  11503. async function getOfficialCompilerVerdict() {
  11504. // const newdata = new FormData();
  11505. // newdata.append('csrf_token', OJBetter.common.cf_csrf_token);
  11506. // newdata.append('action', 'getVerdict');
  11507. // newdata.append('customTestSubmitId', customTestSubmitId);
  11508.  
  11509. // const requestOptions = {
  11510. // method: 'POST',
  11511. // url: `${OJBetter.common.hostAddress}/data/customtest`,
  11512. // data: newdata,
  11513. // headers: {
  11514. // 'X-Csrf-Token': OJBetter.common.cf_csrf_token
  11515. // }
  11516. // };
  11517. const requestOptions = {
  11518. method: 'GET',
  11519. url: `https://atcoder.jp/contests/arc172/custom_test/json?reload=true`,
  11520. };
  11521.  
  11522. const responseDetails = await OJB_GMRequest(requestOptions);
  11523. if (responseDetails.status !== 200 || !responseDetails.response) {
  11524. throw new Error(`${i18next.t('compiler.official.getResultError', { ns: 'codeEditor' })}`);
  11525. }
  11526.  
  11527. const response = JSON.parse(responseDetails.response);
  11528. // if (!response.stat) {
  11529. // throw new Error('Verdict not ready, retrying...');
  11530. // }
  11531. if (response.Interval) {
  11532. throw new Error('Verdict not ready, retrying...');
  11533. }
  11534.  
  11535. // return {
  11536. // Errors: response.verdict === "OK" ? null : response.verdict + '<br>' + response.output,
  11537. // Result: response.output.replace(/\r\n/g, "\n"),
  11538. // Stats: `Status: ${response.stat}`
  11539. // };
  11540. return {
  11541. Errors: response.Result.ExitCode === "0" ? null : response.Stderr,
  11542. Result: response.Stdout.replace(/\r\n/g, "\n"),
  11543. Stats: `Status: ExitCode: ${response.Result.ExitCode} Exec Time: ${response.Result.TimeConsumption} ms Memory: ${response.Result.MemoryConsumption} KB`
  11544. };
  11545. }
  11546.  
  11547. // rextester编译器参数列表
  11548. let rextesterLanguage = "";
  11549. function rextesterCompilerArgsChange(nowSelect) {
  11550. // let LanguageChoiceList = {
  11551. // "4": "9", "6": "8", "7": "5", "9": "1", "13": "13", "19": "42", "20": "21", "28": "30", "31": "24", "32": "20",
  11552. // "34": "17", "36": "4", "43": "6", "45": "7", "46": "4", "50": "7", "51": "9", "52": "27", "54": "7", "55": "23", "60": "4",
  11553. // "61": "7", "65": "1", "67": "12", "70": "5", "73": "7", "74": "4", "75": "46", "77": "43", "79": "1", "80": "27", "83": "43", "87": "4"
  11554. // }
  11555. let LanguageChoiceList = {
  11556. "5001": "7", "5002": "20", "5003": "1", "5005": "4", "5009": "17", "5016": "8", "5037": "13", "5041": "9",
  11557. "5047": "21", "5055": "5", "5058": "17", "5063": "5", "5078": "5", "5082": "5"
  11558. }
  11559.  
  11560. let CompilerArgsList = {
  11561. "6": "-Wall -std=gnu99 -O2 -o a.out source_file.c",
  11562. "7": "-Wall -std=c++14 -O2 -o a.out source_file.cpp",
  11563. "20": "-o a.out source_file.go",
  11564. "27": "-Wall -std=c++14 -stdlib=libc++ -O2 -o a.out source_file.cpp",
  11565. "30": "source_file.d -ofa.out"
  11566. }
  11567. if (nowSelect in LanguageChoiceList) {
  11568. $('#RunTestButton').prop("disabled", false);
  11569. rextesterLanguage = LanguageChoiceList[nowSelect];
  11570. } else {
  11571. $('#RunTestButton').prop("disabled", true);
  11572. }
  11573. if (rextesterLanguage in CompilerArgsList) {
  11574. const rextesterCompilerArgs = CompilerArgsList[rextesterLanguage];
  11575. $('#CompilerArgsInput').val(rextesterCompilerArgs);
  11576. } else {
  11577. $('#CompilerArgsInput').val("");
  11578. }
  11579. }
  11580.  
  11581. // rextester编译器通信
  11582. async function rextesterCompiler(code, input) {
  11583. try {
  11584. const result = await OJB_promiseRetryWrapper(rextesterCompilerRequest, {
  11585. maxRetries: 5,
  11586. retryInterval: 500,
  11587. errorHandler: (err) => ({ Errors: err.message, Result: '', Stats: '' })
  11588. }, code, input);
  11589. return result;
  11590. } catch (error) {
  11591. return { Errors: error.message, Result: '', Stats: '' };
  11592. }
  11593. }
  11594.  
  11595. // rextester编译器请求方法
  11596. async function rextesterCompilerRequest(code, input) {
  11597. const data = new FormData();
  11598. data.append('LanguageChoiceWrapper', rextesterLanguage);
  11599. data.append('EditorChoiceWrapper', '1');
  11600. data.append('LayoutChoiceWrapper', '1');
  11601. data.append('Program', code);
  11602. data.append('CompilerArgs', $('#CompilerArgsInput').val());
  11603. data.append('Input', input);
  11604. data.append('ShowWarnings', 'false');
  11605. data.append('IsInEditMode', 'false');
  11606. data.append('IsLive', 'false');
  11607.  
  11608. const responseDetails = await OJB_GMRequest({
  11609. method: 'POST',
  11610. url: 'https://rextester.com/rundotnet/Run',
  11611. data: data
  11612. });
  11613.  
  11614. if (responseDetails.status !== 200 || !responseDetails.response) {
  11615. throw new Error(`${i18next.t('compiler.common.error', { ns: 'codeEditor' })}`);
  11616. }
  11617.  
  11618. const response = JSON.parse(responseDetails.response);
  11619. return {
  11620. Errors: response.Errors || '',
  11621. Result: response.Result || '',
  11622. Stats: response.Stats || ''
  11623. };
  11624. }
  11625.  
  11626. // wandbox编译器参数列表
  11627. var wandboxlist = JSON.parse(GM_getResourceText("wandboxlist"));
  11628. function wandboxCompilerArgsChange(nowSelect) {
  11629. // let LanguageChoiceList = {
  11630. // "6": "PHP", "7": "Python", "9": "C#", "12": "Haskell", "13": "Perl", "19": "OCaml",
  11631. // "20": "Scala", "28": "D", "31": "Python", "32": "Go", "34": "JavaScript", "36": "Java", "40": "Python", "41": "Python",
  11632. // "43": "C++", "50": "C++", "51": "Pascal", "52": "C++", "54": "C++", "60": "Java", "61": "C++", "65": "C#", "67": "Ruby",
  11633. // "70": "Python", "73": "C++", "74": "Java", "75": "Rust", "79": "C#", "80": "C++", "87": "Java"
  11634. // }
  11635. let LanguageChoiceList = {
  11636. "5001": "C++", "5002": "Go", "5003": "C#", "5005": "Java", "5009": "JavaScript", "5010": "JavaScript",
  11637. "5012": "D", "5013": "D", "5016": "PHP", "5017": "C++", "5018": "Ruby", "5025": "Haskell",
  11638. "5027": "Lua", "5028": "C++", "5031": "C++", "5037": "Perl", "5041": "Pascal", "5042": "C#",
  11639. "5043": "Lua", "5047": "Scala", "5055": "Python", "5056": "Scala", "5059": "OCaml", "5062": "Lisp",
  11640. "5063": "Python", "5077": "D", "5078": "Python", "5081": "OCaml", "5082": "Python"
  11641. }
  11642.  
  11643. // 移除旧的
  11644. $('#CompilerChange').remove();
  11645.  
  11646. if (nowSelect in LanguageChoiceList) {
  11647. $('#RunTestButton').prop("disabled", false);
  11648. const Languagefiltered = wandboxlist.filter(obj => obj.language === LanguageChoiceList[nowSelect]);
  11649.  
  11650. // 创建编译器下拉框
  11651. let CompilerChange = $('<select id="CompilerChange" style="width: 100%;"></select>');
  11652.  
  11653. $('#CompilerSetting').show().append(CompilerChange);
  11654. for (let i = 0; i < Languagefiltered.length; i++) {
  11655. let Compiler = Languagefiltered[i];
  11656. let op = $("<option></option>")
  11657. .val(Compiler.name)
  11658. .text(Compiler["display-name"] + " " + Compiler.version);
  11659. $("#CompilerChange").append(op);
  11660. }
  11661.  
  11662. // 编译器参数刷新
  11663. function refreshCompilerArgs() {
  11664. var flags = '';
  11665. $("#CompilerBox").find("*").each(function () {
  11666. if ($(this).is("input[type='checkbox']")) {
  11667. let flag = $(this).prop("checked") ? $(this).val() : '';
  11668. flags += flag + (flag ? ' ' : '');
  11669. } else if ($(this).is("select") || $(this).is("input") || $(this).is("textarea")) {
  11670. let flag = $(this).val();
  11671. flags += flag + (flag ? ' ' : '');
  11672. }
  11673. });
  11674. $("#CompilerArgsInput").val(flags);
  11675. $("#CompilerArgsInput").prop("readonly", true); // 只读
  11676. }
  11677.  
  11678. // 编译器切换监听
  11679. CompilerChange.change(function () {
  11680. let selectedName = CompilerChange.val();
  11681. let Compiler = Languagefiltered.find(
  11682. (obj) => obj.name === selectedName
  11683. );
  11684.  
  11685. $("#CompilerArgsInput").val(); // 初始化编译器输入框
  11686.  
  11687. $("#CompilerBox").remove();
  11688. let div = $("<div id='CompilerBox'></div>");
  11689.  
  11690. let display_compile_command = OJB_safeCreateJQElement(`<input id='${Compiler.name}' value='${Compiler['display-compile-command']}' style="display:none;"}></input>`);
  11691. div.append(display_compile_command);
  11692.  
  11693. let switches = Compiler.switches;
  11694. for (let i = 0; i < switches.length; i++) {
  11695. let switche = switches[i];
  11696.  
  11697. if (switche.type == "single") {
  11698. let single = OJB_safeCreateJQElement(`
  11699. <div>
  11700. <input type='checkbox' id='${switche.name}' value='${switche['display-flags']}' ${switche.default ? 'checked' : ''}></input>
  11701. <label for='${switche.name}'>${switche['display-name']}</label>
  11702. </div>
  11703. `);
  11704. div.append(single);
  11705. single.find("input").change(function () {
  11706. refreshCompilerArgs();
  11707. });
  11708. } else if (switche.type == "select") {
  11709. let select = OJB_safeCreateJQElement(`<select id='${switche.name}'></select>`);
  11710. select.data('previousValue', switche.options[0]['display-flags']);
  11711. div.append(select);
  11712. for (let i = 0; i < switche.options.length; i++) {
  11713. let option = switche.options[i];
  11714. let op = $("<option></option>")
  11715. .val(option['display-flags'])
  11716. .text(option['display-name']);
  11717. select.append(op);
  11718. }
  11719. select.change(function () {
  11720. refreshCompilerArgs();
  11721. });
  11722. }
  11723. }
  11724.  
  11725. if (Compiler['compiler-option-raw'] == true) {
  11726. let textarea = OJB_safeCreateJQElement(`<textarea id="compiler_option_raw" placeholder="Raw compiler options" style="resize: vertical;"></textarea>`);
  11727. div.append(textarea);
  11728. textarea.on('input', function () {
  11729. refreshCompilerArgs();
  11730. });
  11731. }
  11732. if (Compiler['runtime-option-raw'] == true) {
  11733. let textarea = OJB_safeCreateJQElement(`<textarea id="runtime_option_raw" placeholder="Raw runtime options" style="resize: vertical;"></textarea>`);
  11734. div.append(textarea);
  11735. textarea.on('input', function () {
  11736. refreshCompilerArgs();
  11737. });
  11738. }
  11739. $("#CompilerSetting").append(div);
  11740.  
  11741. refreshCompilerArgs(); // 初始化
  11742. });
  11743.  
  11744. CompilerChange.trigger("change"); // 初始化
  11745. } else {
  11746. $('#RunTestButton').prop("disabled", true);
  11747. }
  11748. }
  11749.  
  11750. // wandbox编译器通信
  11751. async function wandboxCompiler(code, input) {
  11752. try {
  11753. const result = await OJB_promiseRetryWrapper(wandboxCompilerRequest, {
  11754. maxRetries: 5,
  11755. retryInterval: 500,
  11756. errorHandler: (err) => ({ Errors: err.message, Result: '', Stats: '' })
  11757. }, code, input);
  11758. return result;
  11759. } catch (error) {
  11760. return { Errors: error.message, Result: '', Stats: '' };
  11761. }
  11762. }
  11763.  
  11764. // wandbox编译器请求方法
  11765. async function wandboxCompilerRequest(code, input) {
  11766. const data = {
  11767. code: code,
  11768. codes: [],
  11769. compiler: $('#CompilerChange').val().replace($('#compiler_option_raw').val(), '').replace($('#runtime_option_raw').val(), ''),
  11770. 'compiler-option-raw': $('#compiler_option_raw').val(),
  11771. 'runtime-option-raw': $('#runtime_option_raw').val(),
  11772. options: $("#CompilerArgsInput").val(),
  11773. description: '',
  11774. stdin: input,
  11775. title: ''
  11776. };
  11777.  
  11778. const responseDetails = await OJB_GMRequest({
  11779. method: 'POST',
  11780. url: 'https://wandbox.org/api/compile.json',
  11781. data: JSON.stringify(data),
  11782. headers: {
  11783. 'Content-Type': 'application/json'
  11784. }
  11785. });
  11786.  
  11787. if (responseDetails.status !== 200 || !responseDetails.response) {
  11788. throw new Error(`${i18next.t('compiler.common.error', { ns: 'codeEditor' })}`);
  11789. }
  11790.  
  11791. const response = JSON.parse(responseDetails.response);
  11792. return {
  11793. Errors: response.compiler_error === "" ? response.signal : response.compiler_error,
  11794. Result: response.program_output || '',
  11795. Stats: response.status === "0" ? "Finish" : "Error"
  11796. };
  11797. }
  11798.  
  11799. // 更改编译器参数
  11800. function changeCompilerArgs(nowSelect) {
  11801. if (OJBetter.monaco.onlineCompilerChoice == "official") {
  11802. officialCompilerArgsChange(nowSelect);
  11803. } else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
  11804. rextesterCompilerArgsChange(nowSelect);
  11805. } else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
  11806. wandboxCompilerArgsChange(nowSelect);
  11807. }
  11808. }
  11809.  
  11810. // 在线编译器通信
  11811. async function onlineCompilerConnect(code, input) {
  11812. if (OJBetter.monaco.onlineCompilerChoice == "official") {
  11813. return await officialCompiler(code, input);
  11814. } else if (OJBetter.monaco.onlineCompilerChoice == "rextester") {
  11815. return await rextesterCompiler(code, input);
  11816. } else if (OJBetter.monaco.onlineCompilerChoice == "wandbox") {
  11817. return await wandboxCompiler(code, input);
  11818. }
  11819. }
  11820.  
  11821. // 差异对比
  11822. function codeDiff(expectedText, actualText) {
  11823. // 将文本按行拆分
  11824. const expectedLines = expectedText ? expectedText.split('\n') : [];
  11825. const actualLines = actualText ? actualText.split('\n') : [];
  11826.  
  11827. const output = document.createElement('div');
  11828.  
  11829. const createLineElement = (lineNo, contentElement) => {
  11830. const lineDiv = document.createElement('div');
  11831. lineDiv.className = 'diffLine';
  11832.  
  11833. const lineNoDiv = document.createElement('div');
  11834. lineNoDiv.className = 'lineNo';
  11835. lineNoDiv.textContent = lineNo;
  11836.  
  11837. lineDiv.appendChild(lineNoDiv);
  11838. lineDiv.appendChild(contentElement);
  11839.  
  11840. return lineDiv;
  11841. };
  11842.  
  11843. const createContentElement = (isEquals = true, expected = null, removed = null) => {
  11844. const contentDiv = document.createElement('div');
  11845. contentDiv.className = 'lineContent';
  11846.  
  11847. if (isEquals) {
  11848. const span = document.createElement('span');
  11849. span.textContent = expected;
  11850. contentDiv.appendChild(span);
  11851. } else {
  11852. if (removed != null) {
  11853. const removedSpan = document.createElement('span');
  11854. removedSpan.className = 'removed';
  11855. removedSpan.textContent = removed;
  11856. contentDiv.appendChild(removedSpan);
  11857. }
  11858. if (expected != null) {
  11859. const addedSpan = document.createElement('span');
  11860. addedSpan.className = 'added';
  11861. addedSpan.textContent = expected;
  11862. contentDiv.appendChild(addedSpan);
  11863. }
  11864. }
  11865.  
  11866. return contentDiv;
  11867. };
  11868.  
  11869. let index = 1;
  11870.  
  11871. expectedLines.forEach((expectedLine, i) => {
  11872. const actualLine = actualLines[i];
  11873. if (actualLine === undefined) {
  11874. output.appendChild(createLineElement(index++, createContentElement(false, expectedLine)));
  11875. } else if (expectedLine === actualLine) {
  11876. output.appendChild(createLineElement(index++, createContentElement(true, expectedLine)));
  11877. } else {
  11878. output.appendChild(createLineElement(index++, createContentElement(false, expectedLine, actualLine)));
  11879. }
  11880. });
  11881.  
  11882. // 处理多余的 actualLines
  11883. for (let i = expectedLines.length; i < actualLines.length; i++) {
  11884. output.appendChild(createLineElement(index++, createContentElement(false, null, actualLines[i])));
  11885. }
  11886.  
  11887. return output.innerHTML;
  11888. }
  11889.  
  11890. // 内容类型常量
  11891. const TestCaseContentType = {
  11892. TERMINAL: 'terminal',
  11893. DIFF: 'diff',
  11894. NO_DIFF: 'no_diff',
  11895. SUCCESS: 'success'
  11896. };
  11897.  
  11898. // 样例测试状态类
  11899. class TestCaseStatus {
  11900. constructor(item, prefix) {
  11901. this.testCase = OJB_safeCreateJQElement(`<div class="test-case"></div>`);
  11902. this.item = item;
  11903. this.prefix = prefix;
  11904. this.titleElement = OJB_safeCreateJQElement(`<div class="test-case-title">${this.prefix} ${this.item}</div>`);
  11905. this.statusElement = OJB_safeCreateJQElement(`<div class="test-case-status"></div>`);
  11906. this.contentElement = OJB_safeCreateJQElement(`<div class="test-case-content"></div>`);
  11907. this.judgeElement = OJB_safeCreateJQElement(`<div class="test-case-judge"></div>`);
  11908. this.testCase.append(this.titleElement, this.statusElement, this.contentElement, this.judgeElement);
  11909. $('#statePanel').append(this.testCase);
  11910. this.setStatus('Queued', 'queued');
  11911. }
  11912.  
  11913. setTitle(title) {
  11914. this.titleElement.text(title);
  11915. }
  11916.  
  11917. setStatus(text, status) {
  11918. this.statusElement.text(text).removeClass('queued running success error').addClass(status);
  11919. }
  11920.  
  11921. setContent(content, type) {
  11922. // 如果内容类型为SUCCESS,隐藏内容元素并提前返回
  11923. if (type === TestCaseContentType.SUCCESS) {
  11924. this.contentElement.hide();
  11925. return;
  11926. }
  11927.  
  11928. // 根据内容类型创建内容元素
  11929. const createContentElementByType = (content, type) => {
  11930. let contentElement;
  11931. switch (type) {
  11932. case TestCaseContentType.TERMINAL:
  11933. // 为TERMINAL类型创建一个新的终端容器
  11934. contentElement = OJB_safeCreateJQElement(`<div class="terminal-container" style="overflow: auto;"></div>`);
  11935. break;
  11936. case TestCaseContentType.DIFF:
  11937. case TestCaseContentType.NO_DIFF:
  11938. // 为DIFF和NO_DIFF类型创建相应的内容元素,并添加差异说明
  11939. const className = type === TestCaseContentType.DIFF ? "output_diff" : "output_no_diff";
  11940. contentElement = OJB_safeCreateJQElement(`<pre class="${className}">${content}</pre>`);
  11941. appendDiffNote();
  11942. break;
  11943. default:
  11944. throw new Error("Unsupported content type.");
  11945. }
  11946. return contentElement;
  11947. };
  11948.  
  11949. // 初始化终端
  11950. const initializeTerminal = (content, contentElement) => {
  11951. const term = new Terminal({ rows: 10, cols: 150 });
  11952. term.setOption('theme', { background: '#2d2e2c' });
  11953. term.setOption('convertEol', true); // 将换行符\n转换为\r\n
  11954. term.write(content);
  11955. term.open(contentElement.get(0));
  11956. };
  11957.  
  11958. // 添加差异说明
  11959. const appendDiffNote = () => {
  11960. const diffNote = OJB_safeCreateJQElement(`<div class="diff_note">${i18next.t('resultBlock.diffNote', { ns: 'codeEditor' })}</div>`);
  11961. this.testCase.append(diffNote);
  11962. };
  11963.  
  11964. // 创建并追加内容元素
  11965. const contentElement = createContentElementByType(content, type);
  11966. this.contentElement.append(contentElement);
  11967.  
  11968. // 如果内容类型为TERMINAL,初始化并打开终端
  11969. if (type === TestCaseContentType.TERMINAL) {
  11970. initializeTerminal(content, contentElement);
  11971. }
  11972. }
  11973.  
  11974. setJudge(judge) {
  11975. this.judgeElement.text(judge);
  11976. }
  11977. }
  11978.  
  11979. // 样例测试函数
  11980. async function runCode(event, runButton, sourceDiv, submitDiv) {
  11981. event.preventDefault();
  11982. const statePanel = $('#statePanel').show().empty();
  11983. const testData = getTestData();
  11984. const customTestData = await getCustomTestData();
  11985. const totalTests = Object.keys(customTestData).length + Object.keys(testData).length;
  11986.  
  11987. let passedTests = 0;
  11988. let failedTests = 0;
  11989. let hasError = false;
  11990.  
  11991. // 定义一个对象队列,包括创建的样例块实例和对应的样例数据
  11992. const queue = [];
  11993.  
  11994. // 先生成各个样例的块,并显示排队中,将创建的各个对象存到队列中,以便后面更新
  11995. for (const [item, data] of Object.entries(customTestData)) {
  11996. const testCase = new TestCaseStatus(item, i18next.t('resultBlock.title.custom', { ns: 'codeEditor' }));
  11997. queue.push({ testCase, data });
  11998. }
  11999.  
  12000. if (!$('#onlyCustomTest').prop('checked')) {
  12001. for (const [item, data] of Object.entries(testData)) {
  12002. const testCase = new TestCaseStatus(item, i18next.t('resultBlock.title.sample', { ns: 'codeEditor' }));
  12003. queue.push({ testCase, data });
  12004. }
  12005. }
  12006.  
  12007. // 测试函数
  12008. const runTest = async (testCase, data, index) => {
  12009. runButton.setButtonState('running', `${index}/${totalTests}`);
  12010.  
  12011. testCase.setStatus('Running', 'running');
  12012. const result = await onlineCompilerConnect(sourceDiv.val(), data.input);
  12013.  
  12014. if (result.Errors) {
  12015. testCase.setStatus('Compilation error or Time limit', 'error');
  12016. testCase.setContent(result.Errors, TestCaseContentType.TERMINAL);
  12017. hasError = true;
  12018. } else if (result.Result.trim() === data.output.trim()) {
  12019. testCase.setStatus('Accepted', 'success');
  12020. testCase.setContent('The output is correct.', TestCaseContentType.SUCCESS);
  12021. passedTests++;
  12022. } else {
  12023. testCase.setStatus('Wrong Answer', 'error');
  12024. const diffContent = $('#DontShowDiff').prop('checked') ? result.Result.trim() : codeDiff(data.output.trim(), result.Result.trim());
  12025. const contentType = $('#DontShowDiff').prop('checked') ? TestCaseContentType.NO_DIFF : TestCaseContentType.DIFF;
  12026. testCase.setContent(diffContent, contentType);
  12027. failedTests++;
  12028. }
  12029.  
  12030. const judgeStats = `${i18next.t('resultBlock.state', { ns: 'codeEditor' })}${result.Stats}`;
  12031. testCase.setJudge(judgeStats);
  12032.  
  12033. await OJB_delay(500); // 等待500毫秒
  12034. };
  12035.  
  12036. // 对队列中的对象进行测试
  12037. for (let i = 0; i < queue.length; i++) {
  12038. const { testCase, data } = queue[i];
  12039. await runTest(testCase, data, i + 1);
  12040. }
  12041.  
  12042. // 测试完成后更新按钮状态
  12043. if (hasError) {
  12044. runButton.setButtonState('error', i18next.t('runTestButton.error', { ns: 'codeEditor' }));
  12045. } else if (failedTests > 0) {
  12046. runButton.setButtonState('error', `${passedTests}/${totalTests} ` + i18next.t('runTestButton.partial', { ns: 'codeEditor' }));
  12047. } else {
  12048. runButton.setButtonState('success', i18next.t('runTestButton.success', { ns: 'codeEditor' }));
  12049. }
  12050. }
  12051.  
  12052. /**
  12053. * 添加题目页代码编辑器
  12054. * @returns
  12055. */
  12056. async function addProblemPageCodeEditor() {
  12057. // if (typeof ace === 'undefined') {
  12058. // const loadingMessage = new LoadingMessage();
  12059. // loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('error.codeEditor.load', { ns: 'alert' })}`, 'error');
  12060. // return; // 因为Codeforces设定的是未登录时不能访问提交页,也不会加载ace库
  12061. // }
  12062.  
  12063. // 获取提交页链接
  12064. const href = window.location.href;
  12065. let submitUrl = OJBetter.common.hostAddress + $('.form-code-submit').attr('action');
  12066. console.log(submitUrl);
  12067. // if (/\/problemset\//.test(href)) {
  12068. // // problemset
  12069. // submitUrl = OJBetter.common.hostAddress + '/problemset/submit';
  12070. // } else if (/\/gym\//.test(href)) {
  12071. // // gym 题目
  12072. // submitUrl = OJBetter.common.hostAddress + '/gym/' + ((href) => {
  12073. // const regex = /\/gym\/(?<num>[0-9a-zA-Z]*?)\/problem\//;
  12074. // const match = href.match(regex);
  12075. // return match && match.groups.num;
  12076. // })(href) + '/submit';
  12077. // } else if (OJBetter.typeOfPage.is_acmsguru) {
  12078. // // acmsguru 题目
  12079. // submitUrl = href.replace(/\/problemsets[A-Za-z0-9\/#]*/, "/problemsets/acmsguru/submit");
  12080. // } else {
  12081. // submitUrl = href.replace(/\/problem[A-Za-z0-9\/#]*/, "/submit");
  12082. // }
  12083.  
  12084. // // 获取提交页HTML
  12085. // let cloneHTML = await getSubmitHTML(submitUrl);
  12086.  
  12087. // 创建
  12088. // let form = await createCodeEditorForm(submitUrl, cloneHTML);
  12089. let form = await createCodeEditorForm(submitUrl);
  12090. let selectLang = form.selectLang;
  12091. let submitButton = form.submitButton;
  12092. let runButton = form.runButton;
  12093.  
  12094. // 初始化
  12095. CustomTestInit(); // 自定义测试数据面板
  12096. selectLang.val(OJBetter.monaco.compilerSelection);
  12097. changeMonacoLanguage(form);
  12098.  
  12099. // 这里由于Atcoder使用了Select2 ,其在初始化时会触发change事件,因此需要在初始化后再绑定事件
  12100. const observer = new MutationObserver((mutations) => {
  12101. mutations.forEach((mutation) => {
  12102. if (mutation.attributeName === "class" && $(mutation.target).hasClass("select2-hidden-accessible")) {
  12103. $(mutation.target).on('change', (e) => {
  12104. selectLang.on('change', () => changeMonacoLanguage(form)); // 编辑器语言切换监听
  12105. });
  12106. observer.disconnect();
  12107. }
  12108. });
  12109. });
  12110. observer.observe(selectLang[0], { attributes: true });
  12111.  
  12112. // 样例测试
  12113. runButton.on('click', (event) => runCode(event, runButton, form.sourceDiv, form.submitDiv))
  12114. .setHoverRedo();
  12115.  
  12116. // 提交
  12117. submitButton.on('click', async function (event) {
  12118. event.preventDefault();
  12119. if (OJBetter.monaco.setting.isCodeSubmitDoubleConfirm) {
  12120. const submit = await OJB_createDialog(
  12121. i18next.t('submitCode.title', { ns: 'dialog' }),
  12122. i18next.t('submitCode.content', { ns: 'dialog' }),
  12123. [
  12124. i18next.t('submitCode.buttons.0', { ns: 'dialog' }),
  12125. i18next.t('submitCode.buttons.1', { ns: 'dialog' })
  12126. ]
  12127. ); //提交确认
  12128. if (submit) {
  12129. submitButton.after(`<img class="OJBetter_loding" src="//codeforces.org/s/84141/images/ajax-loading-24x24.gif">`);
  12130. $('#OJBetter_SubmitForm').submit();
  12131. } else {
  12132. submitButton.addClass('disabled');
  12133. setTimeout(function () {
  12134. submitButton.removeClass('disabled');
  12135. }, 300);
  12136. }
  12137. } else {
  12138. $('#OJBetter_SubmitForm').submit();
  12139. }
  12140. });
  12141. }
  12142.  
  12143. /**
  12144. * 获取翻译服务目标语言的对应代码
  12145. * @param {string} serverName 服务名称
  12146. * @returns {string} 目标语言,如果没有对应代码则返回中文
  12147. */
  12148. function getTargetLanguage(serverName) {
  12149. let targetLanguage = OJBetter.supportList.translationSupport[serverName][OJBetter.translation.targetLang];
  12150. if (targetLanguage) return targetLanguage;
  12151. else return OJBetter.supportList.translationSupport[serverName]['zh'];
  12152. }
  12153.  
  12154. /**
  12155. * 将文本中Markdown格式的加粗**转换成HTML格式。
  12156. * @param {string} text 文本
  12157. * @returns {string} 替换后的字符串
  12158. */
  12159. function convertBoldMarkdownToHTML(text) {
  12160. return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
  12161. }
  12162.  
  12163. /**
  12164. * 将文本中Markdown格式的链接文本转换成HTML格式。
  12165. * @param {string} text 文本
  12166. * @returns {string} 替换后的字符串
  12167. */
  12168. function convertLinksMarkdownToHTML(text) {
  12169. return text.replace(/(?<!!)\[(.*?)\]\(([^"]*?)("(.*?)")*\)/g, '<a href="$2" title="$4">$1</a>');
  12170. }
  12171.  
  12172. /**
  12173. * 将HTML格式的加粗文本转换回Markdown格式。
  12174. * @param {string} text 文本
  12175. * @returns {string} 替换后的字符串
  12176. */
  12177. function convertBoldHTMLToMarkdown(text) {
  12178. return text.replace(/<strong>(.*?)<\/strong>/g, '**$1**');
  12179. }
  12180.  
  12181. /**
  12182. * 将HTML格式的链接文本转换回Markdown格式。
  12183. * @param {string} html - 包含HTML链接标签<a>的字符串。
  12184. * @returns {string} 转换后的字符串,其中HTML链接标签被替换为Markdown的链接语法。
  12185. */
  12186. function convertLinksHTMLToMarkdown(html) {
  12187. return html.replace(/<a href="([^"]*)"( title="([^"]*)")*>([^<]+)<\/a>/g, '[$4]($1 "$3")');
  12188. }
  12189.  
  12190. /**
  12191. * DeepL翻译
  12192. * @param {string} raw 原文
  12193. * @returns {Promise<TransRawData>} 翻译结果对象
  12194. */
  12195. async function translate_deepl(raw) {
  12196. const id = (Math.floor(Math.random() * 99999) + 100000) * 1000;
  12197. const data = {
  12198. jsonrpc: '2.0',
  12199. method: 'LMT_handle_texts',
  12200. id,
  12201. params: {
  12202. splitting: 'newlines',
  12203. lang: {
  12204. source_lang_user_selected: 'auto',
  12205. target_lang: getTargetLanguage('deepl'),
  12206. },
  12207. texts: [{
  12208. text: raw,
  12209. requestAlternatives: 3
  12210. }],
  12211. timestamp: getTimeStamp(raw.split('i').length - 1)
  12212. }
  12213. }
  12214. let postData = JSON.stringify(data);
  12215. if ((id + 5) % 29 === 0 || (id + 3) % 13 === 0) {
  12216. postData = postData.replace('"method":"', '"method" : "');
  12217. } else {
  12218. postData = postData.replace('"method":"', '"method": "');
  12219. }
  12220. const options = {
  12221. method: 'POST',
  12222. url: 'https://www2.deepl.com/jsonrpc',
  12223. data: postData,
  12224. headers: {
  12225. 'Content-Type': 'application/json',
  12226. 'Host': 'www2.deepl.com',
  12227. 'Origin': 'https://www.deepl.com',
  12228. 'Referer': 'https://www.deepl.com/',
  12229. },
  12230. anonymous: true,
  12231. nocache: true,
  12232. }
  12233.  
  12234. return await BaseTranslate(options, res => JSON.parse(res)?.result?.texts?.[0]?.text || res, res => {
  12235. const resObj = {
  12236. status: true,
  12237. message: 'ok'
  12238. };
  12239. if (res.includes('"message":"Too many requests"')) {
  12240. resObj.status = false;
  12241. resObj.message = i18next.t('error.deepl429', { ns: 'translator' }); // Too many requests 提示
  12242. return resObj;
  12243. };
  12244. return resObj;
  12245. });
  12246. }
  12247.  
  12248. /**
  12249. * 使用 DeepL Free API 进行翻译
  12250. * @param {string} raw 原文
  12251. * @returns {Promise<TransRawData>} 翻译结果对象
  12252. */
  12253. async function translate_deepl_api_free(raw) {
  12254. const data = JSON.stringify({
  12255. text: [raw],
  12256. target_lang: getTargetLanguage('deepl'),
  12257. split_sentences: '1',
  12258. ...(OJBetter.deepl.enableEmphasisProtection || OJBetter.deepl.enableLinkProtection ? { tag_handling: 'html' } : {}),
  12259. ...Object.assign({}, ...OJBetter.deepl.config.data)
  12260. });
  12261.  
  12262. const options = {
  12263. method: "POST",
  12264. url: OJBetter.deepl.config.proxy || "https://api-free.deepl.com/v2/translate",
  12265. headers: {
  12266. "Authorization": `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
  12267. "Content-Type": "application/json",
  12268. ...Object.assign({}, ...OJBetter.deepl.config.header)
  12269. },
  12270. data: data,
  12271. onload: response => response.responseText,
  12272. onerror: error => console.error(error)
  12273. };
  12274.  
  12275. return await BaseTranslate(options, res => JSON.parse(res).translations[0].text);
  12276. }
  12277.  
  12278. /**
  12279. * 使用 DeepL Pro API 进行翻译
  12280. * @param {string} raw 原文
  12281. * @returns {Promise<TransRawData>} 翻译结果对象
  12282. */
  12283. async function translate_deepl_api_pro(raw) {
  12284. const data = JSON.stringify({
  12285. text: [raw],
  12286. target_lang: getTargetLanguage('deepl'),
  12287. split_sentences: '1',
  12288. ...(OJBetter.deepl.enableEmphasisProtection || OJBetter.deepl.enableLinkProtection ? { tag_handling: 'html' } : {}),
  12289. ...Object.assign({}, ...OJBetter.deepl.config.data)
  12290. });
  12291.  
  12292. const options = {
  12293. method: "POST",
  12294. url: OJBetter.deepl.config.proxy || "https://api.deepl.com/v2/translate",
  12295. headers: {
  12296. "Authorization": `DeepL-Auth-Key ${OJBetter.deepl.config.key}`,
  12297. "Content-Type": "application/json",
  12298. ...Object.assign({}, ...OJBetter.deepl.config.header)
  12299. },
  12300. data: data,
  12301. onload: response => response.responseText,
  12302. onerror: error => console.error(error)
  12303. };
  12304.  
  12305. return await BaseTranslate(options, res => JSON.parse(res).translations[0].text);
  12306. }
  12307.  
  12308. /**
  12309. * 使用 DeepLX 进行翻译
  12310. * @param {String} text 原文
  12311. * @returns {Promise<TransRawData>} 翻译结果对象
  12312. */
  12313. async function translate_deeplx(text) {
  12314. const options = {
  12315. method: "POST",
  12316. url: OJBetter.deepl.config.proxy || 'https://api.deeplx.org/translate',
  12317. data: JSON.stringify({
  12318. "text": text,
  12319. "source_lang": "EN",
  12320. "target_lang": getTargetLanguage('deepl'),
  12321. }),
  12322. headers: {
  12323. 'Content-Type': 'application/json',
  12324. ...(OJBetter.deepl.config.key ? { Authorization: `Bearer ${OJBetter.deepl.config.key}` } : {})
  12325. },
  12326. responseType: "json",
  12327. };
  12328.  
  12329. return await BaseTranslate(options, res => {
  12330. const parsedResponse = JSON.parse(res);
  12331. if (parsedResponse.code === 200 && parsedResponse.data) {
  12332. return parsedResponse.data;
  12333. } else {
  12334. throw new Error('Translation failed or invalid response format.');
  12335. }
  12336. });
  12337. }
  12338.  
  12339. function getTimeStamp(iCount) {
  12340. const ts = Date.now();
  12341. if (iCount !== 0) {
  12342. iCount = iCount + 1;
  12343. return ts - (ts % iCount) + iCount;
  12344. } else {
  12345. return ts;
  12346. }
  12347. }
  12348.  
  12349. /**
  12350. * 讯飞听见翻译
  12351. * @param {String} text 要翻译的文本
  12352. * @returns {Promise<TransRawData>} 翻译结果对象
  12353. */
  12354. async function translate_iflyrec(text) {
  12355. const options = {
  12356. method: "POST",
  12357. url: 'https://www.iflyrec.com/TranslationService/v1/textTranslation',
  12358. data: JSON.stringify({
  12359. "from": "2",
  12360. "to": getTargetLanguage('iflyrec'),
  12361. "contents": [{
  12362. "text": text,
  12363. "frontBlankLine": 0
  12364. }]
  12365. }),
  12366. anonymous: true,
  12367. headers: {
  12368. 'Content-Type': 'application/json',
  12369. 'Origin': 'https://www.iflyrec.com',
  12370. },
  12371. responseType: "json",
  12372. };
  12373. return await BaseTranslate(options, res => JSON.parse(res).biz[0].translateResult.replace(/\\n/g, "\n\n"));
  12374. }
  12375.  
  12376. /**
  12377. * 有道翻译
  12378. * @param {string} raw 原文
  12379. * @returns {Promise<TransRawData>} 翻译结果对象
  12380. */
  12381. async function translate_youdao_mobile(raw) {
  12382. const options = {
  12383. method: "POST",
  12384. url: 'http://m.youdao.com/translate',
  12385. data: "inputtext=" + encodeURIComponent(raw) + "&type=" + getTargetLanguage('youdao'),
  12386. anonymous: true,
  12387. headers: {
  12388. "Content-Type": "application/x-www-form-urlencoded",
  12389. 'Host': 'm.youdao.com',
  12390. 'Origin': 'http://m.youdao.com',
  12391. 'Referer': 'http://m.youdao.com/translate',
  12392. }
  12393. }
  12394. return await BaseTranslate(options,
  12395. res => {
  12396. const array = /id="translateResult">\s*?<li>([\s\S]*?)<\/li>\s*?<\/ul/.exec(res);
  12397. if (array && array.length > 1) {
  12398. return array[1];
  12399. } else {
  12400. return res;
  12401. }
  12402. },
  12403. res => {
  12404. const resObj = {
  12405. status: true,
  12406. message: 'ok'
  12407. };
  12408. if (res.includes('<title>413 Request Entity Too Large</title>')) {
  12409. resObj.status = false;
  12410. resObj.message = i18next.t('error.youdao413', { ns: 'translator' }); // Request Entity Too Large 提示
  12411. return resObj;
  12412. };
  12413. return resObj;
  12414. })
  12415. }
  12416.  
  12417. /**
  12418. * google翻译
  12419. * @param {string} raw 原文
  12420. * @returns {Promise<TransRawData>} 翻译结果对象
  12421. */
  12422. async function translate_gg(raw) {
  12423. const params = `tl=${getTargetLanguage('google')}&q=${encodeURIComponent(raw)}`;
  12424. const options = {
  12425. method: "GET",
  12426. url: `https://translate.google.com/m?${params}`,
  12427. }
  12428. return await BaseTranslate(options,
  12429. res => $(res).filter('.result-container').text() || $(res).find('.result-container').text());
  12430. }
  12431.  
  12432. /**
  12433. * 彩云翻译
  12434. * @param {string} raw 原文
  12435. * @returns {Promise<TransRawData>} 翻译结果对象
  12436. */
  12437. async function translate_caiyun(raw) {
  12438. const source = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";
  12439. const dic = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"].reduce((dic, current, index) => { dic[current] = source[index]; return dic }, {});
  12440. const browser_id = CryptoJS.MD5(Math.random().toString()).toString();
  12441. const caiyun_jwt = await (async () => {
  12442. const options = {
  12443. method: "POST",
  12444. url: 'https://api.interpreter.caiyunai.com/v1/user/jwt/generate',
  12445. headers: {
  12446. "content-type": "application/json",
  12447. "x-authorization": "token:qgemv4jr1y38jyq6vhvi",
  12448. "origin": "https://fanyi.caiyunapp.com",
  12449. },
  12450. data: JSON.stringify({ browser_id }),
  12451. }
  12452. const res = await OJB_GMRequest(options);
  12453. return JSON.parse(res.responseText).jwt;
  12454. })();
  12455.  
  12456. // 解码
  12457. const decodeUnicode = str => {
  12458. const decoder = new TextDecoder();
  12459. const data = Uint8Array.from(atob(str), c => c.charCodeAt(0));
  12460. return decoder.decode(data);
  12461. };
  12462. const decoder = line => decodeUnicode([...line].map(i => dic[i] || i).join(""));
  12463.  
  12464. const options = {
  12465. method: "POST",
  12466. url: 'https://api.interpreter.caiyunai.com/v1/translator',
  12467. data: JSON.stringify({
  12468. "source": raw.split('\n'),
  12469. "browser_id": browser_id,
  12470. "trans_type": getTargetLanguage('caiyun'),
  12471. "request_id": "web_fanyi",
  12472. "media": "text",
  12473. "os_type": "web",
  12474. "dict": true,
  12475. "cached": true,
  12476. "replaced": true,
  12477. "style": "formal",
  12478. "model": "",
  12479. "detect": true,
  12480. }),
  12481. headers: {
  12482. "content-type": "application/json;charset=UTF-8",
  12483. "x-authorization": "token:qgemv4jr1y38jyq6vhvi",
  12484. "t-authorization": caiyun_jwt
  12485. }
  12486. }
  12487. return await BaseTranslate(options, res => JSON.parse(res).target.map(decoder).join('\n'))
  12488. }
  12489.  
  12490. /**
  12491. * ChatGPT
  12492. * @param {string} raw 原文
  12493. * @returns {Promise<TransRawData>} 翻译结果对象
  12494. */
  12495. async function translate_openai(raw) {
  12496. const modelDefault = 'gpt-3.5-turbo';
  12497. const lang = getTargetLanguage('openai');
  12498. const prompt = `
  12499. As a professional English translator, your task is to accurately translate a segment of an algorithm programming competition question into ${lang}.
  12500. The translation should use professional terms and maintain the text format, including ${OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru
  12501. ? "keeping the LaTeX equations unchanged."
  12502. : "keeping the brackets【】, HTML tags, and their content unchanged."
  12503. }
  12504. After translation, please ensure that the ${lang} version conforms to normal expression habits.
  12505. What I need is a carefully polished ${lang} translation of my question segment, The segment to be translated is as follows: "
  12506. ${raw}
  12507. "`;
  12508. const data = {
  12509. model: OJBetter.chatgpt.config.model || modelDefault,
  12510. messages: [{
  12511. role: "assistant",
  12512. content: prompt
  12513. }],
  12514. temperature: 0.7,
  12515. ...Object.assign({}, ...OJBetter.chatgpt.config.data)
  12516. }
  12517. const options = {
  12518. method: "POST",
  12519. url: OJBetter.chatgpt.config.proxy || 'https://api.openai.com/v1/chat/completions',
  12520. data: JSON.stringify(data),
  12521. responseType: 'json',
  12522. headers: {
  12523. 'Content-Type': 'application/json',
  12524. 'Authorization': 'Bearer ' + OJBetter.chatgpt.config.key,
  12525. ...Object.assign({}, ...OJBetter.chatgpt.config.header)
  12526. }
  12527. }
  12528. return await BaseTranslate(options,
  12529. res => res,
  12530. undefined,
  12531. response => response.response.choices[0].message.content);
  12532. }
  12533.  
  12534. /**
  12535. * ChatGPT 流式传输
  12536. * @param {string} raw 原文
  12537. * @param {TranslateDiv} translateDiv 翻译结果面板
  12538. * @returns {Promise<TransRawData>} 翻译结果对象
  12539. */
  12540. async function translate_openai_stream(raw, translateDiv) {
  12541. const result = {
  12542. done: true,
  12543. checkPassed: null,
  12544. response: null,
  12545. responseText: null,
  12546. text: "",
  12547. error: null,
  12548. message: null
  12549. };
  12550. const helpText = i18next.t('error.basic', { ns: 'translator' }); // 基本帮助提示信息
  12551. try {
  12552. for await (const delta of openai_stream(raw)) {
  12553. result.text += delta;
  12554. // 翻译结果面板更新
  12555. translateDiv.updateTranslateDiv(result.text, !(OJBetter.typeOfPage.is_oldLatex || OJBetter.typeOfPage.is_acmsguru), false);
  12556. }
  12557. return result;
  12558. } catch (err) {
  12559. console.warn(err);
  12560. result.error = {
  12561. message: err.message || null,
  12562. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  12563. enumerable: err,
  12564. source: 'openai_stream'
  12565. };
  12566. result.message = `${i18next.t('error.GMRequest', { ns: 'translator' })}${helpText}`;
  12567. }
  12568.  
  12569. return result;
  12570. }
  12571.  
  12572. /**
  12573. * 流式传输
  12574. * @param {string} raw 原文
  12575. * @returns {AsyncGenerator<string>} 返回 AsyncGenerator
  12576. */
  12577. async function* openai_stream(raw) {
  12578. const modelDefault = 'gpt-3.5-turbo';
  12579. const lang = getTargetLanguage('openai');
  12580. const prompt = `
  12581. I hope you can act as a professional English translator to help me translate a segment of an algorithm programming competition question into ${lang}.
  12582. 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
  12583. ? "keeping the LaTeX equations unchanged."
  12584. : "keeping the brackets【】, HTML tags, and their content unchanged."
  12585. }
  12586. After completing the translation, please polish the ${lang} version to ensure it conforms to normal expression habits.
  12587. What I need is a carefully polished ${lang} translation of my question segment, which is as follows:
  12588. "
  12589. ${raw}
  12590. "`;
  12591. const data = {
  12592. model: OJBetter.chatgpt.config.model || modelDefault,
  12593. messages: [{
  12594. role: "assistant",
  12595. content: prompt
  12596. }],
  12597. temperature: 0.7,
  12598. stream: true,
  12599. ...Object.assign({}, ...OJBetter.chatgpt.config.data)
  12600. }
  12601. const options = {
  12602. method: "POST",
  12603. url: OJBetter.chatgpt.config.proxy || 'https://api.openai.com/v1/chat/completions',
  12604. data: JSON.stringify(data),
  12605. responseType: 'stream',
  12606. headers: {
  12607. 'Content-Type': 'application/json',
  12608. 'Authorization': 'Bearer ' + OJBetter.chatgpt.config.key,
  12609. ...Object.assign({}, ...OJBetter.chatgpt.config.header)
  12610. }
  12611. }
  12612. const response = await OJB_GMRequest(options, true);
  12613. const reader = response.response.getReader();
  12614. const decoder = new TextDecoder();
  12615. let buffer = ''; // 用于累积数据片段的缓冲区
  12616.  
  12617. while (true) {
  12618. const { done, value } = await reader.read();
  12619. if (done) break;
  12620. buffer += decoder.decode(value, { stream: true }); // 将新的数据片段追加到缓冲区
  12621. let lines = buffer.split("\n\n"); // 处理累积的数据
  12622.  
  12623. // 缓冲区的最后一行可能还未完整接收,保留在缓冲区中,-1
  12624. for (let i = 0; i < lines.length - 1; i++) {
  12625. let line = lines[i];
  12626. line = line.substring(5); // 移除 'data:' 前缀
  12627. if (line.includes('[DONE]')) {
  12628. return; // End
  12629. }
  12630. try {
  12631. let data = JSON.parse(line);
  12632. let delta = data['choices'][0]['delta'];
  12633. let content = delta['content'] ? delta['content'] : "";
  12634. yield content; // 传递数据给调用者
  12635. } catch (error) {
  12636. console.warn(`Error parsing JSON: ${error}\n\nError data: ${line}`);
  12637. }
  12638. }
  12639.  
  12640. // 保留最后一行在缓冲区中
  12641. buffer = lines.slice(-1);
  12642. }
  12643.  
  12644. return buffer;
  12645. }
  12646.  
  12647. /**
  12648. * @typedef {Object} CheckResponseResult
  12649. * @property {boolean} status 检查是否通过
  12650. * @property {string} message 检查失败时的消息
  12651. */
  12652.  
  12653. /**
  12654. * @typedef {Object} ErrorResponse
  12655. * @property {Object} message 错误消息
  12656. * @property {Object} stack 错误堆栈
  12657. * @property {Object} enumerable 可枚举的错误属性
  12658. * @property {string} source 错误来源
  12659. */
  12660.  
  12661. /**
  12662. * @typedef {Object} TransRawData
  12663. * @property {boolean} done 操作是否完成
  12664. * @property {CheckResponseResult|null} checkPassed 检查是否通过的结果
  12665. * @property {Object|null} response 响应对象
  12666. * @property {string|null} text 处理后的文本
  12667. * @property {ErrorResponse} error 错误列表
  12668. * @property {string|null} message 可能的消息
  12669. */
  12670.  
  12671. /**
  12672. * 通用翻译函数
  12673. * @param {Object} options GM_xmlhttpRequest 的参数
  12674. * @param {Function} processer 响应再处理函数,它接收响应文本,并应返回处理后的文本。
  12675. * @param {Function} checkResponse 检查文本是否符合预期的函数,它接收文本,并返回一个Object,包含状态和信息。默认为返回 { status: true, message: 'ok' }
  12676. * @param {Function} getResponseText 重写响应文本获取函数,它接收response,并返回响应文本。 默认为 response.responseText
  12677. * @returns {Promise<TransRawData>} 返回 Promise,其解析值为翻译结果对象
  12678. */
  12679. async function BaseTranslate(options, processer, checkResponse = () => { return { status: true, message: 'ok' } }, getResponseText = (response) => response.responseText) {
  12680. const result = {
  12681. done: false,
  12682. checkPassed: null,
  12683. response: null,
  12684. responseText: null,
  12685. text: "",
  12686. error: null,
  12687. message: null
  12688. };
  12689. const helpText = i18next.t('error.basic', { ns: 'translator' }); // 基本帮助提示信息
  12690. const toDo = async () => {
  12691. try {
  12692. result.response = await OJB_GMRequest(options);
  12693. result.responseText = result.response.responseText;
  12694. result.text = getResponseText(result.response);
  12695. } catch (err) {
  12696. console.warn(err);
  12697. result.error = {
  12698. message: err.message || null,
  12699. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  12700. enumerable: err,
  12701. source: 'GMRequest'
  12702. };
  12703. result.message = `${i18next.t('error.GMRequest', { ns: 'translator' })}${helpText}`;
  12704. throw result;
  12705. }
  12706. try {
  12707. result.text = processer(result.text);
  12708. } catch (err) {
  12709. console.warn(err);
  12710. result.error = {
  12711. message: err.message || null,
  12712. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  12713. enumerable: err,
  12714. source: 'processer'
  12715. };
  12716. result.message = `${i18next.t('error.processer', { ns: 'translator' })}${helpText}`;
  12717. throw result;
  12718. }
  12719. try {
  12720. result.checkPassed = checkResponse(result.text);
  12721. if (result.checkPassed.status) result.done = true;
  12722. else result.message = result.checkPassed.message;
  12723. return result;
  12724. } catch (err) {
  12725. console.warn(err);
  12726. result.error = {
  12727. message: err.message || null,
  12728. stack: err.stack ? err.stack.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;') : null,
  12729. enumerable: err,
  12730. source: 'checkResponse'
  12731. };
  12732. result.message = `${i18next.t('error.checkResponse', { ns: 'translator' })}${helpText}`;
  12733. throw result;
  12734. }
  12735. };
  12736.  
  12737. return await OJB_promiseRetryWrapper(toDo, {
  12738. maxRetries: 3,
  12739. errorHandler: (err, maxRetries, attemptsLeft) => {
  12740. const detailedError = {
  12741. maxRetries: maxRetries,
  12742. attemptsLeft: attemptsLeft,
  12743. ...err
  12744. };
  12745. return detailedError;
  12746. }
  12747. });
  12748. }
  12749.  
  12750. /**
  12751. * 查询服务余额
  12752. * @param {Object} quotaConfig - 配额配置对象
  12753. * @returns {Promise} 返回包含余额信息的 Promise
  12754. */
  12755. async function queryServerBalance(quotaConfig) {
  12756. // 确保传入了有效的配置对象
  12757. if (!quotaConfig || !quotaConfig.url) {
  12758. return Promise.reject(new Error('Quota configuration is missing.'));
  12759. }
  12760.  
  12761. // 准备请求选项
  12762. const requestOptions = {
  12763. method: quotaConfig.method || 'GET',
  12764. url: quotaConfig.url,
  12765. headers: {
  12766. ...Object.assign({}, ...quotaConfig.header)
  12767. },
  12768. data: JSON.stringify({ ...Object.assign({}, ...quotaConfig.data) })
  12769. };
  12770.  
  12771. // 发送请求并返回 Promise
  12772. return OJB_GMRequest(requestOptions).then(response => {
  12773. try {
  12774. const responseData = JSON.parse(response.responseText);
  12775. // 从响应数据中提取余额
  12776. const surplusPath = quotaConfig.surplus;
  12777. const surplusValue = OJB_evaluatePathOrExpression(responseData, surplusPath);
  12778. return surplusValue;
  12779. } catch (error) {
  12780. return Promise.reject(new Error('Failed to parse balance response.'));
  12781. }
  12782. }).catch(error => {
  12783. console.warn('Error querying balance:', error);
  12784. return Promise.reject(error);
  12785. });
  12786. }
  12787.  
  12788. /**
  12789. * 确认 jQuery 已加载
  12790. * @param {number} retryDelay 重试延迟(毫秒)
  12791. * @returns {Promise<void>}
  12792. */
  12793. async function ensureJQueryIsLoaded(retryDelay = 50) {
  12794. while (typeof jQuery === 'undefined') {
  12795. console.warn(`JQuery is not loaded. Retry after ${retryDelay} ms.`);
  12796. await OJB_delay(retryDelay);
  12797. retryDelay = Math.min(retryDelay * 2, 2000);
  12798. }
  12799. }
  12800.  
  12801. /**
  12802. * 加载必须的函数
  12803. * @returns {Promise} 加载提示信息
  12804. */
  12805. async function loadRequiredFunctions() {
  12806. await initVar();// 初始化全局变量
  12807. return Promise.allSettled([
  12808. initDB(), // 连接数据库
  12809. initI18next(), // i18next初始化
  12810. initButtonFunc(), // 加载按钮相关函数
  12811. checkScriptVersion(), // 更新检查
  12812. // ...(OJBetter.typeOfPage.is_acmsguru ? [acmsguruReblock()] : []) // 为acmsguru题面重新划分div
  12813. ]);
  12814. }
  12815.  
  12816. /**
  12817. * DOM加载后即可执行
  12818. */
  12819. function initOnDOMReady() {
  12820. showAnnounce(); // 显示公告
  12821. showWarnMessage(); // 显示警告消息
  12822. initSettingsPanel(); // 加载设置按钮面板
  12823. localizeWebsite(); // 网站本地化替换
  12824. addDependencyStyles(); // 添加一些依赖库的样式
  12825. addI18nStyles(); // 添加包含i18n内容的样式
  12826. // if (OJBetter.basic.expandFoldingblocks) ExpandFoldingblocks(); // 折叠块展开
  12827. // if (OJBetter.basic.renderPerfOpt) RenderPerfOpt(); // 折叠块渲染优化
  12828. if (OJBetter.typeOfPage.is_problem) {
  12829. const problemPageLinkbar = new ProblemPageLinkbar(); // 创建题目页相关链接栏
  12830. if (OJBetter.basic.showCF2vjudge) CF2vjudge(problemPageLinkbar); // 跳转到Vjudge按钮
  12831. if (OJBetter.basic.showJumpToLuogu) CF2luogu(problemPageLinkbar); // 跳转到洛谷按钮
  12832. if (OJBetter.clist.enabled.problem) showRatingByClist_problem(problemPageLinkbar); // problem页显示Rating
  12833. }
  12834. if (OJBetter.typeOfPage.is_contest) {
  12835. if (OJBetter.clist.enabled.contest) showRatingByClist_contest(); // contest页显示Rating
  12836. }
  12837. // if (OJBetter.typeOfPage.is_problemset) {
  12838. // if (OJBetter.clist.enabled.problemset) showRatingByClist_problemset(); // problemset页显示Rating
  12839. // }
  12840. if (OJBetter.typeOfPage.is_problem && OJBetter.monaco.enableOnProblemPage) {
  12841. addProblemPageCodeEditor(); // 添加题目页代码编辑器
  12842. }
  12843. }
  12844.  
  12845. /**
  12846. * 需要在页面资源完全加载后执行的函数
  12847. */
  12848. function onResourcesReady(loadingMessage) {
  12849. if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('loadFunc', { ns: 'alert' })}`);
  12850. initializeInParallel(loadingMessage);
  12851. initializeSequentially(loadingMessage);
  12852. }
  12853.  
  12854. /**
  12855. * 可以异步并行的函数
  12856. */
  12857. function initializeInParallel(loadingMessage) {
  12858. if (OJBetter.basic.darkMode == "dark") darkModeStyleAdjustment(); // 黑暗模式额外的处理事件
  12859. // if (OJBetter.basic.commentPaging) CommentPagination(); // 评论区分页
  12860. if (OJBetter.translation.comment.transMode == "2") multiChoiceTranslation(); // 选段翻译支持
  12861. if (OJBetter.monaco.beautifyPreBlocks) beautifyPreBlocksWithMonaco(); // 美化Pre代码块
  12862. }
  12863.  
  12864. /**
  12865. * 必须按序执行的函数
  12866. */
  12867. async function initializeSequentially(loadingMessage) {
  12868. await addConversionButton(); // 添加MD/复制/翻译按钮
  12869. if ((OJBetter.typeOfPage.is_problem || OJBetter.typeOfPage.is_completeProblemset) && OJBetter.translation.memory.enabled) {
  12870. await initTransResultsRecover(); // 翻译结果恢复功能初始化
  12871. }
  12872. // if (OJBetter.translation.auto.enabled) {
  12873. // await initTransWhenViewable(); // 自动翻译
  12874. // }
  12875. // if (OJBetter.basic.standingsRecolor && OJBetter.typeOfPage.is_cfStandings) {
  12876. // await recolorStandings(); // cf赛制榜单重新着色
  12877. // }
  12878. if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('loadSuccess', { ns: 'alert' })}`, 'success', 3000);
  12879. }
  12880.  
  12881. /**
  12882. * 主方法
  12883. */
  12884. async function main() {
  12885. await ensureJQueryIsLoaded(); // 等待jQuery加载
  12886. const loadingMessage = new LoadingMessage();
  12887. await loadRequiredFunctions(); // 加载必须的函数
  12888. initOnDOMReady(); // DOM加载后即可执行的函数
  12889. if (OJBetter.preference.showLoading) loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('onload', { ns: 'alert' })}`);
  12890.  
  12891. // 检查页面资源是否已经完全加载
  12892. if (OJBetter.state.notWaiteLoaded) {
  12893. onResourcesReady(loadingMessage);
  12894. } else {
  12895. if (document.readyState === 'complete') {
  12896. onResourcesReady(loadingMessage);
  12897. } else {
  12898. window.addEventListener('load', () => onResourcesReady(loadingMessage));
  12899. }
  12900. }
  12901. };
  12902.  
  12903. // ------------------------------
  12904. // 脚本加载入口
  12905. if (document.readyState === 'loading') {
  12906. document.addEventListener("DOMContentLoaded", main);
  12907. } else {
  12908. main(); // 如果DOMContentLoaded已经触发,立即执行
  12909. }
  12910. // ------------------------------
  12911.  
  12912. // ------------------------------
  12913. // 配置自动迁移代码(将在10个小版本后移除-1.19)
  12914. // ------------------------------
  12915.  
  12916. if (GM_getValue("openai_key") || GM_getValue("api2d_key")) {
  12917. const newConfig = { "choice": -1, "configurations": [] };
  12918. if (GM_getValue("openai_key")) {
  12919. let config1 = {
  12920. "note": "我的配置1",
  12921. "model": GM_getValue("openai_model"),
  12922. "key": GM_getValue("openai_key"),
  12923. "proxy": GM_getValue("openai_proxy"),
  12924. "_header": "",
  12925. "_data": ""
  12926. }
  12927. if (GM_getValue("translation") === "openai") newConfig.choice = 0;
  12928. newConfig.configurations.push(config1);
  12929. }
  12930. if (GM_getValue("api2d_key")) {
  12931. let config2 = {
  12932. "note": "api2d",
  12933. "model": GM_getValue("api2d_model"),
  12934. "key": GM_getValue("api2d_key"),
  12935. "proxy": GM_getValue("api2d_request_entry") + '/v1/chat/completions',
  12936. "_header": GM_getValue("x_api2d_no_cache") ? "" : " x-api2d-no-cache : 1",
  12937. "_data": ""
  12938. }
  12939. if (GM_getValue("translation") === "api2d") {
  12940. if (GM_getValue("openai_key")) newConfig.choice = 1;
  12941. else newConfig.choice = 0;
  12942. }
  12943. newConfig.configurations.push(config2);
  12944. }
  12945. GM_setValue("chatgpt-config", newConfig);
  12946. const keysToDelete = ["openai_key", "openai_model", "openai_proxy", "api2d_key", "api2d_model", "api2d_request_entry", "x_api2d_no_cache", "showOpneAiAdvanced"];
  12947. keysToDelete.forEach(key => {
  12948. if (GM_getValue(key) != undefined) GM_deleteValue(key);
  12949. });
  12950. if (GM_getValue("translation") === "api2d") GM_setValue("translation", "openai");
  12951. location.reload();
  12952. }
  12953.  
  12954.  
  12955. // ------------------------------
  12956. // 配置自动迁移代码(将在10个小版本后移除-1.23)
  12957. // ------------------------------
  12958.  
  12959. {
  12960. let bottomZh_CN = GM_getValue("bottomZh_CN");
  12961. if (bottomZh_CN !== undefined) {
  12962. if (bottomZh_CN == true) {
  12963. GM_setValue("localizationLanguage", "zh");
  12964. } else {
  12965. GM_setValue("localizationLanguage", "initial");
  12966. }
  12967. GM_deleteValue("bottomZh_CN");
  12968. location.reload();
  12969. }
  12970. }
  12971. {
  12972. let config = GM_getValue("chatgpt-config");
  12973. if (config && config !== undefined) {
  12974. let index = parseInt(config.choice, 10);
  12975. if (index == -1) config.choice = "";
  12976. else config.choice = config.configurations[index].note;
  12977. config.configurations.forEach(function (item) {
  12978. item.name = item.note;
  12979. delete item.note;
  12980. });
  12981. GM_deleteValue("chatgpt-config");
  12982. GM_setValue("chatgpt_config", config);
  12983. location.reload();
  12984. }
  12985. }