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[™ÎˆLMœÂ‚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)
+ })
+ }
+}