Greasy Fork

来自缓存

Greasy Fork is available in English.

Global Slash Search & Vim Scroll (Pro Max)

'/'搜索, '?'清空, 'Esc'退出, 'j/k'滚动, 'c'评论区, 'd'发弹幕

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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); 
})();