diff --git a/apps/browser-extension/entrypoints/content/twitter.ts b/apps/browser-extension/entrypoints/content/twitter.ts index bc487021..95ae2e60 100644 --- a/apps/browser-extension/entrypoints/content/twitter.ts +++ b/apps/browser-extension/entrypoints/content/twitter.ts @@ -132,59 +132,631 @@ export async function openImportModal() { action: MESSAGE_TYPES.FETCH_PROJECTS, }) - const projects = response.success && response.data ? response.data : xëOm¢G§²Úî�Æ­yÚYˆ -Ú[™ÝË›ØØ][Û‹œ]˜[YHOOH‹ÚKØ›ÛÚÛX\šÜÈŠH‚B\™]\›‚‚_B‚‚XÛÛœÝ\™Ù][[Y[�ÈHØÝ[Y[� œ]Y\žTÙ[XÝÜ�[ -‚BH‹˜ÜÜËLMÍ[ÚLœ‹œ‹L]ÝŒ\ œ‹LMžY\ÍKœ‹L[[XYLÛ‹œ‹[ÍÞ[œXËœ‹M� M™YËœ‹L[žM Û œ‹L[Ü] ŒH‹‚JB‚‚]\™Ù][[Y[�Ë™›Ü‘XXÚ + const projects = response.success && response.data ? response.data : [] -[[Y[� -HOˆ‚BXY�]Û•Ñ[[Y[� -[[Y[�\ÈS[[Y[� -B‚_JBŸB‚‹ÊŠ‚ˆ -ˆYÈ[ˆ[\Ü��]ÛˆÈH›ÛÚÛX\šÈ›Û\ˆ[[Y[�ˆ -‹Â™�[˜Ý[ÛˆY�]Û•Ñ[[Y[� -[[Y[�ˆS[[Y[� -H‚ZYˆ -[[Y[� œ]Y\žTÙ[XÝÜŠ–Ù]K\Ý\\›Y[[ÜžKX�]Û—HŠJH‚B\™]\›‚‚_B‚‚[ØYÜXÙQÜ›Ý\ÚÑ›Û�Ê -B‚‚XÛÛœÝ�]ÛˆHÜ™X]TØ]™UÙY][[Y[� -\Þ[˜È + if (projects.length === 0) { + const importResponse = await browser.runtime.sendMessage({ + type: MESSAGE_TYPES.BATCH_IMPORT_ALL, + }) + if (importResponse?.success) { + await trackEvent(POSTHOG_EVENT_KEY.TWITTER_IMPORT_STARTED, { + source: `${POSTHOG_EVENT_KEY.SOURCE}_content_script`, + }) + } + } else { + await showAllBookmarksProjectModal(projects) + } + } catch (error) { + console.error("Error opening import modal:", error) + await browser.runtime.sendMessage({ + type: MESSAGE_TYPES.BATCH_IMPORT_ALL, + }) + } +} -HOˆ‚BXÛÛœÝ\›H[[Y[� ™Ù]]šX�]Jš™YˆŠB‚BXÛۜݛÛÚÛX\šÐÛÛXÝ[Û’YH\›ËœÜ] -‹ÈŠKœÜ +async function showAllBookmarksProjectModal( + projects: Array<{ id: string; name: string; containerTag: string }>, +) { + await loadSpaceGroteskFonts() -B‚BZYˆ -›ÛÚÛX\šÐÛÛXÝ[Û’Y -H‚BBX]ØZ]ÚÝÑ›Û\”›Ú™XÝÙ[XÝ[Û“[Ù[ -›ÛÚÛX\šÐÛÛXÝ[Û’Y -B‚B_B‚_JB‚‚X�]Û‹œÙ]]šX�]J™]K\Ý\\›Y[[ÜžKX�]Ûˆ‹��YHŠB‚‚Y[[Y[� ˜\[™Ú[ -�]ÛŠB‚Y[[Y[� œÝ[K™›^\™XÝ[ÛˆHœ›ÝÈ‚‚Y[[Y[� œÝ[K˜[YÛ’][\ÈH˜Ù[�\ˆ‚‚Y[[Y[� œÝ[Kš�\ÝYžPÛÛ�[�H˜Ù[�\ˆ‚‚Y[[Y[� œÝ[K™Ø\HŒL‚‚Y[[Y[� œÝ[KœY[™ÈHŒL‚ŸB‚‹ÊŠ‚ˆ -ˆÚÝÜÈH›Ú™XÝÙ[XÝ[Ûˆ[Ù[›Üˆ›Û\ˆ[\Ü�ˆ -‹Â˜\Þ[˜È�[˜Ý[ÛˆÚÝÑ›Û\”›Ú™XÝÙ[XÝ[Û“[Ù[ -›ÛÚÛX\šÐÛÛXÝ[Û’YˆÝš[™ÊH‚X]ØZ]ØYÜXÙQÜ›Ý\ÚÑ›Û�Ê -B‚‚XÛÛœÝ[Ù[HÜ™X]T›Ú™XÝÙ[XÝ[Û“[Ù[ -‚BV×K‚BX\Þ[˜È -Ù[XÝY›Ú™XÝ -HOˆ‚BB[[Ù[ œ™[[Ý™J -B‚‚BB]žH‚BBBX]ØZ]œ›ÝÜÙ\‹œ�[�[YKœÙ[™Y\ÜØYÙJ‚BBBB]\NˆQTÔÐQÑWÕTTË��UÒÒSTÔ•ÐS ‚BBBBZ\Ñ›Û\’[\Ü�ˆ�YK‚BBBBX›ÛÚÛX\šÐÛÛXÝ[Û’Yˆ›ÛÚÛX\šÐÛÛXÝ[Û’Y ‚BBBB\Ù[XÝY›Ú™X݈Ù[XÝY›Ú™XÝ ‚BBB_JB‚BB_HØ]Ú -\œ›ÜŠH‚BBBXÛÛœÛÛK™\œ›ÜŠ‘\œ›Üˆ[\Ü�[™È›ÛÚÛX\šÜΈ‹\œ›ÜŠB‚BB_B‚B_K‚BJ -HOˆ‚BB[[Ù[ œ™[[Ý™J -B‚B_K‚JB‚‚YØÝ[Y[� ˜›ÙK˜\[™Ú[ -[Ù[ -B‚‚]žH‚BXÛۜݙ\ÜÛœÙHH]ØZ]œ›ÝÜÙ\‹œ�[�[YKœÙ[™Y\ÜØYÙJ‚BBXXÝ[ÛŽˆQTÔÐQÑWÕTTË‘‘UÒÔ“Ò‘PÕË‚B_JB‚‚BZYˆ -™\ÜÛœÙKœÝXØÙ\ÜÈ ‰ˆ™\ÜÛœÙK™]JH‚BBXÛۜݛڙXÝÈH™\ÜÛœÙK™]B‚BB]\]S[Ù[Ú]›Ú™XÝÊ[Ù[ ›Ú™XÝÊB‚B_H[ÙH‚BBXÛÛœÛÛK™\œ›ÜŠ‘˜Z[YÈ™]Ú›Ú™XÝΈ‹™\ÜÛœÙK™\œ›ÜŠB‚BB]\]S[Ù[Ú]›Ú™XÝÊ[Ù[ ×JB‚B_B‚_HØ]Ú -\œ›ÜŠH‚BXÛÛœÛÛK™\œ›ÜŠ‘\œ›Üˆ™]Ú[™È›Ú™XÝΈ‹\œ›ÜŠB‚B]\]S[Ù[Ú]›Ú™XÝÊ[Ù[ ×JB‚_BŸB‚‹ÊŠ‚ˆ -ˆ\]\ÈH[Ù[Ú]™]ÚY›Ú™X݈ -‹Â™�[˜Ý[Ûˆ\]S[Ù[Ú]›Ú™XÝÊ‚[[Ù[ˆS[[Y[� ‚\›Ú™XÝΈ\œ˜^OÈYˆÝš[™ÎȘ[YNˆÝš[™ÎÈÛÛ�Z[™\•YΈÝš[™ÈO‹ŠH‚XÛÛœÝÙ[XÝH[Ù[ œ]Y\žTÙ[XÝÜŠˆÜ›Ú™XÝ \Ù[XÝŠH\ÈSÙ[XÝ[[Y[�‚ZYˆ -\Ù[XÝ -H™]\›‚‚‚]Ú[H -Ù[XÝ ˜Ú[™[‹›[™Ýˆ JH‚B\Ù[XÝ œ™[[Ý™PÚ[ -Ù[XÝ ˜Ú[™[–ÌWJB‚_B‚‚ZYˆ -›Ú™XÝË›[™ÝOOH -H‚BXÛۜݛԛڙXÝÓÜ[ÛˆHØÝ[Y[� ˜Ü™X]Q[[Y[� -›Ü[ÛˆŠB‚B[›Ô›Ú™XÝÓÜ[Û‹�˜[YHHˆ‚‚B[›Ô›Ú™XÝÓÜ[Û‹�^ÛÛ�[�H“›È›Ú™XÝÈ]˜Z[X›H‚‚B[›Ô›Ú™XÝÓÜ[Û‹™\ØX›YH�YB‚B\Ù[XÝ ˜\[™Ú[ -›Ô›Ú™XÝÓÜ[ÛŠB‚‚BXÛÛœÝ[\Ü��]ÛˆH[Ù[ œ]Y\žTÙ[XÝÜŠ‚BBH˜�]ÛŽ›\Ý XÚ[‹‚BJH\ÈS�]Û‘[[Y[�‚BZYˆ -[\Ü��]ÛŠH‚BBZ[\Ü��]Û‹™\ØX›YH�YB‚BBZ[\Ü��]Û‹œÝ[K˜ÜÜÕ^H‚BBB\Y[™Îˆ L Mœ‚BBBX›Ü™\Žˆ \ÛÛY™Ø˜J �MK �MK �MK ŒJN‚BBBX›Ü™\‹\˜Y]\Έ Lœ‚BBBX˜XÚÙÜ›Ý[™ˆ™Ø˜J �MK �MK �MK Œ JN‚BBBXÛÛÜŽˆ™Ø˜J �MK �MK �MK ŒÊN‚BBBY›Û� \Ú^™Nˆ M‚BBBY›Û� ]ÙZYÚˆ L ‚BBBXÝ\œÛÜŽˆ›Ý X[ÝÙY‚BBB]˜[œÚ][ÛŽˆ[ ŒœÈX\ÙN‚BBX‚B_B‚_H[ÙH‚B\›Ú™XÝË™›Ü‘XXÚ + const modal = createProjectSelectionModal( + projects, + async (selectedProject) => { + modal.remove() -›Ú™XÝ -HOˆ‚BBXÛÛœÝÜ[ÛˆHØÝ[Y[� ˜Ü™X]Q[[Y[� -›Ü[ÛˆŠB‚BB[Ü[Û‹�˜[YHH›Ú™XÝ šY‚BB[Ü[Û‹�^ÛÛ�[�H›Ú™XÝ ›˜[YB‚BB[Ü[Û‹™]\Ù] ˜ÛÛ�Z[™\•YÈH›Ú™XÝ ˜ÛÛ�Z[™\•Y‚BB\Ù[XÝ ˜\[™Ú[ -Ü[ÛŠB‚B_JB‚_BŸB \ No newline at end of file + try { + const importResponse = await browser.runtime.sendMessage({ + type: MESSAGE_TYPES.BATCH_IMPORT_ALL, + selectedProject: selectedProject, + }) + if (importResponse?.success) { + await trackEvent(POSTHOG_EVENT_KEY.TWITTER_IMPORT_STARTED, { + source: `${POSTHOG_EVENT_KEY.SOURCE}_content_script`, + project_selected: true, + }) + } + } catch (error) { + console.error("Error importing all bookmarks:", error) + } + }, + () => { + modal.remove() + }, + ) + + document.body.appendChild(modal) +} + +/** + * Shows the one-time onboarding toast with progress bar + */ +async function showOnboardingToast() { + await loadSpaceGroteskFonts() + + // Remove any existing toast + const existingToast = document.getElementById( + ELEMENT_IDS.TWITTER_ONBOARDING_TOAST, + ) + if (existingToast) { + existingToast.remove() + } + + const duration = UI_CONFIG.ONBOARDING_TOAST_DURATION + + // Create toast container + const toast = document.createElement("div") + toast.id = ELEMENT_IDS.TWITTER_ONBOARDING_TOAST + toast.style.cssText = ` + position: fixed; + bottom: 20px; + right: 20px; + z-index: 2147483647; + background: #ffffff; + border-radius: 12px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; + font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-size: 14px; + color: #374151; + min-width: 320px; + max-width: 380px; + box-shadow: 0 4px 24px 0 rgba(0,0,0,0.18), 0 1.5px 6px 0 rgba(0,0,0,0.12); + animation: smSlideInUp 0.3s ease-out; + overflow: hidden; + ` + + // Add keyframe animations if not already present + if (!document.getElementById("supermemory-onboarding-toast-styles")) { + const style = document.createElement("style") + style.id = "supermemory-onboarding-toast-styles" + style.textContent = ` + @keyframes smSlideInUp { + from { transform: translateY(100%); opacity: 0; } + to { transform: translateY(0); opacity: 1; } + } + @keyframes smFadeOut { + from { transform: translateY(0); opacity: 1; } + to { transform: translateY(100%); opacity: 0; } + } + @keyframes smProgressGrow { + from { transform: scaleX(0); } + to { transform: scaleX(1); } + } + @keyframes smPulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } + } + ` + document.head.appendChild(style) + } + + // Header with icon, text and close button + const header = document.createElement("div") + header.style.cssText = + "display: flex; align-items: flex-start; gap: 12px; position: relative;" + + const iconUrl = browser.runtime.getURL("/new_logo.png") + const icon = document.createElement("img") + icon.src = iconUrl + icon.alt = "Supermemory" + icon.style.cssText = + "width: 24px; height: 24px; border-radius: 4px; flex-shrink: 0; margin-top: 2px;" + + const textContainer = document.createElement("div") + textContainer.style.cssText = + "display: flex; flex-direction: column; gap: 4px; flex: 1;" + + const title = document.createElement("span") + title.style.cssText = "font-weight: 600; font-size: 14px; color: #111827;" + title.textContent = "Import X/Twitter Bookmarks" + + const description = document.createElement("span") + description.style.cssText = + "font-size: 13px; color: #6b7280; line-height: 1.4;" + description.textContent = + "You can import all your Twitter bookmarks to Supermemory with one click." + + textContainer.appendChild(title) + textContainer.appendChild(description) + + // Close button + const closeButton = document.createElement("button") + closeButton.setAttribute("aria-label", "Close onboarding toast") + closeButton.style.cssText = ` + position: absolute; + top: 0; + right: 0; + background: transparent; + border: none; + cursor: pointer; + padding: 4px; + color: #9ca3af; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background-color 0.2s; + ` + closeButton.innerHTML = ` + + ` + closeButton.addEventListener("mouseenter", () => { + closeButton.style.backgroundColor = "#f3f4f6" + }) + closeButton.addEventListener("mouseleave", () => { + closeButton.style.backgroundColor = "transparent" + }) + closeButton.addEventListener("click", () => { + dismissToast(toast) + }) + + header.appendChild(icon) + header.appendChild(textContainer) + header.appendChild(closeButton) + + // Action buttons + const buttonsContainer = document.createElement("div") + buttonsContainer.style.cssText = "display: flex; gap: 8px; margin-top: 4px;" + + const importButton = document.createElement("button") + importButton.style.cssText = ` + padding: 8px 16px; + border: none; + border-radius: 8px; + background: linear-gradient(182.37deg, #0ff0d2 -91.53%, #5bd3fb -67.8%, #1e0ff0 95.17%); + color: white; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: opacity 0.2s; + font-family: inherit; + ` + importButton.textContent = "Import now" + importButton.addEventListener("mouseenter", () => { + importButton.style.opacity = "0.9" + }) + importButton.addEventListener("mouseleave", () => { + importButton.style.opacity = "1" + }) + importButton.addEventListener("click", async () => { + dismissToast(toast) + await openImportModal() + }) + + const learnMoreButton = document.createElement("button") + learnMoreButton.style.cssText = ` + padding: 8px 16px; + border: 1px solid #e5e7eb; + border-radius: 8px; + background: transparent; + color: #374151; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s; + font-family: inherit; + ` + learnMoreButton.textContent = "Learn more" + learnMoreButton.addEventListener("mouseenter", () => { + learnMoreButton.style.backgroundColor = "#f9fafb" + }) + learnMoreButton.addEventListener("mouseleave", () => { + learnMoreButton.style.backgroundColor = "transparent" + }) + learnMoreButton.addEventListener("click", () => { + window.open("https://docs.supermemory.ai/connectors/twitter", "_blank") + }) + + buttonsContainer.appendChild(importButton) + buttonsContainer.appendChild(learnMoreButton) + + // Progress bar container + const progressBarContainer = document.createElement("div") + progressBarContainer.setAttribute("role", "progressbar") + progressBarContainer.setAttribute("aria-valuemin", "0") + progressBarContainer.setAttribute("aria-valuemax", "100") + progressBarContainer.setAttribute("aria-valuenow", "0") + progressBarContainer.setAttribute( + "aria-label", + "Onboarding toast auto-dismiss progress", + ) + progressBarContainer.style.cssText = ` + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3px; + background: #e5e7eb; + ` + + const progressBar = document.createElement("div") + progressBar.style.cssText = ` + height: 100%; + background: linear-gradient(90deg, #0ff0d2, #5bd3fb, #1e0ff0); + transform-origin: left; + animation: smProgressGrow ${duration}ms linear forwards; + ` + + // Update progress bar ARIA value as animation progresses + const startTime = Date.now() + const updateProgress = () => { + const elapsed = Date.now() - startTime + const progress = Math.min(100, Math.round((elapsed / duration) * 100)) + progressBarContainer.setAttribute("aria-valuenow", String(progress)) + if (progress < 100) { + requestAnimationFrame(updateProgress) + } + } + requestAnimationFrame(updateProgress) + + progressBarContainer.appendChild(progressBar) + + // Assemble toast + toast.appendChild(header) + toast.appendChild(buttonsContainer) + toast.appendChild(progressBarContainer) + + document.body.appendChild(toast) + + // Auto-dismiss after duration + setTimeout(() => { + if (document.body.contains(toast)) { + dismissToast(toast) + } + }, duration) +} + +/** + * Dismiss the toast with animation + */ +function dismissToast(toast: HTMLElement) { + toast.style.animation = "smFadeOut 0.3s ease-out forwards" + setTimeout(() => { + if (document.body.contains(toast)) { + toast.remove() + } + }, 300) +} + +/** + * Remove all Twitter-specific injected UI + */ +function removeAllTwitterUI() { + // Remove import button (legacy) + if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_IMPORT_BUTTON)) { + DOMUtils.removeElement(ELEMENT_IDS.TWITTER_IMPORT_BUTTON) + } + // Remove onboarding toast + if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_ONBOARDING_TOAST)) { + DOMUtils.removeElement(ELEMENT_IDS.TWITTER_ONBOARDING_TOAST) + } + // Remove import progress toast + if (DOMUtils.elementExists(ELEMENT_IDS.TWITTER_IMPORT_PROGRESS_TOAST)) { + DOMUtils.removeElement(ELEMENT_IDS.TWITTER_IMPORT_PROGRESS_TOAST) + } + // Remove any folder buttons + document.querySelectorAll("[data-supermemory-button]").forEach((button) => { + button.remove() + }) +} + +/** + * Shows or updates the import progress toast in the bottom-right + */ +let importToastDismissTimer: ReturnType | null = null + +function showOrUpdateImportProgressToast( + message: string, + status: "progress" | "success" | "error" = "progress", +) { + if (importToastDismissTimer) { + clearTimeout(importToastDismissTimer) + importToastDismissTimer = null + } + + let toast = document.getElementById(ELEMENT_IDS.TWITTER_IMPORT_PROGRESS_TOAST) + + if (!toast) { + // Ensure animation styles are available + if (!document.getElementById("supermemory-onboarding-toast-styles")) { + const style = document.createElement("style") + style.id = "supermemory-onboarding-toast-styles" + style.textContent = ` + @keyframes smSlideInUp { + from { transform: translateY(100%); opacity: 0; } + to { transform: translateY(0); opacity: 1; } + } + @keyframes smFadeOut { + from { transform: translateY(0); opacity: 1; } + to { transform: translateY(100%); opacity: 0; } + } + @keyframes smPulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } + } + ` + document.head.appendChild(style) + } + + // Create new toast + toast = document.createElement("div") + toast.id = ELEMENT_IDS.TWITTER_IMPORT_PROGRESS_TOAST + toast.style.cssText = ` + position: fixed; + bottom: 20px; + right: 20px; + z-index: 2147483647; + background: #ffffff; + border-radius: 12px; + padding: 14px 16px; + display: flex; + align-items: center; + gap: 12px; + font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-size: 14px; + color: #374151; + min-width: 280px; + max-width: 360px; + box-shadow: 0 4px 24px 0 rgba(0,0,0,0.18), 0 1.5px 6px 0 rgba(0,0,0,0.12); + animation: smSlideInUp 0.3s ease-out; + ` + + const iconUrl = browser.runtime.getURL("/new_logo.png") + const icon = document.createElement("img") + icon.src = iconUrl + icon.alt = "Supermemory" + icon.id = "sm-import-progress-icon" + icon.style.cssText = + "width: 20px; height: 20px; border-radius: 4px; flex-shrink: 0; animation: smPulse 1.5s ease-in-out infinite;" + + const textSpan = document.createElement("span") + textSpan.id = "sm-import-progress-text" + textSpan.style.cssText = "font-weight: 500; flex: 1;" + textSpan.textContent = message + + toast.appendChild(icon) + toast.appendChild(textSpan) + document.body.appendChild(toast) + } else { + // Update existing toast + const textSpan = toast.querySelector( + "#sm-import-progress-text", + ) as HTMLSpanElement + if (textSpan) { + textSpan.textContent = message + } + } + + const icon = toast.querySelector( + "#sm-import-progress-icon", + ) as HTMLImageElement + const textSpan = toast.querySelector( + "#sm-import-progress-text", + ) as HTMLSpanElement + + if (status === "progress") { + if (icon) { + icon.style.animation = "smPulse 1.5s ease-in-out infinite" + icon.style.opacity = "1" + } + if (textSpan) textSpan.style.color = "#374151" + } else { + if (icon) { + icon.style.animation = "none" + icon.style.opacity = "1" + } + if (textSpan) { + textSpan.style.color = status === "success" ? "#059669" : "#dc2626" + } + + importToastDismissTimer = setTimeout(() => { + const existingToast = document.getElementById( + ELEMENT_IDS.TWITTER_IMPORT_PROGRESS_TOAST, + ) + if (existingToast) { + dismissToast(existingToast) + } + importToastDismissTimer = null + }, 4000) + } +} + +export function updateTwitterImportUI(message: { + type: string + importedMessage?: string + totalImported?: number +}) { + if (message.type === MESSAGE_TYPES.IMPORT_UPDATE && message.importedMessage) { + showOrUpdateImportProgressToast(message.importedMessage) + } + + if (message.type === MESSAGE_TYPES.IMPORT_DONE) { + showOrUpdateImportProgressToast( + `✓ Imported ${message.totalImported} tweets!`, + "success", + ) + } + + if (message.type === MESSAGE_TYPES.IMPORT_ERROR && message.importedMessage) { + showOrUpdateImportProgressToast(message.importedMessage, "error") + } +} + +export async function handleTwitterNavigation() { + if (!DOMUtils.isOnDomain(DOMAINS.TWITTER)) { + return + } + + if (window.location.pathname === "/i/bookmarks") { + addTwitterImportButtonForFolders() + await handleBookmarksPageLoad() + } else { + removeAllTwitterUI() + } +} + +/** + * Adds import buttons to bookmark folders + */ +function addTwitterImportButtonForFolders() { + if (window.location.pathname !== "/i/bookmarks") { + return + } + + const targetElements = document.querySelectorAll( + ".css-175oi2r.r-1wtj0ep.r-16x9es5.r-1mmae3n.r-o7ynqc.r-6416eg.r-1ny4l3l.r-1loqt21", + ) + + targetElements.forEach((element) => { + addButtonToElement(element as HTMLElement) + }) +} + +/** + * Adds an import button to a bookmark folder element + */ +function addButtonToElement(element: HTMLElement) { + if (element.querySelector("[data-supermemory-button]")) { + return + } + + loadSpaceGroteskFonts() + + const button = createSaveTweetElement(async () => { + const url = element.getAttribute("href") + const bookmarkCollectionId = url?.split("/").pop() + if (bookmarkCollectionId) { + await showFolderProjectSelectionModal(bookmarkCollectionId) + } + }) + + button.setAttribute("data-supermemory-button", "true") + + element.appendChild(button) + element.style.flexDirection = "row" + element.style.alignItems = "center" + element.style.justifyContent = "center" + element.style.gap = "10px" + element.style.padding = "10px" +} + +/** + * Shows the project selection modal for folder imports + */ +async function showFolderProjectSelectionModal(bookmarkCollectionId: string) { + await loadSpaceGroteskFonts() + + const modal = createProjectSelectionModal( + [], + async (selectedProject) => { + modal.remove() + + try { + await browser.runtime.sendMessage({ + type: MESSAGE_TYPES.BATCH_IMPORT_ALL, + isFolderImport: true, + bookmarkCollectionId: bookmarkCollectionId, + selectedProject: selectedProject, + }) + } catch (error) { + console.error("Error importing bookmarks:", error) + } + }, + () => { + modal.remove() + }, + ) + + document.body.appendChild(modal) + + try { + const response = await browser.runtime.sendMessage({ + action: MESSAGE_TYPES.FETCH_PROJECTS, + }) + + if (response.success && response.data) { + const projects = response.data + updateModalWithProjects(modal, projects) + } else { + console.error("Failed to fetch projects:", response.error) + updateModalWithProjects(modal, []) + } + } catch (error) { + console.error("Error fetching projects:", error) + updateModalWithProjects(modal, []) + } +} + +/** + * Updates the modal with fetched projects + */ +function updateModalWithProjects( + modal: HTMLElement, + projects: Array<{ id: string; name: string; containerTag: string }>, +) { + const select = modal.querySelector("#project-select") as HTMLSelectElement + if (!select) return + + while (select.children.length > 1) { + select.removeChild(select.children[1]) + } + + if (projects.length === 0) { + const noProjectsOption = document.createElement("option") + noProjectsOption.value = "" + noProjectsOption.textContent = "No projects available" + noProjectsOption.disabled = true + select.appendChild(noProjectsOption) + + const importButton = modal.querySelector( + "button:last-child", + ) as HTMLButtonElement + if (importButton) { + importButton.disabled = true + importButton.style.cssText = ` + padding: 10px 16px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + background: rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.3); + font-size: 14px; + font-weight: 500; + cursor: not-allowed; + transition: all 0.2s ease; + ` + } + } else { + projects.forEach((project) => { + const option = document.createElement("option") + option.value = project.id + option.textContent = project.name + option.dataset.containerTag = project.containerTag + select.appendChild(option) + }) + } +}