您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
기출넷(rlcnf.net)에서 문제플이/속성암기 기능 이용 시 단축키와 편의기능을 사용할 수 있게 해주는 스크립트입니다.
// ==UserScript== // @name 기출넷플러스 // @namespace http://tampermonkey.net/ // @version 1.2.3 // @description 기출넷(rlcnf.net)에서 문제플이/속성암기 기능 이용 시 단축키와 편의기능을 사용할 수 있게 해주는 스크립트입니다. // @author enc2586, Claude Code, Google Gemini // @match https://rlcnf.net/bbs/board.php?bo_table=* // @match http://rlcnf.net/bbs/board.php?bo_table=* // @match https://rlcnf.net/bbs/board.php* // @match http://rlcnf.net/bbs/board.php* // @grant none // @run-at document-idle // @license MIT // ==/UserScript== (function () { "use strict"; let lastClickTime = 0; let lastBackTime = 0; const CLICK_COOLDOWN = 500; const BACK_COOLDOWN = 1000; let autoExplanationEnabled = true; let sideBySideEnabled = false; let autoScrollEnabled = true; const customCSS = ` .kb-icon { display: inline-block; background-color: transparent; border: 1px solid #888; color: #555; box-shadow: none; padding: 1px 6px; border-radius: 4px; font-size: 12px; font-weight: 500; font-family: 'Arial', sans-serif; margin-left: 9px; vertical-align: middle; position: relative; top: -1px; } .btn-container-flex { display: flex; justify-content: center; align-items: center; gap: 15px; } .auto-explanation-popup { position: fixed; bottom: 20px; right: 20px; background: white; border: 1px solid #e2e8f0; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); padding: 12px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; z-index: 10000; width: 80px; max-height: 40px; overflow: hidden; transition: width 0.3s ease-out, max-height 0.3s ease-out; } #go-btn { bottom: 70px !important; } .auto-explanation-popup:hover { width: 220px; max-height: 300px; } .popup-tab { display: flex; align-items: center; gap: 8px; padding: 8px; margin: -12px -12px 8px -12px; background: #f8fafc; border-radius: 8px 8px 0 0; border-bottom: 1px solid #e2e8f0; cursor: pointer; position: relative; z-index: 1; } .popup-tab-icon { font-size: 16px; } .popup-tab-text { font-size: 12px; font-weight: 500; color: #64748b; } .auto-explanation-toggle { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .toggle-switch { position: relative; width: 36px; height: 20px; background: #e5e7eb; border-radius: 10px; cursor: pointer; transition: background-color 0.2s; } .toggle-switch.active { background: #374151; } .toggle-slider { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; background: white; border-radius: 50%; transition: transform 0.2s; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); } .toggle-switch.active .toggle-slider { transform: translateX(16px); } .toggle-label { color: #374151; font-size: 13px; font-weight: 500; white-space: nowrap; } .side-by-side-container { display: flex !important; align-items: flex-start; position: relative; } .side-by-side-container .view-content { flex: 0 0 30%; min-width: 0; } .side-by-side-container #explanation-section-parents { flex: 0 0 70%; min-width: 0; margin-top: 0 !important; } .resize-handle { width: 8px; background: #e5e7eb; cursor: col-resize; position: absolute; top: 0; bottom: 0; left: calc(30% - 4px); z-index: 10; opacity: 0; transition: opacity 1s ease-out, background-color 0.2s; } .resize-handle.show { opacity: 1; } .resize-handle:hover { background: #9ca3af; opacity: 1 !important; transition: opacity 0.1s ease-in, background-color 0.2s; } .resize-handle::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 3px; height: 30px; background: #6b7280; border-radius: 2px; } .side-by-side-enabled .col-md-offset-2 { margin-left: 0 !important; } .side-by-side-enabled .col-md-8 { width: 100% !important; } .toggle-group { display: flex; flex-direction: column; gap: 8px; } .toggle-item { display: flex; align-items: center; justify-content: space-between; gap: 12px; } html { scroll-behavior: smooth; } `; function addGlobalStyle(css) { const head = document.head || document.getElementsByTagName("head")[0]; if (head) { const style = document.createElement("style"); style.type = "text/css"; style.innerHTML = css; head.appendChild(style); } } function setupUI() { const nextButton = document.querySelector("#scoring"); if (!nextButton) return; const container = nextButton.parentElement; if (!container || !container.classList.contains("text-center")) return; container.classList.add("btn-container-flex"); const nIcon = document.createElement("span"); nIcon.className = "kb-icon"; nIcon.textContent = "R"; nextButton.append(nIcon); const bIcon = document.createElement("span"); bIcon.className = "kb-icon"; bIcon.textContent = "E"; const prevButton = document.createElement("span"); prevButton.id = "prev-problem-btn"; const classesToCopy = Array.from(nextButton.classList).filter( (cls) => cls !== "trans-bg-crimson" ); prevButton.className = classesToCopy.join(" "); prevButton.style.cursor = "pointer"; prevButton.style.setProperty( "background-color", "transparent", "important" ); prevButton.style.setProperty("color", "#000", "important"); prevButton.style.setProperty("border", "none", "important"); prevButton.style.setProperty("opacity", "1", "important"); prevButton.textContent = "뒤로가기"; prevButton.append(bIcon); prevButton.addEventListener("click", () => { const currentTime = Date.now(); if (currentTime - lastBackTime >= BACK_COOLDOWN) { window.history.back(); lastBackTime = currentTime; } }); container.insertBefore(prevButton, nextButton); } function checkAndClickExplanationButton() { if (!autoExplanationEnabled) return; const explanationButton = document.querySelector("#more-explanation-btn"); if (explanationButton && isElementVisible(explanationButton)) { explanationButton.click(); } } function createResizeHandle() { const handle = document.createElement("div"); handle.className = "resize-handle show"; let isResizing = false; let startX = 0; let startLeftPercent = 30; let hideTimeout; // 초기 표시 후 2초 뒤 숨김 setTimeout(() => { handle.classList.remove("show"); }, 2000); handle.addEventListener("mouseenter", () => { clearTimeout(hideTimeout); handle.classList.add("show"); }); handle.addEventListener("mouseleave", () => { if (!isResizing) { hideTimeout = setTimeout(() => { handle.classList.remove("show"); }, 2000); } }); function updateHandlePosition(leftPercent) { handle.style.left = `calc(${leftPercent}% - 4px)`; } handle.addEventListener("mousedown", (e) => { isResizing = true; startX = e.clientX; const container = handle.parentElement; const leftPanel = container.querySelector(".view-content"); const containerWidth = container.offsetWidth; startLeftPercent = (leftPanel.offsetWidth / containerWidth) * 100; document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none"; e.preventDefault(); }); function handleMouseMove(e) { if (!isResizing) return; const container = handle.parentElement; const leftPanel = container.querySelector(".view-content"); const rightPanel = container.querySelector( "#explanation-section-parents" ); const deltaX = e.clientX - startX; const containerWidth = container.offsetWidth; const deltaPercent = (deltaX / containerWidth) * 100; const newLeftPercent = startLeftPercent + deltaPercent; const newRightPercent = 100 - newLeftPercent; // 최소 20%, 최대 80% 제한 if (newLeftPercent >= 20 && newLeftPercent <= 80) { leftPanel.style.flex = `0 0 ${newLeftPercent}%`; rightPanel.style.flex = `0 0 ${newRightPercent}%`; updateHandlePosition(newLeftPercent); } } function handleMouseUp() { isResizing = false; document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); document.body.style.cursor = ""; document.body.style.userSelect = ""; // 드래그 완료 후 2초 뒤 숨김 hideTimeout = setTimeout(() => { handle.classList.remove("show"); }, 2000); } return handle; } function toggleSideBySideLayout() { const viewContent = document.querySelector(".view-content"); const explanationSection = document.querySelector( "#explanation-section-parents" ); const body = document.body; if (!viewContent || !explanationSection) return; let container = document.querySelector(".side-by-side-container"); if (sideBySideEnabled) { if (!container) { container = document.createElement("div"); container.className = "side-by-side-container"; const parent = viewContent.parentNode; parent.insertBefore(container, viewContent); container.appendChild(viewContent); container.appendChild(explanationSection); const resizeHandle = createResizeHandle(); container.appendChild(resizeHandle); } body.classList.add("side-by-side-enabled"); } else { if (container) { const parent = container.parentNode; parent.insertBefore(viewContent, container); parent.insertBefore(explanationSection, container); container.remove(); } body.classList.remove("side-by-side-enabled"); } } function autoScrollToArticle() { if (!autoScrollEnabled) return; const urlParams = new URLSearchParams(window.location.search); if (!urlParams.has("wr_id")) return; const article = document.querySelector("article.exam"); if (article) { article.scrollIntoView({ behavior: "smooth", block: "start" }); } } function createTogglePopup() { const popup = document.createElement("div"); popup.className = "auto-explanation-popup"; popup.innerHTML = ` <div class="popup-tab"> <span class="popup-tab-icon">⚙️</span> <span class="popup-tab-text">설정</span> </div> <div class="toggle-group"> <div class="toggle-item"> <span class="toggle-label">자동 상세 풀이 보기</span> <div class="toggle-switch ${ autoExplanationEnabled ? "active" : "" }" id="explanation-toggle"> <div class="toggle-slider"></div> </div> </div> <div class="toggle-item"> <span class="toggle-label">좌우 나란히 보기</span> <div class="toggle-switch ${ sideBySideEnabled ? "active" : "" }" id="sidebyside-toggle"> <div class="toggle-slider"></div> </div> </div> <div class="toggle-item"> <span class="toggle-label">문제 영역으로 자동 스크롤</span> <div class="toggle-switch ${ autoScrollEnabled ? "active" : "" }" id="autoscroll-toggle"> <div class="toggle-slider"></div> </div> </div> </div> `; const explanationToggle = popup.querySelector("#explanation-toggle"); explanationToggle.addEventListener("click", () => { autoExplanationEnabled = !autoExplanationEnabled; explanationToggle.classList.toggle("active", autoExplanationEnabled); localStorage.setItem("autoExplanationEnabled", autoExplanationEnabled); }); const sideBySideToggle = popup.querySelector("#sidebyside-toggle"); sideBySideToggle.addEventListener("click", () => { sideBySideEnabled = !sideBySideEnabled; sideBySideToggle.classList.toggle("active", sideBySideEnabled); localStorage.setItem("sideBySideEnabled", sideBySideEnabled); toggleSideBySideLayout(); }); const autoScrollToggle = popup.querySelector("#autoscroll-toggle"); autoScrollToggle.addEventListener("click", () => { autoScrollEnabled = !autoScrollEnabled; autoScrollToggle.classList.toggle("active", autoScrollEnabled); localStorage.setItem("autoScrollEnabled", autoScrollEnabled); }); document.body.appendChild(popup); } function initAutoExplanation() { const savedExplanation = localStorage.getItem("autoExplanationEnabled"); if (savedExplanation !== null) { autoExplanationEnabled = savedExplanation === "true"; } const savedSideBySide = localStorage.getItem("sideBySideEnabled"); if (savedSideBySide !== null) { sideBySideEnabled = savedSideBySide === "true"; } const savedAutoScroll = localStorage.getItem("autoScrollEnabled"); if (savedAutoScroll !== null) { autoScrollEnabled = savedAutoScroll === "true"; } createTogglePopup(); toggleSideBySideLayout(); setTimeout(() => { autoScrollToArticle(); }, 100); const observer = new MutationObserver(() => { checkAndClickExplanationButton(); // 좌우 나란히 보기가 활성화되어 있고 아직 적용되지 않았다면 다시 시도 if ( sideBySideEnabled && !document.querySelector(".side-by-side-container") ) { toggleSideBySideLayout(); } }); observer.observe(document.body, { childList: true, subtree: true, }); checkAndClickExplanationButton(); } console.log(` 키보드 단축키가 로드되었습니다: - R: 다음문제 - E: 뒤로가기 `); function isInInputField() { const activeElement = document.activeElement; if (!activeElement) return false; const tagName = activeElement.tagName.toUpperCase(); const isContentEditable = activeElement.isContentEditable || activeElement.getAttribute("contentEditable") === "true"; return ( ["INPUT", "TEXTAREA", "SELECT"].includes(tagName) || isContentEditable ); } function isModalOpen() { const modalSelectors = [".modal", ".popup", ".dialog", '[role="dialog"]']; return modalSelectors.some((selector) => { const element = document.querySelector(selector); return element && (element.offsetWidth > 0 || element.offsetHeight > 0); }); } function isElementVisible(el) { if (!el) return false; const style = window.getComputedStyle(el); return !( el.offsetParent === null || style.display === "none" || style.visibility === "hidden" ); } document.addEventListener("keydown", function (event) { if (event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) return; if (isInInputField()) return; if (isModalOpen()) return; const currentTime = Date.now(); let actionTaken = false; switch (event.key) { case "r": case "R": case "ㄱ": if (currentTime - lastClickTime < CLICK_COOLDOWN) break; const scoringButton = document.querySelector("#scoring"); if (!scoringButton) { break; } if (!isElementVisible(scoringButton)) { break; } scoringButton.click(); lastClickTime = currentTime; actionTaken = true; break; case "e": case "E": case "ㄷ": if (currentTime - lastBackTime < BACK_COOLDOWN) break; window.history.back(); lastBackTime = currentTime; actionTaken = true; break; } if (actionTaken) { event.preventDefault(); event.stopPropagation(); } }); addGlobalStyle(customCSS); setupUI(); initAutoExplanation(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址