你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
(我已經安裝了使用者樣式管理器,讓我安裝!)
// ==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;
}
}
});
})();