import { useEffect, useState, useRef } from "react"; import { Readability } from "@mozilla/readability"; const DEBUG = true; const log = (message: string, ...args: any[]) => { if (DEBUG) { console.log("[content]", message, ...args); } }; export default function SupermemoryContent({ onClose: originalOnClose, }: { onClose: () => void; }) { const [progress, setProgress] = useState(0); const [showSaving, setShowSaving] = useState(true); const abortControllerRef = useRef(new AbortController()); const [toastMessage, setToastMessage] = useState<{ success: boolean; message: string; } | null>(null); const isAbortedRef = useRef(false); const [isImportingBookmarks, setIsImportingBookmarks] = useState(false); const [importProgress, setImportProgress] = useState(0); const [closeTimer, setCloseTimer] = useState(0); const closeTimerRef = useRef>(); const [importStatus, setImportStatus] = useState<{ status: number; message: string; } | null>(null); const [importComplete, setImportComplete] = useState(false); useEffect(() => { const isTwitter = window.location.hostname.match(/^(twitter\.com|x\.com)$/); if (isTwitter) { chrome.storage.local.get(["attemptingImportCurrently"], (result) => { if (result.attemptingImportCurrently) { setIsImportingBookmarks(true); setShowSaving(true); } }); } }, []); useEffect(() => { const messageHandler = ( message: any, sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void ) => { if (message.type === "IMPORT_PROGRESS_UPDATE") { setImportProgress(message.payload.progress); setShowSaving(true); switch (message.payload.status) { case 429: setImportStatus({ status: 429, message: "Rate limited by Twitter. Waiting to retry...", }); break; case 409: setImportStatus({ status: 409, message: "Some tweets were already saved", }); break; case 500: setImportStatus({ status: 500, message: "Error importing some tweets", }); break; case 102: setImportStatus({ status: 102, message: "Processing tweets...", }); break; default: setImportStatus(null); } } else if (message.type === "IMPORT_COMPLETE") { console.log("IMPORT_COMPLETE called"); setImportComplete(true); setImportStatus(null); setToastMessage({ success: true, message: "Bookmarks imported successfully", }); // Send message to all supermemory.ai tabs chrome.tabs.query({ url: "*://*.supermemory.ai/*" }, (tabs) => { tabs.forEach((tab) => { if (tab.id) { chrome.tabs.sendMessage(tab.id, { type: "TWITTER_IMPORT_COMPLETE", }); } }); }); // Start close timer after 2 seconds of showing success setTimeout(() => { if (!isImportingBookmarks) { startCloseTimer(); } }, 1000); } }; chrome.runtime.onMessage.addListener(messageHandler); return () => chrome.runtime.onMessage.removeListener(messageHandler); }, [isImportingBookmarks]); // Clear toast message after delay useEffect(() => { if (toastMessage && !importComplete) { const timer = setTimeout(() => { if (!isAbortedRef.current) { setToastMessage(null); } }, 3000); // Show toast for 3 seconds return () => clearTimeout(timer); } }, [toastMessage, importComplete]); const startCloseTimer = () => { if (closeTimerRef.current) { clearInterval(closeTimerRef.current); } setCloseTimer(0); let count = 0; closeTimerRef.current = setInterval(() => { if (!isAbortedRef.current) { count += 10; setCloseTimer(count); if (count >= 100) { if (closeTimerRef.current) { clearInterval(closeTimerRef.current); } handleClose(); } } }, 200); // 20 steps of 10% over 2 seconds }; const pauseCloseTimer = () => { if (closeTimerRef.current) { clearInterval(closeTimerRef.current); } }; const savePage = async () => { if (isAbortedRef.current) return; const documentClone = document.cloneNode(true) as Document; const mainContent = new Readability(documentClone).parse(); try { const response = await chrome.runtime.sendMessage({ type: "SAVE_PAGE", payload: { description: mainContent?.excerpt, url: window.location.href, prefetched: { contentToVectorize: mainContent?.textContent, contentToSave: mainContent?.content, title: mainContent?.title, type: "page", }, }, }); if (!isAbortedRef.current) { if (response.success) { setToastMessage({ success: true, message: "Page saved successfully", }); // Wait for toast to show before starting close timer setTimeout(() => { if (!isAbortedRef.current) { startCloseTimer(); } }, 1000); } else { setToastMessage({ success: false, message: response.status === 409 ? "Content already exists" : "Failed to save page", }); } } return response; } catch (error) { console.error("Error saving page:", error); setToastMessage({ success: false, message: "Failed to save page" }); } }; const handleClose = () => { isAbortedRef.current = true; abortControllerRef.current.abort(); if (closeTimerRef.current) { clearInterval(closeTimerRef.current); } setProgress(0); setShowSaving(false); originalOnClose(); }; useEffect(() => { if (!showSaving || isImportingBookmarks) return; savePage(); }, [showSaving]); if (!showSaving) return null; return ( <>
{isImportingBookmarks ? importComplete ? "Twitter Bookmarks Imported!" : "Importing Twitter Bookmarks" : "Saved to Supermemory"} {toastMessage && ( {toastMessage.message} )} {importStatus && ( {importStatus.message} )} {isImportingBookmarks && !importComplete && (
{importProgress} tweets processed
)} {importComplete && ( Successfully imported {importProgress} tweets )}
{!isImportingBookmarks && (progress > 0 || closeTimer > 0) && (
)}
); }