// ==UserScript==
// @name Atcoder Better!
// @namespace https://greasyfork.org/users/747162
// @version 1.14.2
// @description Atcoder界面汉化、题目翻译、markdown视图、一键复制题目、跳转到洛谷
// @author 北极小狐
// @match *://atcoder.jp/*
// @run-at document-start
// @connect www2.deepl.com
// @connect api-free.deepl.com
// @connect api.deepl.com
// @connect api.deeplx.org
// @connect www.iflyrec.com
// @connect m.youdao.com
// @connect api.interpreter.caiyunai.com
// @connect translate.google.com
// @connect openai.api2d.net
// @connect api.openai.com
// @connect www.luogu.com.cn
// @connect vjudge.net
// @connect clist.by
// @connect greasyfork.org
// @connect staticfile.net
// @connect aowuucdn.oss-cn-beijing.aliyuncs.com
// @connect aowuucdn.oss-accelerate.aliyuncs.com
// @connect 127.0.0.1
// @connect *
// @grant GM_xmlhttpRequest
// @grant GM_info
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_addStyle
// @grant GM_setClipboard
// @grant GM_getResourceText
// @icon https://aowuucdn.oss-accelerate.aliyuncs.com/atcoder.png
// @require https://cdn.staticfile.net/turndown/7.1.2/turndown.min.js
// @require https://cdn.staticfile.net/markdown-it/13.0.1/markdown-it.min.js
// @require https://cdn.bootcdn.net/ajax/libs/crypto-js/4.1.1/crypto-js.min.js
// @require https://cdn.staticfile.net/chroma-js/2.4.2/chroma.min.js
// @require https://cdn.staticfile.net/xterm/3.9.2/xterm.min.js
// @require https://cdn.staticfile.net/dexie/3.2.4/dexie.min.js
// @require https://cdn.staticfile.net/i18next/23.5.1/i18next.min.js
// @require https://cdn.staticfile.net/i18next-http-backend/2.2.2/i18nextHttpBackend.min.js
// @require https://cdn.staticfile.net/jquery-i18next/1.2.1/jquery-i18next.min.js
// @require https://cdn.staticfile.net/highlight.js/11.3.1/highlight.min.js
// @require https://update.greasyfork.icu/scripts/484742/1311040/i18nextChainedBackendjs.js
// @require https://update.greasyfork.icu/scripts/484743/1311041/i18next-localstorage-backendjs.js
// @resource acwing_cpp_code_completer https://aowuucdn.oss-accelerate.aliyuncs.com/acwing_cpp_code_completer-0.0.11.json
// @resource wandboxlist https://wandbox.org/api/list.json
// @resource xtermcss https://cdn.staticfile.net/xterm/3.9.2/xterm.min.css
// @license GPL3
// @compatible Chrome
// @compatible Firefox
// @compatible Edge
// @incompatible safari
// @supportURL https://github.com/beijixiaohu/OJBetter/issues
// @name:zh-TW Atcoder Better!
// @name:en Atcoder Better!
// @name:de Atcoder Better!
// @name:fr Atcoder Better!
// @name:ko Atcoder Better!
// @name:pt Atcoder Better!
// @name:ja Atcoder Better!
// @name:es Atcoder Better!
// @name:it Atcoder Better!
// @name:hi Atcoder Better!
// @description 一个适用于 AtCoder 的 Tampermonkey 脚本,增强功能与界面。
// @description:zh-TW 一個適用於 AtCoder 的 Tampermonkey 腳本,增強功能與界面。
// @description:en A Tampermonkey script for AtCoder that enhances functionality and interface.
// @description:de Ein Tampermonkey-Skript für AtCoder, das Funktionalität und Benutzeroberfläche verbessert.
// @description:fr Un script Tampermonkey pour AtCoder qui améliore les fonctionnalités et l'interface.
// @description:ko AtCoder를 위한 Tampermonkey 스크립트로 기능과 인터페이스를 개선합니다.
// @description:pt Um script Tampermonkey para AtCoder que aprimora a funcionalidade e a interface.
// @description:ja AtCoder用のTampermonkeyスクリプトで機能とインターフェースを強化します。
// @description:es Un script Tampermonkey para AtCoder que mejora la funcionalidad y la interfaz.
// @description:it Uno script Tampermonkey per AtCoder che migliora la funzionalità e l'interfaccia.
// @description:hi AtCoder के लिए एक Tampermonkey स्क्रिप्ट जो कार्यक्षमता और इंटरफ़ेस को बेहतर बनाता है।
// @downloadURL none
// ==/UserScript==
/**
* @namespace OJBetter
* @desc 主命名空间
*/
const OJBetter = {};
/**
* @namespace state
* @desc 描述脚本的当前状态。
* @memberof OJBetter
*/
OJBetter.state = {
/** @type {string} 脚本名*/
name: GM_info.script.name,
/** @type {string} 格式化后的脚本名*/
formatName: undefined,
/** @type {string} 版本号*/
version: GM_info.script.version,
/** @type {boolean?} 是否跳过页面加载等待 */
notWaiteLoaded: undefined,
/** @type {string} 最后公告版本,用于标识版本更新完成提示 */
lastAnnounceVer: undefined,
/** @type {string} 最后读取的有效公告版本 */
lastReadAnnounceVer: undefined,
/** @type {number} 当前已打开的模态对话框数量*/
openDialogCount: 0
};
/**
* @namespace common
* @desc 通用设置和属性。
* @memberof OJBetter
*/
OJBetter.common = {
/** @type {string} 网站的主机地址 */
hostAddress: location.origin,
/** @type {string?} Atcoder的CSRF令牌 */
at_csrf_token: undefined,
/** @type {Array?} 任务队列 */
taskQueue: undefined,
/** @type {object} OJBetter数据库连接实例*/
database: undefined
};
/**
* @namespace basic
* @desc 基本的用户界面设置。
* @memberof OJBetter
*/
OJBetter.basic = {
/** @type {string} 黑暗模式设置 */
darkMode: undefined,
/** @type {boolean?} 是否展开折叠块 */
expandFoldingblocks: undefined,
/** @type {boolean?} 是否开启折叠块渲染性能优化 */
renderPerfOpt: undefined,
/** @type {boolean?} 评论区分页 */
commentPaging: undefined,
/** @type {boolean?} 显示跳转到Luogu按钮 */
showJumpToLuogu: undefined,
/** @type {boolean?} 显示跳转到Virtual Judge按钮 */
showCF2vjudge: undefined,
/** @type {boolean?} 比赛排行榜重新着色 */
standingsRecolor: undefined
};
/**
* @namespace typeOfPage
* @desc 页面类型判断。
* @memberof OJBetter
*/
OJBetter.typeOfPage = {
/** @type {boolean?} 是否是轻量站 */
is_mSite: false,
/** @type {boolean?} 是否是acmsguru页面 */
is_acmsguru: false,
/** @type {boolean?} 是否是旧版LaTeX页面 */
is_oldLatex: false,
/** @type {boolean?} 是否是题目集页面 */
is_contest: undefined,
/** @type {boolean?} 是否是题目页面 */
is_problem: undefined,
/** @type {boolean?} 是否是完整的问题集页面 */
is_completeProblemset: false,
/** @type {boolean?} 是否是问题集中的问题页面 */
is_problemset_problem: false,
/** @type {boolean?} 是否是问题集页面 */
is_problemset: false,
/** @type {boolean?} 是否是Codeforces排名页面 */
is_cfStandings: false,
/** @type {boolean?} 是否是提交页面 */
is_submitPage: false,
/** @type {boolean?} 是否是主页 */
is_homepage: undefined,
/** @type {boolean?} 是否选择的是英语页面 */
isEnglishLanguage: undefined,
};
/**
* @namespace localization
* @desc 本地化设置。
* @memberof OJBetter
*/
OJBetter.localization = {
/** @type {string?} 网站语言 */
websiteLang: undefined,
/** @type {string?} 脚本语言 */
scriptLang: undefined
};
/**
* @namespace translation
* @desc 翻译设置。
* @memberof OJBetter
*/
OJBetter.translation = {
/** @type {string?} 翻译服务选择 */
choice: undefined,
/** @type {string?} 目标语言 */
targetLang: undefined,
comment: {
/** @type {string?} 评论翻译服务选择 */
choice: undefined,
/** @type {string?} 评论翻译模式 */
transMode: undefined
},
auto: {
/** @type {boolean?} 自动翻译开关 */
enabled: undefined,
/** @type {number?} 短文本长度限制 */
shortTextLength: undefined,
mixTrans: {
/** @type {boolean?} 混合翻译开关 */
enabled: undefined,
/** @type {Array?} 混合翻译服务列表 */
servers: undefined
}
},
memory: {
/** @type {boolean?} 翻译记忆开关 */
enabled: undefined,
/** @type {Object?} 翻译记忆树 */
ttTree: undefined
},
/** @type {string?} 重翻译时的行为 */
retransAction: undefined,
/** @type {number?} 等待时间 */
waitTime: undefined,
/** @type {boolean?} 替换符 */
replaceSymbol: undefined,
/** @type {boolean?} 过滤文本中的*号 */
filterTextWithoutEmphasis: undefined
};
/**
* @namespace clist
* @desc Clist相关设置。
* @memberof OJBetter
*/
OJBetter.clist = {
enabled: {
/** @type {boolean?} 比赛页面开关 */
contest: undefined,
/** @type {boolean?} 问题页面开关 */
problem: undefined,
/** @type {boolean?} 问题集页面开关 */
problemset: undefined
},
/** @type {boolean?} Rating数据防剧透 */
ratingHidden: undefined,
/** @type {string?} Clist key */
authorization: undefined
};
/**
* @namespace monaco
* @desc Monaco编辑器配置。
* @memberof OJBetter
*/
OJBetter.monaco = {
/** @type {boolean?} 在问题页面上启用Monaco编辑器 */
enableOnProblemPage: undefined,
/** @type {boolean?} 美化pre代码块 */
beautifyPreBlocks: undefined,
/** @type {boolean} Monaco编辑器加载完成标志 */
loaderOnload: false,
lsp: {
/** @type {Array?} LSP套接字数组 */
socket: [],
/** @type {boolean?} 是否启用LSP */
enabled: undefined,
/** @type {string?} 工作路径 */
workUri: undefined,
/** @type {string?} 套接字URL */
socketUrl: undefined
},
complet: {
/** @type {boolean?} 是否启用C++代码补全模板 */
cppCodeTemplate: undefined,
/** @type {Object?} 自定义配置 */
customConfig: undefined
},
/** @type {Object?} Monaco编辑器实例 */
editor: null,
/** @type {string?} 在线编译器选择 */
onlineCompilerChoice: undefined,
/** @type {string?} 记忆编译器语言选择 */
compilerSelection: undefined,
/** @type {string?} 当前选择的语言 */
nowLangSelect: undefined,
setting: {
/** @type {Array?} 语言设置数组 */
language: [],
/** @type {string?} 位置 */
position: undefined,
/** @type {boolean} 位置初始化标志 */
position_initialized: false,
/** @type {number?} 字体大小 */
fontsize: undefined,
/** @type {boolean?} 鼠标滚动锁定 */
alwaysConsumeMouseWheel: undefined,
/** @type {boolean?} 提交代码二次确认 */
isCodeSubmitDoubleConfirm: undefined,
/** @type {string?} 提交按钮位置 */
submitButtonPosition: undefined
}
};
/**
* @namespace deepl
* @desc DeepL翻译服务配置。
* @memberof OJBetter
*/
OJBetter.deepl = {
/** @type {Object?} DeepL配置对象 */
configs: undefined,
config: {
/** @type {string?} 类型 */
type: undefined,
/** @type {string?} 名称 */
name: undefined,
/** @type {string?} API类型 */
apiGenre: undefined,
/** @type {string?} API密钥 */
key: undefined,
/** @type {string?} 代理 */
proxy: undefined,
/** @type {Object?} 额外请求头 */
header: undefined,
/** @type {Object?} 额外请求数据 */
data: undefined,
quota: {
/** @type {string?} 余额URL */
url: undefined,
/** @type {string?} 余额请求方法 */
method: undefined,
/** @type {Object?} 余额请求头 */
header: undefined,
/** @type {Object?} 余额请求数据 */
data: undefined,
/** @type {number?} 剩余配额 */
surplus: undefined
}
},
/** @type {boolean?} 启用重点保护 */
enableEmphasisProtection: undefined,
/** @type {boolean?} 启用链接保护 */
enableLinkProtection: undefined
};
/**
* @namespace chatgpt
* @desc ChatGPT服务配置。
* @memberof OJBetter
*/
OJBetter.chatgpt = {
/** @type {Object?} ChatGPT配置对象 */
configs: undefined,
config: {
/** @type {string?} 名称 */
name: undefined,
/** @type {string?} 模型 */
model: undefined,
/** @type {string?} API密钥 */
key: undefined,
/** @type {string?} 代理 */
proxy: undefined,
/** @type {Object?} 额外请求头 */
header: undefined,
/** @type {Object?} 额外请求数据 */
data: undefined,
quota: {
/** @type {string?} 余额URL */
url: undefined,
/** @type {string?} 余额请求方法 */
method: undefined,
/** @type {Object?} 余额请求头 */
header: undefined,
/** @type {Object?} 余额请求数据 */
data: undefined,
/** @type {number?} 剩余配额 */
surplus: undefined
}
},
/** @type {boolean?} 是否为流式传输 */
isStream: undefined
};
/**
* @namespace preference
* @desc 偏好设置
* @memberof OJBetter
*/
OJBetter.preference = {
/** @type {boolean?} 是否显示加载动画 */
showLoading: undefined,
/** @type {boolean?} 是否显示悬停目标区域 */
hoverTargetAreaDisplay: undefined,
/** @type {string?} 按钮图标大小 */
iconButtonSize: undefined,
};
/**
* @namespace about
* @desc 关于页信息
* @memberof OJBetter
*/
OJBetter.about = {
/** @type {string?} 更新通道 */
updateChannel: undefined,
/** @type {string?} 更新源 */
updateSource: undefined
};
/**
* @namespace supportList
* @desc 支持列表
* @memberof OJBetter
*/
OJBetter.supportList = {
/** @type {object} 翻译支持列表和对应语言代码*/
translationSupport: {
'deepl': { 'zh': 'ZH', 'de': 'DE', 'fr': 'FR', 'ko': 'KO', 'pt': 'PT', 'ja': 'JA', 'es': 'ES', 'it': 'IT' },
'iflyrec': { 'zh': '1' },
'youdao': { 'zh': 'AUTO' },
'google': { 'zh': 'zh-CN', 'zh-Hant': 'zh-TW', 'de': 'de', 'fr': 'fr', 'ko': 'ko', 'pt': 'pt', 'ja': 'ja', 'es': 'es', 'it': 'it', 'hi': 'hi' },
'caiyun': { 'zh': 'auto2zh', 'ja': 'auto2ja', 'ko': 'auto2ko', 'es': 'auto2es', 'fr': 'auto2fr' },
'openai': { 'zh': 'Chinese', 'zh-Hant': 'Traditional Chinese', 'de': 'German', 'fr': 'French', 'ko': 'Korean', 'pt': 'Portuguese', 'ja': 'Japanese', 'es': 'Spanish', 'it': 'Italian', 'hi': 'Hindi' }
},
/** @type {object} 更新源支持列表*/
updateSourceSupportList: {
'greasyfork': {
'release': true,
'dev': false
},
'github': {
'release': true,
'dev': true
},
'aliyunoss': {
'release': true,
'dev': true
}
}
}
// ------------------------------
// 一些工具函数
// ------------------------------
/**
* 安全地创建jQuery元素
* @description 通过jQuery创建HTML字符串时,如果字符串以空格开头,在某些Jquery版本中会发生错误,过滤空格以安全的创建元素。
* @param {string} htmlString - HTML字符串。
* @returns jQuery对象
*/
const OJB_safeCreateJQElement = function (htmlString) {
return $(htmlString.replace(/^\s+/, ""));
}
/**
* 将数字或者字符串解析为数字。
* @memberof OJBetter.common
* @param {string} val 要解析的字符串
* @param {boolean} [strict=false] 是否进行严格类型检查
* @returns {number} 解析结果
* @throws {Error} 如果解析失败,则抛出错误
*/
const OJB_parseNumber = (val, strict = false) => {
const num = Number(val);
if (isNaN(num) || (strict && val.toString() !== num.toString())) {
throw new Error('Invalid number');
}
return num;
};
/**
* 将字符串解析为布尔值
* @param {string} val - 要解析的字符串
* @param {boolean} strict - 是否进行严格类型检查
* @returns {boolean} - 解析结果
* @throws {Error} - 如果解析失败,则抛出错误
*/
const OJB_parseBoolean = (val, strict) => {
if (strict) {
if (val === true || val === false) return val;
throw new Error('Invalid boolean');
}
return val === 'true' ? true : val === 'false' ? false : val;
};
/**
* 将字符串解析为对象
* @param {string} val - 要解析的字符串
* @returns {Object} - 解析结果
* @throws {Error} - 如果解析失败,则抛出错误
*/
const OJB_parseObject = val => {
try {
return JSON.parse(val);
} catch {
throw new Error('Invalid JSON');
}
};
/**
* 将字符串解析为键值对数组
* @param {string} val - 要解析的字符串
* @returns {Object[]} - 解析结果
* @throws {Error} - 如果解析失败,则抛出错误
*/
const OJB_parseLinePairArray = val => {
if (typeof val !== 'string' || val.trim() === '') return [];
return val.split("\n").filter(line => line.trim() !== '').map(line => {
const indexOfFirstColon = line.indexOf(":");
if (indexOfFirstColon === -1) throw new Error('Invalid LinePairArray format: ":" is missing');
const key = line.substring(0, indexOfFirstColon).trim();
const value = line.substring(indexOfFirstColon + 1).trim();
return { [key]: value };
});
};
/**
* 移除文本中的HTML标签
* @param {string} text - 包含HTML标签的文本
* @returns {string} - 移除HTML标签后的文本
*/
const OJB_removeHTMLTags = function (text) {
return text.replace(/<\/?[a-zA-Z]+("[^"]*"|'[^']*'|[^'">])*>/g, '');
}
/**
* 获取对象中指定路径表达式的值
* @param {Object} obj - 要计算的对象
* @param {string} pathOrExpression - 要计算的路径表达式
* @returns {any} - 计算结果
* @example
* const obj = {
* "a": {
* "b": 1
* },
* "c": 2
* };
* evaluatePathOrExpression(obj, "a.b"); // 1
* evaluatePathOrExpression(obj, "a.b + c"); // 3
* evaluatePathOrExpression(obj, "a.b + a.c"); // 1
*/
function OJB_evaluatePathOrExpression(obj, pathOrExpression) {
const hasOperator = /[\+\-\*\/]/.test(pathOrExpression);
const getPathValue = (obj, path) => {
return path.split('.').reduce((acc, part) => {
return acc !== undefined && acc !== null && acc.hasOwnProperty(part) ? acc[part] : undefined;
}, obj);
};
const evaluateExpression = (obj, expression) => {
const tokens = expression.split(/([\+\-\*\/])/).map(token => token.trim());
const values = tokens.map(token => {
if (/[\+\-\*\/]/.test(token)) {
return token;
} else {
const value = getPathValue(obj, token);
return value !== undefined ? value : 0;
}
});
const evaluatedExpression = values.join(' ');
try {
return Function(`'use strict'; return (${evaluatedExpression});`)();
} catch (e) {
console.error('Expression evaluation error:', e);
return undefined;
}
};
return hasOperator ? evaluateExpression(obj, pathOrExpression) : getPathValue(obj, pathOrExpression);
}
/**
* 获取 GM 存储的值并根据类型进行处理
* @param {string} key - 要检索的值的键。
* @param {any} defaultValue - 如果值未找到,则返回的默认值。
* @param {Object} [options={}] - 配置选项对象。
* @param {string} [options.type='string'] - 期望的值的类型。可选值:'string', 'number', 'boolean', 'object', 'array', 'linePairArray'。
* @param {boolean} [options.strict=false] - 用于数字和布尔类型,表示是否进行严格类型检查。
* @param {string} [options.pathOrExpression=''] - 用于对象或数组类型,表示路径表达式或获取元素的索引。
* @returns {any} - 检索到的值。
*/
const OJB_getGMValue = (key, defaultValue, { type = 'string', strict = false, pathOrExpression = '' } = {}) => {
let value = GM_getValue(key);
if (value === undefined || value === null || value === "") {
GM_setValue?.(key, defaultValue);
return defaultValue;
}
const parsers = {
string: val => val,
number: (val) => OJB_parseNumber(val, strict),
boolean: (val) => OJB_parseBoolean(val, strict),
object: OJB_parseObject,
array: OJB_parseObject,
linePairArray: OJB_parseLinePairArray
};
if (!(type in parsers)) {
console.error(`Unsupported type: ${type}`);
return defaultValue;
}
try {
value = parsers[type](value);
} catch (e) {
console.error('Error:', e.message);
return defaultValue;
}
// The pathOrExpression processing is not applicable to linePairArray type
if ((type === 'object' || type === 'array') && pathOrExpression) {
const evaluated = OJB_evaluatePathOrExpression(value, pathOrExpression);
if (evaluated === undefined) {
console.error('Path or expression evaluation returned undefined');
return defaultValue;
}
value = evaluated;
}
return value;
};
/**
* 版本号比较方法
* @param {string} version1 版本号1
* @param {string} version2 版本号2
* @returns {number} -1: version1 < version2, 0: version1 = version2, 1: version1 > version2
*/
const OJB_compareVersions = function (version1 = "0", version2 = "0") {
const v1Array = version1.split(".").map(Number);
const v2Array = version2.split(".").map(Number);
const length = Math.max(v1Array.length, v2Array.length);
for (let i = 0; i < length; i++) {
const diff = (v1Array[i] || 0) - (v2Array[i] || 0);
if (diff) return Math.sign(diff);
}
return 0;
}
/**
* 获取上一个主版本号
* @param {string} currentVersion 当前版本号
* @returns {string} 上一个主版本号
*/
const OJB_getPreviousVersion = function (currentVersion) {
const versionArray = currentVersion.split(".").map(Number);
let lastNonZeroIndex = versionArray.length - 1;
while (lastNonZeroIndex >= 0 && versionArray[lastNonZeroIndex] === 0) {
lastNonZeroIndex--;
}
if (lastNonZeroIndex >= 0) {
versionArray[lastNonZeroIndex]--;
for (let i = lastNonZeroIndex + 1; i < versionArray.length; i++) {
versionArray[i] = 0;
}
}
return versionArray.join(".");
};
/**
* 在指定根节点下观察指定选择器的元素,当元素存在时,执行回调函数
* @param {Object} options - 配置对象
* @param {string} options.selector - CSS选择器文本
* @param {Function} options.callback - 回调函数,接收变动的节点作为参数
* @param {Boolean} [options.triggerOnExist=true] - 如果为true,元素已存在时立即触发一次回调
* @param {Element} [options.root=document.body] - 在哪个根节点下监听变化
* @param {Boolean} [options.subtree=false] - 是否监听子树变化(即非直接子元素)
*/
function OJB_observeElement({
selector,
callback,
triggerOnExist = true,
root = document.body,
subtree = false
}) {
// 尝试获取选择器指定的元素
const targetNode = root.querySelector(selector);
if (targetNode) {
// 如果元素已存在,直接开始观察
observeAndReport(targetNode, callback);
// 如果triggerOnExist为true,则立即触发一次回调
if (triggerOnExist) {
callback(targetNode);
}
} else {
// 如果元素不存在,监听DOM变化直到该元素被添加
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE && node.matches(selector)) {
observeAndReport(node, callback);
if (triggerOnExist) {
callback(node);
}
observer.disconnect(); // 停止监听
}
});
});
});
observer.observe(root, { childList: true, subtree, attributes: false });
}
function observeAndReport(node, callback) {
const childObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (childList) {
mutation.addedNodes.forEach((addedNode) => {
if (addedNode.nodeType === Node.ELEMENT_NODE) {
callback(addedNode); // 执行回调函数
}
});
}
if (attributes && mutation.type === 'attributes') {
callback(mutation.target); // 执行回调函数
}
});
});
childObserver.observe(node, { childList: true, subtree: true, attributes: false });
}
}
/**
* 初始化全局变量
*/
async function initVar() {
const { hostname, href } = window.location;
OJBetter.state.formatName = (() => OJBetter.state.name
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, ''))();
OJBetter.state.lastAnnounceVer = OJB_getGMValue("lastAnnounceVer", "0");
OJBetter.state.lastReadAnnounceVer = OJB_getGMValue("lastReadAnnounceVer", "0");
OJBetter.typeOfPage.is_contest = /\/contests\/[^\/]+\/tasks\/?$/.test(href);
OJBetter.typeOfPage.is_problem = href.includes('/tasks/');
OJBetter.typeOfPage.is_homepage = (href === 'https://atcoder.jp/' || href === 'https://atcoder.jp/?lang=ja');
OJBetter.localization.websiteLang = OJB_getGMValue("localizationLanguage", "zh");
OJBetter.localization.scriptLang = OJB_getGMValue("scriptL10nLanguage", "zh");
OJBetter.basic.renderPerfOpt = OJB_getGMValue("renderPerfOpt", false);
OJBetter.basic.commentPaging = OJB_getGMValue("commentPaging", true);
OJBetter.basic.showJumpToLuogu = OJB_getGMValue("showJumpToLuogu", true);
OJBetter.basic.showCF2vjudge = OJB_getGMValue("showCF2vjudge", true);
OJBetter.basic.standingsRecolor = OJB_getGMValue("standingsRecolor", true);
OJBetter.state.notWaiteLoaded = OJB_getGMValue("notWaiteLoaded", false);
OJBetter.translation.targetLang = OJB_getGMValue("transTargetLang", "zh");
OJBetter.translation.choice = OJB_getGMValue("translation", "deepl");
OJBetter.translation.comment.transMode = OJB_getGMValue("commentTranslationMode", "0");
OJBetter.translation.comment.choice = OJB_getGMValue("commentTranslationChoice", "0");
OJBetter.translation.memory.enabled = OJB_getGMValue("memoryTranslateHistory", true);
OJBetter.translation.auto.enabled = OJB_getGMValue("autoTranslation", false);
OJBetter.translation.auto.shortTextLength = OJB_getGMValue("shortTextLength", "2000");
OJBetter.translation.retransAction = OJB_getGMValue("retransAction", "0");
OJBetter.translation.waitTime = OJB_getGMValue("transWaitTime", "200");
OJBetter.translation.auto.mixTrans.enabled = OJB_getGMValue("allowMixTrans", true);
OJBetter.translation.auto.mixTrans.servers = OJB_getGMValue("mixedTranslation", ['deepl', 'iflyrec', 'youdao', 'caiyun']);
OJBetter.common.taskQueue = new TaskQueue();
OJBetter.translation.replaceSymbol = OJB_getGMValue("replaceSymbol", "2");
OJBetter.translation.filterTextWithoutEmphasis = OJB_getGMValue("filterTextWithoutEmphasis", false);
OJBetter.clist.enabled.contest = OJB_getGMValue("showClistRating_contest", false);
OJBetter.clist.enabled.problem = OJB_getGMValue("showClistRating_problem", false);
OJBetter.clist.enabled.problemset = OJB_getGMValue("showClistRating_problemset", false);
OJBetter.clist.ratingHidden = OJB_getGMValue("RatingHidden", false);
OJBetter.clist.authorization = OJB_getGMValue("clist_Authorization", "");
//deepl
OJBetter.deepl.config.type = OJB_getGMValue("deepl_type", "free");
OJBetter.deepl.configs = OJB_getGMValue("deepl_config", {
"choice": "",
"configurations": []
});
if (OJBetter.deepl.configs.choice !== "" && OJBetter.deepl.configs.configurations.length !== 0) {
const choice = OJBetter.deepl.configs.choice;
const configuration = OJBetter.deepl.configs.configurations.find(obj => obj.name === choice);;
if (configuration == undefined) {
let existingConfig = GM_getValue('deepl_config');
existingConfig.choice = "";
GM_setValue('deepl_config', existingConfig);
location.reload();
}
OJBetter.deepl.config.name = configuration.name;
OJBetter.deepl.config.apiGenre = configuration.apiGenre;
OJBetter.deepl.config.key = configuration.key;
OJBetter.deepl.config.proxy = configuration.proxy;
OJBetter.deepl.config.header = OJB_parseLinePairArray(configuration._header);
OJBetter.deepl.config.data = OJB_parseLinePairArray(configuration._data);
OJBetter.deepl.config.quota.url = configuration.quota_url;
OJBetter.deepl.config.quota.method = configuration.quota_method;
OJBetter.deepl.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
OJBetter.deepl.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
OJBetter.deepl.config.quota.surplus = configuration.quota_surplus;
}
OJBetter.deepl.enableEmphasisProtection = OJB_getGMValue("enableEmphasisProtection", true);
OJBetter.deepl.enableLinkProtection = OJB_getGMValue("enableLinkProtection", true);
//openai
OJBetter.chatgpt.isStream = OJB_getGMValue("openai_isStream", true);
OJBetter.chatgpt.configs = OJB_getGMValue("chatgpt_config", {
"choice": "",
"configurations": []
});
if (OJBetter.chatgpt.configs.choice !== "" && OJBetter.chatgpt.configs.configurations.length !== 0) {
const choice = OJBetter.chatgpt.configs.choice;
const configuration = OJBetter.chatgpt.configs.configurations.find(obj => obj.name === choice);;
if (configuration == undefined) {
let existingConfig = GM_getValue('chatgpt_config');
existingConfig.choice = "";
GM_setValue('chatgpt_config', existingConfig);
location.reload();
}
OJBetter.chatgpt.config.name = configuration.name;
OJBetter.chatgpt.config.model = configuration.model;
OJBetter.chatgpt.config.key = configuration.key;
OJBetter.chatgpt.config.proxy = configuration.proxy;
OJBetter.chatgpt.config.header = OJB_parseLinePairArray(configuration._header);
OJBetter.chatgpt.config.data = OJB_parseLinePairArray(configuration._data);
OJBetter.chatgpt.config.quota.url = configuration.quota_url;
OJBetter.chatgpt.config.quota.method = configuration.quota_method;
OJBetter.chatgpt.config.quota.header = OJB_parseLinePairArray(configuration.quota_header);
OJBetter.chatgpt.config.quota.data = OJB_parseLinePairArray(configuration.quota_data);
OJBetter.chatgpt.config.quota.surplus = configuration.quota_surplus;
}
// 编辑器
// if (!OJBetter.typeOfPage.is_mSite) OJBetter.common.cf_csrf_token = Codeforces.getCsrfToken();
// else OJBetter.common.cf_csrf_token = "";
OJBetter.common.at_csrf_token = csrfToken;
// OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "61");
OJBetter.monaco.compilerSelection = OJB_getGMValue("compilerSelection", "5001");
OJBetter.monaco.setting.fontsize = OJB_getGMValue("editorFontSize", "15");
OJBetter.monaco.enableOnProblemPage = OJB_getGMValue("problemPageCodeEditor", true);
OJBetter.monaco.beautifyPreBlocks = OJB_getGMValue("beautifyPreBlocks", true);
OJBetter.monaco.complet.cppCodeTemplate = OJB_getGMValue("cppCodeTemplateComplete", true);
OJBetter.monaco.onlineCompilerChoice = OJB_getGMValue("onlineCompilerChoice", "official");
OJBetter.monaco.setting.isCodeSubmitDoubleConfirm = OJB_getGMValue("isCodeSubmitConfirm", true);
OJBetter.monaco.setting.alwaysConsumeMouseWheel = OJB_getGMValue("alwaysConsumeMouseWheel", true);
OJBetter.monaco.setting.submitButtonPosition = OJB_getGMValue("submitButtonPosition", "bottom");
//自定义补全
OJBetter.monaco.complet.customConfig = OJB_getGMValue("Complet_config", {
"choice": -1,
"configurations": []
});
/**
* 加载monaco编辑器资源
*/
OJBetter.monaco.lsp.enabled = OJB_getGMValue("useLSP", false);
OJBetter.monaco.setting.position = OJB_getGMValue("monacoEditor_position", "initial");
OJBetter.monaco.lsp.workUri = OJB_getGMValue("OJBetter_Bridge_WorkUri", "C:/OJBetter_Bridge");
OJBetter.monaco.lsp.socketUrl = OJB_getGMValue("OJBetter_Bridge_SocketUrl", "ws://127.0.0.1:2323/");
if (OJBetter.monaco.enableOnProblemPage || OJBetter.monaco.beautifyPreBlocks) {
let monacoLoader = document.createElement("script");
monacoLoader.src = "https://cdn.staticfile.net/monaco-editor/0.44.0/min/vs/loader.min.js";
document.head.prepend(monacoLoader);
monacoLoader.onload = () => {
require.config({
paths: { vs: "https://cdn.staticfile.net/monaco-editor/0.44.0/min/vs" },
"vs/nls": { availableLanguages: { "*": "zh-cn" } },
});
require(["vs/editor/editor.main"], () => {
OJBetter.monaco.loaderOnload = true;
});
}
}
OJBetter.preference.showLoading = OJB_getGMValue("showLoading", true);
OJBetter.preference.hoverTargetAreaDisplay = OJB_getGMValue("hoverTargetAreaDisplay", false);
OJBetter.basic.expandFoldingblocks = OJB_getGMValue("expandFoldingblocks", true);
OJBetter.preference.iconButtonSize = OJB_getGMValue("iconButtonSize", "16");
OJBetter.about.updateChannel = OJB_getGMValue("updateChannel", "release");
OJBetter.about.updateSource = OJB_getGMValue("updateSource", "greasyfork");
}
/**
* 显示警告消息
*/
function showWarnMessage() {
if (OJBetter.typeOfPage.is_oldLatex) {
const loadingMessage = new LoadingMessage();
loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_oldLatex', { ns: 'alert' })}`, 'warning');
}
if (OJBetter.typeOfPage.is_acmsguru) {
const loadingMessage = new LoadingMessage();
loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_acmsguru', { ns: 'alert' })}`, 'warning');
}
if (OJBetter.translation.comment.transMode == "1") {
const loadingMessage = new LoadingMessage();
loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_segment', { ns: 'alert' })}`, 'warning');
}
if (OJBetter.translation.comment.transMode == "2") {
const loadingMessage = new LoadingMessage();
loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.trans_select', { ns: 'alert' })}`, 'warning');
}
if (OJBetter.typeOfPage.is_submitPage && OJBetter.monaco.enableOnProblemPage) {
const loadingMessage = new LoadingMessage();
loadingMessage.updateStatus(`${OJBetter.state.name} —— ${i18next.t('warning.is_submitPage', { ns: 'alert' })}`, 'warning');
}
}
// 常量
const helpCircleHTML = '
} 数据库的JSON字符串
*/
async function exportDatabase() {
try {
// 创建一个存储数据的对象
const exportData = {};
// 获取数据库中所有表的名称
const tableNames = OJBetter.common.database.tables.map(table => table.name);
// 遍历每一个表,获取数据
for (const tableName of tableNames) {
const tableData = await OJBetter.common.database.table(tableName).toArray();
exportData[tableName] = tableData;
}
// 将数据对象转换为JSON字符串
const jsonData = JSON.stringify(exportData, null, 4);
return jsonData;
} catch (error) {
console.error("Error exporting database:", error);
}
}
/**
* 导入数据库
* @param {string} jsonData 数据库的JSON字符串
*/
async function importDatabase(jsonData) {
const isConfirmed = await OJB_createDialog(
i18next.t('isImportDatabase.title', { ns: 'dialog' }),
i18next.t('isImportDatabase.content', { ns: 'dialog' }),
[
i18next.t('isImportDatabase.buttons.0', { ns: 'dialog' }),
i18next.t('isImportDatabase.buttons.1', { ns: 'dialog' })
]
);
if (!isConfirmed) {
try {
// 将JSON字符串解析为对象
const importData = JSON.parse(jsonData);
// 开启一个事务,并清空现有数据
await OJBetter.common.database.transaction('rw', OJBetter.common.database.tables, async () => {
// 清空所有表的数据
for (const tableName of OJBetter.common.database.tables.map(table => table.name)) {
await OJBetter.common.database.table(tableName).clear();
}
// 插入新数据
for (const [tableName, rows] of Object.entries(importData)) {
await OJBetter.common.database.table(tableName).bulkAdd(rows);
}
});
alert("Data imported successfully");
} catch (error) {
console.error("Error importing database:", error);
}
}
}
/**
* 将数据下载为文件
* @param {string} data 数据
* @param {string} filename 文件名,默认为'export.json'
* @param {string} fileType 文件MIME类型,默认为'application/json'
* @returns {void}
*/
function downloadDataAsFile(data, filename = 'export.json', fileType = 'application/json') {
// 创建一个blob对象,指定文件类型
const blob = new Blob([data], { type: fileType });
const url = URL.createObjectURL(blob);
// 创建一个隐藏的a标签,模拟点击进行下载
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
// 清理
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/**
* 从文件中读取数据
* @param {Function} callback 回调函数
* @returns {void}
*/
function readFileInput(callback) {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
fileInput.style.display = 'none'; // 隐藏input元素
fileInput.onchange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const fileContent = e.target.result;
if (callback && typeof callback === 'function') {
callback(fileContent); // 调用回调函数,传入文件内容
}
};
reader.readAsText(file);
}
};
document.body.appendChild(fileInput);
fileInput.click();
document.body.removeChild(fileInput);
}
/**
* 删除所有设置
*/
async function deleteAllConfigSettings() {
const isConfirmed = await OJB_createDialog(
i18next.t('isDeleteAllConfigSettings.title', { ns: 'dialog' }),
i18next.t('isDeleteAllConfigSettings.content', { ns: 'dialog' }),
[
i18next.t('isDeleteAllConfigSettings.buttons.0', { ns: 'dialog' }),
i18next.t('isDeleteAllConfigSettings.buttons.1', { ns: 'dialog' })
]
);
if (!isConfirmed) {
const keys = GM_listValues();
keys.forEach(key => {
GM_deleteValue(key);
});
alert('All settings have been deleted.');
window.location.reload();
}
}
/**
* 导出设置到JSON
* @returns {string} JSON字符串
*/
function exportSettingsToJSON() {
const keys = GM_listValues();
let settings = {};
keys.forEach(key => {
settings[key] = GM_getValue(key);
});
return JSON.stringify(settings, null, 4);
}
/**
* 从JSON导入设置
* @param {string} jsonData JSON字符串
* @returns {void}
*/
async function importSettingsFromJSON(jsonData) {
const isConfirmed = await OJB_createDialog(
i18next.t('isImportSettings.title', { ns: 'dialog' }),
i18next.t('isImportSettings.content', { ns: 'dialog' }),
[
i18next.t('isImportSettings.buttons.0', { ns: 'dialog' }),
i18next.t('isImportSettings.buttons.1', { ns: 'dialog' })
]
);
if (!isConfirmed) {
let settings;
try {
settings = JSON.parse(jsonData);
} catch (e) {
console.error('JSON parsing error:', e);
return;
}
Object.keys(settings).forEach(key => {
GM_setValue(key, settings[key]);
});
alert('Settings imported successfully!');
window.location.reload();
}
}
/**
* 加载元素本地化语言数据
* @param {JQuery} element jQuery元素
* @param {number} [retries=10] 重试次数
* @param {number} [interval=50] 重试间隔
*/
function elementLocalize(element, retries = 10, interval = 50) {
if ($.isFunction(element.localize)) {
element.localize();
} else if (retries > 0) {
setTimeout(elementLocalize, interval, element, retries - 1, interval);
} else {
console.error('Unable to localize', element);
}
}
// 切换系统黑暗监听
const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
const changeEventListeners = [];
function handleColorSchemeChange(event) {
event.matches ? $('html').attr('data-theme', 'dark') : $('html').attr('data-theme', 'light');
if (!event.matches) {
var originalColor = $(this).data("original-color");
$(this).css("background-color", originalColor);
if (OJBetter.monaco.editor) {
monaco.editor.setTheme('vs');
}
} else {
if (OJBetter.monaco.editor) {
monaco.editor.setTheme('vs-dark');
}
}
}
// 黑暗模式
(function setDark() {
// 初始化
function setDarkTheme() {
const htmlElement = document.querySelector('html');
if (htmlElement) {
htmlElement.setAttribute('data-theme', 'dark');
} else {
setTimeout(setDarkTheme, 100);
}
}
OJBetter.basic.darkMode = OJB_getGMValue("darkMode", "follow")
if (OJBetter.basic.darkMode == "dark") {
setDarkTheme();
} else if (OJBetter.basic.darkMode == "follow") {
// 添加事件监听器
changeEventListeners.push(handleColorSchemeChange);
mediaQueryList.addEventListener('change', handleColorSchemeChange);
if (window.matchMedia('(prefers-color-scheme: dark)').matches) setDarkTheme();
}
// 定义全局变量
GM_addStyle(`
/* 黑暗支持 */
html[data-theme=dark]:root {
color-scheme: light dark;
}
/* 颜色 */
:root {
/* 文字颜色 */
--ojb-color-text-primary: #a0adb9; /* 主要文字颜色 */
--ojb-color-text-secondary: #9AA4B1; /* 次要文字颜色 */
--ojb-color-text-tertiary: #9BA5B2; /* 第三级文字颜色 */
--ojb-color-text-success: #43A047; /* 成功状态文字颜色 */
--ojb-color-text-highlight: #cbd6e2; /* 高亮文字颜色 */
--ojb-color-text-disabled: #506778; /* 禁用状态文字颜色 */
--ojb-color-text-icon-success: #2e7d32; /* 成功状态图标颜色 */
--ojb-color-text-link: #4b8eda; /* 链接颜色 */
/* 背景颜色 */
--ojb-color-bg-primary: #22272e; /* 主背景颜色 */
--ojb-color-bg-secondary: #2d333b; /* 次级背景颜色 */
--ojb-color-bg-disabled: #24292e; /* 禁用元素背景颜色 */
/* 边框颜色 */
--ojb-color-border-primary: #48535F; /* 主要边框颜色 */
--ojb-color-border-disabled: #404950; /* 禁用状态边框颜色 */
--ojb-color-border-dashed-hover: #03A9F4; /* 虚线边框悬浮颜色 */
--ojb-color-border-radio-checked: #326154; /* 选中的单选框边框颜色 */
/* 阴影颜色 */
--ojb-shadow-standard: 0px 0px 0.5px 0.5px #3A4048; /* 标准阴影 */
--ojb-shadow-menu-modal: 0px 0px 0px 4px #2d333b; /* 菜单和模态框阴影 */
/* 区域遮罩颜色 */
--ojb-overlay-background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px); /* 区域遮罩背景 */
/* 文字阴影 */
--ojb-text-shadow-icon: 1px 1px 0px #2d333b, 1px -1px 0px #2d333b, -1px -1px 0px #2d333b, -1px 1px 0px #2d333b; /* 图标文字阴影 */
}
/* 边框样式 */
:root {
/* 边框样式 */
--ojb-border-width: 1px; /* 边框宽度 */
--ojb-border-style-solid: solid; /* 实线样式 */
--ojb-border-style-dashed: dashed; /* 虚线样式 */
--ojb-border-radius-small: 4px; /* 小圆角 */
--ojb-border-radius-medium: 8px; /* 中圆角 */
--ojb-border-radius-large: 12px; /* 大圆角 */
/* 组合边框样式 */
--ojb-border-solid-primary: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-primary); /* 主要实线边框 */
--ojb-border-dashed: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-primary); /* 主要虚线边框 */
--ojb-border-dashed-hover: var(--ojb-border-width) var(--ojb-border-style-dashed) var(--ojb-color-border-dashed-hover); /* 悬浮虚线边框 */
--ojb-border-solid-disabled: var(--ojb-border-width) var(--ojb-border-style-solid) var(--ojb-color-border-disabled); /* 禁用状态实线边框 */
}
`);
// OJBetter界面样式
GM_addStyle(`
/* 主要文字颜色 */
html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
html[data-theme=dark] .alert-warning, html[data-theme=dark] .markItUpEditor,
html[data-theme=dark] .translate-problem-statement, html[data-theme=dark] .OJBetter_setting_menu,
html[data-theme=dark] .help_tip .tip_text,
html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
html[data-theme=dark] #items-per-page, html[data-theme=dark] #pagBar,
html[data-theme=dark] .OJBetter_setting_sidebar li a:link,
html[data-theme=dark] .popup .content{
color: var(--ojb-color-text-primary);
}
/* 次要文字颜色 */
html[data-theme=dark] .ojb_btn:hover, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_statusBar,
html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #addCustomTest,
html[data-theme=dark] #customTestBlock, html[data-theme=dark] .OJBetter_setting_list.alert_info{
color: var(--ojb-color-text-secondary);
}
/* 文字颜色3 */
html[data-theme=dark] .ojb_btn{
color: var(--ojb-color-text-tertiary);
}
/* 文字颜色 浅绿 */
html[data-theme=dark] #SubmitButton{
color: var(--ojb-color-text-success);
}
/* 禁止文字颜色 */
html[data-theme=dark] .ojb_btn[disabled]{
color: var(--ojb-color-text-disabled);
}
/* 主要背景层次 */
html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] li#add_button:hover,
html[data-theme=dark] .ojb_btn:hover,
html[data-theme=dark] .OJBetter_setting_menu input, html[data-theme=dark] .OJBetter_setting_menu textarea,
html[data-theme=dark] #OJBetter_SubmitForm input,
html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
html[data-theme=dark] #OJBetter_SubmitForm textarea, html[data-theme=dark] #OJBetter_SubmitForm select,
html[data-theme=dark] .OJBetter_setting_sidebar li a.active, html[data-theme=dark] .OJBetter_setting_sidebar li,
html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-track, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-track,
html[data-theme=dark] .OJBetter_modal, html[data-theme=dark] .OJBetter_modal button:hover,
html[data-theme=dark] .popup .content,
html[data-theme=dark] .config_bar_list, html[data-theme=dark] #LSPLog,
html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before,
html[data-theme=dark] .OJBetter_setting_menu a, html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button:hover,
html[data-theme=dark] .OJBetter_setting_menu select{
background-color: var(--ojb-color-bg-primary);
background-image: none;
}
/* 次要背景层次 */
html[data-theme=dark] .ojb_btn,
html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
html[data-theme=dark] .alert-warning, html[data-theme=dark] .SumoSelect>.optWrapper>.options li.opt:hover,
html[data-theme=dark] .translate-problem-statement-panel,
html[data-theme=dark] .translate-problem-statement,
html[data-theme=dark] .OJBetter_setting_list,
html[data-theme=dark] .OJBetter_setting_menu hr,
html[data-theme=dark] .OJBetter_setting_sidebar li a,
html[data-theme=dark] .OJBetter_setting_menu::-webkit-scrollbar-thumb, html[data-theme=dark] .OJBetter_setting_content::-webkit-scrollbar-thumb,
html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] .test-for-popup pre,
html[data-theme=dark] .popup .content pre, html[data-theme=dark] .popup .content pre code,
html[data-theme=dark] ul.config_bar_ul::-webkit-scrollbar-thumb, html[data-theme=dark] #OJBetter_statusBar,
html[data-theme=dark] #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] .sampleDiv,
html[data-theme=dark] #addCustomTest, html[data-theme=dark] #LSPLog li:nth-child(odd),
html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before,
html[data-theme=dark] .config::before, html[data-theme=dark] .config li.tempConfig_add_button:hover,
html[data-theme=dark] .OJBetter_setting_menu details, html[data-theme=dark] #config_bar_menu,
html[data-theme=dark] .OJBetter_setting_menu .OJBetter_setting_list button,
html[data-theme=dark] .OJBetter_setting_menu .badge, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton{
background-color: var(--ojb-color-bg-secondary);
}
/* 禁止背景层次 */
html[data-theme=dark] .ojb_btn[disabled]{
background-color: var(--ojb-color-bg-disabled);
}
/* 实线边框颜色-圆角 */
html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-error,
html[data-theme=dark] .alert-warning, html[data-theme=dark] .translate-problem-statement{
border: var(--ojb-border-solid-primary);
border-radius: 2px;
}
/* 实线边框颜色-无圆角 */
html[data-theme=dark] .ojb_btn,
html[data-theme=dark] .OJBetter_setting_list, html[data-theme=dark] .config_bar_list,
html[data-theme=dark] label.config_bar_ul_li_text,
html[data-theme=dark] .OJBetter_setting_sidebar li, html[data-theme=dark] .OJBetter_setting_menu select,
html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .OJBetter_modal button, html[data-theme=dark] #OJBetter_SubmitForm select,
html[data-theme=dark] #OJBetter_editor, html[data-theme=dark] #OJBetter_statusBar,
html[data-theme=dark] #OJBetter_SubmitForm #RunTestButton, html[data-theme=dark] #programTypeId, html[data-theme=dark] #customTestBlock,
html[data-theme=dark] #OJBetter_SubmitForm #addCustomTest, html[data-theme=dark] #OJBetter_SubmitForm #SubmitButton,
html[data-theme=dark] .OJBetter_setting_menu input,
html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"], html[data-theme=dark] .OJBetter_setting_menu input[type="checkbox"]:checked,
html[data-theme=dark] .OJBetter_setting_menu textarea,
html[data-theme=dark] #OJBetter_SubmitForm input, html[data-theme=dark] #OJBetter_SubmitForm textarea,
html[data-theme=dark] #CompilerSetting select, html[data-theme=dark] #CompilerSetting textarea, html[data-theme=dark] #CompilerBox,
html[data-theme=dark] .OJBetter_setting_menu .OJBetter_checkboxs,
html[data-theme=dark] .help_tip .tip_text, html[data-theme=dark] .config::before,
html[data-theme=dark] #statePanel, html[data-theme=dark] .test-case, html[data-theme=dark] .OJBetter_setting_menu .badge{
border: var(--ojb-border-solid-primary);
}
html[data-theme=dark] #customTestBlock #customTests{
border-top: var(--ojb-border-solid-primary);
}
html[data-theme=dark] .OJBetter_setting_sidebar {
border-right: var(--ojb-border-solid-primary);
}
/* 实线边框-禁止 */
html[data-theme=dark] .ojb_btn[disabled]{
border: var(--ojb-border-solid-disabled);
}
/* 虚线边框 */
html[data-theme=dark] li#add_button,
html[data-theme=dark] .OJBetter_setting_menu_label_text{
border: var(--ojb-border-dashed);
}
/* 虚线边框-悬浮 */
html[data-theme=dark] li#add_button:hover{
border: var(--ojb-border-dashed-hover);
background-color: var(--ojb-color-bg-secondary);
color: var(--ojb-color-border-dashed-hover);
}
/* 无边框 */
html[data-theme=dark] .translate-problem-statement-panel .ojb_btn{
border: none;
}
/* 区域遮罩 */
html[data-theme=dark] .overlay::before {
background: var(--ojb-overlay-background);
color: var(--ojb-color-text-secondary);
text-shadow: 0px 0px 2px #000000;
}
/* 阴影 */
html[data-theme=dark] .translate-problem-statement-panel, html[data-theme=dark] .translate-problem-statement{
box-shadow: var(--ojb-shadow-standard);
}
/* 图标按钮状态样式 */
html[data-theme=dark] .ojb_btn_popover.success i:before, html[data-theme=dark] .ojb_btn_popover.success i {
color: var(--ojb-color-text-icon-success);
}
html[data-theme=dark] .ojb_btn_popover i:before {
text-shadow: var(--ojb-text-shadow-icon);
}
/* 其他样式 */
html[data-theme=dark] .OJBetter_setting_menu, html[data-theme=dark] .OJBetter_modal{
box-shadow: var(--ojb-shadow-menu-modal);
border: 1px solid var(--ojb-color-bg-secondary);
}
html[data-theme=dark] input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
color: var(--ojb-color-text-primary);
border: 1px solid var(--ojb-color-border-radio-checked);
}
html[data-theme=dark] .alert{
text-shadow: none;
}
`);
// 网站界面样式
GM_addStyle(`
/* 文字颜色1 */
html[data-theme=dark] body, html[data-theme=dark] .float-container>#main-container,
html[data-theme=dark] .panel-default>.panel-heading, html[data-theme=dark] #header a,
html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span, html[data-theme=dark] .dropdown-menu,
html[data-theme=dark] .select2-container--bootstrap .select2-selection--single .select2-selection__rendered,
html[data-theme=dark] .ace-tm .ace_gutter, html[data-theme=dark] .translate-problem-statement-panel,
html[data-theme=dark] .select2-container--bootstrap .select2-results__option--highlighted[aria-selected],
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,
html[data-theme=dark] .m-box_inner, html[data-theme=dark] .m-list-job_item, html[data-theme=dark] .a-btn_arrow,
html[data-theme=dark] #header, html[data-theme=dark] #header .header-sub_page li a,
html[data-theme=dark] .select2-container--default .select2-selection--single .select2-selection__rendered, html[data-theme=dark] .select2-results{
color: var(--ojb-color-text-primary) !important;
}
/* 文字颜色2 */
html[data-theme=dark] pre, html[data-theme=dark] .html2mdButton, html[data-theme=dark] .btn-default, html[data-theme=dark] .btn-pre,
html[data-theme=dark] small.contest-duration, html[data-theme=dark] .select2-container--bootstrap .select2-results__option,
html[data-theme=dark] #ace_settingsmenu, #kbshortcutmenu, html[data-theme=dark] code{
color: var(--ojb-color-text-secondary) !important;
}
/* 文字颜色3 */
html[data-theme=dark] input, html[data-theme=dark] #header .header-page li a:hover{
color: var(--ojb-color-text-secondary);
}
/* 文字颜色4 */
html[data-theme=dark] .katex{
color: var(--ojb-color-text-highlight) !important;
}
/* 链接颜色 */
html[data-theme=dark] a:link {
color: var(--ojb-color-text-link);
}
html[data-theme=dark] a:visited {
color: var(--ojb-color-text-secondary);
}
/* 按钮 */
html[data-theme=dark] input:hover, html[data-theme=dark] .btn-default:hover{
background-color: var(--ojb-color-bg-primary) !important;
}
/* 背景层次1 */
html[data-theme=dark] body, html[data-theme=dark] #main-div.float-container, html[data-theme=dark] pre,
html[data-theme=dark] .html2mdButton:hover, html[data-theme=dark] .pagination>.active>a, html[data-theme=dark] .ace-tm,
html[data-theme=dark] .dropdown-menu>li>a:hover, html[data-theme=dark] .dropdown-menu>li>a:focus,
html[data-theme=dark] .dropdown-menu .divider, html[data-theme=dark] .select2-container--bootstrap .select2-selection,
html[data-theme=dark] .ace-tm .ace_gutter-active-line, html[data-theme=dark] .select2-dropdown,
html[data-theme=dark] input, html[data-theme=dark] button, html[data-theme=dark] select, html[data-theme=dark] textarea,
html[data-theme=dark] code, html[data-theme=dark] #keyvisual .keyvisual-inner:before, html[data-theme=dark] .m-box_inner,
html[data-theme=dark] .m-list-job_item, html[data-theme=dark] .select2-container--default .select2-selection--single,
html[data-theme=dark] ol.linenums, html[data-theme=dark] li.L0, html[data-theme=dark] li.L1, html[data-theme=dark] li.L2,
html[data-theme=dark] li.L3, html[data-theme=dark] li.L4, html[data-theme=dark] li.L5, html[data-theme=dark] li.L6,
html[data-theme=dark] li.L7, html[data-theme=dark] li.L8, html[data-theme=dark] li.L9{
background-color: var(--ojb-color-bg-primary) !important;
}
/* 背景层次2 */
html[data-theme=dark] .float-container>#main-container, html[data-theme=dark] #contest-nav-tabs,
html[data-theme=dark] .btn-default, html[data-theme=dark] .html2mdButton,
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,
html[data-theme=dark] .nav>li>a:hover, html[data-theme=dark] .nav>li>a:focus, html[data-theme=dark] .panel,
html[data-theme=dark] .table-striped>tbody>tr:nth-of-type(odd), html[data-theme=dark] .insert-participant-box,
html[data-theme=dark] .btn-pre, html[data-theme=dark] .alert-success, html[data-theme=dark] .alert-info, html[data-theme=dark] .alert-danger,
html[data-theme=dark] .alert-warning, html[data-theme=dark] .panel-default>.panel-heading,
html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span, html[data-theme=dark] .dropdown-menu,
html[data-theme=dark] .ace-tm .ace_gutter, html[data-theme=dark] .select2-container--bootstrap .select2-results__option[aria-selected=true],
html[data-theme=dark] #ace_settingsmenu, #kbshortcutmenu, html[data-theme=dark] #header .header-inner,
html[data-theme=dark] ul#config_bar_ul::-webkit-scrollbar-thumb, html[data-theme=dark] .panel-info>.panel-heading,
html[data-theme=dark] .post-footer, html[data-theme=dark] .a-btn_arrow:before,
html[data-theme=dark] .table-hover>tbody>tr:hover,
html[data-theme=dark] li.L1, html[data-theme=dark] li.L3, html[data-theme=dark] li.L5, html[data-theme=dark] li.L7,
html[data-theme=dark] li.L9{
background-color: var(--ojb-color-bg-secondary) !important;
}
/* 实线边框颜色-圆角 */
html[data-theme=dark] input{
border: var(--ojb-border-solid-primary) !important;
border-radius: 2px;
}
/* 实线边框颜色-无圆角 */
html[data-theme=dark] .btn-default, html[data-theme=dark] .html2mdButton, html[data-theme=dark] .nav-tabs>li>a:hover,
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, html[data-theme=dark] .btn-pre, html[data-theme=dark] .btn-pre:hover,
html[data-theme=dark] pre, html[data-theme=dark] .pagination>li>a, html[data-theme=dark] .pagination>li>span,
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,
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,
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,
html[data-theme=dark] .select2-container--bootstrap .select2-selection, html[data-theme=dark] .select2-container--default .select2-selection--single{
border: var(--ojb-border-solid-primary) !important;
}
html[data-theme=dark] hr, html[data-theme=dark] .panel-footer,
html[data-theme=dark] .table>thead>tr>th, html[data-theme=dark] .table>tbody>tr>th, html[data-theme=dark] .table>tfoot>tr>th,
html[data-theme=dark] .table>thead>tr>td, html[data-theme=dark] .table>tbody>tr>td, html[data-theme=dark] .table>tfoot>tr>td{
border-top: var(--ojb-border-solid-primary) !important;
}
html[data-theme=dark] .nav-tabs, html[data-theme=dark] .panel-info>.panel-heading, html[data-theme=dark] .panel-default>.panel-heading,
html[data-theme=dark] .a-btn_arrow{
border-bottom: var(--ojb-border-solid-primary) !important;
}
html[data-theme=dark] .table>thead>tr>th{
border-bottom: 2px solid var(--ojb-color-border-primary) !important;
}
/* 双实线边框 */
html[data-theme=dark] #header .header-inner{
border-bottom: 5px double var(--ojb-color-border-primary) !important;
}
/* 阴影 */
html[data-theme=dark] .float-container>#main-container{
box-shadow: 0px 0px 10px 5px #fff0;
}
/* 图片-亮度 */
html[data-theme=dark] img{
opacity: .75;
}
/* 反转 */
html[data-theme=dark] .ace_content, html[data-theme=dark] #header .header-logo img, html[data-theme=dark] pre code{
filter: invert(1) hue-rotate(.5turn);
}
/* 区域遮罩 */
html[data-theme=dark] .overlay {
background: repeating-linear-gradient(135deg, #49525f6e, #49525f6e 30px, #49525f29 0px, #49525f29 55px);
color: #9099a3;
text-shadow: 0px 0px 2px #000000;
}
/* 其他样式 */
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{
border-bottom-color: transparent !important;
}
html[data-theme=dark] .collapsible-topic.collapsed .content .collapsible-topic-options:before{
background-image: linear-gradient(#22272e00, #22272e);
}
html[data-theme=dark] .alert{
text-shadow: none;
}
html[data-theme=dark] .m-box-news_post:before{
background: linear-gradient(0deg, #22272e 50%, rgba(255,255,255,0) 100%);
}
html[data-theme=dark] #header .header-sub_page li a:before, html[data-theme=dark] #header .header-page li a:before{
background-color: #9e9e9e !important;
}
html[data-theme=dark] .standings-score{
color: #2196f3;
}
html[data-theme=dark] pre code{
background-color: transparent !important;
}
html[data-theme=dark] #fixed-server-timer {
color: #000;
}
`);
})()
/**
* 黑暗模式额外的处理事件
*/
function darkModeStyleAdjustment() {
}
/**
* 美化Pre代码块
*/
async function beautifyPreBlocksWithMonaco() {
// 用于替换 标签为 Monaco 编辑器的函数
function replacePreWithMonaco(preElement) {
const pre = $(preElement);
if (pre.hasClass('source-code-for-copy')) return; // 跳过复制块
const code = OJB_getCodeFromPre(pre.get(0));
if (!code) return;
const language = OJB_codeLangDetect(code);
// 创建一个用于 Monaco 编辑器的容器
const container = $('');
const lineCount = code.split('\n').length; // 代码的行数
// 计算容器的高度
const calculateContainerHeight = (lineCount) => {
const lineHeight = 20; // 每行代码的高度
const minHeight = 100; // 最小高度
const maxHeight = 1000; // 最大高度
const dynamicHeight = lineCount * lineHeight;
return Math.min(Math.max(dynamicHeight, minHeight), maxHeight) + 'px';
};
// 应用样式
container.css({
height: calculateContainerHeight(lineCount),
width: '100%'
});
pre.replaceWith(container);
// 初始化 Monaco 编辑器
monaco.editor.create(container[0], {
value: code,
language: language,
readOnly: true,
tabSize: 4,
theme: OJBetter.basic.darkMode == "dark" ? "vs-dark" : "vs",
scrollbar: {
verticalScrollbarSize: 10,
horizontalScrollbarSize: 10,
alwaysConsumeMouseWheel: false
},
automaticLayout: true,
scrollBeyondLastLine: false
});
}
// 全局替换页面上所有的 元素
$('pre').each(function () {
replacePreWithMonaco(this);
});
// 监听页面上的提交状态页面窗口的 元素
if (OJBetter.typeOfPage.is_statePage) {
OJB_observeElement({
selector: '#facebox',
callback: (node) => {
// 如果 facebox 中存在 pre 元素,则替换它们
const preElements = $(node).find('pre');
preElements.each(function () {
replacePreWithMonaco(this);
});
}
});
}
}
// 样式
GM_addStyle(`
/*动画*/
@keyframes shake {
0% { transform: translateX(-5px); }
100% { transform: translateX(5px); }
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes rippleout {
0% {
box-shadow: 0 0 0 0 rgba(96, 98, 102, 0.2);
}
100% {
box-shadow: 0 0 0 6px rgba(0, 0, 0, 0);
}
}
@keyframes bounce-in {
20%,40%,60%,80%,from,to {
animation-timing-function: cubic-bezier(.215,.61,.355,1);
}
0% {
opacity: 0;
transform: scale3d(.995,.995,.995);
}
20% {
opacity: 1;
transform: scale3d(1.005,1.005,1.005);
}
40% {
transform: scale3d(.998,.998,.998);
}
60% {
transform: scale3d(1.002,1.002,1.002);
}
80% {
transform: scale3d(.995,.995,.995);
}
to {
opacity: 1;
transform: scale3d(1,1,1);
}
}
/*iconfont图标*/
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal !important;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@font-face {
font-family: 'iconfont'; /* Project id 4284341 */
src: url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2') format('woff2'),
url('//aowuucdn.oss-accelerate.aliyuncs.com/iconfont/iconfont.woff2.ttf') format('truetype');
}
html {
scroll-behavior: smooth;
}
:root {
--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";
}
span.mdViewContent {
white-space: pre-wrap;
}
/* dialog */
dialog {
margin: 0px;
}
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.4);
}
/*题目页链接栏样式*/
#problemToolbar {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
overflow: auto;
height: 100%;
margin: 0.5em;
}
/*html2md面板*/
.html2md-panel {
display: flex;
justify-content: flex-end;
align-items: center;
}
.html2md-panel a {
text-decoration: none;
}
.html2md-panel > button {
margin: 5px;
}
.html2md-panel.is_simple {
position: absolute;
right: 2%;
}
/*通用按钮*/
.ojb_btn {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
background-color: #ffffff;
color: #606266;
width: auto;
font-size: 13px;
border-radius: 0.3rem;
padding: 2px 5px;
margin: 0px 5px;
border: 1px solid #dcdfe6;
}
.ojb_btn[disabled] {
cursor: not-allowed !important;
color: rgb(168, 171, 178) !important;
border: 1px solid #e4e7ed;
background-color: #ffffff;
}
.ojb_btn:hover {
color: #409eff;
border-color: #409eff;
background-color: #f1f8ff;
z-index: 150;
}
.ojb_btn.primary {
color: #ffffff;
border: 1px solid #409eff;
background-color: #409eff;
}
.ojb_btn.primary:hover {
color: #ffffff;
border: 1px solid #79bbff;
background-color: #79bbff;
}
.ojb_btn.success {
color: #4caf50;
border: 1px solid #C8E6C9;
background-color: #f0f9eb;
}
.ojb_btn.warning {
color: #e6a23c;
border: 1px solid #f3d19e;
background-color: #fdf6ec;
}
.ojb_btn.error {
color: #f56c6c;
border: 1px solid #fab6b6;
background-color: #fef0f0;
}
.ojb_btn.enabled {
color: #42A5F5;
border: 1px solid #90CAF9;
background-color: #fafbff;
}
.ojb_btn.active {
animation: rippleout 0.5s ease-in-out;
}
a.ojb_btn {
text-decoration: none;
}
a.ojb_btn:link {
color: #606266;
}
a.ojb_btn span {
margin-left: 2px;
}
/*按钮图标和popover*/
.ojb_btn_popover {
display: flex;
justify-content: center;
position: relative;
outline: none;
appearance: none;
}
.ojb_btn_popover:hover span {
opacity: 1;
visibility: visible;
}
.ojb_btn_popover i:before {
position: absolute;
text-shadow: 1px 1px 0px #ffffff, 1px -1px 0px #ffffff, -1px -1px 0px #ffffff, -1px 1px 0px #ffffff;
}
.ojb_btn_popover span {
cursor: initial;
position: absolute;
left: 50%;
opacity: 0;
visibility: hidden;
padding: 4px 8px;
background-color: rgba(33, 33, 33, 0.8);
color: rgba(255, 255, 255, 0.9019607843);
font-size: 12px;
border-radius: 6px;
line-height: 1.6;
text-align: left;
white-space: nowrap;
transition: all 0.15s ease-in-out;
z-index: 999;
}
.ojb_btn_popover span:hover {
opacity: 0;
visibility: hidden;
}
.ojb_btn_popover.top:hover span {
transform: translate(-50%, 0);
}
.ojb_btn_popover.top span {
bottom: 100%;
transform: translate(-50%, -20%);
margin-bottom: 4px;
}
.ojb_btn_popover.top span:hover {
transform: translate(-50%, -20%);
}
.ojb_btn_popover.bottom:hover span {
transform: translate(-50%, 105%);
}
.ojb_btn_popover.bottom span {
bottom: -2%;
transform: translate(-50%, 100%);
margin-top: 4px;
}
.ojb_btn_popover.bottom span:hover {
transform: translate(-50%, 50%);
}
.ojb_btn_popover.loading i {
color: rgba(33, 33, 33, 0.1);
}
.ojb_btn_popover.loading i:before {
content: "\\e640";
color: rgb(168, 171, 178);
animation: rotate 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
}
.ojb_btn_popover.running i {
color: rgba(33, 33, 33, 0.1);
}
.ojb_btn_popover.running i:before {
content: "\\e600";
color: rgb(168, 171, 178);
animation: rotate 1s linear infinite;
}
.ojb_btn_popover.warning i {
color: rgba(230, 162, 61, 0.8);
}
.ojb_btn_popover.warning i:before {
content: "\\e68b";
font-size: 15px;
left: 10px;
bottom: 0%;
color: #ff9800;
}
.ojb_btn_popover.error i {
color: rgba(245, 108, 108, 0.8);
}
.ojb_btn_popover.error i:before {
content: "\\e651";
font-size: 15px;
left: 10px;
bottom: 0%;
color: #F44336;
}
.ojb_btn_popover.success i {
color: rgba(76, 175, 80, 0.9);
}
.ojb_btn_popover.success i:before {
content: "\\e61e";
font-size: 15px;
left: 10px;
bottom: 0%;
color: #4caf50;
}
.ojb_btn_popover.enabled i {
color: rgba(33, 150, 243, 0.6);
}
.ojb_btn_popover.enabled i:before {
content: "\\e6f4";
font-size: 15px;
left: 10px;
bottom: 0%;
color: #2196F3;
}
.ojb_btn_popover.redo i {
color: rgba(33, 33, 33, 0.1);
}
.ojb_btn_popover.redo i:before {
content: "\\e831";
color: #616161;
}
.ojb_btn_popover.reverse i {
transform: rotate(180deg);
}
/*translateDiv样式*/
.translateDiv .topText {
display: flex;
margin-left: 5px;
color: #9e9e9e;
font-size: 13px;
align-items: center;
}
.translateDiv .borderlessButton{
display: flex;
align-items: center;
margin: 2.5px 7px;
fill: #9E9E9E;
}
.translateDiv .borderlessButton:hover{
cursor: pointer;
fill: #059669;
}
.translateDiv.bounce-in {
animation: bounce-in 1s forwards;
}
html:not([data-theme='dark']) .translateDiv {
box-shadow: 0px 0px 0.5px 0.5px #defdf378;
}
.translate-problem-statement {
justify-items: start;
letter-spacing: 1.8px;
color: #059669;
background-color: #f9f9fa;
border: 1px solid #c5ebdf;
border-radius: 0rem 0rem 0.3rem 0.3rem;
padding: 5px;
margin: -5px 0px 6px 0px;
width: 100%;
box-sizing: border-box;
font-size: 13px;
}
.translate-problem-statement h3 {
font-size: 1.3em;
font-weight: 700;
}
.translate-problem-statement-panel{
display: flex;
justify-content: space-between;
background-color: #f9f9fa;
border: 1px solid #c5ebdf;
border-radius: 0.3rem;
margin: 4px 0px;
}
.translate-problem-statement-panel .ojb_btn {
background: none;
border: none;
color: #9e9e9e;
}
.translate-problem-statement-panel.error, .translate-problem-statement.error {
color: red;
border-color: red;
}
.translate-problem-statement a, .translate-problem-statement a:link {
color: #10b981;
font-weight: 600;
background: 0 0;
text-decoration: none;
}
.translate-problem-statement ol, .translate-problem-statement ul {
display: grid;
margin-inline-start: 0.8em;
margin-block-start: 0em;
margin: 0.5em 0 0 3em;
padding-inline-start: 0px;
}
.translate-problem-statement li {
display: list-item;
height: auto;
word-wrap: break-word;
}
.translate-problem-statement ol li {
list-style-type: auto;
}
.translate-problem-statement ul li {
list-style-type: disc;
}
.translate-problem-statement img {
max-width: 100.0%;
max-height: 100.0%;
}
#task-statement .translate-problem-statement .MathJax {
color: #059669!important;
}
.translate-problem-statement span.math {
margin: 0px 2.5px !important;
}
.translate-problem-statement a:hover {
background-color: #800;
color: #fff;
text-decoration: none;
}
.translate-problem-statement table {
border: 1px #ccc solid !important;
margin: 1.5em 0 !important;
color: #059669 !important;
}
.translate-problem-statement table thead th {
border: 1px #ccc solid !important;
color: #059669 !important;
}
.translate-problem-statement table td {
border-right: 1px solid #ccc;
border-top: 1px solid #ccc;
padding: 0.7143em 0.5em;
}
.translate-problem-statement table th {
padding: 0.7143em 0.5em;
}
.translate-problem-statement p:not(:first-child) {
margin: 1.5em 0 0;
}
.translate-problem-statement p {
line-height: 20px !important;
word-wrap: break-word;
font-size: 13px !important
}
.problem-statement p:last-child {
margin-bottom: 0px !important;
}
/*设置按钮*/
header .enter-or-register-box, header .languages {
position: absolute;
right: 170px;
}
.ojb_btn.OJBetter_setting {
float: right;
height: 30px;
background: #60a5fa;
color: white;
margin: 10px;
border: 1px solid #60a5fa;
}
.ojb_btn.OJBetter_setting.open {
background-color: #e6e6e6;
color: #727378;
cursor: not-allowed;
}
/*设置面板*/
.OJBetter_setting_menu {
box-shadow: 0px 0px 0px 4px #ffffff;
position: fixed;
top: 50%;
left: 50%;
width: 600px;
min-height: 600px;
transform: translate(-50%, -50%);
border-radius: 6px;
background-color: #f0f4f9;
border-collapse: collapse;
border: 1px solid #ffffff;
color: #697e91;
font-family: var(--vp-font-family-base);
padding: 10px 20px 20px 10px;
box-sizing: content-box;
}
.OJBetter_setting_menu h3 {
margin-top: 10px;
font-size: 1.4em;
font-weight: 700;
}
.OJBetter_setting_menu h4 {
margin: 15px 0px 10px 0px;
}
.OJBetter_setting_menu h4,.OJBetter_setting_menu h5 {
font-weight: 600;
}
.OJBetter_setting_menu hr {
border: none;
height: 1px;
background-color: #ccc;
margin: 10px 0;
}
.OJBetter_setting_menu details {
padding: 10px;
margin-bottom: 5px;
background-color: #ffffff;
border-bottom: 1px solid #c9c6c696;
border-radius: 8px;
}
.OJBetter_setting_menu .badge {
border-radius: 4px;
border: 1px solid #009688;
color: #009688;
background-color: #fff;
padding: 0.5px 4px;
margin-left: 5px;
margin-right: auto;
line-height: initial;
font-weight: initial;
}
.OJBetter_setting_menu .missing {
box-shadow: inset 0px 0px 1px 1px red;
}
/* 页面切换 */
.OJBetter_setting_menu .settings-page {
display: none;
}
.OJBetter_setting_menu .settings-page.active {
display: block;
}
.OJBetter_setting_container {
display: flex;
}
.OJBetter_setting_sidebar {
flex: 0 0 auto;
min-width: 110px;
padding: 6px 10px 6px 6px;
margin: 20px 0px;
border-right: 1px solid #d4d8e9;
}
.OJBetter_setting_content {
flex-grow: 1;
margin: 20px 0px 0px 12px;
padding-right: 10px;
max-height: 580px;
overflow-y: auto;
box-sizing: border-box;
}
.OJBetter_setting_sidebar h3 {
margin-top: 0;
}
.OJBetter_setting_sidebar hr {
margin-top: 10px;
margin-bottom: 10px;
border: none;
border-top: 1px solid #DADCE0;
}
.OJBetter_setting_sidebar ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.OJBetter_setting_sidebar li {
margin: 5px 0px;
background-color: #ffffff;
border: 1px solid #d4d8e9;
border-radius: 4px;
font-size: 16px;
}
.OJBetter_setting_sidebar li a {
text-decoration: none;
display: flex;
width: 100%;
font-size: 16px;
color: gray;
background-color: #ffffff;
border: none;
letter-spacing: 2px;
padding: 7px;
margin: 0px;
border-radius: 4px;
align-items: center;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.OJBetter_setting_sidebar li a.active {
background-color: #eceff1c7;
}
/* 链接样式 */
.OJBetter_setting_menu a {
font-size: 13px;
color: #009688;
background-color: #E0F2F1;
border: 1px solid #009688;
border-radius: 4px;
padding: 0px 5px;
margin: 0px 5px;
text-decoration: none;
}
/* 下拉选择框 */
.OJBetter_setting_menu select {
appearance: none;
padding: 5px 10px;
margin: -5px 0px;
border-radius: 6px;
border-style: solid;
border: 1px solid #ced4da;
color: #009688;
background: #ffffff;
font-size: 15px;
}
.OJBetter_setting_menu select:focus-visible {
outline: none;
}
.OJBetter_setting_menu select option:disabled {
color: #EEEEEE;
}
/* 数值输入框 */
.OJBetter_setting_menu input[type="number"] {
width: 40px;
color: #009688;
font-size: 15px;
appearance: none;
padding: 5px 10px;
margin: -5px 3px;
border-radius: 6px;
border-style: solid;
border: 1px solid #ced4da;
}
.OJBetter_setting_menu input[type="number"]:focus-visible {
outline: none;
}
.OJBetter_setting_menu input[type="number"]::-webkit-inner-spin-button,
.OJBetter_setting_menu input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/*设置面板-滚动条*/
.OJBetter_setting_menu::-webkit-scrollbar, .OJBetter_setting_content::-webkit-scrollbar,
.OJBetter_modal .content::-webkit-scrollbar {
width: 5px;
height: 7px;
background-color: #aaa;
}
.OJBetter_setting_menu::-webkit-scrollbar-thumb, .OJBetter_setting_content::-webkit-scrollbar-thumb,
.OJBetter_modal .content::-webkit-scrollbar-thumb {
background-clip: padding-box;
background-color: #d7d9e4;
}
.OJBetter_setting_menu::-webkit-scrollbar-track, .OJBetter_setting_content::-webkit-scrollbar-track,
.OJBetter_modal .content::-webkit-scrollbar-track {
background-color: #f1f1f1;
}
/*设置面板-关闭按钮*/
.OJBetter_setting_menu .tool-box {
position: absolute;
width: 20px;
height: 20px;
top: 3px;
right: 3px;
}
.OJBetter_setting_menu .btn-close {
width: 20px;
height: 20px;
border-radius: 50%;
border: none;
margin: 0px;
padding: 0px;
background-color: #ff000080;
transition: .15s ease all;
box-sizing: border-box;
text-align: center;
color: transparent;
}
.OJBetter_setting_menu .iconfont {
font-size: 10px;
font-weight: bolder;
}
.OJBetter_setting_menu .btn-close:hover {
color: #ffffff;
background-color: #ff0000cc;
box-shadow: 0 5px 5px 0 #00000026;
}
.OJBetter_setting_menu .btn-close:active {
color: #ffffffde;
background-color: #ff000080;
}
/*设置面板-checkbox*/
.OJBetter_setting_menu input[type=checkbox]:focus {
outline: 0px;
}
.OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"] {
margin: 0px;
appearance: none;
-webkit-appearance: none;
width: 40px;
height: 20px;
border: 1.5px solid #D7CCC8;
padding: 0px !important;
border-radius: 20px;
background: #efebe978;
position: relative;
box-sizing: border-box;
}
.OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::before {
content: "";
width: 17px;
height: 17px;
background: #D7CCC8;
border: 1.5px solid #BCAAA4;
border-radius: 50%;
position: absolute;
top: 0;
left: 0;
transform: translate(2%, 2%);
transition: all 0.3s ease-in-out;
box-sizing: border-box;
}
.OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]::after {
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");
position: absolute;
top: 0;
left: 24px;
}
.OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked {
border: 1.5px solid #C5CAE9;
background: #E8EAF6;
}
.OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::before {
background: #C5CAE9;
border: 1.5px solid #7986CB;
transform: translate(122%, 2%);
transition: all 0.3s ease-in-out;
}
.OJBetter_setting_menu .OJBetter_setting_list input[type="checkbox"]:checked::after {
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");
position: absolute;
top: 1.5px;
left: 4.5px;
}
.OJBetter_setting_menu .OJBetter_setting_list button {
cursor: pointer;
color: #7986cb;
background-color: #e8eaf6;
border: 1px solid #7986cb;
border-radius: 6px;
width: 100px;
margin: -5px 2px;
padding: 5px 10px;
}
.OJBetter_setting_menu .OJBetter_setting_list button:hover {
color: #e8eaf6;
background-color: #7986cb;
border: 1px solid #7986cb;
}
.OJBetter_setting_menu label, #darkMode_span, #loaded_span {
font-size: 16px;
}
.OJBetter_setting_list {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 10px;
margin: 5px 0px;
background-color: #ffffff;
border: 1px solid #c9c6c642;
border-bottom-color: #c9c6c696;
border-radius: 8px;
justify-content: space-between;
}
.OJBetter_setting_list.alert_danger {
color: #F44336;
background-color: #FFEBEE;
border: 1px solid #F44336;
margin: 10px 0px;
}
.OJBetter_setting_list.alert_warn {
color: #E65100;
background-color: #FFF3E0;
border: 1px solid #FF9800;
margin: 10px 0px;
}
.OJBetter_setting_list.alert_tip {
color: #009688;
background-color: #E0F2F1;
border: 1px solid #009688;
margin: 10px 0px;
}
.OJBetter_setting_list.alert_info {
color: #ffffff;
background-color: #009688;
margin: 10px 0px;
box-shadow: rgba(0, 0, 0, 0.06) 0px 2px 4px 0px inset;
}
.OJBetter_setting_list p:not(:last-child) {
margin-bottom: 10px;
}
.OJBetter_setting_list p:not(:first-child) {
margin-top: 10px;
}
/*设置面板-checkboxs*/
.OJBetter_setting_menu .OJBetter_checkboxs {
flex-basis: 100%;
display: flex;
padding: 8px;
margin: 10px 0px 0px 0px;
border-bottom: 1px solid #c9c6c696;
border-radius: 8px;
border: 1px solid #c5cae9;
background-color: #f0f8ff;
}
.OJBetter_setting_menu .OJBetter_checkboxs label {
font-size: 13px;
margin: 0px 6px 0px 3px;
}
.OJBetter_setting_menu .OJBetter_checkboxs input[type=checkbox]:checked+label{
color: #7986cb;
}
.OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"] {
border: none;
width: 16px;
height: 16px;
}
.OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]::before{
background: #ffffff;
transform: none;
width: 16px;
height: 16px;
}
.OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked {
background: none;
border: none;
}
.OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::before {
border: 1.5px solid #95a2de;
background: #e8eaf6;
transform: none;
}
.OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:checked::after {
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");
top: 0px;
left: 3.5px;
}
.OJBetter_setting_menu .OJBetter_checkboxs input[type="checkbox"]:disabled+label {
color: #BDBDBD;
}
/*设置面板-radio*/
.OJBetter_setting_menu label {
display: block;
font-weight: initial;
list-style-type: none;
padding-inline-start: 0px;
overflow-x: auto;
max-width: 100%;
margin: 3px 0px;
overflow-x: visible;
}
.OJBetter_setting_menu_label_text {
display: flex;
border: 1px dashed #00aeeccc;
height: 35px;
width: 100%;
color: #6e6e6e;
font-weight: 300;
font-size: 14px;
letter-spacing: 2px;
padding: 7px;
margin-bottom: 4px;
align-items: center;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
input[type="radio"]:checked+.OJBetter_setting_menu_label_text {
background: #41e49930;
border: 1px solid green;
color: green;
text-shadow: 0px 0px 0.5px green;
}
input[type="radio"]:disabled+.OJBetter_setting_menu_label_text {
background: #fafafa00;
border: 1px solid #e0e0e07a;
color: #e0e0e0;
}
.OJBetter_setting_menu label input[type="radio"], .OJBetter_contextmenu label input[type="radio"]{
appearance: none;
list-style: none;
padding: 0px !important;
margin: 0px;
clip: rect(0 0 0 0);
-webkit-clip-path: inset(100%);
clip-path: inset(100%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
/*设置面板-文本输入框*/
.OJBetter_setting_menu input[type="text"] {
display: block;
height: 25px !important;
width: 100%;
background-color: #ffffff;
color: #727378;
font-size: 12px;
border-radius: 0.3rem;
padding: 1px 5px !important;
box-sizing: border-box;
margin: 5px 0px 5px 0px;
border: 1px solid #00aeeccc;
box-shadow: 0 0 1px #0000004d;
}
.OJBetter_setting_menu .OJBetter_setting_list input[type="text"] {
margin-left: 5px;
}
.OJBetter_setting_menu input[type="text"]:focus-visible{
border-style: solid;
border-color: #3f51b5;
outline: none;
}
.OJBetter_setting_menu_config_box {
width: 100%;
display: grid;
margin-top: 5px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.OJBetter_setting_menu input::placeholder {
color: #727378;
}
.OJBetter_setting_menu input.no_default::placeholder{
color: #BDBDBD;
}
.OJBetter_setting_menu input.is_null::placeholder{
color: red;
border-width: 1.5px;
}
.OJBetter_setting_menu input.is_null{
border-color: red;
}
.OJBetter_setting_menu textarea {
resize: vertical;
display: block;
width: 100%;
height: 60px;
background-color: #ffffff;
color: #727378;
font-size: 12px;
padding: 1px 5px !important;
box-sizing: border-box;
margin: 5px 0px 5px 0px;
border: 1px solid #00aeeccc;
box-shadow: 0 0 1px #0000004d;
}
.OJBetter_setting_menu textarea:focus-visible{
border-style: solid;
border-color: #3f51b5;
outline: none;
}
.OJBetter_setting_menu textarea::placeholder{
color: #BDBDBD;
font-size: 14px;
}
.OJBetter_setting_menu #tempConfig_save {
cursor: pointer;
display: inline-flex;
padding: 5px;
background-color: #1aa06d;
color: #ffffff;
font-size: 14px;
line-height: 1.5rem;
font-weight: 500;
justify-content: center;
width: 100%;
border-radius: 0.375rem;
border: none;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
margin-top: 20px
}
.OJBetter_setting_menu button#debug_button.debug_button {
width: 18%;
}
.OJBetter_setting_menu span.tip {
color: #999;
font-size: 12px;
font-weight: 500;
padding: 5px 0px;
}
/*设置面板-tip*/
.help_tip {
margin-right: auto;
}
span.input_label {
font-size: 14px;
}
.help_tip .tip_text {
display: none;
position: absolute;
color: #697e91;
font-weight: 400;
font-size: 14px;
letter-spacing: 0px;
background-color: #ffffff;
padding: 10px;
margin: 5px 0px;
border-radius: 4px;
border: 1px solid #e4e7ed;
box-shadow: 0px 0px 12px rgba(0, 0, 0, .12);
z-index: 100;
}
.help_tip .tip_text p {
margin-bottom: 5px;
}
.help_tip .tip_text:before {
content: "";
position: absolute;
top: -20px;
right: -10px;
bottom: -10px;
left: -10px;
z-index: -1;
}
.help-icon {
cursor: help;
width: 15px;
color: #b4b9d4;
margin-left: 5px;
margin-top: 3px;
}
.OJBetter_setting_menu .OJBetter_setting_menu_label_text .help_tip .help-icon {
color: #7fbeb2;
}
.help_tip .help-icon:hover + .tip_text, .help_tip .tip_text:hover {
display: block;
cursor: help;
width: 250px;
}
/* 版本信息 */
.OJBetter_setting_menu .versionInfo{
display: grid;
justify-items: center;
font-size: 16px;
padding: 10px;
}
.OJBetter_setting_menu .versionInfo>* {
margin: 10px 0px;
}
/* 配置管理面板 */
.config{
width: 100%;
margin: 10px 0px;
}
.config li.tempConfig_add_button {
cursor: pointer;
height: 40px;
border: 1px dashed #BDBDBD;
border-radius: 8px;
background-color: #fcfbfb36;
color: #bdbdbd;
font-size: 14px;
align-items: center;
justify-content: center;
}
.config li.tempConfig_add_button:hover {
border: 1px dashed #03A9F4;
background-color: #d7f0fb8c;
color: #03A9F4;
}
.config .config_bar_list {
display: flex;
width: 100%;
padding-bottom: 2px;
border: 1px solid #c5cae9;
background-color: #f0f8ff;
box-sizing: border-box;
border-radius: 0px 0px 8px 8px;
}
.config .config_bar_list input[type="radio"] {
appearance: none;
width: 0;
height: 0;
overflow: hidden;
}
.config .config_bar_list input[type="radio"] {
margin: 0px;
}
.config .config_bar_list input[type=radio]:focus {
outline: 0px;
}
.config .config_bar_ul_li_text {
display: flex;
align-items: center;
justify-content: center;
max-width: 100%;
height: 40px;
overflow-x: auto;
font-size: 14px;
font-weight: 400;
margin: 0px 4px;
padding: 3px;
border: 1px solid #dedede;
border-radius: 10px;
box-shadow: 0px 2px 4px 0px rgba(0,0,0,.05);
box-sizing: border-box;
}
.config .config_bar_ul li button {
background-color: #e6e6e6;
color: #727378;
height: 23px;
font-size: 14px;
border-radius: 0.3rem;
padding: 1px 5px;
margin: 5px;
border: none;
box-shadow: 0 0 1px #0000004d;
}
.config .config_bar_ul {
display: flex;
align-items: center;
list-style-type: none;
padding-inline-start: 0px;
overflow-x: auto;
max-width: 100%;
margin: 0px;
padding: 5px;
}
.config .config_bar_ul li {
width: 80px;
display: grid;
margin: 4px 4px;
min-width: 100px;
box-sizing: border-box;
}
.config .config_bar_ul_li_text:hover {
background-color: #eae4dc24;
}
input[type="radio"]:checked + .config_bar_ul_li_text {
background: #41b3e430;
border: 1px solid #5e7ce0;
color: #5e7ce0;
}
.config .config_bar_ul::-webkit-scrollbar {
width: 5px;
height: 4px;
}
.config .config_bar_ul::-webkit-scrollbar-thumb {
background-clip: padding-box;
background-color: #d7d9e4;
border-radius: 8px;
}
.config .config_bar_ul::-webkit-scrollbar-button:start:decrement {
width: 4px;
background-color: transparent;
}
.config .config_bar_ul::-webkit-scrollbar-button:end:increment {
width: 4px;
background-color: transparent;
}
.config .config_bar_ul::-webkit-scrollbar-track {
border-radius: 5px;
}
.config .config_bar_ul_li_text::-webkit-scrollbar {
width: 5px;
height: 7px;
background-color: #aaa;
}
.config .config_bar_ul_li_text::-webkit-scrollbar-thumb {
background-clip: padding-box;
background-color: #d7d9e4;
}
.config .config_bar_ul_li_text::-webkit-scrollbar-track {
background-color: #f1f1f1;
}
.config .config_bar_list_add_div {
display: flex;
height: 40px;
margin: 4px 2px;
}
/* 修改菜单 */
#config_bar_menu {
z-index: 400;
position: fixed;
width: 60px;
background: #ffffff;
box-shadow: 1px 1px 4px 0px #0000004d;
border: 0px solid rgba(0,0,0,0.04);
border-radius: 4px;
padding: 8px 0;
}
.config_bar_menu_item {
cursor: pointer;
padding: 2px 6px;
display: flex;
justify-content: center;
align-items: center;
height: 32px;
font-size: 14px;
font-weight: 500;
box-shadow: inset 0px 0px 0px 0px #8bb2d9;
}
#config_bar_menu_edit:hover {
background-color: #00aeec;
color: white;
}
#config_bar_menu_delete:hover {
background-color: #FF5722;
color: white;
}
/* 配置编辑页面 */
#config_edit_menu {
z-index: 300;
width: 450px;
}
/* 黑暗模式选项按钮 */
.dark-mode-selection {
display: flex;
justify-content: center;
align-items: center;
max-width: 350px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.dark-mode-selection label {
margin: 8px 0px 8px 8px;
}
.dark-mode-selection > * {
margin: 6px;
}
.dark-mode-selection .OJBetter_setting_menu_label_text {
border-radius: 8px;
margin-bottom: 0px;
}
/*确认弹窗*/
.OJBetter_modal {
z-index: 600;
display: grid;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
font-family: var(--vp-font-family-base);
width: max-content;
padding: 10px 20px;
box-shadow: 0px 0px 0px 4px #ffffff;
border-radius: 6px;
background-color: #f0f4f9;
border-collapse: collapse;
border: 1px solid #ffffff;
color: #697e91;
}
.OJBetter_modal h2 {
font-size: 1.6em;
font-weight: 700;
}
.OJBetter_modal .content{
white-space: nowrap;
max-height: 500px;
overflow-y: auto;
}
.OJBetter_modal .buttons{
display: flex;
padding-top: 15px;
}
.OJBetter_modal button {
display: inline-flex;
justify-content: center;
align-items: center;
line-height: 1;
white-space: nowrap;
cursor: pointer;
text-align: center;
box-sizing: border-box;
outline: none;
transition: .1s;
user-select: none;
vertical-align: middle;
-webkit-appearance: none;
height: 24px;
padding: 5px 11px;
margin-right: 15px;
font-size: 12px;
border-radius: 4px;
color: #ffffff;
background: #009688;
border-color: #009688;
border: none;
}
.OJBetter_modal button.secondary{
background-color:#4DB6AC;
}
.OJBetter_modal button:hover{
background-color:#4DB6AC;
}
.OJBetter_modal button.secondary:hover {
background-color: #80CBC4;
}
.OJBetter_modal .help-icon {
margin: 0px 8px 0px 0px;
height: 1em;
width: 1em;
line-height: 1em;
display: inline-flex;
justify-content: center;
align-items: center;
position: relative;
fill: currentColor;
font-size: inherit;
}
.OJBetter_modal p {
margin: 5px 0px;
}
/* 右键菜单 */
.OJBetter_contextmenu {
z-index: 500;
display: grid;
position: absolute;
background-color: #f0f4f9;
border-collapse: collapse;
color: #697e91;
font-family: var(--vp-font-family-base);
overflow: hidden;
box-sizing: content-box;
box-shadow: 0px 0px 0px 2px #eddbdb4d;
}
.OJBetter_contextmenu label {
margin: 0px;
}
input[type="radio"]:checked+.OJBetter_contextmenu_label_text {
background: #41e49930;
border: 1px solid green;
color: green;
font-weight: 500;
}
.OJBetter_contextmenu_label_text {
display: flex;
border: 1px dashed #80cbc4;
height: 26px;
width: 100%;
color: gray;
font-size: 13px;
font-weight: initial;
padding: 4px;
align-items: center;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.OJBetter_contextmenu_label_text:hover {
color: #F44336;
border: 1px dashed #009688;
background-color: #ffebcd;
}
/* RatingByClist */
.ratingBadge, html[data-theme=dark] button.ratingBadge{
display: block;
font-weight: 700;
font-size: 11px;
margin-top: 5px;
padding: 2px;
border-radius: 4px;
color: #B0BEC5;
border: 1px solid #cccccc66;
}
/* 多选翻译 */
.block_selected{
box-shadow: 0px 0px 0px 1px #FF9800;
outline: none;
}
/* 悬浮菜单 */
.OJBetter_MiniTranslateButton {
z-index: 100;
display: grid;
position: absolute;
border-collapse: collapse;
fill: #F57C00;
background-color: #FFF3E0;
overflow: hidden;
box-sizing: content-box;
box-shadow: 0px 0px 0px 2px #FFE0B2;
border-radius: 100%;
}
.OJBetter_MiniTranslateButton:hover {
cursor: pointer;
box-shadow: 0px 0px 0px 2px #FFB74D;
}
/* acmsguru划分块 */
.OJBetter_acmsguru {
margin: 0 0 1em!important;
}
/* 代码提交表单 */
#OJBetter_SubmitForm.input-output-copier:hover {
background-color: #ffffff00;
}
#OJBetter_SubmitForm input[type="number"] {
width: 40px;
color: #009688;
appearance: none;
border-radius: 6px;
border-style: solid;
border: none;
background-color: #ffffff00;
}
#OJBetter_SubmitForm :focus-visible {
outline: none;
}
#OJBetter_SubmitForm .topDiv {
height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0px;
box-sizing: border-box;
}
#OJBetter_SubmitForm .topDiv .topRightDiv {
height: 100%;
display: flex;
flex-wrap: wrap;
gap: 0px;
}
#OJBetter_SubmitForm input[type="checkbox"], #OJBetter_SubmitForm label {
margin: 0px;
font-weight: initial;
}
#OJBetter_SubmitForm #fontSizeInput {
border: none;
background-color: #ffffff00;
}
/* 顶部区域 */
#OJBetter_SubmitForm .topRightDiv>* {
height: 100%;
box-sizing: border-box;
}
#OJBetter_SubmitForm .topRightDiv>button{
padding: 0px 8px;
}
#OJBetter_SubmitForm .topRightDiv {
display: flex;
flex-wrap: wrap;
gap: 0px;
align-items: center;
}
/* LSP连接Log */
#LSPLog{
width: 500px;
height: 500px;
position: fixed;
top: 50%;
left: 50%;
padding: 10px;
transform: translate(-50%, -50%);
border: 1px solid;
z-index: 200;
background-color: #ffffff;
}
#LSPLog button{
position: fixed;
top: 10px;
right: 10px;
z-index: 200;
}
#LSPLog #LSPLogList{
width: 500px;
height: 500px;
overflow: auto;
color: #424242;
}
#LSPLog li:nth-child(odd){
background-color: #f5f5f5;
}
#LSPLog details{
padding: 2px;
}
/* 代码编辑器 */
#OJBetter_editor{
box-sizing: border-box;
height: 600px;
border: 1px solid #d3d3d3;
width: 100%;
resize: vertical;
display: flex;
flex-direction: column;
}
#OJBetter_editor.fullscreen{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
z-index: 2000;
}
#OJBetter_editor.bottom{
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 50vh;
z-index: 2000;
}
.ojb_btn.exit_button_bottom {
position: fixed;
bottom: 30px;
right: 15px;
z-index: 2000;
height: 28px;
}
/* monaco */
#OJBetter_monaco {
flex: 1;
min-height: 0;
width: 100%;
}
#OJBetter_monaco .highlight {
border: 1px solid #ffffff00;
background-color: #ffffff00!important
}
.monaco-hover hr {
margin: 4px -8px 4px !important;
}
/* 状态底栏 */
#OJBetter_statusBar{
height: 22px;
font-size: 12px;
color: #757575;
border: 1px solid #d3d3d3;
background-color: #f8f8f8;
padding: 3px;
box-sizing: border-box;
}
/* 提交 */
#OJBetter_submitDiv{
display: flex;
padding-top: 15px;
height: 50px;
box-sizing: border-box;
}
#OJBetter_submitDiv >* {
border-radius: 6px;
}
#OJBetter_submitDiv > button {
height: 100%;
aspect-ratio: 1 / 1;
}
#SubmitButton {
color: #fff;
background-color: #209978;
border-color: #17795E;
}
#SubmitButton:hover {
background-color: #17795e;
}
#SubmitButton.disabled {
background-color: red;
animation: shake 0.07s infinite alternate;
}
#programTypeId{
height: 100%;
padding: 5px 10px;
border-radius: 6px;
border-style: solid;
border: 1px solid #ced4da;
color: #212529;
}
/* 调试 */
.OJBetter_loding{
padding: 6px 0px 0px 5px;
height: 22px;
}
#CompilerArgsInput{
flex-grow: 1;
width: 100%;
height: 100%;
margin-bottom: 10px;
padding: 5px 10px;
border-radius: 6px;
box-sizing: border-box;
border: 1px solid #ccc;
box-shadow: inset 0px 1px 1px rgba(0,0,0,.075);
}
#CompilerArgsInput[disabled] {
cursor: not-allowed;
}
#CompilerSetting{
font-size: 14px;
margin-top: 10px;
display: none;
}
#CompilerSetting select, #CompilerSetting textarea{
padding: 4px 10px;
border-radius: 6px;
border-style: solid;
border: 1px solid #ced4da;
color: #212529;
}
#CompilerBox{
display: grid;
margin-top: 10px;
border: #d0d7de solid 1px;
border-radius: 6px;
}
#CompilerBox > * {
margin: 5px;
}
/* 自定义样例 */
#customTestBlock {
margin-top: 10px;
font-size: 14px;
color: #616161;
border: 1px solid #d3d3d3;
box-sizing: border-box;
position: relative;
}
#customTestBlock #customTests{
border-top: 1px solid #d3d3d3;
margin: 0px 0px 40px 0px;
}
#customTestBlock summary {
cursor: pointer;
padding: 10px;
}
#customTestBlock textarea {
resize: vertical;
}
.sampleDiv {
color: #727378;
background-color: #FAFAFA;
padding: 5px;
margin-bottom: 10px;
box-shadow: inset 0 0 1px #0000004d;
position: relative;
}
.dynamicTextarea {
width: 98%;
height: 120px;
margin: 10px 5px;
border: 1px solid #E0E0E0;
}
.deleteCustomTest {
cursor: pointer;
position: absolute;
top: 5px;
right: 5px;
display: flex;
fill: #9E9E9E;
padding: 2px 2px;
border-radius: 4px;
border: 1px solid #ffffff00;
background-color: #ffffff00;
align-items: center;
}
.deleteCustomTest:hover {
fill: #EF5350;
border: 1px solid #ef9a9a;
background-color: #FFEBEE;
}
#addCustomTest {
cursor: pointer;
position: absolute;
bottom: 5px;
right: 5px;
padding: 3px 10px;
color: #795548;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #FAFAFA;
}
#addCustomTest:hover {
background-color: #f5f5f5;
}
/* 调试结果 */
#statePanel{
display: none;
padding: 5px;
margin-top: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.test-case {
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.test-case:not(:first-child){
margin-top: 5px;
}
.test-case > * {
margin: 5px 0px;
}
.test-case > :first-child {
margin-top: 0px;
}
.test-case > :last-child {
margin-bottom: 0px;
}
.test-case-title, .test-case-status {
font-size: 16px;
display: inline;
}
.test-case-status{
margin-left: 5px;
}
.test-case-status.error{
color: red;
}
.test-case-status.success{
color: #449d44;
}
.test-case-judge {
font-size: 13px;
}
/* 差异对比 */
.output_diff {
color: #5d4037;
margin: 5px 0px;
display: grid;
border: 1px solid #bcaaa4;
font-size: 13px;
font-family: Consolas, "Lucida Console", "Andale Mono", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
overflow: auto;
}
.output_diff .added {
background-color: #c8f7c5;
user-select: none;
}
.output_diff .removed {
background-color: #f7c5c5;
}
.output_diff .diffLine {
display: flex;
}
.output_diff .diffLine:nth-child(odd) {
background-color: #f5f5f5;
}
.lineNo {
display: flex;
align-items: center;
justify-content: center;
width: 17px;
color: #BDBDBD;
font-size: 10px;
border-right: 1px solid;
user-select: none;
}
.lineContent {
display: grid;
width: 100%;
}
.lineContent>span {
height: 16px;
padding-left: 3px;
}
.output_no_diff {
padding: 5px;
border: 1px solid #ddd;
}
.diff_note {
font-size: 10px;
}
/* 移动设备 */
@media (max-device-width: 450px) {
.ojb_btn{
height: 2em;
font-size: 1.2em;
}
.ojb_btn.OJBetter_setting{
height: 2.5em;
font-size: 1em;
}
.OJBetter_setting_menu{
width: 90%;
}
.OJBetter_setting_menu label, #darkMode_span, #loaded_span, .OJBetter_setting_menu_label_text,
.OJBetter_setting_sidebar li{
font-size: 1em;
}
.translate-problem-statement{
font-size: 1.2em;
}
.OJBetter_modal{
font-size: 1.5em;
}
.OJBetter_setting_list, .translate-problem-statement{
padding: 0.5em;
}
.OJBetter_setting_menu_label_text{
height: 2.5em;
padding: 0.5em;
}
#pagBar #jump-input, #pagBar #items-per-page, .OJBetter_modal button{
height: 2.5em;
font-size: 1em;
}
.translate-problem-statement p, .translate-problem-statement ul li{
line-height: 1.5em !important;
}
.OJBetter_contextmenu_label_text{
height: 3em;
font-size: 1em;
}
}
/* 覆盖网站原本的样式 */
div#select-lang {
padding: 0px;
}
`);
/**
* 添加一些依赖库和条件加载的css样式
*/
function addDependencyStyles() {
GM_addStyle(GM_getResourceText("xtermcss"));
// 自定义图标大小
GM_addStyle(`
.iconfont {
font-size: ${OJBetter.preference.iconButtonSize}px;
}
`);
}
/**
* 添加包含i18n内容的css样式
*/
function addI18nStyles() {
GM_addStyle(`
/* 加载鼠标悬浮覆盖层css */
.overlay::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
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);
z-index: 100;
}
.overlay::after {
content: '${i18next.t('targetArea', { ns: 'common' })}';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #00695C;
font-size: 16px;
font-weight: bold;
z-index: 100;
}
.config::before {
content: "${i18next.t('common.configManageTitle', { ns: 'settings' })}";
display: block;
height: 20px;
background-color: #f0f8ff;
border: 1px solid #c5cae9;
border-bottom: 0px;
line-height: 20px;
padding: 2px 10px;
border-radius: 8px 8px 0px 0px;
box-sizing: content-box;
}
.config.missing::before {
content: "${i18next.t('common.missing.radio', { ns: 'settings' })}";
background-color: #fef0f0;
color: #f56c6c;
border: 1px solid #fab6b6;
}
`);
}
// ------------------------------
// 一些工具类
// ------------------------------
/**
* 自定义错误类,以区分不同的错误类型
*/
class OJB_GMError extends Error {
constructor(type, message, originalError) {
super(message);
this.name = 'GMError';
this.type = type;
this.stack = originalError.stack;
Object.assign(this, originalError);
}
}
/**
* 文本块替换/恢复类
*/
class TextBlockReplacer {
constructor() {
/** @type {string[]} 匹配项 */
this.matches = [];
/** @type {Map} 待还原项 */
this.replacements = new Map();
/** @type {Map} 暂时未找到的待还原项 */
this.tempReplacements = new Map();
/** @type {string} 替换符号 */
this.replaceSymbol = OJBetter.translation.replaceSymbol;
}
/**
* 替换文本
* @param {string} text 原文本
* @param {RegExp} regex 匹配规则
* @returns {string} 替换后的文本
*/
replace(text, regex) {
this.matches = text.match(regex) || [];
try {
for (let i = 0; i < this.matches.length; i++) {
const match = this.matches[i];
const id = OJB_getRandomNumber(8);
let replacement = '';
switch (this.replaceSymbol) {
case "1":
replacement = `【${id}】`;
break;
case "2":
replacement = `{${id}}`;
break;
case "3":
replacement = `[${id}]`;
break;
default:
replacement = `【${id}】`;
break;
}
text = text.replace(match, replacement);
this.replacements.set(id, match);
}
} catch (e) { }
return text;
}
/**
* 恢复替换的文本
* @param {string} text 还原前的文本
* @returns {string} 还原后的文本
*/
recover(text) {
let textCopy = text;
/**
* 替换文本
* @param {string} replacement 替换的文本
* @param {string} regexPattern 匹配规则
* @returns {void}
*/
const replaceText = (replacement, regexPattern) => {
const latexMatch = '(?\\$\\$(\\\\.|[^\\$])*?\\$\\$)|(?\\$(\\\\.|[^\\$])*?\\$)|';
const regex = new RegExp(latexMatch + regexPattern, 'g');
textCopy = textCopy.replace(regex, (match, ...args) => {
// LaTeX中的不替换
const groups = args[args.length - 1]; // groups是replace方法的最后一个参数
if (groups.latex_block || groups.latex_inline) return match;
// 没有空格则加一个
const offset = args[args.length - 3]; // offset是replace方法的倒数第三个参数
let leftSpace = "", rightSpace = "";
if (!/\s/.test(textCopy[offset - 1])) leftSpace = " ";
if (!/\s/.test(textCopy[offset + match.length])) rightSpace = " ";
return leftSpace + replacement + rightSpace;
});
};
/**
* 尝试还原
* @param {string} replacement 替换的文本
* @param {string} id 替换的 id
* @returns {boolean} 是否替换成功
*/
const tryRecover = (replacement, id) => {
// 尝试还原,如果还原成功,则从 replacements 中删除
const originalText = textCopy;
replaceText(replacement, `【\\s*${id}\\s*】|\\[\\s*${id}\\s*\\]|{\\s*${id}\\s*}`); // 替换符完整匹配(考虑了多出空格的情况)
replaceText(replacement, `【\\s*${id}(?![】\\d])|(? {
tryRecover(replacement, id);
});
// 处理 tempReplacements 中的项
while (this.tempReplacements.size > 0) {
let found = false;
this.tempReplacements.forEach((replacement, id) => {
found = tryRecover(replacement, id) || found;
});
if (!found) break; // 如果这一轮没有找到任何项,终止循环
}
// 如果 tempReplacements 还有未找到的项
if (this.tempReplacements.size > 0) {
console.warn("There are still some replacements not found:", this.tempReplacements);
}
return textCopy;
}
}
// ------------------------------
// 一些工具函数
// ------------------------------
/**
* 延迟函数
* @param {number} ms 延迟时间(毫秒)
* @returns {Promise}
*/
function OJB_delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 格式化链接格式
* @param {string} url 链接字符串
* @returns {string} 清理后的链接字符串
*/
function OJB_cleanLink(url) {
// 替换'http://'为'https://'
let cleanUrl = url.replace(/^http:\/\//i, 'https://');
// 移除末尾的斜杠
cleanUrl = cleanUrl.replace(/\/$/, '');
return cleanUrl;
}
/**
* 深度比较两个对象或数组是否完全相等。
* @param {any} a - 第一个比较对象。
* @param {any} b - 第二个比较对象。
* @returns {boolean} - 如果两个对象或数组深度相等,则返回true,否则返回false。
*/
function OJB_deepEquals(a, b) {
if (a === b) return true;
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (let key of keysA) {
if (!b.hasOwnProperty(key)) return false;
if (!OJB_deepEquals(a[key], b[key])) return false;
}
return true;
}
/**
* 用于封装需要重试的异步函数
* @param {Function} task 需要封装的异步函数
* @param {Object} options 配置项
* @param {Number} options.maxRetries 重试次数,默认为 5
* @param {Number} options.retryInterval 重试时间间隔,默认为 0 毫秒
* @param {Function} options.errorHandler 错误处理函数,默认为抛出错误
* @param {...any} args task 函数的参数
* @returns {Promise} 返回 Promise
*/
async function OJB_promiseRetryWrapper(task, {
maxRetries = 5,
retryInterval = 0,
errorHandler = (err) => { throw err }
} = {}, ...args) {
let attemptsLeft = maxRetries;
while (attemptsLeft--) {
try {
return await task(...args);
} catch (err) {
if (attemptsLeft <= 0) {
return errorHandler(err, maxRetries, attemptsLeft);
}
if (retryInterval > 0) {
await OJB_delay(retryInterval);
}
}
}
}
/**
* GM_xmlhttpRequest 的 Promise 封装
* @param {Object} options GM_xmlhttpRequest 的参数
* @param {Boolean} isStream 是否为流式请求
* @returns {Promise} 返回 Promise
*/
function OJB_GMRequest(options, isStream = false) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
...options,
...(isStream ? {
onloadstart: resolve
} : {
onload: resolve
}),
onerror: (error) => reject(new OJB_GMError('error', 'An error occurred during the request.', error)),
ontimeout: (error) => reject(new OJB_GMError('timeout', 'The request timed out.', error)),
onabort: (error) => reject(new OJB_GMError('abort', 'The request was aborted.', error)),
});
});
}
/**
* 获取cookie
* @param {string} name cookie名称
* @returns {string} cookie值
*/
function OJB_getCookie(name) {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
const [cookieName, cookieValue] = cookie.split("=");
if (cookieName === name) {
return decodeURIComponent(cookieValue);
}
}
return "";
}
/**
* 检查是否仍在同一浏览器会话中
* @param {string} sessionKey - 会话键名,用于标识会话
* @returns {boolean} - 如果在当前会话中之前已经设置过这个键,则返回true,否则返回false
*/
function OJB_isSameBrowserSession(sessionKey) {
const fullCookieName = `OJB_Session_${sessionKey}`;
const sessionValue = OJB_getCookie(fullCookieName);
if (sessionValue === "") {
document.cookie = `${fullCookieName}=true; path=/`;
return false;
}
return true;
}
/**
* 随机数生成
* @param {number} numDigits 位数
* @returns {number}
*/
function OJB_getRandomNumber(numDigits) {
let min = Math.pow(10, numDigits - 1);
let max = Math.pow(10, numDigits) - 1;
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* 防抖函数
* @param {Function} callback 回调函数
* @returns {Function}
*/
function OJB_debounce(callback) {
let timer;
let immediateExecuted = false;
const delay = 500;
return function () {
clearTimeout(timer);
if (!immediateExecuted) { callback.call(this); immediateExecuted = true; }
timer = setTimeout(() => { immediateExecuted = false; }, delay);
};
}
/**
* 为元素添加鼠标拖拽支持
* @param {JQuery} element 要添加拖拽支持的元素
* @returns {void}
*/
function OJB_addDraggable(element) {
let isDragging = false;
let x, y, l, t, nl, nt;
let isSpecialMouseDown = false; // 选取某些元素时不拖动
element.on('mousedown', function (e) {
isSpecialMouseDown = $(e.target).is('label, p, input, textarea, span, select, details, summary');
if (isSpecialMouseDown) return;
isDragging = true;
x = e.clientX;
y = e.clientY;
l = element.offset().left - $(window).scrollLeft();
t = element.offset().top - $(window).scrollTop();
element.css({ left: l + 'px', top: t + 'px', transform: 'none' });
$(document).on("mousemove", drag);
$(document).on("mouseup", stopDrag);
element.css('cursor', 'all-scroll');
});
const drag = (e) => {
if (!isDragging) return;
// 不执行拖动操作
if ($(e.target).is('label, p, input, textarea, span') || isSpecialMouseDown && !$(e.target).is('input, textarea')) return;
e.preventDefault();
const nx = e.clientX;
const ny = e.clientY;
nl = nx - (x - l);
nt = ny - (y - t);
element.css({ transform: `translate(${nx - x}px, ${ny - y}px)` });
};
const stopDrag = () => {
isDragging = false;
isSpecialMouseDown = false;
element.css('cursor', 'default');
// 在停止拖拽后,设置元素的left和top,并还原transform
element.css({ left: nl + 'px', top: nt + 'px', transform: 'none' });
$(document).off("mousemove", drag);
$(document).off("mouseup", stopDrag);
};
}
/**
* 切换元素的折叠/展开过渡动画
* @param {HTMLElement} element
*/
function OJB_toggleCollapseExpand(element) {
// 设置transitionend事件监听器的函数
const setTransitionListener = (listener) => {
const listenerName = `transitionEndListener${Date.now()}`;
window[listenerName] = listener;
element.addEventListener('transitionend', listener);
element.setAttribute('data-transition-end-listener', listenerName);
};
// 移除事件监听器的函数
const removeTransitionListener = () => {
const transitionEndListenerName = element.getAttribute('data-transition-end-listener');
if (transitionEndListenerName) {
element.removeEventListener('transitionend', window[transitionEndListenerName]);
element.removeAttribute('data-transition-end-listener');
}
};
const collapsed = element.getAttribute('data-collapsed') === 'true';
const sectionHeight = element.scrollHeight;
// 移除事件监听器
removeTransitionListener();
// 设置初始样式
element.style.overflow = 'hidden';
element.style.transition = 'height 0.3s ease-out 0s';
element.style.height = collapsed ? `0px` : `${sectionHeight}px`;
element.style.opacity = collapsed ? '' : '1';
// 需要立即开始动画
requestAnimationFrame(() => {
// 设置结束样式
element.style.height = collapsed ? `${sectionHeight}px` : `0px`;
});
const transitionEndListener = (event) => {
if (event.propertyName === 'height') {
if (collapsed) {
// 展开后的设置
element.style.height = '';
element.style.overflow = '';
} else {
// 折叠后的设置
element.style.opacity = '0';
}
removeTransitionListener();
}
};
setTransitionListener(transitionEndListener);
// 更新data-collapsed属性
element.setAttribute('data-collapsed', collapsed ? 'false' : 'true');
}
/**
* 获取外部JSON并转换为Object
* @param {string} url JSON Url
* @param {boolean} [nacache=true] 是否不使用缓存
* @returns {Promise