Skip to content

Commit 470d7e5

Browse files
authored
Merge pull request #51 from lumpinif/main
ref: bump to v 3.1.3
2 parents a294e24 + 24ab428 commit 470d7e5

10 files changed

Lines changed: 235 additions & 27 deletions

File tree

extensions/chrome/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "thinking-claude",
3-
"version": "3.1.2",
3+
"version": "3.1.3",
44
"description": "Chrome extension for letting Claude think like a real human",
55
"type": "module",
66
"scripts": {

extensions/chrome/public/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"manifest_version": 3,
33
"name": "Thinking Claude",
4-
"version": "3.1.2",
4+
"version": "3.1.3",
55
"description": "Chrome extension for letting Claude think like a real human",
66
"content_scripts": [
77
{
Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import "@/styles/globals.css"
22

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

5-
import { addThinkingBlockToggle } from "./v3/features/thinking-block"
6-
7-
// Only initialize on appropriate pages
8-
if (shouldInitialize(window.location.href)) {
9-
if (document.readyState === "loading") {
10-
document.addEventListener("DOMContentLoaded", addThinkingBlockToggle)
11-
} else {
12-
addThinkingBlockToggle()
13-
}
14-
}
5+
const extensionManager = new ExtensionManager()
6+
extensionManager.initialize()
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Feature } from "@/types"
2+
3+
/**
4+
* Base abstract class for features
5+
* Provides common functionality and enforces feature contract
6+
*/
7+
export abstract class BaseFeature implements Feature {
8+
constructor(readonly id: string) {}
9+
10+
/**
11+
* Initialize the feature
12+
* @returns cleanup function if needed
13+
*/
14+
abstract initialize(): void | (() => void)
15+
16+
/**
17+
* Helper method to safely add event listeners with automatic cleanup
18+
*/
19+
protected addEventListenerWithCleanup(
20+
element: Element,
21+
event: string,
22+
handler: EventListener,
23+
options?: boolean | AddEventListenerOptions
24+
): () => void {
25+
element.addEventListener(event, handler, options)
26+
return () => element.removeEventListener(event, handler, options)
27+
}
28+
}
Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,33 @@
1-
export { addThinkingBlockToggle } from "./thinking-block-toggle"
1+
import type { MutationObserverService } from "@/services/mutation-observer"
2+
3+
import { BaseFeature } from "../base-feature"
4+
import { processThinkingBlocks } from "./process-thinking-block"
5+
6+
/**
7+
* Feature that adds toggle functionality to thinking blocks in the UI
8+
* Manages the collapse/expand and copy functionality for code blocks
9+
*/
10+
export class TCThinkingBlock extends BaseFeature {
11+
/**
12+
* @param mutationObserver - Service to observe DOM changes for thinking blocks
13+
*/
14+
constructor(private mutationObserver: MutationObserverService) {
15+
super("tc-thinking-block")
16+
}
17+
18+
/**
19+
* Initialize the thinking block feature
20+
* Sets up mutation observer to watch for new thinking blocks
21+
* @returns Cleanup function to unsubscribe from mutation observer
22+
*/
23+
initialize(): void | (() => void) {
24+
this.mutationObserver.initialize()
25+
26+
const unsubscribe = this.mutationObserver.subscribe(processThinkingBlocks)
27+
28+
return () => {
29+
// Unsubscribe from mutation observer
30+
unsubscribe()
31+
}
32+
}
33+
}

extensions/chrome/src/content/v3/features/thinking-block/thinking-block-toggle.ts renamed to extensions/chrome/src/content/v3/features/thinking-block/process-thinking-block.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
import { THINKING_BLOCK_CONTROLS_SELECTORS } from "@/selectors"
2-
import { mutationObserver } from "@/services/mutation-observer"
32

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

6-
export function addThinkingBlockToggle() {
7-
mutationObserver.initialize()
8-
return mutationObserver.subscribe(processThinkingBlocks)
9-
}
10-
11-
function processThinkingBlocks() {
5+
export function processThinkingBlocks() {
126
const thinkingBlockControls = document.querySelectorAll(
137
THINKING_BLOCK_CONTROLS_SELECTORS
148
)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { MutationObserverService } from "@/services/mutation-observer"
2+
import { shouldInitialize } from "@/utils/url-utils"
3+
4+
import { TCThinkingBlock } from "../features/thinking-block"
5+
import { FeatureManager } from "./feature-manager"
6+
7+
/**
8+
* Manages the lifecycle and coordination of all extension features and services
9+
*/
10+
export class ExtensionManager {
11+
private featureManager: FeatureManager
12+
private mutationObserver: MutationObserverService
13+
14+
constructor() {
15+
this.mutationObserver = new MutationObserverService()
16+
this.featureManager = new FeatureManager()
17+
18+
this.registerFeatures()
19+
this.setupNavigationListener()
20+
}
21+
22+
/**
23+
* Register all extension features
24+
*/
25+
private registerFeatures(): void {
26+
// Register features with their required services
27+
this.featureManager.register(new TCThinkingBlock(this.mutationObserver))
28+
// Add more features here
29+
}
30+
31+
/**
32+
* Initialize the extension if conditions are met
33+
*/
34+
initialize(): void {
35+
if (!shouldInitialize(window.location.href)) {
36+
return
37+
}
38+
39+
if (document.readyState === "loading") {
40+
document.addEventListener("DOMContentLoaded", () => {
41+
this.featureManager.initialize()
42+
})
43+
} else {
44+
this.featureManager.initialize()
45+
}
46+
}
47+
48+
/**
49+
* Set up listener for navigation events
50+
*/
51+
private setupNavigationListener(): void {
52+
chrome.runtime.onMessage.addListener((message) => {
53+
if (message.type === "NAVIGATION") {
54+
this.featureManager.cleanup()
55+
}
56+
})
57+
}
58+
59+
/**
60+
* Clean up all features and services
61+
*/
62+
cleanup(): void {
63+
this.featureManager.cleanup()
64+
}
65+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Feature } from "@/types"
2+
3+
export class FeatureManager {
4+
private features = new Map<string, Feature>()
5+
private cleanupFunctions = new Map<string, () => void>()
6+
7+
/**
8+
* Register a new feature
9+
* @param feature Feature instance to register
10+
* @throws Error if feature with same id already exists
11+
*/
12+
register(feature: Feature): void {
13+
if (this.features.has(feature.id)) {
14+
throw new Error(`Feature with id ${feature.id} already exists`)
15+
}
16+
this.features.set(feature.id, feature)
17+
}
18+
19+
/**
20+
* Initialize all registered features
21+
*/
22+
initialize(): void {
23+
this.features.forEach((feature, id) => {
24+
try {
25+
const cleanup = feature.initialize()
26+
if (cleanup) {
27+
this.cleanupFunctions.set(id, cleanup)
28+
}
29+
} catch (error) {
30+
console.error(`Failed to initialize feature ${id}:`, error)
31+
}
32+
})
33+
}
34+
35+
/**
36+
* Clean up all features
37+
*/
38+
cleanup(): void {
39+
this.cleanupFunctions.forEach((cleanup, id) => {
40+
try {
41+
cleanup()
42+
} catch (error) {
43+
console.error(`Failed to cleanup feature ${id}:`, error)
44+
}
45+
})
46+
this.cleanupFunctions.clear()
47+
this.features.clear()
48+
}
49+
50+
/**
51+
* Get a registered feature by id
52+
*/
53+
getFeature(id: string): Feature | undefined {
54+
return this.features.get(id)
55+
}
56+
}

extensions/chrome/src/services/mutation-observer.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
type ObserverCallback = () => void
22

3-
class MutationObserverService {
3+
export interface MutationObserverOptions {
4+
childList?: boolean
5+
subtree?: boolean
6+
attributes?: boolean
7+
characterData?: boolean
8+
debounceTime?: number
9+
}
10+
11+
export class MutationObserverService {
412
private observer: MutationObserver | null = null
513
private callbacks: Set<ObserverCallback> = new Set()
614
private timeouts: Map<ObserverCallback, NodeJS.Timeout> = new Map()
715
private isProcessing = false
16+
private options: MutationObserverOptions
17+
18+
constructor(
19+
options: MutationObserverOptions = {
20+
childList: true,
21+
subtree: true,
22+
debounceTime: 200,
23+
}
24+
) {
25+
this.options = options
26+
}
827

928
initialize() {
1029
if (this.observer) return
@@ -22,18 +41,35 @@ class MutationObserverService {
2241
const timeout = setTimeout(() => {
2342
callback()
2443
this.isProcessing = false
25-
}, 200) // Slightly increased debounce time
44+
}, this.options.debounceTime)
2645

2746
this.timeouts.set(callback, timeout)
2847
})
2948
})
3049

3150
this.observer.observe(document.body, {
32-
childList: true,
33-
subtree: true,
51+
childList: this.options.childList,
52+
subtree: this.options.subtree,
53+
attributes: this.options.attributes,
54+
characterData: this.options.characterData,
3455
})
3556
}
3657

58+
/* service-level cleanup but we don't usually need this */
59+
cleanup() {
60+
// 1. Disconnect the MutationObserver
61+
this.observer?.disconnect()
62+
// 2. Clear the observer reference
63+
this.observer = null
64+
// 3. Clear all pending timeouts
65+
this.timeouts.forEach((timeout) => clearTimeout(timeout))
66+
this.timeouts.clear()
67+
// 4. Clear all callbacks
68+
this.callbacks.clear()
69+
// 5. Reset processing flag
70+
this.isProcessing = false
71+
}
72+
3773
subscribe(callback: ObserverCallback) {
3874
this.callbacks.add(callback)
3975
return () => this.unsubscribe(callback)
@@ -48,5 +84,3 @@ class MutationObserverService {
4884
}
4985
}
5086
}
51-
52-
export const mutationObserver = new MutationObserverService()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* Base interface for all features
3+
*/
4+
export interface Feature {
5+
id: string
6+
initialize(): void | (() => void) // Return cleanup function if needed
7+
}

0 commit comments

Comments
 (0)