dcinside 단축키

dcinside 갤러리에서 사용할 수 있는 다양한 단축키를 제공합니다.

目前为 2025-02-26 提交的版本。查看 最新版本

// ==UserScript==
// @name         dcinside 단축키
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @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() {
    // 현재 URL에서 갤러리 타입(mgallery/mini/board/person)과 갤러리 ID 추출
    const urlParts = window.location.href.split('/');
    const galleryType = urlParts.includes('mgallery') ? 'mgallery' :
    urlParts.includes('mini') ? 'mini' :
    urlParts.includes('person') ? 'person' : 'board';
    const galleryIdMatch = window.location.href.match(/id=([^&]+)/);
    const galleryId = galleryIdMatch ? galleryIdMatch[1] : '';

    if (!galleryId) {
        console.log('갤러리 ID가 없습니다.');
    } else {
        // 갤러리 ID가 있을 때만 실행할 코드
        console.log('Gallery type:', galleryType);

        // 현재 페이지 번호 추출
        const pageMatch = window.location.href.match(/page=(\d+)/);
        const currentPage = pageMatch ? parseInt(pageMatch[1]) : 1;

        // 개념글 모드인지 확인
        const isRecommendMode = window.location.href.includes('exception_mode=recommend');

        // 기본 URL 구성
        let baseUrl = `https://gall.dcinside.com/${galleryType}/board/lists/?id=${galleryId}`;
        if (galleryType === 'board') {
            baseUrl = `https://gall.dcinside.com/${galleryType}/lists/?id=${galleryId}`;
        }
        if (isRecommendMode) {
            baseUrl += '&exception_mode=recommend';
        }

        // 숫자 입력 모드 관련 변수
        let numberInputMode = false;
        let inputBuffer = '';
        let numberInputTimeout = null;
        let inputDisplay = null;

        // 페이지 이동 함수 - DOM이 완전히 로드된 후 이동하도록 보장
        function navigateSafely(url) {
            if (document.readyState === 'complete') {
                console.log('페이지 이동:', url);
                window.location.href = url;
            } else {
                console.log('DOM 로딩 대기 중...');
                window.addEventListener('load', function() {
                    console.log('DOM 로드 완료, 페이지 이동:', url);
                    window.location.href = url;
                }, { once: true });
            }
        }

        // 유효한 게시글인지 확인하는 함수
        function isValidPost(numCell, titleCell, subjectCell) {
            if (!numCell || !titleCell) return false;

            const numText = numCell.innerText.trim();
            const cleanedNumText = numText.replace(/\[\d+\]\s*|\[\+\d+\]\s*|\[\-\d+\]\s*/, '');

            // 1. gall_num 셀에 'AD', '공지', '설문'이 있는 경우 제외
            if (cleanedNumText === 'AD' || cleanedNumText === '공지' || cleanedNumText === '설문') {
                return false;
            }

            // 2. 숫자가 아닌 경우 제외
            if (isNaN(cleanedNumText)) {
                return false;
            }

            // 3. gall_subject 셀에 'AD' 또는 '공지'가 포함된 경우 제외
            if (subjectCell) {
                const subjectText = subjectCell.innerText.trim();
                if (subjectText.includes('AD') || subjectText.includes('공지') || subjectText.includes('설문')) {
                    return false;
                }
            }

            // 4. 말머리에 공지 태그가 있는 경우 제외
            if (titleCell.querySelector('em.icon_notice')) {
                return false;
            }

            return true;
        }

        // 유효한 게시글 목록 가져오기
        function getValidPosts() {
            const allPosts = document.querySelectorAll('table.gall_list tbody tr');
            const validPosts = [];
            let currentPostIndex = -1;

            allPosts.forEach((row, index) => {
                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 allPosts = document.querySelectorAll('table.gall_list tbody tr');
            const filteredPosts = [];

            // 필터링된 글 목록 생성
            allPosts.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) return; // 번호 셀이 없으면 건너뛰기

                // 이미 번호표가 있는지 확인
                if (numCell.querySelector('.number-label')) return;

                // 현재 읽고 있는 글인 경우 제외 (crt_icon이 있는 경우)
                if (numCell.querySelector('.sp_img.crt_icon')) {
                    console.log('현재 읽고 있는 글은 건너뜁니다.');
                    return;
                }

                if (!isValidPost(numCell, titleCell, subjectCell)) return;

                // 모든 필터를 통과한 경우 목록에 추가
                filteredPosts.push(row);
            });

            // 현재 읽고 있는 글의 위치 찾기
            const { validPosts, currentPostIndex } = getValidPosts();

            console.log('현재 글 위치:', currentPostIndex);
            console.log('유효한 글 수:', validPosts.length);

            // 최대 100개까지 번호표 추가
            const maxPosts = Math.min(filteredPosts.length, 100);

            for (let i = 0; i < maxPosts; i++) {
                const row = filteredPosts[i];
                const numCell = row.querySelector('td.gall_num');
                const originalText = numCell.innerText.trim();

                // 현재 글 위치를 기준으로 번호 부여
                let labelNumber;

                // 현재 글이 있는 경우 (글 보기 페이지)
                if (currentPostIndex !== -1) {
                    // 현재 글 기준으로 몇 번째 글인지 계산
                    const rowIndex = validPosts.findIndex(post => post.row === row);
                    if (rowIndex === -1) continue; // 찾을 수 없는 경우 건너뛰기

                    // 현재 글 포함하여 순차적으로 번호 부여 (1부터 시작)
                    labelNumber = rowIndex + 1;
                } else {
                    // 글 목록 페이지인 경우 기존 방식대로 1부터 순차적으로 번호 부여
                    labelNumber = i + 1;
                }

                // 기존 번호표가 있는지 확인하고 없을 때만 추가
                if (!numCell.querySelector('.number-label')) {
                    // 번호표를 별도의 span으로 추가하여 식별 가능하게 함
                    const labelSpan = document.createElement('span');
                    labelSpan.className = 'number-label';
                    labelSpan.style.color = '#ff6600';
                    labelSpan.style.fontWeight = 'bold';

                    // 번호표 텍스트 설정
                    labelSpan.textContent = `[${labelNumber}] `;

                    // 기존 내용을 보존하기 위해 텍스트 노드 생성
                    const textNode = document.createTextNode(originalText);

                    // 셀 내용 초기화 후 새 요소 추가
                    numCell.innerHTML = '';
                    numCell.appendChild(labelSpan);
                    numCell.appendChild(textNode);
                }
            }

            console.log(`${maxPosts}개의 글에 번호표를 추가했습니다.`);
        }

        // 숫자 입력 모드 UI 생성/업데이트
        function updateInputDisplay(text) {
            if (!inputDisplay) {
                inputDisplay = document.createElement('div');
                inputDisplay.style.position = 'fixed';
                inputDisplay.style.top = '10px';
                inputDisplay.style.right = '10px';
                inputDisplay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
                inputDisplay.style.color = 'white';
                inputDisplay.style.padding = '10px 15px';
                inputDisplay.style.borderRadius = '5px';
                inputDisplay.style.fontSize = '16px';
                inputDisplay.style.fontWeight = 'bold';
                inputDisplay.style.zIndex = '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 targetNumber = parseInt(number, 10);
            console.log(`입력된 숫자: ${targetNumber}, 유효한 글 수: ${validPosts.length}`);

            if (targetNumber > 0 && targetNumber <= validPosts.length) {
                console.log(`${targetNumber}번 글 클릭:`, validPosts[targetNumber - 1].link.href);
                validPosts[targetNumber - 1].link.click();
                return true;
            }

            return false;
        }

        // 페이지 로드 완료 시 번호표 추가
        if (document.readyState === 'complete') {
            addNumberLabels();
        } else {
            window.addEventListener('load', addNumberLabels);
        }

        // MutationObserver 설정 함수
        function setupMutationObserver() {
            const observeTarget = document.querySelector('table.gall_list tbody');
            if (observeTarget) {
                console.log('MutationObserver 설정 중...');
                const observer = new MutationObserver(() => {
                    console.log('DOM 변경 감지됨, 번호표 갱신 중...');
                    setTimeout(addNumberLabels, 100);
                });

                observer.observe(observeTarget, {
                    childList: true,
                    subtree: true,
                    characterData: true
                });

                return observer;
            }
            return null;
        }

        // 초기 MutationObserver 설정
        let observer = setupMutationObserver();

        // 페이지 변경 감지를 위한 추가 감시
        const bodyObserver = new MutationObserver(() => {
            // 테이블이 다시 로드되었는지 확인
            if (!document.querySelector('.number-label')) {
                console.log('번호표가 사라짐, 다시 추가 중...');
                addNumberLabels();

                // 기존 observer가 있으면 연결 해제
                if (observer) {
                    observer.disconnect();
                }

                // 새로운 테이블에 observer 다시 연결
                observer = setupMutationObserver();
            }
        });

        // body 전체를 감시하여 부분 새로고침 감지
        bodyObserver.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 키보드 이벤트 처리
        document.addEventListener('keydown', function(event) {
            // 이벤트 객체나 key 속성이 없는 경우 처리 중단
            if (!event || typeof event.key === 'undefined') {
                console.log('유효하지 않은 키 이벤트:', event);
                return;
            }

            // 댓글 작성 중이나 글 작성 중에는 단축키 비활성화
            if (document.activeElement.tagName === 'TEXTAREA' ||
                document.activeElement.tagName === 'INPUT' ||
                document.activeElement.isContentEditable) {
                return;
            }

            // Ctrl, Alt, Shift 키가 함께 눌렸을 때는 단축키 기능 비활성화
            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);
                }

                // 3초 후 숫자 입력 모드 종료
                numberInputTimeout = setTimeout(exitNumberInputMode, 3000);

                // 입력 표시 UI 생성
                updateInputDisplay('글 번호 입력: ');

                console.log('숫자 입력 모드 시작');
                return;
            }

            // 숫자 입력 모드에서 숫자 키 입력 처리
            if (numberInputMode && event.key >= '0' && event.key <= '9') {
                event.preventDefault();
                inputBuffer += event.key;
                console.log('입력된 숫자:', inputBuffer);

                // 입력 표시 UI 업데이트
                updateInputDisplay(`글 번호 입력: ${inputBuffer}`);

                // 이전 타이머 재설정
                if (numberInputTimeout) {
                    clearTimeout(numberInputTimeout);
                }

                // 3초 후 숫자 입력 모드 종료
                numberInputTimeout = setTimeout(exitNumberInputMode, 3000);

                return;
            }

            // Enter 키로 숫자 입력 확정
            if (numberInputMode && event.key === 'Enter' && inputBuffer.length > 0) {
                event.preventDefault();
                navigateToPost(inputBuffer);
                exitNumberInputMode();
                return;
            }

            // 숫자 입력 모드에서 ESC 키로 취소
            if (numberInputMode && event.key === 'Escape') {
                event.preventDefault();
                exitNumberInputMode();
                console.log('숫자 입력 모드 취소');
                return;
            }

            // 숫자 입력 모드가 아닐 때 0-9 키를 눌렀을 때 (1-9는 해당 번호의 글로, 0은 10번 글로 이동)
            if (!numberInputMode && event.key >= '0' && event.key <= '9') {
                const keyNumber = parseInt(event.key, 10);
                const targetIndex = keyNumber === 0 ? 9 : keyNumber - 1; // 0 키는 10번으로 처리

                const { validPosts } = getValidPosts();

                // 유효한 범위인지 체크
                if (targetIndex >= 0 && targetIndex < validPosts.length) {
                    const displayNumber = keyNumber === 0 ? 10 : keyNumber;
                    console.log(`${displayNumber}번 글 클릭:`, validPosts[targetIndex].link.href);
                    validPosts[targetIndex].link.click();
                }
            }

            // 기타 단축키 처리
            if (event.key) {
                switch (event.key.toUpperCase()) {
                    case 'W': // 글쓰기
                        const writeBtn = document.querySelector('button#btn_write');
                        if (writeBtn) writeBtn.click();
                        break;

                    case 'C': // 댓글로 커서 이동
                        event.preventDefault();
                        const replyTextarea = document.querySelector('textarea[id^="memo_"]');
                        if (replyTextarea) replyTextarea.focus();
                        break;

                    case 'D': // 새로고침 버튼 클릭
                        event.preventDefault();
                        const refreshBtn = document.querySelector('button.btn_cmt_refresh');
                        if (refreshBtn) refreshBtn.click();
                        break;

                    case 'R': // 페이지 새로고침
                        location.reload();
                        break;

                    case 'Q': // 페이지 최상단으로 이동
                        window.scrollTo(0, 0);
                        break;

                    case 'E': // 글 목록으로 즉각적 스크롤 이동
                        event.preventDefault();
                        const gallList = document.querySelector('table.gall_list');
                        if (gallList) gallList.scrollIntoView({ block: 'start' });
                        break;

                    case 'F': // 전체글로 이동
                        event.preventDefault();
                        const fullListUrl = galleryType === 'board' ?
                              `https://gall.dcinside.com/${galleryType}/lists/?id=${galleryId}` :
                        `https://gall.dcinside.com/${galleryType}/board/lists/?id=${galleryId}`;
                        navigateSafely(fullListUrl);
                        break;

                    case 'G': // 개념글로 이동
                        event.preventDefault();
                        const recommendUrl = 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(recommendUrl);
                        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();
                        const prevPost = document.querySelector('a.prev');
                        if (!prevPost) {
                            // 현재 글 찾기
                            const currentPost = document.querySelector('td.gall_num .sp_img.crt_icon');
                            if (currentPost) {
                                const currentRow = currentPost.closest('tr');
                                if (currentRow && currentRow.previousElementSibling) {
                                    const prevLink = currentRow.previousElementSibling.querySelector('td.gall_tit a:first-child');
                                    if (prevLink) {
                                        navigateSafely(prevLink.href);
                                        return;
                                    }
                                }
                            }
                        } else {
                            navigateSafely(prevPost.href);
                        }
                        break;

                    case 'X': // 다음 글로 이동
                        event.preventDefault();
                        const nextPost = document.querySelector('a.next');
                        if (!nextPost) {
                            // 현재 글 찾기
                            const currentPost = document.querySelector('td.gall_num .sp_img.crt_icon');
                            if (currentPost) {
                                const currentRow = currentPost.closest('tr');
                                if (currentRow && currentRow.nextElementSibling) {
                                    const nextLink = currentRow.nextElementSibling.querySelector('td.gall_tit a:first-child');
                                    if (nextLink) {
                                        navigateSafely(nextLink.href);
                                        return;
                                    }
                                }
                            }
                        } else {
                            navigateSafely(nextPost.href);
                        }
                        break;
                }
            }
        });
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址