Skip to content

Commit

Permalink
Merge pull request #51 from lumpinif/main
Browse files Browse the repository at this point in the history
ref: bump to v 3.1.3
  • Loading branch information
lumpinif authored Nov 28, 2024
2 parents a294e24 + 24ab428 commit 470d7e5
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 27 deletions.
2 changes: 1 addition & 1 deletion extensions/chrome/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "thinking-claude",
"version": "3.1.2",
"version": "3.1.3",
"description": "Chrome extension for letting Claude think like a real human",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion extensions/chrome/public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Thinking Claude",
"version": "3.1.2",
"version": "3.1.3",
"description": "Chrome extension for letting Claude think like a real human",
"content_scripts": [
{
Expand Down
14 changes: 3 additions & 11 deletions extensions/chrome/src/content/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import "@/styles/globals.css"

import { shouldInitialize } from "@/utils/url-utils"
import { ExtensionManager } from "./v3/managers/extension-manager"

import { addThinkingBlockToggle } from "./v3/features/thinking-block"

// Only initialize on appropriate pages
if (shouldInitialize(window.location.href)) {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", addThinkingBlockToggle)
} else {
addThinkingBlockToggle()
}
}
const extensionManager = new ExtensionManager()
extensionManager.initialize()
28 changes: 28 additions & 0 deletions extensions/chrome/src/content/v3/features/base-feature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Feature } from "@/types"

/**
* Base abstract class for features
* Provides common functionality and enforces feature contract
*/
export abstract class BaseFeature implements Feature {
constructor(readonly id: string) {}

/**
* Initialize the feature
* @returns cleanup function if needed
*/
abstract initialize(): void | (() => void)

/**
* Helper method to safely add event listeners with automatic cleanup
*/
protected addEventListenerWithCleanup(
element: Element,
event: string,
handler: EventListener,
options?: boolean | AddEventListenerOptions
): () => void {
element.addEventListener(event, handler, options)
return () => element.removeEventListener(event, handler, options)
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,33 @@
export { addThinkingBlockToggle } from "./thinking-block-toggle"
import type { MutationObserverService } from "@/services/mutation-observer"

import { BaseFeature } from "../base-feature"
import { processThinkingBlocks } from "./process-thinking-block"

/**
* Feature that adds toggle functionality to thinking blocks in the UI
* Manages the collapse/expand and copy functionality for code blocks
*/
export class TCThinkingBlock extends BaseFeature {
/**
* @param mutationObserver - Service to observe DOM changes for thinking blocks
*/
constructor(private mutationObserver: MutationObserverService) {
super("tc-thinking-block")
}

/**
* Initialize the thinking block feature
* Sets up mutation observer to watch for new thinking blocks
* @returns Cleanup function to unsubscribe from mutation observer
*/
initialize(): void | (() => void) {
this.mutationObserver.initialize()

const unsubscribe = this.mutationObserver.subscribe(processThinkingBlocks)

return () => {
// Unsubscribe from mutation observer
unsubscribe()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { THINKING_BLOCK_CONTROLS_SELECTORS } from "@/selectors"
import { mutationObserver } from "@/services/mutation-observer"

import { setupControls } from "./setup-controls"

export function addThinkingBlockToggle() {
mutationObserver.initialize()
return mutationObserver.subscribe(processThinkingBlocks)
}

function processThinkingBlocks() {
export function processThinkingBlocks() {
const thinkingBlockControls = document.querySelectorAll(
THINKING_BLOCK_CONTROLS_SELECTORS
)
Expand Down
65 changes: 65 additions & 0 deletions extensions/chrome/src/content/v3/managers/extension-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { MutationObserverService } from "@/services/mutation-observer"
import { shouldInitialize } from "@/utils/url-utils"

import { TCThinkingBlock } from "../features/thinking-block"
import { FeatureManager } from "./feature-manager"

/**
* Manages the lifecycle and coordination of all extension features and services
*/
export class ExtensionManager {
private featureManager: FeatureManager
private mutationObserver: MutationObserverService

constructor() {
this.mutationObserver = new MutationObserverService()
this.featureManager = new FeatureManager()

this.registerFeatures()
this.setupNavigationListener()
}

/**
* Register all extension features
*/
private registerFeatures(): void {
// Register features with their required services
this.featureManager.register(new TCThinkingBlock(this.mutationObserver))
// Add more features here
}

/**
* Initialize the extension if conditions are met
*/
initialize(): void {
if (!shouldInitialize(window.location.href)) {
return
}

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
this.featureManager.initialize()
})
} else {
this.featureManager.initialize()
}
}

/**
* Set up listener for navigation events
*/
private setupNavigationListener(): void {
chrome.runtime.onMessage.addListener((message) => {
if (message.type === "NAVIGATION") {
this.featureManager.cleanup()
}
})
}

/**
* Clean up all features and services
*/
cleanup(): void {
this.featureManager.cleanup()
}
}
56 changes: 56 additions & 0 deletions extensions/chrome/src/content/v3/managers/feature-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Feature } from "@/types"

export class FeatureManager {
private features = new Map<string, Feature>()
private cleanupFunctions = new Map<string, () => void>()

/**
* Register a new feature
* @param feature Feature instance to register
* @throws Error if feature with same id already exists
*/
register(feature: Feature): void {
if (this.features.has(feature.id)) {
throw new Error(`Feature with id ${feature.id} already exists`)
}
this.features.set(feature.id, feature)
}

/**
* Initialize all registered features
*/
initialize(): void {
this.features.forEach((feature, id) => {
try {
const cleanup = feature.initialize()
if (cleanup) {
this.cleanupFunctions.set(id, cleanup)
}
} catch (error) {
console.error(`Failed to initialize feature ${id}:`, error)
}
})
}

/**
* Clean up all features
*/
cleanup(): void {
this.cleanupFunctions.forEach((cleanup, id) => {
try {
cleanup()
} catch (error) {
console.error(`Failed to cleanup feature ${id}:`, error)
}
})
this.cleanupFunctions.clear()
this.features.clear()
}

/**
* Get a registered feature by id
*/
getFeature(id: string): Feature | undefined {
return this.features.get(id)
}
}
46 changes: 40 additions & 6 deletions extensions/chrome/src/services/mutation-observer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
type ObserverCallback = () => void

class MutationObserverService {
export interface MutationObserverOptions {
childList?: boolean
subtree?: boolean
attributes?: boolean
characterData?: boolean
debounceTime?: number
}

export class MutationObserverService {
private observer: MutationObserver | null = null
private callbacks: Set<ObserverCallback> = new Set()
private timeouts: Map<ObserverCallback, NodeJS.Timeout> = new Map()
private isProcessing = false
private options: MutationObserverOptions

constructor(
options: MutationObserverOptions = {
childList: true,
subtree: true,
debounceTime: 200,
}
) {
this.options = options
}

initialize() {
if (this.observer) return
Expand All @@ -22,18 +41,35 @@ class MutationObserverService {
const timeout = setTimeout(() => {
callback()
this.isProcessing = false
}, 200) // Slightly increased debounce time
}, this.options.debounceTime)

this.timeouts.set(callback, timeout)
})
})

this.observer.observe(document.body, {
childList: true,
subtree: true,
childList: this.options.childList,
subtree: this.options.subtree,
attributes: this.options.attributes,
characterData: this.options.characterData,
})
}

/* service-level cleanup but we don't usually need this */
cleanup() {
// 1. Disconnect the MutationObserver
this.observer?.disconnect()
// 2. Clear the observer reference
this.observer = null
// 3. Clear all pending timeouts
this.timeouts.forEach((timeout) => clearTimeout(timeout))
this.timeouts.clear()
// 4. Clear all callbacks
this.callbacks.clear()
// 5. Reset processing flag
this.isProcessing = false
}

subscribe(callback: ObserverCallback) {
this.callbacks.add(callback)
return () => this.unsubscribe(callback)
Expand All @@ -48,5 +84,3 @@ class MutationObserverService {
}
}
}

export const mutationObserver = new MutationObserverService()
7 changes: 7 additions & 0 deletions extensions/chrome/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Base interface for all features
*/
export interface Feature {
id: string
initialize(): void | (() => void) // Return cleanup function if needed
}

0 comments on commit 470d7e5

Please sign in to comment.