幕布mubu搜索历史记录前进后退

1.1

// ==UserScript==
// @name         幕布mubu搜索历史记录前进后退
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  1.1
// @author       YourName
// @match        https://mubu.com/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        .custom-search-container {
            position: fixed;
            top: 1px;
            left: 50%;
            transform: translateX(-50%);
            z-index: 9999;
            background: white;
            padding: 6px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            display: flex;
            gap: 8px;
            align-items: center;
            will-change: transform;
        }
        .custom-search-input {
            padding: 8px 12px;
            border: 1px solid #e0e0e0;
            border-radius: 20px;
            width: 300px;
            font-size: 14px;
            transition: border-color 0.3s, box-shadow 0.3s;
            background: #f8f8f8;
        }
        .custom-search-input:focus {
            border-color: #5856d5;
            outline: none;
            background: white;
            box-shadow: 0 0 8px rgba(64,158,255,0.2);
        }
        .history-btn {
            padding: 6px 12px;
            background: #f0f0f0;
            border: 1px solid #e0e0e0;
            border-radius: 20px;
            cursor: pointer;
            transition: all 0.2s;
            font-weight: 500;
            color: #666;
        }
        .history-btn:hover {
            background: #5856d5;
            color: white;
            border-color: #5856d5;
            transform: scale(1.05);
        }
        .history-btn:active {
            transform: scale(0.95);
        }
    `);

    const config = {
        historySize: 64,
        mask: 0x3F,
        debounceTime: 40,
        mutationDebounce: 150,
        cacheTTL: 2000,
        observerConfig: {
            valueObserver: {
                attributeFilter: ['value'],
                attributeOldValue: true,
                subtree: true
            },
            domObserver: {
                childList: true,
                subtree: true,
                attributes: false,
                characterData: false
            }
        }
    };

    const cache = {
        inputElement: null,
        lastCacheTime: 0,
        get valid() {
            return performance.now() - this.lastCacheTime < config.cacheTTL
        }
    };

    const optimizedFindSearchBox = (() => {
        let observer;
        const selector = 'input[placeholder="搜索关键词"]:not([disabled])';

        const updateCache = (target) => {
            if (!target || !target.matches(selector)) return;
            if (cache.inputElement === target) return;

            cache.inputElement = target;
            cache.lastCacheTime = performance.now();
        };

        const initObserver = () => {
            observer = new MutationObserver(mutations => {
                for (const mutation of mutations) {
                    if (mutation.addedNodes) {
                        for (const node of mutation.addedNodes) {
                            if (node.nodeType === Node.ELEMENT_NODE) {
                                const found = node.matches(selector) ? node : node.querySelector(selector);
                                if (found) updateCache(found);
                            }
                        }
                    }
                }
            });

            observer.observe(document.documentElement, {
                childList: true,
                subtree: true,
                attributes: false,
                characterData: false
            });
        };

        return {
            get: () => {
                if (!observer) initObserver();


                if (!cache.valid || !cache.inputElement?.isConnected) {
                    const freshElement = document.querySelector(selector);
                    if (freshElement) updateCache(freshElement);
                }
                return cache.inputElement;
            },
            disconnect: () => observer?.disconnect()
        };
    })();

    const historyManager = (() => {
        const buffer = new Array(config.historySize);
        let writePtr = 0;
        let count = 0;
        let precomputedIndexes = new Array(config.historySize);

        const updatePrecomputed = () => {
            for (let i = 0; i < count; i++) {
                precomputedIndexes[i] = (writePtr - count + i + config.historySize) & config.mask;
            }
        };

        return {
            add: (value) => {
                const trimmed = String(value).trim();
                if (!trimmed) return;

                const prevIndex = (writePtr - 1) & config.mask;
                if (trimmed === buffer[prevIndex]) return;

                buffer[writePtr] = trimmed;
                writePtr = (writePtr + 1) & config.mask;
                count = Math.min(count + 1, config.historySize);
                updatePrecomputed();
            },
            get: (index) => buffer[precomputedIndexes[index]] || '',
            size: () => count,
            currentIndex: -1
        };
    })();

    const createSyncHandler = (() => {
        const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
        const inputEvent = new Event('input', { bubbles: true });
        let lastSync = 0;

        return {
            sync: (source, target) => {
                if (source.value === target.value) return;
                const now = performance.now();
                if (now - lastSync < config.debounceTime) return;

                descriptor.set.call(target, source.value);
                target.dispatchEvent(inputEvent);
                lastSync = now;
            }
        };
    })();

    const throttle = (fn, delay) => {
        let lastExec = 0;
        let pendingFrame = null;

        const throttled = (...args) => {
            const now = performance.now();
            const elapsed = now - lastExec;

            const execute = () => {
                fn(...args);
                lastExec = performance.now();
                pendingFrame = null;
            };

            if (elapsed > delay) {
                if (pendingFrame) {
                    cancelAnimationFrame(pendingFrame);
                    pendingFrame = null;
                }
                execute();
            } else if (!pendingFrame) {
                pendingFrame = requestAnimationFrame(() => {
                    if (performance.now() - lastExec >= delay) {
                        execute();
                    }
                });
            }
        };

        throttled.cancel = () => {
            if (pendingFrame) cancelAnimationFrame(pendingFrame);
        };

        return throttled;
    };

    const createControlPanel = () => {
        const container = Object.assign(document.createElement('div'), {
            className: 'custom-search-container'
        });

        const [prevBtn, nextBtn] = ['←', '→'].map(text =>
            Object.assign(document.createElement('button'), {
                className: 'history-btn',
                textContent: text
            })
        );

        const input = Object.assign(document.createElement('input'), {
            className: 'custom-search-input',
            placeholder: '筛选'
        });

        container.append(prevBtn, nextBtn, input);
        document.body.appendChild(container);

        return { input: input, prevBtn: prevBtn, nextBtn: nextBtn };
    };

    const initSystem = () => {
        const { input: customInput, prevBtn, nextBtn } = createControlPanel();
        let lastValue = '';
        let mutationTimeout = null;
        let valueObserver = null;
        let domObserver = null;
        let originalInput = null;
        let originalInputHandler = null;

        const setupValueObserver = (target) => {
            valueObserver?.disconnect();

            valueObserver = new MutationObserver(() => {
                if (!target || isSyncing) return;
                const currentValue = target.value;

                if (currentValue !== lastValue) {
                    isSyncing = true;
                    customInput.value = lastValue = currentValue;
                    historyManager.add(currentValue);
                    historyManager.currentIndex = historyManager.size() - 1;
                    isSyncing = false;
                }
            });

            valueObserver.observe(target, config.observerConfig.valueObserver);
        };

        const bindEvents = (target) => {
            const newHandler = () => {
                if (!isSyncing) {
                    isSyncing = true;
                    customInput.value = target.value;
                    historyManager.add(target.value);
                    historyManager.currentIndex = historyManager.size() - 1;
                    isSyncing = false;
                }
            };

            target.removeEventListener('input', originalInputHandler);
            target.addEventListener('input', newHandler);
            originalInputHandler = newHandler;

            customInput.addEventListener('input', () =>
                createSyncHandler.sync(customInput, target),
                { passive: true }
            );

            const navigateFactory = (direction) => throttle(() => {
                const newIndex = historyManager.currentIndex + direction;
                if (newIndex < -1 || newIndex >= historyManager.size()) return;

                historyManager.currentIndex = Math.max(-1, Math.min(newIndex, historyManager.size() - 1));
                isSyncing = true;
                valueObserver?.disconnect();

                customInput.value = historyManager.get(historyManager.currentIndex);
                createSyncHandler.sync(customInput, target);

                if (valueObserver && target) {
                    valueObserver.observe(target, config.observerConfig.valueObserver);
                }
                isSyncing = false;
            }, 120);

            prevBtn.addEventListener('click', navigateFactory(-1));
            nextBtn.addEventListener('click', navigateFactory(1));
        };

        domObserver = new MutationObserver(() => {
            clearTimeout(mutationTimeout);
            mutationTimeout = setTimeout(() => {
                const newInput = optimizedFindSearchBox.get();
                if (newInput && newInput !== originalInput) {
                    originalInput = newInput;
                    lastValue = newInput.value;
                    setupValueObserver(newInput);
                    bindEvents(newInput);
                }
            }, config.mutationDebounce);
        });

        const contentNode = document.querySelector('#root > div') || document.body;
        domObserver.observe(contentNode, config.observerConfig.domObserver);

        const initialInput = optimizedFindSearchBox.get();
        if (initialInput) {
            originalInput = initialInput;
            lastValue = initialInput.value;
            setupValueObserver(initialInput);
            bindEvents(initialInput);
        }
    };

    const initialize = () => {
        document.body ? initSystem() : window.addEventListener('DOMContentLoaded', initSystem);
    };

    window.addEventListener('unload', () => {
        optimizedFindSearchBox.disconnect();
        valueObserver?.disconnect();
        domObserver?.disconnect();
        originalInput?.removeEventListener('input', originalInputHandler);
        [valueObserver, domObserver, originalInput, originalInputHandler].forEach(item => item = null);
    });

    let isSyncing = false;
    initialize();
})();