From 32608cca619be6105b921a800583fd57ca343073 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Fri, 22 Nov 2024 23:49:12 +0800 Subject: [PATCH 01/26] init: comprehensive extension rewrite Complete rewrite of the Thinking Claude extension with modern tooling: - Archive previous version as chrome_v0 - Migrate to TypeScript and React architecture - Implement robust development workflow with ESLint, Prettier, and Husky - Add webpack for optimized builds - Set up Vitest testing framework - Integrate TailwindCSS for modern styling - Version bump to 1.0.0 to reflect major rewrite --- extensions/changelog.md | 22 ++ extensions/chrome_v0/.vscode/extensions.json | 3 + extensions/chrome_v0/content.js | 362 ++++++++++++++++++ extensions/chrome_v0/manifest.json | 12 + extensions/chrome_v1/.gitignore | 27 ++ extensions/chrome_v1/.husky/post-merge | 4 + extensions/chrome_v1/.husky/pre-commit | 7 + extensions/chrome_v1/.markdownlint-cli2.jsonc | 11 + extensions/chrome_v1/.markdownlintignore | 4 + extensions/chrome_v1/components.json | 20 + extensions/chrome_v1/eslint.config.cjs | 73 ++++ extensions/chrome_v1/package.json | 69 ++++ extensions/chrome_v1/postcss.config.mjs | 6 + extensions/chrome_v1/prettier.config.cjs | 33 ++ .../chrome_v1/public/icons/claude-ai-128.png | Bin 0 -> 19174 bytes .../chrome_v1/public/icons/claude-ai-16.png | Bin 0 -> 636 bytes .../chrome_v1/public/icons/claude-ai-48.png | Bin 0 -> 3247 bytes .../public/icons/claude_app_icon.png | Bin 0 -> 14038 bytes .../chrome_v1/src/components/ui/button.tsx | 0 .../src/content/__tests__/sample.test.ts | 70 ++++ extensions/chrome_v1/src/content/index.tsx | 0 extensions/chrome_v1/src/lib/utils.ts | 6 + extensions/chrome_v1/src/manifest.json | 26 ++ extensions/chrome_v1/src/styles/globals.css | 90 +++++ extensions/chrome_v1/src/types/css.d.ts | 28 ++ extensions/chrome_v1/tailwind.config.cjs | 50 +++ extensions/chrome_v1/tsconfig.json | 27 ++ extensions/chrome_v1/vitest.config.ts | 10 + .../chrome_v1/webpack/webpack.common.js | 45 +++ extensions/chrome_v1/webpack/webpack.dev.js | 12 + extensions/chrome_v1/webpack/webpack.prod.js | 8 + extensions/firefox/content.js | 362 ++++++++++++++++++ extensions/firefox/manifest.json | 19 + 33 files changed, 1406 insertions(+) create mode 100644 extensions/changelog.md create mode 100644 extensions/chrome_v0/.vscode/extensions.json create mode 100644 extensions/chrome_v0/content.js create mode 100644 extensions/chrome_v0/manifest.json create mode 100644 extensions/chrome_v1/.gitignore create mode 100644 extensions/chrome_v1/.husky/post-merge create mode 100644 extensions/chrome_v1/.husky/pre-commit create mode 100644 extensions/chrome_v1/.markdownlint-cli2.jsonc create mode 100644 extensions/chrome_v1/.markdownlintignore create mode 100644 extensions/chrome_v1/components.json create mode 100644 extensions/chrome_v1/eslint.config.cjs create mode 100644 extensions/chrome_v1/package.json create mode 100644 extensions/chrome_v1/postcss.config.mjs create mode 100644 extensions/chrome_v1/prettier.config.cjs create mode 100644 extensions/chrome_v1/public/icons/claude-ai-128.png create mode 100644 extensions/chrome_v1/public/icons/claude-ai-16.png create mode 100644 extensions/chrome_v1/public/icons/claude-ai-48.png create mode 100644 extensions/chrome_v1/public/icons/claude_app_icon.png create mode 100644 extensions/chrome_v1/src/components/ui/button.tsx create mode 100644 extensions/chrome_v1/src/content/__tests__/sample.test.ts create mode 100644 extensions/chrome_v1/src/content/index.tsx create mode 100644 extensions/chrome_v1/src/lib/utils.ts create mode 100644 extensions/chrome_v1/src/manifest.json create mode 100644 extensions/chrome_v1/src/styles/globals.css create mode 100644 extensions/chrome_v1/src/types/css.d.ts create mode 100644 extensions/chrome_v1/tailwind.config.cjs create mode 100644 extensions/chrome_v1/tsconfig.json create mode 100644 extensions/chrome_v1/vitest.config.ts create mode 100644 extensions/chrome_v1/webpack/webpack.common.js create mode 100644 extensions/chrome_v1/webpack/webpack.dev.js create mode 100644 extensions/chrome_v1/webpack/webpack.prod.js create mode 100644 extensions/firefox/content.js create mode 100644 extensions/firefox/manifest.json diff --git a/extensions/changelog.md b/extensions/changelog.md new file mode 100644 index 0000000..323b129 --- /dev/null +++ b/extensions/changelog.md @@ -0,0 +1,22 @@ +## Changelog of the extensions + +### fix: - 11/17/2024 - @lumpinif + +#### Observer Management and Memory Leak Prevention + +- Added observer tracking using Set to manage all MutationObservers +- Added cleanup on element removal to prevent dangling observers +- Added global cleanup on window unload +- Added observer cleanup when observed elements are removed from DOM + +#### Code Quality + +- Fixed code formatting and linting issues flagged by Biome + +#### Development Setup + +- Added .vscode settings with Biome extension recommendation + +#### Platform Updates + +- Updated code in both Chrome and Firefox extensions diff --git a/extensions/chrome_v0/.vscode/extensions.json b/extensions/chrome_v0/.vscode/extensions.json new file mode 100644 index 0000000..699ed73 --- /dev/null +++ b/extensions/chrome_v0/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["biomejs.biome"] +} diff --git a/extensions/chrome_v0/content.js b/extensions/chrome_v0/content.js new file mode 100644 index 0000000..11fd47c --- /dev/null +++ b/extensions/chrome_v0/content.js @@ -0,0 +1,362 @@ +class CodeBlockCollapser { + static SELECTORS = { + PRE: "pre", + CODE_CONTAINER: ".code-block__code", + MAIN_CONTAINER: ".relative.flex.flex-col", + THINKING_LABEL: ".text-text-300", + ORIGINAL_COPY_BTN: ".pointer-events-none", + CODE: "code", + }; + + static CLASSES = { + THINKING_HEADER: "thinking-header", + COPY_CONTAINER: + "from-bg-300/90 to-bg-300/70 pointer-events-auto rounded-md bg-gradient-to-b p-0.5 backdrop-blur-md", + COPY_BUTTON: + "flex flex-row items-center gap-1 rounded-md p-1 py-0.5 text-xs transition-opacity delay-100 hover:bg-bg-200 opacity-60 hover:opacity-100", + COPY_TEXT: "text-text-200 pr-0.5", + TOGGLE_BUTTON: "flex items-center text-text-500 hover:text-text-300", + TOGGLE_LABEL: "font-medium text-sm", + THINKING_ANIMATION: "thinking-animation", + }; + + static ANIMATION_STYLES = ` + @keyframes gradientWave { + 0% { background-position: 200% 50%; } + 100% { background-position: -200% 50%; } + } + + .thinking-animation { + background: linear-gradient( + 90deg, + rgba(156, 163, 175, 0.7) 0%, + rgba(209, 213, 219, 1) 25%, + rgba(156, 163, 175, 0.7) 50%, + rgba(209, 213, 219, 1) 75%, + rgba(156, 163, 175, 0.7) 100% + ); + background-size: 200% 100%; + animation: gradientWave 3s linear infinite; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + color: transparent; + } + `; + + static ICONS = { + COPY: ``, + TICK: ``, + ARROW: ``, + }; + + static TIMINGS = { + RETRY_DELAY: 1000, + MUTATION_DELAY: 100, + CHECK_INTERVAL: 2000, + COPY_FEEDBACK: 2000, + MAX_RETRIES: 10, + }; + + constructor() { + this.observers = new Set(); + this.injectStyles(); + this.initWithRetry(); + + window.addEventListener("unload", () => this.cleanup()); + } + + injectStyles() { + if (!document.getElementById("thinking-animation-styles")) { + const styleSheet = document.createElement("style"); + styleSheet.id = "thinking-animation-styles"; + styleSheet.textContent = CodeBlockCollapser.ANIMATION_STYLES; + document.head.appendChild(styleSheet); + } + } + + createElement(tag, className = "", innerHTML = "") { + const element = document.createElement(tag); + if (className) element.className = className; + if (innerHTML) element.innerHTML = innerHTML; + return element; + } + + createCopyButton() { + const container = this.createElement( + "div", + CodeBlockCollapser.CLASSES.COPY_CONTAINER + ); + const button = this.createElement( + "button", + CodeBlockCollapser.CLASSES.COPY_BUTTON + ); + const iconSpan = this.createElement( + "span", + "", + CodeBlockCollapser.ICONS.COPY + ); + const textSpan = this.createElement( + "span", + CodeBlockCollapser.CLASSES.COPY_TEXT, + "Copy" + ); + + button.append(iconSpan, textSpan); + container.appendChild(button); + + button.addEventListener("click", () => { + const codeText = button + .closest(CodeBlockCollapser.SELECTORS.PRE) + ?.querySelector(CodeBlockCollapser.SELECTORS.CODE)?.textContent; + + if (!codeText) return; + + navigator.clipboard + .writeText(codeText) + .then(() => { + iconSpan.innerHTML = CodeBlockCollapser.ICONS.TICK; + textSpan.textContent = "Copied!"; + + setTimeout(() => { + iconSpan.innerHTML = CodeBlockCollapser.ICONS.COPY; + textSpan.textContent = "Copy"; + }, CodeBlockCollapser.TIMINGS.COPY_FEEDBACK); + }) + .catch((error) => { + console.error("Failed to copy:", error); + }); + }); + + return container; + } + + createToggleButton(isStreaming = false) { + const button = this.createElement( + "button", + CodeBlockCollapser.CLASSES.TOGGLE_BUTTON + ); + const labelText = isStreaming ? "Thinking..." : "View thinking process"; + button.innerHTML = ` + ${CodeBlockCollapser.ICONS.ARROW} + ${labelText} + `; + return button; + } + + updateHeaderState(headerContainer, isStreaming) { + const toggleBtn = headerContainer.querySelector( + `.${CodeBlockCollapser.CLASSES.TOGGLE_BUTTON}` + ); + const label = toggleBtn.querySelector("span"); + + label.textContent = isStreaming ? "Thinking..." : "View thinking process"; + + if (isStreaming) { + label.classList.add(CodeBlockCollapser.CLASSES.THINKING_ANIMATION); + } else { + label.classList.remove(CodeBlockCollapser.CLASSES.THINKING_ANIMATION); + } + } + + setupCodeContainer(container, toggleBtn) { + if (!container) return; + + container.style.cssText = ` + transition: all 0.3s ease-in-out; + overflow-x: hidden; + overflow-y: auto; + max-height: 0; + opacity: 0; + padding: 0; + max-width: 100%; + display: block; + `; + + const codeElement = container.querySelector( + CodeBlockCollapser.SELECTORS.CODE + ); + if (codeElement) { + codeElement.style.cssText = ` + white-space: pre-wrap !important; + word-break: break-word !important; + overflow-wrap: break-word !important; + display: block !important; + max-width: 100% !important; + `; + } + + toggleBtn.addEventListener("click", () => { + const shouldToggleOpen = container.style.maxHeight === "0px"; + const arrow = toggleBtn.querySelector("svg"); + const label = toggleBtn.querySelector("span"); + + container.style.maxHeight = shouldToggleOpen ? "50vh" : "0"; + container.style.opacity = shouldToggleOpen ? "1" : "0"; + container.style.padding = shouldToggleOpen ? "1em" : "0"; + + arrow.style.transform = `rotate(${shouldToggleOpen ? 180 : 0}deg)`; + if ( + !label.classList.contains(CodeBlockCollapser.CLASSES.THINKING_ANIMATION) + ) { + label.textContent = shouldToggleOpen + ? "Hide thinking process" + : "View thinking process"; + } + }); + } + + processBlock(pre) { + const headerContainer = this.createElement( + "div", + CodeBlockCollapser.CLASSES.THINKING_HEADER + ); + headerContainer.style.cssText = + "display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: var(--bg-300);"; + + const isStreaming = pre.closest('[data-is-streaming="true"]') !== null; + const toggleBtn = this.createToggleButton(isStreaming); + const copyBtn = this.createCopyButton(); + + headerContainer.append(toggleBtn, copyBtn); + + const codeContainer = pre.querySelector( + CodeBlockCollapser.SELECTORS.CODE_CONTAINER + ); + this.setupCodeContainer(codeContainer, toggleBtn); + + const mainContainer = pre.querySelector( + CodeBlockCollapser.SELECTORS.MAIN_CONTAINER + ); + if (mainContainer) { + const codeParent = pre.querySelector( + CodeBlockCollapser.SELECTORS.CODE_CONTAINER + )?.parentElement; + if (codeParent) { + mainContainer.insertBefore(headerContainer, codeParent); + } + + const streamingContainer = pre.closest("[data-is-streaming]"); + if (streamingContainer) { + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if ( + mutation.type === "attributes" && + mutation.attributeName === "data-is-streaming" + ) { + const isStreamingNow = + streamingContainer.getAttribute("data-is-streaming") === "true"; + this.updateHeaderState(headerContainer, isStreamingNow); + } + } + }); + + observer.observe(streamingContainer, { + attributes: true, + attributeFilter: ["data-is-streaming"], + }); + + this.observers.add(observer); + + new MutationObserver((mutations) => { + if (!document.contains(streamingContainer)) { + observer.disconnect(); + this.observers.delete(observer); + } + }).observe(document.body, { childList: true, subtree: true }); + } + + for (const selector of [ + CodeBlockCollapser.SELECTORS.THINKING_LABEL, + CodeBlockCollapser.SELECTORS.ORIGINAL_COPY_BTN, + ]) { + const element = pre.querySelector(selector); + if (element) element.style.display = "none"; + } + } + } + + initWithRetry(retryCount = 0) { + if (retryCount >= CodeBlockCollapser.TIMINGS.MAX_RETRIES) return; + + const blocks = document.querySelectorAll(CodeBlockCollapser.SELECTORS.PRE); + if (blocks.length === 0) { + setTimeout( + () => this.initWithRetry(retryCount + 1), + CodeBlockCollapser.TIMINGS.RETRY_DELAY + ); + return; + } + + this.processExistingBlocks(); + this.setupObserver(); + this.setupPeriodicCheck(); + } + + setupObserver() { + const observer = new MutationObserver((mutations) => { + let shouldProcess = false; + for (const mutation of mutations) { + if ( + mutation.addedNodes.length > 0 || + (mutation.type === "attributes" && + mutation.attributeName === "data-is-streaming") + ) { + shouldProcess = true; + } + } + + if (shouldProcess) { + this.processExistingBlocks(); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ["data-is-streaming"], + }); + + this.observers.add(observer); + } + + setupPeriodicCheck() { + setInterval(() => { + this.processExistingBlocks(); + }, CodeBlockCollapser.TIMINGS.CHECK_INTERVAL); + } + + processExistingBlocks() { + for (const pre of document.querySelectorAll( + CodeBlockCollapser.SELECTORS.PRE + )) { + const header = pre.querySelector( + CodeBlockCollapser.SELECTORS.THINKING_LABEL + ); + if ( + header?.textContent.trim() === "thinking" && + !pre.querySelector(`.${CodeBlockCollapser.CLASSES.THINKING_HEADER}`) + ) { + this.processBlock(pre); + } + } + } + + cleanup() { + for (const observer of this.observers) { + observer.disconnect(); + } + this.observers.clear(); + } +} + +new CodeBlockCollapser(); + +document.addEventListener("DOMContentLoaded", () => { + if (!window.codeBlockCollapser) { + window.codeBlockCollapser = new CodeBlockCollapser(); + } +}); diff --git a/extensions/chrome_v0/manifest.json b/extensions/chrome_v0/manifest.json new file mode 100644 index 0000000..38c77a7 --- /dev/null +++ b/extensions/chrome_v0/manifest.json @@ -0,0 +1,12 @@ +{ + "manifest_version": 3, + "name": "Thinking Claude", + "version": "0.2.1", + "description": "Let Claude think. Makes Claude's thinking process expandable and collapsible.", + "content_scripts": [ + { + "matches": ["https://*.claude.ai/*"], + "js": ["content.js"] + } + ] +} diff --git a/extensions/chrome_v1/.gitignore b/extensions/chrome_v1/.gitignore new file mode 100644 index 0000000..250ee83 --- /dev/null +++ b/extensions/chrome_v1/.gitignore @@ -0,0 +1,27 @@ +# Dependencies +node_modules/ + +# Package manager files +bun.lockb + +# Build output +dist/ +build/ + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store + +# Environment variables +.env +.env.local +.env.*.local + +# Debug logs +debug.log + +# Test coverage +coverage/ diff --git a/extensions/chrome_v1/.husky/post-merge b/extensions/chrome_v1/.husky/post-merge new file mode 100644 index 0000000..4ff4400 --- /dev/null +++ b/extensions/chrome_v1/.husky/post-merge @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +git fetch diff --git a/extensions/chrome_v1/.husky/pre-commit b/extensions/chrome_v1/.husky/pre-commit new file mode 100644 index 0000000..28248d5 --- /dev/null +++ b/extensions/chrome_v1/.husky/pre-commit @@ -0,0 +1,7 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +cd "$(dirname "$0")/.." + +bun run fix +bun run lint-staged \ No newline at end of file diff --git a/extensions/chrome_v1/.markdownlint-cli2.jsonc b/extensions/chrome_v1/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..ed6ed29 --- /dev/null +++ b/extensions/chrome_v1/.markdownlint-cli2.jsonc @@ -0,0 +1,11 @@ +{ + "config": { + "default": true, + "MD013": false, + "MD033": false, + "MD041": false, + "MD032": true + }, + "ignores": ["node_modules/**", "dist/**", ".git/**"], + "globs": ["**/*.md"] +} diff --git a/extensions/chrome_v1/.markdownlintignore b/extensions/chrome_v1/.markdownlintignore new file mode 100644 index 0000000..f1d6b92 --- /dev/null +++ b/extensions/chrome_v1/.markdownlintignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.log +.git/ diff --git a/extensions/chrome_v1/components.json b/extensions/chrome_v1/components.json new file mode 100644 index 0000000..23aac84 --- /dev/null +++ b/extensions/chrome_v1/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/styles/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/extensions/chrome_v1/eslint.config.cjs b/extensions/chrome_v1/eslint.config.cjs new file mode 100644 index 0000000..ce851b8 --- /dev/null +++ b/extensions/chrome_v1/eslint.config.cjs @@ -0,0 +1,73 @@ +/** @type {import('eslint').Config[]} */ +module.exports = [ + require('@eslint/js').configs.recommended, + { + files: ['**/*.{ts,tsx,js,jsx}'], + languageOptions: { + ecmaVersion: 2021, + sourceType: 'module', + parser: require('@typescript-eslint/parser'), + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + chrome: 'readonly', + console: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': require('@typescript-eslint/eslint-plugin'), + react: require('eslint-plugin-react'), + 'react-hooks': require('eslint-plugin-react-hooks'), + }, + rules: { + ...require('@typescript-eslint/eslint-plugin').configs.recommended.rules, + ...require('eslint-plugin-react').configs.recommended.rules, + ...require('eslint-plugin-react-hooks').configs.recommended.rules, + 'react/react-in-jsx-scope': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + 'no-console': ['warn', { allow: ['warn', 'error'] }], + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + }, + settings: { + react: { + version: 'detect', + }, + }, + }, + // Config for test files + { + files: ['**/__tests__/**/*', '**/*.test.*'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + 'no-console': 'off', + }, + }, + // Config for configuration files + { + files: ['*.config.js', '*.config.cjs', 'webpack/**/*.js'], + languageOptions: { + globals: { + module: 'readonly', + require: 'readonly', + __dirname: 'readonly', + }, + }, + rules: { + '@typescript-eslint/no-require-imports': 'off', + 'no-undef': 'off', + }, + }, + require('eslint-config-prettier'), +]; diff --git a/extensions/chrome_v1/package.json b/extensions/chrome_v1/package.json new file mode 100644 index 0000000..6b0e400 --- /dev/null +++ b/extensions/chrome_v1/package.json @@ -0,0 +1,69 @@ +{ + "name": "thinking-claude", + "version": "1.0.0", + "description": "Chrome extension for letting Claude think like a real human", + "type": "module", + "scripts": { + "watch": "webpack --config webpack/webpack.dev.js --watch", + "build": "webpack --config webpack/webpack.prod.js", + "start": "webpack serve --config webpack/webpack.dev.js", + "test": "bun test", + "test:watch": "bun test --watch", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "lint:staged": "lint-staged", + "lint:types": "tsc --noEmit", + "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,css,md}\"", + "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,css,md}\"", + "fix": "bun run format && bun run lint:fix", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "clsx": "^2.1.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "tailwind-merge": "^1.14.0" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@ianvs/prettier-plugin-sort-imports": "^3.7.2", + "@types/chrome": "^0.0.246", + "@types/node": "^20.8.2", + "@types/react": "^18.2.24", + "@types/react-dom": "^18.2.8", + "@typescript-eslint/eslint-plugin": "^8.15.0", + "@typescript-eslint/parser": "^8.15.0", + "@vitest/runner": "^2.1.5", + "autoprefixer": "^10.4.16", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.8.1", + "eslint": "^9.15.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", + "husky": "^9.1.7", + "identity-obj-proxy": "^3.0.0", + "lint-staged": "^14.0.1", + "markdownlint-cli2": "^0.15.0", + "postcss": "^8.4.31", + "postcss-loader": "^7.3.3", + "postcss-nesting": "^12.0.1", + "style-loader": "^3.3.3", + "tailwindcss": "^3.3.3", + "tailwindcss-animate": "^1.0.7", + "ts-loader": "^9.4.4", + "typescript": "^5.2.2", + "vitest": "^2.1.5", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1", + "webpack-merge": "^5.9.0" + }, + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.md": "markdownlint-cli2 --config .markdownlint-cli2.jsonc --fix" + } +} diff --git a/extensions/chrome_v1/postcss.config.mjs b/extensions/chrome_v1/postcss.config.mjs new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/extensions/chrome_v1/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/extensions/chrome_v1/prettier.config.cjs b/extensions/chrome_v1/prettier.config.cjs new file mode 100644 index 0000000..1c386ce --- /dev/null +++ b/extensions/chrome_v1/prettier.config.cjs @@ -0,0 +1,33 @@ +/** @type {import('prettier').Config} */ +module.exports = { + endOfLine: "lf", + semi: false, + singleQuote: false, + tabWidth: 2, + trailingComma: "es5", + importOrder: [ + "^(react/(.*)$)|^(react$)", + "^(next/(.*)$)|^(next$)", + "", + "", + "^types$", + "^@/types/(.*)$", + "^@/config/(.*)$", + "^@/lib/(.*)$", + "^@/hooks/(.*)$", + "^@/components/ui/(.*)$", + "^@/components/(.*)$", + "^@/registry/(.*)$", + "^@/styles/(.*)$", + "^@/app/(.*)$", + "", + "^[./]", + ], + importOrderSeparation: true, + importOrderSortSpecifiers: true, + importOrderBuiltinModulesToTop: true, + importOrderParserPlugins: ["typescript", "jsx", "decorators-legacy"], + importOrderMergeDuplicateImports: true, + importOrderCombineTypeAndValueImports: true, + plugins: ["@ianvs/prettier-plugin-sort-imports"], +} diff --git a/extensions/chrome_v1/public/icons/claude-ai-128.png b/extensions/chrome_v1/public/icons/claude-ai-128.png new file mode 100644 index 0000000000000000000000000000000000000000..86387c7dc98dee740d480c809222dc43b276f87e GIT binary patch literal 19174 zcmW)nV|ZlU5`{aqZQHhO+qP}nwmI>{nPg%+6DMEniS6X}+#ik8Ph+3Gt5&_M&h8jx zMJWVWTvz}AfFL6+t_pk%{&$0h1l|t`noI#7K-^TNL;(%6_-DWaa4Qi75dh$465NLg z1n?NfSz5;p0Kla9cLR}8CBFdxWc_5sMby0ww|@KN8%X2`eyJDuIW_8xi(;yyrYnRF zMo-d$(UNmVM~i^NUvbk;#`8i)Cz26|wNOt6#|ncFfUJvKc!)(QETo{YUm%Z*inc5M zdS^UYx=uRrsr}1YjXbn^Q;Ss6eJxO>xZKTut?IPw5~t|x7kn>Vy&H=QAWkO$AU04V z*Pwnd17<-$g2MulKC0~Y@~1Fiag zNi&>coVFp-Eu#d1pp5!9zsFa>JEv@*#@w z-eF{1h)a?N%P>J38_9!7(Twj=KJg=Va)QM^2f!I_0h~|?0&8sL)#w1S{9gCjAq>La zZ88MaC|_#*Ki-S_T6D;~_2T%CXf^B;%9ekeX0Xw}G0DKB=bOWnGpcnJ5PFvM$y?Rw zSnFxgnx%#7h|VlYBfmzQ;q%Qv3Y(Z*$M)R@>{(vNR+aik@7~FgGOVQO4KV# z#N3nO0w}3tu7am+#=$uPrF7AF!1zgY?`ip<3pkmo!vU7(WGqWDWiiTgXCSl1(-h-e zG}olXFqy@&@Lm!Ois^0~c$HSxt|jy>a~6`Ni&VoT9X?9k6#KRxB42TD%|4WaF)X9X z@APDzt8G0b?`++pf-DN`X?Zg+A=Pq0k#o)w=un3V*Jo+QGSC-BS6ta{`D}t{u78a9 zoe>2G!BWAGb5rD;Pmwpqt3frYmbZT9v#5&{I&q4i!FR8~AjHS0+*K5CT7%nU^yt`7 zL1-Lk2(hE&(8L-fpU%P{Dj2T`Ak)6|2Ik`Ab%V3{+#@z@pHf!x+iR8al%CA;EDoRg z-DlNF5Rtu4AE)wo8o;81)*MSq61@=)^lD+Hv`fB2e^YYD7$MG<0^XI=R>kxcNFv`9 zW_afWQw@%;LoL>JQNa$3Dcl$0R)=|Pp2Tqdwn#5qna~4nNIhtvy(9#3iL`2~ta5)h zF2@j4_HUQr8z#-$TEh6u{rJ7K@%n4p)X>1*i-c&qbr)j#v(wiE+QH_u3a^dV?dVT zs?Z*2KIa-buEx2q-HHN=gB=hSZr~&shY2CByTEkplCV56|t7 z;nSQ;Dr4`$?&rF`n1)2$6>srm%0>9 zt8HTbkj|R5B-SLu>EW(SkuLB$|N1GC$1Ji|1Cx=o1mlE6ujbwK6q7a?He`rdM7i2l z`MgI<9W1;?OgCnfg(MvYM6_Z~A(@0ymip2+ulp(vu^fBqWbz5BBn!MKOFDWKI?~FW zo(ApW{>rg(9WeQ5H_?xu=+W57C5_yo7CRF|>su;zhw;!6VZ!+$?Y>UqCTKuX;+E;MeF*n-ax@M(@+sMvjLaM^J6S_r*Ib?f(-^VCiHV>(Xfl3WW`>z=Z{5)x!ZzKK zSBAhf$Ipw2uU*ZXa@%7#UmJDD7vLxkxCacV_V*A!iR8?)VLbogH&0WG6WxoZ|6qpc zED06i!I1>1B1Psg#m-Jxuo%afL~Kkkwsn=csZXMI_Tfyqw`xrjAVb!%{z459l?Yeh zvceHLU3iL9(_GFjcvJvyd!-YF0}*7+@iG@;zx~fJ%e2YER{5yf1DbM%+}oYFWeyoz zp;MksLHHD@Grsza4~@0O&t`gE+(!%(cJj960zaNRo?Cj2UMxEzM!cdYO?;?>+&#yu4R=-0oM|7)#MG z-TL!uZmsvtc@piz5_QkR1i#!1VqqQenK3`+(O4uA*IZTv&t#cu*+Ok4Hk* zspwM&WocC`*UfYp+QNYgZUdxlqmojTS~=~0tM%&)eAPuoF+}r{taU4asqiFGIX)Z+ zR8)Pz=!g2$F`@|ZKqdARgX3Auh`K)W*nKNqKA9NCbH8afe_3piX)Y1snF}IR*%MHj zFG`~RXvny%xN4h)oXI>E$<)IhdvfV);{8#mJ3NK83zd%WZeKDHUHh0b<(+-dYZX$+ zN{t6RFn}))p zznLB5;G&>bpWJdh0c4PUX~dOH>^1iL5Pu_K*FV> ztxuT(;ZWOahCsm!vtws&TC8OWOafCb;svX>h+FX8H(@6Vf}R;867@S`g=yGb%I`Cl zFU^6rh=PxcKk~R;=V34{`$J59U#jl<=V>}-3MZ3=fybw`<9JN5;DZ5y2d#5W%6@&P zBvkL=2U&ThO)$?vLP#(V^%KN(3_Olmx7d1!(?e78P)rjgz5%jD*=!)!iEJo%HTPhT zVNpH1Y%SRU>j?%Rb^r$Zxn|Ym!_Fwl)LHby-@aCoQyQCa_;ep?LAM>dgeSCSF;Ntm zo<)7(P@Du)%(N6%yolRK42lIQGJ8Lp?(cL7I4cD!0~S2T2^^^#rxEy@lI0)LfI<=I zYUB@raArwBJcjp_%L5TysS5(_aj0!6!hj5`XwH8dBNs510m)7tj*( zNH$3Z93<{J>hFrXD(PfQSvwm){Z>T z#_PE6r;v1vsX+I*ZkUlXc&5v_K?cy2v#1S(R7&8?Z|9N6Nk93YWSDVTO}0+22FEI9 z3gq=ofm>5Fc|9Rwe{XOV2X$*%1g+ zTmVVj>@Du#^P5SxE07^K&W1BLNho~fk5pHkz1(Ice5CEDuuVWBqQP_4t&S zd=p%gjT6@s=$yPmPXf`0SG{S8u<6)l&9de97~RD;6mwoyVP0*z)6o?yTvpAamy>DC>jd+T!zz(t5PQt#S!j%rke;)S70KF>R<}t*AIZ;A9dmCtKFy zc~mdhgcv1wbs)d9RM@&r{Dnn6zJShlHzD}wq8@dn_osXzWkl?wV}86r8iS9oA}1F5 z&Z&f1tq%5g?&m_^C!-C0o)2EY%d>X-&5rdi{Ymv)ALdcgHBofb83W5&vqLfoe6f~O z=R6_|)-hHra63_={Wuv7=!-I14MGZrih;ybNE_Z_C|giPowj7A9$`WoLFD(8W0rWfuvueC>rA%DPW!EZ9(j%`8SvAPQ8Jj8H*GU$bM|9W8w zm+e%MUb($^OOSFJ2vL95NW6M;-d%c^(UbS;2oh!1%TZ{IRC+^j>Mqk`P!mJSJ9wl5FCwA5rI04c0`C$Hz0$RV$ z&eA%=d}t!;y<(r2aA2LW?MMY9UvlZ~@6Xpd0rMVcE5zzU57@k#e#O?+B@hPShgYY_ z-`%yG1{d*7s9cGu_|X57ZOzQgV5ABGeWO6V!NFU6L)k z;pYqa_$<}RxH(?kl!{Vic-D<-95*VGwI+T*uyq-T2B@7fdL#5Bdg$Q@q+Vx;I<7~f^4$HSmQf(_GP|7`bEB;Mz*tlXGie@5xo)6xeU zxCW4oSSct_%awEDx!BU)9nsb>@H47lj;;gv!ED9e+hsr(4IFJ1SMijv}3wKj*JWxJPzEv z!_7wW*gGau?Ke5z>A>wp@QikQ!h~P$?iXwv4S&SC7-?k0^~^dKX240&;9Krzu~a3$3~j8~8?hG4cClm6 zt%zZLTBuTBP_9PQeO?#3mqL(zLkTlAs)@ITqVAneG~kf1iZGh(64*LseX|7VXvCDzvY`V8=7UT-hYq!STwT&EoDjuV{wn_)DRnfntnDs(x4i92tU!X-nX%RBn6#Rsa}f zkS!Wjm0yt<-`#i_i_^A<_X!`?ZtwdQ(YuHJ^M7tkmNTZlK1Y)or)Osn;GZXj?;Iaf zeg25EJpcJ!|MotjGvYbE>g##!6_H=FYv@-&{5g;EhSJ((rdY@kbqL_qhR`guOo~eRb#9Xj#~jw`gq_cXn*gx#nAQRw(3aQ93m#ag2^u9KE#)aZ z^M`9w{~pG7gV6m6a}Rq0iRk2&Se0V!(LmEvboFEVC+hhjc^t94KF^ilKDCd>EKa$n zUUNP8UehG+~r54%awR@ecV3KkZ(&#>luB$NOfE>n03|rPU;= zGD46z2nSTHI?KeO*Fd|^kslm%>y`T$Ub-o)+emYg*;gBDo^iCu0dqwm1yI#vM~aG3aEdQQJF4n`A_ zb{y>So@)XQa)q;8HO#5W*eK#;UqJ>+S>u{wJHx0Yjpvr)iCj0rmchi|575nI-@EN& zQ_;I*0DmZxqY-EGakreRL30yow&Kx_99)fg9pJxa1m}KKI(2M$iUkD;0i!@Y-}mp2 zmwu?kk>MD$pF4r?d!cZQ!z+4y;3xU_M&2)bQMVIHUuxoYB_$;A2nZ${tv1jk#<%+; zr|!I+$GLVl+t0mF0Ou(Np|Q8e^M79e0*H)?a{0LZ4&HR?BVe67-k1|8W2ug?~EZ$C)n+wBKK8m{m7LTc#y@})QG#g8I* z;=~Vp@%KN#J283u%dzG*C*V8?^BsQP*hiRU_(lNe-)OT#5fwHvBARR-oS#Q_?0vPg z2TqFjr5#RSVbB)@_s7kUN-c&qPzn+G>;--O-kZL^-pwXbDP9h#gm^63@m6bAtRJf4 zRrsBdOI197f^}i|kdN|;8Q5f?qZkikIL!uj2u1@ky_;0+U;q_qsS`J(bl7LU(w2?W zdvx``#%5>Da&U?bgnvq>kF%Wh5AFHAzka$|Qx*B620oM#9eDM<6EJpP@L^+P_xF8n z_L6oZhxv}(ak8C|MI|8uqR&$)7PsSg7P9qD&9+>ewDD=} zWINg<7B~;rYBJsAdv)cqzT4Rs2)0$9Gypi=SZMg9%AIvln|9eC;^rZafFJUP*fNRf znr{bWf;L1xOosB1?)AlGmvl3V!({pd_v6ys0Z@tl&2aBio7*k0m;;mfdK3anKbk@= zvvk3FH_qsTbfr!w3{8n2rL3&XdA6jZ1Iuo;L9A*~LP+ggSxW#HtOy5JMzIrTqb#k@JuwiX}5of*yVvD%B@vzJEhUg(iO%C%t}gSie3} z<~~vW(_Y-~J`ZX1-0+$A6@YeAaIGI35&~ks)&x@Y^*{qnvYXN95246&wAi^9iRUtc zp*8521bhSkO+OeQSgY@CIh)6gGLtC?Rm;Ew<9IqJ?DZaL>~<^&-YMW*&*fnO?w=U9 z%)X)i-T2uSwd5ziJgrT0QfZX%4Eozr1Ew3_3KQ{*_ES zrtk;h)hIe%dB~!OjIyM4Xhxuf$OJ{|YP{caueH2iO7BkRasJR*RZ%R5-OBy}(KmSe zhAm}7?;EXs_hozM*W0VEcXh*%&CM{euy3DjFD)l9D&$WJv%3BJU|N;Quc+ zuDYz~SnaVD@jyi3yFzb2Hh7sm{$1vNzi45xn5;A?f>v-Wc6o#hs6HKWdqxkPZlH+| zaDuf)(O0yJ2uDEyrK|t(xL<-FxS7A=;dYfHS)Z7P!8(!J1s$ThodBzYqq-uxE8vXS z1_TM0Z?Q4MW)%l#XXKNDXN3P;r{-MtW2j6<&&~eS2H-`Ja9t)C3F*xLNy})}^oAOG3Usy1Wbo2$V{HqT6u9H^GZpZNc z_jxBK!0}WS`tkyds|pMgJ9>_g(IrA$4j^4t4f;j{-!G$R)GBv#?Hc~&vGXX7QUB() zCw8L1QOGwX|JSIFgvUVi z%-NE=+W4tbe{1pwed4BFRgfYKsPB;r;O)#^%~iIJU4C%a9xSZ9WlC-a75OjlnqdIb z7K~oct;70BUZp~jAOhdzgRB47ebruy6yXTpjt_gKLSAB{{}COK5FCDfK5d%J<`)1} z2mj*NdBa-3^A?NORR}gdet*|VAcB+cq6n~tY$quNy|H!Pj^epob)A_0JL3#GEmQZ_ z7|WKJ$8#=YcXxGZ(P8=O*Z($7&7RE;dv_`g{WpOfzU0nv?%M`M*D-i z{hguDOZBYhh3u4|>e*V->GnVkGxO!jqy;IN8h}1@t01Ts32Pmcs={U8{7t9soB-MW zwrizQ#G95BNVt)qWtfl@aAB6s=f&9jw%oXT(K7dc*#QCrMpzh_852}G34JW6(8r#~ z%X{B9vM4llEU18h!0Gupurdajd+KBYD;%}Q_>!E_o9ac+{p^}Uht^(k*s*|Ji1zzE zk@0du78N)Lq}6F|)^-H#{W9yJhpA(AGaF*P&aE^fdd<0WoT9%JF<6GpKEcP{F99j( zyBA`;Fiv;sQUjS1*obf$l7d|o&FrRUetBNwz+c}y?TS@l6>?eo+kQbwKtKYm*F<~t z1(Vp@`z@uUghZ*B9}O&EAXx^UIA2%SUx>hBz&R`QE!uN#0QXggwF6{3Ufx*XRQvu; z1gg-IEe&D^rl_!nhIWf3!`Dre(C+)rHwZAOOo6uMa*kK2=}k(@WM;0HO4lNuy%URc z@tgHUlkMKXqPrf1RxdCSaM&nwPR0~B=n3WaDX3nvG(nVEuS@|om>)kabxJF592aVg zgaFb8lXhErpcrPi)(u%0(EWb(J;Za8ALe(}g|+S1^D@FT_;QP;T#JE?gEIoGyFYE0 zX@N?WZ}U^ANwb3rf~_IBKV@h-CL5~TLa_c%qtBBQ6A?jdq8p>DwT-4 z`8Rs6od7OKNJ!_4pG5sCQlCGYGGS9Xsv5e%&mH@6c63b~dl;o478cScY3`}q@}h1G z*tHTurabZQNny0a0WDj(P4_(iVWY0?+DQGv7Q}R|+aIfm!Y`O+*!e0+Xn5>9Esb+I zDeyAg?(tdkIpG27_wl0U-_iD(8f6U(+o!JrtA)VZxbK1IUM~jC2K^Y|a+v~&A2`DZ zy7ibn&;P;)C`G{JiM;ICLF~TzMQ3=QyJ{Ro&>n;<`_a{Y_ZN=&;am^(GgMR4Vo}V7 z&-bwaon4SB*+?9g92`@XyI|Qm&_EQEwka`Mg)PQqlvyUX!chVAp659EfgPr!1*}t{ z=emN=p#{KUcZw{4R`2g-z6dA2)m` zt#1M%mg!1eJ&WCn{lk->mpE83Mph8Qd5{;>ck=$c{z`DNBKl>i{rq>lR2$@+9;Za2 z-bcmjPj;mfP=NTyAH-f(*Ets!D+9ZWHFUlX4rUu)%U_;=9j_ zWD9<-&$+x(Sed*a^`)3D+*a~Xipe}EJakEfa)Y34ZhUrTkOF0C z@A}t;ya5S)mwmqnf;dYdN+?1D+#STV*-YqGn6k-!FO*x>#dn$K49Cup(B~q|%UxDq zc$XVee9?~gfkNlaU<9y+#dS9XH84<%aj~+7j>KUdMB+IPMqn96pDk7${5lV0e!3Id z$TV=KlqAwg&$q=WRW|KhExe*$jiPE3gE22{m<)eVL3OLepO|QRcR1lagtr|G)LZW9!q1D^=q&%wduzd4=QrYwD|N}S zB)SqowFk&clq&*nQQayraIS>s|7^rxOM{Q*T;l{E6QkjdnaQoW%vH^k2LfHOFWt78 zSH!vNZ5=+(+(w}nE1gKd^Zl+?UABErO!fxhSb*>g2nO0daH@P~QlM`t@Y;?s`nprJ z?>bWd0Gi4Hk|_LGqwlw@Y+esq#;!xvkEf2l5u2)lSfKx+Yv@EBBY0Ms&f`MzyZFiX z_mOw9SzJQ{n7UzZh(OylgvfUh4D4@wS`qxQvP~-TK;59r1ns)i^hh5FVD5uIltBm0 zstLofr6YjfRL-rR5jRrU4)@*zDHyAT;$BC61OJN+k5n*H5Q-kgaN;j7vmsWOq@20s zwCP(2405BFL7dKyyD1=m?TqB}@RH&9=lv|-!G2s{!gJp@*jkeb7;v2RFBeuqVR=ryI6!6|QIR5u`3J||JMY_Z zWr@6~QiQLFK%2i~vgu&dt}+c3JdaCp$@Z4Lm9*XCUCH-S`-HX;AZ{#oFtvx5$Jb;9 zUXfNS`v~A|W;bsnh~X^TpEE{U0+b~cM{Vwe(_W&B0+4s#q1IRLmnNHsf%BONoMf)f zyUA-)ps0-g6Qbi;0q7*5FYt!WeOF*`>^F2@4FZK*P}n(W)1kuzi2B|yCb0b%>n+U8 z%)td-fe=8Vd^Xs(tMbL1XJ zPn$MP+x>AQaVy@zXl%P?kflaP-FyZ70MpBmP1KhaV^zbx@SsW5RXmig9Aed(#d0o2 zJMinu+I_VK_7jP|Z_&JO2BMmr_aLaJV2eHuVP4k8632wT4up1r9U8cUv81top4`B5 zogz2lU%~yS8a@92PvH4KIKV$Yrm5!;0Yo4uC?bF6MDBg*I{jDlr>$p?mjR^SBw()x ziwIb5xy~#X=56n+%*#!EYv0)zxP0$Tb1iF$# zivNouOFz~uQ78DaeG~C{Cx{5p&l5%9xdsAsEC$+5yVmsDO#onep#!@r@j5N-qZnHH ziT{9vD*xt3bYzeW96>LlRq0-v)y1sq+1@M*STFuO}vdm()9N%ZNU)B~*5k#kS!A zz`1W2vNL+eg@=bX23q-{#y}4&V2cn8VEFlWis9a*-gKy*iDs+ovusEMoACVWvOm^ zxY~txJd_x%oXZK{9Y|10YqB4KHD0LN!YY|Tm7pxa%?=aeV9mb@Ky=s}bTV14(cpeN z^)h|G>=;7e(vRx+eaZZyoiprt*|swl0!0|?axhv#F9&Q^?DVsY(E|V%%~NzN{QU8J zSH_%P>2WP^45?!oqBv+_{^%0u z6p~u#KV5H}Im#Kg1b!tKCqzAynm52UHxY%Y`4(s9|&G-Q$W$<^@t z=^bd>z94{~=$F6>fCG9o*&Zh|-DO+KOZLjVP#(fOu9Yh=_|>kQOxQ_zA_6JyQv}+H zvocgto=8!Go1=wP(k47aM=3~ilT*H=NsI%LFqJ2LABK>w7`Z- zAgb>H8xDhsd{@j*wQIl@j}*|a4BHuX^8t2&fx-*S+{@edQtGBD#_s)&zTbGtKVGJD z*nT>0ZFnO9xp8#9BlvkCsv+q?$O5@%dis}2xREE3A|T5_gUUR`+M_i^>Wc;#?bFF z=3f#o{M@w6b0~DNa#2C&zUoZ+|FTpSJVAWyV||DHP^;Rn%?{{HH=0pd8*d%UhCLUCnIwtoAT0gmmb2vWrl=-#*1*0HkganAp`~3^M z5cy#4v&)Z{S8HIlcdNE-9@H8M;Dv>S{p)eX|Aud|oEZd`ncKypZBXc|@Be0vPVk?$ z{h0iZ*{XIWn0%isE|*W4GYuYytQ&n%^~>h+Tf9@yRjJy;YxYx_NP>4f{?IU#s7V^c z={kj?t87e%)WbJdB#`j9{Z554Y%{~O>}++2Q7%L#A(g_9fy6RNP@Qy+5KR?Zx?i~J z`wsjEod2#0%<`VoaeqE67GjbJPv;8-2|D)NLIV09&K80FFuGC0C#;Tn8#5q`0u>L{ z)@(mF)#S5`Q@zM%TR`1z}LCYyx-4zmXhXt@M68x0d~55DhyfBX3T zjoKN#xxS9gg(g>sT7RQQJFVmYuQ@OiiFv0^HT&|SpMjRXUzRA_W&%8nL|{K47$ZD= zBZU+uzL3{!o2mY#XZwLF5ffsZBxI5AbmQ)x8vhpH##T)hZrPsgLv3S0l$a%fbF z;!1NxK`rAzL3068f$=}FK8R;B-tKYd9ya>(|APBE!C~%8aSZO*T?H7AN^LwU_BNEY zuFSC%Maj;H*WfqHKF&H0<%*@+hgcAIB4~L_s+lw*bdXA`VKXa59)!e&qM)L%aeDr8 z6Fz}k+ly!yG@kaN{80%prdY*3k}u>_rJ2@U-vGQvp7u+)(H zE6OR9%DF%SjuDPay7cnoKHQX&f_uGxVj!6Ft3^3);urlP;sY4}G)v1oqiuI8iZXs~ z2^fR~noGXAe{~vq?!C1r$=JfGpX!AjavM(D?E%&95z~&##Tedl?2gf7tfa$OZZSHmprtLDaYUVhSYEkXJ zFI&&JL$^SjE1#pKRa>H3FZqB}J*(pL^2-1%SAJ;5eq8)rlBsC#)EzJAH{HX&DNCYZ zMPg*iVeV5dLo=pprPZI4hrh4DI>k|&E-6z_YZUTfUibao`jjTu^s#BAxZG9hurOA3 zns4?+7HN*7F!Nm+TX}=0>lG3a5?M@M)+=Ym%(OG@vW$PWU-b$?SR;6}(JCJt;f?~M zZkx_#d$G&Lv{MQdoO-I?VK$VatE2jgwG3wOSl{A_k<%FP-2@Nhm#Ec6^tCf1v*4WR zqf&e_R+^j=T9>@mv>%Y;AId*l=Ul%+AvkIDropi1YG4aDX6Xu2IoyvgNWxuVB~YVJ zZ*cGjUTkGZH<_qSnmiz-=7e&?YecP`{xh9CUa8r8)DBz-DsH*@QB7Pjl+4X4hk}lw zEyvOC5hA%hk4Xk#Z`mQBY*gK`RpNp^$bkJi${AE)11y9 zcU;Qh^bBVY#8c)8GWBnt|B#+$<&x(b8*tF~w>Yo8jr8v7Gdb0eo_`Xc4~XUbHI zFNyqt>mF5FM{nDKe>B|P4V|193E_grks+w`c8Khr!=}4&%u?_%}qN}YI@CkAliv~q({oUM1X1bV@fWC883 zA9=OQ3p&41>#2%_p><^*342R)du11r7OJUtMN2d8Jxgha4wYqV@5ys)d`2E|v9N;Z z^U2b3v{PfCMMBd`TEDuqRp=L;PQb(?#YBr2MM=@_Z3*$Rl`M=FY!=E*S9oX55olLM9xi969$M0HBM&a+HL-xV$Uf{s=UyU>;LKXd5YS5rEV)$Kr_05MDn z;bd>?|Dm=tW6scXe@l`b+wc0*!@4}RV@SaLY}ALLK(@?<&k;Dx7)`H$lr`6?4=yma zAR!pXH*`#`akAGzqbUW9rqg0gT;&YeC|X-{?X|L`79b~-svX2v=SkHn<%E0@d`mJ7 zrhDd+MwEL1cYr2Qr0r>M;VqyMGmLQ`edHfk*m9u#Cq+=wVm*1>LkeI*+gDFLIgMsR zrQWsWXn=&Qw$(bHG)S#NrgRdAOqd~I!BiZqd)ZkBji5+#H_-Q)=3SoEl&!`n-tdVy zHbV9!Q+1$p7&N>`=G9kYvBDjgE_`~+JJ84|&%2}0(CZG94?#bdf*6k=1~f>9xWxX- zCm^ja7%t~1?P@wDlkz%wxKNa`*!yEF8h0tAl$f`O>+v9el(F&q0fl?8cs>H{$V-G~ z7TNtx+J*#CsXaO@ZC7P*rc&4Pj|&!C^RMTb@<)3PxX;aA(B6e19&`w#0oG#~HSJ!B z;@AwT#X`r@EO$mnnA3B zlAzKSrj>R0auY+P2SWuFc1OQ)vwG`>hjvpiY z@7>O@C$*dtrAeys?NT|TR`~p+ma1l>$0ZcZ=&!`_!W>^!DY~hTEkD5@ zgB<{%tNEGg>~qbm4TV9r-DnAll_%lmn|U8W3gyf|jJhtGRUC>a)eVssOmL-bV{A+Zs4muSCzn&jK- z{$B3g^v0Fx)XNS#&aq>D$_oLdJ$qM` z_hyB0HH4sQ*>Fv@y`m%YCDxsB|9ayI8W`BeYCRu+`I_v*(@E>8ewIVkv}@1WXYmdPr&YT#JwJ zqhcjmIO34a3-A3H&dk_dA4;NodbqV@1@qj?^cQ}R_klPi;={8R7ZqB4IFmLDlamUqG4MpOL9V9JTq04e&AP0hZfCIcR@*jU@GQC@ zL=Cl(FEG0!qUaU1vjCnwQAd)#A-%Y#w^1Fh(@D*%mIFB9kNt8{XNPOM6nr;G2#iUU zeuUg5=!je&c{pw%MS6!>w+*uw17`zK!J9Ee8$4~Q0E-coKBlnpZB3DeBS^^*0dG0c ziDl0%>v15g-o^Wk-8TlJjtukr{y#w?dmMhbN#XNH1ItD9IsERPN+fYfwSHG`XyM`( z8%lEVIpROG)F+@#{_tnn+?(Fj2T~4uQqziH5rmGFr0>|e&FpT)8jRuS1~0k=M-#q5 z!JTviGGHFBu^IaY(xGBi1m_o=C!ANyLA7ZrIO!+|@y6<%_$pwX-p3hRaf50XFZoiH;MAxD;7kAftsRA^*WULU`Oi+X6wLjD2>b zLOg~l$<0>Z`sxI`n$?2}F@>UzO#o9V##JYaTQ{oNI6Lo_+& zQd0{pn+;N0A1mU)^4*B)N5vc7`Czw@#hu5q=DzK)NKHmdRT7uTMKn1=tW^u6tKOYC zvW_0^FBD9x)3e;BlKY~jH7*%-OuZ9_MW?}I4I3jmXYCq}`7{SU^D`DZWR{$QL1Wat zgHigew&T{1aDK#H7=|T}Y#yU3Pfa>4XS)kP92Tf2xVkwkN~|P1@*G*mN-~OdU!YxT zg%XY!%l;#7FO0qS4{VmenvG88pKL>_DUrjpQf3{t$j!${PL$bEP6bxj*c`}BWz-v~ zRFSisJh9q@qVNH&3&xO4on=!6^U55-S8j2USA|T4w(K2}MPD^kiofYP?%SC2>iRX# z3(Mo)Q?F|Q@d)Lg>C=wK3kQr{O<)$O`)$7zg;HB0tJh~u4eai2D4x*4=du*fN8K47 zG^ec@Uv!!bOPFfj$h=CI* zib^*pDSUN}TMqQ`~^bO|d#{$N{%oMzEoVY=(@sXQ~NqLk=wKfeqm(irM+F=6X zuaVD`s2C{t7N1b=GkS229br*o_=#X*$7AJvKxhSVUKul!0JYMg^r5gjbUkKG=NY*v z{(EroG^CUD9y<&ICrq3xjBEpc3tFd7dm)xXSnv9Ou2<;YRb8?#^*E~13;7g>`3Fiy z<*B@je~Vk$I*msK&%Cs=9TF+T#k#XBNDRPsJx0mZeVK%JvrvvR`jY;@46|Bgjg+&K zpl0gNzr+YmDML4boUZoIFZ~7C?=<8F1pv^V1hU;H>|H-rm+iNotZL4nxUc7J&|D%= zReVZ`qpJ!d%1Y=Xve)m;D~mKS?3)%VQYzOKxpdoq)a>6iY@rb#rn$~H=y9VKl|_&R zW@Xz1N+^R+vq~*AK`O~CufJ#?$u8G+)Yjh$CF&zc&9pgPP#qq{83sM zw7PY}X6;t3bSE{rH#0H=G94yXl_c!W$`*9|cu{YH8meQkYN}~rC6QE660+`=g?qD+ z=lRPKeqgGIOv+Fxe`vc8Zi0y$Yb~MBR$7p$^MoC;oJ@hs83oHC#cOQULt7e-3jO_h>Ws&%rBi@NT3B*%=-8*7HtRvM{*rfSfIO$_=> zVnLmEOix^4D{IA5S+K^%zeHusby4V7@G z>W&CFT1!4t=UGX;t*-VSRIPFT6s97tL#HkR5OoEvet(M6@xjJzNQCS+__Ius1A(aK$m^>}Mkb zYGzkJ^~AwwQpGZr|M*|u=QtOVptH124<(l@rb> zm(4JcW=UnK-Se{DjYhL$l$!xJV-J9Q;9&R`GM-mgIuW`*0xfmqt0 zslw;LTPdNy^S=Nx3C;EoK8H1YOaO&WJMHil0>HD5(iJs?P6xi;(1BkXulyA1tkV5F zAw)JI5|@P^1}AV?T|fx%jpSrNP!%d21{KYR1SZ(xhoS|8D>0p#L1{E+hJ@qI6c$3Z zuXA5q6c&Y=&Q={8+?r*A0!OU~!4A9f++YEvS>wNw!YjKYJH1^g44t+Ri;7V-hlM&qC8Mu| zhJEY6X*qnLUKU=eSf;anEvj~tDfBi=(IawY&bxD7N5puQ z9Tf$t=BS{`UR$QTv3bay1S>W$PF_xp<3@(a=m5s!CP$ZAFdW_m77wYVbAwZ9?Yi4OE>LnXAGR=NUfv zo<3BaHhlj$3X^;W-)N*Q*}j199T3yGXytqR)Ax`wX~^bjCb=$eK7)`70Yz^=iox*_ zUEyp~4J)dMPJ9PbjzS^mBzIHBjL}w58zU*9YX}v*9cB>9PE4;-%UOSpvO@_K?3!Jx z+D#HIEB#7#k#^OYH!bIjzZ0^2y&hjnSB?(T&i4%vW$HaF8v|qc*n%ch@pZJmuOEOz zF)$vX)28Fo=#9ebd-{Nmj-V+#s@5oxL7`?bFrJ#<)0YH7j$Tj>f+$@jCo|BLRLv32 zNg5htLV|4#)6sXXsG%6QMN}NZw5pt{k|CMQ7)>2{z_}@_r$QK-D3$ShnQJQ-E9&XE zz040uJP2?ywWj?5;6sN6io95kh!MHQ^JIdK8m;U=ft7nSo?eC4I`K^=GYNNK zCeSo1R93N~m{~;^&g@U1Gia}mi4L8J3a50ZONSY-UqV(jvH?X;A04$uk0wk=;}&R# z#^f!jS#lF#{(?21>e>u(%Jpl3szkYAh(z)_g-eO0N(Ac7K|!eYI&--`VRFl|wy>=Dr2$7t^TnL;r zFIb6L%q39!eE%SsS(bFDvQxH0sJOk&Yg4P`&P}VFat)p->Z;SBP?Z!e=vdpLcYwQp z+AT!{@`;pQ_B}L1q3k3?0}-go6J3gY%Fzwc+_<8KsyUj_pe*SWdPeNz4c|Q!(Xiwm z3Rmx;g31Xmhu0HLDn(=FjLHlEfDbx~JX9T2?IwJ0ABx_AwC3!TWB>#)lrUJ>D9Cam zI-KeSD_iK$@o|ErfPktss_J9r2YkIJ+VJ?yz$hbzp%6-$tuB}ev=-V;61^pcTcxvw zKmjo;ST#p@uN>f@a|;myioOB3S|P`f2oO5$nCB0jHmcSr$_?v?yv@;+i4-~oZ3Oi_ zeQ-rVU!$^})+vP>^p66}=U1b{G8=^HvNkVqT z%pF5B*-Il3ggTbIX^tklZUQ2^Xj*g;EvH0_ zIVQHsR}`o^gyB$?C?Mo1k1|RX_^SN%7&QiT+x`Fq4&wgYNgP7PET1|B0oyj+dpQvvqNHl*Wx3d-V`?1{K z*5++TQ;-mu06RxCf@r5GC(+jC^9;YETl1f5iwFRhI*Kv7WAY#v+*-CtLJ$n9O$*Al zUJW-!Gv(QaKu56};Q|g~ir4X$icna|J2x-iL19j*cME8;m0;PK52AH`SVR)Fq=V4X zCKX*9h0yG&oKw!7Hk^!Bl%%o8HkRmd&e5=n{Rt+S0;c9z5R+vLfoEX81pn!u gXJg9$9`o7%2Wek~$0nLt?EnA(07*qoM6N<$f|+RO1^@s6 literal 0 HcmV?d00001 diff --git a/extensions/chrome_v1/public/icons/claude-ai-16.png b/extensions/chrome_v1/public/icons/claude-ai-16.png new file mode 100644 index 0000000000000000000000000000000000000000..7e9ff0576d5821f9799486997e5c155cd6d78081 GIT binary patch literal 636 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 Promise.resolve({})), + set: vi.fn().mockImplementation(() => Promise.resolve()), + }, + }, +} as unknown as ChromeMock + +// Add chrome to global +;(globalThis as any).chrome = mockChrome + +describe("Chrome Extension Basic Tests", () => { + beforeEach(() => { + // Clear all mocks before each test + vi.clearAllMocks() + }) + + it("should send messages correctly", () => { + const message = { type: "TEST_MESSAGE", data: "test data" } + chrome.runtime.sendMessage(message) + + expect(mockChrome.runtime.sendMessage).toHaveBeenCalledWith(message) + }) + + it("should handle storage operations", async () => { + const testData = { key: "value" } + + // Setup the mock to return our test data + mockChrome.storage.local.get.mockImplementationOnce(() => + Promise.resolve(testData) + ) + + const result = await chrome.storage.local.get("key") + expect(result).toEqual(testData) + }) + + it("should add message listeners", () => { + const messageListener = (message: any) => console.log(message) + chrome.runtime.onMessage.addListener(messageListener) + + expect(mockChrome.runtime.onMessage.addListener).toHaveBeenCalledWith( + messageListener + ) + }) +}) diff --git a/extensions/chrome_v1/src/content/index.tsx b/extensions/chrome_v1/src/content/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/extensions/chrome_v1/src/lib/utils.ts b/extensions/chrome_v1/src/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/extensions/chrome_v1/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/extensions/chrome_v1/src/manifest.json b/extensions/chrome_v1/src/manifest.json new file mode 100644 index 0000000..9775e99 --- /dev/null +++ b/extensions/chrome_v1/src/manifest.json @@ -0,0 +1,26 @@ +{ + "manifest_version": 3, + "name": "Thinking Claude", + "version": "1.1.0", + "description": "Chrome extension for letting Claude think like a real human", + "content_scripts": [ + { + "matches": ["https://*.claude.ai/*"], + "js": ["content.js"] + } + ], + "icons": { + "16": "icons/claude-ai-16.png", + "48": "icons/claude-ai-48.png", + "128": "icons/claude-ai-128.png" + }, + "action": { + "default_icon": { + "16": "icons/claude-ai-16.png", + "48": "icons/claude-ai-48.png", + "128": "icons/claude-ai-128.png" + }, + "default_title": "Thinking Claude" + }, + "permissions": ["storage"] +} diff --git a/extensions/chrome_v1/src/styles/globals.css b/extensions/chrome_v1/src/styles/globals.css new file mode 100644 index 0000000..d8fade3 --- /dev/null +++ b/extensions/chrome_v1/src/styles/globals.css @@ -0,0 +1,90 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 47.4% 11.2%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --card: 0 0% 100%; + --card-foreground: 222.2 47.4% 11.2%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 100% 50%; + --destructive-foreground: 210 40% 98%; + --ring: 215 20.2% 65.1%; + --radius: 0.5rem; + } + + .dark { + --background: 224 71% 4%; + --foreground: 213 31% 91%; + --muted: 223 47% 11%; + --muted-foreground: 215.4 16.3% 56.9%; + --accent: 216 34% 17%; + --accent-foreground: 210 40% 98%; + --popover: 224 71% 4%; + --popover-foreground: 215 20.2% 65.1%; + --border: 216 34% 17%; + --input: 216 34% 17%; + --card: 224 71% 4%; + --card-foreground: 213 31% 91%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 1.2%; + --secondary: 222.2 47.4% 11.2%; + --secondary-foreground: 210 40% 98%; + --destructive: 0 63% 31%; + --destructive-foreground: 210 40% 98%; + --ring: 216 34% 17%; + } +} + +/* @layer base { + * { + @apply border-border; + } + body { + @apply font-sans antialiased bg-background text-foreground; + } +} + + +@layer base { + :root { + font-family: Inter, system-ui, sans-serif; + } +} */ + +@layer components { + .btn { + @apply inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50; + } + + .btn-primary { + @apply bg-primary-600 text-white hover:bg-primary-700; + } + + .btn-secondary { + @apply bg-bg-200 text-text-700 hover:bg-bg-300; + } + + .btn-outline { + @apply border border-bg-300 bg-transparent text-text-700 hover:bg-bg-100; + } +} + +@layer utilities { + .text-gradient { + @apply bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent; + } +} diff --git a/extensions/chrome_v1/src/types/css.d.ts b/extensions/chrome_v1/src/types/css.d.ts new file mode 100644 index 0000000..001522b --- /dev/null +++ b/extensions/chrome_v1/src/types/css.d.ts @@ -0,0 +1,28 @@ +/** Extension for typescript if we need to import css files + * what this does is that it tells typescript that the styles object is an object with string keys and values + * example usage + * + * import styles from './styles.css'; + * Now TypeScript knows that styles is an object with string keys and values element.className = styles.someClass; + * + */ + +declare module "*.css" { + const content: { [className: string]: string } + export default content +} + +declare module "*.scss" { + const content: { [className: string]: string } + export default content +} + +declare module "*.sass" { + const content: { [className: string]: string } + export default content +} + +declare module "*.less" { + const content: { [className: string]: string } + export default content +} diff --git a/extensions/chrome_v1/tailwind.config.cjs b/extensions/chrome_v1/tailwind.config.cjs new file mode 100644 index 0000000..53bc121 --- /dev/null +++ b/extensions/chrome_v1/tailwind.config.cjs @@ -0,0 +1,50 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ['class'], + content: ['./src/**/*.{ts,tsx}', './src/components/**/*.{ts,tsx}', './src/content/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))', + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + }, + borderRadius: { + lg: `var(--radius)`, + md: `calc(var(--radius) - 2px)`, + sm: 'calc(var(--radius) - 4px)', + }, + }, + }, + plugins: [require('tailwindcss-animate')], +}; diff --git a/extensions/chrome_v1/tsconfig.json b/extensions/chrome_v1/tsconfig.json new file mode 100644 index 0000000..a43bacc --- /dev/null +++ b/extensions/chrome_v1/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es6", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": false, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "typeRoots": ["node_modules/@types", "src/types"], + "types": ["node", "chrome"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "build", "dist"] +} diff --git a/extensions/chrome_v1/vitest.config.ts b/extensions/chrome_v1/vitest.config.ts new file mode 100644 index 0000000..9ea1a8b --- /dev/null +++ b/extensions/chrome_v1/vitest.config.ts @@ -0,0 +1,10 @@ +/// +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + include: ["src/**/*.{test,spec}.{ts,tsx}"], + environment: "node", + runner: "vitest", + }, +}) diff --git a/extensions/chrome_v1/webpack/webpack.common.js b/extensions/chrome_v1/webpack/webpack.common.js new file mode 100644 index 0000000..cfa9f5e --- /dev/null +++ b/extensions/chrome_v1/webpack/webpack.common.js @@ -0,0 +1,45 @@ +import path from "path" +import { fileURLToPath } from "url" +import CopyPlugin from "copy-webpack-plugin" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +export default { + entry: { + // popup: path.resolve(__dirname, '..', 'src', 'popup', 'index.tsx'), //popup is not being developed yet + // background: path.resolve(__dirname, '..', 'src', 'background', 'index.ts'), //background is not being developed yet + content: path.resolve(__dirname, "..", "src", "content", "index.tsx"), + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader", "postcss-loader"], + }, + ], + }, + resolve: { + extensions: [".tsx", ".ts", ".js"], + }, + output: { + path: path.resolve(__dirname, "..", "dist"), + filename: "[name].js", + clean: true, + }, + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: path.resolve(__dirname, "..", "public"), + to: path.resolve(__dirname, "..", "dist"), + }, + ], + }), + ], +} diff --git a/extensions/chrome_v1/webpack/webpack.dev.js b/extensions/chrome_v1/webpack/webpack.dev.js new file mode 100644 index 0000000..b5f64fd --- /dev/null +++ b/extensions/chrome_v1/webpack/webpack.dev.js @@ -0,0 +1,12 @@ +import { merge } from "webpack-merge" + +import common from "./webpack.common.js" + +export default merge(common, { + mode: "development", + devtool: "inline-source-map", + devServer: { + static: "./dist", + hot: true, + }, +}) diff --git a/extensions/chrome_v1/webpack/webpack.prod.js b/extensions/chrome_v1/webpack/webpack.prod.js new file mode 100644 index 0000000..c069fca --- /dev/null +++ b/extensions/chrome_v1/webpack/webpack.prod.js @@ -0,0 +1,8 @@ +import { merge } from "webpack-merge" + +import common from "./webpack.common.js" + +export default merge(common, { + mode: "production", + devtool: false, +}) diff --git a/extensions/firefox/content.js b/extensions/firefox/content.js new file mode 100644 index 0000000..11fd47c --- /dev/null +++ b/extensions/firefox/content.js @@ -0,0 +1,362 @@ +class CodeBlockCollapser { + static SELECTORS = { + PRE: "pre", + CODE_CONTAINER: ".code-block__code", + MAIN_CONTAINER: ".relative.flex.flex-col", + THINKING_LABEL: ".text-text-300", + ORIGINAL_COPY_BTN: ".pointer-events-none", + CODE: "code", + }; + + static CLASSES = { + THINKING_HEADER: "thinking-header", + COPY_CONTAINER: + "from-bg-300/90 to-bg-300/70 pointer-events-auto rounded-md bg-gradient-to-b p-0.5 backdrop-blur-md", + COPY_BUTTON: + "flex flex-row items-center gap-1 rounded-md p-1 py-0.5 text-xs transition-opacity delay-100 hover:bg-bg-200 opacity-60 hover:opacity-100", + COPY_TEXT: "text-text-200 pr-0.5", + TOGGLE_BUTTON: "flex items-center text-text-500 hover:text-text-300", + TOGGLE_LABEL: "font-medium text-sm", + THINKING_ANIMATION: "thinking-animation", + }; + + static ANIMATION_STYLES = ` + @keyframes gradientWave { + 0% { background-position: 200% 50%; } + 100% { background-position: -200% 50%; } + } + + .thinking-animation { + background: linear-gradient( + 90deg, + rgba(156, 163, 175, 0.7) 0%, + rgba(209, 213, 219, 1) 25%, + rgba(156, 163, 175, 0.7) 50%, + rgba(209, 213, 219, 1) 75%, + rgba(156, 163, 175, 0.7) 100% + ); + background-size: 200% 100%; + animation: gradientWave 3s linear infinite; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + color: transparent; + } + `; + + static ICONS = { + COPY: ``, + TICK: ``, + ARROW: ``, + }; + + static TIMINGS = { + RETRY_DELAY: 1000, + MUTATION_DELAY: 100, + CHECK_INTERVAL: 2000, + COPY_FEEDBACK: 2000, + MAX_RETRIES: 10, + }; + + constructor() { + this.observers = new Set(); + this.injectStyles(); + this.initWithRetry(); + + window.addEventListener("unload", () => this.cleanup()); + } + + injectStyles() { + if (!document.getElementById("thinking-animation-styles")) { + const styleSheet = document.createElement("style"); + styleSheet.id = "thinking-animation-styles"; + styleSheet.textContent = CodeBlockCollapser.ANIMATION_STYLES; + document.head.appendChild(styleSheet); + } + } + + createElement(tag, className = "", innerHTML = "") { + const element = document.createElement(tag); + if (className) element.className = className; + if (innerHTML) element.innerHTML = innerHTML; + return element; + } + + createCopyButton() { + const container = this.createElement( + "div", + CodeBlockCollapser.CLASSES.COPY_CONTAINER + ); + const button = this.createElement( + "button", + CodeBlockCollapser.CLASSES.COPY_BUTTON + ); + const iconSpan = this.createElement( + "span", + "", + CodeBlockCollapser.ICONS.COPY + ); + const textSpan = this.createElement( + "span", + CodeBlockCollapser.CLASSES.COPY_TEXT, + "Copy" + ); + + button.append(iconSpan, textSpan); + container.appendChild(button); + + button.addEventListener("click", () => { + const codeText = button + .closest(CodeBlockCollapser.SELECTORS.PRE) + ?.querySelector(CodeBlockCollapser.SELECTORS.CODE)?.textContent; + + if (!codeText) return; + + navigator.clipboard + .writeText(codeText) + .then(() => { + iconSpan.innerHTML = CodeBlockCollapser.ICONS.TICK; + textSpan.textContent = "Copied!"; + + setTimeout(() => { + iconSpan.innerHTML = CodeBlockCollapser.ICONS.COPY; + textSpan.textContent = "Copy"; + }, CodeBlockCollapser.TIMINGS.COPY_FEEDBACK); + }) + .catch((error) => { + console.error("Failed to copy:", error); + }); + }); + + return container; + } + + createToggleButton(isStreaming = false) { + const button = this.createElement( + "button", + CodeBlockCollapser.CLASSES.TOGGLE_BUTTON + ); + const labelText = isStreaming ? "Thinking..." : "View thinking process"; + button.innerHTML = ` + ${CodeBlockCollapser.ICONS.ARROW} + ${labelText} + `; + return button; + } + + updateHeaderState(headerContainer, isStreaming) { + const toggleBtn = headerContainer.querySelector( + `.${CodeBlockCollapser.CLASSES.TOGGLE_BUTTON}` + ); + const label = toggleBtn.querySelector("span"); + + label.textContent = isStreaming ? "Thinking..." : "View thinking process"; + + if (isStreaming) { + label.classList.add(CodeBlockCollapser.CLASSES.THINKING_ANIMATION); + } else { + label.classList.remove(CodeBlockCollapser.CLASSES.THINKING_ANIMATION); + } + } + + setupCodeContainer(container, toggleBtn) { + if (!container) return; + + container.style.cssText = ` + transition: all 0.3s ease-in-out; + overflow-x: hidden; + overflow-y: auto; + max-height: 0; + opacity: 0; + padding: 0; + max-width: 100%; + display: block; + `; + + const codeElement = container.querySelector( + CodeBlockCollapser.SELECTORS.CODE + ); + if (codeElement) { + codeElement.style.cssText = ` + white-space: pre-wrap !important; + word-break: break-word !important; + overflow-wrap: break-word !important; + display: block !important; + max-width: 100% !important; + `; + } + + toggleBtn.addEventListener("click", () => { + const shouldToggleOpen = container.style.maxHeight === "0px"; + const arrow = toggleBtn.querySelector("svg"); + const label = toggleBtn.querySelector("span"); + + container.style.maxHeight = shouldToggleOpen ? "50vh" : "0"; + container.style.opacity = shouldToggleOpen ? "1" : "0"; + container.style.padding = shouldToggleOpen ? "1em" : "0"; + + arrow.style.transform = `rotate(${shouldToggleOpen ? 180 : 0}deg)`; + if ( + !label.classList.contains(CodeBlockCollapser.CLASSES.THINKING_ANIMATION) + ) { + label.textContent = shouldToggleOpen + ? "Hide thinking process" + : "View thinking process"; + } + }); + } + + processBlock(pre) { + const headerContainer = this.createElement( + "div", + CodeBlockCollapser.CLASSES.THINKING_HEADER + ); + headerContainer.style.cssText = + "display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: var(--bg-300);"; + + const isStreaming = pre.closest('[data-is-streaming="true"]') !== null; + const toggleBtn = this.createToggleButton(isStreaming); + const copyBtn = this.createCopyButton(); + + headerContainer.append(toggleBtn, copyBtn); + + const codeContainer = pre.querySelector( + CodeBlockCollapser.SELECTORS.CODE_CONTAINER + ); + this.setupCodeContainer(codeContainer, toggleBtn); + + const mainContainer = pre.querySelector( + CodeBlockCollapser.SELECTORS.MAIN_CONTAINER + ); + if (mainContainer) { + const codeParent = pre.querySelector( + CodeBlockCollapser.SELECTORS.CODE_CONTAINER + )?.parentElement; + if (codeParent) { + mainContainer.insertBefore(headerContainer, codeParent); + } + + const streamingContainer = pre.closest("[data-is-streaming]"); + if (streamingContainer) { + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if ( + mutation.type === "attributes" && + mutation.attributeName === "data-is-streaming" + ) { + const isStreamingNow = + streamingContainer.getAttribute("data-is-streaming") === "true"; + this.updateHeaderState(headerContainer, isStreamingNow); + } + } + }); + + observer.observe(streamingContainer, { + attributes: true, + attributeFilter: ["data-is-streaming"], + }); + + this.observers.add(observer); + + new MutationObserver((mutations) => { + if (!document.contains(streamingContainer)) { + observer.disconnect(); + this.observers.delete(observer); + } + }).observe(document.body, { childList: true, subtree: true }); + } + + for (const selector of [ + CodeBlockCollapser.SELECTORS.THINKING_LABEL, + CodeBlockCollapser.SELECTORS.ORIGINAL_COPY_BTN, + ]) { + const element = pre.querySelector(selector); + if (element) element.style.display = "none"; + } + } + } + + initWithRetry(retryCount = 0) { + if (retryCount >= CodeBlockCollapser.TIMINGS.MAX_RETRIES) return; + + const blocks = document.querySelectorAll(CodeBlockCollapser.SELECTORS.PRE); + if (blocks.length === 0) { + setTimeout( + () => this.initWithRetry(retryCount + 1), + CodeBlockCollapser.TIMINGS.RETRY_DELAY + ); + return; + } + + this.processExistingBlocks(); + this.setupObserver(); + this.setupPeriodicCheck(); + } + + setupObserver() { + const observer = new MutationObserver((mutations) => { + let shouldProcess = false; + for (const mutation of mutations) { + if ( + mutation.addedNodes.length > 0 || + (mutation.type === "attributes" && + mutation.attributeName === "data-is-streaming") + ) { + shouldProcess = true; + } + } + + if (shouldProcess) { + this.processExistingBlocks(); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ["data-is-streaming"], + }); + + this.observers.add(observer); + } + + setupPeriodicCheck() { + setInterval(() => { + this.processExistingBlocks(); + }, CodeBlockCollapser.TIMINGS.CHECK_INTERVAL); + } + + processExistingBlocks() { + for (const pre of document.querySelectorAll( + CodeBlockCollapser.SELECTORS.PRE + )) { + const header = pre.querySelector( + CodeBlockCollapser.SELECTORS.THINKING_LABEL + ); + if ( + header?.textContent.trim() === "thinking" && + !pre.querySelector(`.${CodeBlockCollapser.CLASSES.THINKING_HEADER}`) + ) { + this.processBlock(pre); + } + } + } + + cleanup() { + for (const observer of this.observers) { + observer.disconnect(); + } + this.observers.clear(); + } +} + +new CodeBlockCollapser(); + +document.addEventListener("DOMContentLoaded", () => { + if (!window.codeBlockCollapser) { + window.codeBlockCollapser = new CodeBlockCollapser(); + } +}); diff --git a/extensions/firefox/manifest.json b/extensions/firefox/manifest.json new file mode 100644 index 0000000..88a64e2 --- /dev/null +++ b/extensions/firefox/manifest.json @@ -0,0 +1,19 @@ +{ + "manifest_version": 2, + "name": "Thinking Claude", + "version": "2.1", + "description": "Let Claude think. Makes Claude's thinking process expandable and collapsible.", + + "content_scripts": [ + { + "matches": ["https://*.claude.ai/*"], + "js": ["content.js"] + } + ], + + "browser_specific_settings": { + "gecko": { + "strict_min_version": "58.0" + } + } +} From 7cd3192d709897b57673a95251d4ac807bd6824a Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Fri, 22 Nov 2024 23:51:18 +0800 Subject: [PATCH 02/26] chore: remove old extension root directory Remove the old extension directory as it has been reorganized: - chrome extension -> chrome_v0 - new implementation -> chrome_v1 --- extension/.vscode/extensions.json | 3 - extension/changelog.md | 22 -- extension/chrome/content.js | 362 ------------------------------ extension/chrome/manifest.json | 12 - extension/firefox/content.js | 362 ------------------------------ extension/firefox/manifest.json | 19 -- 6 files changed, 780 deletions(-) delete mode 100644 extension/.vscode/extensions.json delete mode 100644 extension/changelog.md delete mode 100644 extension/chrome/content.js delete mode 100644 extension/chrome/manifest.json delete mode 100644 extension/firefox/content.js delete mode 100644 extension/firefox/manifest.json diff --git a/extension/.vscode/extensions.json b/extension/.vscode/extensions.json deleted file mode 100644 index 699ed73..0000000 --- a/extension/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["biomejs.biome"] -} diff --git a/extension/changelog.md b/extension/changelog.md deleted file mode 100644 index 323b129..0000000 --- a/extension/changelog.md +++ /dev/null @@ -1,22 +0,0 @@ -## Changelog of the extensions - -### fix: - 11/17/2024 - @lumpinif - -#### Observer Management and Memory Leak Prevention - -- Added observer tracking using Set to manage all MutationObservers -- Added cleanup on element removal to prevent dangling observers -- Added global cleanup on window unload -- Added observer cleanup when observed elements are removed from DOM - -#### Code Quality - -- Fixed code formatting and linting issues flagged by Biome - -#### Development Setup - -- Added .vscode settings with Biome extension recommendation - -#### Platform Updates - -- Updated code in both Chrome and Firefox extensions diff --git a/extension/chrome/content.js b/extension/chrome/content.js deleted file mode 100644 index 11fd47c..0000000 --- a/extension/chrome/content.js +++ /dev/null @@ -1,362 +0,0 @@ -class CodeBlockCollapser { - static SELECTORS = { - PRE: "pre", - CODE_CONTAINER: ".code-block__code", - MAIN_CONTAINER: ".relative.flex.flex-col", - THINKING_LABEL: ".text-text-300", - ORIGINAL_COPY_BTN: ".pointer-events-none", - CODE: "code", - }; - - static CLASSES = { - THINKING_HEADER: "thinking-header", - COPY_CONTAINER: - "from-bg-300/90 to-bg-300/70 pointer-events-auto rounded-md bg-gradient-to-b p-0.5 backdrop-blur-md", - COPY_BUTTON: - "flex flex-row items-center gap-1 rounded-md p-1 py-0.5 text-xs transition-opacity delay-100 hover:bg-bg-200 opacity-60 hover:opacity-100", - COPY_TEXT: "text-text-200 pr-0.5", - TOGGLE_BUTTON: "flex items-center text-text-500 hover:text-text-300", - TOGGLE_LABEL: "font-medium text-sm", - THINKING_ANIMATION: "thinking-animation", - }; - - static ANIMATION_STYLES = ` - @keyframes gradientWave { - 0% { background-position: 200% 50%; } - 100% { background-position: -200% 50%; } - } - - .thinking-animation { - background: linear-gradient( - 90deg, - rgba(156, 163, 175, 0.7) 0%, - rgba(209, 213, 219, 1) 25%, - rgba(156, 163, 175, 0.7) 50%, - rgba(209, 213, 219, 1) 75%, - rgba(156, 163, 175, 0.7) 100% - ); - background-size: 200% 100%; - animation: gradientWave 3s linear infinite; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - color: transparent; - } - `; - - static ICONS = { - COPY: ``, - TICK: ``, - ARROW: ``, - }; - - static TIMINGS = { - RETRY_DELAY: 1000, - MUTATION_DELAY: 100, - CHECK_INTERVAL: 2000, - COPY_FEEDBACK: 2000, - MAX_RETRIES: 10, - }; - - constructor() { - this.observers = new Set(); - this.injectStyles(); - this.initWithRetry(); - - window.addEventListener("unload", () => this.cleanup()); - } - - injectStyles() { - if (!document.getElementById("thinking-animation-styles")) { - const styleSheet = document.createElement("style"); - styleSheet.id = "thinking-animation-styles"; - styleSheet.textContent = CodeBlockCollapser.ANIMATION_STYLES; - document.head.appendChild(styleSheet); - } - } - - createElement(tag, className = "", innerHTML = "") { - const element = document.createElement(tag); - if (className) element.className = className; - if (innerHTML) element.innerHTML = innerHTML; - return element; - } - - createCopyButton() { - const container = this.createElement( - "div", - CodeBlockCollapser.CLASSES.COPY_CONTAINER - ); - const button = this.createElement( - "button", - CodeBlockCollapser.CLASSES.COPY_BUTTON - ); - const iconSpan = this.createElement( - "span", - "", - CodeBlockCollapser.ICONS.COPY - ); - const textSpan = this.createElement( - "span", - CodeBlockCollapser.CLASSES.COPY_TEXT, - "Copy" - ); - - button.append(iconSpan, textSpan); - container.appendChild(button); - - button.addEventListener("click", () => { - const codeText = button - .closest(CodeBlockCollapser.SELECTORS.PRE) - ?.querySelector(CodeBlockCollapser.SELECTORS.CODE)?.textContent; - - if (!codeText) return; - - navigator.clipboard - .writeText(codeText) - .then(() => { - iconSpan.innerHTML = CodeBlockCollapser.ICONS.TICK; - textSpan.textContent = "Copied!"; - - setTimeout(() => { - iconSpan.innerHTML = CodeBlockCollapser.ICONS.COPY; - textSpan.textContent = "Copy"; - }, CodeBlockCollapser.TIMINGS.COPY_FEEDBACK); - }) - .catch((error) => { - console.error("Failed to copy:", error); - }); - }); - - return container; - } - - createToggleButton(isStreaming = false) { - const button = this.createElement( - "button", - CodeBlockCollapser.CLASSES.TOGGLE_BUTTON - ); - const labelText = isStreaming ? "Thinking..." : "View thinking process"; - button.innerHTML = ` - ${CodeBlockCollapser.ICONS.ARROW} - ${labelText} - `; - return button; - } - - updateHeaderState(headerContainer, isStreaming) { - const toggleBtn = headerContainer.querySelector( - `.${CodeBlockCollapser.CLASSES.TOGGLE_BUTTON}` - ); - const label = toggleBtn.querySelector("span"); - - label.textContent = isStreaming ? "Thinking..." : "View thinking process"; - - if (isStreaming) { - label.classList.add(CodeBlockCollapser.CLASSES.THINKING_ANIMATION); - } else { - label.classList.remove(CodeBlockCollapser.CLASSES.THINKING_ANIMATION); - } - } - - setupCodeContainer(container, toggleBtn) { - if (!container) return; - - container.style.cssText = ` - transition: all 0.3s ease-in-out; - overflow-x: hidden; - overflow-y: auto; - max-height: 0; - opacity: 0; - padding: 0; - max-width: 100%; - display: block; - `; - - const codeElement = container.querySelector( - CodeBlockCollapser.SELECTORS.CODE - ); - if (codeElement) { - codeElement.style.cssText = ` - white-space: pre-wrap !important; - word-break: break-word !important; - overflow-wrap: break-word !important; - display: block !important; - max-width: 100% !important; - `; - } - - toggleBtn.addEventListener("click", () => { - const shouldToggleOpen = container.style.maxHeight === "0px"; - const arrow = toggleBtn.querySelector("svg"); - const label = toggleBtn.querySelector("span"); - - container.style.maxHeight = shouldToggleOpen ? "50vh" : "0"; - container.style.opacity = shouldToggleOpen ? "1" : "0"; - container.style.padding = shouldToggleOpen ? "1em" : "0"; - - arrow.style.transform = `rotate(${shouldToggleOpen ? 180 : 0}deg)`; - if ( - !label.classList.contains(CodeBlockCollapser.CLASSES.THINKING_ANIMATION) - ) { - label.textContent = shouldToggleOpen - ? "Hide thinking process" - : "View thinking process"; - } - }); - } - - processBlock(pre) { - const headerContainer = this.createElement( - "div", - CodeBlockCollapser.CLASSES.THINKING_HEADER - ); - headerContainer.style.cssText = - "display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: var(--bg-300);"; - - const isStreaming = pre.closest('[data-is-streaming="true"]') !== null; - const toggleBtn = this.createToggleButton(isStreaming); - const copyBtn = this.createCopyButton(); - - headerContainer.append(toggleBtn, copyBtn); - - const codeContainer = pre.querySelector( - CodeBlockCollapser.SELECTORS.CODE_CONTAINER - ); - this.setupCodeContainer(codeContainer, toggleBtn); - - const mainContainer = pre.querySelector( - CodeBlockCollapser.SELECTORS.MAIN_CONTAINER - ); - if (mainContainer) { - const codeParent = pre.querySelector( - CodeBlockCollapser.SELECTORS.CODE_CONTAINER - )?.parentElement; - if (codeParent) { - mainContainer.insertBefore(headerContainer, codeParent); - } - - const streamingContainer = pre.closest("[data-is-streaming]"); - if (streamingContainer) { - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if ( - mutation.type === "attributes" && - mutation.attributeName === "data-is-streaming" - ) { - const isStreamingNow = - streamingContainer.getAttribute("data-is-streaming") === "true"; - this.updateHeaderState(headerContainer, isStreamingNow); - } - } - }); - - observer.observe(streamingContainer, { - attributes: true, - attributeFilter: ["data-is-streaming"], - }); - - this.observers.add(observer); - - new MutationObserver((mutations) => { - if (!document.contains(streamingContainer)) { - observer.disconnect(); - this.observers.delete(observer); - } - }).observe(document.body, { childList: true, subtree: true }); - } - - for (const selector of [ - CodeBlockCollapser.SELECTORS.THINKING_LABEL, - CodeBlockCollapser.SELECTORS.ORIGINAL_COPY_BTN, - ]) { - const element = pre.querySelector(selector); - if (element) element.style.display = "none"; - } - } - } - - initWithRetry(retryCount = 0) { - if (retryCount >= CodeBlockCollapser.TIMINGS.MAX_RETRIES) return; - - const blocks = document.querySelectorAll(CodeBlockCollapser.SELECTORS.PRE); - if (blocks.length === 0) { - setTimeout( - () => this.initWithRetry(retryCount + 1), - CodeBlockCollapser.TIMINGS.RETRY_DELAY - ); - return; - } - - this.processExistingBlocks(); - this.setupObserver(); - this.setupPeriodicCheck(); - } - - setupObserver() { - const observer = new MutationObserver((mutations) => { - let shouldProcess = false; - for (const mutation of mutations) { - if ( - mutation.addedNodes.length > 0 || - (mutation.type === "attributes" && - mutation.attributeName === "data-is-streaming") - ) { - shouldProcess = true; - } - } - - if (shouldProcess) { - this.processExistingBlocks(); - } - }); - - observer.observe(document.body, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ["data-is-streaming"], - }); - - this.observers.add(observer); - } - - setupPeriodicCheck() { - setInterval(() => { - this.processExistingBlocks(); - }, CodeBlockCollapser.TIMINGS.CHECK_INTERVAL); - } - - processExistingBlocks() { - for (const pre of document.querySelectorAll( - CodeBlockCollapser.SELECTORS.PRE - )) { - const header = pre.querySelector( - CodeBlockCollapser.SELECTORS.THINKING_LABEL - ); - if ( - header?.textContent.trim() === "thinking" && - !pre.querySelector(`.${CodeBlockCollapser.CLASSES.THINKING_HEADER}`) - ) { - this.processBlock(pre); - } - } - } - - cleanup() { - for (const observer of this.observers) { - observer.disconnect(); - } - this.observers.clear(); - } -} - -new CodeBlockCollapser(); - -document.addEventListener("DOMContentLoaded", () => { - if (!window.codeBlockCollapser) { - window.codeBlockCollapser = new CodeBlockCollapser(); - } -}); diff --git a/extension/chrome/manifest.json b/extension/chrome/manifest.json deleted file mode 100644 index 3656176..0000000 --- a/extension/chrome/manifest.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "manifest_version": 3, - "name": "Thinking Claude", - "version": "2.1", - "description": "Let Claude think. Makes Claude's thinking process expandable and collapsible.", - "content_scripts": [ - { - "matches": ["https://*.claude.ai/*"], - "js": ["content.js"] - } - ] -} diff --git a/extension/firefox/content.js b/extension/firefox/content.js deleted file mode 100644 index 11fd47c..0000000 --- a/extension/firefox/content.js +++ /dev/null @@ -1,362 +0,0 @@ -class CodeBlockCollapser { - static SELECTORS = { - PRE: "pre", - CODE_CONTAINER: ".code-block__code", - MAIN_CONTAINER: ".relative.flex.flex-col", - THINKING_LABEL: ".text-text-300", - ORIGINAL_COPY_BTN: ".pointer-events-none", - CODE: "code", - }; - - static CLASSES = { - THINKING_HEADER: "thinking-header", - COPY_CONTAINER: - "from-bg-300/90 to-bg-300/70 pointer-events-auto rounded-md bg-gradient-to-b p-0.5 backdrop-blur-md", - COPY_BUTTON: - "flex flex-row items-center gap-1 rounded-md p-1 py-0.5 text-xs transition-opacity delay-100 hover:bg-bg-200 opacity-60 hover:opacity-100", - COPY_TEXT: "text-text-200 pr-0.5", - TOGGLE_BUTTON: "flex items-center text-text-500 hover:text-text-300", - TOGGLE_LABEL: "font-medium text-sm", - THINKING_ANIMATION: "thinking-animation", - }; - - static ANIMATION_STYLES = ` - @keyframes gradientWave { - 0% { background-position: 200% 50%; } - 100% { background-position: -200% 50%; } - } - - .thinking-animation { - background: linear-gradient( - 90deg, - rgba(156, 163, 175, 0.7) 0%, - rgba(209, 213, 219, 1) 25%, - rgba(156, 163, 175, 0.7) 50%, - rgba(209, 213, 219, 1) 75%, - rgba(156, 163, 175, 0.7) 100% - ); - background-size: 200% 100%; - animation: gradientWave 3s linear infinite; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - color: transparent; - } - `; - - static ICONS = { - COPY: ``, - TICK: ``, - ARROW: ``, - }; - - static TIMINGS = { - RETRY_DELAY: 1000, - MUTATION_DELAY: 100, - CHECK_INTERVAL: 2000, - COPY_FEEDBACK: 2000, - MAX_RETRIES: 10, - }; - - constructor() { - this.observers = new Set(); - this.injectStyles(); - this.initWithRetry(); - - window.addEventListener("unload", () => this.cleanup()); - } - - injectStyles() { - if (!document.getElementById("thinking-animation-styles")) { - const styleSheet = document.createElement("style"); - styleSheet.id = "thinking-animation-styles"; - styleSheet.textContent = CodeBlockCollapser.ANIMATION_STYLES; - document.head.appendChild(styleSheet); - } - } - - createElement(tag, className = "", innerHTML = "") { - const element = document.createElement(tag); - if (className) element.className = className; - if (innerHTML) element.innerHTML = innerHTML; - return element; - } - - createCopyButton() { - const container = this.createElement( - "div", - CodeBlockCollapser.CLASSES.COPY_CONTAINER - ); - const button = this.createElement( - "button", - CodeBlockCollapser.CLASSES.COPY_BUTTON - ); - const iconSpan = this.createElement( - "span", - "", - CodeBlockCollapser.ICONS.COPY - ); - const textSpan = this.createElement( - "span", - CodeBlockCollapser.CLASSES.COPY_TEXT, - "Copy" - ); - - button.append(iconSpan, textSpan); - container.appendChild(button); - - button.addEventListener("click", () => { - const codeText = button - .closest(CodeBlockCollapser.SELECTORS.PRE) - ?.querySelector(CodeBlockCollapser.SELECTORS.CODE)?.textContent; - - if (!codeText) return; - - navigator.clipboard - .writeText(codeText) - .then(() => { - iconSpan.innerHTML = CodeBlockCollapser.ICONS.TICK; - textSpan.textContent = "Copied!"; - - setTimeout(() => { - iconSpan.innerHTML = CodeBlockCollapser.ICONS.COPY; - textSpan.textContent = "Copy"; - }, CodeBlockCollapser.TIMINGS.COPY_FEEDBACK); - }) - .catch((error) => { - console.error("Failed to copy:", error); - }); - }); - - return container; - } - - createToggleButton(isStreaming = false) { - const button = this.createElement( - "button", - CodeBlockCollapser.CLASSES.TOGGLE_BUTTON - ); - const labelText = isStreaming ? "Thinking..." : "View thinking process"; - button.innerHTML = ` - ${CodeBlockCollapser.ICONS.ARROW} - ${labelText} - `; - return button; - } - - updateHeaderState(headerContainer, isStreaming) { - const toggleBtn = headerContainer.querySelector( - `.${CodeBlockCollapser.CLASSES.TOGGLE_BUTTON}` - ); - const label = toggleBtn.querySelector("span"); - - label.textContent = isStreaming ? "Thinking..." : "View thinking process"; - - if (isStreaming) { - label.classList.add(CodeBlockCollapser.CLASSES.THINKING_ANIMATION); - } else { - label.classList.remove(CodeBlockCollapser.CLASSES.THINKING_ANIMATION); - } - } - - setupCodeContainer(container, toggleBtn) { - if (!container) return; - - container.style.cssText = ` - transition: all 0.3s ease-in-out; - overflow-x: hidden; - overflow-y: auto; - max-height: 0; - opacity: 0; - padding: 0; - max-width: 100%; - display: block; - `; - - const codeElement = container.querySelector( - CodeBlockCollapser.SELECTORS.CODE - ); - if (codeElement) { - codeElement.style.cssText = ` - white-space: pre-wrap !important; - word-break: break-word !important; - overflow-wrap: break-word !important; - display: block !important; - max-width: 100% !important; - `; - } - - toggleBtn.addEventListener("click", () => { - const shouldToggleOpen = container.style.maxHeight === "0px"; - const arrow = toggleBtn.querySelector("svg"); - const label = toggleBtn.querySelector("span"); - - container.style.maxHeight = shouldToggleOpen ? "50vh" : "0"; - container.style.opacity = shouldToggleOpen ? "1" : "0"; - container.style.padding = shouldToggleOpen ? "1em" : "0"; - - arrow.style.transform = `rotate(${shouldToggleOpen ? 180 : 0}deg)`; - if ( - !label.classList.contains(CodeBlockCollapser.CLASSES.THINKING_ANIMATION) - ) { - label.textContent = shouldToggleOpen - ? "Hide thinking process" - : "View thinking process"; - } - }); - } - - processBlock(pre) { - const headerContainer = this.createElement( - "div", - CodeBlockCollapser.CLASSES.THINKING_HEADER - ); - headerContainer.style.cssText = - "display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: var(--bg-300);"; - - const isStreaming = pre.closest('[data-is-streaming="true"]') !== null; - const toggleBtn = this.createToggleButton(isStreaming); - const copyBtn = this.createCopyButton(); - - headerContainer.append(toggleBtn, copyBtn); - - const codeContainer = pre.querySelector( - CodeBlockCollapser.SELECTORS.CODE_CONTAINER - ); - this.setupCodeContainer(codeContainer, toggleBtn); - - const mainContainer = pre.querySelector( - CodeBlockCollapser.SELECTORS.MAIN_CONTAINER - ); - if (mainContainer) { - const codeParent = pre.querySelector( - CodeBlockCollapser.SELECTORS.CODE_CONTAINER - )?.parentElement; - if (codeParent) { - mainContainer.insertBefore(headerContainer, codeParent); - } - - const streamingContainer = pre.closest("[data-is-streaming]"); - if (streamingContainer) { - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if ( - mutation.type === "attributes" && - mutation.attributeName === "data-is-streaming" - ) { - const isStreamingNow = - streamingContainer.getAttribute("data-is-streaming") === "true"; - this.updateHeaderState(headerContainer, isStreamingNow); - } - } - }); - - observer.observe(streamingContainer, { - attributes: true, - attributeFilter: ["data-is-streaming"], - }); - - this.observers.add(observer); - - new MutationObserver((mutations) => { - if (!document.contains(streamingContainer)) { - observer.disconnect(); - this.observers.delete(observer); - } - }).observe(document.body, { childList: true, subtree: true }); - } - - for (const selector of [ - CodeBlockCollapser.SELECTORS.THINKING_LABEL, - CodeBlockCollapser.SELECTORS.ORIGINAL_COPY_BTN, - ]) { - const element = pre.querySelector(selector); - if (element) element.style.display = "none"; - } - } - } - - initWithRetry(retryCount = 0) { - if (retryCount >= CodeBlockCollapser.TIMINGS.MAX_RETRIES) return; - - const blocks = document.querySelectorAll(CodeBlockCollapser.SELECTORS.PRE); - if (blocks.length === 0) { - setTimeout( - () => this.initWithRetry(retryCount + 1), - CodeBlockCollapser.TIMINGS.RETRY_DELAY - ); - return; - } - - this.processExistingBlocks(); - this.setupObserver(); - this.setupPeriodicCheck(); - } - - setupObserver() { - const observer = new MutationObserver((mutations) => { - let shouldProcess = false; - for (const mutation of mutations) { - if ( - mutation.addedNodes.length > 0 || - (mutation.type === "attributes" && - mutation.attributeName === "data-is-streaming") - ) { - shouldProcess = true; - } - } - - if (shouldProcess) { - this.processExistingBlocks(); - } - }); - - observer.observe(document.body, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ["data-is-streaming"], - }); - - this.observers.add(observer); - } - - setupPeriodicCheck() { - setInterval(() => { - this.processExistingBlocks(); - }, CodeBlockCollapser.TIMINGS.CHECK_INTERVAL); - } - - processExistingBlocks() { - for (const pre of document.querySelectorAll( - CodeBlockCollapser.SELECTORS.PRE - )) { - const header = pre.querySelector( - CodeBlockCollapser.SELECTORS.THINKING_LABEL - ); - if ( - header?.textContent.trim() === "thinking" && - !pre.querySelector(`.${CodeBlockCollapser.CLASSES.THINKING_HEADER}`) - ) { - this.processBlock(pre); - } - } - } - - cleanup() { - for (const observer of this.observers) { - observer.disconnect(); - } - this.observers.clear(); - } -} - -new CodeBlockCollapser(); - -document.addEventListener("DOMContentLoaded", () => { - if (!window.codeBlockCollapser) { - window.codeBlockCollapser = new CodeBlockCollapser(); - } -}); diff --git a/extension/firefox/manifest.json b/extension/firefox/manifest.json deleted file mode 100644 index 88a64e2..0000000 --- a/extension/firefox/manifest.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "manifest_version": 2, - "name": "Thinking Claude", - "version": "2.1", - "description": "Let Claude think. Makes Claude's thinking process expandable and collapsible.", - - "content_scripts": [ - { - "matches": ["https://*.claude.ai/*"], - "js": ["content.js"] - } - ], - - "browser_specific_settings": { - "gecko": { - "strict_min_version": "58.0" - } - } -} From 0e3c27efb23d17c759833a6f682f2bba44aadde2 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Fri, 22 Nov 2024 23:59:20 +0800 Subject: [PATCH 03/26] ci: add GitHub Actions workflow for Chrome extension --- extensions/chrome_v1/.github/workflows/ci.yml | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 extensions/chrome_v1/.github/workflows/ci.yml diff --git a/extensions/chrome_v1/.github/workflows/ci.yml b/extensions/chrome_v1/.github/workflows/ci.yml new file mode 100644 index 0000000..588b1c7 --- /dev/null +++ b/extensions/chrome_v1/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: chrome-extension CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint-and-format: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Run ESLint + run: bun run lint + + - name: Check formatting + run: bunx prettier --check . + + - name: Run markdown lint + run: bunx markdownlint-cli2 "**/*.md" --config .markdownlint-cli2.jsonc + + test-and-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Run tests + run: bun test + + - name: Build extension + run: bun run build + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: chrome-extension + path: dist/ + if-no-files-found: error From 49e60be937c67b28d200e88e124fadb152ee8b00 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 00:03:00 +0800 Subject: [PATCH 04/26] ci: move GitHub Actions workflow to root directory and configure for chrome extension subdirectory --- .../workflows/chrome-extension-ci.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) rename extensions/chrome_v1/.github/workflows/ci.yml => .github/workflows/chrome-extension-ci.yml (86%) diff --git a/extensions/chrome_v1/.github/workflows/ci.yml b/.github/workflows/chrome-extension-ci.yml similarity index 86% rename from extensions/chrome_v1/.github/workflows/ci.yml rename to .github/workflows/chrome-extension-ci.yml index 588b1c7..3208b51 100644 --- a/extensions/chrome_v1/.github/workflows/ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -3,8 +3,16 @@ name: chrome-extension CI on: push: branches: [main] + paths: + - "extensions/chrome_v1/**" pull_request: branches: [main] + paths: + - "extensions/chrome_v1/**" + +defaults: + run: + working-directory: ./extensions/chrome_v1 jobs: lint-and-format: @@ -56,5 +64,5 @@ jobs: uses: actions/upload-artifact@v3 with: name: chrome-extension - path: dist/ + path: extensions/chrome_v1/dist/ if-no-files-found: error From d41aeee3e1fa652c8bb06ec44bff62104fd51073 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 00:07:07 +0800 Subject: [PATCH 05/26] ci: update markdown lint path to explicitly target chrome extension directory --- .github/workflows/chrome-extension-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/chrome-extension-ci.yml b/.github/workflows/chrome-extension-ci.yml index 3208b51..065f18a 100644 --- a/.github/workflows/chrome-extension-ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -39,7 +39,7 @@ jobs: run: bunx prettier --check . - name: Run markdown lint - run: bunx markdownlint-cli2 "**/*.md" --config .markdownlint-cli2.jsonc + run: bunx markdownlint-cli2 "./**/*.md" --config .markdownlint-cli2.jsonc test-and-build: runs-on: ubuntu-latest From 095a4119086dc366d93146bc4595a40c75025df9 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 00:23:12 +0800 Subject: [PATCH 06/26] chore: format and lint files, update CI workflow --- .github/workflows/chrome-extension-ci.yml | 9 ++++----- extensions/chrome_v1/webpack/webpack.common.js | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/chrome-extension-ci.yml b/.github/workflows/chrome-extension-ci.yml index 065f18a..675a7ad 100644 --- a/.github/workflows/chrome-extension-ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -32,16 +32,15 @@ jobs: - name: Install dependencies run: bun install - - name: Run ESLint - run: bun run lint - - - name: Check formatting - run: bunx prettier --check . + - name: Run ESLint and format fix + run: bun run fix - name: Run markdown lint + # DON't TOUCH THIS LINE BELOW # run: bunx markdownlint-cli2 "./**/*.md" --config .markdownlint-cli2.jsonc test-and-build: + needs: lint-and-format runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/extensions/chrome_v1/webpack/webpack.common.js b/extensions/chrome_v1/webpack/webpack.common.js index cfa9f5e..fa91e35 100644 --- a/extensions/chrome_v1/webpack/webpack.common.js +++ b/extensions/chrome_v1/webpack/webpack.common.js @@ -1,5 +1,6 @@ import path from "path" import { fileURLToPath } from "url" + import CopyPlugin from "copy-webpack-plugin" const __filename = fileURLToPath(import.meta.url) From 419af0138777249e8e0dbc24a27385335619aeaf Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 00:36:42 +0800 Subject: [PATCH 07/26] chore: add husky git hooks and root configuration - Add pre-commit hook for chrome_v1 extension - Add post-commit hook for chrome_v1 extension - Add root .gitignore - Add root package.json with husky config --- .gitignore | 61 +++++++++++++++++++++++++++++++++++ .husky/post-commit-extensions | 12 +++++++ .husky/pre-commit-extensions | 11 +++++++ package.json | 11 +++++++ 4 files changed, 95 insertions(+) create mode 100644 .gitignore create mode 100644 .husky/post-commit-extensions create mode 100644 .husky/pre-commit-extensions create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ad9107 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Dependencies +node_modules/ +.pnp/ +.pnp.js +bun.lockb + +# Testing +coverage/ +.nyc_output/ + +# Production & Build files +dist/ +build/ +*.tsbuildinfo + +# Development & IDE +.DS_Store +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +.idea/ +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json + +# Debug logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +debug.log +*.log + +# Cache directories +.npm/ +.eslintcache +.stylelintcache +.prettiercache +.cache/ + +# Chrome Extension specific +*.crx +*.pem +*.zip + +# Temporary files +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/.husky/post-commit-extensions b/.husky/post-commit-extensions new file mode 100644 index 0000000..2f959e2 --- /dev/null +++ b/.husky/post-commit-extensions @@ -0,0 +1,12 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# DON'T TOUCH THIS FILE IF YOU DON'T KNOW WHAT YOU ARE DOING + +# Check if the last commit was in extensions/chrome_v1 +if git log -1 --name-only --pretty=format: | grep "^extensions/chrome_v1" > /dev/null; then + echo "Changes detected in chrome_v1, running git fetch..." + git fetch +fi + +# TODO: config this for other extensions too diff --git a/.husky/pre-commit-extensions b/.husky/pre-commit-extensions new file mode 100644 index 0000000..36e9335 --- /dev/null +++ b/.husky/pre-commit-extensions @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +# DON'T TOUCH THIS FILE IF YOU DON'T KNOW WHAT YOU ARE DOING + +# Check if there are changes in extensions directory +if git diff --cached --name-only | grep "^extensions/chrome_v1" > /dev/null; then + cd extensions/chrome_v1 && bun run fix && bun run lint:staged +fi + +# TODO: config this for other extensions too \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..eec2545 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "thinking-claude-root", + "private": true, + "scripts": { + "prepare": "husky" + }, + "devDependencies": {}, + "dependencies": { + "husky": "^9.1.7" + } +} From 36433005c9b5b62f47e8eeb7d9ba501d867a82dd Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 00:38:31 +0800 Subject: [PATCH 08/26] fix: rename husky hooks to correct filenames --- .husky/{post-commit-extensions => post-commit} | 0 .husky/{pre-commit-extensions => pre-commit} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .husky/{post-commit-extensions => post-commit} (100%) rename .husky/{pre-commit-extensions => pre-commit} (100%) diff --git a/.husky/post-commit-extensions b/.husky/post-commit similarity index 100% rename from .husky/post-commit-extensions rename to .husky/post-commit diff --git a/.husky/pre-commit-extensions b/.husky/pre-commit similarity index 100% rename from .husky/pre-commit-extensions rename to .husky/pre-commit From 8a8ee82eff5645e9d99913af389996d6f69c12c1 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 00:39:18 +0800 Subject: [PATCH 09/26] test: add test file to verify husky hooks --- extensions/chrome_v1/test.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 extensions/chrome_v1/test.txt diff --git a/extensions/chrome_v1/test.txt b/extensions/chrome_v1/test.txt new file mode 100644 index 0000000..859c010 --- /dev/null +++ b/extensions/chrome_v1/test.txt @@ -0,0 +1 @@ +Test file to verify husky hooks From 862f967f99cf06ea7501b1818c1cdd5b87567b2f Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 00:55:46 +0800 Subject: [PATCH 10/26] test: verify husky hooks configuration --- .husky/post-commit | 5 +++-- .husky/pre-commit | 15 +++++++++++++-- extensions/chrome_v1/.husky/post-merge | 4 ---- extensions/chrome_v1/.husky/pre-commit | 7 ------- package.json | 3 +-- 5 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 extensions/chrome_v1/.husky/post-merge delete mode 100644 extensions/chrome_v1/.husky/pre-commit diff --git a/.husky/post-commit b/.husky/post-commit index 2f959e2..0433274 100644 --- a/.husky/post-commit +++ b/.husky/post-commit @@ -3,10 +3,11 @@ # DON'T TOUCH THIS FILE IF YOU DON'T KNOW WHAT YOU ARE DOING -# Check if the last commit was in extensions/chrome_v1 +echo "Checking for changes in chrome_v1..." if git log -1 --name-only --pretty=format: | grep "^extensions/chrome_v1" > /dev/null; then - echo "Changes detected in chrome_v1, running git fetch..." + echo "Changes detected, fetching..." git fetch + echo "Fetch complete" fi # TODO: config this for other extensions too diff --git a/.husky/pre-commit b/.husky/pre-commit index 36e9335..2629ed9 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -4,8 +4,19 @@ # DON'T TOUCH THIS FILE IF YOU DON'T KNOW WHAT YOU ARE DOING # Check if there are changes in extensions directory -if git diff --cached --name-only | grep "^extensions/chrome_v1" > /dev/null; then - cd extensions/chrome_v1 && bun run fix && bun run lint:staged +if ! git diff --cached --name-only | grep "^extensions/chrome_v1" > /dev/null; then + echo "No changes in chrome_v1" + exit 0 +fi + +if ! cd extensions/chrome_v1; then + echo "Failed to change directory" + exit 1 +fi + +if ! bun run fix && bun run lint:staged; then + echo "Fix or lint failed" + exit 1 fi # TODO: config this for other extensions too \ No newline at end of file diff --git a/extensions/chrome_v1/.husky/post-merge b/extensions/chrome_v1/.husky/post-merge deleted file mode 100644 index 4ff4400..0000000 --- a/extensions/chrome_v1/.husky/post-merge +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -git fetch diff --git a/extensions/chrome_v1/.husky/pre-commit b/extensions/chrome_v1/.husky/pre-commit deleted file mode 100644 index 28248d5..0000000 --- a/extensions/chrome_v1/.husky/pre-commit +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -cd "$(dirname "$0")/.." - -bun run fix -bun run lint-staged \ No newline at end of file diff --git a/package.json b/package.json index eec2545..aa3d76f 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,7 @@ "scripts": { "prepare": "husky" }, - "devDependencies": {}, - "dependencies": { + "devDependencies": { "husky": "^9.1.7" } } From 1b668ebdb4698892de3e08cd66e6ab9e27efc4df Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 00:59:23 +0800 Subject: [PATCH 11/26] refactor: remove husky deprecated lines and restore error handling --- .husky/post-commit | 1 - .husky/pre-commit | 1 - 2 files changed, 2 deletions(-) diff --git a/.husky/post-commit b/.husky/post-commit index 0433274..306ad92 100644 --- a/.husky/post-commit +++ b/.husky/post-commit @@ -1,5 +1,4 @@ #!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" # DON'T TOUCH THIS FILE IF YOU DON'T KNOW WHAT YOU ARE DOING diff --git a/.husky/pre-commit b/.husky/pre-commit index 2629ed9..9291da1 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,4 @@ #!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" # DON'T TOUCH THIS FILE IF YOU DON'T KNOW WHAT YOU ARE DOING From 4b223c0c49b1cf55996d0a1ded10ba901ef6c3cd Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 01:10:07 +0800 Subject: [PATCH 12/26] ci: add automatic GitHub release creation for easy extension distribution --- .github/workflows/chrome-extension-ci.yml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/chrome-extension-ci.yml b/.github/workflows/chrome-extension-ci.yml index 675a7ad..7779d4e 100644 --- a/.github/workflows/chrome-extension-ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -42,6 +42,8 @@ jobs: test-and-build: needs: lint-and-format runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v4 @@ -59,9 +61,19 @@ jobs: - name: Build extension run: bun run build - - name: Upload build artifacts - uses: actions/upload-artifact@v3 + - name: Zip Extension + run: zip -r chrome-extension.zip dist/ + + - name: Generate version + id: version + run: echo "version=$(date +'%Y.%m.%d.%H%M')" >> $GITHUB_OUTPUT + + - name: Create Release + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: softprops/action-gh-release@v1 with: - name: chrome-extension - path: extensions/chrome_v1/dist/ - if-no-files-found: error + name: Release ${{ steps.version.outputs.version }} + tag_name: v${{ steps.version.outputs.version }} + files: extensions/chrome_v1/chrome-extension.zip + generate_release_notes: true + token: ${{ secrets.GITHUB_TOKEN }} From e491e2ded56e720e9958a3c4ce17b1717b384673 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 01:16:40 +0800 Subject: [PATCH 13/26] ci: add automatic version bumping and improve release workflow --- .github/workflows/chrome-extension-ci.yml | 32 ++++++++++++++++++----- extensions/chrome_v1/test.txt | 2 +- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.github/workflows/chrome-extension-ci.yml b/.github/workflows/chrome-extension-ci.yml index 7779d4e..027894b 100644 --- a/.github/workflows/chrome-extension-ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -64,16 +64,36 @@ jobs: - name: Zip Extension run: zip -r chrome-extension.zip dist/ - - name: Generate version - id: version - run: echo "version=$(date +'%Y.%m.%d.%H%M')" >> $GITHUB_OUTPUT - + # Automatically increments the patch version (e.g., 1.0.0 -> 1.0.1) + # and creates a release + - name: Check existing tag + id: check_tag + run: | + current_version=$(node -p "require('./package.json').version") + if git ls-remote --tags origin refs/tags/v$current_version >/dev/null; then + # If tag exists, increment patch version + IFS='.' read -r major minor patch <<< "$current_version" + new_version="$major.$minor.$((patch + 1))" + echo "version=$new_version" >> $GITHUB_OUTPUT + echo "version_changed=true" >> $GITHUB_OUTPUT + # Update package.json with new version + sed -i "s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/" package.json + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git add package.json + git commit -m "chore: bump version to $new_version [skip ci]" + git push + else + echo "version=$current_version" >> $GITHUB_OUTPUT + echo "version_changed=false" >> $GITHUB_OUTPUT + fi - name: Create Release if: github.event_name == 'push' && github.ref == 'refs/heads/main' uses: softprops/action-gh-release@v1 with: - name: Release ${{ steps.version.outputs.version }} - tag_name: v${{ steps.version.outputs.version }} + name: Chrome Extension v${{ steps.check_tag.outputs.version }} + tag_name: v${{ steps.check_tag.outputs.version }} files: extensions/chrome_v1/chrome-extension.zip generate_release_notes: true token: ${{ secrets.GITHUB_TOKEN }} + fail_on_unmatched_files: true diff --git a/extensions/chrome_v1/test.txt b/extensions/chrome_v1/test.txt index 859c010..04a9657 100644 --- a/extensions/chrome_v1/test.txt +++ b/extensions/chrome_v1/test.txt @@ -1 +1 @@ -Test file to verify husky hooks +Test file to verify github actions From ad2d7121e0ae31c29d976ecd9df491be4a6e23e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 22 Nov 2024 17:19:16 +0000 Subject: [PATCH 14/26] chore: bump version to 1.0.1 [skip ci] --- extensions/chrome_v1/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/chrome_v1/package.json b/extensions/chrome_v1/package.json index 6b0e400..8bbc88f 100644 --- a/extensions/chrome_v1/package.json +++ b/extensions/chrome_v1/package.json @@ -1,6 +1,6 @@ { "name": "thinking-claude", - "version": "1.0.0", + "version": "1.0.1", "description": "Chrome extension for letting Claude think like a real human", "type": "module", "scripts": { From 6c258f349bd4849549e446d7e2edfeffc98c0976 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 01:28:33 +0800 Subject: [PATCH 15/26] feat: add git pull to post-commit hook for auto-sync --- .husky/post-commit | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.husky/post-commit b/.husky/post-commit index 306ad92..0f95dfc 100644 --- a/.husky/post-commit +++ b/.husky/post-commit @@ -7,6 +7,9 @@ if git log -1 --name-only --pretty=format: | grep "^extensions/chrome_v1" > /dev echo "Changes detected, fetching..." git fetch echo "Fetch complete" + echo "Pulling changes..." + git pull + echo "Pull complete" fi # TODO: config this for other extensions too From f4a158aec7f30d5a3434785f56e82694d2d81a41 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 15:13:09 +0800 Subject: [PATCH 16/26] ci: update github actions for chrome to keep both versions in sync for the package.json and manifest.json --- .github/workflows/chrome-extension-ci.yml | 4 +++- extensions/chrome_v1/{src => public}/manifest.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) rename extensions/chrome_v1/{src => public}/manifest.json (96%) diff --git a/.github/workflows/chrome-extension-ci.yml b/.github/workflows/chrome-extension-ci.yml index 027894b..8ea1869 100644 --- a/.github/workflows/chrome-extension-ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -78,9 +78,11 @@ jobs: echo "version_changed=true" >> $GITHUB_OUTPUT # Update package.json with new version sed -i "s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/" package.json + # Update manifest.json with new version + sed -i "s/\"version\": \"$current_version\"/\"version\": \"$new_version\"/" public/manifest.json git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" - git add package.json + git add package.json public/manifest.json git commit -m "chore: bump version to $new_version [skip ci]" git push else diff --git a/extensions/chrome_v1/src/manifest.json b/extensions/chrome_v1/public/manifest.json similarity index 96% rename from extensions/chrome_v1/src/manifest.json rename to extensions/chrome_v1/public/manifest.json index 9775e99..58617b1 100644 --- a/extensions/chrome_v1/src/manifest.json +++ b/extensions/chrome_v1/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Thinking Claude", - "version": "1.1.0", + "version": "1.0.1", "description": "Chrome extension for letting Claude think like a real human", "content_scripts": [ { From fd8b1547e04a1f88489beb528f35f863f436df12 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 Nov 2024 07:20:44 +0000 Subject: [PATCH 17/26] chore: bump version to 1.0.2 [skip ci] --- extensions/chrome_v1/package.json | 2 +- extensions/chrome_v1/public/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/chrome_v1/package.json b/extensions/chrome_v1/package.json index 8bbc88f..60cd6f0 100644 --- a/extensions/chrome_v1/package.json +++ b/extensions/chrome_v1/package.json @@ -1,6 +1,6 @@ { "name": "thinking-claude", - "version": "1.0.1", + "version": "1.0.2", "description": "Chrome extension for letting Claude think like a real human", "type": "module", "scripts": { diff --git a/extensions/chrome_v1/public/manifest.json b/extensions/chrome_v1/public/manifest.json index 58617b1..86ac1b2 100644 --- a/extensions/chrome_v1/public/manifest.json +++ b/extensions/chrome_v1/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Thinking Claude", - "version": "1.0.1", + "version": "1.0.2", "description": "Chrome extension for letting Claude think like a real human", "content_scripts": [ { From 2811a1cb3e25b72ebf765f7aaa2745ec0be01d2a Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 15:55:00 +0800 Subject: [PATCH 18/26] docs: add deprecation notice for chrome_v0 and update documentation - Add README.md to chrome_v0 with deprecation notice - Update chrome_v1 documentation and CHANGELOG - Reset version numbers for new implementation - Update markdown linting rules --- extensions/chrome_v0/README.md | 38 +++ extensions/chrome_v1/.markdownlint-cli2.jsonc | 12 +- extensions/chrome_v1/CHANGELOG.md | 35 +++ extensions/chrome_v1/README.md | 238 ++++++++++++++++++ extensions/chrome_v1/package.json | 2 +- extensions/chrome_v1/public/manifest.json | 2 +- 6 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 extensions/chrome_v0/README.md create mode 100644 extensions/chrome_v1/CHANGELOG.md create mode 100644 extensions/chrome_v1/README.md diff --git a/extensions/chrome_v0/README.md b/extensions/chrome_v0/README.md new file mode 100644 index 0000000..f700cc2 --- /dev/null +++ b/extensions/chrome_v0/README.md @@ -0,0 +1,38 @@ +# ⚠️ Deprecated: Thinking Claude Chrome Extension (v0) + +> **Important**: This version of the Chrome extension (`chrome_v0`) has been deprecated and is no longer being maintained. Please use our new version for continued support and improvements. + +## Migration Notice 🔄 + +### Why are we deprecating this version? + +We've completely rewritten the extension with: + +- Modern architecture and tech stack +- Improved performance +- Better maintainability +- Enhanced developer experience + +### How to Update? + +Please switch to our new version (`chrome_v1`): + +1. Uninstall this version (`chrome_v0`) from Chrome +2. Visit our [Latest Releases](https://github.com/richards199999/Thinking-Claude/releases) or refer to the [README.md](https://github.com/richards199999/Thinking-Claude/tree/main/extensions/chrome_v1/README.md) in `extensions/chrome_v1` +3. Download and install the new version + +For installation instructions and documentation, see: + +- [New Extension Documentation](https://github.com/richards199999/Thinking-Claude/tree/main/extensions/chrome_v1/README.md) +- [Changelog](https://github.com/richards199999/Thinking-Claude/tree/main/extensions/chrome_v1/CHANGELOG.md) + +## Support + +- This version will remain available for historical reference +- No new features or bug fixes will be added +- For any issues, please use the new version +- Report problems with the new version in our [GitHub Issues](https://github.com/richards199999/Thinking-Claude/issues) + +--- + +Thank you for using Thinking Claude! We're committed to providing the best possible experience, which is why we've moved to a new, improved version. diff --git a/extensions/chrome_v1/.markdownlint-cli2.jsonc b/extensions/chrome_v1/.markdownlint-cli2.jsonc index ed6ed29..15db2a5 100644 --- a/extensions/chrome_v1/.markdownlint-cli2.jsonc +++ b/extensions/chrome_v1/.markdownlint-cli2.jsonc @@ -1,11 +1,19 @@ { "config": { "default": true, + // MD013/line-length : Line length "MD013": false, + // MD033/no-inline-html : Inline HTML "MD033": false, + // MD041/first-line-heading/first-line-h1 : First line in a file should be a top-level heading "MD041": false, - "MD032": true + // MD032/blanks-around-lists : Lists should be surrounded by blank lines + "MD032": true, + // MD024/no-duplicate-heading : Multiple headings with the same content + "MD024": false, + // MD040/fenced-code-language : Fenced code blocks should have a language specified + "MD040": false, }, "ignores": ["node_modules/**", "dist/**", ".git/**"], - "globs": ["**/*.md"] + "globs": ["**/*.md"], } diff --git a/extensions/chrome_v1/CHANGELOG.md b/extensions/chrome_v1/CHANGELOG.md new file mode 100644 index 0000000..a4bd0d8 --- /dev/null +++ b/extensions/chrome_v1/CHANGELOG.md @@ -0,0 +1,35 @@ +# Changelog + +## 2024-11-23 + +### Important Notice 🔄 + +The original Chrome extension (now referred to as `chrome_v0`) has been deprecated and its development has been discontinued. This changelog tracks the new rewritten version (`chrome_v1`) which offers improved architecture, better performance, and enhanced maintainability. + +### Changed + +- Complete rewrite of the extension with modern tech stack +- Improved CI/CD pipeline with automatic version syncing +- Added git pull to post-commit hook for auto-sync + +## 2024-11-22 + +### Added + +- Initial project structure and configuration setup for complete rewrite +- Deprecated old extension (`chrome_v0`) and started fresh development +- GitHub Actions workflow for Chrome extension +- Husky git hooks for code quality +- Automatic version bumping and release workflow +- Automatic GitHub release creation + +### Changed + +- Reorganized extension directory structure +- Improved development environment configuration + +### Technical + +- Set up manifest.json with required permissions and content scripts +- Configured extension icons +- Implemented storage permission for future feature development diff --git a/extensions/chrome_v1/README.md b/extensions/chrome_v1/README.md new file mode 100644 index 0000000..2e11f4a --- /dev/null +++ b/extensions/chrome_v1/README.md @@ -0,0 +1,238 @@ +# Thinking Claude Chrome Extension + +A Chrome extension that enhances Claude's thinking process, making it more human-like and transparent. + +> **Important Notice**: The original Chrome extension (`chrome_v0`) has been deprecated. This is the new rewritten version (`chrome_v1`) with improved architecture and modern tech stack. If you're using the old version, please update to this new version for better performance and continued support. + +## How to Use 🚀 + +### Option 1: Direct Installation (Recommended) + +1. **Download the Extension** + + - Go to [Latest Releases](https://github.com/richards199999/Thinking-Claude/releases) + - Download the latest version (e.g., `thinking-claude-v1.0.2.zip`) + - Extract the ZIP file + +2. **Install in Chrome** + + - Open Chrome and go to `chrome://extensions/` + - Enable "Developer mode" in the top right + - Click "Load unpacked" + - Select the `dist` folder in the extracted folder + +3. **Start Using** + - Visit [Claude.ai](https://claude.ai) + - Start a new conversation or refresh an existing one + - The extension will automatically enhance Claude's thinking process + +### Option 2: Build Locally (For Development) + +1. **Quick Setup** + + ```bash + # Clone the repository + git clone https://github.com/richards199999/Thinking-Claude.git + cd Thinking-Claude/extensions/chrome_v1 + + # Install dependencies + bun install + + # Build the extension + bun run build + ``` + +2. **Load in Chrome** + + - Open Chrome and go to `chrome://extensions/` + - Enable "Developer mode" in the top right + - Click "Load unpacked" + - Select the `dist` folder (created after building) + +3. **Development Mode** + + ```bash + # Start development server with hot reload + bun run start + + # Watch for changes + bun run watch + ``` + +## Tech Stack 🛠️ + +### Core Technologies + +- **Language & Type Safety** + + - [TypeScript](https://www.typescriptlang.org/) - Strongly typed programming language + - [ESLint](https://eslint.org/) - Code linting and standards + - [Prettier](https://prettier.io/) - Code formatting + +- **Frontend** + - [React](https://react.dev/) - UI library + - [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS framework + - [shadcn/ui](https://ui.shadcn.com/) - Best UI components + - [Chrome Extension API](https://developer.chrome.com/docs/extensions/) - Browser extension development + +### Development Tools + +- **Build & Bundle** + + - [Bun](https://bun.sh) - JavaScript all-in-one toolkit + - [Webpack](https://webpack.js.org/) - Module bundler + - [PostCSS](https://postcss.org/) - CSS processing + +- **Testing & Quality** + + - [Vitest](https://vitest.dev/) - Unit testing framework + - [Husky](https://typicode.github.io/husky/) - Git hooks + - [lint-staged](https://github.com/okonet/lint-staged) - Staged files linter + +- **Development Environment** + - [Node.js](https://nodejs.org/) - JavaScript runtime + - [Chrome DevTools](https://developer.chrome.com/docs/devtools/) - Browser debugging + +## Getting Started with development 🚀 + +### What You'll Need + +Required tools: + +- [Bun](https://bun.sh) - A fast all-in-one JavaScript runtime & toolkit +- [Node.js](https://nodejs.org/) (v18 or higher) - JavaScript runtime environment +- [Git](https://git-scm.com/downloads) - For version control +- [Google Chrome](https://www.google.com/chrome/) - The browser we're building for + +This extension uses: + +- [TypeScript](https://www.typescriptlang.org/) - Type-safe JavaScript +- [React](https://react.dev/) - UI framework +- [Tailwind CSS](https://tailwindcss.com/) - Utility-first CSS framework +- [Webpack](https://webpack.js.org/) - Module bundler + +#### Installing Node.js + +1. Download Node.js from [nodejs.org](https://nodejs.org/) +2. Choose the LTS (Long Term Support) version +3. Run the installer +4. Verify installation: + + ```bash + node --version + npm --version + ``` + +#### Installing Bun + +Bun is required to run this project. Here's how to install it: + +**Windows Users:** + +1. First, install Windows Subsystem for Linux (WSL): + + ```powershell + # Open PowerShell as Administrator and run: + wsl --install + ``` + + After installation, restart your computer. + +2. Install Bun through WSL: + + ```bash + # Open WSL terminal and run: + curl -fsSL https://bun.sh/install | bash + ``` + +**macOS or Linux Users:** + +```bash +# Open terminal and run: +curl -fsSL https://bun.sh/install | bash +``` + +To verify installation, run: + +```bash +bun --version +``` + +### Setting Up Your Development Environment + +1. Get the code: + + ```bash + # Clone this repository to your computer + git clone https://github.com/richards199999/Thinking-Claude.git + + # Go to the extension directory + cd extensions/chrome_v1 + + # Install project dependencies + bun install + ``` + +### Development Commands + +Here are the main commands you'll use during development: + +```bash +# Build the extension for production +bun run build + +# Start development mode with auto-reload +bun run start + +# Watch for file changes +bun run watch + +# Run tests +bun run test + +# Fix code style and formatting +bun run fix +``` + +### Installing the Extension in Chrome + +1. Open Chrome and type `chrome://extensions/` in the address bar +2. Turn on "Developer mode" using the switch in the top right corner +3. Click "Load unpacked" and select the `dist (visible after running bun run build)` folder from this project + +## Project Organization 📁 + +``` +chrome_v1/ +├── src/ # Your source code goes here +├── public/ # Built extension (created after running build) +│ ├── manifest.json # Extension configuration +│ ├── content.js # Main extension script +│ └── icons/ # Extension icons +├── package.json # Project configuration and scripts +└── CHANGELOG.md # Version history and changes +``` + +## Development Workflow 🔄 + +### Code Quality Tools + +We use several tools to maintain code quality: + +- **Husky**: Automatically checks your code before commits +- **ESLint**: Finds and fixes JavaScript problems +- **Prettier**: Formats your code consistently + +### Continuous Integration + +Our GitHub Actions setup automatically: + +- Builds the extension +- Updates version numbers +- Creates new releases + +## Need Help? 🤔 + +- Check the [CHANGELOG.md](./CHANGELOG.md) for recent updates +- Visit our [GitHub Issues](https://github.com/richards199999/Thinking-Claude/issues) for known problems or to report new ones +- Feel free to ask questions in our GitHub Discussions diff --git a/extensions/chrome_v1/package.json b/extensions/chrome_v1/package.json index 60cd6f0..6b0e400 100644 --- a/extensions/chrome_v1/package.json +++ b/extensions/chrome_v1/package.json @@ -1,6 +1,6 @@ { "name": "thinking-claude", - "version": "1.0.2", + "version": "1.0.0", "description": "Chrome extension for letting Claude think like a real human", "type": "module", "scripts": { diff --git a/extensions/chrome_v1/public/manifest.json b/extensions/chrome_v1/public/manifest.json index 86ac1b2..c55a9d1 100644 --- a/extensions/chrome_v1/public/manifest.json +++ b/extensions/chrome_v1/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Thinking Claude", - "version": "1.0.2", + "version": "1.0.0", "description": "Chrome extension for letting Claude think like a real human", "content_scripts": [ { From b260b6f8cb4b6c15bc5111d376c98975ad8c2678 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 15:59:12 +0800 Subject: [PATCH 19/26] refactor: simplify pre-commit hook to avoid redundant formatting --- .husky/pre-commit | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 9291da1..1debef7 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -13,8 +13,8 @@ if ! cd extensions/chrome_v1; then exit 1 fi -if ! bun run fix && bun run lint:staged; then - echo "Fix or lint failed" +if ! bun run lint:staged; then + echo "Lint failed" exit 1 fi From 7bbe93f5bf164d52a39f183fab98c2d0aad03639 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 22:15:27 +0800 Subject: [PATCH 20/26] BREAKING CHANGE: reconstruct chrome extension architecture - Rename project folder from chrome_v1 to chrome for better generalization - Complete rebuild of extension structure with modular design - Split content.js into dedicated manager-based modules - Implement separate managers for DOM, events, styles, and UI - Establish new project organization for better scalability --- .github/workflows/chrome-extension-ci.yml | 8 +- .husky/post-commit | 4 +- .husky/pre-commit | 6 +- extensions/{chrome_v1 => chrome}/.gitignore | 0 .../.markdownlint-cli2.jsonc | 0 .../{chrome_v1 => chrome}/.markdownlintignore | 0 extensions/{chrome_v1 => chrome}/CHANGELOG.md | 2 +- extensions/{chrome_v1 => chrome}/README.md | 8 +- .../{chrome_v1 => chrome}/components.json | 0 extensions/chrome/eslint.config.cjs | 84 +++++++++++ extensions/{chrome_v1 => chrome}/package.json | 3 +- .../{chrome_v1 => chrome}/postcss.config.mjs | 0 .../{chrome_v1 => chrome}/prettier.config.cjs | 0 .../public/icons/claude-ai-128.png | Bin .../public/icons/claude-ai-16.png | Bin .../public/icons/claude-ai-48.png | Bin .../public/icons/claude_app_icon.png | Bin .../public/manifest.json | 2 +- .../src}/__tests__/sample.test.ts | 0 extensions/chrome/src/constants/constants.ts | 94 +++++++++++++ extensions/chrome/src/content/index.tsx | 26 ++++ .../chrome/src/content/managers/README.md | 133 ++++++++++++++++++ .../events/event-manager.ts | 91 ++++++++++++ .../managers/thinking-block-manager/index.ts | 78 ++++++++++ .../observer/dom-observer-manager.ts | 85 +++++++++++ .../styles/style-manager.ts | 20 +++ .../ui/ui-component-manager.ts | 113 +++++++++++++++ .../{chrome_v1 => chrome}/src/lib/utils.ts | 0 .../src/styles/globals.css | 0 .../{chrome_v1 => chrome}/src/types/css.d.ts | 0 extensions/chrome/src/types/index.ts | 28 ++++ extensions/chrome/src/utils/dom-utils.ts | 44 ++++++ .../{chrome_v1 => chrome}/tailwind.config.cjs | 0 extensions/{chrome_v1 => chrome}/test.txt | 0 .../{chrome_v1 => chrome}/tsconfig.json | 0 .../{chrome_v1 => chrome}/vitest.config.ts | 0 .../webpack/webpack.common.js | 13 +- .../webpack/webpack.dev.js | 0 .../webpack/webpack.prod.js | 0 extensions/chrome_v0/README.md | 8 +- extensions/chrome_v1/eslint.config.cjs | 73 ---------- .../chrome_v1/src/components/ui/button.tsx | 0 extensions/chrome_v1/src/content/index.tsx | 0 43 files changed, 829 insertions(+), 94 deletions(-) rename extensions/{chrome_v1 => chrome}/.gitignore (100%) rename extensions/{chrome_v1 => chrome}/.markdownlint-cli2.jsonc (100%) rename extensions/{chrome_v1 => chrome}/.markdownlintignore (100%) rename extensions/{chrome_v1 => chrome}/CHANGELOG.md (87%) rename extensions/{chrome_v1 => chrome}/README.md (96%) rename extensions/{chrome_v1 => chrome}/components.json (100%) create mode 100644 extensions/chrome/eslint.config.cjs rename extensions/{chrome_v1 => chrome}/package.json (97%) rename extensions/{chrome_v1 => chrome}/postcss.config.mjs (100%) rename extensions/{chrome_v1 => chrome}/prettier.config.cjs (100%) rename extensions/{chrome_v1 => chrome}/public/icons/claude-ai-128.png (100%) rename extensions/{chrome_v1 => chrome}/public/icons/claude-ai-16.png (100%) rename extensions/{chrome_v1 => chrome}/public/icons/claude-ai-48.png (100%) rename extensions/{chrome_v1 => chrome}/public/icons/claude_app_icon.png (100%) rename extensions/{chrome_v1 => chrome}/public/manifest.json (96%) rename extensions/{chrome_v1/src/content => chrome/src}/__tests__/sample.test.ts (100%) create mode 100644 extensions/chrome/src/constants/constants.ts create mode 100644 extensions/chrome/src/content/index.tsx create mode 100644 extensions/chrome/src/content/managers/README.md create mode 100644 extensions/chrome/src/content/managers/thinking-block-manager/events/event-manager.ts create mode 100644 extensions/chrome/src/content/managers/thinking-block-manager/index.ts create mode 100644 extensions/chrome/src/content/managers/thinking-block-manager/observer/dom-observer-manager.ts create mode 100644 extensions/chrome/src/content/managers/thinking-block-manager/styles/style-manager.ts create mode 100644 extensions/chrome/src/content/managers/thinking-block-manager/ui/ui-component-manager.ts rename extensions/{chrome_v1 => chrome}/src/lib/utils.ts (100%) rename extensions/{chrome_v1 => chrome}/src/styles/globals.css (100%) rename extensions/{chrome_v1 => chrome}/src/types/css.d.ts (100%) create mode 100644 extensions/chrome/src/types/index.ts create mode 100644 extensions/chrome/src/utils/dom-utils.ts rename extensions/{chrome_v1 => chrome}/tailwind.config.cjs (100%) rename extensions/{chrome_v1 => chrome}/test.txt (100%) rename extensions/{chrome_v1 => chrome}/tsconfig.json (100%) rename extensions/{chrome_v1 => chrome}/vitest.config.ts (100%) rename extensions/{chrome_v1 => chrome}/webpack/webpack.common.js (79%) rename extensions/{chrome_v1 => chrome}/webpack/webpack.dev.js (100%) rename extensions/{chrome_v1 => chrome}/webpack/webpack.prod.js (100%) delete mode 100644 extensions/chrome_v1/eslint.config.cjs delete mode 100644 extensions/chrome_v1/src/components/ui/button.tsx delete mode 100644 extensions/chrome_v1/src/content/index.tsx diff --git a/.github/workflows/chrome-extension-ci.yml b/.github/workflows/chrome-extension-ci.yml index 8ea1869..ed5869d 100644 --- a/.github/workflows/chrome-extension-ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -4,15 +4,15 @@ on: push: branches: [main] paths: - - "extensions/chrome_v1/**" + - "extensions/chrome/**" pull_request: branches: [main] paths: - - "extensions/chrome_v1/**" + - "extensions/chrome/**" defaults: run: - working-directory: ./extensions/chrome_v1 + working-directory: ./extensions/chrome jobs: lint-and-format: @@ -95,7 +95,7 @@ jobs: with: name: Chrome Extension v${{ steps.check_tag.outputs.version }} tag_name: v${{ steps.check_tag.outputs.version }} - files: extensions/chrome_v1/chrome-extension.zip + files: extensions/chrome/chrome-extension.zip generate_release_notes: true token: ${{ secrets.GITHUB_TOKEN }} fail_on_unmatched_files: true diff --git a/.husky/post-commit b/.husky/post-commit index 0f95dfc..cfd78ec 100644 --- a/.husky/post-commit +++ b/.husky/post-commit @@ -2,8 +2,8 @@ # DON'T TOUCH THIS FILE IF YOU DON'T KNOW WHAT YOU ARE DOING -echo "Checking for changes in chrome_v1..." -if git log -1 --name-only --pretty=format: | grep "^extensions/chrome_v1" > /dev/null; then +echo "Checking for changes in chrome..." +if git log -1 --name-only --pretty=format: | grep "^extensions/chrome" > /dev/null; then echo "Changes detected, fetching..." git fetch echo "Fetch complete" diff --git a/.husky/pre-commit b/.husky/pre-commit index 1debef7..08b0e42 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,12 +3,12 @@ # DON'T TOUCH THIS FILE IF YOU DON'T KNOW WHAT YOU ARE DOING # Check if there are changes in extensions directory -if ! git diff --cached --name-only | grep "^extensions/chrome_v1" > /dev/null; then - echo "No changes in chrome_v1" +if ! git diff --cached --name-only | grep "^extensions/chrome" > /dev/null; then + echo "No changes in chrome" exit 0 fi -if ! cd extensions/chrome_v1; then +if ! cd extensions/chrome; then echo "Failed to change directory" exit 1 fi diff --git a/extensions/chrome_v1/.gitignore b/extensions/chrome/.gitignore similarity index 100% rename from extensions/chrome_v1/.gitignore rename to extensions/chrome/.gitignore diff --git a/extensions/chrome_v1/.markdownlint-cli2.jsonc b/extensions/chrome/.markdownlint-cli2.jsonc similarity index 100% rename from extensions/chrome_v1/.markdownlint-cli2.jsonc rename to extensions/chrome/.markdownlint-cli2.jsonc diff --git a/extensions/chrome_v1/.markdownlintignore b/extensions/chrome/.markdownlintignore similarity index 100% rename from extensions/chrome_v1/.markdownlintignore rename to extensions/chrome/.markdownlintignore diff --git a/extensions/chrome_v1/CHANGELOG.md b/extensions/chrome/CHANGELOG.md similarity index 87% rename from extensions/chrome_v1/CHANGELOG.md rename to extensions/chrome/CHANGELOG.md index a4bd0d8..b639290 100644 --- a/extensions/chrome_v1/CHANGELOG.md +++ b/extensions/chrome/CHANGELOG.md @@ -4,7 +4,7 @@ ### Important Notice 🔄 -The original Chrome extension (now referred to as `chrome_v0`) has been deprecated and its development has been discontinued. This changelog tracks the new rewritten version (`chrome_v1`) which offers improved architecture, better performance, and enhanced maintainability. +The original Chrome extension (now referred to as `chrome_v0`) has been deprecated and its development has been discontinued. This changelog tracks the new rewritten version (`chrome`) which offers improved architecture, better performance, and enhanced maintainability. ### Changed diff --git a/extensions/chrome_v1/README.md b/extensions/chrome/README.md similarity index 96% rename from extensions/chrome_v1/README.md rename to extensions/chrome/README.md index 2e11f4a..7b0cc45 100644 --- a/extensions/chrome_v1/README.md +++ b/extensions/chrome/README.md @@ -2,7 +2,7 @@ A Chrome extension that enhances Claude's thinking process, making it more human-like and transparent. -> **Important Notice**: The original Chrome extension (`chrome_v0`) has been deprecated. This is the new rewritten version (`chrome_v1`) with improved architecture and modern tech stack. If you're using the old version, please update to this new version for better performance and continued support. +> **Important Notice**: The original Chrome extension (`chrome_v0`) has been deprecated. This is the new rewritten version (`chrome`) with improved architecture and modern tech stack. If you're using the old version, please update to this new version for better performance and continued support. ## How to Use 🚀 @@ -33,7 +33,7 @@ A Chrome extension that enhances Claude's thinking process, making it more human ```bash # Clone the repository git clone https://github.com/richards199999/Thinking-Claude.git - cd Thinking-Claude/extensions/chrome_v1 + cd Thinking-Claude/extensions/chrome # Install dependencies bun install @@ -167,7 +167,7 @@ bun --version git clone https://github.com/richards199999/Thinking-Claude.git # Go to the extension directory - cd extensions/chrome_v1 + cd extensions/chrome # Install project dependencies bun install @@ -203,7 +203,7 @@ bun run fix ## Project Organization 📁 ``` -chrome_v1/ +chrome/ ├── src/ # Your source code goes here ├── public/ # Built extension (created after running build) │ ├── manifest.json # Extension configuration diff --git a/extensions/chrome_v1/components.json b/extensions/chrome/components.json similarity index 100% rename from extensions/chrome_v1/components.json rename to extensions/chrome/components.json diff --git a/extensions/chrome/eslint.config.cjs b/extensions/chrome/eslint.config.cjs new file mode 100644 index 0000000..985076e --- /dev/null +++ b/extensions/chrome/eslint.config.cjs @@ -0,0 +1,84 @@ +/** @type {import('eslint').Config[]} */ +module.exports = [ + require("@eslint/js").configs.recommended, + { + files: ["**/*.{ts,tsx,js,jsx}"], + languageOptions: { + ecmaVersion: 2021, + sourceType: "module", + parser: require("@typescript-eslint/parser"), + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + chrome: "readonly", + console: "readonly", + MutationObserver: "readonly", + window: "readonly", + document: "readonly", + HTMLElement: "readonly", + Element: "readonly", + setTimeout: "readonly", + navigator: "readonly", + setInterval: "readonly", + Node: "readonly", + HTMLButtonElement: "readonly", + }, + }, + plugins: { + "@typescript-eslint": require("@typescript-eslint/eslint-plugin"), + react: require("eslint-plugin-react"), + "react-hooks": require("eslint-plugin-react-hooks"), + }, + rules: { + ...require("@typescript-eslint/eslint-plugin").configs.recommended.rules, + ...require("eslint-plugin-react").configs.recommended.rules, + ...require("eslint-plugin-react-hooks").configs.recommended.rules, + "react/react-in-jsx-scope": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "no-console": "off", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "no-inline-styles": "off", + }, + settings: { + react: { + version: "detect", + }, + }, + }, + // Config for test files + { + files: ["**/__tests__/**/*", "**/*.test.*"], + rules: { + "@typescript-eslint/no-explicit-any": "off", + "no-console": "off", + }, + }, + // Config for configuration files + { + files: ["*.config.js", "*.config.cjs", "webpack/**/*.js"], + languageOptions: { + globals: { + module: "readonly", + require: "readonly", + __dirname: "readonly", + }, + }, + rules: { + "@typescript-eslint/no-require-imports": "off", + "no-undef": "off", + }, + }, + require("eslint-config-prettier"), +] diff --git a/extensions/chrome_v1/package.json b/extensions/chrome/package.json similarity index 97% rename from extensions/chrome_v1/package.json rename to extensions/chrome/package.json index 6b0e400..7c1de56 100644 --- a/extensions/chrome_v1/package.json +++ b/extensions/chrome/package.json @@ -1,6 +1,6 @@ { "name": "thinking-claude", - "version": "1.0.0", + "version": "3.0.0", "description": "Chrome extension for letting Claude think like a real human", "type": "module", "scripts": { @@ -20,6 +20,7 @@ }, "dependencies": { "clsx": "^2.1.1", + "lucide-react": "^0.460.0", "react": "^18.2.0", "react-dom": "^18.2.0", "tailwind-merge": "^1.14.0" diff --git a/extensions/chrome_v1/postcss.config.mjs b/extensions/chrome/postcss.config.mjs similarity index 100% rename from extensions/chrome_v1/postcss.config.mjs rename to extensions/chrome/postcss.config.mjs diff --git a/extensions/chrome_v1/prettier.config.cjs b/extensions/chrome/prettier.config.cjs similarity index 100% rename from extensions/chrome_v1/prettier.config.cjs rename to extensions/chrome/prettier.config.cjs diff --git a/extensions/chrome_v1/public/icons/claude-ai-128.png b/extensions/chrome/public/icons/claude-ai-128.png similarity index 100% rename from extensions/chrome_v1/public/icons/claude-ai-128.png rename to extensions/chrome/public/icons/claude-ai-128.png diff --git a/extensions/chrome_v1/public/icons/claude-ai-16.png b/extensions/chrome/public/icons/claude-ai-16.png similarity index 100% rename from extensions/chrome_v1/public/icons/claude-ai-16.png rename to extensions/chrome/public/icons/claude-ai-16.png diff --git a/extensions/chrome_v1/public/icons/claude-ai-48.png b/extensions/chrome/public/icons/claude-ai-48.png similarity index 100% rename from extensions/chrome_v1/public/icons/claude-ai-48.png rename to extensions/chrome/public/icons/claude-ai-48.png diff --git a/extensions/chrome_v1/public/icons/claude_app_icon.png b/extensions/chrome/public/icons/claude_app_icon.png similarity index 100% rename from extensions/chrome_v1/public/icons/claude_app_icon.png rename to extensions/chrome/public/icons/claude_app_icon.png diff --git a/extensions/chrome_v1/public/manifest.json b/extensions/chrome/public/manifest.json similarity index 96% rename from extensions/chrome_v1/public/manifest.json rename to extensions/chrome/public/manifest.json index c55a9d1..c8ee996 100644 --- a/extensions/chrome_v1/public/manifest.json +++ b/extensions/chrome/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Thinking Claude", - "version": "1.0.0", + "version": "3.0.0", "description": "Chrome extension for letting Claude think like a real human", "content_scripts": [ { diff --git a/extensions/chrome_v1/src/content/__tests__/sample.test.ts b/extensions/chrome/src/__tests__/sample.test.ts similarity index 100% rename from extensions/chrome_v1/src/content/__tests__/sample.test.ts rename to extensions/chrome/src/__tests__/sample.test.ts diff --git a/extensions/chrome/src/constants/constants.ts b/extensions/chrome/src/constants/constants.ts new file mode 100644 index 0000000..c4183b9 --- /dev/null +++ b/extensions/chrome/src/constants/constants.ts @@ -0,0 +1,94 @@ +import { Icons, Selectors, Styles, Timings } from "@/types" + +export const selectors: Selectors = { + // Using data attribute for message container + messageContainer: "[data-is-streaming]", + + // Using combination of class and content for thinking label + thinkingLabel: 'div.text-xs:has(text="thinking")', + + // Using class and language attribute for code block + code: 'code[class*="language-thinking"]', + + // Using specific class combinations and structure + codeContainer: ".relative.flex.flex-col.rounded-lg", + + // Using specific pre within grid structure + pre: ".grid-cols-1.grid > pre", + + // Using specific class and container structure + thinkingProcess: '.code-block__code:has(>code[class*="language-thinking"])', +} + +export const timings: Timings = { + retryDelay: 1000, + mutationDelay: 100, + checkInterval: 2000, + copyFeedback: 2000, + maxRetries: 10, +} + +export const icons: Icons = { + arrow: ``, + tick: ``, + copy: ``, +} + +export const styles: Styles = { + animation: ` + @keyframes gradientWave { + 0% { background-position: 200% 50%; } + 100% { background-position: -200% 50%; } + } + + .thinking-animation { + background: linear-gradient( + 90deg, + rgba(156, 163, 175, 0.7) 0%, + rgba(209, 213, 219, 1) 25%, + rgba(156, 163, 175, 0.7) 50%, + rgba(209, 213, 219, 1) 75%, + rgba(156, 163, 175, 0.7) 100% + ); + background-size: 200% 100%; + animation: gradientWave 3s linear infinite; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + color: transparent; + } + + .thinking-header { + user-select: none; + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + background-color: rgb(40, 44, 52); + border-radius: 6px 6px 0 0; + } + + .thinking-content { + transition: all 0.3s ease-in-out; + overflow-x: hidden; + overflow-y: auto; + max-height: 0; + opacity: 0; + padding: 0; + max-width: 100%; + display: block; + background-color: rgb(40, 44, 52); + border-radius: 0 0 6px 6px; + } + + .thinking-content code { + white-space: pre-wrap !important; + word-break: break-word !important; + overflow-wrap: break-word !important; + display: block !important; + max-width: 100% !important; + } + `, + buttonClass: "flex items-center text-text-500 hover:text-text-300", + labelClass: "font-medium text-sm", +} diff --git a/extensions/chrome/src/content/index.tsx b/extensions/chrome/src/content/index.tsx new file mode 100644 index 0000000..124e7d3 --- /dev/null +++ b/extensions/chrome/src/content/index.tsx @@ -0,0 +1,26 @@ +import { ThinkingBlockManager } from "./managers/thinking-block-manager" + +/** + * Check if current URL matches Claude chat pattern + */ +const isChatURL = (): boolean => { + const url = window.location.href + return url.startsWith("https://claude.ai/chat/") +} + +/** + * Initialize the extension only on Claude chat pages + */ +const init = (): void => { + if (!isChatURL()) return + + const manager = new ThinkingBlockManager() + manager.init() +} + +// Initialize when DOM is ready +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) +} else { + init() +} diff --git a/extensions/chrome/src/content/managers/README.md b/extensions/chrome/src/content/managers/README.md new file mode 100644 index 0000000..1a69cd6 --- /dev/null +++ b/extensions/chrome/src/content/managers/README.md @@ -0,0 +1,133 @@ +# Thinking Process Manager Architecture + +This document outlines the architecture and workflow of the Thinking Process visualization feature in the Chrome extension. + +## Architecture Overview + +```mermaid +flowchart TB + subgraph Extension["Chrome Extension Content Script"] + Entry["index.tsx\nEntry Point"] + end + + subgraph ThinkingBlock["ThinkingBlockManager"] + direction TB + Init["Initialize"] + Process["Process Block"] + Cleanup["Cleanup Resources"] + end + + subgraph Managers["Manager Classes"] + direction LR + DOM["DOMObserverManager\n- Watch for new blocks\n- Periodic checks"] + UI["UIComponentManager\n- Create UI elements\n- Style components"] + Event["EventManager\n- Handle interactions\n- Manage UI state"] + Style["StyleManager\n- Inject styles\n- Manage animations"] + end + + Entry --> Init + Init --> Style + Init --> DOM + DOM --> Process + Process --> UI + Process --> Event + Event --> UI +``` + +## Component Workflow + +```mermaid +sequenceDiagram + participant Entry as index.tsx + participant TBM as ThinkingBlockManager + participant DOM as DOMObserverManager + participant UI as UIComponentManager + participant Event as EventManager + participant Style as StyleManager + + Entry->>TBM: Initialize + TBM->>Style: Inject Styles + TBM->>DOM: Initialize Observer + DOM->>DOM: Setup Periodic Check + + loop DOM Observation + DOM->>TBM: Process New Block + TBM->>UI: Create UI Components + TBM->>Event: Setup Event Handlers + Event-->>UI: Update UI State + end + + Note over TBM,Event: User Interactions + Event->>Event: Handle Toggle + Event->>Event: Handle Copy +``` + +## Component Responsibilities + +### ThinkingBlockManager + +- Central coordinator for the thinking process feature +- Initializes and manages other components +- Processes new thinking blocks +- Handles cleanup on unload + +### DOMObserverManager + +- Observes DOM for new thinking blocks +- Performs periodic checks for missed blocks +- Manages retry mechanism for initialization +- Handles cleanup of observers + +### UIComponentManager + +- Creates UI elements (buttons, containers) +- Applies consistent styling +- Manages component hierarchy +- Handles component updates + +### EventManager + +- Sets up event listeners +- Manages UI state transitions +- Handles copy functionality +- Provides user feedback + +### StyleManager + +- Injects required styles +- Manages animation styles +- Ensures single style injection +- Handles style cleanup + +## User Interaction Flow + +```mermaid +stateDiagram-v2 + [*] --> Hidden: Initial State + Hidden --> Visible: Click Toggle + Visible --> Hidden: Click Toggle + Visible --> Copied: Click Copy + Copied --> Visible: After Feedback + Hidden --> [*]: Cleanup + Visible --> [*]: Cleanup +``` + +## Installation and Usage + +The Thinking Process Manager is automatically initialized when the Chrome extension loads. It requires no manual setup and begins observing for thinking process blocks immediately. + +## Development + +To modify or extend the functionality: + +1. Each manager is designed to be independent and focused on a single responsibility +2. New features should be added to the appropriate manager +3. The ThinkingBlockManager coordinates all interactions between managers +4. Follow the established TypeScript types and interfaces + +## Error Handling + +- DOM observation includes retry mechanism +- Event handlers include error prevention +- Style injection prevents duplicates +- All cleanup is handled automatically diff --git a/extensions/chrome/src/content/managers/thinking-block-manager/events/event-manager.ts b/extensions/chrome/src/content/managers/thinking-block-manager/events/event-manager.ts new file mode 100644 index 0000000..702883c --- /dev/null +++ b/extensions/chrome/src/content/managers/thinking-block-manager/events/event-manager.ts @@ -0,0 +1,91 @@ +import { icons, timings } from "@/constants/constants" + +/** + * Manages event handling and UI state updates for thinking process blocks. + * Handles button click events, copy functionality, and UI state transitions. + */ +export class EventManager { + /** + * Sets up the toggle button click handler. + * @param toggleBtn - The button element that toggles the thinking process visibility + * @param container - The container element that holds the thinking process content + * @param updateUIState - Callback function to update the UI state + */ + static setupToggleButton( + toggleBtn: HTMLButtonElement, + container: HTMLElement, + updateUIState: (isOpen: boolean) => void + ): void { + toggleBtn.addEventListener("click", (e) => { + e.stopPropagation() + const currentState = container.dataset.isOpen === "true" + updateUIState(!currentState) + }) + } + + /** + * Sets up the copy button functionality. + * Handles click events, clipboard operations, and feedback animations. + * @param copyBtn - The button element that triggers the copy operation + * @param content - The element containing the content to be copied + */ + static setupCopyButton( + copyBtn: HTMLButtonElement, + content: Element | null + ): void { + copyBtn.addEventListener("click", (e) => { + e.stopPropagation() + if (content && !copyBtn.disabled) { + copyBtn.disabled = true + copyBtn.style.opacity = "0.5" + copyBtn.style.cursor = "default" + navigator.clipboard.writeText(content.textContent || "") + + const copyBtnText = copyBtn.querySelector("span.text-text-200") + const copyIcon = copyBtn.querySelector("span:first-child") + + if (copyBtnText) copyBtnText.textContent = "Copied!" + if (copyIcon) copyIcon.innerHTML = icons.tick + + setTimeout(() => { + if (copyBtnText) copyBtnText.textContent = "Copy" + if (copyIcon) copyIcon.innerHTML = icons.copy + copyBtn.disabled = false + copyBtn.style.opacity = "" + copyBtn.style.cursor = "" + }, timings.copyFeedback) + } + }) + } + + /** + * Updates the UI state of a thinking process block. + * Handles animations, text changes, and visibility states. + * @param container - The container element to update + * @param toggleBtn - The toggle button element to update + * @param isOpen - Whether the thinking process should be visible + */ + static updateUIState( + container: HTMLElement, + toggleBtn: HTMLButtonElement, + isOpen: boolean + ): void { + container.dataset.isOpen = isOpen.toString() + const arrow = toggleBtn.querySelector("svg") + const label = toggleBtn.querySelector("span") + + container.style.maxHeight = isOpen ? "50vh" : "0" + container.style.opacity = isOpen ? "1" : "0" + container.style.padding = isOpen ? "1em" : "0" + + if (label) { + label.textContent = isOpen + ? "Hide thinking process" + : "View thinking process" + } + + if (arrow) { + arrow.style.transform = `rotate(${isOpen ? 180 : 0}deg)` + } + } +} diff --git a/extensions/chrome/src/content/managers/thinking-block-manager/index.ts b/extensions/chrome/src/content/managers/thinking-block-manager/index.ts new file mode 100644 index 0000000..2b20f10 --- /dev/null +++ b/extensions/chrome/src/content/managers/thinking-block-manager/index.ts @@ -0,0 +1,78 @@ +import { selectors } from "@/constants/constants" + +import { EventManager } from "./events/event-manager" +import { DOMObserverManager } from "./observer/dom-observer-manager" +import { StyleManager } from "./styles/style-manager" +import { UIComponentManager } from "./ui/ui-component-manager" + +/** + * Manages the thinking process visualization feature in Claude's interface. + * Coordinates between UI components, DOM observation, events, and styles + * to create and maintain interactive thinking process blocks. + */ +export class ThinkingBlockManager { + private domObserver: DOMObserverManager + + /** + * Initializes the ThinkingBlockManager instance. + * Injects styles, sets up DOM observation, and adds an event listener for cleanup. + */ + constructor() { + StyleManager.injectStyles() + this.domObserver = new DOMObserverManager(this.processBlock.bind(this)) + this.domObserver.initWithRetry() + + window.addEventListener("unload", () => this.cleanupResources()) + } + + /** + * Processes a code block element to transform it into an interactive thinking process block. + * Creates and sets up the toggle button, copy functionality, and container styling. + * @param pre - The pre element containing the thinking process code + */ + private processBlock(pre: HTMLElement): void { + const container = pre.querySelector(selectors.code)?.parentElement + if (!container) return + + const outerDiv = pre.querySelector(selectors.codeContainer) + if (!outerDiv) return + + while (outerDiv.firstChild) { + outerDiv.removeChild(outerDiv.firstChild) + } + + container.dataset.isOpen = "true" + + const toggleBtn = UIComponentManager.createToggleButton() + const copyBtn = UIComponentManager.createCopyButton() + const header = UIComponentManager.createHeader(toggleBtn, copyBtn) + + UIComponentManager.setupContainer(container) + + outerDiv.appendChild(header) + outerDiv.appendChild(container) + + const updateUIState = (isOpen: boolean) => { + EventManager.updateUIState(container, toggleBtn, isOpen) + } + + EventManager.setupToggleButton(toggleBtn, container, updateUIState) + EventManager.setupCopyButton( + copyBtn, + container.querySelector(selectors.code) + ) + } + + /** + * Cleans up resources when the component is being destroyed. + * Disconnects DOM observers and removes event listeners. + */ + private cleanupResources(): void { + this.domObserver.cleanupObservers() + } + + // Add this method to resolve TypeScript error + init(): void { + // No additional initialization needed, as constructor handles it + } +} diff --git a/extensions/chrome/src/content/managers/thinking-block-manager/observer/dom-observer-manager.ts b/extensions/chrome/src/content/managers/thinking-block-manager/observer/dom-observer-manager.ts new file mode 100644 index 0000000..1e5aaa6 --- /dev/null +++ b/extensions/chrome/src/content/managers/thinking-block-manager/observer/dom-observer-manager.ts @@ -0,0 +1,85 @@ +import { timings } from "@/constants/constants" + +/** + * Manages DOM observation for thinking process blocks. + * Watches for new code blocks being added to the page and triggers processing. + * Also performs periodic checks for any blocks that might have been missed. + */ +export class DOMObserverManager { + private observers: Set + private processBlock: (pre: HTMLElement) => void + + /** + * Creates a new DOMObserverManager instance. + * @param processBlock - Callback function to process newly found code blocks + */ + constructor(processBlock: (pre: HTMLElement) => void) { + this.observers = new Set() + this.processBlock = processBlock + } + + /** + * Initializes the DOM observer with retry capability. + * Will attempt to retry setup if it fails, up to a maximum number of retries. + * @param retryCount - Current number of retry attempts + */ + initWithRetry(retryCount = 0): void { + try { + this.setupObserver() + } catch (error) { + console.error(error) + if (retryCount < timings.maxRetries) { + setTimeout(() => this.initWithRetry(retryCount + 1), timings.retryDelay) + } + } + } + + /** + * Cleans up all observers by disconnecting them and clearing the set. + */ + cleanupObservers(): void { + this.observers.forEach((observer) => observer.disconnect()) + this.observers.clear() + } + + /** + * Sets up the mutation observer to watch for DOM changes. + * Observes the entire document body for added nodes that might contain code blocks. + */ + private setupObserver(): void { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node instanceof HTMLElement) { + const pre = node.matches("pre") ? node : node.querySelector("pre") + if (pre) { + setTimeout(() => this.processBlock(pre), timings.mutationDelay) + } + } + }) + }) + }) + + observer.observe(document.body, { + childList: true, + subtree: true, + }) + + this.observers.add(observer) + this.setupPeriodicBlockCheck() + } + + /** + * Sets up periodic checking for code blocks that might have been missed. + * Runs at regular intervals defined in timing configuration. + */ + private setupPeriodicBlockCheck(): void { + setInterval(() => { + document.querySelectorAll("pre").forEach((pre) => { + if (!pre.querySelector(".thinking-header")) { + this.processBlock(pre as HTMLElement) + } + }) + }, timings.checkInterval) + } +} diff --git a/extensions/chrome/src/content/managers/thinking-block-manager/styles/style-manager.ts b/extensions/chrome/src/content/managers/thinking-block-manager/styles/style-manager.ts new file mode 100644 index 0000000..c3c4eec --- /dev/null +++ b/extensions/chrome/src/content/managers/thinking-block-manager/styles/style-manager.ts @@ -0,0 +1,20 @@ +import { styles } from "@/constants/constants" + +/** + * Manages the injection and removal of styles for thinking process blocks. + * Handles animation styles and ensures they are only injected once. + */ +export class StyleManager { + /** + * Injects the required styles for thinking process animations into the document head. + * Checks if styles are already present to avoid duplicate injection. + */ + static injectStyles(): void { + if (!document.getElementById("thinking-animation-styles")) { + const styleSheet = document.createElement("style") + styleSheet.id = "thinking-animation-styles" + styleSheet.textContent = styles.animation + document.head.appendChild(styleSheet) + } + } +} diff --git a/extensions/chrome/src/content/managers/thinking-block-manager/ui/ui-component-manager.ts b/extensions/chrome/src/content/managers/thinking-block-manager/ui/ui-component-manager.ts new file mode 100644 index 0000000..f00d9ad --- /dev/null +++ b/extensions/chrome/src/content/managers/thinking-block-manager/ui/ui-component-manager.ts @@ -0,0 +1,113 @@ +import { icons, selectors, styles } from "@/constants/constants" + +/** + * Manages the creation and styling of UI components for thinking process blocks. + * Provides factory methods for creating buttons, headers, and containers with consistent styling. + */ +export class UIComponentManager { + /** + * Creates a DOM element with optional class name and inner HTML. + * @param tag - The HTML tag name for the element + * @param className - Optional CSS class names to add + * @param innerHTML - Optional inner HTML content + * @returns The created HTML element + */ + static createElement( + tag: string, + className: string = "", + innerHTML: string = "" + ): HTMLElement { + const element = document.createElement(tag) + if (className) element.className = className + if (innerHTML) element.innerHTML = innerHTML + return element + } + + /** + * Creates a toggle button for showing/hiding thinking process content. + * @param isStreaming - Whether Claude is currently streaming a response + * @returns A styled button element with appropriate text and icon + */ + static createToggleButton(isStreaming = false): HTMLButtonElement { + const button = this.createElement( + "button", + "flex items-center text-text-500 hover:text-text-300" + ) + const labelText = isStreaming + ? "Claude is Thinking..." + : "Hide thinking process" + button.innerHTML = ` + ${icons.arrow} + ${labelText} + ` + return button as HTMLButtonElement + } + + /** + * Creates a copy button with appropriate styling and icon. + * @returns A styled button element for copying content + */ + static createCopyButton(): HTMLButtonElement { + const copyBtnContainer = this.createElement( + "div", + "from-bg-300/90 to-bg-300/70 pointer-events-auto rounded-md bg-gradient-to-b p-0.5 backdrop-blur-md" + ) + + const copyBtn = this.createElement( + "button", + "flex flex-row items-center gap-1 rounded-md p-1 py-0.5 text-xs transition-opacity delay-100 hover:bg-bg-200 opacity-60 hover:opacity-100" + ) as HTMLButtonElement + + const copyIcon = this.createElement("span", "", icons.copy) + const copyBtnText = this.createElement( + "span", + "text-text-200 pr-0.5", + "Copy" + ) + + copyBtn.appendChild(copyIcon) + copyBtn.appendChild(copyBtnText) + copyBtnContainer.appendChild(copyBtn) + + return copyBtn + } + + /** + * Creates a header element containing toggle and copy buttons. + * @param toggleBtn - The toggle button element + * @param copyBtn - The copy button element + * @returns A styled header element containing the buttons + */ + static createHeader( + toggleBtn: HTMLButtonElement, + copyBtn: HTMLButtonElement + ): HTMLElement { + const header = this.createElement("div", "thinking-header", "") + header.style.cssText = + "display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: var(--bg-300);" + + header.appendChild(toggleBtn) + header.appendChild(copyBtn.parentElement!) + + return header + } + + /** + * Sets up the container element with appropriate styling and classes. + * @param container - The container element to style + */ + static setupContainer(container: HTMLElement): void { + container.className = + "code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed" + container.style.cssText = + "transition: 0.3s ease-in-out; overflow: hidden auto; max-height: 50vh; opacity: 1; padding: 1em; max-width: 100%; display: block;" + + const content = container.querySelector(selectors.code) + if (content instanceof HTMLElement) { + content.style.cssText = + "white-space: pre-wrap !important; word-break: break-word !important; overflow-wrap: break-word !important; display: block !important; max-width: 100% !important;" + } + } +} diff --git a/extensions/chrome_v1/src/lib/utils.ts b/extensions/chrome/src/lib/utils.ts similarity index 100% rename from extensions/chrome_v1/src/lib/utils.ts rename to extensions/chrome/src/lib/utils.ts diff --git a/extensions/chrome_v1/src/styles/globals.css b/extensions/chrome/src/styles/globals.css similarity index 100% rename from extensions/chrome_v1/src/styles/globals.css rename to extensions/chrome/src/styles/globals.css diff --git a/extensions/chrome_v1/src/types/css.d.ts b/extensions/chrome/src/types/css.d.ts similarity index 100% rename from extensions/chrome_v1/src/types/css.d.ts rename to extensions/chrome/src/types/css.d.ts diff --git a/extensions/chrome/src/types/index.ts b/extensions/chrome/src/types/index.ts new file mode 100644 index 0000000..d8e8c1a --- /dev/null +++ b/extensions/chrome/src/types/index.ts @@ -0,0 +1,28 @@ +export interface Selectors { + pre: string + thinkingProcess: string + messageContainer: string + thinkingLabel: string + codeContainer: string + code: string +} + +export interface Timings { + retryDelay: number + mutationDelay: number + checkInterval: number + copyFeedback: number + maxRetries: number +} + +export interface Icons { + arrow: string + tick: string + copy: string +} + +export interface Styles { + animation: string + buttonClass: string + labelClass: string +} diff --git a/extensions/chrome/src/utils/dom-utils.ts b/extensions/chrome/src/utils/dom-utils.ts new file mode 100644 index 0000000..8d573e7 --- /dev/null +++ b/extensions/chrome/src/utils/dom-utils.ts @@ -0,0 +1,44 @@ +export const waitForElement = ( + selector: string, + timeout = 5000 +): Promise => { + return new Promise((resolve) => { + if (document.querySelector(selector)) { + return resolve(document.querySelector(selector)) + } + + const observer = new MutationObserver(() => { + if (document.querySelector(selector)) { + observer.disconnect() + resolve(document.querySelector(selector)) + } + }) + + observer.observe(document.body, { + childList: true, + subtree: true, + }) + + setTimeout(() => { + observer.disconnect() + resolve(null) + }, timeout) + }) +} + +export const findElement = async ( + selectors: string[] +): Promise => { + for (const selector of selectors) { + const element = await waitForElement(selector) + if (element) { + console.log("[Thinking Claude] Found element using selector:", selector) + return element + } + } + console.log( + "[Thinking Claude] No matching element found for selectors:", + selectors + ) + return null +} diff --git a/extensions/chrome_v1/tailwind.config.cjs b/extensions/chrome/tailwind.config.cjs similarity index 100% rename from extensions/chrome_v1/tailwind.config.cjs rename to extensions/chrome/tailwind.config.cjs diff --git a/extensions/chrome_v1/test.txt b/extensions/chrome/test.txt similarity index 100% rename from extensions/chrome_v1/test.txt rename to extensions/chrome/test.txt diff --git a/extensions/chrome_v1/tsconfig.json b/extensions/chrome/tsconfig.json similarity index 100% rename from extensions/chrome_v1/tsconfig.json rename to extensions/chrome/tsconfig.json diff --git a/extensions/chrome_v1/vitest.config.ts b/extensions/chrome/vitest.config.ts similarity index 100% rename from extensions/chrome_v1/vitest.config.ts rename to extensions/chrome/vitest.config.ts diff --git a/extensions/chrome_v1/webpack/webpack.common.js b/extensions/chrome/webpack/webpack.common.js similarity index 79% rename from extensions/chrome_v1/webpack/webpack.common.js rename to extensions/chrome/webpack/webpack.common.js index fa91e35..a285e3b 100644 --- a/extensions/chrome_v1/webpack/webpack.common.js +++ b/extensions/chrome/webpack/webpack.common.js @@ -16,7 +16,15 @@ export default { rules: [ { test: /\.tsx?$/, - use: "ts-loader", + use: [ + { + loader: "ts-loader", + options: { + configFile: path.resolve(__dirname, "..", "tsconfig.json"), + transpileOnly: true, + }, + }, + ], exclude: /node_modules/, }, { @@ -27,6 +35,9 @@ export default { }, resolve: { extensions: [".tsx", ".ts", ".js"], + alias: { + "@": path.resolve(__dirname, "..", "src"), + }, }, output: { path: path.resolve(__dirname, "..", "dist"), diff --git a/extensions/chrome_v1/webpack/webpack.dev.js b/extensions/chrome/webpack/webpack.dev.js similarity index 100% rename from extensions/chrome_v1/webpack/webpack.dev.js rename to extensions/chrome/webpack/webpack.dev.js diff --git a/extensions/chrome_v1/webpack/webpack.prod.js b/extensions/chrome/webpack/webpack.prod.js similarity index 100% rename from extensions/chrome_v1/webpack/webpack.prod.js rename to extensions/chrome/webpack/webpack.prod.js diff --git a/extensions/chrome_v0/README.md b/extensions/chrome_v0/README.md index f700cc2..d8dd563 100644 --- a/extensions/chrome_v0/README.md +++ b/extensions/chrome_v0/README.md @@ -15,16 +15,16 @@ We've completely rewritten the extension with: ### How to Update? -Please switch to our new version (`chrome_v1`): +Please switch to our new version (`chrome`): 1. Uninstall this version (`chrome_v0`) from Chrome -2. Visit our [Latest Releases](https://github.com/richards199999/Thinking-Claude/releases) or refer to the [README.md](https://github.com/richards199999/Thinking-Claude/tree/main/extensions/chrome_v1/README.md) in `extensions/chrome_v1` +2. Visit our [Latest Releases](https://github.com/richards199999/Thinking-Claude/releases) or refer to the [README.md](https://github.com/richards199999/Thinking-Claude/tree/main/extensions/chrome/README.md) in `extensions/chrome` 3. Download and install the new version For installation instructions and documentation, see: -- [New Extension Documentation](https://github.com/richards199999/Thinking-Claude/tree/main/extensions/chrome_v1/README.md) -- [Changelog](https://github.com/richards199999/Thinking-Claude/tree/main/extensions/chrome_v1/CHANGELOG.md) +- [New Extension Documentation](https://github.com/richards199999/Thinking-Claude/tree/main/extensions/chrome/README.md) +- [Changelog](https://github.com/richards199999/Thinking-Claude/tree/main/extensions/chrome/CHANGELOG.md) ## Support diff --git a/extensions/chrome_v1/eslint.config.cjs b/extensions/chrome_v1/eslint.config.cjs deleted file mode 100644 index ce851b8..0000000 --- a/extensions/chrome_v1/eslint.config.cjs +++ /dev/null @@ -1,73 +0,0 @@ -/** @type {import('eslint').Config[]} */ -module.exports = [ - require('@eslint/js').configs.recommended, - { - files: ['**/*.{ts,tsx,js,jsx}'], - languageOptions: { - ecmaVersion: 2021, - sourceType: 'module', - parser: require('@typescript-eslint/parser'), - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - globals: { - chrome: 'readonly', - console: 'readonly', - }, - }, - plugins: { - '@typescript-eslint': require('@typescript-eslint/eslint-plugin'), - react: require('eslint-plugin-react'), - 'react-hooks': require('eslint-plugin-react-hooks'), - }, - rules: { - ...require('@typescript-eslint/eslint-plugin').configs.recommended.rules, - ...require('eslint-plugin-react').configs.recommended.rules, - ...require('eslint-plugin-react-hooks').configs.recommended.rules, - 'react/react-in-jsx-scope': 'off', - '@typescript-eslint/no-unused-vars': [ - 'warn', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - }, - ], - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - 'no-console': ['warn', { allow: ['warn', 'error'] }], - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - }, - settings: { - react: { - version: 'detect', - }, - }, - }, - // Config for test files - { - files: ['**/__tests__/**/*', '**/*.test.*'], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - 'no-console': 'off', - }, - }, - // Config for configuration files - { - files: ['*.config.js', '*.config.cjs', 'webpack/**/*.js'], - languageOptions: { - globals: { - module: 'readonly', - require: 'readonly', - __dirname: 'readonly', - }, - }, - rules: { - '@typescript-eslint/no-require-imports': 'off', - 'no-undef': 'off', - }, - }, - require('eslint-config-prettier'), -]; diff --git a/extensions/chrome_v1/src/components/ui/button.tsx b/extensions/chrome_v1/src/components/ui/button.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/extensions/chrome_v1/src/content/index.tsx b/extensions/chrome_v1/src/content/index.tsx deleted file mode 100644 index e69de29..0000000 From 69df506efb474d859df076bd59f1e17b102a8c7c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 Nov 2024 14:16:50 +0000 Subject: [PATCH 21/26] chore: bump version to 3.0.1 [skip ci] --- extensions/chrome/package.json | 2 +- extensions/chrome/public/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/chrome/package.json b/extensions/chrome/package.json index 7c1de56..55754e0 100644 --- a/extensions/chrome/package.json +++ b/extensions/chrome/package.json @@ -1,6 +1,6 @@ { "name": "thinking-claude", - "version": "3.0.0", + "version": "3.0.1", "description": "Chrome extension for letting Claude think like a real human", "type": "module", "scripts": { diff --git a/extensions/chrome/public/manifest.json b/extensions/chrome/public/manifest.json index c8ee996..ddb8e67 100644 --- a/extensions/chrome/public/manifest.json +++ b/extensions/chrome/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Thinking Claude", - "version": "3.0.0", + "version": "3.0.1", "description": "Chrome extension for letting Claude think like a real human", "content_scripts": [ { From 12286fefebec137bd7c65566dfc1f90b0b8e6ac6 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 22:24:58 +0800 Subject: [PATCH 22/26] fix: respect major version numbers in CI workflow - Add check for major versions (x.0.0) to prevent auto-incrementing - Only increment patch version for non-major versions --- .github/workflows/chrome-extension-ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/chrome-extension-ci.yml b/.github/workflows/chrome-extension-ci.yml index ed5869d..dd106ce 100644 --- a/.github/workflows/chrome-extension-ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -70,8 +70,12 @@ jobs: id: check_tag run: | current_version=$(node -p "require('./package.json').version") - if git ls-remote --tags origin refs/tags/v$current_version >/dev/null; then - # If tag exists, increment patch version + if [[ "$current_version" =~ ^[0-9]+\.0\.0$ ]]; then + # Don't increment major versions (x.0.0) + echo "version=$current_version" >> $GITHUB_OUTPUT + echo "version_changed=false" >> $GITHUB_OUTPUT + elif git ls-remote --tags origin refs/tags/v$current_version >/dev/null; then + # If tag exists and it's not a major version, increment patch IFS='.' read -r major minor patch <<< "$current_version" new_version="$major.$minor.$((patch + 1))" echo "version=$new_version" >> $GITHUB_OUTPUT From a9555a481ee4620a13a5dc2e96e09dcc0efdb006 Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 22:27:48 +0800 Subject: [PATCH 23/26] ci: update the comment in ci and test the auto bumping --- .github/workflows/chrome-extension-ci.yml | 1 + extensions/chrome/test.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/chrome-extension-ci.yml b/.github/workflows/chrome-extension-ci.yml index dd106ce..93c21cc 100644 --- a/.github/workflows/chrome-extension-ci.yml +++ b/.github/workflows/chrome-extension-ci.yml @@ -66,6 +66,7 @@ jobs: # Automatically increments the patch version (e.g., 1.0.0 -> 1.0.1) # and creates a release + # Only increment patch version for non-major versions - name: Check existing tag id: check_tag run: | diff --git a/extensions/chrome/test.txt b/extensions/chrome/test.txt index 04a9657..45f8b44 100644 --- a/extensions/chrome/test.txt +++ b/extensions/chrome/test.txt @@ -1 +1 @@ -Test file to verify github actions +Test file to verify github actions auto bumping From 02a64e588469651bd85602e7fbe36aa4ec57c028 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 23 Nov 2024 14:28:37 +0000 Subject: [PATCH 24/26] chore: bump version to 3.0.2 [skip ci] --- extensions/chrome/package.json | 2 +- extensions/chrome/public/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/chrome/package.json b/extensions/chrome/package.json index 55754e0..d265ddd 100644 --- a/extensions/chrome/package.json +++ b/extensions/chrome/package.json @@ -1,6 +1,6 @@ { "name": "thinking-claude", - "version": "3.0.1", + "version": "3.0.2", "description": "Chrome extension for letting Claude think like a real human", "type": "module", "scripts": { diff --git a/extensions/chrome/public/manifest.json b/extensions/chrome/public/manifest.json index ddb8e67..29e4478 100644 --- a/extensions/chrome/public/manifest.json +++ b/extensions/chrome/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Thinking Claude", - "version": "3.0.1", + "version": "3.0.2", "description": "Chrome extension for letting Claude think like a real human", "content_scripts": [ { From de40f6bc4d04e01d705e8d32cd91c4fc7551114a Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 22:35:43 +0800 Subject: [PATCH 25/26] ci: restore the version back to 3.0.0 --- extensions/chrome/package.json | 2 +- extensions/chrome/public/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/chrome/package.json b/extensions/chrome/package.json index d265ddd..7c1de56 100644 --- a/extensions/chrome/package.json +++ b/extensions/chrome/package.json @@ -1,6 +1,6 @@ { "name": "thinking-claude", - "version": "3.0.2", + "version": "3.0.0", "description": "Chrome extension for letting Claude think like a real human", "type": "module", "scripts": { diff --git a/extensions/chrome/public/manifest.json b/extensions/chrome/public/manifest.json index 29e4478..c8ee996 100644 --- a/extensions/chrome/public/manifest.json +++ b/extensions/chrome/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Thinking Claude", - "version": "3.0.2", + "version": "3.0.0", "description": "Chrome extension for letting Claude think like a real human", "content_scripts": [ { From d2740340b995acf452c87c371734a4c28ad821ac Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Sat, 23 Nov 2024 23:04:49 +0800 Subject: [PATCH 26/26] refactor: update extension configuration and assets - Update ESLint config to properly ignore dist directory - Rename extension icons from claude-ai to thinking-claude - Fix version in package.json and manifest.json to 3.0.0 - Update README with clearer project structure --- README.md | 110 +++++++++++------- extensions/chrome/eslint.config.cjs | 8 ++ .../chrome/public/icons/claude-ai-128.png | Bin 19174 -> 0 bytes .../chrome/public/icons/claude-ai-16.png | Bin 636 -> 0 bytes .../chrome/public/icons/claude-ai-48.png | Bin 3247 -> 0 bytes .../chrome/public/icons/claude_app_icon.png | Bin 14038 -> 0 bytes .../public/icons/thinking-claude-128.png | Bin 0 -> 12206 bytes .../public/icons/thinking-claude-16.png | Bin 0 -> 705 bytes .../public/icons/thinking-claude-48.png | Bin 0 -> 3352 bytes extensions/chrome/public/manifest.json | 12 +- 10 files changed, 81 insertions(+), 49 deletions(-) delete mode 100644 extensions/chrome/public/icons/claude-ai-128.png delete mode 100644 extensions/chrome/public/icons/claude-ai-16.png delete mode 100644 extensions/chrome/public/icons/claude-ai-48.png delete mode 100644 extensions/chrome/public/icons/claude_app_icon.png create mode 100644 extensions/chrome/public/icons/thinking-claude-128.png create mode 100644 extensions/chrome/public/icons/thinking-claude-16.png create mode 100644 extensions/chrome/public/icons/thinking-claude-48.png diff --git a/README.md b/README.md index 6881789..f530403 100644 --- a/README.md +++ b/README.md @@ -6,71 +6,93 @@ Let Claude think comprehensively before responding! > Thinking claude **is not aimed for benchmarks or huge leaps in math or something**, since those are pre-determined by the base model (new Claude-3.5 Sonnet). > I only want to explore how further we could reach with Claude's "deep mindset". That said, when using it in your daily tasks, you will find Claude's inner monolog (thinking process) very very fun and interesting. -## Demo: - +## Demo https://github.com/user-attachments/assets/88ff0c75-c51b-42b9-a042-00d47053795a - ## Overview This project consists of two main components: + 1. **Thinking Protocol**: A comprehensive set of instructions that guides Claude to think deeply and systematically before responding 2. **Browser Extension**: A tool that makes Claude's thinking process more readable and manageable in the browser interface ## Project Structure - thinking-claude/ - ├── extension/ - │ ├── .vscode/ - │ ├── chrome/ - │ ├── firefox/ - │ └── changelog.md - ├── model_instructions/ - │ ├── changelog.md - │ ├── v3.5-20241113.md - │ ├── v4-20241118.md - │ └── v4-lite-20241118.md - ├── LICENSE - └── README.md -The project is organized into two main directories: -- `extension/`: Contains browser extension implementations -- `model_instructions/`: Contains thinking protocols for different versions - -Each directory maintains its own changelog for version tracking. + +```bash +thinking-claude/ +├── extensions/ +│ ├── chrome/ # Current version of Chrome extension +│ ├── chrome_v0/ # Legacy Chrome extension (deprecated) +│ ├── firefox/ # Firefox extension (in development) +│ └── changelog.md +├── model_instructions/ +│ ├── changelog.md +│ ├── v5-Exp-20241123.md +│ ├── v4-20241118.md +│ ├── v4-lite-20241118.md +│ └── v3.5-20241113.md +├── .github/ # GitHub configurations and workflows +├── .husky/ # Git hooks for development +├── LICENSE +└── README.md +``` + +The project is organized into two main components: + +- `extensions/`: Browser extension implementations + + - `chrome/`: Current version with modern architecture and features + - `chrome_v0/`: Legacy version (deprecated) + - `firefox/`: Firefox version (in development) + +- `model_instructions/`: Thinking protocols for different versions + - Contains versioned instruction sets + - Each version brings improvements to Claude's thinking process + ## Thinking Protocol The thinking protocol instructs Claude to follow a natural, thorough thought process before providing responses. ## Browser Extension -The browser extension enhances the Claude interface by making the thinking process more manageable: +The browser extension makes Claude's thinking process easier to read and use! It automatically organizes Claude's thoughts into neat, collapsible sections. ### Features -- 🔄 Collapsible thinking process sections -- 📋 Easy copy functionality -- 🎯 Clean and intuitive interface -- ⚡ Automatic processing of new messages -### Installation +- 🎯 Makes Claude's thinking process easy to read +- 🔄 Fold and unfold different parts of Claude's thoughts +- 📋 Copy any part with just one click +- ⚡ Works automatically with new messages +- 🎨 Clean, modern design that's easy on the eyes + +### 🚀 Quick Install Guide -#### Chrome +1. **Chrome Users (Recommended)** -**Quick Install:** -Install directly from the [Chrome Web Store](https://chromewebstore.google.com/detail/thinking-claude/ncjafpbbndpggfhfgjngkcimeaciahpo) + - Install directly from the [Chrome Web Store](https://chromewebstore.google.com/detail/thinking-claude/ncjafpbbndpggfhfgjngkcimeaciahpo) -**Manual Installation:** -1. Clone the repository: - ```bash - git clone https://github.com/yourusername/thinking-claude.git -2. Open Chrome and navigate to `chrome://extensions/` -3. Enable "Developer mode" -4. Click "Load unpacked" and select the `extension/chrome` folder +2. **Manual Installation** + - Download the latest version from our [Releases Page](https://github.com/richards199999/Thinking-Claude/releases) + - Unzip the file + - Open Chrome and go to `chrome://extensions/` + - Turn on "Developer mode" (top right corner) + - Click "Load unpacked" and select the unzipped folder -#### Firefox -1. Clone this repository -2. Open Firefox and navigate to `about:debugging#/runtime/this-firefox` -3. Click "Load Temporary Add-on" -4. Navigate to the repository and select the `extension/firefox/manifest.json` file +👉 Want more details? Check out our [Extension Guide](extensions/chrome/README.md) for: + +- Step-by-step installation instructions +- Development setup +- Advanced features and usage +- Troubleshooting tips + +### 🎉 Getting Started + +Once installed, just: + +1. Visit [Claude.ai](https://claude.ai) +2. Start chatting with Claude +3. That's it! The extension will automatically make Claude's thinking process more readable ## Usage @@ -79,11 +101,12 @@ Install directly from the [Chrome Web Store](https://chromewebstore.google.com/d 1. Copy the latest version in `model_instructions` folder 2. Start a new Project in Claude.ai 3. Paste the instructions to the Custom Instructions section -3. Claude will now follow the thinking protocol for all subsequent interactions +4. Claude will now follow the thinking protocol for all subsequent interactions ### Using the Extension Once installed, the extension automatically: + - Detects Claude's thinking process blocks - Adds collapse/expand functionality - Provides a copy button for each block @@ -98,6 +121,7 @@ Once installed, the extension automatically: ## Contributing Contributions are welcome! Feel free to: + - Submit bug reports - Propose new features - Create pull requests diff --git a/extensions/chrome/eslint.config.cjs b/extensions/chrome/eslint.config.cjs index 985076e..640b2bc 100644 --- a/extensions/chrome/eslint.config.cjs +++ b/extensions/chrome/eslint.config.cjs @@ -1,5 +1,13 @@ /** @type {import('eslint').Config[]} */ module.exports = [ + { + ignores: [ + "**/dist/**", + "**/node_modules/**", + "**/coverage/**", + "**/*.config.js", + ], + }, require("@eslint/js").configs.recommended, { files: ["**/*.{ts,tsx,js,jsx}"], diff --git a/extensions/chrome/public/icons/claude-ai-128.png b/extensions/chrome/public/icons/claude-ai-128.png deleted file mode 100644 index 86387c7dc98dee740d480c809222dc43b276f87e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19174 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 diff --git a/extensions/chrome/public/icons/claude-ai-16.png b/extensions/chrome/public/icons/claude-ai-16.png deleted file mode 100644 index 7e9ff0576d5821f9799486997e5c155cd6d78081..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 636 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
    PTlgAo|Aj+VurdHDq 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 literal 0 HcmV?d00001 diff --git a/extensions/chrome/public/icons/thinking-claude-16.png b/extensions/chrome/public/icons/thinking-claude-16.png new file mode 100644 index 0000000000000000000000000000000000000000..06e6c0c7843bd84ff542dae4f9009502387d5649 GIT binary patch literal 705 zcmV;y0zUnTP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0#->xK~y+T#ge~k z6hRcnzcaIQJDc@x?>Mt2BvFF_(bn@w5g|b&jUp**EbJ{L|Al`*z``ztNCdG7rZ^O% zrigllh{14*1QTNtkMr2O+ne2)-LcAPw276U?(rU<_k#!gZ^-XKtyZ%MA(O7_$(^08 z(Dw)C^7#TG88*ASwPm}W*B1uC^62QO{7bO3v}BJApIG+$2gZftK*}%?!ezp?5i6M( z17-^w6y~*+VHXG?F#yngf#($`9XmTt3`eSfsB(`X(rCV_fPyoY5RjCDF;^O=&pp00nYdpN4R%?tzfJPlQNhD(DkoKo8Q5P z&mxUF&^w=DltS?00aUXA9RoTB(u9_K0u`EPGE~sYfOtb-C(przZP2U_!Z>Ko2XXR9 zIxT1gblV2)hF<9jS}M8Ks0RzOD`59oBrK0Coa@hhbmVw?}17rX`Fb3WD265^TN>dP{o5dvYL6(QhUP*7%G4u{r7QX;A+sD1c!eQ4zPRf}7;38XK_ZgjAw~)ZLK4>Da6Xu6w&*QQLR?_`n%1b^CLA$H(lr@4hb!lN-An)?l-p6 ndTS4l{wz2&#+cq){|i3>Ah7%NV=meW00000NkvXXu0mjfm~Sy! literal 0 HcmV?d00001 diff --git a/extensions/chrome/public/icons/thinking-claude-48.png b/extensions/chrome/public/icons/thinking-claude-48.png new file mode 100644 index 0000000000000000000000000000000000000000..a4be7f338118d08a3d5e278361cdd34ef69120f1 GIT binary patch literal 3352 zcmV+z4d?QSP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D46R8-K~!i%?U{S5 zUBz|AfAiSS^SZwFwXqF0#=HU;NN6!J;^8EvgtSc&PtmAV0%=7EiJGETDs5<_CP)p6 zQmI7=NNuQSn%bb6Tb#Ns@wJHo;|K8rlg5tg*t~-6>-#?E?6ddG^pCyI@wvywPK#Rd zhd*g+@3ZghHQ%?^%$hZG;WSR;G*09Fgy?-VB4!uNf&_E1V9mB3nS(=`3!-n|D_ z`#yL0zW<3Z49@m_Kafg|NU64YT0T}TmwzaPFufd3gkMO&(9n<$g5cqDxq5%4QVGH! z!t*?&QYa~KI3}ki$Hzv;{8C&FrbRSKk4Pti&F|18XD4C>#tSHwFj!znrM?(O6Q)H5+McFS{ltd z&I)|5?RkZi5-CMTKr=~5noX4U&`Kd?-bN{bka-(p4Xw2G%~BLSaH>ei-WR>lT3;3T z(HEkqB((OCLKUA0oD(>kUs5QdpoFh|lvD@=&VjQIYaP~rH6W1qUWlhWq|itq5hAyT z%RQ=CwZC4L(Y`Z)Zm!F8_P- z#y+yi35;>z1X3yl5`h4hx5JX(J0f&st@&f;+(}2tNd*iJ4#q;rWlCy9K^#Z!*@B$2 z;0#P{SwnsI%S;UY6RwqX+GL}LIrPLsj0~;7*<1jwD}rrjblJs$5TK-7Ix;d+?d8>t zlM0BUIT482xi{spjtcL0M?jzX0RYVCVH$gP;GDx*M|Sk*xGY5kC9HFKRGL%NfAR`8 zX>>Vbx`^VM?fZV*%c~nF72w=hfFR1BU6)}u?YFfSZ{elr`R4&p-?R#wW?1VmQ{&(q zde&Ju3#L)$$oIa^@QSZ9w(*%xMCo=+vD8T^l`*C=jZ_a#D8M-<)~(y{$#PtIHjd)J z^E5){O*_PmrLlDllg~YY85_n)Md`Xvfs|y250dQLiF074#y~3c%=x&?FuL{$T5s+F zA<%tuj*&0!c_;ZzN`X>(<&lw*o1JqfYv-Km<$IifGiIN0UlfKU|l{Qxy<9x|@tjKz)}#!ihQv`_!<{007; zvkQAU3nB7nAwWtHGQYQn+>v&Lo|Q( zD)m=i#7s?eWwtyuTboD3*_?@zx@L^~jc(49cWE!t(>P(jD&$Av<0d z2(SjBy~2G(OiV^==MQLZdMfX^2SR&`g zd(|1yUB&u6UBLS1Ha!}JrF-Kj#`AqV?RAQ+wtzefS?rO6xcWG*IRR-Cmo^buf=d(Z z)G*opoxSE!vlb9s@L_aq0O@;(po}W@p(+Ci&o3gJ3+RX_A_8EnC21v(_YcIs-?lkD z0{^l37Onl2aa0mPQR*tC^5RU2JPUOua(g&+4J(t8?b}Rq(@JPHaapU|Jl>fXgHo8` zH}W}QTHp*KD${?*U*gSP*fu-uBG25USxU>8PtTbfeWz`6nt+WPe_S#3=B_Xd7lvVo z@3(7pg_06c)a>mmH;~T(^Y&hnA3lK{eFyKnWw`n{X832g`gp+tgw~Wl@g@8-FTzb8 zC4GAjtpl%On-f&8|6TOV`5jWOi=1^1YYj;&eaAJkOXgo#J<{epJ;uP$_qX4vlzbux z13&NsJl{iWh0?i*V{;;RkDC~#wRIKQo{h*EXA&&E4LABW$t#0|AHI|3=BJV6e$4Re zsM+UYM_#A2{2sy!mgU2;7~k!ka(fZWNjsdi7-MK9X<{;a&*DpKPj>S?ZXsL$@?|S* zD(`PhrH%SjLZhCLG*hzFVB0ki00KKcOk>qo$@Xl-TXG}u&0j|7kmi=B2!8ce5CU21 zC%*o(xp9*cExm)uwSPn7M{B_4)hzbd*e@y;OzLRXQzpj~#)ccM$41r?~$ch)NXwk*?$)FZfJ@+NC=ntnM-?lb>pN0(sXzd%Uj0UV&l^8q+MPd+f@c%MrIOoLB>K*q>={*t#kr((8zUQMj#LOIu||?L zby>>UJCFoY=h*@9+S2`ErqUf4}9~ ziRDVk$E0SlR8q9}(Z;fmmbiDxuj}W!hsDs4)AJin{o~6b{mNHP?eShr6R_o_ElZ

    k;;H03qbgNQe!Ke@%?_wtcVY z2-v!Ht7lU6{k~fN$10U7<+w~3YP6rPj5c*!L}9!r3?lr%$M*z6 zx?wH&NdIEFv%CE-1CC3h+c%6Xl1@J8>EJ4@J*l