Greasy Fork is available in English.
'/'搜索, '?'清空, 'Esc'退出, 'j/k'滚动, 'c'评论区, 'd'发弹幕
// ==UserScript==
// @name Global Slash Search & Vim Scroll (Pro Max)
// @namespace http://quic.search/
// @version 6.0
// @description '/'搜索, '?'清空, 'Esc'退出, 'j/k'滚动, 'c'评论区, 'd'发弹幕
// @author Min9yu3
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// ==========================================
// 【核心架构】:站点精准定位策略配置
// ==========================================
const siteStrategies = {
'bilibili.com': {
wrapper: 'bili-comments',
getCommentInput: () => {
const commentsEl = document.querySelector('bili-comments');
if (commentsEl && commentsEl.shadowRoot) {
const sendEl = commentsEl.shadowRoot.querySelector('bili-comment-send');
if (sendEl && sendEl.shadowRoot) {
return sendEl.shadowRoot.querySelector('textarea');
}
}
return document.querySelector('.reply-box-textarea, .reply-box textarea');
},
// 新增:B 站专属的弹幕输入框获取策略
getDanmakuInput: () => {
// .bpx-player-dm-input 是点播视频的弹幕框
// .chat-input 是 B 站直播间的弹幕/聊天框
return document.querySelector('.bpx-player-dm-input, .chat-input');
}
},
'youtube.com': {
wrapper: '#comments',
getCommentInput: () => document.querySelector('#contenteditable-root'),
// YouTube 直播间的聊天框
getDanmakuInput: () => document.querySelector('#chat-messages #input')
},
'default': {
wrapper: '#comment, #comments, .comment-wrapper',
getCommentInput: () => document.querySelector('textarea[placeholder*="评论"], textarea[placeholder*="comment" i]'),
// 泛用型弹幕/聊天框匹配
getDanmakuInput: () => document.querySelector('input[placeholder*="弹幕"], input[class*="danmaku" i], .chat-input')
}
};
function getCurrentStrategy() {
const host = window.location.hostname;
for (const site in siteStrategies) {
if (site !== 'default' && host.includes(site)) {
return siteStrategies[site];
}
}
return siteStrategies['default'];
}
document.addEventListener('keydown', function(e) {
const active = document.activeElement;
const isInputActive = active && (
active.tagName === 'INPUT' ||
active.tagName === 'TEXTAREA' ||
active.isContentEditable
);
// 【功能 2】:按 Esc 键退出聚焦,并模拟点击关闭面板
if (isInputActive && e.key === 'Escape') {
setTimeout(() => {
const mousedown = new MouseEvent('mousedown', { bubbles: true, cancelable: true, view: window });
const mouseup = new MouseEvent('mouseup', { bubbles: true, cancelable: true, view: window });
document.body.dispatchEvent(mousedown);
document.body.dispatchEvent(mouseup);
const click = new MouseEvent('click', { bubbles: true, cancelable: true, view: window });
document.body.dispatchEvent(click);
active.blur();
}, 20);
return;
}
if (isInputActive) return;
// 【功能 4】:j / k 网页上下顺滑滚动
if (!e.metaKey && !e.ctrlKey && !e.altKey) {
if (e.key === 'j') {
e.preventDefault();
window.scrollBy({ top: 150, left: 0, behavior: 'smooth' });
return;
}
if (e.key === 'k') {
e.preventDefault();
window.scrollBy({ top: -150, left: 0, behavior: 'smooth' });
return;
}
}
// ==========================================
// 【新增功能 6】:'d' 键直达弹幕输入框 (Danmaku)
// ==========================================
if (e.key === 'c' || e.key === 'C') {
e.preventDefault();
e.stopPropagation();
const strategy = getCurrentStrategy();
if (strategy.getDanmakuInput) {
const danmakuInput = strategy.getDanmakuInput();
if (danmakuInput) {
// 如果你当前正在看底部的评论,按 d 会先平滑滚回上方的播放器区域
// danmakuInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
// 延迟一点聚焦,等待滚动动画完成
setTimeout(() => {
danmakuInput.focus();
}, 3);
} else {
console.log("[Global Slash Search] 当前页面未找到弹幕输入框。");
}
}
return;
}
// 【功能 5】:'c' 键精准定位并聚焦评论区
// 【功能 1 & 3】:'/' 聚焦全选,'?' 聚焦清空
if (e.key === '/' || e.key === '?') {
e.preventDefault();
e.stopPropagation();
const searchSelectors = [
'input[type="search"]', 'input[name="q"]', 'input[name="search"]',
'input[placeholder*="Search" i]', 'input[placeholder*="搜索"]',
'.nav-search-input'
];
let searchInput = null;
for (const selector of searchSelectors) {
const candidates = document.querySelectorAll(selector);
for (const input of candidates) {
if (input.offsetParent !== null && !input.disabled && input.type !== 'hidden') {
searchInput = input;
break;
}
}
if (searchInput) break;
}
if (searchInput) {
searchInput.focus();
if (e.key === '?') {
searchInput.value = '';
} else {
searchInput.select();
}
}
}
}, true);
})();