From b88f7d4ab96058f0c3e5a9bf4e39da7be74a03a2 Mon Sep 17 00:00:00 2001 From: saketsarin Date: Tue, 18 Feb 2025 23:26:43 +0530 Subject: [PATCH 1/7] fix: improve windows clipboard text handling --- package-lock.json | 4 ++-- src/utils/clipboard.ts | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb899e2..5dcdd0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "composer-web", - "version": "1.0.1", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "composer-web", - "version": "1.0.1", + "version": "1.0.3", "dependencies": { "puppeteer-core": "^24.2.0" }, diff --git a/src/utils/clipboard.ts b/src/utils/clipboard.ts index 12ee72e..49d1f47 100644 --- a/src/utils/clipboard.ts +++ b/src/utils/clipboard.ts @@ -46,7 +46,8 @@ export async function verifyClipboardContent( : "osascript -e \"get the clipboard as «class PNGf»\""; break; case "win32": - command = "powershell -command \"[Windows.Forms.Clipboard]::ContainsImage()\""; + command = + "powershell -command \"[Windows.Forms.Clipboard]::ContainsImage()\""; break; case "linux": command = @@ -139,10 +140,11 @@ function getClipboardTextCommand( case "darwin": return `echo "${text.replace(/"/g, "\\\"")}" | pbcopy`; case "win32": - return `powershell -command "Set-Clipboard -Value \\"${text.replace( - /"/g, - "`\"" - )}\\""`; + const escapedText = text + .replace(/'/g, "''") + .replace(/`/g, "``") + .replace(/\$/g, "`$"); + return `powershell -command "Set-Clipboard -Value '${escapedText}'"`; case "linux": return `xclip -selection clipboard -in <<< "${text.replace( /"/g, From a04835c1ab2aa5717f3485761c35554620dcaa91 Mon Sep 17 00:00:00 2001 From: saketsarin Date: Wed, 19 Feb 2025 03:27:51 +0530 Subject: [PATCH 2/7] fix: improve Windows clipboard clearing method --- src/utils/clipboard.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/clipboard.ts b/src/utils/clipboard.ts index 49d1f47..5af029c 100644 --- a/src/utils/clipboard.ts +++ b/src/utils/clipboard.ts @@ -10,7 +10,8 @@ export async function clearClipboard(): Promise { command = "osascript -e \"set the clipboard to \"\"\""; break; case "win32": - command = "powershell -command \"Set-Clipboard -Value ''\""; + command = + "powershell -command \"Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Clipboard]::Clear()\""; break; case "linux": command = "xclip -selection clipboard -i /dev/null"; From 6194d09a3e92fa67c8aa79a4f75dc7f4fb1f666e Mon Sep 17 00:00:00 2001 From: Saket Sarin Date: Thu, 20 Feb 2025 16:10:41 +0530 Subject: [PATCH 3/7] refactor: Simplify Windows clipboard operations using VSCode API --- src/utils/clipboard.ts | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/utils/clipboard.ts b/src/utils/clipboard.ts index 5af029c..0808eab 100644 --- a/src/utils/clipboard.ts +++ b/src/utils/clipboard.ts @@ -10,9 +10,8 @@ export async function clearClipboard(): Promise { command = "osascript -e \"set the clipboard to \"\"\""; break; case "win32": - command = - "powershell -command \"Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Clipboard]::Clear()\""; - break; + await vscode.env.clipboard.writeText(""); + return; case "linux": command = "xclip -selection clipboard -i /dev/null"; break; @@ -47,9 +46,14 @@ export async function verifyClipboardContent( : "osascript -e \"get the clipboard as «class PNGf»\""; break; case "win32": - command = - "powershell -command \"[Windows.Forms.Clipboard]::ContainsImage()\""; - break; + if (type === "text") { + const text = await vscode.env.clipboard.readText(); + if (!text) { + throw new Error("Failed to verify text in clipboard"); + } + return; + } + return; // skip image verification for windows case "linux": command = type === "text" @@ -77,7 +81,7 @@ export async function copyImageToClipboard(imagePath: string): Promise { if (command) { await new Promise((resolve, reject) => { - exec(command, { timeout: 500 }, (error: Error | null) => { + exec(command, { timeout: platform === "win32" ? 2000 : 500 }, (error: Error | null) => { if (error) { vscode.window.showErrorMessage( `Failed to copy image to clipboard: ${error.message}` @@ -93,6 +97,12 @@ export async function copyImageToClipboard(imagePath: string): Promise { export async function copyTextToClipboard(text: string): Promise { const platform = process.platform; + + if (platform === "win32") { + await vscode.env.clipboard.writeText(text); + return; + } + const command = getClipboardTextCommand(platform, text); if (!command) { return; @@ -125,7 +135,7 @@ function getClipboardImageCommand( set the clipboard to imageData '`; case "win32": - return `powershell -command \"Add-Type -AssemblyName System.Windows.Forms;$img=[System.Drawing.Image]::FromFile(\"${imagePath}\");[System.Windows.Forms.Clipboard]::SetImage($img);$img.Dispose()\"`; + return `powershell -NoProfile -Command "[Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.Clipboard]::SetImage([System.Drawing.Image]::FromFile('${imagePath.replace(/'/g, "''")}'))"`; case "linux": return `xclip -selection clipboard -t image/png -i "${imagePath}"`; default: @@ -141,11 +151,10 @@ function getClipboardTextCommand( case "darwin": return `echo "${text.replace(/"/g, "\\\"")}" | pbcopy`; case "win32": - const escapedText = text - .replace(/'/g, "''") - .replace(/`/g, "``") - .replace(/\$/g, "`$"); - return `powershell -command "Set-Clipboard -Value '${escapedText}'"`; + return `powershell -command "Set-Clipboard -Value \\"${text.replace( + /"/g, + "`\"" + )}\\""`; case "linux": return `xclip -selection clipboard -in <<< "${text.replace( /"/g, From f2b2b2105703e9eab669a04f518f5feb5e57bf04 Mon Sep 17 00:00:00 2001 From: saketsarin Date: Thu, 20 Feb 2025 16:21:33 +0530 Subject: [PATCH 4/7] refactor: Improve error handling and retry logic for composer integration --- src/composer/integration.ts | 77 ++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 44 deletions(-) diff --git a/src/composer/integration.ts b/src/composer/integration.ts index 452f382..c9d8182 100644 --- a/src/composer/integration.ts +++ b/src/composer/integration.ts @@ -51,67 +51,56 @@ export class ComposerIntegration { logs?: LogData ): Promise { const maxRetries = 2; - let attempt = 0; + let imageAttempt = 0; + let textAttempt = 0; + let imageSuccess = false; + let textSuccess = false; - while (attempt <= maxRetries) { + const formattedLogs = logs ? this.formatLogs(logs) : ""; + + while ( + (screenshot && !imageSuccess && imageAttempt <= maxRetries) || + (formattedLogs && !textSuccess && textAttempt <= maxRetries) + ) { try { await this.openComposer(); - const formattedLogs = logs ? this.formatLogs(logs) : ""; - - if (screenshot) { - let imageError: Error | null = null; - let textError: Error | null = null; - + if (screenshot && !imageSuccess) { await clearClipboard(); - try { await this.sendImageToComposer(screenshot); await verifyClipboardContent("image"); + imageSuccess = true; } catch (err) { - imageError = err as Error; - } - - if (formattedLogs) { - await delay(50); - try { - await clearClipboard(); - await this.prepareTextForComposer(formattedLogs); - await verifyClipboardContent("text"); - } catch (err) { - textError = err as Error; + if (imageAttempt === maxRetries) { + throw new Error(`Failed to send image: ${String(err)}`); } + imageAttempt++; + await delay(100); } + } - if (imageError || textError) { - if (attempt < maxRetries) { - attempt++; - await delay(100); - continue; - } - const errors: string[] = []; - if (imageError) { - errors.push(`Image: ${String(imageError)}`); - } - if (textError) { - errors.push(`Logs: ${String(textError)}`); + if (formattedLogs && !textSuccess) { + await delay(50); + await clearClipboard(); + try { + await this.prepareTextForComposer(formattedLogs); + await verifyClipboardContent("text"); + textSuccess = true; + } catch (err) { + if (textAttempt === maxRetries) { + throw new Error(`Failed to send logs: ${String(err)}`); } - throw new Error(`Failed to send data: ${errors.join(", ")}`); + textAttempt++; + await delay(100); } - } else if (formattedLogs) { - await clearClipboard(); - await this.prepareTextForComposer(formattedLogs); - await verifyClipboardContent("text"); } - await this.showSuccessNotification(); - return; - } catch (error) { - if (attempt < maxRetries) { - attempt++; - await delay(100); - continue; + if ((!screenshot || imageSuccess) && (!formattedLogs || textSuccess)) { + await this.showSuccessNotification(); + return; } + } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); vscode.window.showErrorMessage( From fa65b041317aa11fae7e50c8abe583fa71ff4fd5 Mon Sep 17 00:00:00 2001 From: saketsarin Date: Thu, 20 Feb 2025 16:31:59 +0530 Subject: [PATCH 5/7] feat: Centralize toast notifications with new ToastService --- src/browser/monitor.ts | 26 ++---- src/commands/index.ts | 180 +++++++++++++++--------------------- src/composer/integration.ts | 18 ++-- src/extension.ts | 4 +- src/utils/clipboard.ts | 38 +++++--- src/utils/toast.ts | 96 +++++++++++++++++++ 6 files changed, 213 insertions(+), 149 deletions(-) create mode 100644 src/utils/toast.ts diff --git a/src/browser/monitor.ts b/src/browser/monitor.ts index 51a86b9..9da9d70 100644 --- a/src/browser/monitor.ts +++ b/src/browser/monitor.ts @@ -3,6 +3,7 @@ import * as puppeteer from "puppeteer-core"; import { EventEmitter } from "events"; import { BrowserLog, MonitoredPage, NetworkRequest, LogData } from "../types"; import { ConfigManager } from "../config"; +import { ToastService } from "../utils/toast"; export class BrowserMonitor extends EventEmitter { private static instance: BrowserMonitor; @@ -19,6 +20,7 @@ export class BrowserMonitor extends EventEmitter { private configManager: ConfigManager; private disconnectEmitter = new vscode.EventEmitter(); private healthCheckInterval: NodeJS.Timeout | null = null; + private toastService: ToastService; private constructor() { super(); @@ -28,6 +30,7 @@ export class BrowserMonitor extends EventEmitter { ); this.statusBarItem.command = "web-preview.smartCapture"; this.configManager = ConfigManager.getInstance(); + this.toastService = ToastService.getInstance(); this.updateStatusBar(); } @@ -99,7 +102,7 @@ export class BrowserMonitor extends EventEmitter { } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Failed to connect: ${errorMessage}`); + this.toastService.showError(`Failed to connect: ${errorMessage}`); this.disconnect(); } } @@ -119,7 +122,7 @@ export class BrowserMonitor extends EventEmitter { client.send("Log.enable"), ]); } catch (error) { - vscode.window.showErrorMessage("Failed to initialize browser session"); + this.toastService.showError("Failed to initialize browser session"); await this.disconnect(); return; } @@ -209,33 +212,24 @@ export class BrowserMonitor extends EventEmitter { } private async showSuccessNotification() { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Successfully connected to tab", - cancellable: false, - }, - async (progress) => { + await this.toastService.showProgress( + "Successfully connected to tab", + async () => { await new Promise((resolve) => setTimeout(resolve, 2000)); - progress.report({ increment: 100 }); } ); } private async handleSessionError() { if (this.isConnected) { - vscode.window.showErrorMessage( - "Browser session disconnected. Please reconnect to continue." - ); + this.toastService.showSessionDisconnected(); await this.disconnect(); } } private async handlePageClosed() { if (this.isConnected) { - vscode.window.showWarningMessage( - "The monitored tab was closed. Monitoring has stopped." - ); + this.toastService.showTabClosed(); await this.disconnect(); } } diff --git a/src/commands/index.ts b/src/commands/index.ts index 4b29c83..65ad4a2 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,12 +1,20 @@ -import * as vscode from "vscode"; import { BrowserMonitor } from "../browser/monitor"; import { ComposerIntegration } from "../composer/integration"; +import { ToastService } from "../utils/toast"; export class CommandHandlers { + private browserMonitor: BrowserMonitor; + private composerIntegration: ComposerIntegration; + private toastService: ToastService; + constructor( - private readonly browserMonitor: BrowserMonitor, - private readonly composerIntegration: ComposerIntegration - ) {} + browserMonitor: BrowserMonitor, + composerIntegration: ComposerIntegration + ) { + this.browserMonitor = browserMonitor; + this.composerIntegration = composerIntegration; + this.toastService = ToastService.getInstance(); + } public async handleSmartCapture(): Promise { if (this.browserMonitor.isPageConnected()) { @@ -18,101 +26,72 @@ export class CommandHandlers { public async handleClearLogs(): Promise { if (!this.browserMonitor.isPageConnected()) { - vscode.window.showErrorMessage( - "No browser tab connected. Please connect a tab first." - ); + this.toastService.showNoTabConnected(); return; } - const result = await vscode.window.showWarningMessage( - "Are you sure you want to clear all browser logs?", - "Yes", - "No" + const confirmed = await this.toastService.showConfirmation( + "Are you sure you want to clear all browser logs?" ); - if (result === "Yes") { + if (confirmed) { this.browserMonitor.clearLogs(); - vscode.window.showInformationMessage("Browser logs cleared successfully"); + this.toastService.showLogsClearedSuccess(); } } public async handleSendLogs(): Promise { if (!this.browserMonitor.isPageConnected()) { - vscode.window.showErrorMessage( - "No browser tab connected. Please connect a tab first." - ); + this.toastService.showNoTabConnected(); return; } try { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Sending Logs", - cancellable: false, - }, - async () => { - await this.composerIntegration.sendToComposer( - undefined, - this.browserMonitor.getLogs() - ); - } - ); - vscode.window.showInformationMessage( - "Logs sent to Composer successfully" - ); + await this.toastService.showProgress("Sending Logs", async () => { + await this.composerIntegration.sendToComposer( + undefined, + this.browserMonitor.getLogs() + ); + }); + this.toastService.showLogsSentSuccess(); } catch (error: unknown) { const msg = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Failed to send logs: ${msg}`); + this.toastService.showError(`Failed to send logs: ${msg}`); } } public async handleSendScreenshot(): Promise { if (!this.browserMonitor.isPageConnected()) { - vscode.window.showErrorMessage( - "No browser tab connected. Please connect a tab first." - ); + this.toastService.showNoTabConnected(); return; } try { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Capturing Screenshot", - cancellable: false, - }, - async (progress) => { - const page = await this.browserMonitor.getPageForScreenshot(); - if (!page) { - throw new Error("Page not accessible"); - } - - const screenshot = await page.screenshot({ - type: "png", - fullPage: true, - encoding: "binary", - }); - - progress.report({ message: "Sending to Composer..." }); - await this.composerIntegration.sendToComposer( - Buffer.from(screenshot), - undefined - ); + await this.toastService.showProgress("Capturing Screenshot", async () => { + const page = await this.browserMonitor.getPageForScreenshot(); + if (!page) { + throw new Error("Page not accessible"); } - ); - vscode.window.showInformationMessage( - "Screenshot sent to Composer successfully" - ); + + const screenshot = await page.screenshot({ + type: "png", + fullPage: true, + encoding: "binary", + }); + + await this.composerIntegration.sendToComposer( + Buffer.from(screenshot), + undefined + ); + }); + this.toastService.showScreenshotSentSuccess(); } catch (error: unknown) { const msg = error instanceof Error ? error.message : String(error); if (msg.includes("Target closed") || msg.includes("Target crashed")) { - vscode.window.showErrorMessage( - "Browser page was closed or crashed. Please reconnect to continue monitoring." - ); + this.toastService.showPageClosedOrCrashed(); await this.browserMonitor.disconnect(); } else { - vscode.window.showErrorMessage(`Screenshot capture failed: ${msg}`); + this.toastService.showError(`Screenshot capture failed: ${msg}`); } } } @@ -120,64 +99,49 @@ export class CommandHandlers { private async handleConnect(): Promise { try { await this.browserMonitor.connect(); - vscode.window.showInformationMessage( - "Successfully connected to browser tab" - ); + this.toastService.showConnectionSuccess(); } catch (error: unknown) { const msg = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Failed to connect: ${msg}`); + this.toastService.showError(`Failed to connect: ${msg}`); } } private async handleCapture(): Promise { try { if (!this.browserMonitor.isPageConnected()) { - vscode.window.showErrorMessage( - "No browser tab connected. Please connect a tab first." - ); + this.toastService.showNoTabConnected(); return; } - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Capturing Tab Info", - cancellable: false, - }, - async (progress) => { - const activePage = this.browserMonitor.getActivePage(); - if (!activePage) { - throw new Error("No active page found"); - } - - progress.report({ message: "Taking screenshot..." }); - const page = await this.browserMonitor.getPageForScreenshot(); - if (!page) { - throw new Error("Page not accessible"); - } - - const screenshot = await page.screenshot({ - type: "png", - fullPage: true, - encoding: "binary", - }); - - progress.report({ message: "Sending to Composer..." }); - await this.composerIntegration.sendToComposer( - Buffer.from(screenshot), - this.browserMonitor.getLogs() - ); + await this.toastService.showProgress("Capturing Tab Info", async () => { + const activePage = this.browserMonitor.getActivePage(); + if (!activePage) { + throw new Error("No active page found"); + } + + const page = await this.browserMonitor.getPageForScreenshot(); + if (!page) { + throw new Error("Page not accessible"); } - ); + + const screenshot = await page.screenshot({ + type: "png", + fullPage: true, + encoding: "binary", + }); + + await this.composerIntegration.sendToComposer( + Buffer.from(screenshot), + this.browserMonitor.getLogs() + ); + }); } catch (error: unknown) { const msg = error instanceof Error ? error.message : String(error); if (msg.includes("Target closed") || msg.includes("Target crashed")) { - vscode.window.showErrorMessage( - "Browser page was closed or crashed. Please reconnect to continue monitoring." - ); + this.toastService.showPageClosedOrCrashed(); await this.browserMonitor.disconnect(); } else { - vscode.window.showErrorMessage(`Capture failed: ${msg}`); + this.toastService.showError(`Capture failed: ${msg}`); } } } diff --git a/src/composer/integration.ts b/src/composer/integration.ts index c9d8182..65acdab 100644 --- a/src/composer/integration.ts +++ b/src/composer/integration.ts @@ -9,14 +9,17 @@ import { copyTextToClipboard, delay, } from "../utils/clipboard"; +import { ToastService } from "../utils/toast"; export class ComposerIntegration { private static instance: ComposerIntegration; private readonly context: vscode.ExtensionContext; private composerOpened: boolean = false; + private toastService: ToastService; private constructor(context: vscode.ExtensionContext) { this.context = context; + this.toastService = ToastService.getInstance(); } public static getInstance( @@ -39,7 +42,7 @@ export class ComposerIntegration { this.composerOpened = true; await delay(100); } catch { - vscode.window.showErrorMessage( + this.toastService.showError( "Failed to open composer. Please make sure Cursor is installed and configured." ); return; @@ -103,7 +106,7 @@ export class ComposerIntegration { } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage( + this.toastService.showError( `Failed to send data to composer: ${errorMessage}` ); throw error; @@ -160,15 +163,10 @@ export class ComposerIntegration { } private async showSuccessNotification() { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: "Successfully sent to Composer", - cancellable: false, - }, - async (progress) => { + await this.toastService.showProgress( + "Successfully sent to Composer", + async () => { await delay(2000); - progress.report({ increment: 500 }); } ); } diff --git a/src/extension.ts b/src/extension.ts index 20d6857..720139e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { ComposerIntegration } from "./composer/integration"; import { BrowserMonitor } from "./browser/monitor"; import { CommandHandlers } from "./commands"; +import { ToastService } from "./utils/toast"; export function activate(context: vscode.ExtensionContext) { const composerIntegration = ComposerIntegration.getInstance(context); @@ -10,6 +11,7 @@ export function activate(context: vscode.ExtensionContext) { browserMonitor, composerIntegration ); + const toastService = ToastService.getInstance(); context.subscriptions.push( vscode.commands.registerCommand("web-preview.smartCapture", () => @@ -28,7 +30,7 @@ export function activate(context: vscode.ExtensionContext) { ); browserMonitor.onDisconnect(() => { - vscode.window.showWarningMessage("Browser tab disconnected"); + toastService.showBrowserDisconnected(); }); } diff --git a/src/utils/clipboard.ts b/src/utils/clipboard.ts index 0808eab..bad2b38 100644 --- a/src/utils/clipboard.ts +++ b/src/utils/clipboard.ts @@ -1,5 +1,8 @@ import { exec } from "child_process"; import * as vscode from "vscode"; +import { ToastService } from "./toast"; + +const toastService = ToastService.getInstance(); export async function clearClipboard(): Promise { const platform = process.platform; @@ -52,8 +55,8 @@ export async function verifyClipboardContent( throw new Error("Failed to verify text in clipboard"); } return; - } - return; // skip image verification for windows + } + return; // skip image verification for windows case "linux": command = type === "text" @@ -81,23 +84,27 @@ export async function copyImageToClipboard(imagePath: string): Promise { if (command) { await new Promise((resolve, reject) => { - exec(command, { timeout: platform === "win32" ? 2000 : 500 }, (error: Error | null) => { - if (error) { - vscode.window.showErrorMessage( - `Failed to copy image to clipboard: ${error.message}` - ); - reject(error); - } else { - resolve(); + exec( + command, + { timeout: platform === "win32" ? 2000 : 500 }, + (error: Error | null) => { + if (error) { + toastService.showError( + `Failed to copy image to clipboard: ${error.message}` + ); + reject(error); + } else { + resolve(); + } } - }); + ); }); } } export async function copyTextToClipboard(text: string): Promise { const platform = process.platform; - + if (platform === "win32") { await vscode.env.clipboard.writeText(text); return; @@ -110,7 +117,7 @@ export async function copyTextToClipboard(text: string): Promise { await new Promise((resolve, reject) => { exec(command, { timeout: 500 }, (error: Error | null) => { if (error) { - vscode.window.showErrorMessage( + toastService.showError( `Failed to copy text logs to clipboard: ${error.message}` ); reject(error); @@ -135,7 +142,10 @@ function getClipboardImageCommand( set the clipboard to imageData '`; case "win32": - return `powershell -NoProfile -Command "[Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.Clipboard]::SetImage([System.Drawing.Image]::FromFile('${imagePath.replace(/'/g, "''")}'))"`; + return `powershell -NoProfile -Command "[Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.Clipboard]::SetImage([System.Drawing.Image]::FromFile('${imagePath.replace( + /'/g, + "''" + )}'))"`; case "linux": return `xclip -selection clipboard -t image/png -i "${imagePath}"`; default: diff --git a/src/utils/toast.ts b/src/utils/toast.ts new file mode 100644 index 0000000..1eea5b3 --- /dev/null +++ b/src/utils/toast.ts @@ -0,0 +1,96 @@ +import * as vscode from "vscode"; + +export class ToastService { + private static instance: ToastService; + + private constructor() {} + + public static getInstance(): ToastService { + if (!ToastService.instance) { + ToastService.instance = new ToastService(); + } + return ToastService.instance; + } + + public showError(message: string) { + vscode.window.showErrorMessage(message); + } + + public showWarning(message: string) { + vscode.window.showWarningMessage(message); + } + + public showInfo(message: string) { + vscode.window.showInformationMessage(message); + } + + public async showConfirmation(message: string): Promise { + const result = await vscode.window.showWarningMessage(message, "Yes", "No"); + return result === "Yes"; + } + + public async showProgress( + title: string, + task: () => Promise, + cancellable: boolean = false + ) { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title, + cancellable, + }, + async (progress) => { + try { + await task(); + progress.report({ increment: 100 }); + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + this.showError(msg); + throw error; + } + } + ); + } + + // Common toast messages + public showBrowserDisconnected() { + this.showWarning("Browser tab disconnected"); + } + + public showNoTabConnected() { + this.showError("No browser tab connected. Please connect a tab first."); + } + + public showTabClosed() { + this.showWarning("The monitored tab was closed. Monitoring has stopped."); + } + + public showSessionDisconnected() { + this.showError( + "Browser session disconnected. Please reconnect to continue." + ); + } + + public showConnectionSuccess() { + this.showInfo("Successfully connected to browser tab"); + } + + public showLogsClearedSuccess() { + this.showInfo("Browser logs cleared successfully"); + } + + public showLogsSentSuccess() { + this.showInfo("Logs sent to Composer successfully"); + } + + public showScreenshotSentSuccess() { + this.showInfo("Screenshot sent to Composer successfully"); + } + + public showPageClosedOrCrashed() { + this.showError( + "Browser page was closed or crashed. Please reconnect to continue monitoring." + ); + } +} From d84626a076628af39ad536bc354b19ef5df5ca71 Mon Sep 17 00:00:00 2001 From: saketsarin Date: Thu, 20 Feb 2025 16:58:20 +0530 Subject: [PATCH 6/7] chore: Prepare v1.0.4 release with community guidelines and project improvements --- .github/ISSUE_TEMPLATE/bug_report.md | 40 +++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 22 ++++++ .github/pull_request_template.md | 34 +++++++++ CHANGELOG.md | 22 ++++++ CODE_OF_CONDUCT.md | 63 ++++++++++++++++ CONTRIBUTING.md | 88 +++++++++++++++++++++++ README.md | 27 +++++++ package.json | 2 +- 8 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/pull_request_template.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..57a1458 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] " +labels: bug +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment (please complete the following information):** + +- OS: [e.g. macOS 13.1] +- Chrome/Chromium Version: [e.g. 120.0.6099.129] +- Cursor Version: [e.g. 0.8.0] +- Node.js Version: [e.g. 18.0.0] + +**Additional context** +Add any other context about the problem here. + +**Console Output** + +``` +If you have any relevant console output, paste it here. +``` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..a6e2def --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE] " +labels: enhancement +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +**Potential Implementation** +If you have any ideas about how this could be implemented, share them here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..877d965 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,34 @@ +## Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update + +## How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. + +- [ ] Test A +- [ ] Test B + +## Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules + +## Screenshots (if appropriate): diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b66f9e..8532b75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.4] - 2025-02-20 + +### Added + +- Code of Conduct for community guidelines +- Comprehensive CONTRIBUTING.md guidelines +- New ToastService for centralized notification management +- Enhanced session stability and connection handling + +### Changed + +- Improved error handling and retry logic in composer integration +- Simplified Windows clipboard operations using VSCode API +- Updated documentation for clarity and completeness +- Improved project structure + +### Fixed + +- Windows clipboard text handling and clearing methods +- Enhanced browser session stability +- Improved connection handling and recovery + ## [1.0.3] - 2025-02-18 ### Added diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b17cd68 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,63 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement. +All complaints will be reviewed and investigated promptly and fairly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5170355 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,88 @@ +# Contributing to Composer Web Extension + +First off, thank you for considering contributing to Composer Web Extension! It's people like you that make this extension such a great tool. + +## Code of Conduct + +This project and everyone participating in it is governed by our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. + +## How Can I Contribute? + +### Reporting Bugs + +Before creating bug reports, please check the existing issues as you might find out that you don't need to create one. When you are creating a bug report, please include as many details as possible: + +- Use a clear and descriptive title +- Describe the exact steps which reproduce the problem +- Provide specific examples to demonstrate the steps +- Describe the behavior you observed after following the steps +- Explain which behavior you expected to see instead and why +- Include screenshots if possible +- Include your environment details: + - OS version + - Chrome/Chromium version + - Cursor version + - Node.js version + +### Suggesting Enhancements + +Enhancement suggestions are tracked as GitHub issues. When creating an enhancement suggestion, please include: + +- A clear and descriptive title +- A detailed description of the proposed functionality +- Explain why this enhancement would be useful +- List any similar features in other extensions if you know of any +- Include mockups or examples if applicable + +### Pull Requests + +- Fill in the required template +- Do not include issue numbers in the PR title +- Include screenshots and animated GIFs in your pull request whenever possible +- Follow our coding conventions and style guide +- Document new code +- End all files with a newline + +## Development Process + +1. Fork the repo and create your branch from `main` +2. Run `npm install` to install dependencies +3. Make your changes +4. Add tests if applicable +5. Run `npm test` to ensure nothing is broken +6. Update documentation if needed +7. Create your pull request + +### Local Development Setup + +```bash +# Clone your fork +git clone https://github.com/your-username/composer-web.git + +# Install dependencies +npm install + +# Start development +npm run watch + +# Run tests +npm test +``` + +### Coding Style + +- Use 2 spaces for indentation +- Use semicolons +- Follow TypeScript best practices +- Write meaningful commit messages following [Conventional Commits](https://www.conventionalcommits.org/) + +## Community + +- Join our discussions in GitHub issues +- Follow our updates + +## Questions? + +Feel free to open an issue with your question or reach out to the maintainers. + +Thank you for contributing! 🎉 diff --git a/README.md b/README.md index 2078cab..2988f81 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,33 @@ Available through Command Palette (`Cmd/Ctrl + Shift + P`) or keyboard shortcuts - Google Chrome/Chromium - Node.js ≥ 18.0.0 +## Contributing + +We welcome contributions from the community! Here's how you can help: + +### 🐛 Found a Bug? + +- **Ensure the bug hasn't already been reported** by searching our [Issues](../../issues) +- If you can't find an existing issue, [open a new bug report](../../issues/new?template=bug_report.md) using our bug report template + +### 💡 Have a Feature Idea? + +- Check our [Issues](../../issues) to see if it's already suggested +- If not, [create a feature request](../../issues/new?template=feature_request.md) using our feature request template + +### 🛠️ Want to Contribute Code? + +1. Read our [Contributing Guide](CONTRIBUTING.md) +2. Fork the repository +3. Create your feature branch +4. Make your changes +5. Open a [Pull Request](../../pulls) using our PR template + +For more details, check out our: + +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Contributing Guidelines](CONTRIBUTING.md) + ## License MIT - See LICENSE file for details diff --git a/package.json b/package.json index cb7e161..f093ef6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "composer-web", "displayName": "Composer Web", "description": "A powerful browser integration extension for Cursor that captures live browser content and logs directly into Composer", - "version": "1.0.3", + "version": "1.0.4", "publisher": "saketsarin", "icon": "assets/icon.png", "private": false, From 6cc88802314f2aeaa962288c69b02a5282dc2c44 Mon Sep 17 00:00:00 2001 From: saketsarin Date: Thu, 20 Feb 2025 17:03:17 +0530 Subject: [PATCH 7/7] chore: add config for issue template --- .github/ISSUE_TEMPLATE/config.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..987af1a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Questions & Discussions + url: https://github.com/saketsarin/composer-web/discussions + about: Please ask and answer questions here. + - name: Documentation + url: https://github.com/saketsarin/composer-web/blob/main/README.md + about: Check our documentation for guides and common solutions. \ No newline at end of file