Extracteur de Commentaires Discourse

Outil d'extraction de commentaires avancé pour les forums Discourse avec interface TailwindCSS moderne, filtrage intelligent, extraction d'emails et capacités d'export de données. Accès réservé aux auteurs, vérification API.

// ==UserScript==
// @name         Discourse Comment Extractor | Discourse 评论提取器
// @name:zh-CN   Discourse 评论提取器
// @name:en      Discourse Comment Extractor
// @name:ja      Discourse コメント抽出器
// @name:ko      Discourse 댓글 추출기
// @name:fr      Extracteur de Commentaires Discourse
// @name:de      Discourse Kommentar-Extraktor
// @name:es      Extractor de Comentarios de Discourse
// @name:ru      Извлекатель Комментариев Discourse
// @namespace    https://github.com/discourse-tools/comment-extractor
// @version      1.9.0
// @description  Advanced comment extraction tool for Discourse forums with modern TailwindCSS interface, smart filtering, email extraction, and data export capabilities. Author-only access with API verification.
// @description:zh-CN  提取 Discourse 帖子下的所有评论,支持楼层范围、随机提取、邮箱提取和数据导出功能。现代化TailwindCSS界面设计,仅限帖子作者使用,API权限验证。
// @description:en     Advanced comment extraction tool for Discourse forums with modern TailwindCSS interface, smart filtering, email extraction, and data export capabilities. Author-only access with API verification.
// @description:ja     Discourse フォーラム用の高度なコメント抽出ツール。モダンな TailwindCSS インターフェース、スマートフィルタリング、メール抽出、データエクスポート機能付き。作成者のみアクセス可能、API認証。
// @description:ko     Discourse 포럼용 고급 댓글 추출 도구. 현대적인 TailwindCSS 인터페이스, 스마트 필터링, 이메일 추출, 데이터 내보내기 기능. 작성자 전용 액세스, API 인증.
// @description:fr     Outil d'extraction de commentaires avancé pour les forums Discourse avec interface TailwindCSS moderne, filtrage intelligent, extraction d'emails et capacités d'export de données. Accès réservé aux auteurs, vérification API.
// @description:de     Erweiterte Kommentar-Extraktions-Tool für Discourse-Foren mit modernem TailwindCSS-Interface, intelligentem Filtern, E-Mail-Extraktion und Datenexport-Funktionen. Nur für Autoren zugänglich, API-Verifizierung.
// @description:es     Herramienta avanzada de extracción de comentarios para foros Discourse con interfaz TailwindCSS moderna, filtrado inteligente, extracción de emails y capacidades de exportación de datos. Solo acceso para autores, verificación API.
// @description:ru     Продвинутый инструмент извлечения комментариев для форумов Discourse с современным интерфейсом TailwindCSS, умной фильтрацией, извлечением email и возможностями экспорта данных. Доступ только для авторов, API-верификация.
// @author       dext7r
// @license      MIT
// @homepageURL  https://linux.do/t/topic/705152
// @supportURL   https://linux.do/t/topic/705152
// @icon         data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgo8c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojNjY3ZWVhO3N0b3Atb3BhY2l0eToxIiAvPgo8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiM3NjRiYTI7c3RvcC1vcGFjaXR5OjEiIC8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPGV4dGdvbiB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHg9IjIiIHk9IjIiIHJ4PSIxMiIgZmlsbD0idXJsKCNncmFkaWVudCkiLz4KPHN2ZyB4PSIxNiIgeT0iMTYiIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0id2hpdGUiPgo8cGF0aCBkPSJNOCAxMGg4TTE4IDE0aDZNNiA0aDEyYTIgMiAwIDAxMiAydjEyYTIgMiAwIDAxLTIgMkg2YTIgMiAwIDAxLTItMlY2YTIgMiAwIDAxMi0yeiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIi8+Cjwvc3ZnPgo8L3N2Zz4=
// @icon64       data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgo8c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojNjY3ZWVhO3N0b3Atb3BhY2l0eToxIiAvPgo8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiM3NjRiYTI7c3RvcC1vcGFjaXR5OjEiIC8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPGV4dGdvbiB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHg9IjIiIHk9IjIiIHJ4PSIxMiIgZmlsbD0idXJsKCNncmFkaWVudCkiLz4KPHN2ZyB4PSIxNiIgeT0iMTYiIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgZmlsbD0id2hpdGUiPgo8cGF0aCBkPSJNOCAxMGg4TTE4IDE0aDZNNiA0aDEyYTIgMiAwIDAxMiAydjEyYTIgMiAwIDAxLTIgMkg2YTIgMiAwIDAxLTItMlY2YTIgMiAwIDAxMi0yeiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIi8+Cjwvc3ZnPgo8L3N2Zz4=
// @compatible   chrome >=90
// @compatible   firefox >=88
// @compatible   edge >=90
// @compatible   safari >=14
// @compatible   opera >=76
// 
// @match        https://*/t/*
// @match        https://*/topic/*
// @match        https://*/topics/*
// @match        https://*/discussion/*
// @match        https://*/discussions/*
// @match        http://*/t/*
// @match        http://*/topic/*
// @match        http://*/topics/*
// 
// International Discourse Sites
// @match        https://community.*/t/*
// @match        https://discuss.*/t/*
// @match        https://forum.*/t/*
// @match        https://forums.*/t/*
// @match        https://support.*/t/*
// @match        https://help.*/t/*
// @match        https://talk.*/t/*
// @match        https://chat.*/t/*
// @match        https://discourse.*/t/*
// 
// Popular Discourse Instances
// @match        https://meta.discourse.org/t/*
// @match        https://try.discourse.org/t/*
// @match        https://blog.discourse.org/t/*
// @match        https://developers.discourse.org/t/*
// @match        https://blog.codinghorror.com/t/*
// @match        https://what.thedailywtf.com/t/*
// @match        https://discuss.pytorch.org/t/*
// @match        https://discuss.tensorflow.org/t/*
// @match        https://discuss.atom.io/t/*
// @match        https://discuss.brew.sh/t/*
// @match        https://discuss.elastic.co/t/*
// @match        https://discuss.circleci.com/t/*
// @match        https://discuss.gradle.org/t/*
// @match        https://discuss.kotlinlang.org/t/*
// @match        https://discuss.ocaml.org/t/*
// @match        https://discuss.python.org/t/*
// @match        https://discuss.swift.org/t/*
// @match        https://discuss.vuejs.org/t/*
// @match        https://discuss.wxpython.org/t/*
// @match        https://discuss.yarnpkg.com/t/*
// @match        https://community.frame.work/t/*
// @match        https://community.fly.io/t/*
// @match        https://community.cloudflare.com/t/*
// @match        https://community.postman.com/t/*
// @match        https://community.render.com/t/*
// @match        https://community.spotify.com/t/*
// @match        https://community.openai.com/t/*
// @match        https://developers.google.com/t/*
// @match        https://forum.arduino.cc/t/*
// @match        https://forum.gitlab.com/t/*
// @match        https://forum.freecodecamp.org/t/*
// @match        https://forum.manjaro.org/t/*
// @match        https://forum.endeavouros.com/t/*
// @match        https://forum.kde.org/t/*
// @match        https://forum.snapcraft.io/t/*
// @match        https://forum.unity.com/t/*
// 
// Chinese Discourse Communities
// @match        https://forum.ubuntu.org.cn/t/*
// @match        https://forum.deepin.org/t/*
// @match        https://bbs.archlinuxcn.org/t/*
// @match        https://discuss.flarum.org.cn/t/*
// @match        https://forum.gamer.com.tw/t/*
// @match        https://community.jiumodiary.com/t/*
// @match        https://forum.china-scratch.com/t/*
// @match        https://forum.freebuf.com/t/*
// @match        https://bbs.huaweicloud.com/t/*
// @match        https://developer.aliyun.com/t/*
// @match        https://juejin.cn/t/*
// @match        https://segmentfault.com/t/*
// 
// European Discourse Sites
// @match        https://forum.ubuntu-fr.org/t/*
// @match        https://forum.ubuntu-it.org/t/*
// @match        https://forum.ubuntu-es.org/t/*
// @match        https://forum.ubuntu.de/t/*
// @match        https://forum.manjaro.de/t/*
// @match        https://forum.opensuse.org/t/*
// @match        https://discuss.kde.org/t/*
// @match        https://forum.fedoraproject.org/t/*
// 
// Japanese Discourse Sites
// @match        https://forum.ubuntulinux.jp/t/*
// @match        https://discuss.elastic.co/t/*
// @match        https://jp.discourse.group/t/*
// 
// Generic Wildcard Patterns (for discovery)
// @match        https://*.discourse.group/t/*
// @match        https://*.discoursehosting.com/t/*
// @match        https://*.discoursecdn.com/t/*
// @match        https://discourse-*.herokuapp.com/t/*
// @match        https://*-discourse.com/t/*
// @match        https://discourse.*.com/t/*
// @match        https://discourse.*.org/t/*
// @match        https://discourse.*.net/t/*
// @match        https://discourse.*.io/t/*
// @match        https://discourse.*.dev/t/*
// 
// @grant        none
// @run-at       document-end
// @noframes
// @require      https://cdn.tailwindcss.com/3.3.0
// 
// @tag          discourse
// @tag          comment
// @tag          extractor
// @tag          forum
// @tag          data-export
// @tag          email-extraction
// @tag          csv
// @tag          json
// @tag          tailwindcss
// @tag          modern-ui
// @tag          author-only
// @tag          api-verification
// ==/UserScript==

(function () {
  'use strict';

  /**
   * Discourse 评论提取器主类
   * 使用现代 JavaScript 类语法和高级编程模式
   */
  class DiscourseCommentExtractor {
    constructor() {
      // 常量配置
      this.config = {
        emailRegex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
        storageKey: 'discourse_extractor_history',
        maxHistoryRecords: 100,
        initDelay: 2000,
        permissionCheckDelay: 1000,
        loadingTimeout: 30000,
        maxLoadAttempts: 30
      };

      // 状态管理
      this.state = {
        isInitialized: false,
        currentUser: null,
        topicAuthor: null,
        hasPermission: false,
        isLoading: false
      };

      // 缓存DOM查询结果
      this.cache = new Map();

      // API管理器
      this.api = new DiscourseAPIManager();

      // 权限管理器
      this.permissionManager = new PermissionManager(this.api);

      // UI管理器
      this.uiManager = new UIManager();

      // 存储管理器
      this.storageManager = new StorageManager(this.config.storageKey, this.config.maxHistoryRecords);

      // 绑定方法
      this.init = this.init.bind(this);
      this.handleExtractClick = this.handleExtractClick.bind(this);
    }

    /**
     * 初始化提取器
     */
    async init() {
      if (this.state.isInitialized) return;

      try {
        console.log('🚀 初始化 Discourse 评论提取器...');

        // 检查是否为 Discourse 论坛
        if (!this.isDiscourse()) {
          console.log('❌ 非 Discourse 论坛,跳过初始化');
          return;
        }

        // 等待页面加载完成
        await this.waitForPageReady();

        // 加载样式
        this.uiManager.loadStyles();

        // 检查权限并创建按钮
        await this.checkPermissionAndCreateButton();

        this.state.isInitialized = true;
        console.log('✅ 评论提取器初始化完成');

      } catch (error) {
        console.error('❌ 初始化失败:', error);
      }
    }

    /**
     * 检查是否为 Discourse 论坛
     */
    isDiscourse() {
      return !!(
        document.querySelector('meta[name="generator"][content*="Discourse"]') ||
        document.querySelector('.topic-post, [data-post-id]') ||
        window.location.pathname.includes('/t/') ||
        document.body.classList.contains('discourse')
      );
    }

    /**
     * 等待页面准备就绪
     */
    async waitForPageReady() {
      return new Promise((resolve) => {
        if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', () => {
            setTimeout(resolve, this.config.initDelay);
          });
        } else {
          setTimeout(resolve, this.config.initDelay);
        }
      });
    }

    /**
     * 检查权限并创建按钮
     */
    async checkPermissionAndCreateButton() {
      try {
        // 获取用户和帖子信息
        const [currentUser, topicAuthor] = await Promise.all([
          this.permissionManager.getCurrentUser(),
          this.permissionManager.getTopicAuthor()
        ]);

        this.state.currentUser = currentUser;
        this.state.topicAuthor = topicAuthor;

        console.log('👤 当前用户:', currentUser);
        console.log('📝 帖子作者:', topicAuthor);

        // 检查权限
        this.state.hasPermission = this.permissionManager.checkPermission(currentUser, topicAuthor);

        console.log('🔒 权限检查结果:', this.state.hasPermission);

        // 创建按钮
        this.uiManager.createButton(this.state.hasPermission, this.handleExtractClick, this.handlePermissionError.bind(this));

      } catch (error) {
        console.error('❌ 权限检查失败:', error);
        this.uiManager.createButton(false, null, this.handlePermissionError.bind(this));
      }
    }

    /**
     * 处理提取按钮点击
     */
    async handleExtractClick() {
      try {
        // 双重权限检查
        if (!await this.revalidatePermission()) {
          await this.handlePermissionError();
          return;
        }

        // 显示配置模态框
        this.uiManager.showConfigModal((config) => {
          this.startExtraction(config);
        });

      } catch (error) {
        console.error('❌ 提取过程失败:', error);
        this.uiManager.showToast('提取失败,请重试', 'error');
      }
    }

    /**
     * 重新验证权限
     */
    async revalidatePermission() {
      const [currentUser, topicAuthor] = await Promise.all([
        this.permissionManager.getCurrentUser(),
        this.permissionManager.getTopicAuthor()
      ]);

      return this.permissionManager.checkPermission(currentUser, topicAuthor);
    }

    /**
     * 处理权限错误
     */
    async handlePermissionError() {
      const [currentUser, topicAuthor] = await Promise.all([
        this.permissionManager.getCurrentUser(),
        this.permissionManager.getTopicAuthor()
      ]);

      this.uiManager.showPermissionError(currentUser, topicAuthor);
    }

    /**
     * 开始提取评论
     */
    async startExtraction(config) {
      if (this.state.isLoading) return;

      this.state.isLoading = true;
      const progressModal = this.uiManager.showLoadingProgress();

      try {
        // 创建评论加载器
        const loader = new CommentLoader(this.api);

        // 加载所有评论
        const comments = await loader.loadAllComments((current, total, attempts) => {
          this.uiManager.updateProgress(current, total, attempts);
        });

        // 创建评论提取器
        const extractor = new CommentExtractor(this.config.emailRegex);

        // 提取评论
        const extractedData = extractor.extractComments({
          comments,
          mode: config.mode,
          startFloor: config.startFloor,
          endFloor: config.endFloor,
          randomCount: config.randomCount,
          extractEmails: config.extractEmails
        });

        // 关闭进度模态框
        this.uiManager.closeModal(progressModal);

        // 显示结果
        this.uiManager.showResults(extractedData);

        // 保存到历史记录
        this.storageManager.saveRecord({
          timestamp: Date.now(),
          url: window.location.href,
          title: document.title,
          mode: config.mode,
          totalComments: extractedData.comments.length,
          emailCount: extractedData.emails.length,
          config: config
        });

        this.uiManager.showToast(`成功提取 ${extractedData.comments.length} 条评论`, 'success');

      } catch (error) {
        console.error('提取失败:', error);
        this.uiManager.closeModal(progressModal);
        this.uiManager.showToast('提取失败,请重试', 'error');
      } finally {
        this.state.isLoading = false;
      }
    }
  }

  /**
   * Discourse API 管理器
   */
  class DiscourseAPIManager {
    constructor() {
      this.cache = new Map();
      this.sessionData = null;
    }

    /**
     * 获取当前用户信息
     */
    async getCurrentUser() {
      if (this.cache.has('currentUser')) {
        return this.cache.get('currentUser');
      }

      try {
        const response = await fetch('/session/current.json', {
          method: 'GET',
          credentials: 'include',
          headers: {
            'Accept': 'application/json',
            'X-Requested-With': 'XMLHttpRequest'
          }
        });

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const data = await response.json();
        const user = data.current_user;

        this.cache.set('currentUser', user);
        this.sessionData = data;

        console.log('🔍 API获取当前用户:', user);
        return user;

      } catch (error) {
        console.warn('⚠️ API获取用户信息失败,回退到DOM解析:', error);
        return null;
      }
    }

    /**
     * 获取完整的主题信息
     */
    async getFullTopicInfo() {
      const topicId = this.extractTopicId();
      if (!topicId) {
        throw new Error('无法提取主题ID');
      }

      const cacheKey = `fullTopicInfo_${topicId}`;
      if (this.cache.has(cacheKey)) {
        return this.cache.get(cacheKey);
      }

      try {
        const response = await fetch(`/t/${topicId}.json`, {
          method: 'GET',
          credentials: 'include',
          headers: {
            'Accept': 'application/json',
            'X-Requested-With': 'XMLHttpRequest'
          }
        });

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const topicData = await response.json();

        console.log('🔍 API获取完整主题信息:', {
          id: topicData.id,
          title: topicData.title,
          posts_count: topicData.posts_count,
          created_by: topicData.details?.created_by
        });

        this.cache.set(cacheKey, topicData);
        return topicData;

      } catch (error) {
        console.warn('⚠️ API获取主题信息失败:', error);
        throw error;
      }
    }

    /**
     * 获取总帖子数量 - 新增方法
     */
    async getTotalPostsCount() {
      try {
        const topicInfo = await this.getFullTopicInfo();
        return topicInfo.posts_count || 0;
      } catch (error) {
        console.warn('⚠️ 无法从API获取帖子数量:', error);
        return 0;
      }
    }

    /**
     * 获取主题信息(简化版本)
     */
    async getTopicInfo() {
      return this.getFullTopicInfo();
    }

    /**
     * 获取主题帖子数量(兼容方法)
     */
    async getTopicPostsCount() {
      return this.getTotalPostsCount();
    }

    /**
     * 清除缓存
     */
    clearCache() {
      this.cache.clear();
      this.sessionData = null;
    }

    /**
     * 从URL提取主题ID
     */
    extractTopicId() {
      const pathMatch = window.location.pathname.match(/\/t\/[^\/]+\/(\d+)/);
      if (pathMatch) {
        const topicId = parseInt(pathMatch[1], 10);
        console.log('🔍 从路径提取主题ID:', topicId);
        return topicId;
      }

      const hashMatch = window.location.hash.match(/#\/t\/[^\/]+\/(\d+)/);
      if (hashMatch) {
        const topicId = parseInt(hashMatch[1], 10);
        console.log('🔍 从Hash提取主题ID:', topicId);
        return topicId;
      }

      const urlParams = new URLSearchParams(window.location.search);
      const topicIdParam = urlParams.get('topic_id') || urlParams.get('id');
      if (topicIdParam) {
        const topicId = parseInt(topicIdParam, 10);
        console.log('🔍 从查询参数提取主题ID:', topicId);
        return topicId;
      }

      const metaTopicId = document.querySelector('meta[property="discourse:topic_id"]');
      if (metaTopicId) {
        const topicId = parseInt(metaTopicId.getAttribute('content'), 10);
        console.log('🔍 从Meta标签提取主题ID:', topicId);
        return topicId;
      }

      const bodyDataset = document.body.dataset;
      if (bodyDataset.topicId) {
        const topicId = parseInt(bodyDataset.topicId, 10);
        console.log('🔍 从Body数据提取主题ID:', topicId);
        return topicId;
      }

      console.warn('⚠️ 无法提取主题ID');
      return null;
    }
  }

  /**
   * 权限管理器
   */
  class PermissionManager {
    constructor(apiManager) {
      this.api = apiManager;
    }

    /**
     * 获取当前用户信息
     */
    async getCurrentUser() {
      // 先尝试从API获取
      const apiUser = await this.api.getCurrentUser();
      if (apiUser) {
        return apiUser;
      }

      // 回退到DOM解析
      return this.getCurrentUserFromDOM();
    }

    /**
     * 从DOM获取当前用户信息
     */
    getCurrentUserFromDOM() {
      const userSelectors = [
        '.current-user .username',
        '[data-username]',
        '.header-dropdown-toggle.current-user',
        '.user-menu .username',
        '.current-user-info .username',
        'meta[name="discourse_current_user_id"]'
      ];

      for (const selector of userSelectors) {
        const element = document.querySelector(selector);
        if (element) {
          if (selector.includes('meta')) {
            const userId = element.getAttribute('content');
            if (userId) {
              console.log('🔍 DOM获取用户ID:', userId);
              return { id: parseInt(userId, 10) };
            }
          } else {
            const username = element.textContent?.trim() || element.getAttribute('data-username');
            if (username) {
              console.log('🔍 DOM获取用户名:', username);
              return { username };
            }
          }
        }
      }

      console.warn('⚠️ 无法从DOM获取用户信息');
      return null;
    }

    /**
     * 获取帖子作者信息
     */
    async getTopicAuthor() {
      // 先尝试从API获取
      try {
        const topicInfo = await this.api.getFullTopicInfo();
        if (topicInfo && topicInfo.details && topicInfo.details.created_by) {
          const author = topicInfo.details.created_by;
          console.log('🔍 API获取帖子作者:', author);
          return author;
        }
      } catch (error) {
        console.warn('⚠️ API获取帖子作者失败,回退到DOM解析:', error);
      }

      // 回退到DOM解析
      return this.getTopicAuthorFromDOM();
    }

    /**
     * 从DOM获取帖子作者信息
     */
    getTopicAuthorFromDOM() {
      const authorSelectors = [
        '.topic-post:first-child .username',
        '[data-post-number="1"] .username',
        '.topic-avatar .username',
        '.original-poster .username',
        '.first-post .username',
        '.topic-meta-data .username',
        '.creator .username'
      ];

      for (const selector of authorSelectors) {
        const element = document.querySelector(selector);
        if (element) {
          const username = element.textContent?.trim() || element.getAttribute('data-username');
          if (username) {
            console.log('🔍 DOM获取帖子作者:', username);
            return { username };
          }
        }
      }

      const postElement = document.querySelector('.topic-post[data-post-number="1"], .topic-post:first-child');
      if (postElement) {
        const userElement = postElement.querySelector('[data-username], .username');
        if (userElement) {
          const username = userElement.textContent?.trim() || userElement.getAttribute('data-username');
          if (username) {
            console.log('🔍 DOM获取首个帖子作者:', username);
            return { username };
          }
        }
      }

      console.warn('⚠️ 无法从DOM获取帖子作者');
      return null;
    }

    /**
     * 检查权限
     */
    checkPermission(currentUser, topicAuthor) {
      if (!currentUser || !topicAuthor) {
        console.log('🔒 权限检查:用户或作者信息缺失');
        return false;
      }

      const normalizeUsername = (username) => {
        return username ? username.toString().toLowerCase().trim() : '';
      };

      const currentUsername = normalizeUsername(currentUser.username);
      const authorUsername = normalizeUsername(topicAuthor.username);

      const hasPermission = currentUsername === authorUsername;

      console.log('🔒 权限检查详情:', {
        currentUser: currentUsername,
        topicAuthor: authorUsername,
        hasPermission
      });

      return hasPermission;
    }
  }

  /**
   * UI管理器 - 现代化TailwindCSS设计
   */
  class UIManager {
    constructor() {
      this.stylesLoaded = false;
    }

    /**
     * 加载样式
     */
    loadStyles() {
      if (this.stylesLoaded) return;

      this.loadTailwindCSS();
      this.addCustomStyles();
      this.stylesLoaded = true;
    }

    /**
     * 加载 Tailwind CSS
     */
    loadTailwindCSS() {
      if (document.querySelector('#tailwind-css-discourse-extractor')) return;

      const link = document.createElement('link');
      link.id = 'tailwind-css-discourse-extractor';
      link.rel = 'stylesheet';
      link.href = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css';
      document.head.appendChild(link);
    }

    /**
     * 添加现代化自定义样式 - 增强移动端支持
     */
    addCustomStyles() {
      if (document.querySelector('#discourse-extractor-styles')) return;

      const style = document.createElement('style');
      style.id = 'discourse-extractor-styles';
      style.textContent = `
        /* 主容器样式 - 响应式优化 */
        .discourse-extractor-modal {
          position: fixed !important;
          top: 0 !important;
          left: 0 !important;
          width: 100vw !important;
          height: 100vh !important;
          background: linear-gradient(135deg, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.8) 100%) !important;
          backdrop-filter: blur(8px) !important;
          display: flex !important;
          align-items: center !important;
          justify-content: center !important;
          z-index: 999999 !important;
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
          animation: fadeInModal 0.3s ease-out !important;
          padding: 0.5rem !important;
        }

        .discourse-extractor-content {
          background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%) !important;
          border-radius: 20px !important;
          max-width: 100% !important;
          max-height: 100% !important;
          width: 100% !important;
          overflow-y: auto !important;
          padding: 0 !important;
          box-shadow:
            0 25px 50px -12px rgba(0, 0, 0, 0.25),
            0 0 0 1px rgba(255, 255, 255, 0.05) !important;
          position: relative !important;
          animation: slideUpContent 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) !important;
        }

        /* 桌面端样式 */
        @media (min-width: 768px) {
          .discourse-extractor-modal {
            padding: 2rem !important;
          }

          .discourse-extractor-content {
            max-width: 95vw !important;
            max-height: 95vh !important;
            width: 1000px !important;
            border-radius: 24px !important;
          }
        }

        /* 移动端全屏优化 */
        @media (max-width: 767px) {
          .discourse-extractor-modal {
            padding: 0 !important;
          }

          .discourse-extractor-content {
            border-radius: 0 !important;
            height: 100vh !important;
            max-height: 100vh !important;
          }
        }
        
        /* 现代化按钮 - 响应式优化 */
        .discourse-extractor-btn {
          position: fixed !important;
          top: 1rem !important;
          right: 1rem !important;
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
          color: white !important;
          border: none !important;
          border-radius: 16px !important;
          padding: 12px 20px !important;
          font-size: 14px !important;
          font-weight: 600 !important;
          cursor: pointer !important;
          z-index: 999998 !important;
          display: flex !important;
          align-items: center !important;
          gap: 8px !important;
          box-shadow:
            0 8px 25px rgba(102, 126, 234, 0.4),
            0 0 0 1px rgba(255, 255, 255, 0.1) inset !important;
          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
          backdrop-filter: blur(10px) !important;
          min-height: 44px !important; /* 移动端触摸目标最小尺寸 */
          min-width: 44px !important;
        }

        /* 桌面端按钮样式 */
        @media (min-width: 768px) {
          .discourse-extractor-btn {
            top: 20px !important;
            right: 20px !important;
            padding: 14px 24px !important;
            gap: 10px !important;
            border-radius: 18px !important;
          }
        }

        /* 移动端按钮优化 */
        @media (max-width: 767px) {
          .discourse-extractor-btn {
            top: 0.75rem !important;
            right: 0.75rem !important;
            padding: 10px 16px !important;
            font-size: 13px !important;
            border-radius: 14px !important;
            box-shadow:
              0 6px 20px rgba(102, 126, 234, 0.4),
              0 0 0 1px rgba(255, 255, 255, 0.1) inset !important;
          }
        }

        .discourse-extractor-btn:hover {
          transform: translateY(-3px) scale(1.02) !important;
          box-shadow:
            0 15px 35px rgba(102, 126, 234, 0.5),
            0 0 0 1px rgba(255, 255, 255, 0.2) inset !important;
          background: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 100%) !important;
        }

        /* 移动端触摸优化 */
        @media (max-width: 767px) {
          .discourse-extractor-btn:hover {
            transform: scale(1.05) !important;
          }
        }

        .discourse-extractor-btn:active {
          transform: translateY(-1px) scale(1.01) !important;
        }

        /* 移动端触摸反馈 */
        @media (max-width: 767px) {
          .discourse-extractor-btn:active {
            transform: scale(0.98) !important;
          }
        }
        
        /* 关闭按钮 - 移动端优化 */
        .discourse-extractor-close {
          position: absolute !important;
          top: 12px !important;
          right: 12px !important;
          background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%) !important;
          border: none !important;
          border-radius: 12px !important;
          width: 44px !important; /* 移动端触摸目标最小尺寸 */
          height: 44px !important;
          display: flex !important;
          align-items: center !important;
          justify-content: center !important;
          cursor: pointer !important;
          font-size: 20px !important;
          color: #64748b !important;
          transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important;
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
          z-index: 10 !important;
        }

        /* 桌面端关闭按钮 */
        @media (min-width: 768px) {
          .discourse-extractor-close {
            top: 16px !important;
            right: 16px !important;
            width: 40px !important;
            height: 40px !important;
            font-size: 18px !important;
          }
        }

        /* 移动端关闭按钮优化 */
        @media (max-width: 767px) {
          .discourse-extractor-close {
            top: 8px !important;
            right: 8px !important;
            width: 48px !important;
            height: 48px !important;
            font-size: 22px !important;
            border-radius: 14px !important;
          }
        }

        .discourse-extractor-close:hover {
          background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%) !important;
          color: white !important;
          transform: rotate(90deg) scale(1.1) !important;
          box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3) !important;
        }

        /* 移动端触摸优化 */
        @media (max-width: 767px) {
          .discourse-extractor-close:hover {
            transform: scale(1.1) !important;
          }

          .discourse-extractor-close:active {
            transform: scale(0.95) !important;
          }
        }
        
        /* Toast通知 - 移动端优化 */
        .toast-notification {
          position: fixed !important;
          top: 80px !important;
          right: 1rem !important;
          left: 1rem !important;
          background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
          color: white !important;
          padding: 16px 20px !important;
          border-radius: 12px !important;
          font-size: 14px !important;
          font-weight: 600 !important;
          z-index: 999999 !important;
          box-shadow:
            0 10px 25px rgba(16, 185, 129, 0.3),
            0 0 0 1px rgba(255, 255, 255, 0.1) inset !important;
          animation: slideInDown 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), slideOutUp 0.3s ease 2.7s !important;
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
          backdrop-filter: blur(10px) !important;
          display: flex !important;
          align-items: center !important;
          gap: 8px !important;
          max-width: none !important;
        }

        /* 桌面端Toast */
        @media (min-width: 768px) {
          .toast-notification {
            top: 90px !important;
            right: 20px !important;
            left: auto !important;
            max-width: 400px !important;
            padding: 16px 24px !important;
            animation: slideInRight 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), slideOutRight 0.3s ease 2.7s !important;
          }
        }

        /* 移动端Toast优化 */
        @media (max-width: 767px) {
          .toast-notification {
            top: 70px !important;
            margin: 0 0.75rem !important;
            padding: 14px 18px !important;
            font-size: 13px !important;
            border-radius: 10px !important;
          }
        }

        .toast-notification.error {
          background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%) !important;
          box-shadow:
            0 10px 25px rgba(239, 68, 68, 0.3),
            0 0 0 1px rgba(255, 255, 255, 0.1) inset !important;
        }
        
        /* 动画效果 */
        @keyframes fadeInModal {
          from { opacity: 0; }
          to { opacity: 1; }
        }

        @keyframes slideUpContent {
          from { 
            opacity: 0; 
            transform: translateY(30px) scale(0.95); 
          }
          to { 
            opacity: 1; 
            transform: translateY(0) scale(1); 
          }
        }
        
        @keyframes slideInRight {
          from { transform: translateX(100%) scale(0.8); opacity: 0; }
          to { transform: translateX(0) scale(1); opacity: 1; }
        }

        @keyframes slideOutRight {
          from { transform: translateX(0) scale(1); opacity: 1; }
          to { transform: translateX(100%) scale(0.8); opacity: 0; }
        }

        /* 移动端Toast动画 */
        @keyframes slideInDown {
          from { transform: translateY(-100%) scale(0.9); opacity: 0; }
          to { transform: translateY(0) scale(1); opacity: 1; }
        }

        @keyframes slideOutUp {
          from { transform: translateY(0) scale(1); opacity: 1; }
          to { transform: translateY(-100%) scale(0.9); opacity: 0; }
        }

        /* 自定义滚动条 */
        .discourse-extractor-content::-webkit-scrollbar {
          width: 6px;
        }

        .discourse-extractor-content::-webkit-scrollbar-track {
          background: transparent;
        }

        .discourse-extractor-content::-webkit-scrollbar-thumb {
          background: linear-gradient(to bottom, #cbd5e1, #94a3b8);
          border-radius: 3px;
        }

        .discourse-extractor-content::-webkit-scrollbar-thumb:hover {
          background: linear-gradient(to bottom, #94a3b8, #64748b);
        }

        /* 自定义滚动条增强版 */
        .custom-scrollbar::-webkit-scrollbar {
          width: 8px;
          height: 8px;
        }

        .custom-scrollbar::-webkit-scrollbar-track {
          background: linear-gradient(to bottom, #f1f5f9, #e2e8f0);
          border-radius: 4px;
        }

        .custom-scrollbar::-webkit-scrollbar-thumb {
          background: linear-gradient(to bottom, #64748b, #475569);
          border-radius: 4px;
          border: 1px solid #e2e8f0;
        }

        .custom-scrollbar::-webkit-scrollbar-thumb:hover {
          background: linear-gradient(to bottom, #475569, #334155);
        }

        .custom-scrollbar::-webkit-scrollbar-corner {
          background: #f1f5f9;
        }

        /* 行高增强 */
        .line-height-7 {
          line-height: 1.75;
        }

        /* 文本截断 */
        .line-clamp-3 {
          display: -webkit-box;
          -webkit-line-clamp: 3;
          -webkit-box-orient: vertical;
          overflow: hidden;
        }

        /* 特殊效果 */
        .glass-effect {
          background: rgba(255, 255, 255, 0.85) !important;
          backdrop-filter: blur(20px) !important;
          border: 1px solid rgba(255, 255, 255, 0.2) !important;
        }

        .gradient-border {
          position: relative;
        }

        .gradient-border::before {
          content: '';
          position: absolute;
          inset: 0;
          padding: 2px;
          background: linear-gradient(135deg, #667eea, #764ba2, #f093fb);
          border-radius: inherit;
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask-composite: exclude;
        }

        /* 按钮加载状态 */
        .btn-loading {
          position: relative;
          overflow: hidden;
        }

        .btn-loading::after {
          content: '';
          position: absolute;
          top: 0;
          left: -100%;
          width: 100%;
          height: 100%;
          background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
          animation: shimmer 1.5s infinite;
        }

        @keyframes shimmer {
          0% { left: -100%; }
          100% { left: 100%; }
        }

        /* 卡片悬停效果 */
        .card-hover {
          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        .card-hover:hover {
          transform: translateY(-4px);
          box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
        }

        /* 粒子动画效果 */
        @keyframes float {
          0%, 100% { transform: translateY(0px); }
          50% { transform: translateY(-10px); }
        }

        @keyframes glow {
          0%, 100% { box-shadow: 0 0 20px rgba(59, 130, 246, 0.3); }
          50% { box-shadow: 0 0 30px rgba(59, 130, 246, 0.6); }
        }

        /* 背景粒子效果 */
        .particle {
          position: absolute;
          border-radius: 50%;
          background: radial-gradient(circle, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 70%);
          animation: float 3s ease-in-out infinite;
        }

        .particle:nth-child(1) { animation-delay: 0s; }
        .particle:nth-child(2) { animation-delay: 0.5s; }
        .particle:nth-child(3) { animation-delay: 1s; }
        .particle:nth-child(4) { animation-delay: 1.5s; }
        .particle:nth-child(5) { animation-delay: 2s; }

        /* 增强的渐变文字效果 */
        .gradient-text {
          background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
          background-size: 200% 200%;
          -webkit-background-clip: text;
          -webkit-text-fill-color: transparent;
          animation: gradientShift 3s ease infinite;
        }

        @keyframes gradientShift {
          0% { background-position: 0% 50%; }
          50% { background-position: 100% 50%; }
          100% { background-position: 0% 50%; }
        }

        /* 增强的弹出动画 */
        @keyframes bounceInUp {
          0% {
            opacity: 0;
            transform: translateY(100px) scale(0.3);
          }
          50% {
            opacity: 1;
            transform: translateY(-30px) scale(1.05);
          }
          70% {
            transform: translateY(10px) scale(0.9);
          }
          100% {
            opacity: 1;
            transform: translateY(0) scale(1);
          }
        }

        /* 脉冲效果 */
        @keyframes pulse-slow {
          0%, 100% {
            opacity: 1;
            transform: scale(1);
          }
          50% {
            opacity: 0.8;
            transform: scale(1.05);
          }
        }

        .pulse-slow {
          animation: pulse-slow 2s ease-in-out infinite;
        }

        /* 移动端专用样式 */
        @media (max-width: 767px) {
          /* 防止缩放 */
          .discourse-extractor-modal {
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
            -webkit-touch-callout: none;
            -webkit-tap-highlight-color: transparent;
          }

          /* 优化触摸滚动 */
          .discourse-extractor-content {
            -webkit-overflow-scrolling: touch;
            overscroll-behavior: contain;
          }

          /* 移动端输入框优化 */
          input[type="number"], input[type="text"] {
            font-size: 16px !important; /* 防止iOS缩放 */
            -webkit-appearance: none;
            border-radius: 8px !important;
          }

          /* 移动端按钮优化 */
          button {
            -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
          }

          /* 移动端模态框手势支持 */
          .discourse-extractor-content {
            touch-action: pan-y;
          }

          /* 移动端文字选择优化 */
          .selectable-text {
            -webkit-user-select: text;
            -moz-user-select: text;
            -ms-user-select: text;
            user-select: text;
          }
        }

        /* 平板端样式 */
        @media (min-width: 768px) and (max-width: 1023px) {
          .discourse-extractor-content {
            max-width: 90vw !important;
            width: 90vw !important;
          }
        }

        /* 大屏幕优化 */
        @media (min-width: 1440px) {
          .discourse-extractor-content {
            max-width: 1200px !important;
          }
        }

        /* 横屏移动端优化 */
        @media (max-width: 767px) and (orientation: landscape) {
          .discourse-extractor-content {
            max-height: 90vh !important;
          }
        }

        /* 高分辨率屏幕优化 */
        @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
          .discourse-extractor-btn {
            border: 0.5px solid rgba(255, 255, 255, 0.2) !important;
          }
        }
      `;
      document.head.appendChild(style);
    }

    /**
     * 创建现代化按钮
     */
    createButton(hasPermission, onExtractClick, onPermissionError) {
      if (document.querySelector('#discourse-extract-btn')) return;

      const button = document.createElement('button');
      button.id = 'discourse-extract-btn';
      button.className = 'discourse-extractor-btn';

      if (hasPermission) {
        button.innerHTML = `
          <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
            <path d="M14 2v6h6"/>
            <path d="M16 13H8"/>
            <path d="M16 17H8"/>
            <path d="M10 9H8"/>
          </svg>
          <span>智能提取</span>
        `;
        button.addEventListener('click', onExtractClick);
      } else {
        button.style.opacity = '0.6';
        button.style.cursor = 'not-allowed';
        button.style.background = 'linear-gradient(135deg, #64748b 0%, #475569 100%)';
        button.innerHTML = `
          <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
            <path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6z"/>
          </svg>
          <span>仅限作者</span>
        `;
        button.addEventListener('click', onPermissionError);
      }

      document.body.appendChild(button);
    }

    /**
     * 显示现代化Toast通知
     */
    showToast(message, type = 'success') {
      const existingToast = document.querySelector('.toast-notification');
      if (existingToast) existingToast.remove();

      const toast = document.createElement('div');
      toast.className = `toast-notification ${type}`;

      const icon = type === 'success' ?
        '<svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24"><path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>' :
        '<svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24"><path d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>';

      toast.innerHTML = `${icon}<span>${message}</span>`;
      document.body.appendChild(toast);

      setTimeout(() => {
        if (toast.parentNode) toast.remove();
      }, 3000);
    }

    /**
     * 显示权限错误 - 现代化设计
     */
    showPermissionError(currentUser, topicAuthor) {
      const existingError = document.querySelector('#permission-error-modal');
      if (existingError) return;

      const modal = document.createElement('div');
      modal.id = 'permission-error-modal';
      modal.className = 'discourse-extractor-modal';
      modal.innerHTML = `
        <div class="discourse-extractor-content max-w-md">
          <button class="discourse-extractor-close">×</button>
          
          <div class="p-8 text-center">
            <!-- 错误图标 -->
            <div class="w-20 h-20 mx-auto mb-6 bg-gradient-to-br from-red-100 to-red-200 rounded-full flex items-center justify-center">
              <svg class="w-10 h-10 text-red-500" fill="currentColor" viewBox="0 0 24 24">
                <path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6z"/>
              </svg>
            </div>
            
            <!-- 标题 -->
            <h2 class="text-2xl font-bold text-gray-800 mb-4">权限不足</h2>
            
            <!-- 描述 -->
            <div class="text-gray-600 mb-6 space-y-3">
              <p class="leading-relaxed">抱歉,此功能仅限帖子作者使用</p>
              <div class="bg-gray-50 rounded-lg p-4 text-sm">
                <div class="space-y-2">
                  <div class="flex justify-between items-center">
                    <span class="font-medium text-gray-700">当前用户:</span>
                    <span class="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">
                      ${currentUser?.username || '未登录'}
                    </span>
                  </div>
                  <div class="flex justify-between items-center">
                    <span class="font-medium text-gray-700">帖子作者:</span>
                    <span class="px-3 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium">
                      ${topicAuthor?.username || '未知'}
                    </span>
                  </div>
                </div>
              </div>
            </div>
            
            <!-- 按钮 -->
            <button class="w-full bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white font-semibold py-3 px-6 rounded-xl transition-all duration-300 transform hover:scale-105 shadow-lg">
              我知道了
            </button>
          </div>
        </div>
      `;

      const closeModal = () => {
        modal.remove();
      };

      modal.querySelector('.discourse-extractor-close').addEventListener('click', closeModal);
      modal.querySelector('button:last-child').addEventListener('click', closeModal);
      modal.addEventListener('click', (e) => {
        if (e.target === modal) closeModal();
      });

      document.body.appendChild(modal);
    }

    /**
     * 显示加载进度
     */
    showLoadingProgress() {
      const modal = document.createElement('div');
      modal.id = 'loading-progress-modal';
      modal.className = 'discourse-extractor-modal';
      modal.innerHTML = `
        <div class="discourse-extractor-content max-w-md glass-effect">
          <div class="p-8 text-center">
            <div class="w-16 h-16 mx-auto mb-6 bg-gradient-to-br from-blue-100 to-indigo-200 rounded-full flex items-center justify-center">
              <div class="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
            </div>
            <h3 class="text-xl font-bold text-gray-800 mb-4">正在加载评论</h3>
            <div class="text-gray-600 mb-6">
              <p id="progress-text">正在扫描页面...</p>
              <div class="w-full bg-gray-200 rounded-full h-2 mt-4">
                <div id="progress-bar" class="bg-gradient-to-r from-blue-500 to-indigo-600 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
              </div>
            </div>
          </div>
        </div>
      `;
      document.body.appendChild(modal);
      return modal;
    }

    /**
     * 更新进度
     */
    updateProgress(current, total, attempts) {
      const progressText = document.getElementById('progress-text');
      const progressBar = document.getElementById('progress-bar');

      if (progressText && progressBar) {
        progressText.textContent = `已加载 ${current}/${total} 条评论 (第 ${attempts} 次尝试)`;
        const percentage = total > 0 ? (current / total) * 100 : 0;
        progressBar.style.width = `${Math.min(percentage, 100)}%`;
      }
    }

    /**
     * 关闭模态框
     */
    closeModal(modal) {
      if (modal && modal.parentNode) {
        modal.remove();
      }
    }

    /**
     * 显示配置模态框 - 现代化TailwindCSS设计
     */
    showConfigModal(onConfirm) {
      const existingModal = document.querySelector('#discourse-config-modal');
      if (existingModal) existingModal.remove();

      const modal = document.createElement('div');
      modal.id = 'discourse-config-modal';
      modal.className = 'discourse-extractor-modal';
      modal.innerHTML = `
        <div class="discourse-extractor-content max-w-lg md:max-w-2xl lg:max-w-4xl">
          <button class="discourse-extractor-close">×</button>

          <!-- Header with beautiful gradient - 响应式优化 -->
          <div class="bg-gradient-to-r from-indigo-500 via-purple-600 to-pink-500 p-4 md:p-8 rounded-t-xl md:rounded-t-2xl text-white relative overflow-hidden">
            <div class="absolute inset-0 bg-white bg-opacity-10 backdrop-blur-sm"></div>
            <div class="relative z-10 text-center">
              <div class="w-16 h-16 md:w-20 md:h-20 mx-auto mb-3 md:mb-4 bg-white bg-opacity-20 rounded-full flex items-center justify-center backdrop-blur-sm">
                <svg class="w-8 h-8 md:w-10 md:h-10 text-white" fill="currentColor" viewBox="0 0 24 24">
                  <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
                </svg>
              </div>
              <h2 class="text-2xl md:text-3xl font-bold mb-2">智能提取配置</h2>
              <p class="text-white text-opacity-90 text-sm md:text-base">选择您的提取偏好和参数</p>
            </div>
          </div>

          <!-- Content - 响应式间距 -->
          <div class="p-4 md:p-8">
            <form id="extract-config-form" class="space-y-6 md:space-y-8">
              <!-- 提取模式选择 -->
              <div class="space-y-3 md:space-y-4">
                <h3 class="text-base md:text-lg font-semibold text-gray-800 mb-3 md:mb-4 flex items-center">
                  <svg class="w-4 h-4 md:w-5 md:h-5 mr-2 text-indigo-500" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
                  </svg>
                  提取模式
                </h3>

                <div class="space-y-2 md:space-y-3">
                  <!-- 全部评论模式 - 移动端优化 -->
                  <label class="group relative flex items-center p-3 md:p-4 border-2 border-indigo-500 bg-gradient-to-r from-indigo-50 to-blue-50 rounded-lg md:rounded-xl cursor-pointer hover:shadow-lg transition-all duration-300 transform hover:scale-102 min-h-[60px] md:min-h-auto">
                    <input type="radio" name="mode" value="all" checked class="sr-only">
                    <div class="w-5 h-5 border-2 border-indigo-500 rounded-full mr-3 md:mr-4 flex items-center justify-center relative flex-shrink-0">
                      <div class="w-3 h-3 bg-indigo-500 rounded-full opacity-100 transform scale-100 transition-all duration-200"></div>
                    </div>
                    <div class="flex-1 min-w-0">
                      <div class="font-semibold text-gray-800 group-hover:text-indigo-700 transition-colors text-sm md:text-base">全部评论</div>
                      <div class="text-xs md:text-sm text-gray-600 mt-1">提取页面上所有可见的评论内容</div>
                    </div>
                    <svg class="w-5 h-5 md:w-6 md:h-6 text-indigo-500 opacity-100 transition-all duration-200 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
                    </svg>
                  </label>
                  
                  <!-- 楼层范围模式 - 移动端优化 -->
                  <label class="group relative flex items-center p-3 md:p-4 border-2 border-gray-200 bg-white rounded-lg md:rounded-xl cursor-pointer hover:border-orange-400 hover:bg-gradient-to-r hover:from-orange-50 hover:to-amber-50 hover:shadow-lg transition-all duration-300 transform hover:scale-102 min-h-[60px] md:min-h-auto">
                    <input type="radio" name="mode" value="range" class="sr-only">
                    <div class="w-5 h-5 border-2 border-orange-500 rounded-full mr-3 md:mr-4 flex items-center justify-center flex-shrink-0">
                      <div class="w-3 h-3 bg-orange-500 rounded-full opacity-0 transform scale-0 transition-all duration-200"></div>
                    </div>
                    <div class="flex-1 min-w-0">
                      <div class="font-semibold text-gray-800 group-hover:text-orange-700 transition-colors text-sm md:text-base">楼层范围</div>
                      <div class="text-xs md:text-sm text-gray-600 mt-1">指定起始楼层和结束楼层进行精确提取</div>
                    </div>
                    <svg class="w-5 h-5 md:w-6 md:h-6 text-orange-500 opacity-0 transition-all duration-200 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
                    </svg>
                  </label>

                  <!-- 随机提取模式 - 移动端优化 -->
                  <label class="group relative flex items-center p-3 md:p-4 border-2 border-gray-200 bg-white rounded-lg md:rounded-xl cursor-pointer hover:border-green-400 hover:bg-gradient-to-r hover:from-green-50 hover:to-emerald-50 hover:shadow-lg transition-all duration-300 transform hover:scale-102 min-h-[60px] md:min-h-auto">
                    <input type="radio" name="mode" value="random" class="sr-only">
                    <div class="w-5 h-5 border-2 border-green-500 rounded-full mr-3 md:mr-4 flex items-center justify-center flex-shrink-0">
                      <div class="w-3 h-3 bg-green-500 rounded-full opacity-0 transform scale-0 transition-all duration-200"></div>
                    </div>
                    <div class="flex-1 min-w-0">
                      <div class="font-semibold text-gray-800 group-hover:text-green-700 transition-colors text-sm md:text-base">随机提取</div>
                      <div class="text-xs md:text-sm text-gray-600 mt-1">从所有评论中随机选择指定数量</div>
                    </div>
                    <svg class="w-5 h-5 md:w-6 md:h-6 text-green-500 opacity-0 transition-all duration-200 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
                    </svg>
                  </label>
                </div>
              </div>

              <!-- 楼层范围配置 - 移动端优化 -->
              <div id="range-config" class="hidden opacity-0 transform translate-y-4 transition-all duration-500">
                <div class="bg-gradient-to-br from-orange-50 to-amber-100 rounded-lg md:rounded-xl p-4 md:p-6 border-2 border-orange-200 card-hover">
                  <h4 class="font-semibold text-orange-800 mb-3 md:mb-4 flex items-center text-sm md:text-base">
                    <svg class="w-4 h-4 md:w-5 md:h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"/>
                    </svg>
                    楼层范围设置
                  </h4>
                  <div class="grid grid-cols-1 md:grid-cols-2 gap-3 md:gap-4">
                    <div>
                      <label class="block text-xs md:text-sm font-medium text-orange-700 mb-2">起始楼层</label>
                      <input type="number" id="start-floor" min="1" value="1"
                        class="w-full px-3 md:px-4 py-2 md:py-3 border-2 border-orange-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500 transition-all duration-200 bg-white text-sm md:text-base min-h-[44px]">
                    </div>
                    <div>
                      <label class="block text-xs md:text-sm font-medium text-orange-700 mb-2">结束楼层</label>
                      <input type="number" id="end-floor" min="1" placeholder="默认到最后"
                        class="w-full px-3 md:px-4 py-2 md:py-3 border-2 border-orange-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-orange-500 transition-all duration-200 bg-white text-sm md:text-base min-h-[44px]">
                    </div>
                  </div>
                </div>
              </div>

              <!-- 随机提取配置 - 移动端优化 -->
              <div id="random-config" class="hidden opacity-0 transform translate-y-4 transition-all duration-500">
                <div class="bg-gradient-to-br from-green-50 to-emerald-100 rounded-lg md:rounded-xl p-4 md:p-6 border-2 border-green-200 card-hover">
                  <h4 class="font-semibold text-green-800 mb-3 md:mb-4 flex items-center text-sm md:text-base">
                    <svg class="w-4 h-4 md:w-5 md:h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547L3.71 16.292a1 1 0 001.4 1.43l.93-.93c.22-.22.546-.31.858-.243l2.387.477a8 8 0 005.147-.689l.318-.158a4 4 0 012.573-.345l2.387.477c.312.067.638-.023.858-.243l.93-.93a1 1 0 001.4-1.43l-.534-.535z"/>
                    </svg>
                    随机提取设置
                  </h4>
                  <div>
                    <label class="block text-xs md:text-sm font-medium text-green-700 mb-2">提取数量</label>
                    <input type="number" id="random-count" min="1" value="10"
                      class="w-full px-3 md:px-4 py-2 md:py-3 border-2 border-green-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white text-sm md:text-base min-h-[44px]">
                    <p class="text-xs md:text-sm text-green-600 mt-2">从所有评论中随机选择指定数量</p>
                  </div>
                </div>
              </div>

              <!-- 邮箱提取选项 - 移动端优化 -->
              <div class="bg-gradient-to-br from-blue-50 to-indigo-100 rounded-lg md:rounded-xl p-4 md:p-6 border-2 border-blue-200 card-hover">
                <label class="flex items-center cursor-pointer group min-h-[60px] md:min-h-auto">
                  <input type="checkbox" id="extract-emails" checked class="sr-only">
                  <div class="relative mr-3 md:mr-4 flex-shrink-0">
                    <div class="w-12 h-6 bg-blue-500 rounded-full shadow-inner transition-all duration-300"></div>
                    <div class="absolute left-1 top-1 w-4 h-4 bg-white rounded-full shadow-md transition-transform duration-300 transform translate-x-6"></div>
                  </div>
                  <div class="flex-1 min-w-0">
                    <div class="font-semibold text-blue-800 group-hover:text-blue-900 transition-colors text-sm md:text-base">同时提取邮箱地址</div>
                    <div class="text-xs md:text-sm text-blue-600 mt-1">自动识别并提取评论中的邮箱地址</div>
                  </div>
                </label>
              </div>

              <!-- 按钮组 - 移动端优化 -->
              <div class="flex flex-col md:flex-row gap-3 md:gap-4 pt-4 md:pt-6">
                <button type="button" id="cancel-btn"
                  class="flex-1 px-4 md:px-6 py-3 md:py-4 bg-gradient-to-r from-gray-100 to-gray-200 hover:from-gray-200 hover:to-gray-300 text-gray-700 font-semibold rounded-lg md:rounded-xl transition-all duration-300 transform hover:scale-105 shadow-md hover:shadow-lg text-sm md:text-base min-h-[48px] md:min-h-auto">
                  取消
                </button>
                <button type="submit"
                  class="flex-1 px-4 md:px-6 py-3 md:py-4 bg-gradient-to-r from-indigo-500 via-purple-600 to-pink-500 hover:from-indigo-600 hover:via-purple-700 hover:to-pink-600 text-white font-semibold rounded-lg md:rounded-xl transition-all duration-300 transform hover:scale-105 shadow-lg hover:shadow-xl btn-loading text-sm md:text-base min-h-[48px] md:min-h-auto">
                  开始提取
                </button>
              </div>
            </form>
          </div>
        </div>
      `;

      this.attachConfigEventListeners(modal, onConfirm);
      document.body.appendChild(modal);
      return modal;
    }

    /**
     * 添加配置弹窗的事件监听器
     */
    attachConfigEventListeners(modal, onConfirm) {
      const form = modal.querySelector('#extract-config-form');
      const modeRadios = modal.querySelectorAll('input[name="mode"]');
      const rangeConfig = modal.querySelector('#range-config');
      const randomConfig = modal.querySelector('#random-config');
      const emailToggle = modal.querySelector('#extract-emails');

      // 单选按钮样式切换
      modeRadios.forEach(radio => {
        const label = radio.closest('label');
        const radioCircle = label.querySelector('div:first-child div');
        const checkIcon = label.querySelector('svg:last-child');

        radio.addEventListener('change', () => {
          // 重置所有选项
          modeRadios.forEach(r => {
            const rLabel = r.closest('label');
            const rCircle = rLabel.querySelector('div:first-child div');
            const rIcon = rLabel.querySelector('svg:last-child');

            rCircle.style.opacity = '0';
            rCircle.style.transform = 'scale(0)';
            rIcon.style.opacity = '0';
            rLabel.classList.remove('border-indigo-500', 'border-orange-400', 'border-green-400');
            rLabel.classList.add('border-gray-200');
          });

          // 激活当前选项
          radioCircle.style.opacity = '1';
          radioCircle.style.transform = 'scale(1)';
          checkIcon.style.opacity = '1';

          // 根据模式设置不同的边框颜色和背景
          if (radio.value === 'all') {
            label.classList.remove('border-gray-200');
            label.classList.add('border-indigo-500', 'bg-gradient-to-r', 'from-indigo-50', 'to-blue-50');
          } else if (radio.value === 'range') {
            label.classList.remove('border-gray-200');
            label.classList.add('border-orange-400', 'bg-gradient-to-r', 'from-orange-50', 'to-amber-50');
          } else if (radio.value === 'random') {
            label.classList.remove('border-gray-200');
            label.classList.add('border-green-400', 'bg-gradient-to-r', 'from-green-50', 'to-emerald-50');
          }

          // 显示/隐藏配置面板
          if (radio.value === 'range') {
            randomConfig.classList.add('hidden', 'opacity-0', 'translate-y-4');
            rangeConfig.classList.remove('hidden');
            setTimeout(() => {
              rangeConfig.classList.remove('opacity-0', 'translate-y-4');
            }, 10);
          } else if (radio.value === 'random') {
            rangeConfig.classList.add('hidden', 'opacity-0', 'translate-y-4');
            randomConfig.classList.remove('hidden');
            setTimeout(() => {
              randomConfig.classList.remove('opacity-0', 'translate-y-4');
            }, 10);
          } else {
            rangeConfig.classList.add('hidden', 'opacity-0', 'translate-y-4');
            randomConfig.classList.add('hidden', 'opacity-0', 'translate-y-4');
          }
        });
      });

      // 邮箱开关切换动画
      emailToggle.addEventListener('change', () => {
        const toggleContainer = emailToggle.closest('label').querySelector('div');
        const toggleSwitch = toggleContainer.querySelector('div:last-child');
        const toggleBg = toggleContainer.querySelector('div:first-child');

        if (emailToggle.checked) {
          toggleSwitch.classList.remove('translate-x-0');
          toggleSwitch.classList.add('translate-x-6');
          toggleBg.classList.remove('bg-gray-300');
          toggleBg.classList.add('bg-blue-500');
        } else {
          toggleSwitch.classList.remove('translate-x-6');
          toggleSwitch.classList.add('translate-x-0');
          toggleBg.classList.remove('bg-blue-500');
          toggleBg.classList.add('bg-gray-300');
        }
      });

      // 取消按钮
      modal.querySelector('#cancel-btn').addEventListener('click', () => {
        modal.remove();
      });

      // 关闭按钮
      modal.querySelector('.discourse-extractor-close').addEventListener('click', () => {
        modal.remove();
      });

      // 点击背景关闭
      modal.addEventListener('click', (e) => {
        if (e.target === modal) {
          modal.remove();
        }
      });

      // 移动端手势支持
      this.addMobileGestureSupport(modal);

      // 表单提交
      form.addEventListener('submit', (e) => {
        e.preventDefault();

        const submitBtn = form.querySelector('button[type="submit"]');
        submitBtn.classList.add('btn-loading');
        submitBtn.innerHTML = `
          <div class="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"></div>
          正在配置...
        `;

        setTimeout(() => {
          const formData = new FormData(form);
          const config = {
            mode: formData.get('mode'),
            extractEmails: emailToggle.checked,
            startFloor: parseInt(modal.querySelector('#start-floor').value) || 1,
            endFloor: parseInt(modal.querySelector('#end-floor').value) || null,
            randomCount: parseInt(modal.querySelector('#random-count').value) || 10
          };

          modal.remove();
          onConfirm(config);
        }, 800);
      });
    }

    /**
     * 获取模式文本
     */
    getModeText(mode) {
      const modeMap = {
        'all': '全部评论',
        'range': '楼层范围',
        'random': '随机提取'
      };
      return modeMap[mode] || '未知模式';
    }

    /**
     * 下载JSON文件
     */
    downloadJSON(data) {
      try {
        const jsonData = JSON.stringify(data, null, 2);
        const blob = new Blob([jsonData], { type: 'application/json;charset=utf-8' });
        const url = URL.createObjectURL(blob);

        const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
        const filename = `discourse-comments-${data.extractConfig?.mode || 'all'}-${timestamp}.json`;

        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);

        this.showToast('JSON文件下载成功');
      } catch (error) {
        console.error('下载JSON失败:', error);
        this.showToast('下载失败,请重试', 'error');
      }
    }

    /**
     * 下载CSV文件
     */
    downloadCSV(data) {
      try {
        const headers = ['楼层', '作者', '时间', '内容', '邮箱'];
        const csvContent = [
          '\ufeff', // BOM for Excel Chinese support
          headers.join(','),
          ...data.comments.map(comment => {
            const content = `"${comment.content.replace(/"/g, '""').replace(/\n/g, ' ')}"`;
            const emails = comment.emails ? comment.emails.join(';') : '';
            return [
              comment.floor,
              `"${comment.author}"`,
              `"${comment.time}"`,
              content,
              `"${emails}"`
            ].join(',');
          })
        ].join('\n');

        const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8' });
        const url = URL.createObjectURL(blob);

        const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
        const filename = `discourse-comments-${data.extractConfig?.mode || 'all'}-${timestamp}.csv`;

        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);

        this.showToast('CSV文件下载成功');
      } catch (error) {
        console.error('下载CSV失败:', error);
        this.showToast('下载失败,请重试', 'error');
      }
    }

    /**
     * 复制完整数据
     */
    async copyData(data) {
      try {
        const textData = [
          `=== Discourse 评论提取结果 ===`,
          `提取时间: ${data.extractTime}`,
          `页面标题: ${data.pageTitle}`,
          `页面链接: ${data.pageUrl}`,
          `提取模式: ${this.getModeText(data.extractConfig?.mode)}`,
          `评论总数: ${data.comments.length}`,
          `邮箱总数: ${data.emails.length}`,
          '',
          '=== 评论详情 ===',
          ...data.comments.map(comment => [
            `楼层 #${comment.floor} - ${comment.author} - ${comment.time}`,
            comment.content,
            comment.emails && comment.emails.length > 0 ? `邮箱: ${comment.emails.join(', ')}` : '',
            '---'
          ].filter(line => line).join('\n'))
        ].join('\n');

        await navigator.clipboard.writeText(textData);
        this.showToast('数据已复制到剪贴板');
      } catch (error) {
        console.error('复制数据失败:', error);
        this.showToast('复制失败,请重试', 'error');
      }
    }

    /**
     * 复制单个邮箱地址
     */
    async copySingleEmail(email) {
      try {
        await navigator.clipboard.writeText(email);
        this.showToast(`✨ 已复制: ${email}`);
      } catch (error) {
        console.error('复制邮箱失败:', error);
        this.showToast('复制失败,请重试', 'error');
      }
    }

    /**
     * 显示邮箱复制选项模态框
     */
    showEmailCopyOptions(data) {
      if (data.emails.length === 0) {
        this.showToast('没有邮箱地址可复制', 'error');
        return;
      }

      const existingModal = document.querySelector('#email-copy-options-modal');
      if (existingModal) existingModal.remove();

      const modal = document.createElement('div');
      modal.id = 'email-copy-options-modal';
      modal.className = 'discourse-extractor-modal';
      modal.innerHTML = `
        <div class="discourse-extractor-content max-w-md">
          <button class="discourse-extractor-close">×</button>

          <div class="p-6 md:p-8">
            <!-- Header -->
            <div class="text-center mb-6">
              <div class="w-16 h-16 mx-auto mb-4 bg-gradient-to-br from-orange-400 to-amber-500 rounded-full flex items-center justify-center">
                <svg class="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 24 24">
                  <path d="M3 8l7.89 7.89a1 1 0 001.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
                </svg>
              </div>
              <h2 class="text-2xl font-bold text-gray-800 mb-2">选择复制格式</h2>
              <p class="text-gray-600">共 ${data.emails.length} 个邮箱地址</p>
            </div>

            <!-- Separator Options -->
            <div class="space-y-3 mb-6">
              <button class="copy-option-btn w-full p-4 bg-gradient-to-r from-blue-50 to-indigo-50 hover:from-blue-100 hover:to-indigo-100 border-2 border-blue-200 hover:border-blue-300 rounded-xl transition-all duration-300 text-left group" data-separator=",">
                <div class="flex items-center justify-between">
                  <div>
                    <div class="font-semibold text-blue-800 mb-1">逗号分隔</div>
                    <div class="text-sm text-blue-600">[email protected], [email protected]</div>
                  </div>
                  <svg class="w-5 h-5 text-blue-500 group-hover:scale-110 transition-transform" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M16 1H4a2 2 0 0 0-2 2v14h2V3h12V1zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm0 16H8V7h11v14z"/>
                  </svg>
                </div>
              </button>

              <button class="copy-option-btn w-full p-4 bg-gradient-to-r from-green-50 to-emerald-50 hover:from-green-100 hover:to-emerald-100 border-2 border-green-200 hover:border-green-300 rounded-xl transition-all duration-300 text-left group" data-separator=" ">
                <div class="flex items-center justify-between">
                  <div>
                    <div class="font-semibold text-green-800 mb-1">空格分隔</div>
                    <div class="text-sm text-green-600">[email protected] [email protected]</div>
                  </div>
                  <svg class="w-5 h-5 text-green-500 group-hover:scale-110 transition-transform" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M16 1H4a2 2 0 0 0-2 2v14h2V3h12V1zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm0 16H8V7h11v14z"/>
                  </svg>
                </div>
              </button>

              <button class="copy-option-btn w-full p-4 bg-gradient-to-r from-purple-50 to-violet-50 hover:from-purple-100 hover:to-violet-100 border-2 border-purple-200 hover:border-purple-300 rounded-xl transition-all duration-300 text-left group" data-separator=";">
                <div class="flex items-center justify-between">
                  <div>
                    <div class="font-semibold text-purple-800 mb-1">分号分隔</div>
                    <div class="text-sm text-purple-600">[email protected]; [email protected]</div>
                  </div>
                  <svg class="w-5 h-5 text-purple-500 group-hover:scale-110 transition-transform" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M16 1H4a2 2 0 0 0-2 2v14h2V3h12V1zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm0 16H8V7h11v14z"/>
                  </svg>
                </div>
              </button>

              <button class="copy-option-btn w-full p-4 bg-gradient-to-r from-amber-50 to-orange-50 hover:from-amber-100 hover:to-orange-100 border-2 border-amber-200 hover:border-amber-300 rounded-xl transition-all duration-300 text-left group" data-separator="\\n">
                <div class="flex items-center justify-between">
                  <div>
                    <div class="font-semibold text-amber-800 mb-1">换行分隔</div>
                    <div class="text-sm text-amber-600">每行一个邮箱地址</div>
                  </div>
                  <svg class="w-5 h-5 text-amber-500 group-hover:scale-110 transition-transform" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M16 1H4a2 2 0 0 0-2 2v14h2V3h12V1zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm0 16H8V7h11v14z"/>
                  </svg>
                </div>
              </button>
            </div>

            <!-- Cancel Button -->
            <button class="cancel-btn w-full p-3 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium rounded-lg transition-all duration-300">
              取消
            </button>
          </div>
        </div>
      `;

      const closeModal = () => {
        modal.remove();
      };

      // Event listeners
      modal.querySelector('.discourse-extractor-close').addEventListener('click', closeModal);
      modal.querySelector('.cancel-btn').addEventListener('click', closeModal);
      modal.addEventListener('click', (e) => {
        if (e.target === modal) closeModal();
      });

      // Copy option buttons
      modal.querySelectorAll('.copy-option-btn').forEach(btn => {
        btn.addEventListener('click', async () => {
          const separator = btn.getAttribute('data-separator');
          const actualSeparator = separator === '\\n' ? '\n' : separator;
          await this.copyEmailsWithSeparator(data.emails, actualSeparator);
          closeModal();
        });
      });

      document.body.appendChild(modal);
    }

    /**
     * 复制邮箱地址(带分隔符选择)
     */
    async copyEmailsWithSeparator(emails, separator) {
      try {
        const emailText = emails.join(separator);
        await navigator.clipboard.writeText(emailText);

        const separatorName = {
          ',': '逗号',
          ' ': '空格',
          ';': '分号',
          '\n': '换行'
        }[separator] || '自定义';

        this.showToast(`已复制 ${emails.length} 个邮箱地址 (${separatorName}分隔)`);
      } catch (error) {
        console.error('复制邮箱失败:', error);
        this.showToast('复制失败,请重试', 'error');
      }
    }

    /**
     * 复制邮箱地址(兼容旧方法)
     */
    async copyEmails(data) {
      this.showEmailCopyOptions(data);
    }

    /**
     * 显示历史记录 - 现代化设计
     */
    showHistory() {
      const existingModal = document.querySelector('#discourse-history-modal');
      if (existingModal) existingModal.remove();

      const storageManager = new StorageManager('discourse_extractor_history', 100);
      const history = storageManager.getHistory();

      const modal = document.createElement('div');
      modal.id = 'discourse-history-modal';
      modal.className = 'discourse-extractor-modal';
      modal.innerHTML = `
        <div class="discourse-extractor-content max-w-5xl">
          <button class="discourse-extractor-close">×</button>
          
          <!-- Header -->
          <div class="bg-gradient-to-r from-amber-500 via-orange-500 to-red-500 p-8 rounded-t-xl text-white relative overflow-hidden">
            <div class="absolute inset-0 bg-white bg-opacity-10 backdrop-blur-sm"></div>
            <div class="relative z-10 text-center">
              <div class="w-20 h-20 mx-auto mb-4 bg-white bg-opacity-20 rounded-full flex items-center justify-center backdrop-blur-sm">
                <svg class="w-10 h-10 text-white" fill="currentColor" viewBox="0 0 24 24">
                  <path d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
                </svg>
              </div>
              <h2 class="text-3xl font-bold mb-2">📚 提取历史</h2>
              <p class="text-white text-opacity-90">共 ${history.length} 条记录</p>
            </div>
          </div>

          <!-- Content -->
          <div class="p-8 max-h-[60vh] overflow-y-auto">
            ${history.length === 0 ? `
              <div class="text-center py-16">
                <div class="w-24 h-24 mx-auto mb-6 bg-gradient-to-br from-gray-100 to-gray-200 rounded-full flex items-center justify-center">
                  <svg class="w-12 h-12 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
                  </svg>
                </div>
                <h3 class="text-2xl font-semibold text-gray-700 mb-3">暂无历史记录</h3>
                <p class="text-gray-500 text-lg">开始提取评论后,记录将显示在这里</p>
              </div>
            ` : `
              <div class="grid gap-6">
                ${history.map((record, index) => `
                  <div class="bg-gradient-to-r from-white to-gray-50 border-2 border-gray-200 rounded-xl p-6 card-hover shadow-md">
                    <div class="flex justify-between items-start mb-4">
                      <div class="flex-1">
                        <h4 class="text-xl font-semibold text-gray-800 mb-2 line-clamp-2">${record.pageTitle || '未知页面'}</h4>
                        <p class="text-sm text-gray-500 mb-3">${new Date(record.timestamp).toLocaleString()}</p>
                        <div class="flex flex-wrap gap-2">
                          <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-800">
                            ${record.totalComments || 0} 评论
                          </span>
                          <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
                            ${record.emailCount || 0} 邮箱
                          </span>
                          <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-purple-100 text-purple-800">
                            ${this.getModeText(record.mode)}
                          </span>
                        </div>
                      </div>
                    </div>
                    
                    <div class="flex gap-3">
                      <button onclick="window.historyActions.reloadRecord(${record.id})" class="flex-1 px-4 py-3 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-medium rounded-lg transition-all duration-300 transform hover:scale-105 shadow-md">
                        重新查看
                      </button>
                      <button onclick="window.historyActions.copyRecord(${record.id})" class="flex-1 px-4 py-3 bg-gradient-to-r from-purple-500 to-purple-600 hover:from-purple-600 hover:to-purple-700 text-white font-medium rounded-lg transition-all duration-300 transform hover:scale-105 shadow-md">
                        复制数据
                      </button>
                      <a href="${record.url || '#'}" target="_blank" class="flex-1 px-4 py-3 bg-gradient-to-r from-gray-500 to-gray-600 hover:from-gray-600 hover:to-gray-700 text-white font-medium rounded-lg transition-all duration-300 transform hover:scale-105 shadow-md text-center">
                        查看原帖
                      </a>
                    </div>
                  </div>
                `).join('')}
              </div>
              
              <div class="mt-8 pt-6 border-t-2 border-gray-200 text-center">
                <button onclick="window.historyActions.clearHistory()" class="px-8 py-4 bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white font-semibold rounded-xl transition-all duration-300 transform hover:scale-105 shadow-lg">
                  清空历史记录
                </button>
              </div>
            `}
          </div>
        </div>
      `;

      // 添加全局操作函数
      window.historyActions = {
        reloadRecord: (recordId) => {
          const record = history.find(r => r.id === recordId);
          if (record) {
            modal.remove();
            this.showResults(record);
          }
        },
        copyRecord: async (recordId) => {
          const record = history.find(r => r.id === recordId);
          if (record) {
            await this.copyData(record);
          }
        },
        clearHistory: () => {
          if (confirm('确定要清空所有历史记录吗?此操作不可恢复。')) {
            storageManager.clearHistory();
            modal.remove();
            this.showToast('历史记录已清空');
          }
        }
      };

      // 关闭功能
      const closeModal = () => {
        delete window.historyActions;
        modal.remove();
      };

      modal.addEventListener('click', (e) => {
        if (e.target === modal) closeModal();
      });

      modal.querySelector('.discourse-extractor-close').addEventListener('click', closeModal);

      // 添加移动端手势支持
      this.addMobileGestureSupport(modal);

      document.body.appendChild(modal);
    }

    /**
     * 显示结果 - 现代化TailwindCSS设计
     */
    showResults(data) {
      const existingModal = document.querySelector('#discourse-results-modal');
      if (existingModal) existingModal.remove();

      const modal = document.createElement('div');
      modal.id = 'discourse-results-modal';
      modal.className = 'discourse-extractor-modal';
      modal.innerHTML = `
        <div class="discourse-extractor-content max-w-full md:max-w-6xl">
          <button class="discourse-extractor-close">×</button>

          <!-- Header with enhanced gradient and particles effect - 移动端优化 -->
          <div class="bg-gradient-to-br from-emerald-400 via-teal-500 to-cyan-600 p-4 md:p-8 rounded-t-xl text-white relative overflow-hidden">
            <!-- Background decorative elements -->
            <div class="absolute inset-0 opacity-10">
              <div class="absolute top-4 left-8 w-32 h-32 bg-white rounded-full blur-2xl animate-pulse"></div>
              <div class="absolute top-16 right-12 w-24 h-24 bg-white rounded-full blur-xl animate-pulse delay-1000"></div>
              <div class="absolute bottom-8 left-1/3 w-20 h-20 bg-white rounded-full blur-lg animate-pulse delay-500"></div>
            </div>
            
            <!-- Glassmorphism overlay -->
            <div class="absolute inset-0 bg-gradient-to-r from-white/10 to-transparent backdrop-blur-sm"></div>
            
            <div class="relative z-10">
              <div class="flex flex-col md:flex-row items-center justify-between mb-6 md:mb-8 space-y-4 md:space-y-0">
                <div class="flex items-center space-x-3 md:space-x-4 text-center md:text-left">
                  <!-- Enhanced success icon with animation - 移动端优化 -->
                  <div class="w-12 h-12 md:w-16 md:h-16 bg-white/20 rounded-full flex items-center justify-center backdrop-blur-sm border border-white/30 animate-bounce">
                    <svg class="w-6 h-6 md:w-8 md:h-8 text-white" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
                    </svg>
                  </div>
                  <div>
                    <h2 class="text-2xl md:text-4xl font-bold mb-1 md:mb-2 bg-gradient-to-r from-white to-emerald-100 bg-clip-text text-transparent">
                      🎉 提取完成
                    </h2>
                    <p class="text-emerald-100 text-sm md:text-lg font-medium">数据提取成功,一切准备就绪!</p>
                  </div>
                </div>

                <!-- Floating stats badge - 移动端优化 -->
                <div class="text-center md:text-right">
                  <div class="bg-white/20 backdrop-blur-sm rounded-xl md:rounded-2xl p-3 md:p-4 border border-white/30">
                    <div class="text-2xl md:text-3xl font-bold mb-1">${data.comments.length}</div>
                    <div class="text-emerald-100 text-xs md:text-sm font-medium">条精彩评论</div>
                  </div>
                </div>
              </div>
              
              <!-- Enhanced statistics cards with hover effects - 移动端优化 -->
              <div class="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-6">
                <div class="group bg-white/15 backdrop-blur-md rounded-xl md:rounded-2xl p-4 md:p-6 text-center border border-white/20 hover:bg-white/25 transition-all duration-500 hover:scale-105 hover:shadow-2xl cursor-pointer">
                  <div class="w-10 h-10 md:w-12 md:h-12 mx-auto mb-2 md:mb-3 bg-white/20 rounded-full flex items-center justify-center group-hover:rotate-12 transition-transform duration-300">
                    <svg class="w-5 h-5 md:w-6 md:h-6 text-white" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M8 10h8M8 14h6M6 4h12a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z"/>
                    </svg>
                  </div>
                  <div class="text-2xl md:text-3xl font-bold mb-1 md:mb-2 group-hover:text-yellow-200 transition-colors">${data.comments.length}</div>
                  <div class="text-emerald-100 text-xs md:text-sm font-medium">评论总数</div>
                  <div class="text-xs text-emerald-200 mt-1 opacity-75">已成功解析</div>
                </div>

                <div class="group bg-white/15 backdrop-blur-md rounded-xl md:rounded-2xl p-4 md:p-6 text-center border border-white/20 hover:bg-white/25 transition-all duration-500 hover:scale-105 hover:shadow-2xl cursor-pointer">
                  <div class="w-10 h-10 md:w-12 md:h-12 mx-auto mb-2 md:mb-3 bg-white/20 rounded-full flex items-center justify-center group-hover:rotate-12 transition-transform duration-300">
                    <svg class="w-5 h-5 md:w-6 md:h-6 text-white" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M3 8l7.89 7.89a1 1 0 001.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
                    </svg>
                  </div>
                  <div class="text-2xl md:text-3xl font-bold mb-1 md:mb-2 group-hover:text-yellow-200 transition-colors">${data.emails.length}</div>
                  <div class="text-emerald-100 text-xs md:text-sm font-medium">邮箱地址</div>
                  <div class="text-xs text-emerald-200 mt-1 opacity-75">自动识别</div>
                </div>

                <div class="group bg-white/15 backdrop-blur-md rounded-xl md:rounded-2xl p-4 md:p-6 text-center border border-white/20 hover:bg-white/25 transition-all duration-500 hover:scale-105 hover:shadow-2xl cursor-pointer">
                  <div class="w-10 h-10 md:w-12 md:h-12 mx-auto mb-2 md:mb-3 bg-white/20 rounded-full flex items-center justify-center group-hover:rotate-12 transition-transform duration-300">
                    <svg class="w-5 h-5 md:w-6 md:h-6 text-white" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
                    </svg>
                  </div>
                  <div class="text-lg md:text-2xl font-bold mb-1 md:mb-2 group-hover:text-yellow-200 transition-colors">${this.getModeText(data.extractConfig?.mode)}</div>
                  <div class="text-emerald-100 text-xs md:text-sm font-medium">提取模式</div>
                  <div class="text-xs text-emerald-200 mt-1 opacity-75">智能算法</div>
                </div>
              </div>
            </div>
            
            <!-- Floating particles animation -->
            <div class="absolute inset-0 overflow-hidden pointer-events-none">
              <div class="absolute -top-2 -left-2 w-4 h-4 bg-white/30 rounded-full animate-ping"></div>
              <div class="absolute top-1/2 -right-2 w-3 h-3 bg-white/20 rounded-full animate-ping delay-700"></div>
              <div class="absolute -bottom-2 left-1/4 w-2 h-2 bg-white/25 rounded-full animate-ping delay-1000"></div>
            </div>
          </div>

          <!-- Content with enhanced design - 移动端优化 -->
          <div class="p-4 md:p-8 bg-gradient-to-b from-gray-50 to-white">
            <!-- Email tags section with improved design - 移动端优化 -->
            ${data.emails.length > 0 ? `
              <div class="mb-6 md:mb-10">
                <div class="flex flex-col md:flex-row md:items-center justify-between mb-4 md:mb-6 space-y-2 md:space-y-0">
                  <h3 class="text-lg md:text-2xl font-bold text-gray-800 flex items-center">
                    <div class="w-8 h-8 md:w-10 md:h-10 bg-gradient-to-br from-green-400 to-emerald-500 rounded-lg md:rounded-xl flex items-center justify-center mr-2 md:mr-3">
                      <svg class="w-4 h-4 md:w-5 md:h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
                        <path d="M3 8l7.89 7.89a1 1 0 001.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
                      </svg>
                    </div>
                    邮箱地址收集
                  </h3>
                  <div class="bg-gradient-to-r from-green-500 to-emerald-600 text-white px-3 md:px-4 py-1 md:py-2 rounded-full text-xs md:text-sm font-semibold shadow-lg">
                    ${data.emails.length} 个地址
                  </div>
                </div>
                
                <div class="bg-gradient-to-br from-green-50 via-emerald-50 to-teal-50 rounded-xl md:rounded-2xl p-4 md:p-6 border-2 border-green-100 shadow-inner">
                  <div class="flex flex-wrap gap-2 md:gap-3 max-h-32 md:max-h-40 overflow-y-auto custom-scrollbar">
                    ${data.emails.map((email, index) => `
                      <span class="email-tag group inline-flex items-center px-3 md:px-4 py-1 md:py-2 bg-gradient-to-r from-green-100 to-emerald-100 hover:from-green-200 hover:to-emerald-200 text-green-800 text-xs md:text-sm rounded-full border border-green-200 hover:border-green-300 transition-all duration-300 cursor-pointer hover:shadow-lg hover:scale-105 min-h-[32px] md:min-h-auto"
                            data-email="${email}" data-index="${index}">
                        <svg class="w-3 h-3 md:w-4 md:h-4 mr-1 md:mr-2 group-hover:animate-spin" fill="currentColor" viewBox="0 0 24 24">
                          <path d="M3 8l7.89 7.89a1 1 0 001.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
                        </svg>
                        ${email}
                      </span>
                    `).join('')}
                  </div>
                  <div class="mt-3 md:mt-4 text-center">
                    <p class="text-green-700 text-xs md:text-sm font-medium">💡 点击任意邮箱地址即可复制</p>
                  </div>
                </div>
              </div>
            ` : ''}

            <!-- Enhanced action buttons with better visual hierarchy - 移动端优化 -->
            <div class="mb-6 md:mb-10">
              <h3 class="text-lg md:text-2xl font-bold text-gray-800 mb-4 md:mb-6 flex items-center">
                <div class="w-8 h-8 md:w-10 md:h-10 bg-gradient-to-br from-blue-400 to-indigo-500 rounded-lg md:rounded-xl flex items-center justify-center mr-2 md:mr-3">
                  <svg class="w-4 h-4 md:w-5 md:h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
                    <path d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4"/>
                  </svg>
                </div>
                数据操作中心
              </h3>

              <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3 md:gap-4">
                <button id="download-json" class="group relative overflow-hidden bg-gradient-to-br from-blue-50 to-indigo-100 hover:from-blue-100 hover:to-indigo-200 border-2 border-blue-200 hover:border-blue-300 rounded-xl md:rounded-2xl p-3 md:p-6 transition-all duration-500 transform hover:scale-105 hover:shadow-xl min-h-[80px] md:min-h-auto">
                  <div class="absolute inset-0 bg-gradient-to-r from-blue-600/0 via-blue-600/10 to-blue-600/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000"></div>
                  <div class="relative z-10 flex flex-col items-center">
                    <div class="w-10 h-10 md:w-14 md:h-14 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl md:rounded-2xl flex items-center justify-center mb-2 md:mb-4 group-hover:rotate-12 transition-transform duration-300 shadow-lg">
                      <svg class="w-5 h-5 md:w-7 md:h-7 text-white" fill="currentColor" viewBox="0 0 24 24">
                        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/>
                        <path d="M14 2v6h6"/>
                        <path d="M16 13H8"/>
                        <path d="M16 17H8"/>
                        <path d="M10 9H8"/>
                      </svg>
                    </div>
                    <span class="font-bold text-blue-800 text-sm md:text-lg group-hover:text-blue-900">下载 JSON</span>
                    <span class="text-blue-600 text-xs mt-1 hidden md:block">结构化数据</span>
                  </div>
                </button>
                
                <button id="download-csv" class="group relative overflow-hidden bg-gradient-to-br from-green-50 to-emerald-100 hover:from-green-100 hover:to-emerald-200 border-2 border-green-200 hover:border-green-300 rounded-xl md:rounded-2xl p-3 md:p-6 transition-all duration-500 transform hover:scale-105 hover:shadow-xl min-h-[80px] md:min-h-auto">
                  <div class="absolute inset-0 bg-gradient-to-r from-green-600/0 via-green-600/10 to-green-600/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000"></div>
                  <div class="relative z-10 flex flex-col items-center">
                    <div class="w-10 h-10 md:w-14 md:h-14 bg-gradient-to-br from-green-500 to-green-600 rounded-xl md:rounded-2xl flex items-center justify-center mb-2 md:mb-4 group-hover:rotate-12 transition-transform duration-300 shadow-lg">
                      <svg class="w-5 h-5 md:w-7 md:h-7 text-white" fill="currentColor" viewBox="0 0 24 24">
                        <path d="M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm-7 14a1 1 0 0 1-1-1V8a1 1 0 0 1 2 0v8a1 1 0 0 1-1 1z"/>
                      </svg>
                    </div>
                    <span class="font-bold text-green-800 text-sm md:text-lg group-hover:text-green-900">下载 CSV</span>
                    <span class="text-green-600 text-xs mt-1 hidden md:block">电子表格</span>
                  </div>
                </button>

                <button id="copy-data" class="group relative overflow-hidden bg-gradient-to-br from-purple-50 to-violet-100 hover:from-purple-100 hover:to-violet-200 border-2 border-purple-200 hover:border-purple-300 rounded-xl md:rounded-2xl p-3 md:p-6 transition-all duration-500 transform hover:scale-105 hover:shadow-xl min-h-[80px] md:min-h-auto">
                  <div class="absolute inset-0 bg-gradient-to-r from-purple-600/0 via-purple-600/10 to-purple-600/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000"></div>
                  <div class="relative z-10 flex flex-col items-center">
                    <div class="w-10 h-10 md:w-14 md:h-14 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl md:rounded-2xl flex items-center justify-center mb-2 md:mb-4 group-hover:rotate-12 transition-transform duration-300 shadow-lg">
                      <svg class="w-5 h-5 md:w-7 md:h-7 text-white" fill="currentColor" viewBox="0 0 24 24">
                        <path d="M16 1H4a2 2 0 0 0-2 2v14h2V3h12V1zm3 4H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2zm0 16H8V7h11v14z"/>
                      </svg>
                    </div>
                    <span class="font-bold text-purple-800 text-sm md:text-lg group-hover:text-purple-900">复制数据</span>
                    <span class="text-purple-600 text-xs mt-1 hidden md:block">到剪贴板</span>
                  </div>
                </button>

                <button id="copy-emails" class="group relative overflow-hidden bg-gradient-to-br from-orange-50 to-amber-100 hover:from-orange-100 hover:to-amber-200 border-2 border-orange-200 hover:border-orange-300 rounded-xl md:rounded-2xl p-3 md:p-6 transition-all duration-500 transform hover:scale-105 hover:shadow-xl min-h-[80px] md:min-h-auto">
                  <div class="absolute inset-0 bg-gradient-to-r from-orange-600/0 via-orange-600/10 to-orange-600/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000"></div>
                  <div class="relative z-10 flex flex-col items-center">
                    <div class="w-10 h-10 md:w-14 md:h-14 bg-gradient-to-br from-orange-500 to-orange-600 rounded-xl md:rounded-2xl flex items-center justify-center mb-2 md:mb-4 group-hover:rotate-12 transition-transform duration-300 shadow-lg">
                      <svg class="w-5 h-5 md:w-7 md:h-7 text-white" fill="currentColor" viewBox="0 0 24 24">
                        <path d="M3 8l7.89 7.89a1 1 0 001.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
                      </svg>
                    </div>
                    <span class="font-bold text-orange-800 text-sm md:text-lg group-hover:text-orange-900">复制邮箱</span>
                    <span class="text-orange-600 text-xs mt-1 hidden md:block">邮件地址</span>
                  </div>
                </button>

                <button id="view-history" class="group relative overflow-hidden bg-gradient-to-br from-gray-50 to-slate-100 hover:from-gray-100 hover:to-slate-200 border-2 border-gray-200 hover:border-gray-300 rounded-xl md:rounded-2xl p-3 md:p-6 transition-all duration-500 transform hover:scale-105 hover:shadow-xl min-h-[80px] md:min-h-auto">
                  <div class="absolute inset-0 bg-gradient-to-r from-gray-600/0 via-gray-600/10 to-gray-600/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-1000"></div>
                  <div class="relative z-10 flex flex-col items-center">
                    <div class="w-10 h-10 md:w-14 md:h-14 bg-gradient-to-br from-gray-500 to-gray-600 rounded-xl md:rounded-2xl flex items-center justify-center mb-2 md:mb-4 group-hover:rotate-12 transition-transform duration-300 shadow-lg">
                      <svg class="w-5 h-5 md:w-7 md:h-7 text-white" fill="currentColor" viewBox="0 0 24 24">
                        <path d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
                      </svg>
                    </div>
                    <span class="font-bold text-gray-800 text-sm md:text-lg group-hover:text-gray-900">查看历史</span>
                    <span class="text-gray-600 text-xs mt-1 hidden md:block">操作记录</span>
                  </div>
                </button>
              </div>
            </div>

            <!-- Enhanced comments list with better styling -->
            <div>
              <div class="flex items-center justify-between mb-6">
                <h3 class="text-2xl font-bold text-gray-800 flex items-center">
                  <div class="w-10 h-10 bg-gradient-to-br from-indigo-400 to-purple-500 rounded-xl flex items-center justify-center mr-3">
                    <svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
                      <path d="M8 10h8M8 14h6M6 4h12a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2z"/>
                    </svg>
                  </div>
                  评论内容详览
                </h3>
                <div class="bg-gradient-to-r from-indigo-500 to-purple-600 text-white px-4 py-2 rounded-full text-sm font-semibold shadow-lg">
                  ${data.comments.length} 条评论
                </div>
              </div>
              
              <div class="bg-white rounded-2xl border-2 border-gray-100 shadow-xl overflow-hidden">
                <div class="max-h-96 overflow-y-auto custom-scrollbar bg-gradient-to-b from-gray-50 to-white">
                  <div class="divide-y divide-gray-100">
                    ${data.comments.map((comment, index) => `
                      <div class="p-6 hover:bg-gradient-to-r hover:from-blue-50 hover:to-indigo-50 transition-all duration-300 group">
                        <div class="flex items-start space-x-4">
                          <!-- Enhanced floor badge -->
                          <div class="flex-shrink-0">
                            <div class="w-12 h-12 bg-gradient-to-br from-blue-500 via-purple-500 to-pink-500 rounded-2xl flex items-center justify-center text-white font-bold text-sm shadow-lg group-hover:rotate-6 transition-transform duration-300">
                              ${comment.floor}
                            </div>
                          </div>
                          
                          <!-- Comment content with improved typography -->
                          <div class="flex-1 min-w-0">
                            <div class="flex items-center justify-between mb-3">
                              <h4 class="font-bold text-gray-900 text-lg group-hover:text-blue-700 transition-colors">
                                ${comment.author}
                              </h4>
                              <span class="text-sm text-gray-500 bg-gray-100 px-3 py-1 rounded-full">
                                ${comment.time}
                              </span>
                            </div>
                            
                            <div class="bg-white bg-opacity-60 rounded-xl p-4 border border-gray-100 group-hover:border-blue-200 transition-colors">
                              <p class="text-gray-800 leading-relaxed line-height-7">${comment.content}</p>
                            </div>
                            
                            ${comment.emails && comment.emails.length > 0 ? `
                              <div class="mt-4 flex flex-wrap gap-2">
                                ${comment.emails.map((email, emailIndex) => `
                                  <span class="comment-email-tag inline-flex items-center px-3 py-1 bg-gradient-to-r from-green-100 to-emerald-100 text-green-800 text-xs rounded-full border border-green-200 hover:from-green-200 hover:to-emerald-200 transition-colors cursor-pointer hover:shadow-md hover:scale-105"
                                        data-email="${email}" data-comment-index="${index}" data-email-index="${emailIndex}">
                                    <svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 24 24">
                                      <path d="M3 8l7.89 7.89a1 1 0 001.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
                                    </svg>
                                    ${email}
                                  </span>
                                `).join('')}
                              </div>
                            ` : ''}
                          </div>
                        </div>
                      </div>
                    `).join('')}
                  </div>
                </div>
                
                <!-- Comments list footer -->
                <div class="bg-gradient-to-r from-gray-100 to-gray-200 px-6 py-4 text-center">
                  <p class="text-gray-700 font-medium">
                    📊 共显示 <span class="font-bold text-indigo-600">${data.comments.length}</span> 条评论内容
                  </p>
                </div>
              </div>
            </div>
          </div>
        </div>
      `;

      this.attachResultsEventListeners(modal, data);
      document.body.appendChild(modal);
    }

    /**
     * 添加移动端手势支持
     */
    addMobileGestureSupport(modal) {
      if (!('ontouchstart' in window)) return; // 非触摸设备跳过

      const content = modal.querySelector('.discourse-extractor-content');
      let startY = 0;
      let currentY = 0;
      let isDragging = false;
      let startTime = 0;

      const handleTouchStart = (e) => {
        startY = e.touches[0].clientY;
        currentY = startY;
        startTime = Date.now();
        isDragging = true;
        content.style.transition = 'none';
      };

      const handleTouchMove = (e) => {
        if (!isDragging) return;

        currentY = e.touches[0].clientY;
        const deltaY = currentY - startY;

        // 只允许向下滑动
        if (deltaY > 0) {
          const opacity = Math.max(0.3, 1 - deltaY / 300);
          const scale = Math.max(0.9, 1 - deltaY / 1000);

          content.style.transform = `translateY(${deltaY}px) scale(${scale})`;
          modal.style.backgroundColor = `rgba(0, 0, 0, ${0.8 * opacity})`;
        }
      };

      const handleTouchEnd = (e) => {
        if (!isDragging) return;

        isDragging = false;
        const deltaY = currentY - startY;
        const deltaTime = Date.now() - startTime;
        const velocity = deltaY / deltaTime;

        content.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)';

        // 判断是否应该关闭模态框
        if (deltaY > 100 || velocity > 0.5) {
          // 关闭模态框
          content.style.transform = 'translateY(100vh) scale(0.8)';
          modal.style.backgroundColor = 'rgba(0, 0, 0, 0)';
          setTimeout(() => modal.remove(), 300);
        } else {
          // 恢复原位
          content.style.transform = 'translateY(0) scale(1)';
          modal.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
        }
      };

      // 添加触摸事件监听器
      content.addEventListener('touchstart', handleTouchStart, { passive: true });
      content.addEventListener('touchmove', handleTouchMove, { passive: true });
      content.addEventListener('touchend', handleTouchEnd, { passive: true });
    }

    /**
     * 添加结果模态框事件监听器
     */
    attachResultsEventListeners(modal, data) {
      const closeModal = () => {
        modal.remove();
      };

      // 关闭按钮
      modal.querySelector('.discourse-extractor-close').addEventListener('click', closeModal);
      modal.addEventListener('click', (e) => {
        if (e.target === modal) closeModal();
      });

      // 功能按钮事件
      modal.querySelector('#download-json').addEventListener('click', () => {
        this.downloadJSON(data);
      });

      modal.querySelector('#download-csv').addEventListener('click', () => {
        this.downloadCSV(data);
      });

      modal.querySelector('#copy-data').addEventListener('click', () => {
        this.copyData(data);
      });

      modal.querySelector('#copy-emails').addEventListener('click', () => {
        this.copyEmails(data);
      });

      // Email tag click handlers (main email section)
      modal.querySelectorAll('.email-tag').forEach(tag => {
        tag.addEventListener('click', () => {
          const email = tag.getAttribute('data-email');
          this.copySingleEmail(email);
        });
      });

      // Comment email tag click handlers
      modal.querySelectorAll('.comment-email-tag').forEach(tag => {
        tag.addEventListener('click', () => {
          const email = tag.getAttribute('data-email');
          this.copySingleEmail(email);
        });
      });

      modal.querySelector('#view-history').addEventListener('click', () => {
        closeModal();
        this.showHistory();
      });

      // 按钮点击效果
      modal.querySelectorAll('button[id]').forEach(btn => {
        btn.addEventListener('mousedown', () => {
          btn.style.transform = 'scale(0.95)';
        });
        btn.addEventListener('mouseup', () => {
          btn.style.transform = 'scale(1.05)';
        });
      });

      // 添加移动端手势支持
      this.addMobileGestureSupport(modal);
    }
  }

  /**
   * 存储管理器
   */
  class StorageManager {
    constructor(storageKey, maxRecords) {
      this.storageKey = storageKey;
      this.maxRecords = maxRecords;
    }

    /**
     * 获取历史记录
     */
    getHistory() {
      try {
        const history = localStorage.getItem(this.storageKey);
        return history ? JSON.parse(history) : [];
      } catch (error) {
        console.error('读取历史记录失败:', error);
        return [];
      }
    }

    /**
     * 保存记录
     */
    saveRecord(record) {
      try {
        const history = this.getHistory();
        const newRecord = {
          id: Date.now(),
          timestamp: new Date().toLocaleString('zh-CN'),
          url: window.location.href,
          pageTitle: document.title,
          ...record
        };

        history.unshift(newRecord);

        if (history.length > this.maxRecords) {
          history.splice(this.maxRecords);
        }

        localStorage.setItem(this.storageKey, JSON.stringify(history));
        return newRecord;
      } catch (error) {
        console.error('保存历史记录失败:', error);
        return null;
      }
    }

    /**
     * 清除历史记录
     */
    clearHistory() {
      try {
        localStorage.removeItem(this.storageKey);
        return true;
      } catch (error) {
        console.error('清除历史记录失败:', error);
        return false;
      }
    }
  }

  /**
   * 评论加载器
   */
  class CommentLoader {
    constructor(apiManager) {
      this.api = apiManager;
      this.maxAttempts = 30;
      this.loadDelay = 2500;
      this.cachedTotalPosts = null; // 缓存总帖子数
    }

    /**
     * 获取帖子总数
     */
    async getTotalPostsCount() {
      if (this.cachedTotalPosts !== null) {
        return this.cachedTotalPosts;
      }

      try {
        // 优先从 API 获取
        const apiCount = await this.api.getTopicPostsCount();
        if (apiCount > 0) {
          console.log('✅ 从 API 获取帖子总数:', apiCount);
          this.cachedTotalPosts = apiCount;
          return apiCount;
        }

        // 备选方案1: 从页面进度指示器获取
        const progressElement = document.querySelector('.topic-timeline .timeline-last-read, .timeline-last-read');
        if (progressElement) {
          const progressText = progressElement.textContent.trim();
          const match = progressText.match(/\d+\s*\/\s*(\d+)/);
          if (match) {
            const domCount = parseInt(match[1]);
            console.log('✅ 从页面进度获取帖子总数:', domCount);
            this.cachedTotalPosts = domCount;
            return domCount;
          }
        }

        // 备选方案2: 从全局对象获取
        if (window.Discourse?.currentTopic?.posts_count) {
          const globalCount = window.Discourse.currentTopic.posts_count;
          console.log('✅ 从全局对象获取帖子总数:', globalCount);
          this.cachedTotalPosts = globalCount;
          return globalCount;
        }

        // 备选方案3: 估算当前可见帖子数量
        const posts = document.querySelectorAll('.topic-post[data-post-id], article[data-post-id]');
        const estimatedCount = Math.max(posts.length, 50); // 至少假设50个帖子
        console.log('⚠️ 使用估算帖子总数:', estimatedCount);
        this.cachedTotalPosts = estimatedCount;
        return estimatedCount;

      } catch (error) {
        console.error('❌ 获取帖子总数失败:', error);
        // 最后的默认值
        this.cachedTotalPosts = 100;
        return 100;
      }
    }

    /**
     * 加载所有评论
     */
    async loadAllComments(progressCallback) {
      let attempts = 0;
      let consecutiveNoProgress = 0;

      console.log('🔄 开始自动加载所有评论...');

      // 获取真实的帖子总数
      const totalPosts = await this.getTotalPostsCount();
      console.log('📊 帖子总数:', totalPosts);

      while (attempts < this.maxAttempts) {
        attempts++;

        const progress = await this.getLoadingProgress();
        console.log(`第 ${attempts} 次尝试,当前进度: ${progress.current}/${progress.total}`);

        if (progressCallback) {
          progressCallback(progress.current, progress.total, attempts);
        }

        if (progress.current >= progress.total) {
          console.log('✅ 已达到目标数量,停止加载');
          break;
        }

        if (!this.hasMoreContent(progress)) {
          console.log('❌ 没有更多内容可加载');
          break;
        }

        const beforeProgress = progress.current;
        const loaded = await this.loadMoreContent();

        if (!loaded) {
          consecutiveNoProgress++;
        } else {
          consecutiveNoProgress = 0;
        }

        await this.sleep(this.loadDelay);

        const afterProgress = await this.getLoadingProgress();
        if (afterProgress.current === beforeProgress) {
          consecutiveNoProgress++;
          if (consecutiveNoProgress >= 5) {
            console.log('❌ 连续多次无进度,停止加载');
            break;
          }
        }
      }

      const finalProgress = await this.getLoadingProgress();
      console.log(`✅ 加载完成,最终进度: ${finalProgress.current}/${finalProgress.total}`);
      return finalProgress;
    }

    /**
     * 获取加载进度
     */
    async getLoadingProgress() {
      // 获取真实的总帖子数
      const totalPosts = await this.getTotalPostsCount();

      // 从页面右侧的进度指示器获取当前进度
      const progressNavigation = document.querySelector('.topic-timeline .timeline-last-read, .timeline-last-read');
      if (progressNavigation) {
        const progressText = progressNavigation.textContent.trim();
        const match = progressText.match(/(\d+)\s*\/\s*(\d+)/);
        if (match) {
          return {
            current: parseInt(match[1]),
            total: parseInt(match[2]) // 使用页面显示的真实总数
          };
        }
      }

      // 备选方案:计算当前可见的帖子数量
      const posts = document.querySelectorAll('.topic-post[data-post-id], article[data-post-id]');
      const uniquePostIds = new Set();
      posts.forEach(post => {
        const postId = post.getAttribute('data-post-id');
        if (postId) uniquePostIds.add(postId);
      });

      return {
        current: uniquePostIds.size,
        total: totalPosts // 使用从 API 获取的真实总数
      };
    }

    /**
     * 检查是否有更多内容
     */
    hasMoreContent(progress) {
      if (!progress) return true;
      return progress.current < progress.total;
    }

    /**
     * 加载更多内容
     */
    async loadMoreContent() {
      const beforeHeight = document.body.scrollHeight;
      this.scrollToBottom();
      await this.sleep(1500);
      const afterHeight = document.body.scrollHeight;
      return afterHeight > beforeHeight;
    }

    /**
     * 滚动到底部
     */
    scrollToBottom() {
      window.scrollTo(0, document.body.scrollHeight);
    }

    /**
     * 等待函数
     */
    sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
  }

  /**
   * 评论提取器
   */
  class CommentExtractor {
    constructor(emailRegex) {
      this.emailRegex = emailRegex;
    }

    /**
     * 提取评论
     */
    extractComments(options = {}) {
      const {
        mode = 'all',
        startFloor = 1,
        endFloor = null,
        randomCount = 10,
        extractEmails = true
      } = options;

      const allComments = [];
      const allEmails = new Set();
      const processedPostIds = new Set();

      const posts = document.querySelectorAll('.topic-post[data-post-id], article[data-post-id]');
      console.log(`找到 ${posts.length} 个帖子元素`);

      // 首先提取所有评论
      posts.forEach((post, index) => {
        const postId = post.getAttribute('data-post-id');
        if (!postId || processedPostIds.has(postId)) return;
        processedPostIds.add(postId);

        const authorElement = post.querySelector('.username, .trigger-user-card');
        const author = authorElement ? authorElement.textContent.trim() : '未知用户';

        const timeElement = post.querySelector('.post-date, .relative-date, time');
        const time = timeElement ? timeElement.textContent.trim() : '未知时间';

        const contentElement = post.querySelector('.cooked, .post-content');
        const content = contentElement ? contentElement.textContent.trim() : '';

        if (content && content.length > 0) {
          // 只在需要时提取邮箱
          const postEmails = extractEmails ? (content.match(this.emailRegex) || []) : [];

          const comment = {
            id: postId,
            floor: index + 1,
            author: author,
            time: time,
            content: content,
            emails: postEmails
          };

          allComments.push(comment);

          // 收集所有邮箱
          if (extractEmails) {
            postEmails.forEach(email => allEmails.add(email));
          }
        }
      });

      // 根据模式过滤评论
      let filteredComments = [];
      switch (mode) {
        case 'range':
          const actualEndFloor = endFloor || allComments.length;
          filteredComments = allComments.filter(comment =>
            comment.floor >= startFloor && comment.floor <= actualEndFloor
          );
          console.log(`楼层范围过滤: ${startFloor}-${actualEndFloor}, 结果: ${filteredComments.length} 条评论`);
          break;

        case 'random':
          const shuffled = [...allComments].sort(() => 0.5 - Math.random());
          filteredComments = shuffled.slice(0, Math.min(randomCount, allComments.length));
          filteredComments.sort((a, b) => a.floor - b.floor);
          console.log(`随机提取: ${randomCount} 条, 实际获得: ${filteredComments.length} 条评论`);
          break;

        default: // 'all'
          filteredComments = allComments;
          console.log(`全部提取: ${filteredComments.length} 条评论`);
          break;
      }

      // 如果是范围或随机模式,重新计算邮箱
      const finalEmails = new Set();
      if (extractEmails && (mode === 'range' || mode === 'random')) {
        filteredComments.forEach(comment => {
          if (comment.emails) {
            comment.emails.forEach(email => finalEmails.add(email));
          }
        });
      } else if (extractEmails) {
        // 全部模式使用之前收集的所有邮箱
        allEmails.forEach(email => finalEmails.add(email));
      }

      const result = {
        comments: filteredComments,
        emails: extractEmails ? Array.from(finalEmails) : [],
        pageTitle: document.title,
        pageUrl: window.location.href,
        extractTime: new Date().toLocaleString('zh-CN'),
        extractConfig: options,
        totalComments: allComments.length
      };

      console.log('📊 提取结果:', {
        模式: mode,
        总评论数: result.totalComments,
        提取评论数: result.comments.length,
        邮箱数: result.emails.length,
        是否提取邮箱: extractEmails
      });

      return result;
    }
  }

  // 初始化
  function init() {
    const extractor = new DiscourseCommentExtractor();
    extractor.init();
  }

  // 启动
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();