Greasy Fork

Settings Tab Manager (STM)

Provides an API for other userscripts to add tabs to a site's settings menu.

当前为 2025-04-22 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/533630/1575889/Settings%20Tab%20Manager%20%28STM%29.js

// ==UserScript==
// @name         Settings Tab Manager (STM)
// @namespace    shared-settings-manager
// @version      1.1.3
// @description  Provides an API for other userscripts to add tabs to a site's settings menu.
// @author       nipah, Gemini
// @license      MIT
// @match        https://8chan.moe/*
// @match        https://8chan.se/*
// @grant        GM_addStyle
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // ... (Constants, State, Readiness Promise, Public API Definition, Styling remain the same) ...
    const log = (...args) => console.log(`[${MANAGER_ID}]`, ...args);
    const warn = (...args) => console.warn(`[${MANAGER_ID}]`, ...args);
    const error = (...args) => console.error(`[${MANAGER_ID}]`, ...args);
    const MANAGER_ID = 'SettingsTabManager';
    const SELECTORS = {/* ... */};
    const ACTIVE_CLASSES = {/* ... */};
    const ATTRS = {/* ... */};

    // ... (findSettingsElements, deactivateCurrentTab, activateTab, handleTabClick, attachTabClickListener remain the same) ...
    function handleTabClick(event) { /* ... same code (no stopPropagation) ... */ }
    function activateTab(scriptId) { /* ... same code ... */ }


    // --- REMOVED createSeparator function ---


    /** Creates and inserts the tab and panel elements for a given script config. (No Separators) */
    function createTabAndPanel(config) {
        if (!tabContainerEl || !panelContainerEl) {
            error(`Cannot create tab/panel for ${config.scriptId}: Containers not found.`);
            return;
        }
        if (tabContainerEl.querySelector(`span[${ATTRS.SCRIPT_ID}="${config.scriptId}"]`)) {
            log(`Tab already exists for ${config.scriptId}, skipping creation.`);
            return;
        }

        log(`Creating tab/panel for: ${config.scriptId} (No Separator)`);

        // --- Create Tab ---
        const newTab = document.createElement('span');
        newTab.className = SELECTORS.SITE_TAB.substring(1); // Use site's class
        newTab.textContent = config.tabTitle;
        newTab.setAttribute(ATTRS.SCRIPT_ID, config.scriptId);
        newTab.setAttribute(ATTRS.MANAGED, 'true');
        newTab.setAttribute('title', `${config.tabTitle} (Settings by ${config.scriptId})`);
        const desiredOrder = typeof config.order === 'number' ? config.order : Infinity;
        newTab.setAttribute('data-stm-order', desiredOrder);

        // --- Create Panel ---
        const newPanel = document.createElement('div');
        newPanel.className = SELECTORS.SITE_PANEL.substring(1); // Use site's class
        newPanel.setAttribute(ATTRS.SCRIPT_ID, config.scriptId);
        newPanel.setAttribute(ATTRS.MANAGED, 'true');
        newPanel.id = `${MANAGER_ID}-${config.scriptId}-panel`;

        // --- Insertion Logic (Simplified: No Separators) ---
        let insertBeforeElement = null;
        const existingStmTabs = Array.from(
            tabContainerEl.querySelectorAll(`span[${ATTRS.MANAGED}][${ATTRS.SCRIPT_ID}]`)
        ).sort( // Sort based on order attribute
            (a, b) => parseInt(a.getAttribute('data-stm-order') || Infinity, 10) - parseInt(b.getAttribute('data-stm-order') || Infinity, 10)
        );

        // Find the first existing STM tab with a higher order number
        for (const existingTab of existingStmTabs) {
            const existingOrder = parseInt(existingTab.getAttribute('data-stm-order') || Infinity, 10);
            if (desiredOrder < existingOrder) {
                insertBeforeElement = existingTab;
                break;
            }
        }

        // Insert the new tab
        if (insertBeforeElement) {
            // Insert before the found element
            tabContainerEl.insertBefore(newTab, insertBeforeElement);
        } else {
            // Append at the end (after all other STM tabs, potentially before native 'Other' etc.)
            // We could add logic here to find the last native tab if needed,
            // but appending might be sufficient.
            tabContainerEl.appendChild(newTab);
        }

        // Append Panel (Order doesn't matter visually)
        panelContainerEl.appendChild(newPanel);

        // --- Initialize Panel Content ---
        try {
            Promise.resolve(config.onInit(newPanel, newTab)).catch(e => {
                error(`Error during async onInit for ${config.scriptId}:`, e);
                newPanel.innerHTML = `<p style="color: red;">Error initializing settings panel for ${config.scriptId}. See console.</p>`;
            });
        } catch (e) {
            error(`Error during sync onInit for ${config.scriptId}:`, e);
            newPanel.innerHTML = `<p style="color: red;">Error initializing settings panel for ${config.scriptId}. See console.</p>`;
        }
    }

    // ... (processPendingRegistrations, Initialization, Observer, API Implementation, Global Exposure remain the same) ...
    function initializeManager() { /* ... same code ... */ }
    const observer = new MutationObserver(/* ... */);
    // ... start observer ...
    // ... initializeManager() call ...
    // ... API Implementation functions (registerTabImpl etc.) ...
    // ... Global Exposure logic ...

})();