JSON Formatter

类名添加miw_前缀 + 样式作用域隔离 + 修复Chromium主题 + 避免普通网页误解析JSON

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name          JSON Formatter
// @namespace     https://github.com/askmiw
// @author        [email protected]
// @description   类名添加miw_前缀 + 样式作用域隔离 + 修复Chromium主题 + 避免普通网页误解析JSON
// @version       1.0.0
// @match         *://*/*
// @match         file:///*
// @grant         GM_addStyle
// @grant         GM_registerMenuCommand
// @run-at        document-end
// ==/UserScript==

(function() {
    'use strict';

    // 配置项
    let CONFIG = {
        AUTO_FORMAT: true,
        DARK_MODE: true,
        COLLAPSE_LEVEL: Infinity,
        SHOW_LINE_NUMBERS: false,
        ENABLE_COPY: true,
        MIN_JSON_LENGTH: 10,
        MAX_RENDER_SIZE: 50000,
        SHOW_NODE_COUNT: true
    };

    // 动态样式管理(所有类名带miw_前缀)
    let styleElement = null;
    let globalFormatter = null;
    let mutationObserver = null;

    // ========== 核心修改:所有样式类名添加miw_前缀 ==========
    function updateStyles() {
        // 移除旧样式
        if (styleElement && styleElement.parentNode) {
            styleElement.parentNode.removeChild(styleElement);
        }

        styleElement = document.createElement('style');
        const lineNumberStyles = CONFIG.SHOW_LINE_NUMBERS
            ? `
            .miw_json-formatter-wrapper .miw_line-number {
                display: inline-block !important;
                min-width: 30px !important;
                width: 30px !important;
                text-align: right !important;
                margin-right: 10px !important;
                color: ${CONFIG.DARK_MODE ? '#6a9955' : '#999'} !important;
                user-select: none !important;
                padding: 0 !important;
            }
            `
            : `
            .miw_json-formatter-wrapper .miw_line-number {
                display: none !important;
                min-width: 0px !important;
                width: 0px !important;
                margin-right: 0px !important;
                padding: 0 !important;
                visibility: hidden !important;
                opacity: 0 !important;
            }
            `;

        // 主题样式(类名带miw_前缀)
        const themeStyles = CONFIG.DARK_MODE
            ? `
            .miw_json-formatter-wrapper {
                background-color: #1e1e1e !important;
                color: #e0e0e0 !important;
            }
            .miw_json-formatter-wrapper .miw_json-toolbar {
                background: rgba(30, 30, 30, 0.95) !important;
                border-color: #444 !important;
            }
            .miw_json-formatter-wrapper .miw_toolbar-button {
                background: #2d2d2d !important;
                border-color: #555 !important;
                color: #e0e0e0 !important;
            }
            .miw_json-formatter-wrapper .miw_search-box {
                background: #2d2d2d !important;
                border-color: #555 !important;
                color: #e0e0e0 !important;
            }
            .miw_json-formatter-wrapper .miw_search-nav-btn {
                background: #2d2d2d !important;
                border-color: #555 !important;
                color: #e0e0e0 !important;
            }
            .miw_json-formatter-wrapper .miw_json-raw-view {
                background: #2d2d2d !important;
                border-color: #444 !important;
                color: #e0e0e0 !important;
            }
            `
            : `
            .miw_json-formatter-wrapper {
                background-color: #ffffff !important;
                color: #333333 !important;
            }
            .miw_json-formatter-wrapper .miw_json-toolbar {
                background: rgba(255, 255, 255, 0.95) !important;
                border-color: #ddd !important;
            }
            .miw_json-formatter-wrapper .miw_toolbar-button {
                background: #f5f5f5 !important;
                border-color: #ddd !important;
                color: #333 !important;
            }
            .miw_json-formatter-wrapper .miw_search-box {
                background: #fff !important;
                border-color: #ddd !important;
                color: #333 !important;
            }
            .miw_json-formatter-wrapper .miw_search-nav-btn {
                background: #f5f5f5 !important;
                border-color: #ddd !important;
                color: #333 !important;
            }
            .miw_json-formatter-wrapper .miw_json-raw-view {
                background: #f9f9f9 !important;
                border-color: #ddd !important;
                color: #333 !important;
            }
            `;

        // 所有样式类名均添加miw_前缀,且通过后代选择器限定作用域
        styleElement.textContent = `
            /* 核心容器(带miw_前缀) */
            .miw_json-formatter-wrapper {
                font-family: 'Consolas', 'Monaco', 'Courier New', monospace !important;
                min-height: 100vh !important;
                padding: 20px !important;
                line-height: 1.4 !important;
                box-sizing: border-box !important;
                position: relative !important;
                margin: 0 !important;
                width: 100% !important;
            }

            ${themeStyles}

            .miw_json-formatter-wrapper .miw_json-toolbar {
                position: fixed !important;
                top: 10px !important;
                right: 10px !important;
                border-radius: 6px !important;
                padding: 8px 12px !important;
                z-index: 99999 !important;
                display: flex !important;
                gap: 6px !important;
                align-items: center !important;
                backdrop-filter: blur(10px) !important;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2) !important;
                flex-wrap: wrap !important;
            }

            .miw_json-formatter-wrapper .miw_toolbar-button {
                padding: 4px 8px !important;
                border-radius: 4px !important;
                cursor: pointer !important;
                font-size: 12px !important;
                transition: all 0.2s !important;
            }

            .miw_json-formatter-wrapper .miw_toolbar-button:hover {
                background: ${CONFIG.DARK_MODE ? '#3d3d3d' : '#e8e8e8'} !important;
                border-color: #007acc !important;
            }

            .miw_json-formatter-wrapper .miw_toolbar-button.active {
                background: #007acc !important;
                color: white !important;
                border-color: #007acc !important;
            }

            /* 搜索区域(带miw_前缀) */
            .miw_json-formatter-wrapper .miw_search-container {
                display: flex !important;
                align-items: center !important;
                gap: 4px !important;
            }

            .miw_json-formatter-wrapper .miw_search-box {
                padding: 4px 8px !important;
                border-radius: 4px !important;
                font-size: 12px !important;
                width: 180px !important;
                outline: none !important;
            }

            .miw_json-formatter-wrapper .miw_search-nav-btn {
                padding: 4px 6px !important;
                border-radius: 4px !important;
                cursor: pointer !important;
                font-size: 11px !important;
                width: 24px !important;
                height: 24px !important;
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
                transition: all 0.2s !important;
            }

            .miw_json-formatter-wrapper .miw_search-nav-btn:hover {
                background: ${CONFIG.DARK_MODE ? '#3d3d3d' : '#e8e8e8'} !important;
                border-color: #007acc !important;
            }

            .miw_json-formatter-wrapper .miw_search-count {
                font-size: 11px !important;
                color: ${CONFIG.DARK_MODE ? '#aaa' : '#666'} !important;
                min-width: 40px !important;
                text-align: center !important;
            }

            .miw_json-formatter-wrapper .miw_json-notification {
                position: fixed !important;
                bottom: 20px !important;
                left: 50% !important;
                transform: translateX(-50%) !important;
                background: #007acc !important;
                color: white !important;
                padding: 8px 16px !important;
                border-radius: 4px !important;
                opacity: 0 !important;
                transition: opacity 0.3s !important;
                pointer-events: none !important;
                z-index: 99999 !important;
            }

            .miw_json-formatter-wrapper .miw_json-notification.show {
                opacity: 1 !important;
            }

            /* JSON布局样式(带miw_前缀) */
            .miw_json-formatter-wrapper .miw_json-item {
                margin: 2px 0 !important;
            }

            .miw_json-formatter-wrapper .miw_json-line {
                display: flex !important;
                margin: 1px 0 !important;
                position: relative !important;
                align-items: flex-start !important;
            }

            /* 动态行号样式(带miw_前缀) */
            ${lineNumberStyles}

            .miw_json-formatter-wrapper .miw_line-content {
                flex: 1 !important;
                width: 100% !important;
            }

            .miw_json-formatter-wrapper .miw_json-collapsible {
                cursor: pointer !important;
                position: relative !important;
                padding-left: 16px !important;
                user-select: none !important;
                display: inline-block !important;
            }

            .miw_json-formatter-wrapper .miw_json-collapsible::before {
                content: '▶' !important;
                position: absolute !important;
                left: 0 !important;
                font-size: 10px !important;
                transition: transform 0.2s !important;
            }

            .miw_json-formatter-wrapper .miw_json-collapsible.expanded::before {
                content: '▼' !important;
            }

            /* 节点数样式(带miw_前缀) */
            .miw_json-formatter-wrapper .miw_json-node-count {
                color: ${CONFIG.DARK_MODE ? '#888' : '#999'} !important;
                font-style: italic !important;
                margin-left: 5px !important;
                font-size: 11px !important;
            }

            .miw_json-formatter-wrapper .miw_json-children {
                margin-left: 20px !important;
                border-left: 1px dashed ${CONFIG.DARK_MODE ? '#444' : '#ddd'} !important;
                padding-left: 10px !important;
            }

            .miw_json-formatter-wrapper .miw_json-key {
                color: ${CONFIG.DARK_MODE ? '#9cdcfe' : '#881391'} !important;
                font-weight: bold !important;
            }

            .miw_json-formatter-wrapper .miw_json-string {
                color: ${CONFIG.DARK_MODE ? '#ce9178' : '#c41a16'} !important;
            }

            .miw_json-formatter-wrapper .miw_json-number {
                color: ${CONFIG.DARK_MODE ? '#b5cea8' : '#1c00cf'} !important;
            }

            .miw_json-formatter-wrapper .miw_json-boolean {
                color: ${CONFIG.DARK_MODE ? '#569cd6' : '#0b22b9'} !important;
            }

            .miw_json-formatter-wrapper .miw_json-null {
                color: ${CONFIG.DARK_MODE ? '#569cd6' : '#0b22b9'} !important;
            }

            .miw_json-formatter-wrapper .miw_json-punctuation {
                color: ${CONFIG.DARK_MODE ? '#d4d4d4' : '#333'} !important;
                margin: 0 2px !important;
            }

            .miw_json-formatter-wrapper .miw_json-ellipsis {
                color: ${CONFIG.DARK_MODE ? '#888' : '#999'} !important;
                font-style: italic !important;
                margin-left: 5px !important;
            }

            /* 搜索高亮(带miw_前缀) */
            .miw_json-formatter-wrapper .miw_search-highlight {
                background-color: rgba(255, 255, 0, 0.4) !important;
                border-radius: 2px !important;
                padding: 1px 0 !important;
            }

            .miw_json-formatter-wrapper .miw_search-highlight.current {
                background-color: rgba(255, 165, 0, 0.6) !important;
                color: #000 !important;
                font-weight: bold !important;
            }

            .miw_json-formatter-wrapper .miw_error-container {
                background: ${CONFIG.DARK_MODE ? 'rgba(255, 0, 0, 0.1)' : 'rgba(255, 255, 255, 0.05)'} !important;
                border: 1px solid #ff4444 !important;
                border-radius: 6px !important;
                padding: 20px !important;
                margin: 20px !important;
                font-family: monospace !important;
            }

            .miw_json-formatter-wrapper .miw_error-title {
                color: #ff4444 !important;
                font-size: 16px !important;
                margin-bottom: 10px !important;
                font-weight: bold !important;
            }

            /* 原文显示样式(带miw_前缀) */
            .miw_json-formatter-wrapper .miw_json-raw-view {
                border-radius: 4px !important;
                padding: 20px !important;
                margin: 20px 0 !important;
                font-family: 'Courier New', monospace !important;
                white-space: pre-wrap !important;
                word-break: break-all !important;
                max-height: calc(100vh - 80px) !important;
                overflow-y: auto !important;
                font-size: 13px !important;
                line-height: 1.5 !important;
            }

            /* 视图切换容器(带miw_前缀) */
            .miw_json-formatter-wrapper .miw_view-container {
                width: 100% !important;
                height: 100% !important;
            }
        `;

        // 仅在脚本激活时添加样式
        if (globalFormatter?.isActive) {
            document.head.appendChild(styleElement);
        }
    }

    // JSON提取类(逻辑不变)
    class JSONExtractor {
        static extractJSON(content) {
            if (!content || content.length < CONFIG.MIN_JSON_LENGTH) return null;
            try {
                const parsed = JSON.parse(content);
                if (parsed && (typeof parsed === 'object' || Array.isArray(parsed))) {
                    return content.trim();
                }
            } catch (e) {}

            const bestMatch = this.findCompleteJson(content);
            if (bestMatch) {
                try {
                    const parsed = JSON.parse(bestMatch);
                    if (parsed && (typeof parsed === 'object' || Array.isArray(parsed))) {
                        return bestMatch;
                    }
                } catch (e) {}
            }

            try {
                const repaired = this.safeRepair(content);
                const parsed = JSON.parse(repaired);
                if (parsed && (typeof parsed === 'object' || Array.isArray(parsed))) {
                    return repaired;
                }
            } catch (e) {}
            return null;
        }

        static findCompleteJson(content) {
            const jsonStartIndices = [];
            for (let i = 0; i < content.length; i++) {
                if (content[i] === '{' || content[i] === '[') jsonStartIndices.push(i);
            }

            for (const start of jsonStartIndices) {
                let stack = [], inString = false, escaped = false, end = start;
                for (let i = start; i < content.length; i++) {
                    const char = content[i];
                    if (escaped) { escaped = false; continue; }
                    if (char === '\\') { escaped = true; continue; }
                    if (char === '"') { inString = !inString; continue; }

                    if (!inString) {
                        if (char === '{' || char === '[') stack.push(char);
                        else if (char === '}' || char === ']') {
                            if (stack.length === 0) break;
                            const last = stack.pop();
                            if ((last === '{' && char !== '}') || (last === '[' && char !== ']')) {
                                stack.length = 0; break;
                            }
                        }
                    }

                    if (stack.length === 0 && !inString) {
                        end = i;
                        const candidate = content.substring(start, end + 1).trim();
                        try {
                            const parsed = JSON.parse(candidate);
                            if (parsed && (typeof parsed === 'object' || Array.isArray(parsed))) {
                                return candidate;
                            }
                        } catch (e) { continue; }
                    }
                }
            }
            return null;
        }

        static safeRepair(content) {
            let repaired = content.trim();
            repaired = repaired.replace(/^\ufeff/, '')
                               .replace(/<!--[\s\S]*?-->/g, '')
                               .replace(/\/\/[^\r\n]*/g, '')
                               .replace(/\/\*[\s\S]*?\*\//g, '')
                               .replace(/,\s*([}\]])/g, '$1')
                               .replace(/[\x00-\x1F\x7F]/g, '')
                               .replace(/^[a-zA-Z_$][a-zA-Z0-9_$]*\(\s*([\s\S]*?)\s*\);?$/, '$1');
            return repaired;
        }

        static isJsonResponse() {
            const contentType = (document.contentType || '').toLowerCase();
            if (contentType.includes('text/html')) return false;

            const validTypes = ['application/json', 'text/json', 'application/javascript', 'text/javascript', 'application/ld+json', 'text/plain'];
            const hasValidType = validTypes.some(type => contentType.includes(type));

            const bodyTrimmed = document.body.textContent?.trim() || '';
            const hasJsonStructure = (bodyTrimmed.startsWith('{') || bodyTrimmed.startsWith('[')) &&
                                    !bodyTrimmed.startsWith('<html') && !bodyTrimmed.startsWith('<head') && !bodyTrimmed.startsWith('<body');

            if (!hasValidType && !hasJsonStructure) return false;

            const extracted = this.extractJSON(bodyTrimmed);
            return !!extracted;
        }

        static isNonJsonFile() {
            const url = window.location.href.toLowerCase();
            const nonJsonExts = ['.js', '.ts', '.vue', '.jsx', '.tsx', '.php', '.py'];
            return nonJsonExts.some(ext => url.endsWith(ext));
        }
    }

    // JSON格式化核心类(所有元素类名添加miw_前缀)
    class LocalJSONFormatter {
        constructor() {
            this.container = null;
            this.jsonData = null;
            this.originalContent = null;
            this.searchTerm = '';
            this.lineCounter = 1;
            this.isActive = false;
            this.isRendering = false;
            this.matchNodes = [];
            this.currentMatchIndex = -1;
            this.contentWrapper = null;
            this.isRawView = false;
        }

        isJsonContent() {
            if (this.isActive || document.querySelector('.miw_json-formatter-wrapper')) return false;
            return JSONExtractor.isJsonResponse();
        }

        getPageContent() {
            if (document.querySelector('.miw_json-formatter-wrapper')) return null;
            let content = '';

            const preCodeTags = document.querySelectorAll('pre, code');
            for (const el of preCodeTags) {
                const text = el.textContent?.trim() || '';
                if (text.length >= CONFIG.MIN_JSON_LENGTH) {
                    content = text; break;
                }
            }

            if (!content) {
                content = document.body.textContent?.trim() || '';
            }

            this.originalContent = content;
            const jsonContent = JSONExtractor.extractJSON(content);
            return jsonContent || content;
        }

        parseJsonSafely(content) {
            if (!content) throw new Error('待解析内容为空');
            try {
                const parsed = JSON.parse(content);
                if (parsed && (typeof parsed === 'object' || Array.isArray(parsed))) {
                    return parsed;
                }
                return null;
            } catch (e) {
                return null;
            }
        }

        getLineNumber() {
            const num = this.lineCounter++;
            if (!CONFIG.SHOW_LINE_NUMBERS) {
                return '';
            }
            return `<span class="miw_line-number">${num}</span>`;
        }

        formatJson(data, depth = 0) {
            let html = '';
            if (this.isRendering && this.lineCounter > CONFIG.MAX_RENDER_SIZE) {
                return `<span class="miw_json-ellipsis">数据过大,已截断显示</span>`;
            }

            switch (true) {
                case data === null:
                    html = `<span class="miw_json-null">null</span>`;
                    break;
                case typeof data === 'boolean':
                    html = `<span class="miw_json-boolean">${data}</span>`;
                    break;
                case typeof data === 'number':
                    html = `<span class="miw_json-number">${data}</span>`;
                    break;
                case typeof data === 'string': {
                    const escaped = this.escapeHtml(data);
                    html = `<span class="miw_json-string">"${escaped}"</span>`;
                    break;
                }
                case Array.isArray(data):
                    html = this.formatArray(data, depth);
                    break;
                case typeof data === 'object':
                    html = this.formatObject(data, depth);
                    break;
                default:
                    html = `<span class="miw_json-string">${this.escapeHtml(String(data))}</span>`;
            }
            return html;
        }

        formatArray(data, depth) {
            if (data.length === 0) return `<span class="miw_json-punctuation">[</span><span class="miw_json-punctuation">]</span>`;

            const isCollapsed = depth >= CONFIG.COLLAPSE_LEVEL;
            const nodeCount = CONFIG.SHOW_NODE_COUNT ? `<span class="miw_json-node-count">${data.length} 项</span>` : '';

            let html = `<div class="miw_json-line">${this.getLineNumber()}<div class="miw_line-content">`;
            html += `<div class="miw_json-collapsible ${isCollapsed ? '' : 'expanded'}" data-depth="${depth}" data-type="array">`;
            html += `<span class="miw_json-punctuation">[</span>${nodeCount}`;
            html += `</div></div></div>`;

            html += `<div class="miw_json-children" ${isCollapsed ? 'style="display:none;"' : ''}>`;
            data.forEach((item, idx) => {
                html += `<div class="miw_json-line">${this.getLineNumber()}<div class="miw_line-content">`;
                html += this.formatJson(item, depth + 1);
                if (idx < data.length - 1) html += `<span class="miw_json-punctuation">,</span>`;
                html += `</div></div>`;
            });
            html += `</div>`;

            html += `<div class="miw_json-line">${this.getLineNumber()}<div class="miw_line-content">`;
            html += `<span class="miw_json-punctuation">]</span>`;
            html += `</div></div>`;
            return html;
        }

        formatObject(data, depth) {
            const keys = Object.keys(data);
            if (keys.length === 0) return `<span class="miw_json-punctuation">{</span><span class="miw_json-punctuation">}</span>`;

            const isCollapsed = depth >= CONFIG.COLLAPSE_LEVEL;
            const nodeCount = CONFIG.SHOW_NODE_COUNT ? `<span class="miw_json-node-count">${keys.length} 个属性</span>` : '';

            let html = `<div class="miw_json-line">${this.getLineNumber()}<div class="miw_line-content">`;
            html += `<div class="miw_json-collapsible ${isCollapsed ? '' : 'expanded'}" data-depth="${depth}" data-type="object">`;
            html += `<span class="miw_json-punctuation">{</span>${nodeCount}`;
            html += `</div></div></div>`;

            html += `<div class="miw_json-children" ${isCollapsed ? 'style="display:none;"' : ''}>`;
            keys.forEach((key, idx) => {
                html += `<div class="miw_json-line">${this.getLineNumber()}<div class="miw_line-content">`;
                html += `<span class="miw_json-key">"${this.escapeHtml(key)}"</span><span class="miw_json-punctuation">:</span> `;
                html += this.formatJson(data[key], depth + 1);
                if (idx < keys.length - 1) html += `<span class="miw_json-punctuation">,</span>`;
                html += `</div></div>`;
            });
            html += `</div>`;

            html += `<div class="miw_json-line">${this.getLineNumber()}<div class="miw_line-content">`;
            html += `<span class="miw_json-punctuation">}</span>`;
            html += `</div></div>`;
            return html;
        }

        escapeHtml(str) {
            return str.replace(/&/g, '&amp;')
                      .replace(/</g, '&lt;')
                      .replace(/>/g, '&gt;')
                      .replace(/"/g, '&quot;')
                      .replace(/'/g, '&#039;');
        }

        // ========== 核心修改:工具栏类名添加miw_前缀 ==========
        createToolbar() {
            const toolbar = document.createElement('div');
            toolbar.className = 'miw_json-toolbar'; // 添加miw_前缀
            const lineBtnActive = CONFIG.SHOW_LINE_NUMBERS ? 'active' : '';
            const lineBtnText = CONFIG.SHOW_LINE_NUMBERS ? '行号:开' : '行号:关';
            const rawBtnText = this.isRawView ? '显示格式化' : '显示原文';

            const searchContainer = `
                <div class="miw_search-container"> <!-- 添加miw_前缀 -->
                    <input type="text" class="miw_search-box" placeholder="搜索关键词..." /> <!-- 添加miw_前缀 -->
                    <button class="miw_search-nav-btn prev-match-btn">↑</button> <!-- 添加miw_前缀 -->
                    <button class="miw_search-nav-btn next-match-btn">↓</button> <!-- 添加miw_前缀 -->
                    <span class="miw_search-count">0/0</span> <!-- 添加miw_前缀 -->
                </div>
            `;

            toolbar.innerHTML = `
                ${searchContainer}
                <button class="miw_toolbar-button expand-btn">展开全部</button> <!-- 添加miw_前缀 -->
                <button class="miw_toolbar-button collapse-btn">折叠全部</button> <!-- 添加miw_前缀 -->
                <button class="miw_toolbar-button copy-btn">复制内容</button> <!-- 添加miw_前缀 -->
                <button class="miw_toolbar-button theme-btn">切换主题</button> <!-- 添加miw_前缀 -->
                <button class="miw_toolbar-button line-numbers-btn ${lineBtnActive}">${lineBtnText}</button> <!-- 添加miw_前缀 -->
                <button class="miw_toolbar-button raw-view-btn">${rawBtnText}</button> <!-- 添加miw_前缀 -->
                <button class="miw_toolbar-button close-btn">关闭</button> <!-- 添加miw_前缀 -->
            `;
            return toolbar;
        }

        // ========== 核心修改:通知元素类名添加miw_前缀 ==========
        createNotification() {
            const note = document.createElement('div');
            note.className = 'miw_json-notification'; // 添加miw_前缀
            return note;
        }

        showNotification(message, duration = 2000) {
            // 选择器添加miw_前缀
            const note = document.querySelector('.miw_json-formatter-wrapper .miw_json-notification');
            if (!note) return;
            note.textContent = message;
            note.classList.add('show');
            setTimeout(() => note.classList.remove('show'), duration);
        }

        async copyToClipboard(text, msg = '已复制到剪贴板') {
            try {
                await navigator.clipboard.writeText(text);
                this.showNotification(msg);
            } catch (err) {
                this.showNotification('复制失败,请手动复制', 3000);
            }
        }

        escapeRegExp(str) {
            return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        }

        expandAllCollapsibles() {
            // 选择器添加miw_前缀
            const allCollapsibles = this.contentWrapper.querySelectorAll('.miw_json-collapsible');
            allCollapsibles.forEach(el => {
                el.classList.add('expanded');
                const childContainer = el.closest('.miw_json-line')?.nextElementSibling; // 添加miw_前缀
                if (childContainer?.classList.contains('miw_json-children')) { // 添加miw_前缀
                    childContainer.style.display = 'block';
                }
            });
        }

        collectAllTextNodes(rootNode) {
            const textNodes = [];
            const traverse = (node) => {
                if (!node) return;
                const childNodes = node.childNodes;
                for (let i = 0; i < childNodes.length; i++) {
                    const child = childNodes[i];
                    if (child.nodeType === Node.TEXT_NODE) {
                        textNodes.push(child);
                    } else if (child.nodeType === Node.ELEMENT_NODE && !child.classList.contains('miw_json-ellipsis')) { // 添加miw_前缀
                        traverse(child);
                    }
                }
            };
            traverse(rootNode);
            return textNodes;
        }

        searchJSON(term) {
            if (this.isRawView) return;

            this.searchTerm = term.trim();
            this.matchNodes = [];
            this.currentMatchIndex = -1;

            // 选择器添加miw_前缀
            const highlights = this.contentWrapper.querySelectorAll('.miw_search-highlight');
            highlights.forEach(hl => {
                const parent = hl.parentNode;
                while (hl.firstChild) parent.insertBefore(hl.firstChild, hl);
                parent.removeChild(hl);
                parent.normalize();
            });

            this.updateSearchCount();
            if (!this.searchTerm) return;

            this.expandAllCollapsibles();

            const escapedTerm = this.escapeRegExp(this.searchTerm);
            const regex = new RegExp(escapedTerm, 'gi');

            const allTextNodes = this.collectAllTextNodes(this.contentWrapper);
            allTextNodes.forEach(textNode => {
                const originalText = textNode.textContent;
                if (!originalText) return;

                regex.lastIndex = 0;
                const matches = [];
                let match;

                while ((match = regex.exec(originalText)) !== null) {
                    matches.push({
                        start: match.index,
                        end: match.index + match[0].length,
                        value: match[0]
                    });
                }

                if (matches.length === 0) return;

                const fragment = document.createDocumentFragment();
                let lastPos = 0;

                matches.forEach(m => {
                    if (m.start > lastPos) {
                        fragment.appendChild(document.createTextNode(originalText.slice(lastPos, m.start)));
                    }
                    const hlSpan = document.createElement('span');
                    hlSpan.className = 'miw_search-highlight'; // 添加miw_前缀
                    hlSpan.textContent = m.value;
                    fragment.appendChild(hlSpan);
                    this.matchNodes.push(hlSpan);
                    lastPos = m.end;
                });

                if (lastPos < originalText.length) {
                    fragment.appendChild(document.createTextNode(originalText.slice(lastPos)));
                }

                textNode.parentNode.replaceChild(fragment, textNode);
            });

            this.updateSearchCount();
            if (this.matchNodes.length > 0) {
                this.currentMatchIndex = 0;
                this.highlightCurrentMatch();
                this.scrollToCurrentMatch();
                this.showNotification(`找到 ${this.matchNodes.length} 个匹配项`, 2000);
            } else {
                this.showNotification('未找到匹配内容', 2000);
            }
        }

        updateSearchCount() {
            // 选择器添加miw_前缀
            const countEl = this.container.querySelector('.miw_search-count');
            if (!countEl) return;
            const total = this.matchNodes.length;
            const current = this.currentMatchIndex >= 0 ? this.currentMatchIndex + 1 : 0;
            countEl.textContent = `${current}/${total}`;
        }

        highlightCurrentMatch() {
            // 类名添加miw_前缀
            this.matchNodes.forEach(node => node.classList.remove('current'));
            if (this.currentMatchIndex >= 0 && this.currentMatchIndex < this.matchNodes.length) {
                this.matchNodes[this.currentMatchIndex].classList.add('current');
            }
        }

        scrollToCurrentMatch() {
            if (this.currentMatchIndex < 0 || this.currentMatchIndex >= this.matchNodes.length) return;
            const target = this.matchNodes[this.currentMatchIndex];
            target.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
        }

        prevMatch() {
            if (this.isRawView) {
                this.showNotification('原文视图不支持搜索', 1500);
                return;
            }
            if (this.matchNodes.length === 0) { this.showNotification('无匹配内容', 1500); return; }
            this.currentMatchIndex = this.currentMatchIndex <= 0 ? this.matchNodes.length - 1 : this.currentMatchIndex - 1;
            this.highlightCurrentMatch();
            this.scrollToCurrentMatch();
            this.updateSearchCount();
        }

        nextMatch() {
            if (this.isRawView) {
                this.showNotification('原文视图不支持搜索', 1500);
                return;
            }
            if (this.matchNodes.length === 0) { this.showNotification('无匹配内容', 1500); return; }
            this.currentMatchIndex = this.currentMatchIndex >= this.matchNodes.length - 1 ? 0 : this.currentMatchIndex + 1;
            this.highlightCurrentMatch();
            this.scrollToCurrentMatch();
            this.updateSearchCount();
        }

        toggleRawView() {
            this.isRawView = !this.isRawView;
            // 选择器添加miw_前缀
            const viewContainer = this.container.querySelector('.miw_view-container');

            if (this.isRawView) {
                // 类名添加miw_前缀
                viewContainer.innerHTML = `<div class="miw_json-raw-view">${this.escapeHtml(this.originalContent)}</div>`;
                this.showNotification('已切换至原文视图', 1500);
            } else {
                this.lineCounter = 1;
                const formattedContent = this.jsonData ? this.formatJson(this.jsonData) : this.escapeHtml(this.originalContent);
                viewContainer.innerHTML = formattedContent;
                this.contentWrapper = viewContainer;
                if (this.searchTerm) this.searchJSON(this.searchTerm);
                this.showNotification('已切换至格式化视图', 1500);
            }

            // 选择器添加miw_前缀
            const rawBtn = this.container.querySelector('.miw_raw-view-btn');
            rawBtn.textContent = this.isRawView ? '显示格式化' : '显示原文';
        }

        bindEvents() {
            // 选择器添加miw_前缀
            const toolbar = this.container.querySelector('.miw_json-toolbar');
            if (!toolbar) return;

            // 选择器添加miw_前缀
            const searchBox = toolbar.querySelector('.miw_search-box');
            searchBox.addEventListener('input', (e) => this.searchJSON(e.target.value));
            searchBox.addEventListener('keydown', (e) => {
                if (e.key === 'Enter') {
                    e.preventDefault();
                    e.shiftKey ? this.prevMatch() : this.nextMatch();
                }
            });

            // 选择器添加miw_前缀
            toolbar.querySelector('.miw_search-nav-btn.prev-match-btn').addEventListener('click', () => this.prevMatch());
            toolbar.querySelector('.miw_search-nav-btn.next-match-btn').addEventListener('click', () => this.nextMatch());

            // 选择器添加miw_前缀
            toolbar.querySelector('.miw_toolbar-button.expand-btn').addEventListener('click', () => {
                if (this.isRawView) {
                    this.showNotification('原文视图不支持展开', 1500);
                    return;
                }
                this.expandAllCollapsibles();
                this.showNotification('已展开全部节点');
            });

            // 选择器添加miw_前缀
            toolbar.querySelector('.miw_toolbar-button.collapse-btn').addEventListener('click', () => {
                if (this.isRawView) {
                    this.showNotification('原文视图不支持折叠', 1500);
                    return;
                }
                // 选择器添加miw_前缀
                this.container.querySelectorAll('.miw_json-collapsible').forEach(el => {
                    el.classList.remove('expanded');
                    // 选择器添加miw_前缀
                    const child = el.closest('.miw_json-line')?.nextElementSibling;
                    if (child?.classList.contains('miw_json-children')) child.style.display = 'none'; // 添加miw_前缀
                });
                this.showNotification('已折叠全部节点');
            });

            // 选择器添加miw_前缀
            toolbar.querySelector('.miw_toolbar-button.copy-btn').addEventListener('click', async () => {
                const copyContent = this.isRawView || !this.jsonData
                    ? this.originalContent
                    : JSON.stringify(this.jsonData, null, 2);
                await this.copyToClipboard(copyContent, '已复制内容到剪贴板');
            });

            // 选择器添加miw_前缀
            toolbar.querySelector('.miw_toolbar-button.theme-btn').addEventListener('click', () => {
                CONFIG.DARK_MODE = !CONFIG.DARK_MODE;
                updateStyles();
                this.reRender();
                this.showNotification(`已切换至${CONFIG.DARK_MODE ? '暗色' : '亮色'}主题`);
            });

            // 选择器添加miw_前缀
            toolbar.querySelector('.miw_toolbar-button.line-numbers-btn').addEventListener('click', () => {
                if (this.isRawView) {
                    this.showNotification('原文视图不支持行号切换', 1500);
                    return;
                }
                CONFIG.SHOW_LINE_NUMBERS = !CONFIG.SHOW_LINE_NUMBERS;
                updateStyles();
                this.lineCounter = 1;
                // 选择器添加miw_前缀
                const lineBtn = toolbar.querySelector('.miw_toolbar-button.line-numbers-btn');
                lineBtn.textContent = CONFIG.SHOW_LINE_NUMBERS ? '行号:开' : '行号:关';
                lineBtn.classList.toggle('active', CONFIG.SHOW_LINE_NUMBERS);
                this.reRender();
                this.showNotification(`行号${CONFIG.SHOW_LINE_NUMBERS ? '开启' : '关闭'}`);
            });

            // 选择器添加miw_前缀
            toolbar.querySelector('.miw_toolbar-button.raw-view-btn').addEventListener('click', () => this.toggleRawView());

            // 选择器添加miw_前缀
            toolbar.querySelector('.miw_toolbar-button.close-btn').addEventListener('click', () => this.closeFormatter());

            this.container.addEventListener('click', e => {
                if (this.isRawView) return;
                // 选择器添加miw_前缀
                const target = e.target.closest('.miw_json-collapsible');
                if (!target) return;
                const isExpanded = target.classList.contains('expanded');
                // 选择器添加miw_前缀
                const childContainer = target.closest('.miw_json-line')?.nextElementSibling;
                if (!childContainer?.classList.contains('miw_json-children')) return; // 添加miw_前缀

                if (isExpanded) {
                    target.classList.remove('expanded');
                    childContainer.style.display = 'none';
                } else {
                    target.classList.add('expanded');
                    childContainer.style.display = 'block';
                }
            });
        }

        closeFormatter() {
            this.isActive = false;
            // 移除脚本样式
            if (styleElement && styleElement.parentNode) {
                styleElement.parentNode.removeChild(styleElement);
            }
            window.history.length > 1 ? window.history.back() : window.location.reload();
        }

        showError(error) {
            // 所有类名添加miw_前缀
            this.container.innerHTML = `
                <div class="miw_error-container">
                    <div class="miw_error-title">内容处理失败</div>
                    <div class="miw_error-message">${this.escapeHtml(error.message)}</div>
                    <div style="margin-top:15px;">
                        <button id="retry-btn" class="miw_toolbar-button" style="margin-right:8px;">重试</button>
                        <button id="raw-btn" class="miw_toolbar-button" style="margin-right:8px;">查看原文</button>
                        <button id="back-btn" class="miw_toolbar-button">返回页面</button>
                    </div>
                    ${this.originalContent ? `<div class="miw_json-raw-view" style="margin-top:15px;">${this.escapeHtml(this.originalContent)}</div>` : ''}
                </div>
            `;
            setTimeout(() => {
                document.getElementById('retry-btn')?.addEventListener('click', () => this.apply());
                document.getElementById('raw-btn')?.addEventListener('click', () => {
                    this.container.innerHTML = `<div class="miw_json-raw-view">${this.escapeHtml(this.originalContent)}</div>`;
                });
                document.getElementById('back-btn')?.addEventListener('click', () => this.closeFormatter());
            }, 0);
        }

        reRender() {
            if (!this.isActive || !this.contentWrapper) return;

            if (this.isRawView) {
                updateStyles();
                return;
            }

            this.lineCounter = 1;
            const formattedContent = this.jsonData ? this.formatJson(this.jsonData) : this.escapeHtml(this.originalContent);
            this.contentWrapper.innerHTML = formattedContent;
            if (this.searchTerm) this.searchJSON(this.searchTerm);
        }

        apply() {
            if (this.isActive || this.isRendering) return;
            this.isRendering = true;

            try {
                if (!this.isJsonContent()) {
                    this.isRendering = false;
                    return;
                }

                const rawContent = this.getPageContent();
                if (!rawContent) {
                    this.isRendering = false;
                    return;
                }

                this.jsonData = this.parseJsonSafely(rawContent);

                // 核心修改:容器类名添加miw_前缀
                if (!this.container) {
                    this.container = document.createElement('div');
                    this.container.className = 'miw_json-formatter-wrapper'; // 添加miw_前缀
                }
                document.body.replaceChildren(this.container);
                this.isActive = true;

                // 激活后加载样式
                updateStyles();

                // 核心修改:内容容器类名添加miw_前缀
                this.contentWrapper = document.createElement('div');
                this.contentWrapper.className = 'miw_view-container'; // 添加miw_前缀

                this.isRawView = !this.jsonData || JSONExtractor.isNonJsonFile();
                if (this.isRawView) {
                    // 类名添加miw_前缀
                    this.contentWrapper.innerHTML = `<div class="miw_json-raw-view">${this.escapeHtml(this.originalContent)}</div>`;
                } else {
                    this.lineCounter = 1;
                    const jsonContent = this.formatJson(this.jsonData);
                    this.contentWrapper.innerHTML = jsonContent;
                }

                this.container.innerHTML = '';
                this.container.appendChild(this.createToolbar());
                this.container.appendChild(this.contentWrapper);
                this.container.appendChild(this.createNotification());

                this.bindEvents();

                if (this.isRawView) {
                    this.showNotification('非JSON文件,默认显示原文', 2000);
                } else {
                    this.showNotification('JSON 格式化完成', 1500);
                }
            } catch (err) {
                console.error('内容处理错误:', err);
                if (!this.container) {
                    this.container = document.createElement('div');
                    this.container.className = 'miw_json-formatter-wrapper'; // 添加miw_前缀
                    document.body.replaceChildren(this.container);
                    this.isActive = true;
                    updateStyles();
                }
                this.showError(err);
            } finally {
                this.isRendering = false;
            }
        }
    }

    // 监听页面动态内容
    function setupContentObserver() {
        if (mutationObserver) return;
        mutationObserver = new MutationObserver(mutations => {
            if (globalFormatter?.isActive) return;
            for (const mut of mutations) {
                if (mut.addedNodes.length && JSONExtractor.isJsonResponse()) {
                    setTimeout(() => {
                        globalFormatter?.apply();
                    }, 300);
                    break;
                }
            }
        });
        mutationObserver.observe(document.body, { childList: true, subtree: true, characterData: true });
    }

    // 注册脚本菜单
    function registerMenuCommands() {
        if (typeof GM_registerMenuCommand !== 'function') return;
        GM_registerMenuCommand('🔍 格式化/查看原文', () => {
            if (!globalFormatter) globalFormatter = new LocalJSONFormatter();
            globalFormatter.apply();
        });
        GM_registerMenuCommand('📋 复制当前内容', () => {
            if (globalFormatter?.isActive) {
                const copyContent = globalFormatter.isRawView || !globalFormatter.jsonData
                    ? globalFormatter.originalContent
                    : JSON.stringify(globalFormatter.jsonData, null, 2);
                globalFormatter.copyToClipboard(copyContent);
            }
        });
        GM_registerMenuCommand('🌓 切换明暗主题', () => {
            if (!globalFormatter?.isActive) return;
            CONFIG.DARK_MODE = !CONFIG.DARK_MODE;
            updateStyles();
            if (globalFormatter?.isActive) globalFormatter.reRender();
        });
        GM_registerMenuCommand('🔢 切换行号显示', () => {
            if (!globalFormatter?.isActive || globalFormatter?.isRawView) return;
            CONFIG.SHOW_LINE_NUMBERS = !CONFIG.SHOW_LINE_NUMBERS;
            updateStyles();
            if (globalFormatter?.isActive) {
                globalFormatter.lineCounter = 1;
                globalFormatter.reRender();
            }
        });
    }

    // 初始化
    function init() {
        globalFormatter = new LocalJSONFormatter();
        registerMenuCommands();
        setupContentObserver();
        if (CONFIG.AUTO_FORMAT) {
            setTimeout(() => {
                globalFormatter.apply();
            }, 500);
        }
    }

    init();
})();