Bangumi jump to multiple sites

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
            }
        });

    }
})();