Skip to content

Latest commit

 

History

History
187 lines (156 loc) · 7.33 KB

CONTRIBUTING.md

File metadata and controls

187 lines (156 loc) · 7.33 KB

Contributing to Video Speed HUD & Question Time Watcher

Thank you for your interest in contributing! This guide outlines the project architecture, data schemas, coding guidelines, and workflows to help you build and test features for the extension.


🛠️ Project Architecture & Execution Worlds

The extension is built on Manifest V3 and runs in two distinct contexts (worlds) to balance features and security:

graph TD
    A[Browser Toolbar Popup / popup.js] <--> |chrome.storage.local| B[Isolated Content Scripts]
    B --> |DOM / events| C[Host Webpage Video Player]
    E[PW Enhancements Plugin / pw-enhancements.js] --> |REST API / Proxy| F[PW Slides API]
    E --> |DOM / controls| C
Loading

1. Isolated Execution World (Default Content Scripts)

Files in src/core/ (e.g., speed-controller.js, question-timer.js, silence-skipper.js, study-tracker.js) execute in the browser's isolated content script sandbox.

  • Capabilities: Direct access to the DOM, standard page events, and the chrome.storage API.
  • Security: Isolated from page scripts to ensure security and prevent prototype pollution.

💾 Chrome Local Storage Schema

All extension data is stored in chrome.storage.local. Please keep the following data structures intact when updating storage logic:

1. Per-URL Question Timer Logs (qt_log_[encodedUrl])

  • Key: 'qt_log_' + encodeURIComponent(cleanUrl) (where cleanUrl excludes the hash # component).
  • Value:
    interface QuestionTimerSession {
      url: string;
      currentQuestionNum: number;
      elapsedTime: number; // In-progress stopwatch time (ms)
      isRunning: boolean;
      startTime: number; // Timestamp of start/resume
      logs: Array<{
        qNum: number;
        durationSec: number;
        timeFormatted: string;
        targetSec: number; // 0 if benchmark disabled
        isOnTime: boolean;
      }>;
      isMinimized: boolean;
      isHidden: boolean;
      isClickThrough: boolean;
      posLeft: string | null; // CSS left position
      posTop: string | null;  // CSS top position
      lastUpdated: number;    // Date.now() timestamp
    }

2. Daily Study Progress (studyTrackerData)

  • Key: studyTrackerData
  • Value:
    interface StudyTrackerData {
      dailyGoalHours: number; // e.g., 6.0
      streakDays: number;     // Active day streak
      lastActiveDate: string; // YYYY-MM-DD
      history: {
        [dateString: string]: { // YYYY-MM-DD keys
          realSec: number;     // Actual study time elapsed
          coverageSec: number; // Playback rate adjusted time covered
          questionSec: number; // Time spent solving questions
          platforms: {
            [platformName: string]: number; // Platform-specific second tracking
          };
        };
      };
    }

📋 Coding Guidelines & Standards

🛡️ Guard Against Extension Context Invalidation

When an extension is updated or reloaded in Developer Mode, existing tabs run into an "Extension context invalidated" error on subsequent Chrome API calls.

  • Rule: Wrap Chrome storage accesses in try/catch blocks and always check for context validity before calling storage functions. Use the helper:
    function isContextValid() {
      try {
        return typeof chrome !== 'undefined' && chrome.runtime && !!chrome.runtime.id;
      } catch (e) {
        return false;
      }
    }

⌨️ Respect Interactive Form Controls

Content scripts must not intercept keyboard hotkeys when a user is actively typing in a text field, input, search bar, or rich-text editor (including inside Shadow DOM trees).

  • Rule: Use a comprehensive isTyping(e) guard at the beginning of all document keydown/keyup event handlers:
    function isTyping(e) {
      const active = document.activeElement;
      const target = e?.target;
    
      if (e && typeof e.composedPath === 'function') {
        const path = e.composedPath();
        for (let i = 0; i < path.length; i++) {
          const el = path[i];
          if (el && el.nodeType === 1) {
            const tag = el.tagName;
            if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tag)) return true;
            if (el.isContentEditable || el.getAttribute('contenteditable') === 'true') return true;
            const role = el.getAttribute('role');
            if (['textbox', 'searchbox', 'combobox'].includes(role)) return true;
          }
        }
      }
    
      let cur = active;
      while (cur && cur.shadowRoot && cur.shadowRoot.activeElement) {
        cur = cur.shadowRoot.activeElement;
      }
      if (cur) {
        const tag = cur.tagName;
        if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tag)) return true;
        if (cur.isContentEditable || cur.getAttribute('contenteditable') === 'true') return true;
        const role = cur.getAttribute('role');
        if (['textbox', 'searchbox', 'combobox'].includes(role)) return true;
      }
    
      if (target && target.nodeType === 1) {
        if (['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName)) return true;
        if (target.isContentEditable || (target.closest && target.closest('[contenteditable="true"], input, textarea, select, [role="textbox"], [role="searchbox"]'))) {
          return true;
        }
      }
    
      return false;
    }

🧬 Prevent Namespace Pollution

Keep global scope clean. Every script must be encapsulated in an Immediately Invoked Function Expression (IIFE) with 'use strict'; enabled.

(function () {
  'use strict';
  // Module code here...
})();

🌿 Support Single Page Applications (SPA)

Many modern lecture platforms use React, Vue, or Next.js (client-side routing) which do not trigger full page load events when navigating.

  • Rule: Use a periodic interval or a MutationObserver to monitor DOM changes or URL changes to automatically re-bind event listeners or attach badges to dynamic <video> players. See remaining-time.js and question-timer.js for examples.

🚀 Performance & Styling

  • No External Dependencies: Write clean, vanilla JS. Avoid importing jQuery, Lodash, or large utility libraries.
  • Separation of Styles: Do not use inline CSS strings inside Javascript files. Write style declarations inside src/styles/hud.css or scoped stylesheets and apply them via class toggles.

🎯 Creating Site-Specific Plugins

To add optimizations or keyboard shortcuts for a specific platform (similar to pw-enhancements.js):

  1. Create the Script: Create a new file under src/plugins/your-plugin.js.
  2. Create Scoped CSS: If layout tweaks are required, add src/styles/your-plugin-custom.css.
  3. Register in manifest.json: Add a new match block under content_scripts:
    {
      "matches": ["*://*.yourplatform.com/*"],
      "css": ["src/styles/your-plugin-custom.css"],
      "js": ["src/plugins/your-plugin.js"]
    }

🚀 Testing & Debugging Workflow

🔍 Inspecting Logs

  • Content Scripts (HUD, Speed, Timer): Press F12 inside the lecture page and inspect the standard page Console.
  • Popup Dashboard: Right-click the extension icon in the toolbar, select Inspect popup, and view the developer console.

💾 Inspecting local storage

To view active storage data directly, you can run the following command in the Console of your page:

chrome.storage.local.get(null, data => console.log(data));

Thank you for contributing to make video learning faster and more productive for everyone! ❤️