Greasy Fork

Greasy Fork is available in English.

Bangumi jump to multiple sites

在Bangumi游戏条目上添加实用的按钮

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Bangumi jump to multiple sites
// @namespace    http://tampermonkey.net/
// @version      0.9.5.6
// @description  在Bangumi游戏条目上添加实用的按钮
// @author       Sedoruee
// @include      /https?:\/\/(bgm\.tv|bangumi\.tv|chii\.in).*/
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const subjectType = document.querySelector('.nameSingle > .grey')?.textContent;
    const gameTitle = document.querySelector('.nameSingle > a')?.textContent;

    if (subjectType === '游戏' && gameTitle) {
        const nameSingle = document.querySelector('.nameSingle');

        GM_addStyle(`
            .combined-button, .multisearch-select-container .combined-button, .jump-button {
                display: inline-flex;
                align-items: center;
                margin-left: 5px;
                border: 1px solid #ccc;
                border-radius: 3px;
                background-color: #f0f0f0;
                color: black;
                font-size: 14px;
                cursor: pointer;
                height: 32px;
                box-sizing: border-box;
                overflow: hidden;
            }

            .button-name {
                padding: 5px 10px;
                border: none;
                background-color: transparent;
                color: inherit;
                font-size: inherit;
                cursor: pointer;
                text-align: center;
                flex-grow: 1;
                flex-shrink: 0;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
                min-width: 50px;
            }

            .select-arrow {
                -webkit-appearance: none;
                -moz-appearance: none;
                appearance: none;
                background-color: transparent;
                border: none;
                padding: 5px 10px;
                cursor: pointer;
                font-size: inherit;
                color: inherit;
                position: relative;
                z-index: 1;
                width: 20px;
                flex-shrink: 0;
                background-image: url('data:image/svg+xml;utf8,<svg fill="black" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>');
                background-repeat: no-repeat;
                background-position: center;
                border-left: 1px solid #ddd;
                margin-left: 2px;
            }

            .select-arrow::-ms-expand {
                display: none;
            }

            .combined-button:hover, .multisearch-select-container .combined-button:hover, .jump-button:hover,
            .button-name:hover, .select-arrow:hover {
                background-color: #e0e0e0;
            }
            .combined-button:active, .multisearch-select-container .combined-button:active, .jump-button:active,
            .button-name:active, .select-arrow:active {
                background-color: #d0d0d0;
            }

            .jump-button {
                height: 32px;
                line-height: 32px;
                padding-top: 0;
                padding-bottom: 0;
                display: inline-flex;
                align-items: center;
                text-align: center;
            }

            .multisearch-select-container {
                display: inline-block;
                position: relative;
                margin-left: 5px;
            }

            .multisearch-select-dropdown {
                position: absolute;
                top: 100%;
                left: 0;
                z-index: 10;
                border: 1px solid #ccc;
                border-radius: 3px;
                background-color: white;
                padding: 5px 0;
                min-width: 150px;
                display: none;
            }

            .multisearch-select-dropdown.show {
                display: block;
            }

            .multisearch-select-dropdown label {
                display: block;
                padding: 5px 15px;
                cursor: pointer;
            }

            .multisearch-select-dropdown label:hover {
                background-color: #f0f0f0;
            }

            .settings-panel {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background-color: white;
                border: 1px solid #ccc;
                padding: 20px;
                box-shadow: 0 4px 8px rgba(0,0,0,0.1);
                z-index: 1000;
                max-height: 80vh;
                overflow-y: auto;
            }

            .settings-panel h2, .settings-panel h3 {
                margin-top: 0;
            }

            .settings-panel label {
                display: block;
                margin-bottom: 5px;
            }

            .settings-panel input[type="text"], .settings-panel input[type="url"], .settings-panel select {
                width: calc(100% - 10px);
                padding: 8px;
                margin-bottom: 10px;
                border: 1px solid #ddd;
                box-sizing: border-box;
            }

            .settings-panel button {
                padding: 8px 15px;
                background-color: #f0f0f0;
                border: 1px solid #ccc;
                border-radius: 3px;
                cursor: pointer;
                margin-right: 5px;
            }

            .settings-panel button:hover {
                background-color: #e0e0e0;
            }

            .settings-panel-buttons {
                margin-bottom: 15px;
                border: 1px solid #eee;
                padding: 10px;
                border-radius: 5px;
            }
            .settings-panel-button-item {
                margin-bottom: 10px;
                padding-bottom: 10px;
                border-bottom: 1px dashed #eee;
            }
            .settings-panel-button-item:last-child {
                border-bottom: none;
                margin-bottom: 0;
                padding-bottom: 0;
            }
            .settings-panel-button-actions {
                margin-top: 5px;
            }

            .settings-tutorial {
                margin-top: 20px;
                border-top: 1px solid #eee;
                padding-top: 15px;
            }

            .settings-tutorial h3 {
                margin-bottom: 10px;
            }

            .settings-tutorial p {
                line-height: 1.6;
            }
            .hidden {
                display: none !important;
            }
        `);

        const createButton = (buttonConfig) => {
            if (buttonConfig.type === 'jump') {
                return createJumpButton(buttonConfig);
            } else if (buttonConfig.type === 'combined') {
                return createCombinedButton(buttonConfig);
            } else if (buttonConfig.type === 'multisearch') {
                return createMultiSearchSelect(buttonConfig);
            }
            return null;
        };

        const createJumpButton = (buttonConfig) => {
            const button = document.createElement('button');
            button.textContent = buttonConfig.name;
            button.className = 'jump-button';
            button.addEventListener('click', () => {
                if (buttonConfig.clipboardText) {
                    GM_setClipboard(gameTitle);
                }
                window.open(buttonConfig.url.replace('{{gameTitle}}', encodeURIComponent(gameTitle)));
            });
            return button;
        };

        const createCombinedButton = (buttonConfig) => {
            const container = document.createElement('div');
            container.className = 'combined-button';

            const nameButton = document.createElement('button');
            nameButton.className = 'button-name';
            container.appendChild(nameButton);

            const selectArrow = document.createElement('select');
            selectArrow.className = 'select-arrow';
            container.appendChild(selectArrow);

            buttonConfig.options.forEach(site => {
                const option = document.createElement('option');
                option.value = site.value;
                option.text = site.text;
                selectArrow.appendChild(option);
            });

            const storedSite = GM_getValue(buttonConfig.storageKey, buttonConfig.defaultOption);
            selectArrow.value = storedSite;
            updateButtonName(nameButton, selectArrow.options[selectArrow.selectedIndex].text);

            selectArrow.addEventListener('change', function() {
                GM_setValue(buttonConfig.storageKey, this.value);
                updateButtonName(nameButton, this.options[this.selectedIndex].text);
            });

            nameButton.addEventListener('click', () => {
                const selectedOption = selectArrow.value;
                const selectedSiteOption = buttonConfig.options.find(opt => opt.value === selectedOption);
                if (selectedSiteOption) {
                    if (buttonConfig.clipboardText) {
                        GM_setClipboard(gameTitle);
                    }
                    window.open(selectedSiteOption.url.replace('{{gameTitle}}', encodeURIComponent(gameTitle)));
                }
            });
            selectArrow.addEventListener('click', function(event) {
                this.focus();
                event.stopPropagation();
            });
            container.addEventListener('click', function(event) {
                if (!container.contains(event.target)) {
                    selectArrow.blur();
                }
            });

            function updateButtonName(button, siteName) {
                button.textContent = siteName;
            }
            return container;
        };

        const createMultiSearchSelect = (buttonConfig) => {
            const container = document.createElement('div');
            container.className = 'multisearch-select-container';

            const buttonArea = document.createElement('div');
            buttonArea.className = 'combined-button';
            container.appendChild(buttonArea);

            const nameButton = document.createElement('button');
            nameButton.className = 'button-name';
            nameButton.textContent = buttonConfig.name;
            buttonArea.appendChild(nameButton);

            const selectArrow = document.createElement('button');
            selectArrow.className = 'select-arrow';
            buttonArea.appendChild(selectArrow);

            const dropdown = document.createElement('div');
            dropdown.className = 'multisearch-select-dropdown';
            dropdown.id = 'multisearchDropdown';
            container.appendChild(dropdown);

            const sites = buttonConfig.options;

            let storedMultiSearchSites = GM_getValue(buttonConfig.storageKey, sites.filter(site => site.checked).map(site => site.value).join(','));
            let selectedSitesValues = storedMultiSearchSites ? storedMultiSearchSites.split(',') : sites.filter(site => site.checked).map(site => site.value);

            sites.forEach(site => {
                const label = document.createElement('label');
                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.value = site.value;
                checkbox.checked = selectedSitesValues.includes(site.value);

                checkbox.addEventListener('change', function() {
                    let currentSelectedValues = Array.from(dropdown.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value);
                    GM_setValue(buttonConfig.storageKey, currentSelectedValues.join(','));
                });

                label.appendChild(checkbox);
                label.appendChild(document.createTextNode(' ' + site.text));
                dropdown.appendChild(label);
            });

            selectArrow.addEventListener('click', function(event) {
                dropdown.classList.toggle('show');
                event.stopPropagation();
            });
            nameButton.addEventListener('click', () => {
                openPreviewWindowsWithDelay(buttonConfig, gameTitle, container, 500); // 延迟 500ms 打开
            });

            document.addEventListener('click', function(event) {
                if (!container.contains(event.target)) {
                    dropdown.classList.remove('show');
                }
            });
            return container;
        };

        // 修改后的延迟打开函数
        function openPreviewWindowsWithDelay(buttonConfig, gameTitle, multiSearchSelectContainer, delay) {
            setTimeout(() => {
                openPreviewWindows(buttonConfig, gameTitle, multiSearchSelectContainer);
            }, delay);
        }

        function openPreviewWindows(buttonConfig, gameTitle, multiSearchSelectContainer) {
            closePreviewWindows();

            const dropdownElement = multiSearchSelectContainer.querySelector('.multisearch-select-dropdown');
            const selectedCheckboxes = dropdownElement.querySelectorAll('input[type="checkbox"]:checked');
            const selectedSitesValues = Array.from(selectedCheckboxes).map(cb => cb.value);

            const sites = buttonConfig.options.filter(site => selectedSitesValues.includes(site.value));
            const urls = sites.map(site => site.url.replace('{{gameTitle}}', encodeURIComponent(gameTitle)));

            if (urls.length === 0) {
                alert("请选择至少一个多搜索站点。");
                return;
            }

            const gap = 10;
            const winWidth = Math.floor(screen.width / urls.length);
            const winHeight = 1600;
            const totalWidth = winWidth * urls.length + gap * (urls.length -1 );
            const leftStart = Math.floor((screen.width - totalWidth) / 2);
            const topPos = Math.floor((screen.height - winHeight) / 2);

            previewWindows = [];
            urls.forEach((url, index) => {
                const leftPos = leftStart + index * (winWidth + gap);
                const features = `width=${winWidth},height=${winHeight},left=${leftPos},top=${topPos},resizable=yes,scrollbars=yes`;
                const newWin = window.open(url, '_blank', features);
                if (newWin) {
                    previewWindows.push(newWin);
                    newWin.onload = () => {
                        newWin.document.addEventListener('click', function(event) {
                            if (event.target.tagName === 'A') {
                                event.preventDefault();
                                const href = event.target.href;
                                closePreviewWindows();
                                window.open(href, '_blank');
                            }
                        });
                    };
                } else {
                    console.warn("弹窗被拦截,无法打开:", url);
                }
            });

            focusMonitorDelayTimer = setTimeout(() => {
                startFocusMonitor();
            }, 2000);
        }

        let previewWindows = [];
        let monitorInterval = null;
        let focusMonitorDelayTimer = null;

        function closePreviewWindows() {
            stopFocusMonitor();
            if (focusMonitorDelayTimer) {
                clearTimeout(focusMonitorDelayTimer);
                focusMonitorDelayTimer = null;
            }
            previewWindows.forEach(win => {
                if (win && !win.closed) {
                    win.close();
                }
            });
            previewWindows = [];
        }

        function startFocusMonitor() {
            if (!monitorInterval) {
                monitorInterval = setInterval(monitorFocus, 300);
            }
        }

        function stopFocusMonitor() {
            if (monitorInterval) {
                clearInterval(monitorInterval);
                monitorInterval = null;
            }
        }

        function monitorFocus() {
            for (let i = 0; i < previewWindows.length; i++) {
                if (previewWindows[i] && previewWindows[i].closed) {
                    closePreviewWindows();
                    return;
                }
            }
            if (!document.hasFocus()) {
                let previewWindowFocused = false;
                for (let win of previewWindows) {
                    if (win && !win.closed && win.document.hasFocus()) {
                        previewWindowFocused = true;
                        break;
                    }
                }
                if (!previewWindowFocused) {
                    closePreviewWindows();
                }
            }
        }

        function loadButtonSettings() {
            return GM_getValue('customButtons', [
                { type: 'jump', name: 'VNDB', url: 'https://vndb.org/v?q={{gameTitle}}' },
                { type: 'jump', name: 'Hitomi', url: 'https://hitomi.la/search.html?type%3Agamecg%20{{gameTitle}}%20orderby%3Apopular%20orderbykey%3Ayear', processTitle: 'hitomi' },
                {
                    type: 'combined',
                    name: '魔皇/zi0',
                    storageKey: 'selectedSite',
                    defaultOption: 'mhdy',
                    clipboardText: true,
                    options: [
                        { value: 'mhdy', text: '魔皇地狱', url: 'https://pan1.mhdy.shop/' },
                        { value: 'zi0', text: 'zi0.cc', url: 'https://zi0.cc/' }
                    ]
                },
                { type: 'jump', name: '2dfan', clipboardText: true, url: 'https://2dfan.com/subjects/search?keyword={{gameTitle}}' },
                {
                    type: 'multisearch',
                    name: '多搜索',
                    storageKey: 'multiSearchSites',
                    options: [
                        { value: 'ai2', text: 'ai2.moe', url: 'https://www.ai2.moe/search/?q={{gameTitle}}&updated_after=any&sortby=relevancy&search_in=titles', checked: true },
                        { value: 'moyu', text: 'moyu.moe', url: 'https://www.moyu.moe/search?q={{gameTitle}}', checked: true },
                        { value: '2dfan_preview', text: '2dfan', url: 'https://2dfan.com/subjects/search?keyword={{gameTitle}}', checked: true }
                    ]
                }
            ]);
        }

        function saveButtonSettings(settings) {
            GM_setValue('customButtons', settings);
        }

        function renderButtons() {
            const buttonSettings = loadButtonSettings();
            nameSingle.querySelectorAll('.jump-button, .combined-button, .multisearch-select-container').forEach(btn => btn.remove());
            nameSingle.appendChild(document.createElement('br'));
            buttonSettings.forEach(buttonConfig => {
                const button = createButton(buttonConfig);
                if (button) {
                    nameSingle.appendChild(button);
                }
            });
             const settingsButton = document.createElement('button');
            settingsButton.textContent = '设置';
            settingsButton.className = 'jump-button';
            settingsButton.addEventListener('click', openSettingsPanel);
            nameSingle.appendChild(settingsButton);
        }

        renderButtons();

        let settingsPanel;

        function openSettingsPanel() {
            if (!settingsPanel) {
                settingsPanel = createSettingsPanel();
                document.body.appendChild(settingsPanel);
            }
            settingsPanel.classList.remove('hidden');
        }

        function closeSettingsPanel() {
            if (settingsPanel) {
                settingsPanel.classList.add('hidden');
            }
        }

        function createSettingsPanel() {
            const panel = document.createElement('div');
            panel.className = 'settings-panel hidden';

            const title = document.createElement('h2');
            title.textContent = '网址按钮设置';
            panel.appendChild(title);

            const tutorialSection = createTutorialSection();
            panel.appendChild(tutorialSection);

            const buttonsSection = createButtonsSettingsSection();
            panel.appendChild(buttonsSection);

            const closeButton = document.createElement('button');
            closeButton.textContent = '关闭';
            closeButton.addEventListener('click', closeSettingsPanel);
            panel.appendChild(closeButton);

            return panel;
        }

        function createTutorialSection() {
            const section = document.createElement('div');
            section.className = 'settings-tutorial';

            const title = document.createElement('h3');
            title.textContent = '修改教程';
            section.appendChild(title);

            const tutorialText = document.createElement('p');
            tutorialText.innerHTML = `
                1. **添加按钮**: 点击 “添加按钮”,填写按钮名称,类型,URL等信息,点击 “保存按钮” 完成添加。<br>
                2. **修改按钮**: 在按钮列表中,点击 “编辑” 按钮,修改按钮信息后,点击 “保存按钮” 完成修改。<br>
                3. **删除按钮**: 在按钮列表中,点击 “删除” 按钮即可删除按钮。<br>
                4. **按钮类型说明**: <br>
                   - **Jump Button**:  点击直接跳转到设置的URL,URL中可以使用 {{gameTitle}} 占位符代表游戏标题。<br>
                   - **Combined Button**:  合并按钮,左侧为按钮名称,右侧下拉选择不同站点。需要设置多个 options,每个 option 包含 value, text, url。<br>
                   - **MultiSearch Select**: 多搜索按钮,点击按钮显示下拉菜单,用户可以选择多个站点进行多搜索。需要设置多个 options,每个 option 包含 value, text, url, checked (默认选中)。<br>
                5. **保存设置**: 修改完成后,点击 “保存设置” 保存所有更改。点击 “关闭” 关闭设置面板。<br>
                *URL 填写说明*: URL 中可以使用 {{gameTitle}} 作为占位符,脚本运行时会自动替换为当前页面的游戏标题。
            `;
            section.appendChild(tutorialText);
            return section;
        }


        function createButtonsSettingsSection() {
            const section = document.createElement('div');
            section.className = 'settings-panel-buttons';

            const title = document.createElement('h3');
            title.textContent = '按钮列表';
            section.appendChild(title);

            const buttonsList = document.createElement('div');
            section.appendChild(buttonsList);

            let currentButtonSettings = loadButtonSettings();

            function refreshButtonsList() {
                buttonsList.innerHTML = '';
                currentButtonSettings.forEach((buttonConfig, index) => {
                    const buttonItem = createButtonItem(buttonConfig, index);
                    buttonsList.appendChild(buttonItem);
                });
            }

            function createButtonItem(buttonConfig, index) {
                const item = document.createElement('div');
                item.className = 'settings-panel-button-item';

                const nameLabel = document.createElement('label');
                nameLabel.textContent = `名称: ${buttonConfig.name}, 类型: ${buttonConfig.type}`;
                item.appendChild(nameLabel);

                const actions = document.createElement('div');
                actions.className = 'settings-panel-button-actions';

                const editButton = document.createElement('button');
                editButton.textContent = '编辑';
                editButton.addEventListener('click', () => openEditButtonForm(buttonConfig, index));
                actions.appendChild(editButton);

                const deleteButton = document.createElement('button');
                deleteButton.textContent = '删除';
                deleteButton.addEventListener('click', () => {
                    currentButtonSettings.splice(index, 1);
                    saveButtonSettings(currentButtonSettings);
                    refreshButtonsList();
                    renderButtons();
                });
                actions.appendChild(deleteButton);

                item.appendChild(actions);
                return item;
            }

            refreshButtonsList();

            const addButtonButton = document.createElement('button');
            addButtonButton.textContent = '添加按钮';
            addButtonButton.addEventListener('click', openAddButtonForm);
            section.appendChild(addButtonButton);

            let formContainer = document.createElement('div');
            section.appendChild(formContainer);
            let currentForm = null;

            function openAddButtonForm() {
                if (currentForm) formContainer.removeChild(currentForm);
                currentForm = createButtonForm(null, (newButtonConfig) => {
                    currentButtonSettings.push(newButtonConfig);
                    saveButtonSettings(currentButtonSettings);
                    refreshButtonsList();
                    renderButtons();
                    formContainer.removeChild(currentForm);
                    currentForm = null;
                }, () => {
                    formContainer.removeChild(currentForm);
                    currentForm = null;
                });
                formContainer.appendChild(currentForm);
            }

            function openEditButtonForm(buttonConfig, index) {
                if (currentForm) formContainer.removeChild(currentForm);
                currentForm = createButtonForm(buttonConfig, (updatedButtonConfig) => {
                    currentButtonSettings[index] = updatedButtonConfig;
                    saveButtonSettings(currentButtonSettings);
                    refreshButtonsList();
                    renderButtons();
                    formContainer.removeChild(currentForm);
                    currentForm = null;
                }, () => {
                    formContainer.removeChild(currentForm);
                    currentForm = null;
                });
                formContainer.appendChild(currentForm);
            }


            function createButtonForm(initialConfig, saveCallback, cancelCallback) {
                const isEdit = initialConfig !== null;
                const form = document.createElement('div');

                const typeLabel = document.createElement('label');
                typeLabel.textContent = '按钮类型:';
                const typeSelect = document.createElement('select');
                const types = ['jump', 'combined', 'multisearch'];
                types.forEach(type => {
                    const option = document.createElement('option');
                    option.value = type;
                    option.textContent = type;
                    typeSelect.appendChild(option);
                });
                typeSelect.value = initialConfig ? initialConfig.type : 'jump';
                form.appendChild(typeLabel);
                form.appendChild(typeSelect);

                const nameLabel = document.createElement('label');
                nameLabel.textContent = '按钮名称:';
                const nameInput = document.createElement('input');
                nameInput.type = 'text';
                nameInput.value = initialConfig ? initialConfig.name : '';
                form.appendChild(nameLabel);
                form.appendChild(nameInput);

                const urlLabel = document.createElement('label');
                urlLabel.textContent = 'URL (Jump/Combined/MultiSearch):';
                const urlInput = document.createElement('input');
                urlInput.type = 'url';
                urlInput.value = initialConfig && initialConfig.url ? initialConfig.url : '';
                form.appendChild(urlLabel);
                form.appendChild(urlInput);

                const storageKeyLabel = document.createElement('label');
                storageKeyLabel.textContent = 'Storage Key (Combined/MultiSearch):';
                const storageKeyInput = document.createElement('input');
                storageKeyInput.type = 'text';
                storageKeyInput.value = initialConfig && initialConfig.storageKey ? initialConfig.storageKey : '';
                form.appendChild(storageKeyLabel);
                form.appendChild(storageKeyInput);

                const defaultOptionLabel = document.createElement('label');
                defaultOptionLabel.textContent = 'Default Option Value (Combined):';
                const defaultOptionInput = document.createElement('input');
                defaultOptionInput.type = 'text';
                defaultOptionInput.value = initialConfig && initialConfig.defaultOption ? initialConfig.defaultOption : '';
                form.appendChild(defaultOptionLabel);
                form.appendChild(defaultOptionInput);

                const clipboardTextLabel = document.createElement('label');
                clipboardTextLabel.textContent = '复制标题到剪贴板 (Jump/Combined/2dfan):';
                const clipboardTextSelect = document.createElement('select');
                const clipboardOptions = [{value: true, text: '是'}, {value: false, text: '否'}];
                clipboardOptions.forEach(opt => {
                    const option = document.createElement('option');
                    option.value = opt.value;
                    option.textContent = opt.text;
                    clipboardTextSelect.appendChild(option);
                });
                clipboardTextSelect.value = initialConfig && initialConfig.clipboardText !== undefined ? String(initialConfig.clipboardText) : 'false';
                form.appendChild(clipboardTextLabel);
                form.appendChild(clipboardTextSelect);


                const optionsLabel = document.createElement('label');
                optionsLabel.textContent = 'Options (Combined/MultiSearch, JSON Array):';
                const optionsTextarea = document.createElement('input');
                optionsTextarea.type = 'text';
                optionsTextarea.value = initialConfig && initialConfig.options ? JSON.stringify(initialConfig.options) : '[]';
                form.appendChild(optionsLabel);
                form.appendChild(optionsTextarea);


                const saveButton = document.createElement('button');
                saveButton.textContent = '保存按钮';
                saveButton.addEventListener('click', () => {
                    let optionsArray = [];
                    try {
                        optionsArray = JSON.parse(optionsTextarea.value || '[]');
                    } catch (e) {
                        alert('Options JSON 格式错误');
                        return;
                    }

                    const newConfig = {
                        type: typeSelect.value,
                        name: nameInput.value,
                        url: urlInput.value || undefined,
                        storageKey: storageKeyInput.value || undefined,
                        defaultOption: defaultOptionInput.value || undefined,
                        clipboardText: clipboardTextSelect.value === 'true',
                        options: optionsArray.length > 0 ? optionsArray : undefined
                    };
                    saveCallback(newConfig);
                });
                form.appendChild(saveButton);

                const cancelButton = document.createElement('button');
                cancelButton.textContent = '取消';
                cancelButton.addEventListener('click', cancelCallback);
                form.appendChild(cancelButton);

                return form;
            }

            return section;
        }

        document.addEventListener('keydown', function(event) {
            if (event.key === 'Escape') {
                closeSettingsPanel();
            }
        });

    }
})();