// ==UserScript==
// @name [IJWS]I just want to study-我只是想学习
// @namespace http://tampermonkey.net/
// @version release-251206.100
// @description 组卷网辅助功能,提供刷题模式来沉浸式刷题
// @author 云氢YunHydrogen
// @match *://zujuan.xkw.com/*
// @icon https://www.xkw.com/favicon.ico
// @grant GM_addStyle
// @license AGPL-3.0
// @downloadURL https://update.greasyfork.icu/scripts/557987/%5BIJWS%5DI%20just%20want%20to%20study-%E6%88%91%E5%8F%AA%E6%98%AF%E6%83%B3%E5%AD%A6%E4%B9%A0.user.js
// @updateURL https://update.greasyfork.icu/scripts/557987/%5BIJWS%5DI%20just%20want%20to%20study-%E6%88%91%E5%8F%AA%E6%98%AF%E6%83%B3%E5%AD%A6%E4%B9%A0.meta.js
// ==/UserScript==
(function() {
'use strict';
const cleanCss = `
/* 隐藏全局干扰元素 */
body.clean-mode .header,
body.clean-mode .bread-nav,
body.clean-mode .footer,
body.clean-mode .fiexd-nav,
body.clean-mode .ai-entry,
body.clean-mode .other-info,
body.clean-mode .xep-root,
body.clean-mode iframe,
body.clean-mode .care-mode_protect-eye,
body.clean-mode .free-ad,
body.clean-mode .low-browser,
body.clean-mode .ques-additional,
body.clean-mode .exam-item__custom,
body.clean-mode .exam-item__info,
body.clean-mode .sec-title .btn-box,
body.clean-mode .ctrl-box
{
display: none !important;
}
/* 页面布局调整 */
body.clean-mode {
background-color: #ffffff !important;
overflow-x: hidden;
}
body.clean-mode .page.exam-detail {
width: 100% !important;
max-width: 950px !important;
margin: 0 auto !important;
padding: 5px 20px !important;
background: #ffffff !important;
float: none !important;
}
body.clean-mode .exam-cnt {
width: 100% !important;
float: none !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}
body.clean-mode .page.exam-detail::before,
body.clean-mode .page.exam-detail::after,
body.clean-mode .page.exam-detail.clearfix::before,
body.clean-mode .page.exam-detail.clearfix::after {
content: none !important;
display: none !important;
}
/* 全模式题目衬底收紧 */
.tk-quest-item,
.quesroot {
margin: 0 !important;
padding: 0 !important;
}
/* 统一 wrapper/quesdiv/selected-mask 上下间距 */
.wrapper,
.quesdiv,
.selected-mask {
margin-block: 0 !important;
padding-block: 0 !important;
}
/* 题目样式优化 */
body.clean-mode .tk-quest-item {
margin-bottom: 16px !important;
padding-top: 8px !important;
padding-bottom: 12px !important;
border-bottom: 1px dashed #ccc !important;
background: #fff !important;
}
body.clean-mode .tk-quest-item:hover,
body.clean-mode .quesroot:hover {
border: none !important;
border-bottom: 1px dashed #ccc !important;
box-shadow: none !important;
background: #fff !important;
}
body.clean-mode .tk-quest-item::after,
body.clean-mode .tk-quest-item::before,
body.clean-mode .quesroot::after,
body.clean-mode .quesroot::before {
content: none !important;
display: none !important;
}
body.clean-mode .exam-item__cnt {
font-size: 16px !important;
line-height: 1.8 !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
color: #000 !important;
background: #ffffff !important;
border: 0.5px solid #e0e7f1 !important;
border-radius: 8px !important;
padding: 0 16px !important;
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06) !important;
}
body.clean-mode .exam-item__opt {
background-color: #f5f7fa !important;
border: 1px solid #dde3ec !important;
padding: 14px !important;
border-radius: 8px !important;
margin-top: 12px !important;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.6), 0 6px 18px rgba(56, 103, 214, 0.08) !important;
}
#ijws-practice-answers .exam-item__opt {
background-color: #f5f7fa !important;
border: 1px solid #dde3ec !important;
padding: 14px !important;
border-radius: 8px !important;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.6), 0 6px 18px rgba(56, 103, 214, 0.08) !important;
margin-top: 6px !important;
}
/* 去除 selected-mask 导致的模糊和遮罩 */
body.clean-mode .selected-mask {
filter: none !important;
-webkit-filter: none !important;
opacity: 1 !important;
background: none !important;
transform: none !important;
position: static !important;
}
body.clean-mode .selected-mask::before,
body.clean-mode .selected-mask::after {
display: none !important;
content: none !important;
width: 0 !important;
height: 0 !important;
}
/* 标题样式优化 */
body.clean-mode .sec-title {
margin: 0 !important;
padding: 5px 0 !important;
}
body.clean-mode .title-txt {
font-weight: bold !important;
font-family: "SimHei", "黑体", "Microsoft YaHei", sans-serif !important;
font-size: 22px !important;
letter-spacing: 0.5px !important;
display: inline-block !important;
padding: 4px 12px !important;
border-radius: 6px !important;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08) !important;
margin: 0 !important;
}
body.clean-mode .sec-title span {
background: #FFCC11 !important;
color: #ffffff !important;
font-weight: bold !important;
display: inline-block !important;
font-size: 11px !important;
line-height: 1.2 !important;
padding: 3px 3px !important;
border-radius: 3px !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* 题目题号样式 (黄色衬底) */
.ijws-q-tit-num {
display: none;
color: #ffffff;
font-weight: bold;
background-color: #FFCC11;
padding: 3px 3px;
border-radius: 4px;
margin-right: 6px;
font-size: 12px;
vertical-align: middle;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
body.clean-mode .ijws-q-tit-num {
display: inline-block !important;
}
/* 练习模式答案区 */
.ijws-practice-hidden {
display: none !important;
}
#ijws-practice-answers {
margin-top: 40px;
padding: 18px;
border: 1px dashed #39C5BB;
border-radius: 8px;
background: #fdfefe;
}
#ijws-practice-answers .ijws-practice-heading {
font-weight: bold;
margin-bottom: 16px;
color: #39C5BB;
font-size: 18px;
}
.ijws-practice-answer {
margin-bottom: 14px;
padding-bottom: 12px;
border-bottom: 1px solid #eee;
}
.ijws-practice-title {
font-weight: bold;
margin-bottom: 8px;
color: #333;
}
/* 答案模式答案区 */
#ijws-answer-only {
margin-top: 32px;
padding: 18px;
border: 1px dashed #FFB300;
border-radius: 8px;
background: #fffaf3;
}
#ijws-answer-only .ijws-answer-heading {
font-weight: bold;
margin-bottom: 16px;
color: #d48806;
font-size: 18px;
}
#ijws-answer-only .ijws-answer-item {
margin-bottom: 14px;
padding-bottom: 12px;
border-bottom: 1px solid #f0d9a4;
}
#ijws-answer-only .ijws-answer-title {
font-weight: bold;
margin-bottom: 8px;
color: #b36b00;
}
/* 打印优化 */
@media print {
#ijws-container { display: none !important; }
body.clean-mode .page.exam-detail { max-width: 100% !important; padding: 0 !important; }
body.clean-mode .exam-item__opt { background-color: transparent !important; border: 1px solid #eee; }
body.clean-mode .tk-quest-item { page-break-inside: avoid; }
}
/* 提示弹窗 */
#ijws-toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 20px;
border-radius: 20px;
font-size: 14px;
z-index: 2147483647;
display: flex;
align-items: center;
gap: 8px;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
#ijws-toast.show {
opacity: 1;
}
#ijws-toast svg {
width: 20px;
height: 20px;
}
/* 子菜单激活状态 */
.ijws-menu-item.active {
background-color: #00FFCC !important;
}
`;
const cursorSvg = ``;
const cursorDataUrl = `data:image/svg+xml;utf8,${encodeURIComponent(cursorSvg)}`;
const PRACTICE_CONTAINER_ID = 'ijws-practice-answers';
const ANSWER_CONTAINER_ID = 'ijws-answer-only';
const GITHUB_URL = 'https://github.com/Yun-Hydrogen/IJustWanttoStudy';
const uiCss = `
/* 悬浮球容器 */
#ijws-container {
position: fixed;
bottom: 120px;
right: 120px;
z-index: 2147483647;
width: 50px;
height: 50px;
user-select: none;
cursor: url('${cursorDataUrl}') 6 6, auto;
}
/* 主按钮 */
#ijws-main-btn {
width: 100%;
height: 100%;
background-color: #66CCFF;
border-radius: 50%;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
cursor: url('${cursorDataUrl}') 6 6, grab;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 12px;
position: absolute;
top: 0;
left: 0;
z-index: 10;
transition: background-color 0.3s, transform 0.2s;
}
#ijws-main-btn:active {
cursor: url('${cursorDataUrl}') 6 6, grabbing;
transform: scale(0.95);
}
#ijws-main-btn svg {
width: 20px;
height: 20px;
margin-top: 2px;
fill: white;
}
/* 菜单项 */
.ijws-menu-item {
width: 32px;
height: 32px;
background-color: #fff;
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
position: absolute;
top: 9px; /* (50-32)/2 */
left: 9px;
display: flex;
align-items: center;
justify-content: center;
cursor: url('${cursorDataUrl}') 6 6, pointer;
opacity: 0;
transform: translate(0, 0) scale(0);
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 5;
pointer-events: none;
}
/* 菜单展开位置 */
#ijws-container.open .ijws-menu-item {
pointer-events: auto;
}
#ijws-container.open .ijws-menu-item:nth-of-type(2) { /* 菜单1: 左侧 */
transform: translateX(-45px) scale(1);
opacity: 1;
}
#ijws-container.open .ijws-menu-item:nth-of-type(3) { /* 菜单2: 上方 */
transform: translateY(-45px) scale(1);
opacity: 1;
}
#ijws-container.open .ijws-menu-item:nth-of-type(4) { /* 菜单3: 右侧 */
transform: translateX(45px) scale(1);
opacity: 1;
}
#ijws-container.open .ijws-menu-item:nth-of-type(5) { /* 菜单4: 下方 */
transform: translateY(45px) scale(1);
opacity: 1;
}
/* 图标 */
.ijws-menu-item svg {
width: 18px;
height: 18px;
}
.ijws-menu-item svg,
.ijws-menu-item svg path {
fill: #000000 !important;
}
/* Tooltip */
.ijws-tooltip {
position: absolute;
background: rgba(0,0,0,0.8);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
visibility: hidden;
}
/* Tooltip 位置 */
.ijws-menu-item:nth-of-type(2) .ijws-tooltip { /* 菜单1(左) */
right: 40px;
top: 50%;
transform: translateY(-50%);
}
.ijws-menu-item:nth-of-type(3) .ijws-tooltip { /* 菜单2(上) */
bottom: 40px;
left: 50%;
transform: translateX(-50%);
}
.ijws-menu-item:nth-of-type(4) .ijws-tooltip { /* 菜单3(右) */
left: 40px;
top: 50%;
transform: translateY(-50%);
}
.ijws-menu-item:nth-of-type(5) .ijws-tooltip { /* 菜单4(EIFORS*/
top: 40px;
left: 50%;
transform: translateX(-50%);
}
.ijws-menu-item:hover .ijws-tooltip {
opacity: 1;
visibility: visible;
}
`;
const MODE = Object.freeze({
NONE: 'none',
EXAM: 'exam',
REFERENCE: 'reference',
PRACTICE: 'practice',
ANSWER: 'answer'
});
const MODE_TOAST = Object.freeze({
[MODE.EXAM]: { enter: '已进入试卷模式', exit: '已退出试卷模式' },
[MODE.REFERENCE]: { enter: '已进入参考模式', exit: '已退出参考模式' },
[MODE.PRACTICE]: { enter: '已进入练习模式', exit: '已退出练习模式' },
[MODE.ANSWER]: { enter: '已进入答案模式', exit: '已退出答案模式' }
});
const state = {
currentMode: MODE.NONE,
isDragging: false,
dragStart: { x: 0, y: 0 },
initialPosition: { left: 0, top: 0 },
toastTimer: null,
longPressTimer: null,
mutationObserver: null
};
const DOM = {
container: null,
mainBtn: null,
toast: null,
menuItems: new Map()
};
const ICON = Object.freeze({
exam: `
`,
reference: `
`,
practice: `
`,
answer: `
`,
extra: `
`
});
const MENU_CONFIG = [
{ mode: MODE.EXAM, tooltip: '试卷模式', icon: ICON.exam },
{ mode: MODE.REFERENCE, tooltip: '参考模式', icon: ICON.reference },
{ mode: MODE.PRACTICE, tooltip: '练习模式', icon: ICON.practice },
{ mode: MODE.ANSWER, tooltip: '答案模式', icon: ICON.answer },
{
tooltip: '备用功能',
icon: ICON.extra,
onClick: () => showToast('备用功能开发中,敬请期待', ICON.extra, '#409EFF')
}
];
init();
function init() {
injectStyles();
buildFloatingUi();
attachGlobalHandlers();
observeDynamicContent();
}
function injectStyles() {
const styleEl = document.createElement('style');
styleEl.id = 'clean-mode-style';
styleEl.textContent = cleanCss + uiCss;
document.head.appendChild(styleEl);
}
function buildFloatingUi() {
DOM.container = document.createElement('div');
DOM.container.id = 'ijws-container';
DOM.mainBtn = createMainButton();
DOM.container.appendChild(DOM.mainBtn);
MENU_CONFIG.forEach((config) => {
const item = createMenuItem(config);
DOM.container.appendChild(item);
if (config.mode) {
DOM.menuItems.set(config.mode, { element: item, icon: config.icon });
}
});
document.body.appendChild(DOM.container);
DOM.toast = createToast();
}
function createMainButton() {
const mainBtn = document.createElement('div');
mainBtn.id = 'ijws-main-btn';
mainBtn.innerHTML = `
IJWS
`;
mainBtn.addEventListener('mousedown', handleMainButtonMouseDown);
['mouseup', 'mouseleave'].forEach(evt => mainBtn.addEventListener(evt, clearLongPressTimer));
mainBtn.addEventListener('contextmenu', handleContextMenuOpenGithub);
mainBtn.addEventListener('click', handleMainButtonClick);
return mainBtn;
}
function createMenuItem(config) {
const item = document.createElement('div');
item.className = 'ijws-menu-item';
item.innerHTML = `
${config.icon}
${config.tooltip}
`;
if (config.mode) {
item.addEventListener('click', () => handleModeToggle(config.mode));
} else if (typeof config.onClick === 'function') {
item.addEventListener('click', () => config.onClick());
}
return item;
}
function createToast() {
const toast = document.createElement('div');
toast.id = 'ijws-toast';
document.body.appendChild(toast);
return toast;
}
function attachGlobalHandlers() {
document.addEventListener('click', questionClickBlocker, true);
}
function observeDynamicContent() {
state.mutationObserver = new MutationObserver(() => {
if (document.body.classList.contains('clean-mode')) {
processQuestionTitles();
}
});
state.mutationObserver.observe(document.body, { childList: true, subtree: true });
}
function handleMainButtonMouseDown(event) {
state.isDragging = false;
state.dragStart.x = event.clientX;
state.dragStart.y = event.clientY;
const rect = DOM.container.getBoundingClientRect();
state.initialPosition.left = rect.left;
state.initialPosition.top = rect.top;
state.longPressTimer = setTimeout(() => {
if (!state.isDragging) {
openGithubHome();
}
}, 800);
const onMouseMove = (moveEvent) => {
const dx = moveEvent.clientX - state.dragStart.x;
const dy = moveEvent.clientY - state.dragStart.y;
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
state.isDragging = true;
clearTimeout(state.longPressTimer);
DOM.container.style.left = `${state.initialPosition.left + dx}px`;
DOM.container.style.top = `${state.initialPosition.top + dy}px`;
DOM.container.style.right = 'auto';
}
};
const onMouseUp = () => {
clearTimeout(state.longPressTimer);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
}
function clearLongPressTimer() {
clearTimeout(state.longPressTimer);
}
function handleContextMenuOpenGithub(event) {
event.preventDefault();
openGithubHome();
}
function handleMainButtonClick() {
if (!state.isDragging) {
DOM.container.classList.toggle('open');
}
}
function openGithubHome() {
window.open(GITHUB_URL, '_blank');
}
function questionClickBlocker(event) {
if (state.currentMode === MODE.NONE) return;
const target = event.target.closest('.tk-quest-item, .quesroot');
if (target) {
event.stopPropagation();
event.preventDefault();
}
}
function handleModeToggle(mode) {
const menuInfo = DOM.menuItems.get(mode);
if (!menuInfo) return;
const isSameMode = state.currentMode === mode;
if (isSameMode) {
exitMode(mode);
setMode(MODE.NONE);
showToast(MODE_TOAST[mode].exit, menuInfo.icon, '#f56c6c');
return;
}
exitMode(state.currentMode);
enterMode(mode);
setMode(mode);
showToast(MODE_TOAST[mode].enter, menuInfo.icon, '#67c23a');
DOM.container.classList.remove('open');
}
function setMode(mode) {
state.currentMode = mode;
DOM.menuItems.forEach((info, key) => {
info.element.classList.toggle('active', key === mode);
});
}
function enterMode(mode) {
if (mode === MODE.NONE) return;
toggleCleanVisuals(true);
switch (mode) {
case MODE.EXAM:
break;
case MODE.ANSWER:
// 答案模式:隐藏题目,仅保留答案列表
ensureAnswersVisible();
setTimeout(() => {
if (state.currentMode === MODE.ANSWER) {
buildAnswerOnlyAppendix();
}
}, 400);
break;
case MODE.REFERENCE:
ensureAnswersVisible();
setTimeout(() => {
if (state.currentMode === MODE.REFERENCE) {
addQuestionNumbers();
}
}, 1000);
break;
case MODE.PRACTICE:
clearPracticeArtifacts();
ensureAnswersVisible();
setTimeout(() => {
if (state.currentMode === MODE.PRACTICE) {
addQuestionNumbers();
moveAnswersToAppendix();
}
}, 800);
break;
default:
break;
}
}
function exitMode(mode) {
switch (mode) {
case MODE.EXAM:
toggleCleanVisuals(false);
break;
case MODE.ANSWER:
clearAnswerModeArtifacts();
resetReferenceArtifacts();
toggleCleanVisuals(false);
break;
case MODE.REFERENCE:
toggleCleanVisuals(false);
resetReferenceArtifacts();
break;
case MODE.PRACTICE:
toggleCleanVisuals(false);
clearPracticeArtifacts();
resetReferenceArtifacts();
break;
default:
break;
}
}
function showToast(message, iconSvg, iconColor) {
if (!DOM.toast) return;
const processedSvg = iconSvg
.replace(/fill="[^"]*"/g, '')
.replace('