您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
마우스 오버시 썸네일 표시 + 댓글 자동 확장 + 스크롤 제거
当前为
// ==UserScript== // @name kone 썸네일 + 댓글 개선 // @namespace http://tampermonkey.net/ // @version 3.0 // @description 마우스 오버시 썸네일 표시 + 댓글 자동 확장 + 스크롤 제거 // @author 김머시기 // @match https://kone.gg/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @license MIT // @run-at document-idle // ==/UserScript== (async function () { 'use strict'; // ==== 설정 ==== let thumbSize = await GM_getValue('thumbSize', 400); let autoSlide = await GM_getValue('autoSlide', false); const MenuID = [null, null]; const initializedLinks = new WeakSet(); function updateMenu() { if (MenuID[1]) GM_unregisterMenuCommand(MenuID[1]); MenuID[1] = GM_registerMenuCommand( `자동 슬라이드 : ${autoSlide === false ? '꺼짐' : `${(autoSlide / 1000).toFixed(1)}초`}`, async () => { const states = [false, 1500, 2500, 3500]; let idx = states.indexOf(autoSlide); autoSlide = states[(idx + 1) % states.length]; await GM_setValue('autoSlide', autoSlide); updateMenu(); }, { autoClose: false } ); if (MenuID[0]) GM_unregisterMenuCommand(MenuID[0]); MenuID[0] = GM_registerMenuCommand( `썸네일 크기 : ${thumbSize}px`, async () => { const sizes = [200, 320, 400, 480, 720]; let idx = sizes.indexOf(thumbSize); thumbSize = sizes[(idx + 1) % sizes.length]; await GM_setValue('thumbSize', thumbSize); updateMenu(); }, { autoClose: false } ); } updateMenu(); // ==== 썸네일 박스 ==== const previewBox = document.createElement('div'); const previewImage = document.createElement('img'); const iframe = document.createElement('iframe'); let hoverId = 0; let currentIndex = 0; let imageList = []; let isPreviewVisible = false; let hoverTimer = null; let autoSlideTimer = null; Object.assign(previewBox.style, { position: 'fixed', pointerEvents: 'none', zIndex: 9999, display: 'none', border: '1px solid #ccc', background: '#fff', padding: '4px', boxShadow: '0 0 8px rgba(0,0,0,0.3)', borderRadius: '6px' }); Object.assign(previewImage.style, { width: '100%', height: 'auto', objectFit: 'contain', display: 'block' }); Object.assign(iframe.style, { position: 'fixed', left: '-9999px', width: '1px', height: '1px', visibility: 'hidden' }); previewBox.appendChild(previewImage); document.body.appendChild(previewBox); document.body.appendChild(iframe); function applySize() { previewBox.style.maxWidth = thumbSize + 'px'; previewBox.style.maxHeight = thumbSize + 'px'; previewImage.style.maxWidth = thumbSize + 'px'; previewImage.style.maxHeight = thumbSize + 'px'; } function updateImage() { if (imageList.length > 0) { previewImage.src = imageList[currentIndex]; previewBox.style.display = 'block'; } else { hidePreview(); } } function startAutoSlide() { if (autoSlideTimer) clearInterval(autoSlideTimer); if (typeof autoSlide === 'number' && imageList.length > 1) { autoSlideTimer = setInterval(() => { currentIndex = (currentIndex + 1) % imageList.length; updateImage(); }, autoSlide); } } function stopAutoSlide() { clearInterval(autoSlideTimer); autoSlideTimer = null; } function onKeyDown(e) { if (!isPreviewVisible) return; if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') { e.preventDefault(); if (e.key === 'ArrowRight') { currentIndex = (currentIndex + 1) % imageList.length; } else { currentIndex = (currentIndex - 1 + imageList.length) % imageList.length; } updateImage(); } } function extractImagesFromIframeDocument(doc) { const content = doc.querySelector('.prose'); if (!content) return []; return [...content.querySelectorAll('img')].map(img => img.src).filter(src => ( src && !/kone-logo|default|placeholder|data:image/.test(src) )); } function moveHandler(e) { const padding = 20; const boxW = previewBox.offsetWidth || thumbSize; const boxH = previewBox.offsetHeight || thumbSize; let left = e.clientX + padding; let top = e.clientY + padding; if (left + boxW > window.innerWidth) left = e.clientX - boxW - padding; if (top + boxH > window.innerHeight) top = e.clientY - boxH - padding; previewBox.style.left = `${Math.max(0, left)}px`; previewBox.style.top = `${Math.max(0, top)}px`; } function hidePreview() { previewBox.style.display = 'none'; previewImage.src = ''; iframe.src = 'about:blank'; imageList = []; isPreviewVisible = false; stopAutoSlide(); document.removeEventListener('mousemove', moveHandler); document.removeEventListener('keydown', onKeyDown); } function showPreviewAtMouse(event, url, thisHoverId) { document.addEventListener('mousemove', moveHandler); document.addEventListener('keydown', onKeyDown); moveHandler(event); previewBox.style.display = 'block'; iframe.onload = () => { if (thisHoverId !== hoverId) return; try { const doc = iframe.contentDocument || iframe.contentWindow.document; imageList = extractImagesFromIframeDocument(doc); currentIndex = 0; applySize(); updateImage(); isPreviewVisible = true; startAutoSlide(); } catch (e) { console.error('iframe access error', e); hidePreview(); } }; iframe.src = url; } function handleMouseEnter(event, element, href) { clearTimeout(hoverTimer); const thisHoverId = ++hoverId; hoverTimer = setTimeout(() => { if (thisHoverId !== hoverId) return; const fullUrl = href.startsWith('http') ? href : location.origin + href; showPreviewAtMouse(event, fullUrl, thisHoverId); }, 100); } function attachEvents() { const allLinks = document.querySelectorAll('a[href*="/s/"]'); allLinks.forEach(link => { if (initializedLinks.has(link)) return; initializedLinks.add(link); link.addEventListener('mouseenter', e => handleMouseEnter(e, link, link.getAttribute('href'))); link.addEventListener('mouseleave', () => { clearTimeout(hoverTimer); hidePreview(); }); link.addEventListener('click', hidePreview); }); } new MutationObserver(attachEvents).observe(document.body, { childList: true, subtree: true }); attachEvents(); // ==== 댓글 전체 펼치기 + 감시 ==== function clickAllExpandButtons() { const buttons = document.querySelectorAll('button.group.pointer-events-auto'); let clicked = false; buttons.forEach(btn => { const plus = btn.querySelector('.lucide-circle-plus'); if (plus) { btn.click(); clicked = true; } }); if (clicked) setTimeout(clickAllExpandButtons, 500); } function runCommentFix() { clickAllExpandButtons(); const container = document.querySelector('#comment')?.parentElement; if (container) { const observer = new MutationObserver(() => { clickAllExpandButtons(); }); observer.observe(container, { childList: true, subtree: true }); } } const styleFix = document.createElement('style'); styleFix.textContent = ` .comment-wrapper, .overflow-x-auto, .overflow-hidden { overflow: visible !important; max-height: none !important; } `; document.head.appendChild(styleFix); function observeURLChange() { let lastUrl = location.href; const observer = new MutationObserver(() => { if (location.href !== lastUrl && location.href.includes('/s/')) { lastUrl = location.href; setTimeout(() => { runCommentFix(); attachEvents(); // 썸네일 재연결 }, 1000); } }); observer.observe(document.body, { childList: true, subtree: true }); const originalPush = history.pushState; history.pushState = function () { originalPush.apply(this, arguments); setTimeout(() => { runCommentFix(); attachEvents(); }, 1000); }; window.addEventListener('popstate', () => { setTimeout(() => { runCommentFix(); attachEvents(); }, 1000); }); } setTimeout(() => { runCommentFix(); observeURLChange(); }, 1500); })();