diff --git a/README.md b/README.md
index d568da3..b423d86 100644
--- a/README.md
+++ b/README.md
@@ -105,7 +105,7 @@ Learn more: [electronjs/doc](https://www.electronjs.org/docs/latest/api/extensio
| [@noi/ask-custom](https://github.com/lencx/Noi/tree/main/extensions/noi-ask-custom) | 0.1.0 | The best assistant for batch asking and quick typing of prompts. |
| [@noi/export-chatgpt](https://github.com/lencx/Noi/tree/main/extensions/noi-export-chatgpt) | 0.1.1 | ChatGPT chat history export, supports PDF, Image, and Markdown formats. |
| [@noi/reset](https://github.com/lencx/Noi/tree/main/extensions/noi-reset) | 0.1.3 | Reset certain website styles to enhance compatibility with Noi. |
-| [Thinking Claude](https://github.com/lencx/Noi/tree/main/extensions/thinking-claude) | 0.1.0 | Makes Claude's thinking process expandable and collapsible |
+| [Thinking Claude](https://github.com/lencx/Noi/tree/main/extensions/thinking-claude) | 0.1.1 | Makes Claude's thinking process expandable and collapsible |
[](https://star-history.com/#lencx/Noi&Timeline)
diff --git a/extensions/README.md b/extensions/README.md
index a7eea3c..5174590 100644
--- a/extensions/README.md
+++ b/extensions/README.md
@@ -13,5 +13,5 @@ Learn more: [electronjs/doc](https://www.electronjs.org/docs/latest/api/extensio
| [@noi/ask-custom](https://github.com/lencx/Noi/tree/main/extensions/noi-ask-custom) | 0.1.0 | The best assistant for batch asking and quick typing of prompts. |
| [@noi/export-chatgpt](https://github.com/lencx/Noi/tree/main/extensions/noi-export-chatgpt) | 0.1.1 | ChatGPT chat history export, supports PDF, Image, and Markdown formats. |
| [@noi/reset](https://github.com/lencx/Noi/tree/main/extensions/noi-reset) | 0.1.3 | Reset certain website styles to enhance compatibility with Noi. |
-| [Thinking Claude](https://github.com/lencx/Noi/tree/main/extensions/thinking-claude) | 0.1.0 | Makes Claude's thinking process expandable and collapsible |
+| [Thinking Claude](https://github.com/lencx/Noi/tree/main/extensions/thinking-claude) | 0.1.1 | Makes Claude's thinking process expandable and collapsible |
\ No newline at end of file
diff --git a/extensions/noi.extensions.json b/extensions/noi.extensions.json
index b2bda13..146e6eb 100644
--- a/extensions/noi.extensions.json
+++ b/extensions/noi.extensions.json
@@ -37,7 +37,7 @@
{
"name": "Thinking Claude",
"description": "Makes Claude's thinking process expandable and collapsible",
- "version": "0.1.0",
+ "version": "0.1.1",
"url": "https://github.com/lencx/Noi/tree/main/extensions/thinking-claude",
"dirname": "thinking-claude",
"disabled": false
diff --git a/extensions/thinking-claude/main.js b/extensions/thinking-claude/main.js
index 53e5d8b..877da04 100644
--- a/extensions/thinking-claude/main.js
+++ b/extensions/thinking-claude/main.js
@@ -1,31 +1,86 @@
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'
+ };
+
+ static ICONS = {
+ COPY: ``,
+ TICK: ``,
+ ARROW: ``
+ };
+
+ static TIMINGS = {
+ RETRY_DELAY: 1000,
+ MUTATION_DELAY: 100,
+ CHECK_INTERVAL: 2000,
+ COPY_FEEDBACK: 2000,
+ MAX_RETRIES: 10
+ };
+
+ static STYLES = {
+ HEADER: 'display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: var(--bg-300);',
+ CODE_CONTAINER: `
+ transition: all 0.3s ease-in-out;
+ overflow: hidden;
+ max-height: 0;
+ opacity: 0;
+ padding: 0;
+ max-width: 100%;
+ display: block;
+ `,
+ CODE_ELEMENT: `
+ white-space: pre-wrap !important;
+ word-break: break-word !important;
+ overflow-wrap: break-word !important;
+ display: block !important;
+ max-width: 100% !important;
+ `
+ };
+
constructor() {
- console.log("CodeBlockCollapser: Initializing..."); // Debug log
this.initWithRetry();
}
- initWithRetry(retryCount = 0) {
- if (retryCount > 10) {
- console.log("CodeBlockCollapser: Max retries reached"); // Debug log
+ createElement(tag, className = '', innerHTML = '') {
+ const element = document.createElement(tag);
+ if (className) element.className = className;
+ if (innerHTML) element.innerHTML = innerHTML;
+ return element;
+ }
+
+ async initWithRetry(retryCount = 0) {
+ if (retryCount >= CodeBlockCollapser.TIMINGS.MAX_RETRIES) {
return;
}
- const blocks = document.querySelectorAll("pre");
+ const blocks = document.querySelectorAll(CodeBlockCollapser.SELECTORS.PRE);
if (blocks.length === 0) {
- console.log("CodeBlockCollapser: No blocks found, retrying..."); // Debug log
- setTimeout(() => this.initWithRetry(retryCount + 1), 1000);
- return;
+ await new Promise(resolve =>
+ setTimeout(resolve, CodeBlockCollapser.TIMINGS.RETRY_DELAY)
+ );
+ return this.initWithRetry(retryCount + 1);
}
- console.log(`CodeBlockCollapser: Found ${blocks.length} blocks`); // Debug log
this.init();
}
init() {
- // Initial processing
this.processExistingBlocks();
- // Watch for new content
const observer = new MutationObserver((mutations) => {
let shouldProcess = false;
@@ -40,8 +95,7 @@ class CodeBlockCollapser {
});
if (shouldProcess) {
- console.log("CodeBlockCollapser: New content detected"); // Debug log
- setTimeout(() => this.processExistingBlocks(), 100);
+ this.processExistingBlocks();
}
});
@@ -49,153 +103,167 @@ class CodeBlockCollapser {
childList: true,
subtree: true,
attributes: true,
- attributeFilter: ["data-is-streaming"],
+ attributeFilter: ["data-is-streaming"]
});
- // Additional periodic check
setInterval(() => {
this.processExistingBlocks();
}, 2000);
}
- createCopyButton() {
- const container = document.createElement("div");
- container.className =
- "from-bg-300/90 to-bg-300/70 pointer-events-auto rounded-md bg-gradient-to-b p-0.5 backdrop-blur-md";
+ setupObserver() {
+ const observer = new MutationObserver(this.handleMutations.bind(this));
+ observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ attributes: true,
+ attributeFilter: ['data-is-streaming']
+ });
+ }
- const button = document.createElement("button");
- button.className =
- "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";
+ setupPeriodicCheck() {
+ setInterval(() => {
+ if (!this.processingTimeout) {
+ this.processExistingBlocks();
+ }
+ }, CodeBlockCollapser.TIMINGS.CHECK_INTERVAL);
+ }
- const copyIcon = ``;
+ handleMutations(mutations) {
+ const hasRelevantChanges = mutations.some(mutation =>
+ mutation.addedNodes.length > 0 ||
+ (mutation.type === 'attributes' && mutation.attributeName === 'data-is-streaming')
+ );
- const tickIcon = ``;
+ if (hasRelevantChanges) {
+ this.debouncedProcess();
+ }
+ }
- const iconSpan = document.createElement("span");
- iconSpan.innerHTML = copyIcon;
+ debouncedProcess() {
+ if (this.processingTimeout) {
+ clearTimeout(this.processingTimeout);
+ }
- const textSpan = document.createElement("span");
- textSpan.className = "text-text-200 pr-0.5";
- textSpan.textContent = "Copy";
+ this.processingTimeout = setTimeout(() => {
+ this.processExistingBlocks();
+ this.processingTimeout = null;
+ }, CodeBlockCollapser.TIMINGS.MUTATION_DELAY);
+ }
- button.appendChild(iconSpan);
- button.appendChild(textSpan);
+ async 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", async () => {
- const codeText = button
- .closest("pre")
- ?.querySelector("code")?.textContent;
- if (codeText) {
+ button.addEventListener('click', async () => {
+ const codeText = button.closest(CodeBlockCollapser.SELECTORS.PRE)
+ ?.querySelector(CodeBlockCollapser.SELECTORS.CODE)?.textContent;
+
+ if (!codeText) return;
+
+ try {
await navigator.clipboard.writeText(codeText);
- iconSpan.innerHTML = tickIcon;
- textSpan.textContent = "Copied!";
+ iconSpan.innerHTML = CodeBlockCollapser.ICONS.TICK;
+ textSpan.textContent = 'Copied!';
setTimeout(() => {
- iconSpan.innerHTML = copyIcon;
- textSpan.textContent = "Copy";
- }, 2000);
+ iconSpan.innerHTML = CodeBlockCollapser.ICONS.COPY;
+ textSpan.textContent = 'Copy';
+ }, CodeBlockCollapser.TIMINGS.COPY_FEEDBACK);
+ } catch (error) {
+ console.error('Failed to copy:', error);
}
});
return container;
}
+ createToggleButton() {
+ const button = this.createElement('button', CodeBlockCollapser.CLASSES.TOGGLE_BUTTON);
+ button.innerHTML = `
+ ${CodeBlockCollapser.ICONS.ARROW}
+ View thinking process
+ `;
+ return button;
+ }
+
processExistingBlocks() {
- document.querySelectorAll("pre").forEach((pre) => {
- const header = pre.querySelector(".text-text-300");
- if (
- header &&
- header.textContent.trim() === "thinking" &&
- !pre.querySelector(".thinking-header")
- ) {
- console.log("CodeBlockCollapser: Processing block"); // Debug log
+ document.querySelectorAll(CodeBlockCollapser.SELECTORS.PRE).forEach(pre => {
+ const header = pre.querySelector(CodeBlockCollapser.SELECTORS.THINKING_LABEL);
+ if (header?.textContent.trim() === 'thinking' &&
+ !pre.querySelector(`.${CodeBlockCollapser.CLASSES.THINKING_HEADER}`)) {
this.processBlock(pre);
}
});
}
- processBlock(pre) {
- // Create header container
- const headerContainer = document.createElement("div");
- headerContainer.className = "thinking-header";
- headerContainer.style.cssText =
- "display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: var(--bg-300);";
+ setupCodeContainer(container, toggleBtn) {
+ if (!container) return;
- // Create toggle button
- const toggleBtn = document.createElement("button");
- toggleBtn.className = "flex items-center text-text-500 hover:text-text-300";
- toggleBtn.innerHTML = `
-
- View thinking process
- `;
+ container.style.cssText = CodeBlockCollapser.STYLES.CODE_CONTAINER;
- // Create copy button
- const copyBtn = this.createCopyButton();
-
- // Add buttons to header
- headerContainer.appendChild(toggleBtn);
- headerContainer.appendChild(copyBtn);
-
- // Setup code container
- const codeContainer = pre.querySelector(".code-block__code");
- if (codeContainer) {
- // Set initial styles
- codeContainer.style.transition = "all 0.3s ease-in-out";
- codeContainer.style.overflow = "hidden";
- codeContainer.style.maxHeight = "0";
- codeContainer.style.opacity = "0";
- codeContainer.style.padding = "0";
-
- toggleBtn.addEventListener("click", () => {
- const isCollapsed = codeContainer.style.maxHeight === "0px";
- const arrow = toggleBtn.querySelector("svg");
-
- if (isCollapsed) {
- codeContainer.style.maxHeight = "1000px";
- codeContainer.style.opacity = "1";
- codeContainer.style.padding = "1em";
- arrow.style.transform = "rotate(180deg)";
- toggleBtn.querySelector("span").textContent = "Hide thinking process";
- } else {
- codeContainer.style.maxHeight = "0";
- codeContainer.style.opacity = "0";
- codeContainer.style.padding = "0";
- arrow.style.transform = "rotate(0deg)";
- toggleBtn.querySelector("span").textContent = "View thinking process";
- }
- });
+ const codeElement = container.querySelector(CodeBlockCollapser.SELECTORS.CODE);
+ if (codeElement) {
+ codeElement.style.cssText = CodeBlockCollapser.STYLES.CODE_ELEMENT;
}
- // Add to DOM
- const mainContainer = pre.querySelector(".relative.flex.flex-col");
- if (mainContainer) {
- mainContainer.insertBefore(
- headerContainer,
- pre.querySelector(".code-block__code").parentElement
- );
+ this.setupToggleHandler(toggleBtn, container);
+ }
- // Hide original elements
- const thinkingLabel = pre.querySelector(".text-text-300");
- const originalCopyBtn = pre.querySelector(".pointer-events-none");
- if (thinkingLabel) thinkingLabel.style.display = "none";
- if (originalCopyBtn) originalCopyBtn.style.display = "none";
+ setupToggleHandler(toggleBtn, codeContainer) {
+ toggleBtn.addEventListener('click', () => {
+ const isCollapsed = codeContainer.style.maxHeight === '0px';
+ const arrow = toggleBtn.querySelector('svg');
+ const label = toggleBtn.querySelector('span');
+
+ Object.assign(codeContainer.style, {
+ maxHeight: isCollapsed ? '1000px' : '0',
+ opacity: isCollapsed ? '1' : '0',
+ padding: isCollapsed ? '1em' : '0'
+ });
+
+ arrow.style.transform = `rotate(${isCollapsed ? 180 : 0}deg)`;
+ label.textContent = isCollapsed ? 'Hide thinking process' : 'View thinking process';
+ });
+ }
+
+ async processBlock(pre) {
+ const headerContainer = this.createElement('div', CodeBlockCollapser.CLASSES.THINKING_HEADER);
+ headerContainer.style.cssText = CodeBlockCollapser.STYLES.HEADER;
+
+ const toggleBtn = this.createToggleButton();
+ const copyBtn = await 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);
+ }
+
+ [CodeBlockCollapser.SELECTORS.THINKING_LABEL, CodeBlockCollapser.SELECTORS.ORIGINAL_COPY_BTN]
+ .forEach(selector => {
+ const element = pre.querySelector(selector);
+ if (element) element.style.display = 'none';
+ });
}
}
}
-// Initialize with both DOMContentLoaded and load events
-document.addEventListener("DOMContentLoaded", () => {
- console.log("CodeBlockCollapser: DOMContentLoaded fired"); // Debug log
- new CodeBlockCollapser();
-});
-
-window.addEventListener("load", () => {
- console.log("CodeBlockCollapser: Load fired"); // Debug log
- new CodeBlockCollapser();
-});
-
-// Immediate initialization as well
new CodeBlockCollapser();
+
+document.addEventListener("DOMContentLoaded", () => {
+ if (!window.codeBlockCollapser) {
+ window.codeBlockCollapser = new CodeBlockCollapser();
+ }
+});
\ No newline at end of file
diff --git a/extensions/thinking-claude/manifest.json b/extensions/thinking-claude/manifest.json
index 8d14509..8151e28 100644
--- a/extensions/thinking-claude/manifest.json
+++ b/extensions/thinking-claude/manifest.json
@@ -1,10 +1,9 @@
{
"manifest_version": 3,
"name": "Thinking Claude",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "Makes Claude's thinking process expandable and collapsible",
"homepage": "https://github.com/lencx/Noi/tree/main/extensions/thinking-claude",
- "permissions": ["activeTab"],
"content_scripts": [
{
"matches": ["https://claude.ai/*"],