您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
dcinside 갤러리에서 사용할 수 있는 다양한 단축키를 제공합니다.
当前为
// ==UserScript== // @name dcinside shortcut // @namespace http://tampermonkey.net/ // @version 1.0.3 // @description dcinside 갤러리에서 사용할 수 있는 다양한 단축키를 제공합니다. // - 글 목록에 번호 추가 (1~100번까지 표시) // - 숫자 키 (1~9, 0)로 해당 번호의 글로 이동 (0은 10번 글) // - ` or . + 숫자 입력+ ` or .으로 특정 번호의 글로 이동 // - W: 글쓰기 페이지로 이동 // - C: 댓글 입력창으로 커서 이동 // - D: 댓글 새로고침 및 스크롤 // - R: 페이지 새로고침 // - Q: 페이지 최상단으로 스크롤 // - E: 글 목록으로 스크롤 // - F: 전체글 보기로 이동 // - G: 개념글 보기로 이동 // - A: 이전 페이지로 이동 // - S: 다음 페이지로 이동 // - Z: 이전 글로 이동 // - X: 다음 글로 이동 // @author 노노하꼬 // @match *://gall.dcinside.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=dcinside.com // @grant none // @license MIT // @supportURL https://gallog.dcinside.com/nonohako/guestbook // ==/UserScript== (function() { 'use strict'; // URL 및 갤러리 정보 추출 const url = window.location.href; const galleryType = url.includes('mgallery') ? 'mgallery' : url.includes('mini') ? 'mini' : url.includes('person') ? 'person' : 'board'; const galleryId = (url.match(/id=([^&]+)/) || [])[1] || ''; if (!galleryId) { console.warn('갤러리 ID가 없습니다.'); return; } console.log('Gallery type:', galleryType); // 현재 페이지 번호 및 개념글 모드 확인 const currentPage = parseInt((url.match(/page=(\d+)/) || [])[1]) || 1; const isRecommendMode = url.includes('exception_mode=recommend'); // 기본 URL 구성 let baseUrl = (galleryType === 'board') ? `https://gall.dcinside.com/${galleryType}/lists/?id=${galleryId}` : `https://gall.dcinside.com/${galleryType}/board/lists/?id=${galleryId}`; if (isRecommendMode) { baseUrl += '&exception_mode=recommend'; } // 숫자 입력 모드 관련 변수 let numberInputMode = false, inputBuffer = '', numberInputTimeout = null, inputDisplay = null; // DOM 로드 후 안전하게 페이지 이동 function navigateSafely(url) { const navigate = () => { console.log('페이지 이동:', url); window.location.href = url; }; document.readyState === 'complete' ? navigate() : window.addEventListener('load', navigate, { once: true }); } // 게시글 유효성 검사 함수들 const isBlockedPost = numCell => { const row = numCell.closest('tr'); return row && (row.classList.contains('block-disable') || row.classList.contains('list_trend') || row.style.display === 'none'); }; const isInvalidNumberCell = numCell => { const cleanedText = numCell.innerText.trim().replace(/\[\d+\]\s*|\[\+\d+\]\s*|\[\-\d+\]\s*/, ''); return ['AD', '공지', '설문'].includes(cleanedText) || isNaN(cleanedText); }; const isInvalidTitleCell = titleCell => !!titleCell.querySelector('em.icon_notice'); const isInvalidSubjectCell = subjectCell => { const text = subjectCell.innerText.trim(); return ['AD', '공지', '설문'].some(keyword => text.includes(keyword)); }; const isValidPost = (numCell, titleCell, subjectCell) => { if (!numCell || !titleCell) return false; if (isBlockedPost(numCell) || isInvalidNumberCell(numCell) || isInvalidTitleCell(titleCell)) return false; if (subjectCell && isInvalidSubjectCell(subjectCell)) return false; return true; }; // 유효한 게시글 목록 및 현재 게시글 인덱스 구하기 function getValidPosts() { const rows = document.querySelectorAll('table.gall_list tbody tr'); const validPosts = []; let currentPostIndex = -1; rows.forEach(row => { const numCell = row.querySelector('td.gall_num'); const titleCell = row.querySelector('td.gall_tit'); const subjectCell = row.querySelector('td.gall_subject'); if (!isValidPost(numCell, titleCell, subjectCell)) return; if (numCell.querySelector('.sp_img.crt_icon')) { currentPostIndex = validPosts.length; } const postLink = titleCell.querySelector('a:first-child'); if (postLink) { validPosts.push({ row, link: postLink }); } }); return { validPosts, currentPostIndex }; } // 글 목록에 번호표 추가 function addNumberLabels() { if (document.querySelector('.number-label')) { console.log('번호표가 이미 추가되어 있습니다.'); return; } console.log('글 목록에 번호표 추가 중...'); const allRows = document.querySelectorAll('table.gall_list tbody tr'); const filteredRows = []; allRows.forEach(row => { const numCell = row.querySelector('td.gall_num'); const titleCell = row.querySelector('td.gall_tit'); const subjectCell = row.querySelector('td.gall_subject'); if (!numCell || numCell.querySelector('.number-label') || numCell.querySelector('.sp_img.crt_icon')) return; if (!isValidPost(numCell, titleCell, subjectCell)) return; filteredRows.push(row); }); const { validPosts, currentPostIndex } = getValidPosts(); const maxPosts = Math.min(filteredRows.length, 100); for (let i = 0; i < maxPosts; i++) { const row = filteredRows[i]; const numCell = row.querySelector('td.gall_num'); const originalText = numCell.innerText.trim(); let labelNumber = currentPostIndex !== -1 ? (validPosts.findIndex(post => post.row === row) + 1) : (i + 1); if (!numCell.querySelector('.number-label')) { const labelSpan = document.createElement('span'); labelSpan.className = 'number-label'; labelSpan.style.cssText = 'color: #ff6600; font-weight: bold;'; labelSpan.textContent = `[${labelNumber}] `; numCell.innerHTML = ''; numCell.appendChild(labelSpan); numCell.appendChild(document.createTextNode(originalText)); } } console.log(`${maxPosts}개의 글에 번호표를 추가했습니다.`); } // 숫자 입력 모드 UI 업데이트 function updateInputDisplay(text) { if (!inputDisplay) { inputDisplay = document.createElement('div'); inputDisplay.style.cssText = 'position: fixed; top: 10px; right: 10px; background-color: rgba(0,0,0,0.7); color: white; padding: 10px 15px; border-radius: 5px; font-size: 16px; font-weight: bold; z-index: 9999;'; document.body.appendChild(inputDisplay); } inputDisplay.textContent = text; } function exitNumberInputMode() { numberInputMode = false; inputBuffer = ''; if (numberInputTimeout) { clearTimeout(numberInputTimeout); numberInputTimeout = null; } if (inputDisplay) { document.body.removeChild(inputDisplay); inputDisplay = null; } console.log('숫자 입력 모드 종료'); } function navigateToPost(number) { const { validPosts } = getValidPosts(); const targetIndex = parseInt(number, 10) - 1; console.log(`입력된 숫자: ${number}, 유효한 글 수: ${validPosts.length}`); if (targetIndex >= 0 && targetIndex < validPosts.length) { console.log(`${targetIndex + 1}번 글 클릭:`, validPosts[targetIndex].link.href); validPosts[targetIndex].link.click(); return true; } return false; } // 페이지 로드 시 번호표 추가 if (document.readyState === 'complete') { addNumberLabels(); } else { window.addEventListener('load', addNumberLabels); } // MutationObserver를 통해 동적 변화 감시 (번호표 재추가) function setupMutationObserver(target) { if (!target) return null; const observer = new MutationObserver(() => setTimeout(addNumberLabels, 100)); observer.observe(target, { childList: true, subtree: true, characterData: true }); return observer; } let observer = setupMutationObserver(document.querySelector('table.gall_list tbody')); const bodyObserver = new MutationObserver(() => { if (!document.querySelector('.number-label')) { addNumberLabels(); if (observer) observer.disconnect(); observer = setupMutationObserver(document.querySelector('table.gall_list tbody')); } }); bodyObserver.observe(document.body, { childList: true, subtree: true }); // 키보드 이벤트 처리 (숫자 입력 모드 및 단축키) document.addEventListener('keydown', event => { if (!event || typeof event.key === 'undefined') return; const active = document.activeElement; if (active && (active.tagName === 'TEXTAREA' || active.tagName === 'INPUT' || active.isContentEditable)) return; if (event.ctrlKey || event.altKey || event.shiftKey || event.metaKey) return; // 백틱(`) 또는 마침표(.)로 숫자 입력 모드 전환 if (event.key === '`' || event.key === '.') { event.preventDefault(); if (numberInputMode && inputBuffer.length > 0) { navigateToPost(inputBuffer); exitNumberInputMode(); return; } numberInputMode = true; inputBuffer = ''; if (numberInputTimeout) clearTimeout(numberInputTimeout); numberInputTimeout = setTimeout(exitNumberInputMode, 3000); updateInputDisplay('글 번호 입력: '); console.log('숫자 입력 모드 시작'); return; } // 숫자 입력 모드: 숫자 키 처리 if (numberInputMode && event.key >= '0' && event.key <= '9') { event.preventDefault(); inputBuffer += event.key; updateInputDisplay(`글 번호 입력: ${inputBuffer}`); if (numberInputTimeout) clearTimeout(numberInputTimeout); numberInputTimeout = setTimeout(exitNumberInputMode, 3000); return; } // Enter: 입력 확정, Escape: 취소 if (numberInputMode && event.key === 'Enter' && inputBuffer.length > 0) { event.preventDefault(); navigateToPost(inputBuffer); exitNumberInputMode(); return; } if (numberInputMode && event.key === 'Escape') { event.preventDefault(); exitNumberInputMode(); console.log('숫자 입력 모드 취소'); return; } // 숫자 키 직접 입력 (숫자 입력 모드 아닐 때) if (!numberInputMode && event.key >= '0' && event.key <= '9') { const keyNumber = parseInt(event.key, 10); const targetIndex = keyNumber === 0 ? 9 : keyNumber - 1; const { validPosts } = getValidPosts(); if (targetIndex >= 0 && targetIndex < validPosts.length) { validPosts[targetIndex].link.click(); } return; } // 기타 단축키 처리 switch (event.key.toUpperCase()) { case 'W': { // 글쓰기 const btn = document.querySelector('button#btn_write'); if (btn) btn.click(); break; } case 'C': { // 댓글 입력창으로 이동 event.preventDefault(); const textarea = document.querySelector('textarea[id^="memo_"]'); if (textarea) textarea.focus(); break; } case 'D': { // 댓글 새로고침 event.preventDefault(); const refresh = document.querySelector('button.btn_cmt_refresh'); if (refresh) refresh.click(); break; } case 'R': { // 페이지 새로고침 location.reload(); break; } case 'Q': { // 최상단 스크롤 window.scrollTo(0, 0); break; } case 'E': { // 글 목록으로 스크롤 event.preventDefault(); const list = document.querySelector('table.gall_list'); if (list) list.scrollIntoView({ block: 'start' }); break; } case 'F': { // 전체글 보기 event.preventDefault(); const fullUrl = (galleryType === 'board') ? `https://gall.dcinside.com/${galleryType}/lists/?id=${galleryId}` : `https://gall.dcinside.com/${galleryType}/board/lists/?id=${galleryId}`; navigateSafely(fullUrl); break; } case 'G': { // 개념글 보기 event.preventDefault(); const recUrl = (galleryType === 'board') ? `https://gall.dcinside.com/${galleryType}/lists/?id=${galleryId}&exception_mode=recommend` : `https://gall.dcinside.com/${galleryType}/board/lists/?id=${galleryId}&exception_mode=recommend`; navigateSafely(recUrl); break; } case 'A': { // 이전 페이지 event.preventDefault(); if (currentPage > 1) navigateSafely(`${baseUrl}&page=${currentPage - 1}`); break; } case 'S': { // 다음 페이지 event.preventDefault(); navigateSafely(`${baseUrl}&page=${currentPage + 1}`); break; } case 'Z': { // 이전 글 event.preventDefault(); let prevLink = document.querySelector('a.prev'); if (!prevLink) { const crtIcon = document.querySelector('td.gall_num .sp_img.crt_icon'); if (crtIcon) { let row = crtIcon.closest('tr')?.previousElementSibling; while (row && (row.classList.contains('block-disable') || row.classList.contains('list_trend') || row.style.display === 'none')) { row = row.previousElementSibling; } if (row) prevLink = row.querySelector('td.gall_tit a:first-child'); } } if (prevLink) navigateSafely(prevLink.href); break; } case 'X': { // 다음 글 event.preventDefault(); let nextLink = document.querySelector('a.next'); if (!nextLink) { const crtIcon = document.querySelector('td.gall_num .sp_img.crt_icon'); if (crtIcon) { let row = crtIcon.closest('tr')?.nextElementSibling; while (row && (row.classList.contains('block-disable') || row.classList.contains('list_trend') || row.style.display === 'none')) { row = row.nextElementSibling; } if (row) nextLink = row.querySelector('td.gall_tit a:first-child'); } } if (nextLink) navigateSafely(nextLink.href); break; } } }); })();