Chapter 07: Options Page

Haiyue
72min

Chapter 7: Options Page

Learning Objectives

  1. Understand the purpose and characteristics of the Options page
  2. Master the development techniques for complex settings interfaces
  3. Learn data validation and settings synchronization mechanisms
  4. Implement user-friendly configuration experiences

7.1 Options Page Overview

The Options page serves as the central settings hub for Chrome extensions, offering more comprehensive configuration features and greater operational space than a Popup.

7.1.1 Characteristics and Purpose

🔄 正在渲染 Mermaid 图表...

tip Core Features

  • An independent, full webpage with more space
  • Supports complex forms and interactions
  • Can include multiple configuration categories
  • Setting changes affect extension behavior
  • Supports importing/exporting configurations

7.1.2 Manifest Configuration

{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0",
  "options_page": "options.html",
  "options_ui": {
    "page": "options.html",
    "open_in_tab": true
  },
  "permissions": [
    "storage"
  ]
}

7.2 Complete Options Page Design

7.2.1 HTML Structure

<!-- options.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Extension Settings</title>
  <link rel="stylesheet" href="options.css">
</head>
<body>
  <div class="container">
    <!-- Header Navigation -->
    <header class="header">
      <div class="header-content">
        <div class="logo-section">
          <img src="icons/icon48.png" alt="Logo" class="logo">
          <div class="title-section">
            <h1>Extension Settings</h1>
            <p class="version">Version 1.0.0</p>
          </div>
        </div>
        <div class="header-actions">
          <button class="btn btn-secondary" id="exportBtn">Export Settings</button>
          <button class="btn btn-secondary" id="importBtn">Import Settings</button>
          <input type="file" id="importFile" accept=".json" style="display: none;">
        </div>
      </div>
    </header>

    <div class="main-wrapper">
      <!-- Sidebar Navigation -->
      <nav class="sidebar">
        <ul class="nav-list">
          <li class="nav-item active" data-tab="general">
            <span class="nav-icon">⚙️</span>
            <span class="nav-text">General Settings</span>
          </li>
          <li class="nav-item" data-tab="features">
            <span class="nav-icon">🛠️</span>
            <span class="nav-text">Feature Configuration</span>
          </li>
          <li class="nav-item" data-tab="appearance">
            <span class="nav-icon">🎨</span>
            <span class="nav-text">Appearance Settings</span>
          </li>
          <li class="nav-item" data-tab="advanced">
            <span class="nav-icon">🔧</span>
            <span class="nav-text">Advanced Settings</span>
          </li>
          <li class="nav-item" data-tab="about">
            <span class="nav-icon">ℹ️</span>
            <span class="nav-text">About</span>
          </li>
        </ul>
      </nav>

      <!-- Main Content Area -->
      <main class="main-content">
        <!-- General Settings -->
        <section class="tab-content active" id="general">
          <div class="content-header">
            <h2>General Settings</h2>
            <p>Configure basic behavior and functionality of the extension</p>
          </div>

          <div class="settings-group">
            <h3>Basic Functionality</h3>

            <div class="setting-item">
              <div class="setting-label">
                <label for="enableExtension">Enable Extension</label>
                <p class="setting-description">Control the global switch of the extension</p>
              </div>
              <div class="setting-control">
                <label class="toggle-switch">
                  <input type="checkbox" id="enableExtension">
                  <span class="toggle-slider"></span>
                </label>
              </div>
            </div>

            <div class="setting-item">
              <div class="setting-label">
                <label for="autoStart">Auto Start on Browser Launch</label>
                <p class="setting-description">Automatically activate the extension when the browser starts</p>
              </div>
              <div class="setting-control">
                <label class="toggle-switch">
                  <input type="checkbox" id="autoStart">
                  <span class="toggle-slider"></span>
                </label>
              </div>
            </div>

            <div class="setting-item">
              <div class="setting-label">
                <label for="updateInterval">Update Interval</label>
                <p class="setting-description">Time interval for automatic data updates</p>
              </div>
              <div class="setting-control">
                <select id="updateInterval" class="setting-select">
                  <option value="1">1 Minute</option>
                  <option value="5">5 Minutes</option>
                  <option value="10">10 Minutes</option>
                  <option value="30">30 Minutes</option>
                  <option value="60">1 Hour</option>
                </select>
              </div>
            </div>

            <div class="setting-item">
              <div class="setting-label">
                <label for="maxCacheSize">Cache Size Limit</label>
                <p class="setting-description">Maximum cache data size (MB)</p>
              </div>
              <div class="setting-control">
                <div class="input-group">
                  <input type="range" id="cacheSlider" min="10" max="500" value="100" class="range-input">
                  <input type="number" id="maxCacheSize" min="10" max="500" value="100" class="number-input">
                  <span class="input-unit">MB</span>
                </div>
              </div>
            </div>
          </div>

          <div class="settings-group">
            <h3>Notification Settings</h3>

            <div class="setting-item">
              <div class="setting-label">
                <label for="enableNotifications">Enable Notifications</label>
                <p class="setting-description">Allow the extension to display desktop notifications</p>
              </div>
              <div class="setting-control">
                <label class="toggle-switch">
                  <input type="checkbox" id="enableNotifications">
                  <span class="toggle-slider"></span>
                </label>
              </div>
            </div>

            <div class="setting-item notification-dependent" style="display: none;">
              <div class="setting-label">
                <label for="notificationSound">Notification Sound</label>
                <p class="setting-description">Play a sound alert for notifications</p>
              </div>
              <div class="setting-control">
                <label class="toggle-switch">
                  <input type="checkbox" id="notificationSound">
                  <span class="toggle-slider"></span>
                </label>
              </div>
            </div>
          </div>
        </section>

        <!-- Feature Configuration -->
        <section class="tab-content" id="features">
          <div class="content-header">
            <h2>Feature Configuration</h2>
            <p>Enable or disable specific feature modules</p>
          </div>

          <div class="settings-group">
            <h3>Core Features</h3>

            <div class="feature-grid">
              <div class="feature-card">
                <div class="feature-header">
                  <div class="feature-icon">📊</div>
                  <div class="feature-info">
                    <h4>Data Analysis</h4>
                    <p>Analyze web page data and user behavior</p>
                  </div>
                  <label class="toggle-switch">
                    <input type="checkbox" id="featureAnalytics">
                    <span class="toggle-slider"></span>
                  </label>
                </div>
                <div class="feature-settings" id="analyticsSettings" style="display: none;">
                  <div class="sub-setting">
                    <label>
                      <input type="checkbox" id="trackClicks"> Track click events
                    </label>
                  </div>
                  <div class="sub-setting">
                    <label>
                      <input type="checkbox" id="trackPageViews"> Track page views
                    </label>
                  </div>
                </div>
              </div>

              <div class="feature-card">
                <div class="feature-header">
                  <div class="feature-icon">🔒</div>
                  <div class="feature-info">
                    <h4>Privacy Protection</h4>
                    <p>Block tracking and protect privacy</p>
                  </div>
                  <label class="toggle-switch">
                    <input type="checkbox" id="featurePrivacy">
                    <span class="toggle-slider"></span>
                  </label>
                </div>
                <div class="feature-settings" id="privacySettings" style="display: none;">
                  <div class="sub-setting">
                    <label>
                      <input type="checkbox" id="blockTrackers"> Block trackers
                    </label>
                  </div>
                  <div class="sub-setting">
                    <label>
                      <input type="checkbox" id="blockAds"> Block ads
                    </label>
                  </div>
                </div>
              </div>
            </div>
          </div>

          <div class="settings-group">
            <h3>Website Whitelist</h3>
            <div class="whitelist-section">
              <div class="input-group">
                <input type="url" id="whitelistInput" placeholder="Enter website domain, e.g.: example.com">
                <button type="button" id="addWhitelistBtn" class="btn btn-primary">Add</button>
              </div>
              <ul class="whitelist-list" id="whitelistList">
                <!-- Dynamically generated whitelist items -->
              </ul>
            </div>
          </div>
        </section>

        <!-- Appearance Settings -->
        <section class="tab-content" id="appearance">
          <div class="content-header">
            <h2>Appearance Settings</h2>
            <p>Customize interface appearance and theme</p>
          </div>

          <div class="settings-group">
            <h3>Theme Settings</h3>

            <div class="theme-selector">
              <div class="theme-option" data-theme="light">
                <div class="theme-preview light-preview">
                  <div class="preview-header"></div>
                  <div class="preview-content">
                    <div class="preview-line"></div>
                    <div class="preview-line short"></div>
                  </div>
                </div>
                <div class="theme-info">
                  <h4>Light Theme</h4>
                  <p>Classic light interface</p>
                </div>
                <input type="radio" name="theme" value="light" id="themeLight">
              </div>

              <div class="theme-option" data-theme="dark">
                <div class="theme-preview dark-preview">
                  <div class="preview-header"></div>
                  <div class="preview-content">
                    <div class="preview-line"></div>
                    <div class="preview-line short"></div>
                  </div>
                </div>
                <div class="theme-info">
                  <h4>Dark Theme</h4>
                  <p>Eye-friendly dark interface</p>
                </div>
                <input type="radio" name="theme" value="dark" id="themeDark">
              </div>

              <div class="theme-option" data-theme="auto">
                <div class="theme-preview auto-preview">
                  <div class="preview-header"></div>
                  <div class="preview-content">
                    <div class="preview-line"></div>
                    <div class="preview-line short"></div>
                  </div>
                </div>
                <div class="theme-info">
                  <h4>Follow System</h4>
                  <p>Automatically switch themes</p>
                </div>
                <input type="radio" name="theme" value="auto" id="themeAuto">
              </div>
            </div>
          </div>

          <div class="settings-group">
            <h3>Font Settings</h3>

            <div class="setting-item">
              <div class="setting-label">
                <label for="fontSize">Font Size</label>
                <p class="setting-description">Adjust interface font size</p>
              </div>
              <div class="setting-control">
                <div class="input-group">
                  <input type="range" id="fontSizeSlider" min="12" max="18" value="14" class="range-input">
                  <span class="font-size-display">14px</span>
                </div>
              </div>
            </div>

            <div class="setting-item">
              <div class="setting-label">
                <label for="fontFamily">Font Family</label>
                <p class="setting-description">Select interface font</p>
              </div>
              <div class="setting-control">
                <select id="fontFamily" class="setting-select">
                  <option value="system">System Default</option>
                  <option value="sans-serif">Sans-serif</option>
                  <option value="serif">Serif</option>
                  <option value="monospace">Monospace</option>
                </select>
              </div>
            </div>
          </div>
        </section>

        <!-- Advanced Settings -->
        <section class="tab-content" id="advanced">
          <div class="content-header">
            <h2>Advanced Settings</h2>
            <p>Configuration options for advanced users</p>
          </div>

          <div class="settings-group">
            <h3>Developer Options</h3>

            <div class="setting-item">
              <div class="setting-label">
                <label for="debugMode">Debug Mode</label>
                <p class="setting-description">Enable detailed debugging information</p>
              </div>
              <div class="setting-control">
                <label class="toggle-switch">
                  <input type="checkbox" id="debugMode">
                  <span class="toggle-slider"></span>
                </label>
              </div>
            </div>

            <div class="setting-item">
              <div class="setting-label">
                <label for="logLevel">Log Level</label>
                <p class="setting-description">Control the verbosity of log output</p>
              </div>
              <div class="setting-control">
                <select id="logLevel" class="setting-select">
                  <option value="error">Errors Only</option>
                  <option value="warn">Warnings and Above</option>
                  <option value="info">Info and Above</option>
                  <option value="debug">Debug and Above</option>
                </select>
              </div>
            </div>
          </div>

          <div class="settings-group">
            <h3>Data Management</h3>

            <div class="setting-item">
              <div class="setting-label">
                <label>Clear Cache</label>
                <p class="setting-description">Delete all cached data</p>
              </div>
              <div class="setting-control">
                <button type="button" id="clearCacheBtn" class="btn btn-secondary">Clear Cache</button>
              </div>
            </div>

            <div class="setting-item">
              <div class="setting-label">
                <label>Reset Settings</label>
                <p class="setting-description">Restore all settings to default values</p>
              </div>
              <div class="setting-control">
                <button type="button" id="resetSettingsBtn" class="btn btn-danger">Reset Settings</button>
              </div>
            </div>
          </div>
        </section>

        <!-- About Page -->
        <section class="tab-content" id="about">
          <div class="content-header">
            <h2>About Extension</h2>
            <p>Version information and related links</p>
          </div>

          <div class="about-content">
            <div class="about-card">
              <div class="about-icon">
                <img src="icons/icon128.png" alt="Extension Icon">
              </div>
              <div class="about-info">
                <h3>My Chrome Extension</h3>
                <p class="version-info">Version 1.0.0</p>
                <p class="description">This is a powerful Chrome extension that helps users improve browsing efficiency.</p>
              </div>
            </div>

            <div class="links-section">
              <h4>Related Links</h4>
              <ul class="links-list">
                <li><a href="#" id="homepageLink" target="_blank">Official Website</a></li>
                <li><a href="#" id="supportLink" target="_blank">Technical Support</a></li>
                <li><a href="#" id="feedbackLink" target="_blank">Feedback</a></li>
                <li><a href="#" id="privacyLink" target="_blank">Privacy Policy</a></li>
              </ul>
            </div>

            <div class="changelog-section">
              <h4>Changelog</h4>
              <div class="changelog-list">
                <div class="changelog-item">
                  <div class="changelog-version">v1.0.0</div>
                  <div class="changelog-date">2025-12-03</div>
                  <div class="changelog-content">
                    <ul>
                      <li>Initial version release</li>
                      <li>Basic functionality complete</li>
                      <li>User interface optimized</li>
                    </ul>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </section>
      </main>
    </div>

    <!-- Save Status Notification -->
    <div class="save-status" id="saveStatus" style="display: none;">
      <span class="status-icon">✓</span>
      <span class="status-text">Settings Saved</span>
    </div>
  </div>

  <!-- Confirmation Dialog -->
  <div class="modal-overlay" id="confirmModal" style="display: none;">
    <div class="modal">
      <div class="modal-header">
        <h3 id="modalTitle">Confirm Operation</h3>
      </div>
      <div class="modal-body">
        <p id="modalMessage">Are you sure you want to perform this operation?</p>
      </div>
      <div class="modal-footer">
        <button type="button" id="modalCancel" class="btn btn-secondary">Cancel</button>
        <button type="button" id="modalConfirm" class="btn btn-primary">Confirm</button>
      </div>
    </div>
  </div>

  <script src="options.js"></script>
</body>
</html>

7.2.2 Options Page Generator Configuration

Configuration structure and example for dynamically generating the Options page:

// options_generator.js - Chrome Extension Options Page Generator
class OptionsGenerator {
  constructor() {
    this.title = "Extension Settings";
    this.version = "1.0.0";
    this.tabs = [];
    this.settingsStructure = {};
  }

  addTab(tabId, icon, title, description) {
    const tab = {
      id: tabId,
      icon: icon,
      title: title,
      description: description,
      groups: []
    };
    this.tabs.push(tab);
  }

  addSettingsGroup(tabId, groupTitle, settings) {
    const tab = this.tabs.find(t => t.id === tabId);
    if (tab) {
      const group = {
        title: groupTitle,
        settings: settings
      };
      tab.groups.push(group);
    }
  }

  generateSettingHTML(setting) {
    const settingType = setting.type || 'checkbox';
    const settingId = setting.id || '';
    const label = setting.label || '';
    const description = setting.description || '';

    if (settingType === 'checkbox') {
      return `
            <div class="setting-item">
                <div class="setting-label">
                    <label for="${settingId}">${label}</label>
                    <p class="setting-description">${description}</p>
                </div>
                <div class="setting-control">
                    <label class="toggle-switch">
                        <input type="checkbox" id="${settingId}">
                        <span class="toggle-slider"></span>
                    </label>
                </div>
            </div>`;
    }

    if (settingType === 'select') {
      const optionsHTML = setting.options.map(option =>
        `<option value="${option.value}">${option.text}</option>`
      ).join('');

      return `
            <div class="setting-item">
                <div class="setting-label">
                    <label for="${settingId}">${label}</label>
                    <p class="setting-description">${description}</p>
                </div>
                <div class="setting-control">
                    <select id="${settingId}" class="setting-select">
                        ${optionsHTML}
                    </select>
                </div>
            </div>`;
    }

    if (settingType === 'range') {
      const minVal = setting.min || 0;
      const maxVal = setting.max || 100;
      const defaultVal = setting.default || 50;
      const unit = setting.unit || '';

      return `
            <div class="setting-item">
                <div class="setting-label">
                    <label for="${settingId}">${label}</label>
                    <p class="setting-description">${description}</p>
                </div>
                <div class="setting-control">
                    <div class="input-group">
                        <input type="range" id="${settingId}Slider" min="${minVal}"
                               max="${maxVal}" value="${defaultVal}" class="range-input">
                        <input type="number" id="${settingId}" min="${minVal}"
                               max="${maxVal}" value="${defaultVal}" class="number-input">
                        <span class="input-unit">${unit}</span>
                    </div>
                </div>
            </div>`;
    }

    return '';
  }

  generateTabContent(tab) {
    const groupsHTML = tab.groups.map(group => {
      const settingsHTML = group.settings.map(setting =>
        this.generateSettingHTML(setting)
      ).join('');

      return `
            <div class="settings-group">
                <h3>${group.title}</h3>
                ${settingsHTML}
            </div>`;
    }).join('');

    return `
        <section class="tab-content" id="${tab.id}">
            <div class="content-header">
                <h2>${tab.title}</h2>
                <p>${tab.description}</p>
            </div>
            ${groupsHTML}
        </section>`;
  }

  generateNavigation() {
    const navItems = this.tabs.map((tab, i) => {
      const activeClass = i === 0 ? ' active' : '';
      return `
            <li class="nav-item${activeClass}" data-tab="${tab.id}">
                <span class="nav-icon">${tab.icon}</span>
                <span class="nav-text">${tab.title}</span>
            </li>`;
    }).join('');

    return `
        <nav class="sidebar">
            <ul class="nav-list">
                ${navItems}
            </ul>
        </nav>`;
  }

  generateCompleteHTML() {
    const navigationHTML = this.generateNavigation();
    const contentHTML = this.tabs.map((tab, i) => {
      const activeClass = i === 0 ? ' active' : '';
      let tabHTML = this.generateTabContent(tab);
      tabHTML = tabHTML.replace('class="tab-content"',
                                `class="tab-content${activeClass}"`);
      return tabHTML;
    }).join('');

    return `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${this.title}</title>
    <link rel="stylesheet" href="options.css">
</head>
<body>
    <div class="container">
        <header class="header">
            <div class="header-content">
                <div class="logo-section">
                    <img src="icons/icon48.png" alt="Logo" class="logo">
                    <div class="title-section">
                        <h1>${this.title}</h1>
                        <p class="version">Version ${this.version}</p>
                    </div>
                </div>
            </div>
        </header>

        <div class="main-wrapper">
            ${navigationHTML}

            <main class="main-content">
                ${contentHTML}
            </main>
        </div>
    </div>

    <script src="options.js"></script>
</body>
</html>`;
  }
}

// Usage example
function createSampleOptions() {
  const generator = new OptionsGenerator();

  // Add General Settings tab
  generator.addTab('general', '⚙️', 'General Settings', 'Configure basic behavior and functionality of the extension');
  generator.addSettingsGroup('general', 'Basic Functionality', [
    {
      type: 'checkbox',
      id: 'enableExtension',
      label: 'Enable Extension',
      description: 'Control the global switch of the extension'
    },
    {
      type: 'select',
      id: 'updateInterval',
      label: 'Update Interval',
      description: 'Time interval for automatic data updates',
      options: [
        { value: '1', text: '1 Minute' },
        { value: '5', text: '5 Minutes' },
        { value: '10', text: '10 Minutes' }
      ]
    },
    {
      type: 'range',
      id: 'maxCacheSize',
      label: 'Cache Size Limit',
      description: 'Maximum cache data size',
      min: 10,
      max: 500,
      default: 100,
      unit: 'MB'
    }
  ]);

  // Add Appearance Settings tab
  generator.addTab('appearance', '🎨', 'Appearance Settings', 'Customize interface appearance and theme');
  generator.addSettingsGroup('appearance', 'Theme Settings', [
    {
      type: 'select',
      id: 'theme',
      label: 'Theme',
      description: 'Select interface theme',
      options: [
        { value: 'light', text: 'Light Theme' },
        { value: 'dark', text: 'Dark Theme' },
        { value: 'auto', text: 'Follow System' }
      ]
    },
    {
      type: 'range',
      id: 'fontSize',
      label: 'Font Size',
      description: 'Adjust interface font size',
      min: 12,
      max: 18,
      default: 14,
      unit: 'px'
    }
  ]);

  return generator.generateCompleteHTML();
}

// Generate example page
const sampleHTML = createSampleOptions();
console.log("Generated Options page HTML:", sampleHTML.substring(0, 1000) + "...");

7.3.1 Modern Layout Styles

/* options.css */
:root {
  --primary-color: #4285f4;
  --secondary-color: #34a853;
  --danger-color: #ea4335;
  --warning-color: #fbbc05;
  --text-primary: #202124;
  --text-secondary: #5f6368;
  --text-muted: #9aa0a6;
  --background-primary: #ffffff;
  --background-secondary: #f8f9fa;
  --background-tertiary: #e8f0fe;
  --border-color: #dadce0;
  --border-light: #f1f3f4;
  --shadow-light: 0 1px 3px rgba(0, 0, 0, 0.1);
  --shadow-medium: 0 4px 6px rgba(0, 0, 0, 0.1);
  --shadow-heavy: 0 8px 16px rgba(0, 0, 0, 0.15);
  --border-radius: 8px;
  --border-radius-large: 12px;
  --transition: all 0.2s ease;
  --sidebar-width: 240px;
}

/* Dark theme */
.dark-theme {
  --text-primary: #e8eaed;
  --text-secondary: #9aa0a6;
  --text-muted: #5f6368;
  --background-primary: #202124;
  --background-secondary: #303134;
  --background-tertiary: #1a73e8;
  --border-color: #5f6368;
  --border-light: #3c4043;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-primary);
  background-color: var(--background-primary);
  overflow-x: hidden;
}

.container {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Header styles */
.header {
  background-color: var(--background-primary);
  border-bottom: 1px solid var(--border-color);
  padding: 20px 24px;
  position: sticky;
  top: 0;
  z-index: 100;
  box-shadow: var(--shadow-light);
}

.header-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  max-width: 1200px;
  margin: 0 auto;
}

.logo-section {
  display: flex;
  align-items: center;
  gap: 16px;
}

.logo {
  width: 40px;
  height: 40px;
  border-radius: var(--border-radius);
}

.title-section h1 {
  font-size: 24px;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 2px;
}

.version {
  font-size: 12px;
  color: var(--text-secondary);
}

.header-actions {
  display: flex;
  gap: 12px;
}

/* Main layout */
.main-wrapper {
  flex: 1;
  display: flex;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
}

/* Sidebar navigation */
.sidebar {
  width: var(--sidebar-width);
  background-color: var(--background-secondary);
  border-right: 1px solid var(--border-color);
  padding: 24px 0;
  position: sticky;
  top: 81px;
  height: calc(100vh - 81px);
  overflow-y: auto;
}

.nav-list {
  list-style: none;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 24px;
  cursor: pointer;
  transition: var(--transition);
  color: var(--text-secondary);
}

.nav-item:hover {
  background-color: var(--background-tertiary);
  color: var(--primary-color);
}

.nav-item.active {
  background-color: var(--background-tertiary);
  color: var(--primary-color);
  border-right: 3px solid var(--primary-color);
}

.nav-icon {
  font-size: 18px;
  width: 24px;
  text-align: center;
}

.nav-text {
  font-weight: 500;
}

/* Main content area */
.main-content {
  flex: 1;
  padding: 24px;
  overflow-y: auto;
  max-height: calc(100vh - 81px);
}

.tab-content {
  display: none;
  animation: fadeIn 0.3s ease;
}

.tab-content.active {
  display: block;
}

.content-header {
  margin-bottom: 32px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--border-light);
}

.content-header h2 {
  font-size: 28px;
  font-weight: 400;
  color: var(--text-primary);
  margin-bottom: 8px;
}

.content-header p {
  font-size: 16px;
  color: var(--text-secondary);
}

/* Settings group */
.settings-group {
  margin-bottom: 40px;
  background-color: var(--background-primary);
  border: 1px solid var(--border-light);
  border-radius: var(--border-radius-large);
  overflow: hidden;
  box-shadow: var(--shadow-light);
}

.settings-group h3 {
  font-size: 16px;
  font-weight: 500;
  color: var(--text-primary);
  padding: 16px 20px;
  background-color: var(--background-secondary);
  border-bottom: 1px solid var(--border-light);
  margin: 0;
}

/* Setting item */
.setting-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 20px;
  border-bottom: 1px solid var(--border-light);
}

.setting-item:last-child {
  border-bottom: none;
}

.setting-label {
  flex: 1;
  margin-right: 20px;
}

.setting-label label {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-primary);
  display: block;
  margin-bottom: 4px;
  cursor: pointer;
}

.setting-description {
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.4;
}

.setting-control {
  flex-shrink: 0;
  display: flex;
  align-items: center;
}

/* Toggle switch styles */
.toggle-switch {
  position: relative;
  display: inline-block;
  width: 44px;
  height: 24px;
  cursor: pointer;
}

.toggle-switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-slider {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  transition: var(--transition);
  border-radius: 24px;
}

.toggle-slider:before {
  position: absolute;
  content: "";
  height: 18px;
  width: 18px;
  left: 3px;
  bottom: 3px;
  background-color: white;
  transition: var(--transition);
  border-radius: 50%;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.toggle-switch input:checked + .toggle-slider {
  background-color: var(--primary-color);
}

.toggle-switch input:checked + .toggle-slider:before {
  transform: translateX(20px);
}

/* Select box styles */
.setting-select {
  padding: 8px 32px 8px 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background-color: var(--background-primary);
  color: var(--text-primary);
  font-size: 14px;
  cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
  background-position: right 8px center;
  background-repeat: no-repeat;
  background-size: 16px;
  min-width: 120px;
}

.setting-select:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px rgba(66, 133, 244, 0.1);
}

/* Input group styles */
.input-group {
  display: flex;
  align-items: center;
  gap: 12px;
}

.range-input {
  width: 120px;
  height: 6px;
  border-radius: 3px;
  background: var(--border-color);
  outline: none;
  appearance: none;
}

.range-input::-webkit-slider-thumb {
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--primary-color);
  cursor: pointer;
  box-shadow: var(--shadow-light);
}

.range-input::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--primary-color);
  cursor: pointer;
  border: none;
  box-shadow: var(--shadow-light);
}

.number-input {
  width: 60px;
  padding: 6px 8px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background-color: var(--background-primary);
  color: var(--text-primary);
  font-size: 13px;
  text-align: center;
}

.input-unit {
  font-size: 12px;
  color: var(--text-secondary);
  min-width: 20px;
}

/* Button styles */
.btn {
  padding: 8px 16px;
  border: none;
  border-radius: var(--border-radius);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: var(--transition);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-decoration: none;
}

.btn:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-medium);
}

.btn:active {
  transform: translateY(0);
}

.btn-primary {
  background-color: var(--primary-color);
  color: white;
}

.btn-primary:hover {
  background-color: #3367d6;
}

.btn-secondary {
  background-color: var(--background-secondary);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
}

.btn-secondary:hover {
  background-color: var(--border-color);
}

.btn-danger {
  background-color: var(--danger-color);
  color: white;
}

.btn-danger:hover {
  background-color: #d33b2c;
}

/* Feature cards */
.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
}

.feature-card {
  border: 1px solid var(--border-light);
  border-radius: var(--border-radius-large);
  padding: 20px;
  background-color: var(--background-primary);
  box-shadow: var(--shadow-light);
  transition: var(--transition);
}

.feature-card:hover {
  box-shadow: var(--shadow-medium);
}

.feature-header {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 12px;
}

.feature-icon {
  font-size: 24px;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--background-tertiary);
  border-radius: var(--border-radius);
}

.feature-info {
  flex: 1;
}

.feature-info h4 {
  font-size: 16px;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 4px;
}

.feature-info p {
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.4;
}

.feature-settings {
  padding-top: 12px;
  border-top: 1px solid var(--border-light);
}

.sub-setting {
  padding: 8px 0;
}

.sub-setting label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-primary);
  cursor: pointer;
}

/* Theme selector */
.theme-selector {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
}

.theme-option {
  border: 2px solid var(--border-light);
  border-radius: var(--border-radius-large);
  padding: 16px;
  cursor: pointer;
  transition: var(--transition);
  position: relative;
}

.theme-option:hover {
  border-color: var(--primary-color);
  box-shadow: var(--shadow-medium);
}

.theme-option input[type="radio"] {
  position: absolute;
  top: 12px;
  right: 12px;
}

.theme-preview {
  width: 100%;
  height: 80px;
  border-radius: var(--border-radius);
  margin-bottom: 12px;
  overflow: hidden;
  border: 1px solid var(--border-light);
}

.light-preview {
  background: linear-gradient(to bottom, #f8f9fa 30%, #ffffff 30%);
}

.dark-preview {
  background: linear-gradient(to bottom, #303134 30%, #202124 30%);
}

.auto-preview {
  background: linear-gradient(to right, #f8f9fa 50%, #303134 50%);
}

.preview-header {
  height: 30%;
  background-color: rgba(0, 0, 0, 0.1);
}

.preview-content {
  padding: 8px;
}

.preview-line {
  height: 4px;
  background-color: rgba(0, 0, 0, 0.2);
  border-radius: 2px;
  margin-bottom: 4px;
}

.preview-line.short {
  width: 60%;
}

.theme-info h4 {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 2px;
}

.theme-info p {
  font-size: 12px;
  color: var(--text-secondary);
}

/* Whitelist styles */
.whitelist-section .input-group {
  margin-bottom: 16px;
}

.whitelist-section input[type="url"] {
  flex: 1;
  padding: 10px 12px;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  font-size: 14px;
}

.whitelist-list {
  list-style: none;
  border: 1px solid var(--border-light);
  border-radius: var(--border-radius);
  max-height: 200px;
  overflow-y: auto;
}

.whitelist-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border-light);
}

.whitelist-item:last-child {
  border-bottom: none;
}

.whitelist-url {
  font-size: 14px;
  color: var(--text-primary);
}

.remove-btn {
  color: var(--danger-color);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--border-radius);
  font-size: 12px;
}

.remove-btn:hover {
  background-color: var(--danger-color);
  color: white;
}

/* About page styles */
.about-content {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.about-card {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 24px;
  background-color: var(--background-secondary);
  border-radius: var(--border-radius-large);
  border: 1px solid var(--border-light);
}

.about-icon img {
  width: 64px;
  height: 64px;
  border-radius: var(--border-radius-large);
}

.about-info h3 {
  font-size: 20px;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 4px;
}

.version-info {
  font-size: 14px;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

.description {
  font-size: 14px;
  color: var(--text-primary);
  line-height: 1.5;
}

.links-section h4, 
.changelog-section h4 {
  font-size: 16px;
  font-weight: 500;
  color: var(--text-primary);
  margin-bottom: 12px;
}

.links-list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.links-list a {
  color: var(--primary-color);
  text-decoration: none;
  font-size: 14px;
  padding: 6px 12px;
  border-radius: var(--border-radius);
  border: 1px solid var(--primary-color);
  transition: var(--transition);
}

.links-list a:hover {
  background-color: var(--primary-color);
  color: white;
}

.changelog-list {
  background-color: var(--background-secondary);
  border-radius: var(--border-radius-large);
  overflow: hidden;
  border: 1px solid var(--border-light);
}

.changelog-item {
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-light);
}

.changelog-item:last-child {
  border-bottom: none;
}

.changelog-version {
  font-size: 14px;
  font-weight: 600;
  color: var(--primary-color);
}

.changelog-date {
  font-size: 12px;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

.changelog-content ul {
  margin-left: 16px;
}

.changelog-content li {
  font-size: 13px;
  color: var(--text-primary);
  line-height: 1.4;
  margin-bottom: 2px;
}

/* Save status notification */
.save-status {
  position: fixed;
  bottom: 20px;
  right: 20px;
  padding: 12px 16px;
  background-color: var(--secondary-color);
  color: white;
  border-radius: var(--border-radius);
  display: flex;
  align-items: center;
  gap: 8px;
  box-shadow: var(--shadow-heavy);
  z-index: 1000;
  animation: slideInRight 0.3s ease;
}

.status-icon {
  font-weight: bold;
}

/* Modal styles */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2000;
}

.modal {
  background-color: var(--background-primary);
  border-radius: var(--border-radius-large);
  box-shadow: var(--shadow-heavy);
  max-width: 400px;
  width: 90%;
  overflow: hidden;
}

.modal-header {
  padding: 16px 20px;
  background-color: var(--background-secondary);
  border-bottom: 1px solid var(--border-light);
}

.modal-header h3 {
  font-size: 16px;
  font-weight: 500;
  color: var(--text-primary);
}

.modal-body {
  padding: 20px;
}

.modal-body p {
  font-size: 14px;
  color: var(--text-primary);
  line-height: 1.5;
}

.modal-footer {
  padding: 16px 20px;
  background-color: var(--background-secondary);
  border-top: 1px solid var(--border-light);
  display: flex;
  gap: 12px;
  justify-content: flex-end;
}

/* Animations */
@keyframes fadeIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes slideInRight {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}

/* Responsive design */
@media (max-width: 768px) {
  .main-wrapper {
    flex-direction: column;
  }

  .sidebar {
    width: 100%;
    height: auto;
    position: static;
    border-right: none;
    border-bottom: 1px solid var(--border-color);
  }

  .nav-list {
    display: flex;
    overflow-x: auto;
    padding: 0 12px;
  }

  .nav-item {
    white-space: nowrap;
    padding: 12px 16px;
    border-right: none;
    border-bottom: 3px solid transparent;
  }

  .nav-item.active {
    border-right: none;
    border-bottom-color: var(--primary-color);
  }

  .main-content {
    max-height: none;
  }

  .header-content {
    flex-direction: column;
    gap: 16px;
    text-align: center;
  }

  .feature-grid {
    grid-template-columns: 1fr;
  }

  .theme-selector {
    grid-template-columns: 1fr;
  }

  .about-card {
    flex-direction: column;
    text-align: center;
  }
}

@media (max-width: 480px) {
  .header {
    padding: 16px;
  }

  .main-content {
    padding: 16px;
  }

  .setting-item {
    flex-direction: column;
    gap: 12px;
    align-items: stretch;
  }

  .setting-control {
    align-self: flex-end;
  }
}

/* Print styles */
@media print {
  .sidebar, 
  .header-actions, 
  .save-status, 
  .modal-overlay {
    display: none;
  }

  .main-wrapper {
    flex-direction: column;
  }

  .main-content {
    max-height: none;
  }

  .tab-content {
    display: block !important;
    page-break-before: always;
  }

  .tab-content:first-child {
    page-break-before: auto;
  }
}

7.4 JavaScript Interaction Logic

7.4.1 Complete Options Script

// options.js
class OptionsManager {
  constructor() {
    this.settings = {};
    this.defaults = {
      enableExtension: true,
      autoStart: false,
      updateInterval: 5,
      maxCacheSize: 100,
      enableNotifications: true,
      notificationSound: false,
      featureAnalytics: false,
      featurePrivacy: true,
      theme: 'light',
      fontSize: 14,
      fontFamily: 'system',
      debugMode: false,
      logLevel: 'error',
      whitelist: []
    };

    this.init();
  }

  async init() {
    try {
      await this.loadSettings();
      this.setupNavigation();
      this.bindEvents();
      this.populateUI();
      this.setupDependencies();

      console.log('Options page initialized');
    } catch (error) {
      console.error('Initialization failed:', error);
      this.showToast('Initialization failed, please refresh the page', 'error');
    }
  }

  async loadSettings() {
    try {
      const result = await chrome.storage.sync.get(null);
      this.settings = { ...this.defaults, ...result };
    } catch (error) {
      console.error('Failed to load settings:', error);
      this.settings = { ...this.defaults };
    }
  }

  setupNavigation() {
    const navItems = document.querySelectorAll('.nav-item');
    const tabContents = document.querySelectorAll('.tab-content');

    navItems.forEach(item => {
      item.addEventListener('click', () => {
        const tabId = item.dataset.tab;

        // Update navigation state
        navItems.forEach(nav => nav.classList.remove('active'));
        item.classList.add('active');

        // Show corresponding tab content
        tabContents.forEach(content => {
          content.classList.remove('active');
          if (content.id === tabId) {
            content.classList.add('active');
          }
        });
      });
    });
  }

  bindEvents() {
    // Event binding for general settings items
    this.bindCheckboxes();
    this.bindSelects();
    this.bindRanges();
    this.bindButtons();
    this.bindSpecialControls();
  }

  bindCheckboxes() {
    const checkboxes = document.querySelectorAll('input[type="checkbox"]');

    checkboxes.forEach(checkbox => {
      checkbox.addEventListener('change', (e) => {
        const key = e.target.id;
        const value = e.target.checked;
        this.updateSetting(key, value);
      });
    });
  }

  bindSelects() {
    const selects = document.querySelectorAll('select');

    selects.forEach(select => {
      select.addEventListener('change', (e) => {
        const key = e.target.id;
        const value = e.target.value;

        if (key === 'updateInterval') {
          this.updateSetting(key, parseInt(value));
        } else {
          this.updateSetting(key, value);
        }
      });
    });
  }

  bindRanges() {
    // Cache size slider
    const cacheSlider = document.getElementById('cacheSlider');
    const cacheInput = document.getElementById('maxCacheSize');

    if (cacheSlider && cacheInput) {
      const updateCacheSize = (value) => {
        cacheSlider.value = value;
        cacheInput.value = value;
        this.updateSetting(key, parseInt(value));
      };

      cacheSlider.addEventListener('input', (e) => {
        updateCacheSize(e.target.value);
      });

      cacheInput.addEventListener('change', (e) => {
        updateCacheSize(e.target.value);
      });
    }

    // Font size slider
    const fontSizeSlider = document.getElementById('fontSizeSlider');
    const fontSizeDisplay = document.querySelector('.font-size-display');

    if (fontSizeSlider && fontSizeDisplay) {
      fontSizeSlider.addEventListener('input', (e) => {
        const value = parseInt(e.target.value);
        fontSizeDisplay.textContent = `${value}px`;
        this.updateSetting('fontSize', value);
        this.applyFontSize(value);
      });
    }
  }

  bindButtons() {
    // Export settings
    const exportBtn = document.getElementById('exportBtn');
    if (exportBtn) {
      exportBtn.addEventListener('click', () => this.exportSettings());
    }

    // Import settings
    const importBtn = document.getElementById('importBtn');
    const importFile = document.getElementById('importFile');

    if (importBtn && importFile) {
      importBtn.addEventListener('click', () => importFile.click());
      importFile.addEventListener('change', (e) => this.importSettings(e));
    }

    // Clear cache
    const clearCacheBtn = document.getElementById('clearCacheBtn');
    if (clearCacheBtn) {
      clearCacheBtn.addEventListener('click', () => this.clearCache());
    }

    // Reset settings
    const resetBtn = document.getElementById('resetSettingsBtn');
    if (resetBtn) {
      resetBtn.addEventListener('click', () => this.resetSettings());
    }

    // Whitelist management
    const addWhitelistBtn = document.getElementById('addWhitelistBtn');
    if (addWhitelistBtn) {
      addWhitelistBtn.addEventListener('click', () => this.addToWhitelist());
    }

    // Feature card toggling
    this.bindFeatureCards();
  }

  bindSpecialControls() {
    // Theme selection
    const themeRadios = document.querySelectorAll('input[name="theme"]');
    themeRadios.forEach(radio => {
      radio.addEventListener('change', (e) => {
        if (e.target.checked) {
          this.updateSetting('theme', e.target.value);
          this.applyTheme(e.target.value);
        }
      });
    });

    // Notification settings dependencies
    const notificationToggle = document.getElementById('enableNotifications');
    if (notificationToggle) {
      notificationToggle.addEventListener('change', (e) => {
        this.toggleNotificationDependencies(e.target.checked);
      });
    }
  }

  bindFeatureCards() {
    const featureToggles = {
      'featureAnalytics': 'analyticsSettings',
      'featurePrivacy': 'privacySettings'
    };

    Object.entries(featureToggles).forEach(([toggleId, settingsId]) => {
      const toggle = document.getElementById(toggleId);
      const settings = document.getElementById(settingsId);

      if (toggle && settings) {
        toggle.addEventListener('change', (e) => {
          settings.style.display = e.target.checked ? 'block' : 'none';
        });
      }
    });
  }

  populateUI() {
    // Populate checkboxes
    Object.keys(this.settings).forEach(key => {
      const element = document.getElementById(key);

      if (element) {
        if (element.type === 'checkbox') {
          element.checked = this.settings[key];
        } else if (element.tagName === 'SELECT') {
          element.value = this.settings[key];
        } else if (element.type === 'number' || element.type === 'range') {
          element.value = this.settings[key];
        }
      }
    });

    // Set theme radio button
    const themeRadio = document.getElementById(`theme${this.settings.theme.charAt(0).toUpperCase() + this.settings.theme.slice(1)}`);
    if (themeRadio) {
      themeRadio.checked = true;
    }

    // Synchronize slider and input fields
    this.syncRangeInputs();

    // Populate whitelist
    this.populateWhitelist();

    // Apply current theme
    this.applyTheme(this.settings.theme);

    // Apply font size
    this.applyFontSize(this.settings.fontSize);

    // Update feature card states
    this.updateFeatureCards();
  }

7.4.1 Complete Options Script (Continued)

  syncRangeInputs() {
    // Cache size
    const cacheSlider = document.getElementById('cacheSlider');
    const cacheInput = document.getElementById('maxCacheSize');

    if (cacheSlider && cacheInput) {
      const value = this.settings.maxCacheSize;
      cacheSlider.value = value;
      cacheInput.value = value;
    }

    // Font size
    const fontSizeSlider = document.getElementById('fontSizeSlider');
    const fontSizeDisplay = document.querySelector('.font-size-display');

    if (fontSizeSlider && fontSizeDisplay) {
      const value = this.settings.fontSize;
      fontSizeSlider.value = value;
      fontSizeDisplay.textContent = `${value}px`;
    }
  }

  setupDependencies() {
    // Notification settings dependencies
    this.toggleNotificationDependencies(this.settings.enableNotifications);
  }

  toggleNotificationDependencies(enabled) {
    const dependentElements = document.querySelectorAll('.notification-dependent');
    dependentElements.forEach(el => {
      el.style.display = enabled ? 'flex' : 'none';
    });
  }

  updateFeatureCards() {
    const featureToggles = {
      'featureAnalytics': 'analyticsSettings',
      'featurePrivacy': 'privacySettings'
    };

    Object.entries(featureToggles).forEach(([toggleId, settingsId]) => {
      const toggle = document.getElementById(toggleId);
      const settings = document.getElementById(settingsId);

      if (toggle && settings) {
        settings.style.display = toggle.checked ? 'block' : 'none';
      }
    });
  }

  async updateSetting(key, value) {
    this.settings[key] = value;

    try {
      await chrome.storage.sync.set({ [key]: value });
      this.showSaveStatus();

      // Notify background script of setting change
      chrome.runtime.sendMessage({
        action: 'settingChanged',
        key: key,
        value: value
      });

    } catch (error) {
      console.error('Failed to save settings:', error);
      this.showToast('Failed to save settings', 'error');
    }
  }

  applyTheme(theme) {
    document.body.className = `${theme}-theme`;

    // If auto theme, decide based on system settings
    if (theme === 'auto') {
      const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
      document.body.className = `${isDark ? 'dark' : 'light'}-theme`;
    }
  }

  applyFontSize(size) {
    document.documentElement.style.setProperty('--base-font-size', `${size}px`);
  }

  // Whitelist management
  populateWhitelist() {
    const whitelistList = document.getElementById('whitelistList');
    if (!whitelistList) return;

    whitelistList.innerHTML = '';

    this.settings.whitelist.forEach((domain, index) => {
      const listItem = document.createElement('li');
      listItem.className = 'whitelist-item';
      listItem.innerHTML = `
        <span class="whitelist-url">${domain}</span>
        <button type="button" class="remove-btn" data-index="${index}">Remove</button>
      `;

      listItem.querySelector('.remove-btn').addEventListener('click', () => {
        this.removeFromWhitelist(index);
      });

      whitelistList.appendChild(listItem);
    });
  }

  addToWhitelist() {
    const input = document.getElementById('whitelistInput');
    if (!input) return;

    const domain = input.value.trim();
    if (!domain) {
      this.showToast('Please enter a valid domain', 'warning');
      return;
    }

    // Simple domain validation
    const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-_.]*\.[a-zA-Z]{2,}$/;
    if (!domainRegex.test(domain)) {
      this.showToast('Please enter a valid domain format', 'warning');
      return;
    }

    if (this.settings.whitelist.includes(domain)) {
      this.showToast('This domain is already in the whitelist', 'warning');
      return;
    }

    this.settings.whitelist.push(domain);
    this.updateSetting('whitelist', this.settings.whitelist);
    this.populateWhitelist();

    input.value = '';
    this.showToast('Domain added to whitelist', 'success');
  }

  removeFromWhitelist(index) {
    this.settings.whitelist.splice(index, 1);
    this.updateSetting('whitelist', this.settings.whitelist);
    this.populateWhitelist();
    this.showToast('Domain removed from whitelist', 'success');
  }

  // Configuration import/export
  exportSettings() {
    const dataStr = JSON.stringify(this.settings, null, 2);
    const dataBlob = new Blob([dataStr], { type: 'application/json' });

    const link = document.createElement('a');
    link.href = URL.createObjectURL(dataBlob);
    link.download = `extension-settings-${new Date().toISOString().split('T')[0]}.json`;
    link.click();

    this.showToast('Configuration exported', 'success');
  }

  async importSettings(event) {
    const file = event.target.files[0];
    if (!file) return;

    try {
      const text = await file.text();
      const importedSettings = JSON.parse(text);

      // Validate imported settings
      const validSettings = {};
      Object.keys(this.defaults).forEach(key => {
        if (importedSettings.hasOwnProperty(key)) {
          validSettings[key] = importedSettings[key];
        }
      });

      this.showConfirmDialog(
        'Confirm Import',
        'Importing configuration will overwrite current settings, are you sure you want to continue?',
        async () => {
          this.settings = { ...this.defaults, ...validSettings };
          await chrome.storage.sync.set(this.settings);
          this.populateUI();
          this.showToast('Configuration imported', 'success');
        }
      );

    } catch (error) {
      console.error('Import failed:', error);
      this.showToast('Invalid configuration file format', 'error');
    }

    // Clear file selection
    event.target.value = '';
  }

  // Data management
  async clearCache() {
    this.showConfirmDialog(
      'Clear Cache',
      'Are you sure you want to clear all cached data? This operation cannot be undone.',
      async () => {
        try {
          await chrome.storage.local.clear();
          this.showToast('Cache cleared', 'success');
        } catch (error) {
          console.error('Failed to clear cache:', error);
          this.showToast('Failed to clear cache', 'error');
        }
      }
    );
  }

  async resetSettings() {
    this.showConfirmDialog(
      'Reset Settings',
      'Are you sure you want to restore all settings to default values? This operation cannot be undone.',
      async () => {
        try {
          this.settings = { ...this.defaults };
          await chrome.storage.sync.set(this.settings);
          this.populateUI();
          this.showToast('Settings reset', 'success');
        } catch (error) {
          console.error('Failed to reset settings:', error);
          this.showToast('Failed to reset settings', 'error');
        }
      }
    );
  }

  // UI feedback methods
  showSaveStatus() {
    const saveStatus = document.getElementById('saveStatus');
    if (saveStatus) {
      saveStatus.style.display = 'flex';

      setTimeout(() => {
        saveStatus.style.display = 'none';
      }, 2000);
    }
  }

  showToast(message, type = 'info') {
    const toast = document.createElement('div');
    toast.className = `toast toast-${type}`;
    toast.textContent = message;

    toast.style.cssText = `
      position: fixed;
      top: 20px;
      right: 20px;
      padding: 12px 16px;
      border-radius: 6px;
      color: white;
      font-size: 14px;
      z-index: 10000;
      animation: slideInRight 0.3s ease;
      background-color: ${ 
        type === 'success' ? '#34a853' :
        type === 'error' ? '#ea4335' :
        type === 'warning' ? '#fbbc05' : '#4285f4'
      };
    `;

    document.body.appendChild(toast);

    setTimeout(() => {
      toast.style.animation = 'fadeOut 0.3s ease';
      setTimeout(() => {
        if (toast.parentNode) {
          toast.parentNode.removeChild(toast);
        }
      }, 300);
    }, 3000);
  }

  showConfirmDialog(title, message, onConfirm) {
    const modal = document.getElementById('confirmModal');
    const modalTitle = document.getElementById('modalTitle');
    const modalMessage = document.getElementById('modalMessage');
    const modalConfirm = document.getElementById('modalConfirm');
    const modalCancel = document.getElementById('modalCancel');

    if (modal && modalTitle && modalMessage && modalConfirm && modalCancel) {
      modalTitle.textContent = title;
      modalMessage.textContent = message;

      const hideModal = () => {
        modal.style.display = 'none';
      };

      modalConfirm.onclick = () => {
        hideModal();
        onConfirm();
      };

      modalCancel.onclick = hideModal;

      modal.onclick = (e) => {
        if (e.target === modal) hideModal();
      };

      modal.style.display = 'flex';
    }
  }
}

// Initialization
document.addEventListener('DOMContentLoaded', () => {
  new OptionsManager();
});

// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
  // Ctrl/Cmd + S: Quick save
  if ((e.ctrlKey || e.metaKey) && e.key === 's') {
    e.preventDefault();
    // Trigger save status display
    const event = new Event('change');
    document.querySelector('input[type="checkbox"]')?.dispatchEvent(event);
  }

  // Escape: Close modal
  if (e.key === 'Escape') {
    const modal = document.getElementById('confirmModal');
    if (modal && modal.style.display === 'flex') {
      modal.style.display = 'none';
    }
  }
});

// Listen for system theme changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
  const currentTheme = document.querySelector('input[name="theme"]:checked')?.value;
  if (currentTheme === 'auto') {
    document.body.className = `${e.matches ? 'dark' : 'light'}-theme`;
  }
});

7.4.2 Extending Settings Manager Functionality

Advanced functionality implementation for the extension settings manager:

// settings_manager.js - Chrome Extension Settings Manager
class SettingsManager {
  constructor(configFile = "extension_settings") {
    this.configFile = configFile;
    this.defaults = {
      enableExtension: true,
      autoStart: false,
      updateInterval: 5,
      maxCacheSize: 100,
      enableNotifications: true,
      notificationSound: false,
      featureAnalytics: false,
      featurePrivacy: true,
      theme: 'light',
      fontSize: 14,
      fontFamily: 'system',
      debugMode: false,
      logLevel: 'error',
      whitelist: []
    };
    this.settings = {};
    this.loadSettings();
  }

  async loadSettings() {
    try {
      const result = await chrome.storage.sync.get(null);
      this.settings = { ...this.defaults, ...result };
      console.log("Settings loaded successfully");
    } catch (error) {
      console.error("Failed to load settings:", error);
      this.settings = { ...this.defaults };
    }
  }

  async saveSettings() {
    try {
      await chrome.storage.sync.set(this.settings);
      console.log("Settings saved successfully");
      return true;
    } catch (error) {
      console.error("Failed to save settings:", error);
      return false;
    }
  }

  getSetting(key, defaultValue = null) {
    return this.settings[key] !== undefined ? this.settings[key] : defaultValue;
  }

  async updateSetting(key, value) {
    if (key in this.defaults) {
      const oldValue = this.settings[key];
      this.settings[key] = value;

      if (await this.saveSettings()) {
        console.log(`Setting updated: ${key}: ${oldValue} -> ${value}`);
        this.onSettingChanged(key, value, oldValue);
        return true;
      } else {
        // Rollback changes
        this.settings[key] = oldValue;
        return false;
      }
    } else {
      console.error(`Unknown setting key: ${key}`);
      return false;
    }
  }

  onSettingChanged(key, newValue, oldValue) {
    if (key === 'theme') {
      console.log(`Theme changed to: ${newValue}`);
    } else if (key === 'enableExtension') {
      console.log(`Extension ${newValue ? 'enabled' : 'disabled'}`);
    } else if (key === 'whitelist') {
      if (newValue.length > oldValue.length) {
        console.log("New whitelist domain added");
      } else if (newValue.length < oldValue.length) {
        console.log("Whitelist domain removed");
      }
    }
  }

  async addToWhitelist(domain) {
    if (!this.isValidDomain(domain)) {
      console.error(`Invalid domain: ${domain}`);
      return false;
    }

    const whitelist = this.settings.whitelist || [];
    if (whitelist.includes(domain)) {
      console.log(`Domain already exists: ${domain}`);
      return false;
    }

    whitelist.push(domain);
    return await this.updateSetting('whitelist', whitelist);
  }

  async removeFromWhitelist(domain) {
    const whitelist = this.settings.whitelist || [];
    if (!whitelist.includes(domain)) {
      console.log(`Domain not in whitelist: ${domain}`);
      return false;
    }

    const newWhitelist = whitelist.filter(d => d !== domain);
    return await this.updateSetting('whitelist', newWhitelist);
  }

  isValidDomain(domain) {
    const pattern = /^[a-zA-Z0-9][a-zA-Z0-9-_.]*\.[a-zA-Z]{2,}$/;
    return pattern.test(domain);
  }

  async exportSettings(filename = null) {
    if (!filename) {
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
      filename = `extension_settings_export_${timestamp}.json`;
    }

    try {
      const exportData = {
        exported_at: new Date().toISOString(),
        version: '1.0.0',
        settings: this.settings
      };

      const dataStr = JSON.stringify(exportData, null, 2);
      const dataBlob = new Blob([dataStr], { type: 'application/json' });
      const link = document.createElement('a');
      link.href = URL.createObjectURL(dataBlob);
      link.download = filename;
      link.click();

      console.log(`Settings exported to: ${filename}`);
      return filename;
    } catch (error) {
      console.error("Failed to export settings:", error);
      return "";
    }
  }

  async importSettings(file) {
    try {
      const text = await file.text();
      const importData = JSON.parse(text);

      let importedSettings;
      if ('settings' in importData) {
        importedSettings = importData.settings;
      } else {
        importedSettings = importData;
      }

      // Validate and import settings
      const validSettings = {};
      Object.keys(this.defaults).forEach(key => {
        if (key in importedSettings) {
          validSettings[key] = importedSettings[key];
        }
      });

      this.settings = { ...this.defaults, ...validSettings };

      if (await this.saveSettings()) {
        console.log(`Settings imported from file`);
        return true;
      } else {
        return false;
      }
    } catch (error) {
      console.error("Failed to import settings:", error);
      return false;
    }
  }

  async resetSettings() {
    this.settings = { ...this.defaults };
    if (await this.saveSettings()) {
      console.log("Settings reset to default values");
      return true;
    }
    return false;
  }

  getAllSettings() {
    return { ...this.settings };
  }

  validateSettings() {
    const errors = [];

    // Validate update interval
    const interval = this.settings.updateInterval || 5;
    if (!Number.isInteger(interval) || interval < 1 || interval > 60) {
      errors.push("Update interval must be an integer between 1-60");
    }

    // Validate cache size
    const cacheSize = this.settings.maxCacheSize || 100;
    if (!Number.isInteger(cacheSize) || cacheSize < 10 || cacheSize > 500) {
      errors.push("Cache size must be an integer between 10-500");
    }

    // Validate font size
    const fontSize = this.settings.fontSize || 14;
    if (!Number.isInteger(fontSize) || fontSize < 12 || fontSize > 18) {
      errors.push("Font size must be an integer between 12-18");
    }

    // Validate theme
    const theme = this.settings.theme || 'light';
    if (!['light', 'dark', 'auto'].includes(theme)) {
      errors.push("Theme must be one of light, dark, or auto");
    }

    // Validate whitelist
    const whitelist = this.settings.whitelist || [];
    if (!Array.isArray(whitelist)) {
      errors.push("Whitelist must be an array");
    } else {
      whitelist.forEach(domain => {
        if (!this.isValidDomain(domain)) {
          errors.push(`Invalid whitelist domain: ${domain}`);
        }
      });
    }

    return errors;
  }

  printSettingsSummary() {
    console.log("\n=== Extension Settings Summary ===");
    console.log(`Extension Status: ${this.settings.enableExtension ? 'Enabled' : 'Disabled'}`);
    console.log(`Theme: ${this.settings.theme || 'light'}`);
    console.log(`Update Interval: ${this.settings.updateInterval || 5} minutes`);
    console.log(`Cache Size: ${this.settings.maxCacheSize || 100}MB`);
    console.log(`Notifications: ${this.settings.enableNotifications ? 'Enabled' : 'Disabled'}`);
    console.log(`Whitelisted Domains: ${(this.settings.whitelist || []).length}`);
    console.log(`Debug Mode: ${this.settings.debugMode ? 'Enabled' : 'Disabled'}`);
    console.log("==================\n");
  }
}

// Usage example
async function demoSettingsManager() {
  const manager = new SettingsManager();

  // Wait for settings to load
  await manager.loadSettings();

  // Display current settings summary
  manager.printSettingsSummary();

  // Update some settings
  await manager.updateSetting('theme', 'dark');
  await manager.updateSetting('updateInterval', 10);
  await manager.updateSetting('enableNotifications', false);

  // Add whitelist domains
  await manager.addToWhitelist('example.com');
  await manager.addToWhitelist('google.com');

  // Validate settings
  const errors = manager.validateSettings();
  if (errors.length > 0) {
    console.log("Settings validation errors:");
    errors.forEach(error => console.log(`  - ${error}`));
  } else {
    console.log("All settings validated successfully");
  }

  // Export settings
  const exportFile = await manager.exportSettings();

  // Display final settings summary
  manager.printSettingsSummary();

  return manager;
}

// Example: Using in an extension
// const settingsManager = new SettingsManager();

Settings Manager Functionality Table

FunctionMethodDescription
Load SettingsloadSettings()Loads all settings from chrome.storage.sync
Save SettingssaveSettings()Saves settings to chrome.storage.sync
Get SettinggetSetting(key, default)Gets a single setting value
Update SettingupdateSetting(key, value)Updates a single setting and saves it
Export SettingsexportSettings(filename)Exports settings to a JSON file
Import SettingsimportSettings(file)Imports settings from a JSON file
Reset SettingsresetSettings()Restores all settings to default values
Validate SettingsvalidateSettings()Validates the validity of all settings
Whitelist ManagementaddToWhitelist(domain)Adds a domain to the whitelist
Whitelist ManagementremoveFromWhitelist(domain)Removes a domain from the whitelist

warning Best Practices

  1. Data Validation: Always validate user-entered setting values
  2. Default Values: Provide reasonable default values for all setting items
  3. Error Handling: Gracefully handle storage failure scenarios
  4. User Experience: Provide instant visual feedback
  5. Performance Optimization: Avoid frequent storage operations

tip Extended Features

  • Support setting grouping and searching
  • Implement setting item dependencies
  • Add setting validation rules
  • Support batch operations
  • Provide advanced user mode

note Summary This chapter introduces the complete development process for Chrome extension Options pages, including complex HTML structure, modern CSS styles, comprehensive JavaScript interaction logic, and best practices for settings management. An excellent Options page can significantly enhance user experience and extension usability. The next chapter will cover messaging mechanisms to enable efficient communication between extension components.

Categories