// ==UserScript==
// @name Zod.kr 편의성 스크립트
// @namespace http://tampermonkey.net/
// @version 1.33
// @description 서명 확장 등 편의 기능 포함, 단축키로 페이지 이동하기 등
// @match https://zod.kr/*
// @match https://*.zod.kr/*
// @grant unsafeWindow
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 1) jQuery 로드 대기
function waitForjQuery(callback) {
if (typeof unsafeWindow.jQuery === 'undefined') {
setTimeout(function() {
waitForjQuery(callback);
}, 100);
} else {
callback(unsafeWindow.jQuery);
}
}
function main($) {
$(document).ready(function() {
// ---------------------------------
// 0. 서명 확장/축소
// ---------------------------------
let signaturesExpanded = false;
let expandButtons = [];
$('.app-article-signature__profile-body').each(function() {
var signature = $(this);
var contentDiv = signature.find('div[style*="max-height:100px"]');
if (contentDiv.length) {
const expandButton = $('<button>서명 확장</button>');
expandButton.css({
position: 'relative',
backgroundColor: '#3F9DFF',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
padding: '5px 10px',
marginTop: '10px',
zIndex: '1000'
});
expandButton.on('click', function() {
if (signaturesExpanded) {
contentDiv.css({
'max-height': '100px',
'height': '',
'overflow': 'hidden'
});
expandButton.text('서명 확장');
signaturesExpanded = false;
} else {
contentDiv.css({
'max-height': '100%',
'height': 'auto',
'overflow': 'visible'
});
expandButton.text('서명 축소');
signaturesExpanded = true;
}
});
signature.append(expandButton);
expandButtons.push({ button: expandButton, contentDiv: contentDiv });
}
});
// ---------------------------------
// 1. 여러 단축키(이동)
// ---------------------------------
let keyDownTimes = {};
const navigationKeys = {
'z': 'https://zod.kr/', // ZOD 메인화면
'a': 'https://zod.kr/all', // 전체글보기
'n': 'https://zod.kr/news_all', // 뉴스 모아보기 게시판
'r': 'https://zod.kr/review', // 리뷰 모아보기 게시판
'b': 'https://zod.kr/benchmark', // 리뷰 > 벤치마크 게시판
'c': 'https://zod.kr/community', // 커뮤니티 모아보기 게시판
'f': 'https://zod.kr/free', // 커뮤니티 > 자유게시판
'g': 'https://zod.kr/game', // 커뮤니티 > 게임게시판
'h': 'https://zod.kr/hardware', // PC하드웨어 모아보기 게시판
'1': 'https://zod.kr/cpu', // PC하드웨어 > CPU / 메인보드 / 램
'2': 'https://zod.kr/gpu', // PC하드웨어 > 그래픽카드
'3': 'https://zod.kr/case', // PC하드웨어 > 케이스 / 쿨링
'4': 'https://zod.kr/ssd', // PC하드웨어 > 저장장치
'5': 'https://zod.kr/psu', // PC하드웨어 > 파워서플라이
'6': 'https://zod.kr/display', // PC하드웨어 > 디스플레이
'7': 'https://zod.kr/keyma', // PC하드웨어 > 키보드 / 마우스
'8': 'https://zod.kr/audio', // PC하드웨어 > 오디오
'9': 'https://zod.kr/general', // PC하드웨어 > PC 일반
'0': 'https://zod.kr/pcbuild', // PC하드웨어 > 조립 / 견적
'm': 'https://zod.kr/device', // 모바일 모아보기 게시판
't': 'https://zod.kr/all_tips', // 정보 모아보기 게시판
'u': 'https://zod.kr/user_review', // 정보 > 유저리뷰 게시판
'd': 'https://zod.kr/deal', // 특가 모아보기 게시판
'q': 'https://zod.kr/qna', // 문의/버그신고 게시판
'`': 'https://zod.kr/member/notifications', // 내 알림 목록 보기
'x': 'https://zod.kr/notice', // 공지사항 게시판
'\\': 'https://zod.kr/popular'
};
// ---------------------------------
// 2. 검색창 관련
// ---------------------------------
// 2-1) (모바일) S 로 여는 검색
const mobileSearchBtn = document.querySelector('.app-board-container--only-mobile .app-icon-button');
const MOBILE_SEARCH_INPUT_SELECTOR = 'input[name="search_keyword"].app-input.app-input-expand';
// 2-2) (오버레이) Alt+S / Ctrl+Alt+S 로 여는 검색
const overlaySearchToggleBtn = document.querySelector('a.app-header-item.app-icon-button.app-icon-button-gray.app-search-toggle');
const OVERLAY_SEARCH_INPUT_SELECTOR = 'input.app-search-form__input[name="search_keyword"]';
// ---------------------------------
// 3. keydown 핸들러
// ---------------------------------
$(document).on('keydown', function(e) {
const key = e.key.toLowerCase();
// (A) 입력창 포커스 중인 경우 => ESC 나 Alt+S 만 예외 통과
if ($(':focus').is('input, textarea, [contenteditable="true"]')) {
// 1) ESC 키
if (e.key === 'Escape') {
// 여기서 검색창 닫기 로직 등 처리
const closeButtonSmall = document.querySelector('.app-dialog-close');
if (closeButtonSmall && closeButtonSmall.offsetHeight > 0) {
closeButtonSmall.click();
return;
}
if (overlaySearchToggleBtn) {
overlaySearchToggleBtn.click();
setTimeout(() => {
const overlaySearchInput = document.querySelector(OVERLAY_SEARCH_INPUT_SELECTOR);
if (overlaySearchInput) {
overlaySearchInput.focus();
}
}, 100);
}
return;
}
// 2) Alt+S 키
if (key === 's' && e.altKey) {
e.preventDefault();
if (overlaySearchToggleBtn) {
overlaySearchToggleBtn.click();
setTimeout(() => {
const overlaySearchInput = document.querySelector(OVERLAY_SEARCH_INPUT_SELECTOR);
if (overlaySearchInput) {
overlaySearchInput.focus();
}
}, 100);
}
return;
}
// 그 외에는 단축키 동작 막고 그냥 return
return;
}
// (B) ESC - 이제 "열려 있다면 무조건 닫기" 방식
if (e.key === 'Escape') {
// -- 1) 'S'로 열렸을 때 닫기 (모바일 검색창)
const closeButtonSmall = document.querySelector('.app-dialog-close');
if (closeButtonSmall && closeButtonSmall.offsetHeight > 0) {
closeButtonSmall.click();
return;
}
// -- 2) 'Alt+S'/'Ctrl+Alt+S' 로 열렸을 때 닫기 (전체 검색창)
if (overlaySearchToggleBtn) {
overlaySearchToggleBtn.click();
// 토글 열림 후 포커스
setTimeout(() => {
const overlaySearchInput = document.querySelector(OVERLAY_SEARCH_INPUT_SELECTOR);
if (overlaySearchInput) {
overlaySearchInput.focus();
}
}, 100);
}
// (그 외 열려있는 것이 없다면 그냥 끝)
return;
}
// (C) Alt+S / Ctrl+Alt+S => 통합검색
if (key === 's' && e.altKey) {
e.preventDefault(); // 충돌 방지
if (overlaySearchToggleBtn) {
overlaySearchToggleBtn.click();
setTimeout(() => {
const overlaySearchInput = document.querySelector(OVERLAY_SEARCH_INPUT_SELECTOR);
if (overlaySearchInput) {
// ★ 여기서 검색어 지우기
overlaySearchInput.value = '';
overlaySearchInput.focus();
}
}, 100);
}
return;
}
// (D) 'e'로 서명 전체 확장/축소 (altKey/ctrlKey 없을때)
if (!e.altKey && !e.ctrlKey && key === 'e') {
signaturesExpanded = !signaturesExpanded;
expandButtons.forEach(function(item) {
if (signaturesExpanded) {
item.contentDiv.css({
'max-height': '100%',
'height': 'auto',
'overflow': 'visible'
});
item.button.text('서명 축소');
} else {
item.contentDiv.css({
'max-height': '100px',
'height': '',
'overflow': 'hidden'
});
item.button.text('서명 확장');
}
});
}
// (E) 's' 키 => 모바일 검색 열기 + 포커스
else if (!e.altKey && !e.ctrlKey && key === 's') {
if (mobileSearchBtn) {
mobileSearchBtn.click();
setTimeout(() => {
const mobileSearchInput = document.querySelector(MOBILE_SEARCH_INPUT_SELECTOR);
if (mobileSearchInput) {
// ★ 여기서 검색어 지우기
mobileSearchInput.value = '';
mobileSearchInput.focus();
}
}, 100);
}
}
// (F) 그 외 지정된 단축키
else if (!e.altKey && !e.ctrlKey) {
if (navigationKeys.hasOwnProperty(key)) {
if (!keyDownTimes[key]) {
keyDownTimes[key] = Date.now();
}
}
}
});
// ---------------------------------
// 4. keyup 핸들러
// ---------------------------------
$(document).on('keyup', function(e) {
// 입력 중이면 패스
if ($(':focus').is('input, textarea, [contenteditable="true"]')) {
return;
}
// altKey, ctrlKey 눌린 상태도 패스
if (e.altKey || e.ctrlKey) {
return;
}
// 길게 누른 단축키 이동
const key = e.key.toLowerCase();
if (navigationKeys.hasOwnProperty(key) && keyDownTimes[key]) {
let duration = Date.now() - keyDownTimes[key];
if (duration >= 80) {
window.location.href = navigationKeys[key];
}
delete keyDownTimes[key];
}
});
});
// ---------------------------------
// 5. Alt+Enter, Ctrl+Enter, Alt+Ctrl+Enter => 등록 / 추천+등록
// ---------------------------------
function addAltEnterFeature() {
function addAltEnterListener(textarea) {
if (textarea.dataset.altEnterListenerAdded === 'true') {
return;
}
textarea.dataset.altEnterListenerAdded = 'true';
textarea.addEventListener('keydown', function(event) {
if (
(event.key === 'Enter' || event.keyCode === 13) &&
(event.altKey || event.ctrlKey)
) {
event.preventDefault();
var form = textarea.closest('form');
if (form) {
var submitButtons = form.querySelectorAll('button[type="submit"]');
var targetButton = null;
if (event.altKey && event.ctrlKey) {
// Alt+Ctrl+Enter => "추천+등록"
submitButtons.forEach(function(button) {
if (button.textContent.trim() === '추천+등록') {
targetButton = button;
}
});
} else {
// Alt+Enter 또는 Ctrl+Enter => "등록"
submitButtons.forEach(function(button) {
if (button.textContent.trim() === '등록') {
targetButton = button;
}
});
}
if (targetButton) {
targetButton.click();
setTimeout(() => {
textarea.blur();
document.activeElement.blur();
}, 100);
}
}
}
});
}
// 초기 로드된 textarea 처리
var textareas = document.querySelectorAll('textarea.app-textarea');
textareas.forEach(function(textarea) {
addAltEnterListener(textarea);
});
// 동적으로 추가되는 textarea도 처리
var altEnterObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1) {
if (node.matches('textarea.app-textarea')) {
addAltEnterListener(node);
} else {
var innerTextareas = node.querySelectorAll('textarea.app-textarea');
innerTextareas.forEach(function(textarea) {
addAltEnterListener(textarea);
});
}
}
});
});
});
altEnterObserver.observe(document.body, { childList: true, subtree: true });
}
addAltEnterFeature();
}
waitForjQuery(function($) {
main($);
});
})();