diff options
Diffstat (limited to 'apps/browser-extension')
24 files changed, 1556 insertions, 1521 deletions
diff --git a/apps/browser-extension/entrypoints/background.ts b/apps/browser-extension/entrypoints/background.ts index 9d46a5e9..b71b54c7 100644 --- a/apps/browser-extension/entrypoints/background.ts +++ b/apps/browser-extension/entrypoints/background.ts @@ -1,52 +1,52 @@ -import { getDefaultProject, saveMemory, searchMemories } from "../utils/api" +import { getDefaultProject, saveMemory, searchMemories } from "../utils/api"; import { CONTAINER_TAGS, CONTEXT_MENU_IDS, MESSAGE_TYPES, POSTHOG_EVENT_KEY, -} from "../utils/constants" -import { trackEvent } from "../utils/posthog" -import { captureTwitterTokens } from "../utils/twitter-auth" +} from "../utils/constants"; +import { trackEvent } from "../utils/posthog"; +import { captureTwitterTokens } from "../utils/twitter-auth"; import { type TwitterImportConfig, TwitterImporter, -} from "../utils/twitter-import" +} from "../utils/twitter-import"; import type { ExtensionMessage, MemoryData, MemoryPayload, -} from "../utils/types" +} from "../utils/types"; export default defineBackground(() => { - let twitterImporter: TwitterImporter | null = null + let twitterImporter: TwitterImporter | null = null; browser.runtime.onInstalled.addListener(async (details) => { browser.contextMenus.create({ id: CONTEXT_MENU_IDS.SAVE_TO_SUPERMEMORY, title: "sync to supermemory", contexts: ["selection", "page", "link"], - }) + }); if (details.reason === "install") { await trackEvent("extension_installed", { reason: details.reason, version: browser.runtime.getManifest().version, - }) + }); browser.tabs.create({ url: browser.runtime.getURL("/welcome.html"), - }) + }); } - }) + }); // Intercept Twitter requests to capture authentication headers. browser.webRequest.onBeforeSendHeaders.addListener( (details) => { - captureTwitterTokens(details) - return {} + captureTwitterTokens(details); + return {}; }, { urls: ["*://x.com/*", "*://twitter.com/*"] }, ["requestHeaders", "extraHeaders"], - ) + ); // Handle context menu clicks. browser.contextMenus.onClicked.addListener(async (info, tab) => { @@ -56,27 +56,27 @@ export default defineBackground(() => { await browser.tabs.sendMessage(tab.id, { action: MESSAGE_TYPES.SAVE_MEMORY, actionSource: "context_menu", - }) + }); } catch (error) { - console.error("Failed to send message to content script:", error) + console.error("Failed to send message to content script:", error); } } } - }) + }); // Send message to current active tab. const sendMessageToCurrentTab = async (message: string) => { const tabs = await browser.tabs.query({ active: true, currentWindow: true, - }) + }); if (tabs.length > 0 && tabs[0].id) { await browser.tabs.sendMessage(tabs[0].id, { type: MESSAGE_TYPES.IMPORT_UPDATE, importedMessage: message, - }) + }); } - } + }; /** * Send import completion message @@ -85,14 +85,14 @@ export default defineBackground(() => { const tabs = await browser.tabs.query({ active: true, currentWindow: true, - }) + }); if (tabs.length > 0 && tabs[0].id) { await browser.tabs.sendMessage(tabs[0].id, { type: MESSAGE_TYPES.IMPORT_DONE, totalImported, - }) + }); } - } + }; /** * Save memory to supermemory API @@ -102,14 +102,14 @@ export default defineBackground(() => { actionSource: string, ): Promise<{ success: boolean; data?: unknown; error?: string }> => { try { - let containerTag: string = CONTAINER_TAGS.DEFAULT_PROJECT + let containerTag: string = CONTAINER_TAGS.DEFAULT_PROJECT; try { - const defaultProject = await getDefaultProject() + const defaultProject = await getDefaultProject(); if (defaultProject?.containerTag) { - containerTag = defaultProject.containerTag + containerTag = defaultProject.containerTag; } } catch (error) { - console.warn("Failed to get default project, using fallback:", error) + console.warn("Failed to get default project, using fallback:", error); } const payload: MemoryPayload = { @@ -118,48 +118,48 @@ export default defineBackground(() => { data.content || `${data.highlightedText}\n\n${data.html}\n\n${data?.url}`, metadata: { sm_source: "consumer" }, - } + }; - const responseData = await saveMemory(payload) + const responseData = await saveMemory(payload); await trackEvent(POSTHOG_EVENT_KEY.SAVE_MEMORY_ATTEMPTED, { source: `${POSTHOG_EVENT_KEY.SOURCE}_${actionSource}`, has_highlight: !!data.highlightedText, url_domain: data.url ? new URL(data.url).hostname : undefined, - }) + }); - return { success: true, data: responseData } + return { success: true, data: responseData }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error", - } + }; } - } + }; const getRelatedMemories = async ( data: string, eventSource: string, ): Promise<{ success: boolean; data?: unknown; error?: string }> => { try { - const responseData = await searchMemories(data) + const responseData = await searchMemories(data); const response = responseData as { - results?: Array<{ memory?: string }> - } - const memories: string[] = [] + results?: Array<{ memory?: string }>; + }; + const memories: string[] = []; response.results?.forEach((result, index) => { - memories.push(`${index + 1}. ${result.memory} \n`) - }) - console.log("Memories:", memories) - await trackEvent(eventSource) - return { success: true, data: memories } + memories.push(`${index + 1}. ${result.memory} \n`); + }); + console.log("Memories:", memories); + await trackEvent(eventSource); + return { success: true, data: memories }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error", - } + }; } - } + }; /** * Handle extension messages @@ -172,83 +172,83 @@ export default defineBackground(() => { onProgress: sendMessageToCurrentTab, onComplete: sendImportDoneMessage, onError: async (error: Error) => { - await sendMessageToCurrentTab(`Error: ${error.message}`) + await sendMessageToCurrentTab(`Error: ${error.message}`); }, - } + }; - twitterImporter = new TwitterImporter(importConfig) - twitterImporter.startImport().catch(console.error) - sendResponse({ success: true }) - return true + twitterImporter = new TwitterImporter(importConfig); + twitterImporter.startImport().catch(console.error); + sendResponse({ success: true }); + return true; } // Handle regular memory save request if (message.action === MESSAGE_TYPES.SAVE_MEMORY) { - ;(async () => { + (async () => { try { const result = await saveMemoryToSupermemory( message.data as MemoryData, message.actionSource || "unknown", - ) - sendResponse(result) + ); + sendResponse(result); } catch (error) { sendResponse({ success: false, error: error instanceof Error ? error.message : "Unknown error", - }) + }); } - })() - return true + })(); + return true; } if (message.action === MESSAGE_TYPES.GET_RELATED_MEMORIES) { - ;(async () => { + (async () => { try { const result = await getRelatedMemories( message.data as string, message.actionSource || "unknown", - ) - sendResponse(result) + ); + sendResponse(result); } catch (error) { sendResponse({ success: false, error: error instanceof Error ? error.message : "Unknown error", - }) + }); } - })() - return true + })(); + return true; } if (message.action === MESSAGE_TYPES.CAPTURE_PROMPT) { - ;(async () => { + (async () => { try { const messageData = message.data as { - prompt: string - platform: string - source: string - } - console.log("=== PROMPT CAPTURED ===") - console.log(messageData) - console.log("========================") + prompt: string; + platform: string; + source: string; + }; + console.log("=== PROMPT CAPTURED ==="); + console.log(messageData); + console.log("========================"); const memoryData: MemoryData = { content: messageData.prompt, - } + }; const result = await saveMemoryToSupermemory( memoryData, `prompt_capture_${messageData.platform}`, - ) - sendResponse(result) + ); + sendResponse(result); } catch (error) { sendResponse({ success: false, error: error instanceof Error ? error.message : "Unknown error", - }) + }); } - })() - return true + })(); + return true; } }, - ) -}) + ); +}); diff --git a/apps/browser-extension/entrypoints/content/chatgpt.ts b/apps/browser-extension/entrypoints/content/chatgpt.ts index 73e0354f..4657acb7 100644 --- a/apps/browser-extension/entrypoints/content/chatgpt.ts +++ b/apps/browser-extension/entrypoints/content/chatgpt.ts @@ -5,78 +5,78 @@ import { POSTHOG_EVENT_KEY, STORAGE_KEYS, UI_CONFIG, -} from "../../utils/constants" +} from "../../utils/constants"; import { createChatGPTInputBarElement, DOMUtils, -} from "../../utils/ui-components" +} from "../../utils/ui-components"; -let chatGPTDebounceTimeout: NodeJS.Timeout | null = null -let chatGPTRouteObserver: MutationObserver | null = null -let chatGPTUrlCheckInterval: NodeJS.Timeout | null = null -let chatGPTObserverThrottle: NodeJS.Timeout | null = null +let chatGPTDebounceTimeout: NodeJS.Timeout | null = null; +let chatGPTRouteObserver: MutationObserver | null = null; +let chatGPTUrlCheckInterval: NodeJS.Timeout | null = null; +let chatGPTObserverThrottle: NodeJS.Timeout | null = null; export function initializeChatGPT() { if (!DOMUtils.isOnDomain(DOMAINS.CHATGPT)) { - return + return; } if (document.body.hasAttribute("data-chatgpt-initialized")) { - return + return; } setTimeout(() => { - addSupermemoryButtonToMemoriesDialog() - addSaveChatGPTElementBeforeComposerBtn() - setupChatGPTAutoFetch() - }, 2000) + addSupermemoryButtonToMemoriesDialog(); + addSaveChatGPTElementBeforeComposerBtn(); + setupChatGPTAutoFetch(); + }, 2000); - setupChatGPTPromptCapture() + setupChatGPTPromptCapture(); - setupChatGPTRouteChangeDetection() + setupChatGPTRouteChangeDetection(); - document.body.setAttribute("data-chatgpt-initialized", "true") + document.body.setAttribute("data-chatgpt-initialized", "true"); } function setupChatGPTRouteChangeDetection() { if (chatGPTRouteObserver) { - chatGPTRouteObserver.disconnect() + chatGPTRouteObserver.disconnect(); } if (chatGPTUrlCheckInterval) { - clearInterval(chatGPTUrlCheckInterval) + clearInterval(chatGPTUrlCheckInterval); } if (chatGPTObserverThrottle) { - clearTimeout(chatGPTObserverThrottle) - chatGPTObserverThrottle = null + clearTimeout(chatGPTObserverThrottle); + chatGPTObserverThrottle = null; } - let currentUrl = window.location.href + let currentUrl = window.location.href; const checkForRouteChange = () => { if (window.location.href !== currentUrl) { - currentUrl = window.location.href - console.log("ChatGPT route changed, re-adding supermemory elements") + currentUrl = window.location.href; + console.log("ChatGPT route changed, re-adding supermemory elements"); setTimeout(() => { - addSupermemoryButtonToMemoriesDialog() - addSaveChatGPTElementBeforeComposerBtn() - setupChatGPTAutoFetch() - }, 1000) + addSupermemoryButtonToMemoriesDialog(); + addSaveChatGPTElementBeforeComposerBtn(); + setupChatGPTAutoFetch(); + }, 1000); } - } + }; - chatGPTUrlCheckInterval = setInterval(checkForRouteChange, 2000) + chatGPTUrlCheckInterval = setInterval(checkForRouteChange, 2000); chatGPTRouteObserver = new MutationObserver((mutations) => { if (chatGPTObserverThrottle) { - return + return; } - let shouldRecheck = false + let shouldRecheck = false; mutations.forEach((mutation) => { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { - const element = node as Element + const element = node as Element; if ( element.querySelector?.("#prompt-textarea") || element.querySelector?.("button.composer-btn") || @@ -84,65 +84,65 @@ function setupChatGPTRouteChangeDetection() { element.matches?.("#prompt-textarea") || element.id === "prompt-textarea" ) { - shouldRecheck = true + shouldRecheck = true; } } - }) + }); } - }) + }); if (shouldRecheck) { chatGPTObserverThrottle = setTimeout(() => { try { - chatGPTObserverThrottle = null - addSupermemoryButtonToMemoriesDialog() - addSaveChatGPTElementBeforeComposerBtn() - setupChatGPTAutoFetch() + chatGPTObserverThrottle = null; + addSupermemoryButtonToMemoriesDialog(); + addSaveChatGPTElementBeforeComposerBtn(); + setupChatGPTAutoFetch(); } catch (error) { - console.error("Error in ChatGPT observer callback:", error) + console.error("Error in ChatGPT observer callback:", error); } - }, 300) + }, 300); } - }) + }); try { chatGPTRouteObserver.observe(document.body, { childList: true, subtree: true, - }) + }); } catch (error) { - console.error("Failed to set up ChatGPT route observer:", error) + console.error("Failed to set up ChatGPT route observer:", error); if (chatGPTUrlCheckInterval) { - clearInterval(chatGPTUrlCheckInterval) + clearInterval(chatGPTUrlCheckInterval); } - chatGPTUrlCheckInterval = setInterval(checkForRouteChange, 1000) + chatGPTUrlCheckInterval = setInterval(checkForRouteChange, 1000); } } async function getRelatedMemoriesForChatGPT(actionSource: string) { try { const userQuery = - document.getElementById("prompt-textarea")?.textContent || "" + document.getElementById("prompt-textarea")?.textContent || ""; const icon = document.querySelectorAll( '[id*="sm-chatgpt-input-bar-element-before-composer"]', - )[0] + )[0]; - const iconElement = icon as HTMLElement + const iconElement = icon as HTMLElement; if (!iconElement) { - console.warn("ChatGPT icon element not found, cannot update feedback") - return + console.warn("ChatGPT icon element not found, cannot update feedback"); + return; } - updateChatGPTIconFeedback("Searching memories...", iconElement) + updateChatGPTIconFeedback("Searching memories...", iconElement); const timeoutPromise = new Promise((_, reject) => setTimeout( () => reject(new Error("Memory search timeout")), UI_CONFIG.API_REQUEST_TIMEOUT, ), - ) + ); const response = await Promise.race([ browser.runtime.sendMessage({ @@ -151,78 +151,78 @@ async function getRelatedMemoriesForChatGPT(actionSource: string) { actionSource: actionSource, }), timeoutPromise, - ]) + ]); if (response?.success && response?.data) { - const promptElement = document.getElementById("prompt-textarea") + const promptElement = document.getElementById("prompt-textarea"); if (promptElement) { - promptElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${response.data}</div>` + promptElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${response.data}</div>`; console.log( "Prompt element dataset:", promptElement.dataset.supermemories, - ) + ); - iconElement.dataset.memoriesData = response.data + iconElement.dataset.memoriesData = response.data; - updateChatGPTIconFeedback("Included Memories", iconElement) + updateChatGPTIconFeedback("Included Memories", iconElement); } else { console.warn( "ChatGPT prompt element not found after successful memory fetch", - ) - updateChatGPTIconFeedback("Memories found", iconElement) + ); + updateChatGPTIconFeedback("Memories found", iconElement); } } else { - console.warn("No memories found or API response invalid") - updateChatGPTIconFeedback("No memories found", iconElement) + console.warn("No memories found or API response invalid"); + updateChatGPTIconFeedback("No memories found", iconElement); } } catch (error) { - console.error("Error getting related memories:", error) + console.error("Error getting related memories:", error); try { const icon = document.querySelectorAll( '[id*="sm-chatgpt-input-bar-element-before-composer"]', - )[0] as HTMLElement + )[0] as HTMLElement; if (icon) { - updateChatGPTIconFeedback("Error fetching memories", icon) + updateChatGPTIconFeedback("Error fetching memories", icon); } } catch (feedbackError) { - console.error("Failed to update error feedback:", feedbackError) + console.error("Failed to update error feedback:", feedbackError); } } } function addSupermemoryButtonToMemoriesDialog() { - const dialogs = document.querySelectorAll('[role="dialog"]') - let memoriesDialog: HTMLElement | null = null + const dialogs = document.querySelectorAll('[role="dialog"]'); + let memoriesDialog: HTMLElement | null = null; for (const dialog of dialogs) { - const headerText = dialog.querySelector("h2") + const headerText = dialog.querySelector("h2"); if (headerText?.textContent?.includes("Saved memories")) { - memoriesDialog = dialog as HTMLElement - break + memoriesDialog = dialog as HTMLElement; + break; } } - if (!memoriesDialog) return + if (!memoriesDialog) return; - if (memoriesDialog.querySelector("#supermemory-save-button")) return + if (memoriesDialog.querySelector("#supermemory-save-button")) return; const deleteAllContainer = memoriesDialog.querySelector( ".mt-5.flex.justify-end", - ) - if (!deleteAllContainer) return + ); + if (!deleteAllContainer) return; - const supermemoryButton = document.createElement("button") - supermemoryButton.id = "supermemory-save-button" - supermemoryButton.className = "btn relative btn-primary-outline mr-2" + const supermemoryButton = document.createElement("button"); + supermemoryButton.id = "supermemory-save-button"; + supermemoryButton.className = "btn relative btn-primary-outline mr-2"; - const iconUrl = browser.runtime.getURL("/icon-16.png") + const iconUrl = browser.runtime.getURL("/icon-16.png"); supermemoryButton.innerHTML = ` <div class="flex items-center justify-center gap-2"> <img src="${iconUrl}" alt="supermemory" style="width: 16px; height: 16px; flex-shrink: 0; border-radius: 2px;" /> Save to supermemory </div> - ` + `; supermemoryButton.style.cssText = ` background: #1C2026 !important; @@ -234,54 +234,54 @@ function addSupermemoryButtonToMemoriesDialog() { font-size: 14px !important; margin-right: 8px !important; cursor: pointer !important; - ` + `; supermemoryButton.addEventListener("mouseenter", () => { - supermemoryButton.style.backgroundColor = "#2B2E33" - }) + supermemoryButton.style.backgroundColor = "#2B2E33"; + }); supermemoryButton.addEventListener("mouseleave", () => { - supermemoryButton.style.backgroundColor = "#1C2026" - }) + supermemoryButton.style.backgroundColor = "#1C2026"; + }); supermemoryButton.addEventListener("click", async () => { - await saveMemoriesToSupermemory() - }) + await saveMemoriesToSupermemory(); + }); deleteAllContainer.insertBefore( supermemoryButton, deleteAllContainer.firstChild, - ) + ); } async function saveMemoriesToSupermemory() { try { - DOMUtils.showToast("loading") + DOMUtils.showToast("loading"); - const memoriesTable = document.querySelector('[role="dialog"] table tbody') + const memoriesTable = document.querySelector('[role="dialog"] table tbody'); if (!memoriesTable) { - DOMUtils.showToast("error") - return + DOMUtils.showToast("error"); + return; } - const memoryRows = memoriesTable.querySelectorAll("tr") - const memories: string[] = [] + const memoryRows = memoriesTable.querySelectorAll("tr"); + const memories: string[] = []; memoryRows.forEach((row) => { - const memoryCell = row.querySelector("td .py-2.whitespace-pre-wrap") + const memoryCell = row.querySelector("td .py-2.whitespace-pre-wrap"); if (memoryCell?.textContent) { - memories.push(memoryCell.textContent.trim()) + memories.push(memoryCell.textContent.trim()); } - }) + }); - console.log("Memories:", memories) + console.log("Memories:", memories); if (memories.length === 0) { - DOMUtils.showToast("error") - return + DOMUtils.showToast("error"); + return; } - const combinedContent = `ChatGPT Saved Memories:\n\n${memories.map((memory, index) => `${index + 1}. ${memory}`).join("\n\n")}` + const combinedContent = `ChatGPT Saved Memories:\n\n${memories.map((memory, index) => `${index + 1}. ${memory}`).join("\n\n")}`; const response = await browser.runtime.sendMessage({ action: MESSAGE_TYPES.SAVE_MEMORY, @@ -289,18 +289,18 @@ async function saveMemoriesToSupermemory() { html: combinedContent, }, actionSource: "chatgpt_memories_dialog", - }) + }); - console.log({ response }) + console.log({ response }); if (response.success) { - DOMUtils.showToast("success") + DOMUtils.showToast("success"); } else { - DOMUtils.showToast("error") + DOMUtils.showToast("error"); } } catch (error) { - console.error("Error saving memories to supermemory:", error) - DOMUtils.showToast("error") + console.error("Error saving memories to supermemory:", error); + DOMUtils.showToast("error"); } } @@ -310,10 +310,10 @@ function updateChatGPTIconFeedback( resetAfter = 0, ) { if (!iconElement.dataset.originalHtml) { - iconElement.dataset.originalHtml = iconElement.innerHTML + iconElement.dataset.originalHtml = iconElement.innerHTML; } - const feedbackDiv = document.createElement("div") + const feedbackDiv = document.createElement("div"); feedbackDiv.style.cssText = ` display: flex; align-items: center; @@ -326,15 +326,15 @@ function updateChatGPTIconFeedback( font-weight: 500; cursor: ${message === "Included Memories" ? "pointer" : "default"}; position: relative; - ` + `; feedbackDiv.innerHTML = ` <span>✓</span> <span>${message}</span> - ` + `; if (message === "Included Memories" && iconElement.dataset.memoriesData) { - const popup = document.createElement("div") + const popup = document.createElement("div"); popup.style.cssText = ` position: fixed; bottom: 80px; @@ -352,9 +352,9 @@ function updateChatGPTIconFeedback( z-index: 999999; display: none; border: 1px solid #333; - ` + `; - const header = document.createElement("div") + const header = document.createElement("div"); header.style.cssText = ` display: flex; justify-content: space-between; @@ -362,28 +362,28 @@ function updateChatGPTIconFeedback( padding: 8px; border-bottom: 1px solid #333; opacity: 0.8; - ` + `; header.innerHTML = ` <span style="font-weight: 600; color: #fff;">Included Memories</span> - ` + `; - const content = document.createElement("div") + const content = document.createElement("div"); content.style.cssText = ` padding: 0; max-height: 300px; overflow-y: auto; - ` + `; - const memoriesText = iconElement.dataset.memoriesData || "" - console.log("Memories text:", memoriesText) + const memoriesText = iconElement.dataset.memoriesData || ""; + console.log("Memories text:", memoriesText); const individualMemories = memoriesText .split(/[,\n]/) .map((memory) => memory.trim()) - .filter((memory) => memory.length > 0 && memory !== ",") - console.log("Individual memories:", individualMemories) + .filter((memory) => memory.length > 0 && memory !== ","); + console.log("Individual memories:", individualMemories); individualMemories.forEach((memory, index) => { - const memoryItem = document.createElement("div") + const memoryItem = document.createElement("div"); memoryItem.style.cssText = ` display: flex; align-items: center; @@ -391,16 +391,16 @@ function updateChatGPTIconFeedback( padding: 10px; font-size: 13px; line-height: 1.4; - ` + `; - const memoryText = document.createElement("div") + const memoryText = document.createElement("div"); memoryText.style.cssText = ` flex: 1; color: #e5e5e5; - ` - memoryText.textContent = memory.trim() + `; + memoryText.textContent = memory.trim(); - const removeBtn = document.createElement("button") + const removeBtn = document.createElement("button"); removeBtn.style.cssText = ` background: transparent; color: #9ca3af; @@ -413,252 +413,255 @@ function updateChatGPTIconFeedback( display: flex; align-items: center; justify-content: center; - ` - removeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>` - removeBtn.dataset.memoryIndex = index.toString() + `; + removeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`; + removeBtn.dataset.memoryIndex = index.toString(); removeBtn.addEventListener("mouseenter", () => { - removeBtn.style.color = "#ef4444" - }) + removeBtn.style.color = "#ef4444"; + }); removeBtn.addEventListener("mouseleave", () => { - removeBtn.style.color = "#9ca3af" - }) + removeBtn.style.color = "#9ca3af"; + }); - memoryItem.appendChild(memoryText) - memoryItem.appendChild(removeBtn) - content.appendChild(memoryItem) - }) + memoryItem.appendChild(memoryText); + memoryItem.appendChild(removeBtn); + content.appendChild(memoryItem); + }); - popup.appendChild(header) - popup.appendChild(content) - document.body.appendChild(popup) + popup.appendChild(header); + popup.appendChild(content); + document.body.appendChild(popup); feedbackDiv.addEventListener("mouseenter", () => { - const textSpan = feedbackDiv.querySelector("span:last-child") + const textSpan = feedbackDiv.querySelector("span:last-child"); if (textSpan) { - textSpan.textContent = "Click to see memories" + textSpan.textContent = "Click to see memories"; } - }) + }); feedbackDiv.addEventListener("mouseleave", () => { - const textSpan = feedbackDiv.querySelector("span:last-child") + const textSpan = feedbackDiv.querySelector("span:last-child"); if (textSpan) { - textSpan.textContent = "Included Memories" + textSpan.textContent = "Included Memories"; } - }) + }); feedbackDiv.addEventListener("click", (e) => { - e.stopPropagation() - popup.style.display = "block" - }) + e.stopPropagation(); + popup.style.display = "block"; + }); document.addEventListener("click", (e) => { if (!popup.contains(e.target as Node)) { - popup.style.display = "none" + popup.style.display = "none"; } - }) + }); content.querySelectorAll("button[data-memory-index]").forEach((button) => { - const htmlButton = button as HTMLButtonElement + const htmlButton = button as HTMLButtonElement; htmlButton.addEventListener("click", () => { - const index = Number.parseInt(htmlButton.dataset.memoryIndex || "0", 10) - const memoryItem = htmlButton.parentElement + const index = Number.parseInt( + htmlButton.dataset.memoryIndex || "0", + 10, + ); + const memoryItem = htmlButton.parentElement; if (memoryItem) { - content.removeChild(memoryItem) + content.removeChild(memoryItem); } const currentMemories = (iconElement.dataset.memoriesData || "") .split(/[,\n]/) .map((memory) => memory.trim()) - .filter((memory) => memory.length > 0 && memory !== ",") - currentMemories.splice(index, 1) + .filter((memory) => memory.length > 0 && memory !== ","); + currentMemories.splice(index, 1); - const updatedMemories = currentMemories.join(" ,") + const updatedMemories = currentMemories.join(" ,"); - iconElement.dataset.memoriesData = updatedMemories + iconElement.dataset.memoriesData = updatedMemories; - const promptElement = document.getElementById("prompt-textarea") + const promptElement = document.getElementById("prompt-textarea"); if (promptElement) { - promptElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${updatedMemories}</div>` + promptElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${updatedMemories}</div>`; } content .querySelectorAll("button[data-memory-index]") .forEach((btn, newIndex) => { - const htmlBtn = btn as HTMLButtonElement - htmlBtn.dataset.memoryIndex = newIndex.toString() - }) + const htmlBtn = btn as HTMLButtonElement; + htmlBtn.dataset.memoryIndex = newIndex.toString(); + }); if (currentMemories.length <= 1) { if (promptElement?.dataset.supermemories) { - delete promptElement.dataset.supermemories - delete iconElement.dataset.memoriesData - iconElement.innerHTML = iconElement.dataset.originalHtml || "" - delete iconElement.dataset.originalHtml + delete promptElement.dataset.supermemories; + delete iconElement.dataset.memoriesData; + iconElement.innerHTML = iconElement.dataset.originalHtml || ""; + delete iconElement.dataset.originalHtml; } - popup.style.display = "none" + popup.style.display = "none"; if (document.body.contains(popup)) { - document.body.removeChild(popup) + document.body.removeChild(popup); } } - }) - }) + }); + }); setTimeout(() => { if (document.body.contains(popup)) { - document.body.removeChild(popup) + document.body.removeChild(popup); } - }, 300000) + }, 300000); } - iconElement.innerHTML = "" - iconElement.appendChild(feedbackDiv) + iconElement.innerHTML = ""; + iconElement.appendChild(feedbackDiv); if (resetAfter > 0) { setTimeout(() => { - iconElement.innerHTML = iconElement.dataset.originalHtml || "" - delete iconElement.dataset.originalHtml - }, resetAfter) + iconElement.innerHTML = iconElement.dataset.originalHtml || ""; + delete iconElement.dataset.originalHtml; + }, resetAfter); } } function addSaveChatGPTElementBeforeComposerBtn() { - const composerButtons = document.querySelectorAll("button.composer-btn") + const composerButtons = document.querySelectorAll("button.composer-btn"); composerButtons.forEach((button) => { if (button.hasAttribute("data-supermemory-icon-added-before")) { - return + return; } - const parent = button.parentElement - if (!parent) return + const parent = button.parentElement; + if (!parent) return; - const parentSiblings = parent.parentElement?.children - if (!parentSiblings) return + const parentSiblings = parent.parentElement?.children; + if (!parentSiblings) return; - let hasSpeechButtonSibling = false + let hasSpeechButtonSibling = false; for (const sibling of parentSiblings) { if ( sibling.getAttribute("data-testid") === "composer-speech-button-container" ) { - hasSpeechButtonSibling = true - break + hasSpeechButtonSibling = true; + break; } } - if (!hasSpeechButtonSibling) return + if (!hasSpeechButtonSibling) return; - const grandParent = parent.parentElement - if (!grandParent) return + const grandParent = parent.parentElement; + if (!grandParent) return; const existingIcon = grandParent.querySelector( `#${ELEMENT_IDS.CHATGPT_INPUT_BAR_ELEMENT}-before-composer`, - ) + ); if (existingIcon) { - button.setAttribute("data-supermemory-icon-added-before", "true") - return + button.setAttribute("data-supermemory-icon-added-before", "true"); + return; } const saveChatGPTElement = createChatGPTInputBarElement(async () => { await getRelatedMemoriesForChatGPT( POSTHOG_EVENT_KEY.CHATGPT_CHAT_MEMORIES_SEARCHED, - ) - }) + ); + }); - saveChatGPTElement.id = `${ELEMENT_IDS.CHATGPT_INPUT_BAR_ELEMENT}-before-composer-${Date.now()}-${Math.random().toString(36).substring(2, 11)}` + saveChatGPTElement.id = `${ELEMENT_IDS.CHATGPT_INPUT_BAR_ELEMENT}-before-composer-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; - button.setAttribute("data-supermemory-icon-added-before", "true") + button.setAttribute("data-supermemory-icon-added-before", "true"); - grandParent.insertBefore(saveChatGPTElement, parent) + grandParent.insertBefore(saveChatGPTElement, parent); - setupChatGPTAutoFetch() - }) + setupChatGPTAutoFetch(); + }); } async function setupChatGPTAutoFetch() { const result = await chrome.storage.local.get([ STORAGE_KEYS.AUTO_SEARCH_ENABLED, - ]) - const autoSearchEnabled = result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false + ]); + const autoSearchEnabled = result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false; if (!autoSearchEnabled) { - return + return; } - const promptTextarea = document.getElementById("prompt-textarea") + const promptTextarea = document.getElementById("prompt-textarea"); if ( !promptTextarea || promptTextarea.hasAttribute("data-supermemory-auto-fetch") ) { - return + return; } - promptTextarea.setAttribute("data-supermemory-auto-fetch", "true") + promptTextarea.setAttribute("data-supermemory-auto-fetch", "true"); const handleInput = () => { if (chatGPTDebounceTimeout) { - clearTimeout(chatGPTDebounceTimeout) + clearTimeout(chatGPTDebounceTimeout); } chatGPTDebounceTimeout = setTimeout(async () => { - const content = promptTextarea.textContent?.trim() || "" + const content = promptTextarea.textContent?.trim() || ""; if (content.length > 2) { await getRelatedMemoriesForChatGPT( POSTHOG_EVENT_KEY.CHATGPT_CHAT_MEMORIES_AUTO_SEARCHED, - ) + ); } else if (content.length === 0) { const icons = document.querySelectorAll( '[id*="sm-chatgpt-input-bar-element-before-composer"]', - ) + ); icons.forEach((icon) => { - const iconElement = icon as HTMLElement + const iconElement = icon as HTMLElement; if (iconElement.dataset.originalHtml) { - iconElement.innerHTML = iconElement.dataset.originalHtml - delete iconElement.dataset.originalHtml - delete iconElement.dataset.memoriesData + iconElement.innerHTML = iconElement.dataset.originalHtml; + delete iconElement.dataset.originalHtml; + delete iconElement.dataset.memoriesData; } - }) + }); if (promptTextarea.dataset.supermemories) { - delete promptTextarea.dataset.supermemories + delete promptTextarea.dataset.supermemories; } } - }, UI_CONFIG.AUTO_SEARCH_DEBOUNCE_DELAY) - } + }, UI_CONFIG.AUTO_SEARCH_DEBOUNCE_DELAY); + }; - promptTextarea.addEventListener("input", handleInput) + promptTextarea.addEventListener("input", handleInput); } function setupChatGPTPromptCapture() { if (document.body.hasAttribute("data-chatgpt-prompt-capture-setup")) { - return + return; } - document.body.setAttribute("data-chatgpt-prompt-capture-setup", "true") + document.body.setAttribute("data-chatgpt-prompt-capture-setup", "true"); const capturePromptContent = async (source: string) => { - const promptTextarea = document.getElementById("prompt-textarea") + const promptTextarea = document.getElementById("prompt-textarea"); - let promptContent = "" + let promptContent = ""; if (promptTextarea) { - promptContent = promptTextarea.textContent || "" + promptContent = promptTextarea.textContent || ""; } - const storedMemories = promptTextarea?.dataset.supermemories + const storedMemories = promptTextarea?.dataset.supermemories; if ( storedMemories && promptTextarea && !promptContent.includes("Supermemories of user") ) { - promptTextarea.innerHTML = `${promptTextarea.innerHTML} ${storedMemories}` - promptContent = promptTextarea.textContent || "" + promptTextarea.innerHTML = `${promptTextarea.innerHTML} ${storedMemories}`; + promptContent = promptTextarea.textContent || ""; } if (promptTextarea && promptContent.trim()) { - console.log(`ChatGPT prompt submitted via ${source}:`, promptContent) + console.log(`ChatGPT prompt submitted via ${source}:`, promptContent); try { await browser.runtime.sendMessage({ @@ -668,57 +671,57 @@ function setupChatGPTPromptCapture() { platform: "chatgpt", source: source, }, - }) + }); } catch (error) { - console.error("Error sending ChatGPT prompt to background:", error) + console.error("Error sending ChatGPT prompt to background:", error); } } const icons = document.querySelectorAll( '[id*="sm-chatgpt-input-bar-element-before-composer"]', - ) + ); icons.forEach((icon) => { - const iconElement = icon as HTMLElement + const iconElement = icon as HTMLElement; if (iconElement.dataset.originalHtml) { - iconElement.innerHTML = iconElement.dataset.originalHtml - delete iconElement.dataset.originalHtml - delete iconElement.dataset.memoriesData + iconElement.innerHTML = iconElement.dataset.originalHtml; + delete iconElement.dataset.originalHtml; + delete iconElement.dataset.memoriesData; } - }) + }); if (promptTextarea?.dataset.supermemories) { - delete promptTextarea.dataset.supermemories + delete promptTextarea.dataset.supermemories; } - } + }; document.addEventListener( "click", async (event) => { - const target = event.target as HTMLElement + const target = event.target as HTMLElement; if ( target.id === "composer-submit-button" || target.closest("#composer-submit-button") ) { - await capturePromptContent("button click") + await capturePromptContent("button click"); } }, true, - ) + ); document.addEventListener( "keydown", async (event) => { - const target = event.target as HTMLElement + const target = event.target as HTMLElement; if ( target.id === "prompt-textarea" && event.key === "Enter" && !event.shiftKey ) { - await capturePromptContent("Enter key") + await capturePromptContent("Enter key"); } }, true, - ) + ); } diff --git a/apps/browser-extension/entrypoints/content/claude.ts b/apps/browser-extension/entrypoints/content/claude.ts index e0853d41..e5832bf7 100644 --- a/apps/browser-extension/entrypoints/content/claude.ts +++ b/apps/browser-extension/entrypoints/content/claude.ts @@ -5,210 +5,210 @@ import { POSTHOG_EVENT_KEY, STORAGE_KEYS, UI_CONFIG, -} from "../../utils/constants" +} from "../../utils/constants"; import { createClaudeInputBarElement, DOMUtils, -} from "../../utils/ui-components" +} from "../../utils/ui-components"; -let claudeDebounceTimeout: NodeJS.Timeout | null = null -let claudeRouteObserver: MutationObserver | null = null -let claudeUrlCheckInterval: NodeJS.Timeout | null = null -let claudeObserverThrottle: NodeJS.Timeout | null = null +let claudeDebounceTimeout: NodeJS.Timeout | null = null; +let claudeRouteObserver: MutationObserver | null = null; +let claudeUrlCheckInterval: NodeJS.Timeout | null = null; +let claudeObserverThrottle: NodeJS.Timeout | null = null; export function initializeClaude() { if (!DOMUtils.isOnDomain(DOMAINS.CLAUDE)) { - return + return; } if (document.body.hasAttribute("data-claude-initialized")) { - return + return; } setTimeout(() => { - addSupermemoryIconToClaudeInput() - setupClaudeAutoFetch() - }, 2000) + addSupermemoryIconToClaudeInput(); + setupClaudeAutoFetch(); + }, 2000); - setupClaudePromptCapture() + setupClaudePromptCapture(); - setupClaudeRouteChangeDetection() + setupClaudeRouteChangeDetection(); - document.body.setAttribute("data-claude-initialized", "true") + document.body.setAttribute("data-claude-initialized", "true"); } function setupClaudeRouteChangeDetection() { if (claudeRouteObserver) { - claudeRouteObserver.disconnect() + claudeRouteObserver.disconnect(); } if (claudeUrlCheckInterval) { - clearInterval(claudeUrlCheckInterval) + clearInterval(claudeUrlCheckInterval); } if (claudeObserverThrottle) { - clearTimeout(claudeObserverThrottle) - claudeObserverThrottle = null + clearTimeout(claudeObserverThrottle); + claudeObserverThrottle = null; } - let currentUrl = window.location.href + let currentUrl = window.location.href; const checkForRouteChange = () => { if (window.location.href !== currentUrl) { - currentUrl = window.location.href - console.log("Claude route changed, re-adding supermemory icon") + currentUrl = window.location.href; + console.log("Claude route changed, re-adding supermemory icon"); setTimeout(() => { - addSupermemoryIconToClaudeInput() - setupClaudeAutoFetch() - }, 1000) + addSupermemoryIconToClaudeInput(); + setupClaudeAutoFetch(); + }, 1000); } - } + }; - claudeUrlCheckInterval = setInterval(checkForRouteChange, 2000) + claudeUrlCheckInterval = setInterval(checkForRouteChange, 2000); claudeRouteObserver = new MutationObserver((mutations) => { if (claudeObserverThrottle) { - return + return; } - let shouldRecheck = false + let shouldRecheck = false; mutations.forEach((mutation) => { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { - const element = node as Element + const element = node as Element; if ( element.querySelector?.('div[contenteditable="true"]') || element.querySelector?.("textarea") || element.matches?.('div[contenteditable="true"]') || element.matches?.("textarea") ) { - shouldRecheck = true + shouldRecheck = true; } } - }) + }); } - }) + }); if (shouldRecheck) { claudeObserverThrottle = setTimeout(() => { try { - claudeObserverThrottle = null - addSupermemoryIconToClaudeInput() - setupClaudeAutoFetch() + claudeObserverThrottle = null; + addSupermemoryIconToClaudeInput(); + setupClaudeAutoFetch(); } catch (error) { - console.error("Error in Claude observer callback:", error) + console.error("Error in Claude observer callback:", error); } - }, 300) + }, 300); } - }) + }); try { claudeRouteObserver.observe(document.body, { childList: true, subtree: true, - }) + }); } catch (error) { - console.error("Failed to set up Claude route observer:", error) + console.error("Failed to set up Claude route observer:", error); if (claudeUrlCheckInterval) { - clearInterval(claudeUrlCheckInterval) + clearInterval(claudeUrlCheckInterval); } - claudeUrlCheckInterval = setInterval(checkForRouteChange, 1000) + claudeUrlCheckInterval = setInterval(checkForRouteChange, 1000); } } function addSupermemoryIconToClaudeInput() { const targetContainers = document.querySelectorAll( ".relative.flex-1.flex.items-center.gap-2.shrink.min-w-0", - ) + ); targetContainers.forEach((container) => { if (container.hasAttribute("data-supermemory-icon-added")) { - return + return; } const existingIcon = container.querySelector( `#${ELEMENT_IDS.CLAUDE_INPUT_BAR_ELEMENT}`, - ) + ); if (existingIcon) { - container.setAttribute("data-supermemory-icon-added", "true") - return + container.setAttribute("data-supermemory-icon-added", "true"); + return; } const supermemoryIcon = createClaudeInputBarElement(async () => { await getRelatedMemoriesForClaude( POSTHOG_EVENT_KEY.CLAUDE_CHAT_MEMORIES_SEARCHED, - ) - }) + ); + }); - supermemoryIcon.id = `${ELEMENT_IDS.CLAUDE_INPUT_BAR_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}` + supermemoryIcon.id = `${ELEMENT_IDS.CLAUDE_INPUT_BAR_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; - container.setAttribute("data-supermemory-icon-added", "true") + container.setAttribute("data-supermemory-icon-added", "true"); - container.insertBefore(supermemoryIcon, container.firstChild) - }) + container.insertBefore(supermemoryIcon, container.firstChild); + }); } async function getRelatedMemoriesForClaude(actionSource: string) { try { - let userQuery = "" + let userQuery = ""; const supermemoryContainer = document.querySelector( '[data-supermemory-icon-added="true"]', - ) + ); if (supermemoryContainer?.parentElement?.previousElementSibling) { const pTag = supermemoryContainer.parentElement.previousElementSibling.querySelector( "p", - ) - userQuery = pTag?.innerText || pTag?.textContent || "" + ); + userQuery = pTag?.innerText || pTag?.textContent || ""; } if (!userQuery.trim()) { const textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement + ) as HTMLElement; userQuery = - textareaElement?.innerText || textareaElement?.textContent || "" + textareaElement?.innerText || textareaElement?.textContent || ""; } if (!userQuery.trim()) { const inputElements = document.querySelectorAll( 'div[contenteditable="true"], textarea, input[type="text"]', - ) + ); for (const element of inputElements) { const text = (element as HTMLElement).innerText || - (element as HTMLInputElement).value + (element as HTMLInputElement).value; if (text?.trim()) { - userQuery = text.trim() - break + userQuery = text.trim(); + break; } } } - console.log("Claude query extracted:", userQuery) + console.log("Claude query extracted:", userQuery); if (!userQuery.trim()) { - console.log("No query text found for Claude") - return + console.log("No query text found for Claude"); + return; } - const icon = document.querySelector('[id*="sm-claude-input-bar-element"]') + const icon = document.querySelector('[id*="sm-claude-input-bar-element"]'); - const iconElement = icon as HTMLElement + const iconElement = icon as HTMLElement; if (!iconElement) { - console.warn("Claude icon element not found, cannot update feedback") - return + console.warn("Claude icon element not found, cannot update feedback"); + return; } - updateClaudeIconFeedback("Searching memories...", iconElement) + updateClaudeIconFeedback("Searching memories...", iconElement); const timeoutPromise = new Promise((_, reject) => setTimeout( () => reject(new Error("Memory search timeout")), UI_CONFIG.API_REQUEST_TIMEOUT, ), - ) + ); const response = await Promise.race([ browser.runtime.sendMessage({ @@ -217,46 +217,46 @@ async function getRelatedMemoriesForClaude(actionSource: string) { actionSource: actionSource, }), timeoutPromise, - ]) + ]); - console.log("Claude memories response:", response) + console.log("Claude memories response:", response); if (response?.success && response?.data) { const textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement + ) as HTMLElement; if (textareaElement) { - textareaElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${response.data}</div>` + textareaElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${response.data}</div>`; console.log( "Text element dataset:", textareaElement.dataset.supermemories, - ) + ); - iconElement.dataset.memoriesData = response.data + iconElement.dataset.memoriesData = response.data; - updateClaudeIconFeedback("Included Memories", iconElement) + updateClaudeIconFeedback("Included Memories", iconElement); } else { console.warn( "Claude input area not found after successful memory fetch", - ) - updateClaudeIconFeedback("Memories found", iconElement) + ); + updateClaudeIconFeedback("Memories found", iconElement); } } else { - console.warn("No memories found or API response invalid for Claude") - updateClaudeIconFeedback("No memories found", iconElement) + console.warn("No memories found or API response invalid for Claude"); + updateClaudeIconFeedback("No memories found", iconElement); } } catch (error) { - console.error("Error getting related memories for Claude:", error) + console.error("Error getting related memories for Claude:", error); try { const icon = document.querySelector( '[id*="sm-claude-input-bar-element"]', - ) as HTMLElement + ) as HTMLElement; if (icon) { - updateClaudeIconFeedback("Error fetching memories", icon) + updateClaudeIconFeedback("Error fetching memories", icon); } } catch (feedbackError) { - console.error("Failed to update Claude error feedback:", feedbackError) + console.error("Failed to update Claude error feedback:", feedbackError); } } } @@ -267,10 +267,10 @@ function updateClaudeIconFeedback( resetAfter = 0, ) { if (!iconElement.dataset.originalHtml) { - iconElement.dataset.originalHtml = iconElement.innerHTML + iconElement.dataset.originalHtml = iconElement.innerHTML; } - const feedbackDiv = document.createElement("div") + const feedbackDiv = document.createElement("div"); feedbackDiv.style.cssText = ` display: flex; align-items: center; @@ -283,15 +283,15 @@ function updateClaudeIconFeedback( font-weight: 500; cursor: ${message === "Included Memories" ? "pointer" : "default"}; position: relative; - ` + `; feedbackDiv.innerHTML = ` <span>✓</span> <span>${message}</span> - ` + `; if (message === "Included Memories" && iconElement.dataset.memoriesData) { - const popup = document.createElement("div") + const popup = document.createElement("div"); popup.style.cssText = ` position: fixed; bottom: 80px; @@ -309,9 +309,9 @@ function updateClaudeIconFeedback( z-index: 999999; display: none; border: 1px solid #333; - ` + `; - const header = document.createElement("div") + const header = document.createElement("div"); header.style.cssText = ` display: flex; justify-content: space-between; @@ -319,28 +319,28 @@ function updateClaudeIconFeedback( padding: 8px; border-bottom: 1px solid #333; opacity: 0.8; - ` + `; header.innerHTML = ` <span style="font-weight: 600; color: #fff;">Included Memories</span> - ` + `; - const content = document.createElement("div") + const content = document.createElement("div"); content.style.cssText = ` padding: 0; max-height: 300px; overflow-y: auto; - ` + `; - const memoriesText = iconElement.dataset.memoriesData || "" - console.log("Memories text:", memoriesText) + const memoriesText = iconElement.dataset.memoriesData || ""; + console.log("Memories text:", memoriesText); const individualMemories = memoriesText .split(/[,\n]/) .map((memory) => memory.trim()) - .filter((memory) => memory.length > 0 && memory !== ",") - console.log("Individual memories:", individualMemories) + .filter((memory) => memory.length > 0 && memory !== ","); + console.log("Individual memories:", individualMemories); individualMemories.forEach((memory, index) => { - const memoryItem = document.createElement("div") + const memoryItem = document.createElement("div"); memoryItem.style.cssText = ` display: flex; align-items: center; @@ -348,16 +348,16 @@ function updateClaudeIconFeedback( padding: 10px; font-size: 13px; line-height: 1.4; - ` + `; - const memoryText = document.createElement("div") + const memoryText = document.createElement("div"); memoryText.style.cssText = ` flex: 1; color: #e5e5e5; - ` - memoryText.textContent = memory.trim() + `; + memoryText.textContent = memory.trim(); - const removeBtn = document.createElement("button") + const removeBtn = document.createElement("button"); removeBtn.style.cssText = ` background: transparent; color: #9ca3af; @@ -370,154 +370,159 @@ function updateClaudeIconFeedback( display: flex; align-items: center; justify-content: center; - ` - removeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>` - removeBtn.dataset.memoryIndex = index.toString() + `; + removeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`; + removeBtn.dataset.memoryIndex = index.toString(); removeBtn.addEventListener("mouseenter", () => { - removeBtn.style.color = "#ef4444" - }) + removeBtn.style.color = "#ef4444"; + }); removeBtn.addEventListener("mouseleave", () => { - removeBtn.style.color = "#9ca3af" - }) + removeBtn.style.color = "#9ca3af"; + }); - memoryItem.appendChild(memoryText) - memoryItem.appendChild(removeBtn) - content.appendChild(memoryItem) - }) + memoryItem.appendChild(memoryText); + memoryItem.appendChild(removeBtn); + content.appendChild(memoryItem); + }); - popup.appendChild(header) - popup.appendChild(content) - document.body.appendChild(popup) + popup.appendChild(header); + popup.appendChild(content); + document.body.appendChild(popup); feedbackDiv.addEventListener("mouseenter", () => { - const textSpan = feedbackDiv.querySelector("span:last-child") + const textSpan = feedbackDiv.querySelector("span:last-child"); if (textSpan) { - textSpan.textContent = "Click to see memories" + textSpan.textContent = "Click to see memories"; } - }) + }); feedbackDiv.addEventListener("mouseleave", () => { - const textSpan = feedbackDiv.querySelector("span:last-child") + const textSpan = feedbackDiv.querySelector("span:last-child"); if (textSpan) { - textSpan.textContent = "Included Memories" + textSpan.textContent = "Included Memories"; } - }) + }); feedbackDiv.addEventListener("click", (e) => { - e.stopPropagation() - popup.style.display = "block" - }) + e.stopPropagation(); + popup.style.display = "block"; + }); document.addEventListener("click", (e) => { if (!popup.contains(e.target as Node)) { - popup.style.display = "none" + popup.style.display = "none"; } - }) + }); content.querySelectorAll("button[data-memory-index]").forEach((button) => { - const htmlButton = button as HTMLButtonElement + const htmlButton = button as HTMLButtonElement; htmlButton.addEventListener("click", () => { - const index = Number.parseInt(htmlButton.dataset.memoryIndex || "0", 10) - const memoryItem = htmlButton.parentElement + const index = Number.parseInt( + htmlButton.dataset.memoryIndex || "0", + 10, + ); + const memoryItem = htmlButton.parentElement; if (memoryItem) { - content.removeChild(memoryItem) + content.removeChild(memoryItem); } const currentMemories = (iconElement.dataset.memoriesData || "") .split(/[,\n]/) .map((memory) => memory.trim()) - .filter((memory) => memory.length > 0 && memory !== ",") - currentMemories.splice(index, 1) + .filter((memory) => memory.length > 0 && memory !== ","); + currentMemories.splice(index, 1); - const updatedMemories = currentMemories.join(" ,") + const updatedMemories = currentMemories.join(" ,"); - iconElement.dataset.memoriesData = updatedMemories + iconElement.dataset.memoriesData = updatedMemories; const textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement + ) as HTMLElement; if (textareaElement) { - textareaElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${updatedMemories}</div>` + textareaElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${updatedMemories}</div>`; } content .querySelectorAll("button[data-memory-index]") .forEach((btn, newIndex) => { - const htmlBtn = btn as HTMLButtonElement - htmlBtn.dataset.memoryIndex = newIndex.toString() - }) + const htmlBtn = btn as HTMLButtonElement; + htmlBtn.dataset.memoryIndex = newIndex.toString(); + }); if (currentMemories.length <= 1) { if (textareaElement?.dataset.supermemories) { - delete textareaElement.dataset.supermemories - delete iconElement.dataset.memoriesData - iconElement.innerHTML = iconElement.dataset.originalHtml || "" - delete iconElement.dataset.originalHtml + delete textareaElement.dataset.supermemories; + delete iconElement.dataset.memoriesData; + iconElement.innerHTML = iconElement.dataset.originalHtml || ""; + delete iconElement.dataset.originalHtml; } - popup.style.display = "none" + popup.style.display = "none"; if (document.body.contains(popup)) { - document.body.removeChild(popup) + document.body.removeChild(popup); } } - }) - }) + }); + }); setTimeout(() => { if (document.body.contains(popup)) { - document.body.removeChild(popup) + document.body.removeChild(popup); } - }, 300000) + }, 300000); } - iconElement.innerHTML = "" - iconElement.appendChild(feedbackDiv) + iconElement.innerHTML = ""; + iconElement.appendChild(feedbackDiv); if (resetAfter > 0) { setTimeout(() => { - iconElement.innerHTML = iconElement.dataset.originalHtml || "" - delete iconElement.dataset.originalHtml - }, resetAfter) + iconElement.innerHTML = iconElement.dataset.originalHtml || ""; + delete iconElement.dataset.originalHtml; + }, resetAfter); } } function setupClaudePromptCapture() { if (document.body.hasAttribute("data-claude-prompt-capture-setup")) { - return + return; } - document.body.setAttribute("data-claude-prompt-capture-setup", "true") + document.body.setAttribute("data-claude-prompt-capture-setup", "true"); const captureClaudePromptContent = async (source: string) => { - let promptContent = "" + let promptContent = ""; const contentEditableDiv = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement + ) as HTMLElement; if (contentEditableDiv) { promptContent = - contentEditableDiv.textContent || contentEditableDiv.innerText || "" + contentEditableDiv.textContent || contentEditableDiv.innerText || ""; } if (!promptContent) { - const textarea = document.querySelector("textarea") as HTMLTextAreaElement + const textarea = document.querySelector( + "textarea", + ) as HTMLTextAreaElement; if (textarea) { - promptContent = textarea.value || "" + promptContent = textarea.value || ""; } } - const storedMemories = contentEditableDiv?.dataset.supermemories + const storedMemories = contentEditableDiv?.dataset.supermemories; if ( storedMemories && contentEditableDiv && !promptContent.includes("Supermemories of user") ) { - contentEditableDiv.innerHTML = `${contentEditableDiv.innerHTML} ${storedMemories}` + contentEditableDiv.innerHTML = `${contentEditableDiv.innerHTML} ${storedMemories}`; promptContent = - contentEditableDiv.textContent || contentEditableDiv.innerText || "" + contentEditableDiv.textContent || contentEditableDiv.innerText || ""; } if (promptContent.trim()) { - console.log(`Claude prompt submitted via ${source}:`, promptContent) + console.log(`Claude prompt submitted via ${source}:`, promptContent); try { await browser.runtime.sendMessage({ @@ -527,52 +532,52 @@ function setupClaudePromptCapture() { platform: "claude", source: source, }, - }) + }); } catch (error) { - console.error("Error sending Claude prompt to background:", error) + console.error("Error sending Claude prompt to background:", error); } } const icons = document.querySelectorAll( '[id*="sm-claude-input-bar-element"]', - ) + ); icons.forEach((icon) => { - const iconElement = icon as HTMLElement + const iconElement = icon as HTMLElement; if (iconElement.dataset.originalHtml) { - iconElement.innerHTML = iconElement.dataset.originalHtml - delete iconElement.dataset.originalHtml - delete iconElement.dataset.memoriesData + iconElement.innerHTML = iconElement.dataset.originalHtml; + delete iconElement.dataset.originalHtml; + delete iconElement.dataset.memoriesData; } - }) + }); if (contentEditableDiv?.dataset.supermemories) { - delete contentEditableDiv.dataset.supermemories + delete contentEditableDiv.dataset.supermemories; } - } + }; document.addEventListener( "click", async (event) => { - const target = event.target as HTMLElement + const target = event.target as HTMLElement; const sendButton = target.closest( "button.inline-flex.items-center.justify-center.relative.shrink-0.can-focus.select-none", ) || target.closest('button[class*="bg-accent-main-000"]') || - target.closest('button[class*="rounded-lg"]') + target.closest('button[class*="rounded-lg"]'); if (sendButton) { - await captureClaudePromptContent("button click") + await captureClaudePromptContent("button click"); } }, true, - ) + ); document.addEventListener( "keydown", async (event) => { - const target = event.target as HTMLElement + const target = event.target as HTMLElement; if ( (target.matches('div[contenteditable="true"]') || @@ -582,67 +587,67 @@ function setupClaudePromptCapture() { event.key === "Enter" && !event.shiftKey ) { - await captureClaudePromptContent("Enter key") + await captureClaudePromptContent("Enter key"); } }, true, - ) + ); } async function setupClaudeAutoFetch() { const result = await chrome.storage.local.get([ STORAGE_KEYS.AUTO_SEARCH_ENABLED, - ]) - const autoSearchEnabled = result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false + ]); + const autoSearchEnabled = result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false; if (!autoSearchEnabled) { - return + return; } const textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement + ) as HTMLElement; if ( !textareaElement || textareaElement.hasAttribute("data-supermemory-auto-fetch") ) { - return + return; } - textareaElement.setAttribute("data-supermemory-auto-fetch", "true") + textareaElement.setAttribute("data-supermemory-auto-fetch", "true"); const handleInput = () => { if (claudeDebounceTimeout) { - clearTimeout(claudeDebounceTimeout) + clearTimeout(claudeDebounceTimeout); } claudeDebounceTimeout = setTimeout(async () => { - const content = textareaElement.textContent?.trim() || "" + const content = textareaElement.textContent?.trim() || ""; if (content.length > 2) { await getRelatedMemoriesForClaude( POSTHOG_EVENT_KEY.CLAUDE_CHAT_MEMORIES_AUTO_SEARCHED, - ) + ); } else if (content.length === 0) { const icons = document.querySelectorAll( '[id*="sm-claude-input-bar-element"]', - ) + ); icons.forEach((icon) => { - const iconElement = icon as HTMLElement + const iconElement = icon as HTMLElement; if (iconElement.dataset.originalHtml) { - iconElement.innerHTML = iconElement.dataset.originalHtml - delete iconElement.dataset.originalHtml - delete iconElement.dataset.memoriesData + iconElement.innerHTML = iconElement.dataset.originalHtml; + delete iconElement.dataset.originalHtml; + delete iconElement.dataset.memoriesData; } - }) + }); if (textareaElement.dataset.supermemories) { - delete textareaElement.dataset.supermemories + delete textareaElement.dataset.supermemories; } } - }, UI_CONFIG.AUTO_SEARCH_DEBOUNCE_DELAY) - } + }, UI_CONFIG.AUTO_SEARCH_DEBOUNCE_DELAY); + }; - textareaElement.addEventListener("input", handleInput) + textareaElement.addEventListener("input", handleInput); } diff --git a/apps/browser-extension/entrypoints/content/index.ts b/apps/browser-extension/entrypoints/content/index.ts index a79f50fb..829c2fa9 100644 --- a/apps/browser-extension/entrypoints/content/index.ts +++ b/apps/browser-extension/entrypoints/content/index.ts @@ -1,10 +1,18 @@ -import { DOMAINS, MESSAGE_TYPES } from "../../utils/constants" -import { DOMUtils } from "../../utils/ui-components" -import { initializeChatGPT } from "./chatgpt" -import { initializeClaude } from "./claude" -import { saveMemory, setupGlobalKeyboardShortcut, setupStorageListener } from "./shared" -import { initializeT3 } from "./t3" -import { handleTwitterNavigation, initializeTwitter, updateTwitterImportUI } from "./twitter" +import { DOMAINS, MESSAGE_TYPES } from "../../utils/constants"; +import { DOMUtils } from "../../utils/ui-components"; +import { initializeChatGPT } from "./chatgpt"; +import { initializeClaude } from "./claude"; +import { + saveMemory, + setupGlobalKeyboardShortcut, + setupStorageListener, +} from "./shared"; +import { initializeT3 } from "./t3"; +import { + handleTwitterNavigation, + initializeTwitter, + updateTwitterImportUI, +} from "./twitter"; export default defineContentScript({ matches: ["<all_urls>"], @@ -12,56 +20,56 @@ export default defineContentScript({ // Setup global event listeners browser.runtime.onMessage.addListener(async (message) => { if (message.action === MESSAGE_TYPES.SHOW_TOAST) { - DOMUtils.showToast(message.state) + DOMUtils.showToast(message.state); } else if (message.action === MESSAGE_TYPES.SAVE_MEMORY) { - await saveMemory() + await saveMemory(); } else if (message.type === MESSAGE_TYPES.IMPORT_UPDATE) { - updateTwitterImportUI(message) + updateTwitterImportUI(message); } else if (message.type === MESSAGE_TYPES.IMPORT_DONE) { - updateTwitterImportUI(message) + updateTwitterImportUI(message); } - }) + }); // Setup global keyboard shortcuts - setupGlobalKeyboardShortcut() + setupGlobalKeyboardShortcut(); // Setup storage listener - setupStorageListener() + setupStorageListener(); // Observer for dynamic content changes const observeForDynamicChanges = () => { const observer = new MutationObserver(() => { if (DOMUtils.isOnDomain(DOMAINS.CHATGPT)) { - initializeChatGPT() + initializeChatGPT(); } if (DOMUtils.isOnDomain(DOMAINS.CLAUDE)) { - initializeClaude() + initializeClaude(); } if (DOMUtils.isOnDomain(DOMAINS.T3)) { - initializeT3() + initializeT3(); } if (DOMUtils.isOnDomain(DOMAINS.TWITTER)) { - handleTwitterNavigation() + handleTwitterNavigation(); } - }) + }); observer.observe(document.body, { childList: true, subtree: true, - }) - } + }); + }; // Initialize platform-specific functionality - initializeChatGPT() - initializeClaude() - initializeT3() - initializeTwitter() + initializeChatGPT(); + initializeClaude(); + initializeT3(); + initializeTwitter(); // Start observing for dynamic changes if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", observeForDynamicChanges) + document.addEventListener("DOMContentLoaded", observeForDynamicChanges); } else { - observeForDynamicChanges() + observeForDynamicChanges(); } }, -})
\ No newline at end of file +}); diff --git a/apps/browser-extension/entrypoints/content/shared.ts b/apps/browser-extension/entrypoints/content/shared.ts index d8b665c5..8c6aae65 100644 --- a/apps/browser-extension/entrypoints/content/shared.ts +++ b/apps/browser-extension/entrypoints/content/shared.ts @@ -1,13 +1,13 @@ -import { MESSAGE_TYPES, STORAGE_KEYS } from "../../utils/constants" -import { DOMUtils } from "../../utils/ui-components" +import { MESSAGE_TYPES, STORAGE_KEYS } from "../../utils/constants"; +import { DOMUtils } from "../../utils/ui-components"; export async function saveMemory() { try { - DOMUtils.showToast("loading") + DOMUtils.showToast("loading"); - const highlightedText = window.getSelection()?.toString() || "" - const url = window.location.href - const html = document.documentElement.outerHTML + const highlightedText = window.getSelection()?.toString() || ""; + const url = window.location.href; + const html = document.documentElement.outerHTML; const response = await browser.runtime.sendMessage({ action: MESSAGE_TYPES.SAVE_MEMORY, @@ -17,17 +17,17 @@ export async function saveMemory() { url, }, actionSource: "context_menu", - }) + }); - console.log("Response from enxtension:", response) + console.log("Response from enxtension:", response); if (response.success) { - DOMUtils.showToast("success") + DOMUtils.showToast("success"); } else { - DOMUtils.showToast("error") + DOMUtils.showToast("error"); } } catch (error) { - console.error("Error saving memory:", error) - DOMUtils.showToast("error") + console.error("Error saving memory:", error); + DOMUtils.showToast("error"); } } @@ -38,19 +38,19 @@ export function setupGlobalKeyboardShortcut() { event.shiftKey && event.key === "m" ) { - event.preventDefault() - await saveMemory() + event.preventDefault(); + await saveMemory(); } - }) + }); } export function setupStorageListener() { window.addEventListener("message", (event) => { if (event.source !== window) { - return + return; } - const bearerToken = event.data.token - const userData = event.data.userData + const bearerToken = event.data.token; + const userData = event.data.userData; if (bearerToken && userData) { if ( !( @@ -61,8 +61,8 @@ export function setupStorageListener() { ) { console.log( "Bearer token and user data is only allowed to be used on localhost or supermemory.ai", - ) - return + ); + return; } chrome.storage.local.set( @@ -71,7 +71,7 @@ export function setupStorageListener() { [STORAGE_KEYS.USER_DATA]: userData, }, () => {}, - ) + ); } - }) -}
\ No newline at end of file + }); +} diff --git a/apps/browser-extension/entrypoints/content/t3.ts b/apps/browser-extension/entrypoints/content/t3.ts index 4332076e..d630ffe5 100644 --- a/apps/browser-extension/entrypoints/content/t3.ts +++ b/apps/browser-extension/entrypoints/content/t3.ts @@ -5,204 +5,206 @@ import { POSTHOG_EVENT_KEY, STORAGE_KEYS, UI_CONFIG, -} from "../../utils/constants" -import { createT3InputBarElement, DOMUtils } from "../../utils/ui-components" +} from "../../utils/constants"; +import { createT3InputBarElement, DOMUtils } from "../../utils/ui-components"; -let t3DebounceTimeout: NodeJS.Timeout | null = null -let t3RouteObserver: MutationObserver | null = null -let t3UrlCheckInterval: NodeJS.Timeout | null = null -let t3ObserverThrottle: NodeJS.Timeout | null = null +let t3DebounceTimeout: NodeJS.Timeout | null = null; +let t3RouteObserver: MutationObserver | null = null; +let t3UrlCheckInterval: NodeJS.Timeout | null = null; +let t3ObserverThrottle: NodeJS.Timeout | null = null; export function initializeT3() { if (!DOMUtils.isOnDomain(DOMAINS.T3)) { - return + return; } if (document.body.hasAttribute("data-t3-initialized")) { - return + return; } setTimeout(() => { - console.log("Adding supermemory icon to T3 input") - addSupermemoryIconToT3Input() - setupT3AutoFetch() - }, 2000) + console.log("Adding supermemory icon to T3 input"); + addSupermemoryIconToT3Input(); + setupT3AutoFetch(); + }, 2000); - setupT3PromptCapture() + setupT3PromptCapture(); - setupT3RouteChangeDetection() + setupT3RouteChangeDetection(); - document.body.setAttribute("data-t3-initialized", "true") + document.body.setAttribute("data-t3-initialized", "true"); } function setupT3RouteChangeDetection() { if (t3RouteObserver) { - t3RouteObserver.disconnect() + t3RouteObserver.disconnect(); } if (t3UrlCheckInterval) { - clearInterval(t3UrlCheckInterval) + clearInterval(t3UrlCheckInterval); } if (t3ObserverThrottle) { - clearTimeout(t3ObserverThrottle) - t3ObserverThrottle = null + clearTimeout(t3ObserverThrottle); + t3ObserverThrottle = null; } - let currentUrl = window.location.href + let currentUrl = window.location.href; const checkForRouteChange = () => { if (window.location.href !== currentUrl) { - currentUrl = window.location.href - console.log("T3 route changed, re-adding supermemory icon") + currentUrl = window.location.href; + console.log("T3 route changed, re-adding supermemory icon"); setTimeout(() => { - addSupermemoryIconToT3Input() - setupT3AutoFetch() - }, 1000) + addSupermemoryIconToT3Input(); + setupT3AutoFetch(); + }, 1000); } - } + }; - t3UrlCheckInterval = setInterval(checkForRouteChange, 2000) + t3UrlCheckInterval = setInterval(checkForRouteChange, 2000); t3RouteObserver = new MutationObserver((mutations) => { if (t3ObserverThrottle) { - return + return; } - let shouldRecheck = false + let shouldRecheck = false; mutations.forEach((mutation) => { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { - const element = node as Element + const element = node as Element; if ( element.querySelector?.("textarea") || element.querySelector?.('div[contenteditable="true"]') || element.matches?.("textarea") || element.matches?.('div[contenteditable="true"]') ) { - shouldRecheck = true + shouldRecheck = true; } } - }) + }); } - }) + }); if (shouldRecheck) { t3ObserverThrottle = setTimeout(() => { try { - t3ObserverThrottle = null - addSupermemoryIconToT3Input() - setupT3AutoFetch() + t3ObserverThrottle = null; + addSupermemoryIconToT3Input(); + setupT3AutoFetch(); } catch (error) { - console.error("Error in T3 observer callback:", error) + console.error("Error in T3 observer callback:", error); } - }, 300) + }, 300); } - }) + }); try { t3RouteObserver.observe(document.body, { childList: true, subtree: true, - }) + }); } catch (error) { - console.error("Failed to set up T3 route observer:", error) + console.error("Failed to set up T3 route observer:", error); if (t3UrlCheckInterval) { - clearInterval(t3UrlCheckInterval) + clearInterval(t3UrlCheckInterval); } - t3UrlCheckInterval = setInterval(checkForRouteChange, 1000) + t3UrlCheckInterval = setInterval(checkForRouteChange, 1000); } } function addSupermemoryIconToT3Input() { const targetContainers = document.querySelectorAll( ".flex.min-w-0.items-center.gap-2.overflow-hidden", - ) + ); targetContainers.forEach((container) => { if (container.hasAttribute("data-supermemory-icon-added")) { - return + return; } const existingIcon = container.querySelector( `#${ELEMENT_IDS.T3_INPUT_BAR_ELEMENT}`, - ) + ); if (existingIcon) { - container.setAttribute("data-supermemory-icon-added", "true") - return + container.setAttribute("data-supermemory-icon-added", "true"); + return; } const supermemoryIcon = createT3InputBarElement(async () => { - await getRelatedMemoriesForT3(POSTHOG_EVENT_KEY.T3_CHAT_MEMORIES_SEARCHED) - }) + await getRelatedMemoriesForT3( + POSTHOG_EVENT_KEY.T3_CHAT_MEMORIES_SEARCHED, + ); + }); - supermemoryIcon.id = `${ELEMENT_IDS.T3_INPUT_BAR_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}` + supermemoryIcon.id = `${ELEMENT_IDS.T3_INPUT_BAR_ELEMENT}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; - container.setAttribute("data-supermemory-icon-added", "true") + container.setAttribute("data-supermemory-icon-added", "true"); - container.insertBefore(supermemoryIcon, container.firstChild) - }) + container.insertBefore(supermemoryIcon, container.firstChild); + }); } async function getRelatedMemoriesForT3(actionSource: string) { try { - let userQuery = "" + let userQuery = ""; const supermemoryContainer = document.querySelector( '[data-supermemory-icon-added="true"]', - ) + ); if ( supermemoryContainer?.parentElement?.parentElement?.previousElementSibling ) { const textareaElement = supermemoryContainer.parentElement.parentElement.previousElementSibling.querySelector( "textarea", - ) - userQuery = textareaElement?.value || "" + ); + userQuery = textareaElement?.value || ""; } if (!userQuery.trim()) { const textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement + ) as HTMLElement; userQuery = - textareaElement?.innerText || textareaElement?.textContent || "" + textareaElement?.innerText || textareaElement?.textContent || ""; } if (!userQuery.trim()) { - const textareas = document.querySelectorAll("textarea") + const textareas = document.querySelectorAll("textarea"); for (const textarea of textareas) { - const text = (textarea as HTMLTextAreaElement).value + const text = (textarea as HTMLTextAreaElement).value; if (text?.trim()) { - userQuery = text.trim() - break + userQuery = text.trim(); + break; } } } - console.log("T3 query extracted:", userQuery) + console.log("T3 query extracted:", userQuery); if (!userQuery.trim()) { - console.log("No query text found for T3") - return + console.log("No query text found for T3"); + return; } - const icon = document.querySelector('[id*="sm-t3-input-bar-element"]') + const icon = document.querySelector('[id*="sm-t3-input-bar-element"]'); - const iconElement = icon as HTMLElement + const iconElement = icon as HTMLElement; if (!iconElement) { - console.warn("T3 icon element not found, cannot update feedback") - return + console.warn("T3 icon element not found, cannot update feedback"); + return; } - updateT3IconFeedback("Searching memories...", iconElement) + updateT3IconFeedback("Searching memories...", iconElement); const timeoutPromise = new Promise((_, reject) => setTimeout( () => reject(new Error("Memory search timeout")), UI_CONFIG.API_REQUEST_TIMEOUT, ), - ) + ); const response = await Promise.race([ browser.runtime.sendMessage({ @@ -211,15 +213,15 @@ async function getRelatedMemoriesForT3(actionSource: string) { actionSource: actionSource, }), timeoutPromise, - ]) + ]); - console.log("T3 memories response:", response) + console.log("T3 memories response:", response); if (response?.success && response?.data) { - let textareaElement = null + let textareaElement = null; const supermemoryContainer = document.querySelector( '[data-supermemory-icon-added="true"]', - ) + ); if ( supermemoryContainer?.parentElement?.parentElement ?.previousElementSibling @@ -227,46 +229,46 @@ async function getRelatedMemoriesForT3(actionSource: string) { textareaElement = supermemoryContainer.parentElement.parentElement.previousElementSibling.querySelector( "textarea", - ) + ); } if (!textareaElement) { textareaElement = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement + ) as HTMLElement; } if (textareaElement) { if (textareaElement.tagName === "TEXTAREA") { - ;(textareaElement as HTMLTextAreaElement).dataset.supermemories = - `<br>Supermemories of user (only for the reference): ${response.data}</br>` + (textareaElement as HTMLTextAreaElement).dataset.supermemories = + `<br>Supermemories of user (only for the reference): ${response.data}</br>`; } else { - ;(textareaElement as HTMLElement).dataset.supermemories = - `<br>Supermemories of user (only for the reference): ${response.data}</br>` + (textareaElement as HTMLElement).dataset.supermemories = + `<br>Supermemories of user (only for the reference): ${response.data}</br>`; } - iconElement.dataset.memoriesData = response.data + iconElement.dataset.memoriesData = response.data; - updateT3IconFeedback("Included Memories", iconElement) + updateT3IconFeedback("Included Memories", iconElement); } else { - console.warn("T3 input area not found after successful memory fetch") - updateT3IconFeedback("Memories found", iconElement) + console.warn("T3 input area not found after successful memory fetch"); + updateT3IconFeedback("Memories found", iconElement); } } else { - console.warn("No memories found or API response invalid for T3") - updateT3IconFeedback("No memories found", iconElement) + console.warn("No memories found or API response invalid for T3"); + updateT3IconFeedback("No memories found", iconElement); } } catch (error) { - console.error("Error getting related memories for T3:", error) + console.error("Error getting related memories for T3:", error); try { const icon = document.querySelector( '[id*="sm-t3-input-bar-element"]', - ) as HTMLElement + ) as HTMLElement; if (icon) { - updateT3IconFeedback("Error fetching memories", icon) + updateT3IconFeedback("Error fetching memories", icon); } } catch (feedbackError) { - console.error("Failed to update T3 error feedback:", feedbackError) + console.error("Failed to update T3 error feedback:", feedbackError); } } } @@ -277,10 +279,10 @@ function updateT3IconFeedback( resetAfter = 0, ) { if (!iconElement.dataset.originalHtml) { - iconElement.dataset.originalHtml = iconElement.innerHTML + iconElement.dataset.originalHtml = iconElement.innerHTML; } - const feedbackDiv = document.createElement("div") + const feedbackDiv = document.createElement("div"); feedbackDiv.style.cssText = ` display: flex; align-items: center; @@ -293,15 +295,15 @@ function updateT3IconFeedback( font-weight: 500; cursor: ${message === "Included Memories" ? "pointer" : "default"}; position: relative; - ` + `; feedbackDiv.innerHTML = ` <span>✓</span> <span>${message}</span> - ` + `; if (message === "Included Memories" && iconElement.dataset.memoriesData) { - const popup = document.createElement("div") + const popup = document.createElement("div"); popup.style.cssText = ` position: fixed; bottom: 80px; @@ -319,9 +321,9 @@ function updateT3IconFeedback( z-index: 999999; display: none; border: 1px solid #333; - ` + `; - const header = document.createElement("div") + const header = document.createElement("div"); header.style.cssText = ` display: flex; justify-content: space-between; @@ -329,28 +331,28 @@ function updateT3IconFeedback( padding: 8px; border-bottom: 1px solid #333; opacity: 0.8; - ` + `; header.innerHTML = ` <span style="font-weight: 600; color: #fff;">Included Memories</span> - ` + `; - const content = document.createElement("div") + const content = document.createElement("div"); content.style.cssText = ` padding: 0; max-height: 300px; overflow-y: auto; - ` + `; - const memoriesText = iconElement.dataset.memoriesData || "" - console.log("Memories text:", memoriesText) + const memoriesText = iconElement.dataset.memoriesData || ""; + console.log("Memories text:", memoriesText); const individualMemories = memoriesText .split(/[,\n]/) .map((memory) => memory.trim()) - .filter((memory) => memory.length > 0 && memory !== ",") - console.log("Individual memories:", individualMemories) + .filter((memory) => memory.length > 0 && memory !== ","); + console.log("Individual memories:", individualMemories); individualMemories.forEach((memory, index) => { - const memoryItem = document.createElement("div") + const memoryItem = document.createElement("div"); memoryItem.style.cssText = ` display: flex; align-items: center; @@ -358,16 +360,16 @@ function updateT3IconFeedback( padding: 10px; font-size: 13px; line-height: 1.4; - ` + `; - const memoryText = document.createElement("div") + const memoryText = document.createElement("div"); memoryText.style.cssText = ` flex: 1; color: #e5e5e5; - ` - memoryText.textContent = memory.trim() + `; + memoryText.textContent = memory.trim(); - const removeBtn = document.createElement("button") + const removeBtn = document.createElement("button"); removeBtn.style.cssText = ` background: transparent; color: #9ca3af; @@ -380,164 +382,169 @@ function updateT3IconFeedback( display: flex; align-items: center; justify-content: center; - ` - removeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>` - removeBtn.dataset.memoryIndex = index.toString() + `; + removeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>`; + removeBtn.dataset.memoryIndex = index.toString(); removeBtn.addEventListener("mouseenter", () => { - removeBtn.style.color = "#ef4444" - }) + removeBtn.style.color = "#ef4444"; + }); removeBtn.addEventListener("mouseleave", () => { - removeBtn.style.color = "#9ca3af" - }) + removeBtn.style.color = "#9ca3af"; + }); - memoryItem.appendChild(memoryText) - memoryItem.appendChild(removeBtn) - content.appendChild(memoryItem) - }) + memoryItem.appendChild(memoryText); + memoryItem.appendChild(removeBtn); + content.appendChild(memoryItem); + }); - popup.appendChild(header) - popup.appendChild(content) - document.body.appendChild(popup) + popup.appendChild(header); + popup.appendChild(content); + document.body.appendChild(popup); feedbackDiv.addEventListener("mouseenter", () => { - const textSpan = feedbackDiv.querySelector("span:last-child") + const textSpan = feedbackDiv.querySelector("span:last-child"); if (textSpan) { - textSpan.textContent = "Click to see memories" + textSpan.textContent = "Click to see memories"; } - }) + }); feedbackDiv.addEventListener("mouseleave", () => { - const textSpan = feedbackDiv.querySelector("span:last-child") + const textSpan = feedbackDiv.querySelector("span:last-child"); if (textSpan) { - textSpan.textContent = "Included Memories" + textSpan.textContent = "Included Memories"; } - }) + }); feedbackDiv.addEventListener("click", (e) => { - e.stopPropagation() - popup.style.display = "block" - }) + e.stopPropagation(); + popup.style.display = "block"; + }); document.addEventListener("click", (e) => { if (!popup.contains(e.target as Node)) { - popup.style.display = "none" + popup.style.display = "none"; } - }) + }); content.querySelectorAll("button[data-memory-index]").forEach((button) => { - const htmlButton = button as HTMLButtonElement + const htmlButton = button as HTMLButtonElement; htmlButton.addEventListener("click", () => { - const index = Number.parseInt(htmlButton.dataset.memoryIndex || "0", 10) - const memoryItem = htmlButton.parentElement + const index = Number.parseInt( + htmlButton.dataset.memoryIndex || "0", + 10, + ); + const memoryItem = htmlButton.parentElement; if (memoryItem) { - content.removeChild(memoryItem) + content.removeChild(memoryItem); } const currentMemories = (iconElement.dataset.memoriesData || "") .split(/[,\n]/) .map((memory) => memory.trim()) - .filter((memory) => memory.length > 0 && memory !== ",") - currentMemories.splice(index, 1) + .filter((memory) => memory.length > 0 && memory !== ","); + currentMemories.splice(index, 1); - const updatedMemories = currentMemories.join(" ,") + const updatedMemories = currentMemories.join(" ,"); - iconElement.dataset.memoriesData = updatedMemories + iconElement.dataset.memoriesData = updatedMemories; const textareaElement = (document.querySelector("textarea") as HTMLTextAreaElement) || - (document.querySelector('div[contenteditable="true"]') as HTMLElement) + (document.querySelector( + 'div[contenteditable="true"]', + ) as HTMLElement); if (textareaElement) { - textareaElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${updatedMemories}</div>` + textareaElement.dataset.supermemories = `<div>Supermemories of user (only for the reference): ${updatedMemories}</div>`; } content .querySelectorAll("button[data-memory-index]") .forEach((btn, newIndex) => { - const htmlBtn = btn as HTMLButtonElement - htmlBtn.dataset.memoryIndex = newIndex.toString() - }) + const htmlBtn = btn as HTMLButtonElement; + htmlBtn.dataset.memoryIndex = newIndex.toString(); + }); if (currentMemories.length <= 1) { if (textareaElement?.dataset.supermemories) { - delete textareaElement.dataset.supermemories - delete iconElement.dataset.memoriesData - iconElement.innerHTML = iconElement.dataset.originalHtml || "" - delete iconElement.dataset.originalHtml + delete textareaElement.dataset.supermemories; + delete iconElement.dataset.memoriesData; + iconElement.innerHTML = iconElement.dataset.originalHtml || ""; + delete iconElement.dataset.originalHtml; } - popup.style.display = "none" + popup.style.display = "none"; if (document.body.contains(popup)) { - document.body.removeChild(popup) + document.body.removeChild(popup); } } - }) - }) + }); + }); setTimeout(() => { if (document.body.contains(popup)) { - document.body.removeChild(popup) + document.body.removeChild(popup); } - }, 300000) + }, 300000); } - iconElement.innerHTML = "" - iconElement.appendChild(feedbackDiv) + iconElement.innerHTML = ""; + iconElement.appendChild(feedbackDiv); if (resetAfter > 0) { setTimeout(() => { - iconElement.innerHTML = iconElement.dataset.originalHtml || "" - delete iconElement.dataset.originalHtml - }, resetAfter) + iconElement.innerHTML = iconElement.dataset.originalHtml || ""; + delete iconElement.dataset.originalHtml; + }, resetAfter); } } function setupT3PromptCapture() { if (document.body.hasAttribute("data-t3-prompt-capture-setup")) { - return + return; } - document.body.setAttribute("data-t3-prompt-capture-setup", "true") + document.body.setAttribute("data-t3-prompt-capture-setup", "true"); const captureT3PromptContent = async (source: string) => { - let promptContent = "" + let promptContent = ""; - const textarea = document.querySelector("textarea") as HTMLTextAreaElement + const textarea = document.querySelector("textarea") as HTMLTextAreaElement; if (textarea) { - promptContent = textarea.value || "" + promptContent = textarea.value || ""; } if (!promptContent) { const contentEditableDiv = document.querySelector( 'div[contenteditable="true"]', - ) as HTMLElement + ) as HTMLElement; if (contentEditableDiv) { promptContent = - contentEditableDiv.textContent || contentEditableDiv.innerText || "" + contentEditableDiv.textContent || contentEditableDiv.innerText || ""; } } const textareaElement = textarea || - (document.querySelector('div[contenteditable="true"]') as HTMLElement) - const storedMemories = textareaElement?.dataset.supermemories + (document.querySelector('div[contenteditable="true"]') as HTMLElement); + const storedMemories = textareaElement?.dataset.supermemories; if ( storedMemories && textareaElement && !promptContent.includes("Supermemories of user") ) { if (textareaElement.tagName === "TEXTAREA") { - ;(textareaElement as HTMLTextAreaElement).value = - `${promptContent} ${storedMemories}` - promptContent = (textareaElement as HTMLTextAreaElement).value + (textareaElement as HTMLTextAreaElement).value = + `${promptContent} ${storedMemories}`; + promptContent = (textareaElement as HTMLTextAreaElement).value; } else { - textareaElement.innerHTML = `${textareaElement.innerHTML} ${storedMemories}` + textareaElement.innerHTML = `${textareaElement.innerHTML} ${storedMemories}`; promptContent = - textareaElement.textContent || textareaElement.innerText || "" + textareaElement.textContent || textareaElement.innerText || ""; } } if (promptContent.trim()) { - console.log(`T3 prompt submitted via ${source}:`, promptContent) + console.log(`T3 prompt submitted via ${source}:`, promptContent); try { await browser.runtime.sendMessage({ @@ -547,48 +554,48 @@ function setupT3PromptCapture() { platform: "t3", source: source, }, - }) + }); } catch (error) { - console.error("Error sending T3 prompt to background:", error) + console.error("Error sending T3 prompt to background:", error); } } - const icons = document.querySelectorAll('[id*="sm-t3-input-bar-element"]') + const icons = document.querySelectorAll('[id*="sm-t3-input-bar-element"]'); icons.forEach((icon) => { - const iconElement = icon as HTMLElement + const iconElement = icon as HTMLElement; if (iconElement.dataset.originalHtml) { - iconElement.innerHTML = iconElement.dataset.originalHtml - delete iconElement.dataset.originalHtml - delete iconElement.dataset.memoriesData + iconElement.innerHTML = iconElement.dataset.originalHtml; + delete iconElement.dataset.originalHtml; + delete iconElement.dataset.memoriesData; } - }) + }); if (textareaElement?.dataset.supermemories) { - delete textareaElement.dataset.supermemories + delete textareaElement.dataset.supermemories; } - } + }; document.addEventListener( "click", async (event) => { - const target = event.target as HTMLElement + const target = event.target as HTMLElement; const sendButton = target.closest("button.focus-visible\\:ring-ring") || target.closest('button[class*="bg-[rgb(162,59,103)]"]') || - target.closest('button[class*="rounded-lg"]') + target.closest('button[class*="rounded-lg"]'); if (sendButton) { - await captureT3PromptContent("button click") + await captureT3PromptContent("button click"); } }, true, - ) + ); document.addEventListener( "keydown", async (event) => { - const target = event.target as HTMLElement + const target = event.target as HTMLElement; if ( (target.matches("textarea") || @@ -597,9 +604,9 @@ function setupT3PromptCapture() { !event.shiftKey ) { if (target.matches("textarea")) { - const promptContent = (target as HTMLTextAreaElement).value || "" + const promptContent = (target as HTMLTextAreaElement).value || ""; if (promptContent.trim()) { - console.log("T3 prompt submitted via Enter key:", promptContent) + console.log("T3 prompt submitted via Enter key:", promptContent); try { await browser.runtime.sendMessage({ @@ -610,83 +617,83 @@ function setupT3PromptCapture() { source: "Enter key", }, actionSource: "t3", - }) + }); } catch (error) { console.error( "Error sending T3 textarea prompt to background:", error, - ) + ); } } } else { - await captureT3PromptContent("Enter key") + await captureT3PromptContent("Enter key"); } } }, true, - ) + ); } async function setupT3AutoFetch() { const result = await chrome.storage.local.get([ STORAGE_KEYS.AUTO_SEARCH_ENABLED, - ]) - const autoSearchEnabled = result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false + ]); + const autoSearchEnabled = result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false; if (!autoSearchEnabled) { - return + return; } const textareaElement = (document.querySelector("textarea") as HTMLTextAreaElement) || - (document.querySelector('div[contenteditable="true"]') as HTMLElement) + (document.querySelector('div[contenteditable="true"]') as HTMLElement); if ( !textareaElement || textareaElement.hasAttribute("data-supermemory-auto-fetch") ) { - return + return; } - textareaElement.setAttribute("data-supermemory-auto-fetch", "true") + textareaElement.setAttribute("data-supermemory-auto-fetch", "true"); const handleInput = () => { if (t3DebounceTimeout) { - clearTimeout(t3DebounceTimeout) + clearTimeout(t3DebounceTimeout); } t3DebounceTimeout = setTimeout(async () => { - let content = "" + let content = ""; if (textareaElement.tagName === "TEXTAREA") { - content = (textareaElement as HTMLTextAreaElement).value?.trim() || "" + content = (textareaElement as HTMLTextAreaElement).value?.trim() || ""; } else { - content = textareaElement.textContent?.trim() || "" + content = textareaElement.textContent?.trim() || ""; } if (content.length > 2) { await getRelatedMemoriesForT3( POSTHOG_EVENT_KEY.T3_CHAT_MEMORIES_AUTO_SEARCHED, - ) + ); } else if (content.length === 0) { const icons = document.querySelectorAll( '[id*="sm-t3-input-bar-element"]', - ) + ); icons.forEach((icon) => { - const iconElement = icon as HTMLElement + const iconElement = icon as HTMLElement; if (iconElement.dataset.originalHtml) { - iconElement.innerHTML = iconElement.dataset.originalHtml - delete iconElement.dataset.originalHtml - delete iconElement.dataset.memoriesData + iconElement.innerHTML = iconElement.dataset.originalHtml; + delete iconElement.dataset.originalHtml; + delete iconElement.dataset.memoriesData; } - }) + }); if (textareaElement.dataset.supermemories) { - delete textareaElement.dataset.supermemories + delete textareaElement.dataset.supermemories; } } - }, UI_CONFIG.AUTO_SEARCH_DEBOUNCE_DELAY) - } + }, UI_CONFIG.AUTO_SEARCH_DEBOUNCE_DELAY); + }; - textareaElement.addEventListener("input", handleInput) + textareaElement.addEventListener("input", handleInput); } diff --git a/apps/browser-extension/entrypoints/content/twitter.ts b/apps/browser-extension/entrypoints/content/twitter.ts index 15c6ec50..272983cf 100644 --- a/apps/browser-extension/entrypoints/content/twitter.ts +++ b/apps/browser-extension/entrypoints/content/twitter.ts @@ -3,24 +3,24 @@ import { ELEMENT_IDS, MESSAGE_TYPES, POSTHOG_EVENT_KEY, -} from "../../utils/constants" -import { trackEvent } from "../../utils/posthog" -import { createTwitterImportButton, DOMUtils } from "../../utils/ui-components" +} from "../../utils/constants"; +import { trackEvent } from "../../utils/posthog"; +import { createTwitterImportButton, DOMUtils } from "../../utils/ui-components"; export function initializeTwitter() { if (!DOMUtils.isOnDomain(DOMAINS.TWITTER)) { - return + return; } // Initial setup if (window.location.pathname === "/i/bookmarks") { setTimeout(() => { - addTwitterImportButton() - }, 2000) + addTwitterImportButton(); + }, 2000); } else { // Remove button if not on bookmarks page if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_IMPORT_BUTTON)) { - DOMUtils.removeElement(ELEMENT_IDS.TWITTER_IMPORT_BUTTON) + DOMUtils.removeElement(ELEMENT_IDS.TWITTER_IMPORT_BUTTON); } } } @@ -28,75 +28,75 @@ export function initializeTwitter() { function addTwitterImportButton() { // Only show the import button on the bookmarks page if (window.location.pathname !== "/i/bookmarks") { - return + return; } if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_IMPORT_BUTTON)) { - return + return; } const button = createTwitterImportButton(async () => { try { await browser.runtime.sendMessage({ type: MESSAGE_TYPES.BATCH_IMPORT_ALL, - }) + }); await trackEvent(POSTHOG_EVENT_KEY.TWITTER_IMPORT_STARTED, { source: `${POSTHOG_EVENT_KEY.SOURCE}_content_script`, - }) + }); } catch (error) { - console.error("Error starting import:", error) + console.error("Error starting import:", error); } - }) + }); - document.body.appendChild(button) + document.body.appendChild(button); } export function updateTwitterImportUI(message: { - type: string - importedMessage?: string - totalImported?: number + type: string; + importedMessage?: string; + totalImported?: number; }) { const importButton = document.getElementById( ELEMENT_IDS.TWITTER_IMPORT_BUTTON, - ) - if (!importButton) return + ); + if (!importButton) return; - const iconUrl = browser.runtime.getURL("/icon-16.png") + const iconUrl = browser.runtime.getURL("/icon-16.png"); if (message.type === MESSAGE_TYPES.IMPORT_UPDATE) { importButton.innerHTML = ` <img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 4px;" /> <span style="font-weight: 500; font-size: 14px;">${message.importedMessage}</span> - ` - importButton.style.cursor = "default" + `; + importButton.style.cursor = "default"; } if (message.type === MESSAGE_TYPES.IMPORT_DONE) { importButton.innerHTML = ` <img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 4px;" /> <span style="font-weight: 500; font-size: 14px; color: #059669;">✓ Imported ${message.totalImported} tweets!</span> - ` + `; setTimeout(() => { importButton.innerHTML = ` <img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 4px;" /> <span style="font-weight: 500; font-size: 14px;">Import Bookmarks</span> - ` - importButton.style.cursor = "pointer" - }, 3000) + `; + importButton.style.cursor = "pointer"; + }, 3000); } } export function handleTwitterNavigation() { if (!DOMUtils.isOnDomain(DOMAINS.TWITTER)) { - return + return; } if (window.location.pathname === "/i/bookmarks") { - addTwitterImportButton() + addTwitterImportButton(); } else { if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_IMPORT_BUTTON)) { - DOMUtils.removeElement(ELEMENT_IDS.TWITTER_IMPORT_BUTTON) + DOMUtils.removeElement(ELEMENT_IDS.TWITTER_IMPORT_BUTTON); } } -}
\ No newline at end of file +} diff --git a/apps/browser-extension/entrypoints/popup/App.tsx b/apps/browser-extension/entrypoints/popup/App.tsx index 3e4f15e2..3d4aeedd 100644 --- a/apps/browser-extension/entrypoints/popup/App.tsx +++ b/apps/browser-extension/entrypoints/popup/App.tsx @@ -1,34 +1,35 @@ -import { useQueryClient } from "@tanstack/react-query" -import { useEffect, useState } from "react" -import "./App.css" -import { MESSAGE_TYPES, STORAGE_KEYS } from "../../utils/constants" +import { useQueryClient } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; +import "./App.css"; +import { MESSAGE_TYPES, STORAGE_KEYS } from "../../utils/constants"; import { useDefaultProject, useProjects, useSetDefaultProject, -} from "../../utils/query-hooks" -import type { Project } from "../../utils/types" +} from "../../utils/query-hooks"; +import type { Project } from "../../utils/types"; function App() { - const [userSignedIn, setUserSignedIn] = useState<boolean>(false) - const [loading, setLoading] = useState<boolean>(true) - const [showProjectSelector, setShowProjectSelector] = useState<boolean>(false) - const [currentUrl, setCurrentUrl] = useState<string>("") - const [currentTitle, setCurrentTitle] = useState<string>("") - const [saving, setSaving] = useState<boolean>(false) + const [userSignedIn, setUserSignedIn] = useState<boolean>(false); + const [loading, setLoading] = useState<boolean>(true); + const [showProjectSelector, setShowProjectSelector] = + useState<boolean>(false); + const [currentUrl, setCurrentUrl] = useState<string>(""); + const [currentTitle, setCurrentTitle] = useState<string>(""); + const [saving, setSaving] = useState<boolean>(false); const [activeTab, setActiveTab] = useState<"save" | "imports" | "settings">( "save", - ) - const [autoSearchEnabled, setAutoSearchEnabled] = useState<boolean>(false) + ); + const [autoSearchEnabled, setAutoSearchEnabled] = useState<boolean>(false); - const queryClient = useQueryClient() + const queryClient = useQueryClient(); const { data: projects = [], isLoading: loadingProjects } = useProjects({ enabled: userSignedIn, - }) + }); const { data: defaultProject } = useDefaultProject({ enabled: userSignedIn, - }) - const setDefaultProjectMutation = useSetDefaultProject() + }); + const setDefaultProjectMutation = useSetDefaultProject(); useEffect(() => { const checkAuthStatus = async () => { @@ -36,131 +37,131 @@ function App() { const result = await chrome.storage.local.get([ STORAGE_KEYS.BEARER_TOKEN, STORAGE_KEYS.AUTO_SEARCH_ENABLED, - ]) - const isSignedIn = !!result[STORAGE_KEYS.BEARER_TOKEN] - setUserSignedIn(isSignedIn) + ]); + const isSignedIn = !!result[STORAGE_KEYS.BEARER_TOKEN]; + setUserSignedIn(isSignedIn); const autoSearchSetting = - result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false - setAutoSearchEnabled(autoSearchSetting) + result[STORAGE_KEYS.AUTO_SEARCH_ENABLED] ?? false; + setAutoSearchEnabled(autoSearchSetting); } catch (error) { - console.error("Error checking auth status:", error) - setUserSignedIn(false) + console.error("Error checking auth status:", error); + setUserSignedIn(false); } finally { - setLoading(false) + setLoading(false); } - } + }; const getCurrentTab = async () => { try { const tabs = await chrome.tabs.query({ active: true, currentWindow: true, - }) + }); if (tabs.length > 0 && tabs[0].url && tabs[0].title) { - setCurrentUrl(tabs[0].url) - setCurrentTitle(tabs[0].title) + setCurrentUrl(tabs[0].url); + setCurrentTitle(tabs[0].title); } } catch (error) { - console.error("Error getting current tab:", error) + console.error("Error getting current tab:", error); } - } + }; - checkAuthStatus() - getCurrentTab() - }, []) + checkAuthStatus(); + getCurrentTab(); + }, []); const handleProjectSelect = (project: Project) => { setDefaultProjectMutation.mutate(project, { onSuccess: () => { - setShowProjectSelector(false) + setShowProjectSelector(false); }, onError: (error) => { - console.error("Error setting default project:", error) + console.error("Error setting default project:", error); }, - }) - } + }); + }; const handleShowProjectSelector = () => { - setShowProjectSelector(true) - } + setShowProjectSelector(true); + }; useEffect(() => { if (!defaultProject && projects.length > 0) { - const firstProject = projects[0] - setDefaultProjectMutation.mutate(firstProject) + const firstProject = projects[0]; + setDefaultProjectMutation.mutate(firstProject); } - }, [defaultProject, projects, setDefaultProjectMutation]) + }, [defaultProject, projects, setDefaultProjectMutation]); const handleSaveCurrentPage = async () => { - setSaving(true) + setSaving(true); try { const tabs = await chrome.tabs.query({ active: true, currentWindow: true, - }) + }); if (tabs.length > 0 && tabs[0].id) { const response = await chrome.tabs.sendMessage(tabs[0].id, { action: MESSAGE_TYPES.SAVE_MEMORY, actionSource: "popup", - }) + }); if (response?.success) { await chrome.tabs.sendMessage(tabs[0].id, { action: MESSAGE_TYPES.SHOW_TOAST, state: "success", - }) + }); } - window.close() + window.close(); } } catch (error) { - console.error("Failed to save current page:", error) + console.error("Failed to save current page:", error); try { const tabs = await chrome.tabs.query({ active: true, currentWindow: true, - }) + }); if (tabs.length > 0 && tabs[0].id) { await chrome.tabs.sendMessage(tabs[0].id, { action: MESSAGE_TYPES.SHOW_TOAST, state: "error", - }) + }); } } catch (toastError) { - console.error("Failed to show error toast:", toastError) + console.error("Failed to show error toast:", toastError); } - window.close() + window.close(); } finally { - setSaving(false) + setSaving(false); } - } + }; const handleAutoSearchToggle = async (enabled: boolean) => { try { await chrome.storage.local.set({ [STORAGE_KEYS.AUTO_SEARCH_ENABLED]: enabled, - }) - setAutoSearchEnabled(enabled) + }); + setAutoSearchEnabled(enabled); } catch (error) { - console.error("Error updating auto search setting:", error) + console.error("Error updating auto search setting:", error); } - } + }; const handleSignOut = async () => { try { - await chrome.storage.local.remove([STORAGE_KEYS.BEARER_TOKEN]) - await chrome.storage.local.remove([STORAGE_KEYS.USER_DATA]) - await chrome.storage.local.remove([STORAGE_KEYS.DEFAULT_PROJECT]) - setUserSignedIn(false) - queryClient.clear() + await chrome.storage.local.remove([STORAGE_KEYS.BEARER_TOKEN]); + await chrome.storage.local.remove([STORAGE_KEYS.USER_DATA]); + await chrome.storage.local.remove([STORAGE_KEYS.DEFAULT_PROJECT]); + setUserSignedIn(false); + queryClient.clear(); } catch (error) { - console.error("Error signing out:", error) + console.error("Error signing out:", error); } - } + }; if (loading) { return ( @@ -179,7 +180,7 @@ function App() { <div>Loading...</div> </div> </div> - ) + ); } return ( @@ -330,7 +331,7 @@ function App() { onClick={() => { chrome.tabs.create({ url: "https://chatgpt.com/#settings/Personalization", - }) + }); }} type="button" > @@ -361,17 +362,17 @@ function App() { const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true, - }) + }); - const targetUrl = "https://x.com/i/bookmarks" + const targetUrl = "https://x.com/i/bookmarks"; if (activeTab?.url === targetUrl) { - return + return; } await chrome.tabs.create({ url: targetUrl, - }) + }); }} type="button" > @@ -504,7 +505,7 @@ function App() { <button className="bg-transparent border-none text-blue-500 cursor-pointer underline text-sm p-0 hover:text-blue-700" onClick={() => { - window.open("mailto:[email protected]", "_blank") + window.open("mailto:[email protected]", "_blank"); }} type="button" > @@ -519,7 +520,7 @@ function App() { url: import.meta.env.PROD ? "https://app.supermemory.ai/login" : "http://localhost:3000/login", - }) + }); }} type="button" > @@ -530,7 +531,7 @@ function App() { )} </div> </div> - ) + ); } -export default App +export default App; diff --git a/apps/browser-extension/entrypoints/popup/main.tsx b/apps/browser-extension/entrypoints/popup/main.tsx index 746e1018..5376a552 100644 --- a/apps/browser-extension/entrypoints/popup/main.tsx +++ b/apps/browser-extension/entrypoints/popup/main.tsx @@ -1,11 +1,11 @@ -import { QueryClientProvider } from "@tanstack/react-query" -import React from "react" -import ReactDOM from "react-dom/client" -import { queryClient } from "../../utils/query-client" -import App from "./App.js" -import "./style.css" +import { QueryClientProvider } from "@tanstack/react-query"; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { queryClient } from "../../utils/query-client"; +import App from "./App.js"; +import "./style.css"; -const rootElement = document.getElementById("root") +const rootElement = document.getElementById("root"); if (rootElement) { ReactDOM.createRoot(rootElement).render( <React.StrictMode> @@ -13,5 +13,5 @@ if (rootElement) { <App /> </QueryClientProvider> </React.StrictMode>, - ) + ); } diff --git a/apps/browser-extension/entrypoints/welcome/Welcome.tsx b/apps/browser-extension/entrypoints/welcome/Welcome.tsx index 9463eba4..00bd01cc 100644 --- a/apps/browser-extension/entrypoints/welcome/Welcome.tsx +++ b/apps/browser-extension/entrypoints/welcome/Welcome.tsx @@ -77,7 +77,7 @@ function Welcome() { url: import.meta.env.PROD ? "https://app.supermemory.ai/login" : "http://localhost:3000/login", - }) + }); }} type="button" > @@ -101,7 +101,7 @@ function Welcome() { </div> </div> </div> - ) + ); } -export default Welcome +export default Welcome; diff --git a/apps/browser-extension/entrypoints/welcome/main.tsx b/apps/browser-extension/entrypoints/welcome/main.tsx index 2df8dbf1..84ad6b8c 100644 --- a/apps/browser-extension/entrypoints/welcome/main.tsx +++ b/apps/browser-extension/entrypoints/welcome/main.tsx @@ -1,11 +1,11 @@ -import { QueryClientProvider } from "@tanstack/react-query" -import React from "react" -import ReactDOM from "react-dom/client" -import { queryClient } from "../../utils/query-client" -import Welcome from "./Welcome" -import "./welcome.css" +import { QueryClientProvider } from "@tanstack/react-query"; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { queryClient } from "../../utils/query-client"; +import Welcome from "./Welcome"; +import "./welcome.css"; -const rootElement = document.getElementById("root") +const rootElement = document.getElementById("root"); if (rootElement) { ReactDOM.createRoot(rootElement).render( <React.StrictMode> @@ -13,5 +13,5 @@ if (rootElement) { <Welcome /> </QueryClientProvider> </React.StrictMode>, - ) + ); } diff --git a/apps/browser-extension/utils/api.ts b/apps/browser-extension/utils/api.ts index 2a4c838b..273f9152 100644 --- a/apps/browser-extension/utils/api.ts +++ b/apps/browser-extension/utils/api.ts @@ -1,27 +1,27 @@ /** * API service for supermemory browser extension */ -import { API_ENDPOINTS, STORAGE_KEYS } from "./constants" +import { API_ENDPOINTS, STORAGE_KEYS } from "./constants"; import { AuthenticationError, type MemoryPayload, type Project, type ProjectsResponse, SupermemoryAPIError, -} from "./types" +} from "./types"; /** * Get bearer token from storage */ async function getBearerToken(): Promise<string> { - const result = await chrome.storage.local.get([STORAGE_KEYS.BEARER_TOKEN]) - const token = result[STORAGE_KEYS.BEARER_TOKEN] + const result = await chrome.storage.local.get([STORAGE_KEYS.BEARER_TOKEN]); + const token = result[STORAGE_KEYS.BEARER_TOKEN]; if (!token) { - throw new AuthenticationError("Bearer token not found") + throw new AuthenticationError("Bearer token not found"); } - return token + return token; } /** @@ -31,7 +31,7 @@ async function makeAuthenticatedRequest<T>( endpoint: string, options: RequestInit = {}, ): Promise<T> { - const token = await getBearerToken() + const token = await getBearerToken(); const response = await fetch(`${API_ENDPOINTS.SUPERMEMORY_API}${endpoint}`, { ...options, @@ -41,19 +41,19 @@ async function makeAuthenticatedRequest<T>( "Content-Type": "application/json", ...options.headers, }, - }) + }); if (!response.ok) { if (response.status === 401) { - throw new AuthenticationError("Invalid or expired token") + throw new AuthenticationError("Invalid or expired token"); } throw new SupermemoryAPIError( `API request failed: ${response.statusText}`, response.status, - ) + ); } - return response.json() + return response.json(); } /** @@ -62,11 +62,11 @@ async function makeAuthenticatedRequest<T>( export async function fetchProjects(): Promise<Project[]> { try { const response = - await makeAuthenticatedRequest<ProjectsResponse>("/v3/projects") - return response.projects + await makeAuthenticatedRequest<ProjectsResponse>("/v3/projects"); + return response.projects; } catch (error) { - console.error("Failed to fetch projects:", error) - throw error + console.error("Failed to fetch projects:", error); + throw error; } } @@ -77,11 +77,11 @@ export async function getDefaultProject(): Promise<Project | null> { try { const result = await chrome.storage.local.get([ STORAGE_KEYS.DEFAULT_PROJECT, - ]) - return result[STORAGE_KEYS.DEFAULT_PROJECT] || null + ]); + return result[STORAGE_KEYS.DEFAULT_PROJECT] || null; } catch (error) { - console.error("Failed to get default project:", error) - return null + console.error("Failed to get default project:", error); + return null; } } @@ -92,10 +92,10 @@ export async function setDefaultProject(project: Project): Promise<void> { try { await chrome.storage.local.set({ [STORAGE_KEYS.DEFAULT_PROJECT]: project, - }) + }); } catch (error) { - console.error("Failed to set default project:", error) - throw error + console.error("Failed to set default project:", error); + throw error; } } @@ -107,11 +107,11 @@ export async function saveMemory(payload: MemoryPayload): Promise<unknown> { const response = await makeAuthenticatedRequest<unknown>("/v3/documents", { method: "POST", body: JSON.stringify(payload), - }) - return response + }); + return response; } catch (error) { - console.error("Failed to save memory:", error) - throw error + console.error("Failed to save memory:", error); + throw error; } } @@ -123,11 +123,11 @@ export async function searchMemories(query: string): Promise<unknown> { const response = await makeAuthenticatedRequest<unknown>("/v4/search", { method: "POST", body: JSON.stringify({ q: query, include: { relatedMemories: true } }), - }) - return response + }); + return response; } catch (error) { - console.error("Failed to search memories:", error) - throw error + console.error("Failed to search memories:", error); + throw error; } } @@ -150,13 +150,13 @@ export async function saveAllTweets( }, }), }, - ) - return response + ); + return response; } catch (error) { if (error instanceof SupermemoryAPIError && error.statusCode === 409) { // Skip if already exists (409 Conflict) - return + return; } - throw error + throw error; } } diff --git a/apps/browser-extension/utils/constants.ts b/apps/browser-extension/utils/constants.ts index ac286717..39ea54a7 100644 --- a/apps/browser-extension/utils/constants.ts +++ b/apps/browser-extension/utils/constants.ts @@ -8,7 +8,7 @@ export const API_ENDPOINTS = { SUPERMEMORY_WEB: import.meta.env.PROD ? "https://app.supermemory.ai" : "http://localhost:3000", -} as const +} as const; /** * Storage Keys @@ -22,7 +22,7 @@ export const STORAGE_KEYS = { TWITTER_AUTH_TOKEN: "twitter-auth-token", DEFAULT_PROJECT: "sm-default-project", AUTO_SEARCH_ENABLED: "sm-auto-search-enabled", -} as const +} as const; /** * DOM Element IDs @@ -35,7 +35,7 @@ export const ELEMENT_IDS = { CHATGPT_INPUT_BAR_ELEMENT: "sm-chatgpt-input-bar-element", CLAUDE_INPUT_BAR_ELEMENT: "sm-claude-input-bar-element", T3_INPUT_BAR_ELEMENT: "sm-t3-input-bar-element", -} as const +} as const; /** * UI Configuration @@ -49,7 +49,7 @@ export const UI_CONFIG = { OBSERVER_THROTTLE_DELAY: 300, // milliseconds between observer callback executions ROUTE_CHECK_INTERVAL: 2000, // milliseconds between route change checks API_REQUEST_TIMEOUT: 10000, // milliseconds for API request timeout -} as const +} as const; /** * Supported Domains @@ -60,7 +60,7 @@ export const DOMAINS = { CLAUDE: ["claude.ai"], T3: ["t3.chat"], SUPERMEMORY: ["localhost", "supermemory.ai", "app.supermemory.ai"], -} as const +} as const; /** * Container Tags @@ -68,7 +68,7 @@ export const DOMAINS = { export const CONTAINER_TAGS = { TWITTER_BOOKMARKS: "sm_project_twitter_bookmarks", DEFAULT_PROJECT: "sm_project_default", -} as const +} as const; /** * Message Types for extension communication @@ -81,11 +81,11 @@ export const MESSAGE_TYPES = { IMPORT_DONE: "sm-import-done", GET_RELATED_MEMORIES: "sm-get-related-memories", CAPTURE_PROMPT: "sm-capture-prompt", -} as const +} as const; export const CONTEXT_MENU_IDS = { SAVE_TO_SUPERMEMORY: "sm-save-to-supermemory", -} as const +} as const; export const POSTHOG_EVENT_KEY = { TWITTER_IMPORT_STARTED: "twitter_import_started", @@ -98,4 +98,4 @@ export const POSTHOG_EVENT_KEY = { CLAUDE_CHAT_MEMORIES_AUTO_SEARCHED: "claude_chat_memories_auto_searched", CHATGPT_CHAT_MEMORIES_SEARCHED: "chatgpt_chat_memories_searched", CHATGPT_CHAT_MEMORIES_AUTO_SEARCHED: "chatgpt_chat_memories_auto_searched", -} as const +} as const; diff --git a/apps/browser-extension/utils/memory-popup.ts b/apps/browser-extension/utils/memory-popup.ts index ba2d2a1c..b10d814b 100644 --- a/apps/browser-extension/utils/memory-popup.ts +++ b/apps/browser-extension/utils/memory-popup.ts @@ -4,13 +4,13 @@ */ export interface MemoryPopupConfig { - memoriesData: string - onClose: () => void - onRemove?: () => void + memoriesData: string; + onClose: () => void; + onRemove?: () => void; } export function createMemoryPopup(config: MemoryPopupConfig): HTMLElement { - const popup = document.createElement("div") + const popup = document.createElement("div"); popup.style.cssText = ` position: fixed; bottom: 80px; @@ -28,9 +28,9 @@ export function createMemoryPopup(config: MemoryPopupConfig): HTMLElement { z-index: 999999; display: none; overflow: hidden; - ` + `; - const header = document.createElement("div") + const header = document.createElement("div"); header.style.cssText = ` display: flex; justify-content: space-between; @@ -38,56 +38,56 @@ export function createMemoryPopup(config: MemoryPopupConfig): HTMLElement { padding: 8px; border-bottom: 1px solid #333; opacity: 0.8; - ` + `; header.innerHTML = ` <span style="font-size: 11px; font-weight: 600; letter-spacing: 0.5px;">INCLUDED MEMORIES</span> <div style="display: flex; gap: 4px;"> ${config.onRemove ? '<button id="remove-memories-btn" style="background: none; border: none; color: #ff4444; cursor: pointer; font-size: 14px; padding: 2px; border-radius: 2px;" title="Remove memories">✕</button>' : ""} <button id="close-popup-btn" style="background: none; border: none; color: white; cursor: pointer; font-size: 14px; padding: 2px; border-radius: 2px;">✕</button> </div> - ` + `; - const content = document.createElement("div") + const content = document.createElement("div"); content.style.cssText = ` padding: 8px; max-height: 300px; overflow-y: auto; line-height: 1.4; - ` - content.textContent = config.memoriesData + `; + content.textContent = config.memoriesData; - const closeBtn = header.querySelector("#close-popup-btn") - closeBtn?.addEventListener("click", config.onClose) + const closeBtn = header.querySelector("#close-popup-btn"); + closeBtn?.addEventListener("click", config.onClose); - const removeBtn = header.querySelector("#remove-memories-btn") + const removeBtn = header.querySelector("#remove-memories-btn"); if (removeBtn && config.onRemove) { - removeBtn.addEventListener("click", config.onRemove) + removeBtn.addEventListener("click", config.onRemove); } - popup.appendChild(header) - popup.appendChild(content) + popup.appendChild(header); + popup.appendChild(content); - return popup + return popup; } export function showMemoryPopup(popup: HTMLElement): void { - popup.style.display = "block" + popup.style.display = "block"; setTimeout(() => { if (popup.style.display === "block") { - hideMemoryPopup(popup) + hideMemoryPopup(popup); } - }, 10000) + }, 10000); } export function hideMemoryPopup(popup: HTMLElement): void { - popup.style.display = "none" + popup.style.display = "none"; } export function toggleMemoryPopup(popup: HTMLElement): void { if (popup.style.display === "none" || popup.style.display === "") { - showMemoryPopup(popup) + showMemoryPopup(popup); } else { - hideMemoryPopup(popup) + hideMemoryPopup(popup); } } diff --git a/apps/browser-extension/utils/posthog.ts b/apps/browser-extension/utils/posthog.ts index cdcdbc4e..8cf0b557 100644 --- a/apps/browser-extension/utils/posthog.ts +++ b/apps/browser-extension/utils/posthog.ts @@ -1,21 +1,21 @@ -import { PostHog } from "posthog-js/dist/module.no-external" -import { STORAGE_KEYS } from "./constants" +import { PostHog } from "posthog-js/dist/module.no-external"; +import { STORAGE_KEYS } from "./constants"; export async function identifyUser(posthog: PostHog): Promise<void> { - const stored = await chrome.storage.local.get([STORAGE_KEYS.USER_DATA]) - const userData = stored[STORAGE_KEYS.USER_DATA] + const stored = await chrome.storage.local.get([STORAGE_KEYS.USER_DATA]); + const userData = stored[STORAGE_KEYS.USER_DATA]; if (userData?.userId) { posthog.identify(userData.userId, { email: userData.email, name: userData.name, userId: userData.userId, - }) + }); } } -let posthogInstance: PostHog | null = null -let initializationPromise: Promise<PostHog> | null = null +let posthogInstance: PostHog | null = null; +let initializationPromise: Promise<PostHog> | null = null; export const POSTHOG_CONFIG = { api_host: "https://api.supermemory.ai/orange", @@ -24,40 +24,40 @@ export const POSTHOG_CONFIG = { persistence: "localStorage", capture_pageview: false, autocapture: false, -} as const +} as const; export async function getPostHogInstance(): Promise<PostHog> { if (posthogInstance) { - return posthogInstance + return posthogInstance; } if (initializationPromise) { - return initializationPromise + return initializationPromise; } - initializationPromise = initializePostHog() - return initializationPromise + initializationPromise = initializePostHog(); + return initializationPromise; } async function initializePostHog(): Promise<PostHog> { try { - const posthog = new PostHog() + const posthog = new PostHog(); if (!import.meta.env.WXT_POSTHOG_API_KEY) { - console.error("PostHog API key not configured") - throw new Error("PostHog API key not configured") + console.error("PostHog API key not configured"); + throw new Error("PostHog API key not configured"); } - posthog.init(import.meta.env.WXT_POSTHOG_API_KEY || "", POSTHOG_CONFIG) + posthog.init(import.meta.env.WXT_POSTHOG_API_KEY || "", POSTHOG_CONFIG); - await identifyUser(posthog) + await identifyUser(posthog); - posthogInstance = posthog - return posthog + posthogInstance = posthog; + return posthog; } catch (error) { - console.error("Failed to initialize PostHog:", error) - initializationPromise = null - throw error + console.error("Failed to initialize PostHog:", error); + initializationPromise = null; + throw error; } } @@ -66,9 +66,9 @@ export async function trackEvent( properties?: Record<string, unknown>, ): Promise<void> { try { - const posthog = await getPostHogInstance() - posthog.capture(eventName, properties) + const posthog = await getPostHogInstance(); + posthog.capture(eventName, properties); } catch (error) { - console.error(`Failed to track event ${eventName}:`, error) + console.error(`Failed to track event ${eventName}:`, error); } } diff --git a/apps/browser-extension/utils/query-client.ts b/apps/browser-extension/utils/query-client.ts index c1839691..dac7b639 100644 --- a/apps/browser-extension/utils/query-client.ts +++ b/apps/browser-extension/utils/query-client.ts @@ -1,7 +1,7 @@ /** * React Query configuration for supermemory browser extension */ -import { QueryClient } from "@tanstack/react-query" +import { QueryClient } from "@tanstack/react-query"; export const queryClient = new QueryClient({ defaultOptions: { @@ -11,9 +11,9 @@ export const queryClient = new QueryClient({ retry: (failureCount, error) => { // Don't retry on authentication errors if (error?.constructor?.name === "AuthenticationError") { - return false + return false; } - return failureCount < 3 + return failureCount < 3; }, refetchOnWindowFocus: false, }, @@ -21,4 +21,4 @@ export const queryClient = new QueryClient({ retry: 1, }, }, -}) +}); diff --git a/apps/browser-extension/utils/query-hooks.ts b/apps/browser-extension/utils/query-hooks.ts index 721a68ad..f1b683e7 100644 --- a/apps/browser-extension/utils/query-hooks.ts +++ b/apps/browser-extension/utils/query-hooks.ts @@ -1,21 +1,21 @@ /** * React Query hooks for supermemory API */ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { fetchProjects, getDefaultProject, saveMemory, searchMemories, setDefaultProject, -} from "./api" -import type { MemoryPayload } from "./types" +} from "./api"; +import type { MemoryPayload } from "./types"; // Query Keys export const queryKeys = { projects: ["projects"] as const, defaultProject: ["defaultProject"] as const, -} +}; // Projects Query export function useProjects(options?: { enabled?: boolean }) { @@ -24,7 +24,7 @@ export function useProjects(options?: { enabled?: boolean }) { queryFn: fetchProjects, staleTime: 5 * 60 * 1000, // 5 minutes enabled: options?.enabled ?? true, - }) + }); } // Default Project Query @@ -34,31 +34,31 @@ export function useDefaultProject(options?: { enabled?: boolean }) { queryFn: getDefaultProject, staleTime: 2 * 60 * 1000, // 2 minutes enabled: options?.enabled ?? true, - }) + }); } // Set Default Project Mutation export function useSetDefaultProject() { - const queryClient = useQueryClient() + const queryClient = useQueryClient(); return useMutation({ mutationFn: setDefaultProject, onSuccess: (_, project) => { - queryClient.setQueryData(queryKeys.defaultProject, project) + queryClient.setQueryData(queryKeys.defaultProject, project); }, - }) + }); } // Save Memory Mutation export function useSaveMemory() { return useMutation({ mutationFn: (payload: MemoryPayload) => saveMemory(payload), - }) + }); } // Search Memories Mutation export function useSearchMemories() { return useMutation({ mutationFn: (query: string) => searchMemories(query), - }) + }); } diff --git a/apps/browser-extension/utils/route-detection.ts b/apps/browser-extension/utils/route-detection.ts index a8a4714f..26787800 100644 --- a/apps/browser-extension/utils/route-detection.ts +++ b/apps/browser-extension/utils/route-detection.ts @@ -3,20 +3,20 @@ * Shared logic for detecting route changes across different AI chat platforms */ -import { UI_CONFIG } from "./constants" +import { UI_CONFIG } from "./constants"; export interface RouteDetectionConfig { - platform: string - selectors: string[] - reinitCallback: () => void - checkInterval?: number - observerThrottleDelay?: number + platform: string; + selectors: string[]; + reinitCallback: () => void; + checkInterval?: number; + observerThrottleDelay?: number; } export interface RouteDetectionCleanup { - observer: MutationObserver | null - urlCheckInterval: NodeJS.Timeout | null - observerThrottle: NodeJS.Timeout | null + observer: MutationObserver | null; + urlCheckInterval: NodeJS.Timeout | null; + observerThrottle: NodeJS.Timeout | null; } export function createRouteDetection( @@ -24,94 +24,97 @@ export function createRouteDetection( cleanup: RouteDetectionCleanup, ): void { if (cleanup.observer) { - cleanup.observer.disconnect() + cleanup.observer.disconnect(); } if (cleanup.urlCheckInterval) { - clearInterval(cleanup.urlCheckInterval) + clearInterval(cleanup.urlCheckInterval); } if (cleanup.observerThrottle) { - clearTimeout(cleanup.observerThrottle) - cleanup.observerThrottle = null + clearTimeout(cleanup.observerThrottle); + cleanup.observerThrottle = null; } - let currentUrl = window.location.href + let currentUrl = window.location.href; const checkForRouteChange = () => { if (window.location.href !== currentUrl) { - currentUrl = window.location.href - console.log(`${config.platform} route changed, re-initializing`) - setTimeout(config.reinitCallback, 1000) + currentUrl = window.location.href; + console.log(`${config.platform} route changed, re-initializing`); + setTimeout(config.reinitCallback, 1000); } - } + }; cleanup.urlCheckInterval = setInterval( checkForRouteChange, config.checkInterval || UI_CONFIG.ROUTE_CHECK_INTERVAL, - ) + ); cleanup.observer = new MutationObserver((mutations) => { if (cleanup.observerThrottle) { - return + return; } - let shouldRecheck = false + let shouldRecheck = false; mutations.forEach((mutation) => { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { - const element = node as Element + const element = node as Element; for (const selector of config.selectors) { if ( element.querySelector?.(selector) || element.matches?.(selector) ) { - shouldRecheck = true - break + shouldRecheck = true; + break; } } } - }) + }); } - }) + }); if (shouldRecheck) { cleanup.observerThrottle = setTimeout(() => { try { - cleanup.observerThrottle = null - config.reinitCallback() + cleanup.observerThrottle = null; + config.reinitCallback(); } catch (error) { - console.error(`Error in ${config.platform} observer callback:`, error) + console.error( + `Error in ${config.platform} observer callback:`, + error, + ); } - }, config.observerThrottleDelay || UI_CONFIG.OBSERVER_THROTTLE_DELAY) + }, config.observerThrottleDelay || UI_CONFIG.OBSERVER_THROTTLE_DELAY); } - }) + }); try { cleanup.observer.observe(document.body, { childList: true, subtree: true, - }) + }); } catch (error) { - console.error(`Failed to set up ${config.platform} route observer:`, error) + console.error(`Failed to set up ${config.platform} route observer:`, error); if (cleanup.urlCheckInterval) { - clearInterval(cleanup.urlCheckInterval) + clearInterval(cleanup.urlCheckInterval); } - cleanup.urlCheckInterval = setInterval(checkForRouteChange, 1000) + cleanup.urlCheckInterval = setInterval(checkForRouteChange, 1000); } } export function cleanupRouteDetection(cleanup: RouteDetectionCleanup): void { if (cleanup.observer) { - cleanup.observer.disconnect() - cleanup.observer = null + cleanup.observer.disconnect(); + cleanup.observer = null; } if (cleanup.urlCheckInterval) { - clearInterval(cleanup.urlCheckInterval) - cleanup.urlCheckInterval = null + clearInterval(cleanup.urlCheckInterval); + cleanup.urlCheckInterval = null; } if (cleanup.observerThrottle) { - clearTimeout(cleanup.observerThrottle) - cleanup.observerThrottle = null + clearTimeout(cleanup.observerThrottle); + cleanup.observerThrottle = null; } } diff --git a/apps/browser-extension/utils/twitter-auth.ts b/apps/browser-extension/utils/twitter-auth.ts index 3dfc50f6..17cbe464 100644 --- a/apps/browser-extension/utils/twitter-auth.ts +++ b/apps/browser-extension/utils/twitter-auth.ts @@ -2,12 +2,12 @@ * Twitter Authentication Module * Handles token capture and storage for Twitter API access */ -import { STORAGE_KEYS } from "./constants" +import { STORAGE_KEYS } from "./constants"; export interface TwitterAuthTokens { - cookie: string - csrf: string - auth: string + cookie: string; + csrf: string; + auth: string; } /** @@ -17,41 +17,41 @@ export interface TwitterAuthTokens { */ export function captureTwitterTokens( details: chrome.webRequest.WebRequestDetails & { - requestHeaders?: chrome.webRequest.HttpHeader[] + requestHeaders?: chrome.webRequest.HttpHeader[]; }, ): boolean { if (!(details.url.includes("x.com") || details.url.includes("twitter.com"))) { - return false + return false; } const authHeader = details.requestHeaders?.find( (header) => header.name.toLowerCase() === "authorization", - ) + ); const cookieHeader = details.requestHeaders?.find( (header) => header.name.toLowerCase() === "cookie", - ) + ); const csrfHeader = details.requestHeaders?.find( (header) => header.name.toLowerCase() === "x-csrf-token", - ) + ); if (authHeader?.value && cookieHeader?.value && csrfHeader?.value) { chrome.storage.session.get([STORAGE_KEYS.TOKENS_LOGGED], (result) => { if (!result[STORAGE_KEYS.TOKENS_LOGGED]) { - console.log("Twitter auth tokens captured successfully") - chrome.storage.session.set({ [STORAGE_KEYS.TOKENS_LOGGED]: true }) + console.log("Twitter auth tokens captured successfully"); + chrome.storage.session.set({ [STORAGE_KEYS.TOKENS_LOGGED]: true }); } - }) + }); chrome.storage.session.set({ [STORAGE_KEYS.TWITTER_COOKIE]: cookieHeader.value, [STORAGE_KEYS.TWITTER_CSRF]: csrfHeader.value, [STORAGE_KEYS.TWITTER_AUTH_TOKEN]: authHeader.value, - }) + }); - return true + return true; } - return false + return false; } /** @@ -63,21 +63,21 @@ export async function getTwitterTokens(): Promise<TwitterAuthTokens | null> { STORAGE_KEYS.TWITTER_COOKIE, STORAGE_KEYS.TWITTER_CSRF, STORAGE_KEYS.TWITTER_AUTH_TOKEN, - ]) + ]); if ( !result[STORAGE_KEYS.TWITTER_COOKIE] || !result[STORAGE_KEYS.TWITTER_CSRF] || !result[STORAGE_KEYS.TWITTER_AUTH_TOKEN] ) { - return null + return null; } return { cookie: result[STORAGE_KEYS.TWITTER_COOKIE], csrf: result[STORAGE_KEYS.TWITTER_CSRF], auth: result[STORAGE_KEYS.TWITTER_AUTH_TOKEN], - } + }; } /** @@ -86,16 +86,16 @@ export async function getTwitterTokens(): Promise<TwitterAuthTokens | null> { * @returns Headers object ready for fetch requests */ export function createTwitterAPIHeaders(tokens: TwitterAuthTokens): Headers { - const headers = new Headers() - headers.append("Cookie", tokens.cookie) - headers.append("X-Csrf-Token", tokens.csrf) - headers.append("Authorization", tokens.auth) - headers.append("Content-Type", "application/json") + const headers = new Headers(); + headers.append("Cookie", tokens.cookie); + headers.append("X-Csrf-Token", tokens.csrf); + headers.append("Authorization", tokens.auth); + headers.append("Content-Type", "application/json"); headers.append( "User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", - ) - headers.append("Accept", "*/*") - headers.append("Accept-Language", "en-US,en;q=0.9") - return headers + ); + headers.append("Accept", "*/*"); + headers.append("Accept-Language", "en-US,en;q=0.9"); + return headers; } diff --git a/apps/browser-extension/utils/twitter-import.ts b/apps/browser-extension/utils/twitter-import.ts index e68d6dbf..bfd3a046 100644 --- a/apps/browser-extension/utils/twitter-import.ts +++ b/apps/browser-extension/utils/twitter-import.ts @@ -3,45 +3,45 @@ * Handles the import process for Twitter bookmarks */ -import { saveAllTweets } from "./api" -import { createTwitterAPIHeaders, getTwitterTokens } from "./twitter-auth" +import { saveAllTweets } from "./api"; +import { createTwitterAPIHeaders, getTwitterTokens } from "./twitter-auth"; import { BOOKMARKS_URL, buildRequestVariables, extractNextCursor, getAllTweets, type TwitterAPIResponse, -} from "./twitter-utils" +} from "./twitter-utils"; -export type ImportProgressCallback = (message: string) => Promise<void> +export type ImportProgressCallback = (message: string) => Promise<void>; -export type ImportCompleteCallback = (totalImported: number) => Promise<void> +export type ImportCompleteCallback = (totalImported: number) => Promise<void>; export interface TwitterImportConfig { - onProgress: ImportProgressCallback - onComplete: ImportCompleteCallback - onError: (error: Error) => Promise<void> + onProgress: ImportProgressCallback; + onComplete: ImportCompleteCallback; + onError: (error: Error) => Promise<void>; } /** * Rate limiting configuration */ class RateLimiter { - private waitTime = 60000 // Start with 1 minute + private waitTime = 60000; // Start with 1 minute async handleRateLimit(onProgress: ImportProgressCallback): Promise<void> { - const waitTimeInSeconds = this.waitTime / 1000 + const waitTimeInSeconds = this.waitTime / 1000; await onProgress( `Rate limit reached. Waiting for ${waitTimeInSeconds} seconds before retrying...`, - ) + ); - await new Promise((resolve) => setTimeout(resolve, this.waitTime)) - this.waitTime *= 2 // Exponential backoff + await new Promise((resolve) => setTimeout(resolve, this.waitTime)); + this.waitTime *= 2; // Exponential backoff } reset(): void { - this.waitTime = 60000 + this.waitTime = 60000; } } @@ -49,8 +49,8 @@ class RateLimiter { * Main class for handling Twitter bookmarks import */ export class TwitterImporter { - private importInProgress = false - private rateLimiter = new RateLimiter() + private importInProgress = false; + private rateLimiter = new RateLimiter(); constructor(private config: TwitterImportConfig) {} @@ -60,19 +60,19 @@ export class TwitterImporter { */ async startImport(): Promise<void> { if (this.importInProgress) { - throw new Error("Import already in progress") + throw new Error("Import already in progress"); } - this.importInProgress = true - const uniqueGroupId = crypto.randomUUID() + this.importInProgress = true; + const uniqueGroupId = crypto.randomUUID(); try { - await this.batchImportAll("", 0, uniqueGroupId) - this.rateLimiter.reset() + await this.batchImportAll("", 0, uniqueGroupId); + this.rateLimiter.reset(); } catch (error) { - await this.config.onError(error as Error) + await this.config.onError(error as Error); } finally { - this.importInProgress = false + this.importInProgress = false; } } @@ -81,52 +81,56 @@ export class TwitterImporter { * @param cursor - Pagination cursor for Twitter API * @param totalImported - Number of tweets imported so far */ - private async batchImportAll(cursor = "", totalImported = 0, uniqueGroupId = "twitter_bookmarks"): Promise<void> { + private async batchImportAll( + cursor = "", + totalImported = 0, + uniqueGroupId = "twitter_bookmarks", + ): Promise<void> { try { // Use a local variable to track imported count - let importedCount = totalImported + let importedCount = totalImported; // Get authentication tokens - const tokens = await getTwitterTokens() + const tokens = await getTwitterTokens(); if (!tokens) { await this.config.onProgress( "Please visit Twitter/X first to capture authentication tokens", - ) - return + ); + return; } // Create headers for API request - const headers = createTwitterAPIHeaders(tokens) + const headers = createTwitterAPIHeaders(tokens); // Build API request with pagination - const variables = buildRequestVariables(cursor) + const variables = buildRequestVariables(cursor); const urlWithCursor = cursor ? `${BOOKMARKS_URL}&variables=${encodeURIComponent(JSON.stringify(variables))}` - : BOOKMARKS_URL + : BOOKMARKS_URL; const response = await fetch(urlWithCursor, { method: "GET", headers, redirect: "follow", - }) + }); if (!response.ok) { - const errorText = await response.text() - console.error(`Twitter API Error ${response.status}:`, errorText) + const errorText = await response.text(); + console.error(`Twitter API Error ${response.status}:`, errorText); if (response.status === 429) { - await this.rateLimiter.handleRateLimit(this.config.onProgress) - return this.batchImportAll(cursor, totalImported, uniqueGroupId) + await this.rateLimiter.handleRateLimit(this.config.onProgress); + return this.batchImportAll(cursor, totalImported, uniqueGroupId); } throw new Error( `Failed to fetch data: ${response.status} - ${errorText}`, - ) + ); } - const data: TwitterAPIResponse = await response.json() - const tweets = getAllTweets(data) + const data: TwitterAPIResponse = await response.json(); + const tweets = getAllTweets(data); - const documents: MemoryPayload[] = [] + const documents: MemoryPayload[] = []; // Convert tweets to MemoryPayload for (const tweet of tweets) { @@ -139,49 +143,51 @@ export class TwitterImporter { likes: tweet.favorite_count, retweets: tweet.retweet_count || 0, sm_internal_group_id: uniqueGroupId, - } + }; documents.push({ containerTags: ["sm_project_twitter_bookmarks"], content: `https://x.com/${tweet.user.screen_name}/status/${tweet.id_str}`, metadata, customId: tweet.id_str, - }) - importedCount++ - await this.config.onProgress(`Imported ${importedCount} tweets, so far...`) + }); + importedCount++; + await this.config.onProgress( + `Imported ${importedCount} tweets, so far...`, + ); } catch (error) { - console.error("Error importing tweet:", error) + console.error("Error importing tweet:", error); } } try { if (documents.length > 0) { - await saveAllTweets(documents) + await saveAllTweets(documents); } - console.log("Tweets saved") - console.log("Documents:", documents) + console.log("Tweets saved"); + console.log("Documents:", documents); } catch (error) { - console.error("Error saving tweets batch:", error) - await this.config.onError(error as Error) - return + console.error("Error saving tweets batch:", error); + await this.config.onError(error as Error); + return; } // Handle pagination const instructions = - data.data?.bookmark_timeline_v2?.timeline?.instructions - const nextCursor = extractNextCursor(instructions || []) + data.data?.bookmark_timeline_v2?.timeline?.instructions; + const nextCursor = extractNextCursor(instructions || []); - console.log("Next cursor:", nextCursor) - console.log("Tweets length:", tweets.length) + console.log("Next cursor:", nextCursor); + console.log("Tweets length:", tweets.length); if (nextCursor && tweets.length > 0) { - await new Promise((resolve) => setTimeout(resolve, 1000)) // Rate limiting - await this.batchImportAll(nextCursor, importedCount, uniqueGroupId) + await new Promise((resolve) => setTimeout(resolve, 1000)); // Rate limiting + await this.batchImportAll(nextCursor, importedCount, uniqueGroupId); } else { - await this.config.onComplete(importedCount) + await this.config.onComplete(importedCount); } } catch (error) { - console.error("Batch import error:", error) - await this.config.onError(error as Error) + console.error("Batch import error:", error); + await this.config.onError(error as Error); } } } diff --git a/apps/browser-extension/utils/twitter-utils.ts b/apps/browser-extension/utils/twitter-utils.ts index 7a7b86db..89d774bd 100644 --- a/apps/browser-extension/utils/twitter-utils.ts +++ b/apps/browser-extension/utils/twitter-utils.ts @@ -1,120 +1,120 @@ // Twitter API data structures and transformation utilities interface TwitterAPITweet { - __typename?: string + __typename?: string; legacy: { - lang?: string - favorite_count: number - created_at: string - display_text_range?: [number, number] + lang?: string; + favorite_count: number; + created_at: string; + display_text_range?: [number, number]; entities?: { - hashtags?: Array<{ indices: [number, number]; text: string }> + hashtags?: Array<{ indices: [number, number]; text: string }>; urls?: Array<{ - display_url: string - expanded_url: string - indices: [number, number] - url: string - }> + display_url: string; + expanded_url: string; + indices: [number, number]; + url: string; + }>; user_mentions?: Array<{ - id_str: string - indices: [number, number] - name: string - screen_name: string - }> - symbols?: Array<{ indices: [number, number]; text: string }> - media?: MediaEntity[] - } - id_str: string - full_text: string - reply_count?: number - retweet_count?: number - quote_count?: number - } + id_str: string; + indices: [number, number]; + name: string; + screen_name: string; + }>; + symbols?: Array<{ indices: [number, number]; text: string }>; + media?: MediaEntity[]; + }; + id_str: string; + full_text: string; + reply_count?: number; + retweet_count?: number; + quote_count?: number; + }; core?: { user_results?: { result?: { legacy?: { - id_str: string - name: string - profile_image_url_https: string - screen_name: string - verified: boolean - } - is_blue_verified?: boolean - } - } - } + id_str: string; + name: string; + profile_image_url_https: string; + screen_name: string; + verified: boolean; + }; + is_blue_verified?: boolean; + }; + }; + }; } interface MediaEntity { - type: string - media_url_https: string + type: string; + media_url_https: string; sizes?: { large?: { - w: number - h: number - } - } + w: number; + h: number; + }; + }; video_info?: { variants?: Array<{ - url: string - }> - duration_millis?: number - } + url: string; + }>; + duration_millis?: number; + }; } export interface Tweet { - __typename?: string - lang?: string - favorite_count: number - created_at: string - display_text_range?: [number, number] + __typename?: string; + lang?: string; + favorite_count: number; + created_at: string; + display_text_range?: [number, number]; entities: { hashtags: Array<{ - indices: [number, number] - text: string - }> + indices: [number, number]; + text: string; + }>; urls?: Array<{ - display_url: string - expanded_url: string - indices: [number, number] - url: string - }> + display_url: string; + expanded_url: string; + indices: [number, number]; + url: string; + }>; user_mentions: Array<{ - id_str: string - indices: [number, number] - name: string - screen_name: string - }> + id_str: string; + indices: [number, number]; + name: string; + screen_name: string; + }>; symbols: Array<{ - indices: [number, number] - text: string - }> - } - id_str: string - text: string + indices: [number, number]; + text: string; + }>; + }; + id_str: string; + text: string; user: { - id_str: string - name: string - profile_image_url_https: string - screen_name: string - verified: boolean - is_blue_verified?: boolean - } - conversation_count: number + id_str: string; + name: string; + profile_image_url_https: string; + screen_name: string; + verified: boolean; + is_blue_verified?: boolean; + }; + conversation_count: number; photos?: Array<{ - url: string - width: number - height: number - }> + url: string; + width: number; + height: number; + }>; videos?: Array<{ - url: string - thumbnail_url: string - duration: number - }> - retweet_count?: number - quote_count?: number - reply_count?: number + url: string; + thumbnail_url: string; + duration: number; + }>; + retweet_count?: number; + quote_count?: number; + reply_count?: number; } export interface TwitterAPIResponse { @@ -122,16 +122,16 @@ export interface TwitterAPIResponse { bookmark_timeline_v2: { timeline: { instructions: Array<{ - type: string + type: string; entries?: Array<{ - entryId: string - sortIndex: string - content: Record<string, unknown> - }> - }> - } - } - } + entryId: string; + sortIndex: string; + content: Record<string, unknown>; + }>; + }>; + }; + }; + }; } // Twitter API features configuration @@ -165,9 +165,9 @@ export const TWITTER_API_FEATURES = { articles_preview_enabled: true, rweb_video_timestamps_enabled: true, verified_phone_label_enabled: true, -} +}; -export const BOOKMARKS_URL = `https://x.com/i/api/graphql/xLjCVTqYWz8CGSprLU349w/Bookmarks?features=${encodeURIComponent(JSON.stringify(TWITTER_API_FEATURES))}` +export const BOOKMARKS_URL = `https://x.com/i/api/graphql/xLjCVTqYWz8CGSprLU349w/Bookmarks?features=${encodeURIComponent(JSON.stringify(TWITTER_API_FEATURES))}`; /** * Transform raw Twitter API response data into standardized Tweet format @@ -177,29 +177,29 @@ export function transformTweetData( ): Tweet | null { try { const content = input.content as { - itemContent?: { tweet_results?: { result?: unknown } } - } - const tweetData = content?.itemContent?.tweet_results?.result + itemContent?: { tweet_results?: { result?: unknown } }; + }; + const tweetData = content?.itemContent?.tweet_results?.result; if (!tweetData) { - return null + return null; } - const tweet = tweetData as TwitterAPITweet + const tweet = tweetData as TwitterAPITweet; if (!tweet.legacy) { - return null + return null; } // Handle media entities - const media = (tweet.legacy.entities?.media as MediaEntity[]) || [] + const media = (tweet.legacy.entities?.media as MediaEntity[]) || []; const photos = media .filter((m) => m.type === "photo") .map((m) => ({ url: m.media_url_https, width: m.sizes?.large?.w || 0, height: m.sizes?.large?.h || 0, - })) + })); const videos = media .filter((m) => m.type === "video") @@ -207,7 +207,7 @@ export function transformTweetData( url: m.video_info?.variants?.[0]?.url || "", thumbnail_url: m.media_url_https, duration: m.video_info?.duration_millis || 0, - })) + })); const transformed: Tweet = { __typename: tweet.__typename, @@ -239,20 +239,20 @@ export function transformTweetData( retweet_count: tweet.legacy.retweet_count || 0, quote_count: tweet.legacy.quote_count || 0, reply_count: tweet.legacy.reply_count || 0, - } + }; if (photos.length > 0) { - transformed.photos = photos + transformed.photos = photos; } if (videos.length > 0) { - transformed.videos = videos + transformed.videos = videos; } - return transformed + return transformed; } catch (error) { - console.error("Error transforming tweet data:", error) - return null + console.error("Error transforming tweet data:", error); + return null; } } @@ -260,29 +260,29 @@ export function transformTweetData( * Extract all tweets from Twitter API response */ export function getAllTweets(data: TwitterAPIResponse): Tweet[] { - const tweets: Tweet[] = [] + const tweets: Tweet[] = []; try { const instructions = - data.data?.bookmark_timeline_v2?.timeline?.instructions || [] + data.data?.bookmark_timeline_v2?.timeline?.instructions || []; for (const instruction of instructions) { if (instruction.type === "TimelineAddEntries" && instruction.entries) { for (const entry of instruction.entries) { if (entry.entryId.startsWith("tweet-")) { - const tweet = transformTweetData(entry) + const tweet = transformTweetData(entry); if (tweet) { - tweets.push(tweet) + tweets.push(tweet); } } } } } } catch (error) { - console.error("Error extracting tweets:", error) + console.error("Error extracting tweets:", error); } - return tweets + return tweets; } /** @@ -295,69 +295,69 @@ export function extractNextCursor( for (const instruction of instructions) { if (instruction.type === "TimelineAddEntries" && instruction.entries) { const entries = instruction.entries as Array<{ - entryId: string - content?: { value?: string } - }> + entryId: string; + content?: { value?: string }; + }>; for (const entry of entries) { if (entry.entryId.startsWith("cursor-bottom-")) { - return entry.content?.value || null + return entry.content?.value || null; } } } } } catch (error) { - console.error("Error extracting cursor:", error) + console.error("Error extracting cursor:", error); } - return null + return null; } /** * Convert Tweet object to markdown format for storage */ export function tweetToMarkdown(tweet: Tweet): string { - const username = tweet.user?.screen_name || "unknown" - const displayName = tweet.user?.name || "Unknown User" - const date = new Date(tweet.created_at).toLocaleDateString() - const time = new Date(tweet.created_at).toLocaleTimeString() + const username = tweet.user?.screen_name || "unknown"; + const displayName = tweet.user?.name || "Unknown User"; + const date = new Date(tweet.created_at).toLocaleDateString(); + const time = new Date(tweet.created_at).toLocaleTimeString(); - let markdown = `# Tweet by @${username} (${displayName})\n\n` - markdown += `**Date:** ${date} ${time}\n` - markdown += `**Likes:** ${tweet.favorite_count} | **Retweets:** ${tweet.retweet_count || 0} | **Replies:** ${tweet.reply_count || 0}\n\n` + let markdown = `# Tweet by @${username} (${displayName})\n\n`; + markdown += `**Date:** ${date} ${time}\n`; + markdown += `**Likes:** ${tweet.favorite_count} | **Retweets:** ${tweet.retweet_count || 0} | **Replies:** ${tweet.reply_count || 0}\n\n`; // Add tweet text - markdown += `${tweet.text}\n\n` + markdown += `${tweet.text}\n\n`; // Add media if present if (tweet.photos && tweet.photos.length > 0) { - markdown += "**Images:**\n" + markdown += "**Images:**\n"; tweet.photos.forEach((photo, index) => { - markdown += `\n` - }) - markdown += "\n" + markdown += `\n`; + }); + markdown += "\n"; } if (tweet.videos && tweet.videos.length > 0) { - markdown += "**Videos:**\n" + markdown += "**Videos:**\n"; tweet.videos.forEach((video, index) => { - markdown += `[Video ${index + 1}](${video.url})\n` - }) - markdown += "\n" + markdown += `[Video ${index + 1}](${video.url})\n`; + }); + markdown += "\n"; } // Add hashtags and mentions if (tweet.entities.hashtags.length > 0) { - markdown += `**Hashtags:** ${tweet.entities.hashtags.map((h) => `#${h.text}`).join(", ")}\n` + markdown += `**Hashtags:** ${tweet.entities.hashtags.map((h) => `#${h.text}`).join(", ")}\n`; } if (tweet.entities.user_mentions.length > 0) { - markdown += `**Mentions:** ${tweet.entities.user_mentions.map((m) => `@${m.screen_name}`).join(", ")}\n` + markdown += `**Mentions:** ${tweet.entities.user_mentions.map((m) => `@${m.screen_name}`).join(", ")}\n`; } // Add raw data for reference - markdown += `\n---\n<details>\n<summary>Raw Tweet Data</summary>\n\n\`\`\`json\n${JSON.stringify(tweet, null, 2)}\n\`\`\`\n</details>` + markdown += `\n---\n<details>\n<summary>Raw Tweet Data</summary>\n\n\`\`\`json\n${JSON.stringify(tweet, null, 2)}\n\`\`\`\n</details>`; - return markdown + return markdown; } /** @@ -367,11 +367,11 @@ export function buildRequestVariables(cursor?: string, count = 100) { const variables = { count, includePromotedContent: false, - } + }; if (cursor) { - ;(variables as Record<string, unknown>).cursor = cursor + (variables as Record<string, unknown>).cursor = cursor; } - return variables + return variables; } diff --git a/apps/browser-extension/utils/types.ts b/apps/browser-extension/utils/types.ts index 06a4ae72..d5049e19 100644 --- a/apps/browser-extension/utils/types.ts +++ b/apps/browser-extension/utils/types.ts @@ -5,102 +5,102 @@ /** * Toast states for UI feedback */ -export type ToastState = "loading" | "success" | "error" +export type ToastState = "loading" | "success" | "error"; /** * Message types for extension communication */ export interface ExtensionMessage { - action?: string - type?: string - data?: unknown - state?: ToastState - importedMessage?: string - totalImported?: number - actionSource?: string + action?: string; + type?: string; + data?: unknown; + state?: ToastState; + importedMessage?: string; + totalImported?: number; + actionSource?: string; } /** * Memory data structure for saving content */ export interface MemoryData { - html?: string - content?: string - highlightedText?: string - url?: string + html?: string; + content?: string; + highlightedText?: string; + url?: string; } /** * Supermemory API payload for storing memories */ export interface MemoryPayload { - containerTags?: string[] - content: string + containerTags?: string[]; + content: string; metadata: { - sm_source: string - [key: string]: unknown - } - customId?: string + sm_source: string; + [key: string]: unknown; + }; + customId?: string; } /** * Twitter-specific memory metadata */ export interface TwitterMemoryMetadata { - sm_source: "twitter_bookmarks" - tweet_id: string - author: string - created_at: string - likes: number - retweets: number + sm_source: "twitter_bookmarks"; + tweet_id: string; + author: string; + created_at: string; + likes: number; + retweets: number; } /** * Storage data structure for Chrome storage */ export interface StorageData { - bearerToken?: string + bearerToken?: string; twitterAuth?: { - cookie: string - csrf: string - auth: string - } - tokens_logged?: boolean - cookie?: string - csrf?: string - auth?: string - defaultProject?: Project + cookie: string; + csrf: string; + auth: string; + }; + tokens_logged?: boolean; + cookie?: string; + csrf?: string; + auth?: string; + defaultProject?: Project; projectsCache?: { - projects: Project[] - timestamp: number - } + projects: Project[]; + timestamp: number; + }; } /** * Context menu click info */ export interface ContextMenuClickInfo { - menuItemId: string | number - editable?: boolean - frameId?: number - frameUrl?: string - linkUrl?: string - mediaType?: string - pageUrl?: string - parentMenuItemId?: string | number - selectionText?: string - srcUrl?: string - targetElementId?: number - wasChecked?: boolean + menuItemId: string | number; + editable?: boolean; + frameId?: number; + frameUrl?: string; + linkUrl?: string; + mediaType?: string; + pageUrl?: string; + parentMenuItemId?: string | number; + selectionText?: string; + srcUrl?: string; + targetElementId?: number; + wasChecked?: boolean; } /** * API Response types */ export interface APIResponse<T = unknown> { - success: boolean - data?: T - error?: string + success: boolean; + data?: T; + error?: string; } /** @@ -112,41 +112,41 @@ export class ExtensionError extends Error { public code?: string, public statusCode?: number, ) { - super(message) - this.name = "ExtensionError" + super(message); + this.name = "ExtensionError"; } } export class TwitterAPIError extends ExtensionError { constructor(message: string, statusCode?: number) { - super(message, "TWITTER_API_ERROR", statusCode) - this.name = "TwitterAPIError" + super(message, "TWITTER_API_ERROR", statusCode); + this.name = "TwitterAPIError"; } } export class SupermemoryAPIError extends ExtensionError { constructor(message: string, statusCode?: number) { - super(message, "SUPERMEMORY_API_ERROR", statusCode) - this.name = "SupermemoryAPIError" + super(message, "SUPERMEMORY_API_ERROR", statusCode); + this.name = "SupermemoryAPIError"; } } export class AuthenticationError extends ExtensionError { constructor(message = "Authentication required") { - super(message, "AUTH_ERROR") - this.name = "AuthenticationError" + super(message, "AUTH_ERROR"); + this.name = "AuthenticationError"; } } export interface Project { - id: string - name: string - containerTag: string - createdAt: string - updatedAt: string - documentCount: number + id: string; + name: string; + containerTag: string; + createdAt: string; + updatedAt: string; + documentCount: number; } export interface ProjectsResponse { - projects: Project[] + projects: Project[]; } diff --git a/apps/browser-extension/utils/ui-components.ts b/apps/browser-extension/utils/ui-components.ts index 8a56ea5a..ed14faff 100644 --- a/apps/browser-extension/utils/ui-components.ts +++ b/apps/browser-extension/utils/ui-components.ts @@ -3,8 +3,8 @@ * Reusable UI components for the browser extension */ -import { ELEMENT_IDS, UI_CONFIG } from "./constants" -import type { ToastState } from "./types" +import { ELEMENT_IDS, UI_CONFIG } from "./constants"; +import type { ToastState } from "./types"; /** * Creates a toast notification element @@ -12,8 +12,8 @@ import type { ToastState } from "./types" * @returns HTMLElement - The toast element */ export function createToast(state: ToastState): HTMLElement { - const toast = document.createElement("div") - toast.id = ELEMENT_IDS.SUPERMEMORY_TOAST + const toast = document.createElement("div"); + toast.id = ELEMENT_IDS.SUPERMEMORY_TOAST; toast.style.cssText = ` position: fixed; @@ -33,12 +33,12 @@ export function createToast(state: ToastState): HTMLElement { max-width: 300px; animation: slideIn 0.3s ease-out; box-shadow: 0 4px 24px 0 rgba(0,0,0,0.18), 0 1.5px 6px 0 rgba(0,0,0,0.12); - ` + `; // Add keyframe animations and fonts if not already present if (!document.getElementById("supermemory-toast-styles")) { - const style = document.createElement("style") - style.id = "supermemory-toast-styles" + const style = document.createElement("style"); + style.id = "supermemory-toast-styles"; style.textContent = ` @font-face { font-family: 'Space Grotesk'; @@ -87,15 +87,15 @@ export function createToast(state: ToastState): HTMLElement { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } - ` - document.head.appendChild(style) + `; + document.head.appendChild(style); } - const icon = document.createElement("div") - icon.style.cssText = "width: 20px; height: 20px; flex-shrink: 0;" + const icon = document.createElement("div"); + icon.style.cssText = "width: 20px; height: 20px; flex-shrink: 0;"; - const text = document.createElement("span") - text.style.fontWeight = "500" + const text = document.createElement("span"); + text.style.fontWeight = "500"; // Configure toast based on state switch (state) { @@ -111,16 +111,16 @@ export function createToast(state: ToastState): HTMLElement { <path d="M20.49 15.49L18.36 17.62" stroke="#6366f1" stroke-width="2" stroke-linecap="round" opacity="0.9"/> <path d="M5.64 6.36L3.51 8.49" stroke="#6366f1" stroke-width="2" stroke-linecap="round" opacity="0.6"/> </svg> - ` - icon.style.animation = "spin 1s linear infinite" - text.textContent = "Adding to Memory..." - break + `; + icon.style.animation = "spin 1s linear infinite"; + text.textContent = "Adding to Memory..."; + break; case "success": { - const iconUrl = browser.runtime.getURL("/icon-16.png") - icon.innerHTML = `<img src="${iconUrl}" width="20" height="20" alt="Success" style="border-radius: 2px;" />` - text.textContent = "Added to Memory" - break + const iconUrl = browser.runtime.getURL("/icon-16.png"); + icon.innerHTML = `<img src="${iconUrl}" width="20" height="20" alt="Success" style="border-radius: 2px;" />`; + text.textContent = "Added to Memory"; + break; } case "error": @@ -130,15 +130,15 @@ export function createToast(state: ToastState): HTMLElement { <path d="M15 9L9 15" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M9 9L15 15" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> - ` - text.textContent = "Failed to save memory / Make sure you are logged in" - break + `; + text.textContent = "Failed to save memory / Make sure you are logged in"; + break; } - toast.appendChild(icon) - toast.appendChild(text) + toast.appendChild(icon); + toast.appendChild(text); - return toast + return toast; } /** @@ -147,8 +147,8 @@ export function createToast(state: ToastState): HTMLElement { * @returns HTMLElement - The button element */ export function createTwitterImportButton(onClick: () => void): HTMLElement { - const button = document.createElement("div") - button.id = ELEMENT_IDS.TWITTER_IMPORT_BUTTON + const button = document.createElement("div"); + button.id = ELEMENT_IDS.TWITTER_IMPORT_BUTTON; button.style.cssText = ` position: fixed; top: 10px; @@ -164,27 +164,27 @@ export function createTwitterImportButton(onClick: () => void): HTMLElement { align-items: center; gap: 8px; transition: all 0.2s ease; - ` + `; - const iconUrl = browser.runtime.getURL("/icon-16.png") + const iconUrl = browser.runtime.getURL("/icon-16.png"); button.innerHTML = ` <img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 4px;" /> <span style="font-weight: 500; font-size: 12px;">Import Bookmarks</span> - ` + `; button.addEventListener("mouseenter", () => { - button.style.opacity = "0.8" - button.style.boxShadow = "0 4px 12px rgba(29, 155, 240, 0.4)" - }) + button.style.opacity = "0.8"; + button.style.boxShadow = "0 4px 12px rgba(29, 155, 240, 0.4)"; + }); button.addEventListener("mouseleave", () => { - button.style.opacity = "1" - button.style.boxShadow = "0 2px 8px rgba(29, 155, 240, 0.3)" - }) + button.style.opacity = "1"; + button.style.boxShadow = "0 2px 8px rgba(29, 155, 240, 0.3)"; + }); - button.addEventListener("click", onClick) + button.addEventListener("click", onClick); - return button + return button; } /** @@ -193,7 +193,7 @@ export function createTwitterImportButton(onClick: () => void): HTMLElement { * @returns HTMLElement - The save button element */ export function createSaveTweetElement(onClick: () => void): HTMLElement { - const iconButton = document.createElement("div") + const iconButton = document.createElement("div"); iconButton.style.cssText = ` display: inline-flex; align-items: flex-end; @@ -206,29 +206,29 @@ export function createSaveTweetElement(onClick: () => void): HTMLElement { margin-right: 10px; margin-bottom: 2px; z-index: 1000; - ` + `; - const iconFileName = "/icon-16.png" - const iconUrl = browser.runtime.getURL(iconFileName) + const iconFileName = "/icon-16.png"; + const iconUrl = browser.runtime.getURL(iconFileName); iconButton.innerHTML = ` <img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 4px;" /> - ` + `; iconButton.addEventListener("mouseenter", () => { - iconButton.style.opacity = "1" - }) + iconButton.style.opacity = "1"; + }); iconButton.addEventListener("mouseleave", () => { - iconButton.style.opacity = "0.7" - }) + iconButton.style.opacity = "0.7"; + }); iconButton.addEventListener("click", (event) => { - event.stopPropagation() - event.preventDefault() - onClick() - }) + event.stopPropagation(); + event.preventDefault(); + onClick(); + }); - return iconButton + return iconButton; } /** @@ -237,7 +237,7 @@ export function createSaveTweetElement(onClick: () => void): HTMLElement { * @returns HTMLElement - The save button element */ export function createChatGPTInputBarElement(onClick: () => void): HTMLElement { - const iconButton = document.createElement("div") + const iconButton = document.createElement("div"); iconButton.style.cssText = ` display: inline-flex; align-items: center; @@ -247,30 +247,30 @@ export function createChatGPTInputBarElement(onClick: () => void): HTMLElement { cursor: pointer; transition: opacity 0.2s ease; border-radius: 50%; - ` + `; // Use appropriate icon based on theme - const iconFileName = "/icon-16.png" - const iconUrl = browser.runtime.getURL(iconFileName) + const iconFileName = "/icon-16.png"; + const iconUrl = browser.runtime.getURL(iconFileName); iconButton.innerHTML = ` <img src="${iconUrl}" width="20" height="20" alt="Save to Memory" style="border-radius: 50%;" /> - ` + `; iconButton.addEventListener("mouseenter", () => { - iconButton.style.opacity = "0.8" - }) + iconButton.style.opacity = "0.8"; + }); iconButton.addEventListener("mouseleave", () => { - iconButton.style.opacity = "1" - }) + iconButton.style.opacity = "1"; + }); iconButton.addEventListener("click", (event) => { - event.stopPropagation() - event.preventDefault() - onClick() - }) + event.stopPropagation(); + event.preventDefault(); + onClick(); + }); - return iconButton + return iconButton; } /** @@ -279,7 +279,7 @@ export function createChatGPTInputBarElement(onClick: () => void): HTMLElement { * @returns HTMLElement - The save button element */ export function createClaudeInputBarElement(onClick: () => void): HTMLElement { - const iconButton = document.createElement("div") + const iconButton = document.createElement("div"); iconButton.style.cssText = ` display: inline-flex; align-items: center; @@ -290,31 +290,31 @@ export function createClaudeInputBarElement(onClick: () => void): HTMLElement { transition: all 0.2s ease; border-radius: 6px; background: transparent; - ` + `; - const iconFileName = "/icon-16.png" - const iconUrl = browser.runtime.getURL(iconFileName) + const iconFileName = "/icon-16.png"; + const iconUrl = browser.runtime.getURL(iconFileName); iconButton.innerHTML = ` <img src="${iconUrl}" width="20" height="20" alt="Get Related Memories from supermemory" style="border-radius: 4px;" /> - ` + `; iconButton.addEventListener("mouseenter", () => { - iconButton.style.backgroundColor = "rgba(0, 0, 0, 0.05)" - iconButton.style.borderColor = "rgba(0, 0, 0, 0.2)" - }) + iconButton.style.backgroundColor = "rgba(0, 0, 0, 0.05)"; + iconButton.style.borderColor = "rgba(0, 0, 0, 0.2)"; + }); iconButton.addEventListener("mouseleave", () => { - iconButton.style.backgroundColor = "transparent" - iconButton.style.borderColor = "rgba(0, 0, 0, 0.1)" - }) + iconButton.style.backgroundColor = "transparent"; + iconButton.style.borderColor = "rgba(0, 0, 0, 0.1)"; + }); iconButton.addEventListener("click", (event) => { - event.stopPropagation() - event.preventDefault() - onClick() - }) + event.stopPropagation(); + event.preventDefault(); + onClick(); + }); - return iconButton + return iconButton; } /** @@ -323,7 +323,7 @@ export function createClaudeInputBarElement(onClick: () => void): HTMLElement { * @returns HTMLElement - The save button element */ export function createT3InputBarElement(onClick: () => void): HTMLElement { - const iconButton = document.createElement("div") + const iconButton = document.createElement("div"); iconButton.style.cssText = ` display: inline-flex; align-items: center; @@ -334,31 +334,31 @@ export function createT3InputBarElement(onClick: () => void): HTMLElement { transition: all 0.2s ease; border-radius: 6px; background: transparent; - ` + `; - const iconFileName = "/icon-16.png" - const iconUrl = browser.runtime.getURL(iconFileName) + const iconFileName = "/icon-16.png"; + const iconUrl = browser.runtime.getURL(iconFileName); iconButton.innerHTML = ` <img src="${iconUrl}" width="20" height="20" alt="Get Related Memories from supermemory" style="border-radius: 4px;" /> - ` + `; iconButton.addEventListener("mouseenter", () => { - iconButton.style.backgroundColor = "rgba(0, 0, 0, 0.05)" - iconButton.style.borderColor = "rgba(0, 0, 0, 0.2)" - }) + iconButton.style.backgroundColor = "rgba(0, 0, 0, 0.05)"; + iconButton.style.borderColor = "rgba(0, 0, 0, 0.2)"; + }); iconButton.addEventListener("mouseleave", () => { - iconButton.style.backgroundColor = "transparent" - iconButton.style.borderColor = "rgba(0, 0, 0, 0.1)" - }) + iconButton.style.backgroundColor = "transparent"; + iconButton.style.borderColor = "rgba(0, 0, 0, 0.1)"; + }); iconButton.addEventListener("click", (event) => { - event.stopPropagation() - event.preventDefault() - onClick() - }) + event.stopPropagation(); + event.preventDefault(); + onClick(); + }); - return iconButton + return iconButton; } /** @@ -371,7 +371,7 @@ export const DOMUtils = { * @returns boolean */ isOnDomain(domains: readonly string[]): boolean { - return domains.includes(window.location.hostname) + return domains.includes(window.location.hostname); }, /** @@ -379,9 +379,9 @@ export const DOMUtils = { * @returns boolean - true if dark mode, false if light mode */ isDarkMode(): boolean { - const htmlElement = document.documentElement - const style = htmlElement.getAttribute("style") - return style?.includes("color-scheme: dark") || false + const htmlElement = document.documentElement; + const style = htmlElement.getAttribute("style"); + return style?.includes("color-scheme: dark") || false; }, /** @@ -390,7 +390,7 @@ export const DOMUtils = { * @returns boolean */ elementExists(id: string): boolean { - return !!document.getElementById(id) + return !!document.getElementById(id); }, /** @@ -398,8 +398,8 @@ export const DOMUtils = { * @param id - Element ID to remove */ removeElement(id: string): void { - const element = document.getElementById(id) - element?.remove() + const element = document.getElementById(id); + element?.remove(); }, /** @@ -412,18 +412,20 @@ export const DOMUtils = { state: ToastState, duration: number = UI_CONFIG.TOAST_DURATION, ): HTMLElement { - const existingToast = document.getElementById(ELEMENT_IDS.SUPERMEMORY_TOAST) + const existingToast = document.getElementById( + ELEMENT_IDS.SUPERMEMORY_TOAST, + ); if ((state === "success" || state === "error") && existingToast) { - const icon = existingToast.querySelector("div") - const text = existingToast.querySelector("span") + const icon = existingToast.querySelector("div"); + const text = existingToast.querySelector("span"); if (icon && text) { if (state === "success") { - const iconUrl = browser.runtime.getURL("/icon-16.png") - icon.innerHTML = `<img src="${iconUrl}" width="20" height="20" alt="Success" style="border-radius: 2px;" />` - icon.style.animation = "" - text.textContent = "Added to Memory" + const iconUrl = browser.runtime.getURL("/icon-16.png"); + icon.innerHTML = `<img src="${iconUrl}" width="20" height="20" alt="Success" style="border-radius: 2px;" />`; + icon.style.animation = ""; + text.textContent = "Added to Memory"; } else if (state === "error") { icon.innerHTML = ` <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> @@ -431,52 +433,52 @@ export const DOMUtils = { <path d="M15 9L9 15" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M9 9L15 15" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg> - ` - icon.style.animation = "" + `; + icon.style.animation = ""; text.textContent = - "Failed to save memory / Make sure you are logged in" + "Failed to save memory / Make sure you are logged in"; } // Auto-dismiss setTimeout(() => { if (document.body.contains(existingToast)) { - existingToast.style.animation = "fadeOut 0.3s ease-out" + existingToast.style.animation = "fadeOut 0.3s ease-out"; setTimeout(() => { if (document.body.contains(existingToast)) { - existingToast.remove() + existingToast.remove(); } - }, 300) + }, 300); } - }, duration) + }, duration); - return existingToast + return existingToast; } } const existingToasts = document.querySelectorAll( `#${ELEMENT_IDS.SUPERMEMORY_TOAST}`, - ) + ); existingToasts.forEach((toast) => { - toast.remove() - }) + toast.remove(); + }); - const toast = createToast(state) - document.body.appendChild(toast) + const toast = createToast(state); + document.body.appendChild(toast); // Auto-dismiss for success and error states if (state === "success" || state === "error") { setTimeout(() => { if (document.body.contains(toast)) { - toast.style.animation = "fadeOut 0.3s ease-out" + toast.style.animation = "fadeOut 0.3s ease-out"; setTimeout(() => { if (document.body.contains(toast)) { - toast.remove() + toast.remove(); } - }, 300) + }, 300); } - }, duration) + }, duration); } - return toast + return toast; }, -} +}; diff --git a/apps/browser-extension/wxt.config.ts b/apps/browser-extension/wxt.config.ts index 1bd7910d..4a795fa1 100644 --- a/apps/browser-extension/wxt.config.ts +++ b/apps/browser-extension/wxt.config.ts @@ -1,5 +1,5 @@ -import tailwindcss from "@tailwindcss/vite" -import { defineConfig, type WxtViteConfig } from "wxt" +import tailwindcss from "@tailwindcss/vite"; +import { defineConfig, type WxtViteConfig } from "wxt"; // See https://wxt.dev/api/config.html export default defineConfig({ @@ -32,4 +32,4 @@ export default defineConfig({ webExt: { chromiumArgs: ["--user-data-dir=./.wxt/chrome-data"], }, -}) +}); |