fix(extension): restore complete X import UI source

This commit is contained in:
abhinav7x94 2026-08-16 07:27:34 +05:30
parent 8b3456d051
commit 7301480830

View file

@ -132,59 +132,631 @@ export async function openImportModal() {
action: MESSAGE_TYPES.FETCH_PROJECTS,
})
const projects = response.success && response.data ? response.data : xëOm¢G§²Úî<EFBFBD>Æ­yÚYˆ
Ú[™ÝË›ØØ][Û‹œ]˜[YHOOH‹ÚKØ›ÛÚÛX\šÜÈŠH‚B\™]\›‚‚_B‚‚XÛÛœÝ\™Ù][[Y[<EFBFBD>ÈHØÝ[Y[<EFBFBD> œ]Y\žTÙ[XÝÜ<EFBFBD>[
‚BH‹˜ÜÜËLMÍ[ÚLœ‹œ‹L]ÝŒ\ œ‹LMžY\ÍKœ‹L[[XYLÛ‹œ‹[ÍÞ[œXËœ‹M<EFBFBD> M™YËœ‹L[žM Û œ‹L[Ü] ŒH‹‚JB‚‚]\™Ù][[Y[<EFBFBD>Ë™›Ü‘XXÚ
const projects = response.success && response.data ? response.data : []
[[Y[<EFBFBD>
HOˆ‚BXY<EFBFBD>]Û•Ñ[[Y[<EFBFBD>
[[Y[<EFBFBD>\ÈS[[Y[<EFBFBD>
B‚_JBŸB‚‹ÊŠ‚ˆ
ˆYÈ[ˆ[\Ü<EFBFBD><EFBFBD>]ÛˆÈH›ÛÚÛX\šÈ›Û\ˆ[[Y[<EFBFBD>ˆ
‹Â™<EFBFBD>[˜Ý[ÛˆY<EFBFBD>]Û•Ñ[[Y[<EFBFBD>
[[Y[<EFBFBD>ˆS[[Y[<EFBFBD>
H‚ZYˆ
[[Y[<EFBFBD> œ]Y\žTÙ[XÝÜŠ–Ù]K\Ý\\›Y[[ÜžKX<EFBFBD>]Û—HŠJH‚B\™]\›‚‚_B‚‚[ØYÜXÙQÜ›Ý\ÚÑ›Û<EFBFBD>Ê
B‚‚XÛÛœÝ<EFBFBD>]ÛˆHÜ™X]TØ]™UÙY][[Y[<EFBFBD>
\Þ[˜È
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[<EFBFBD> ™Ù]]šX<EFBFBD>]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<EFBFBD>]Û‹œÙ]]šX<EFBFBD>]J™]K\Ý\\›Y[[ÜžKX<EFBFBD>]Ûˆ‹<EFBFBD><EFBFBD>YHŠB‚‚Y[[Y[<EFBFBD> ˜\[™Ú[
<EFBFBD>]ÛŠB‚Y[[Y[<EFBFBD> œÝ[K™›^\™XÝ[ÛˆHœ›ÝÈ‚‚Y[[Y[<EFBFBD> œÝ[K˜[YÛ’][\ÈH˜Ù[<EFBFBD>\ˆ‚‚Y[[Y[<EFBFBD> œÝ[Kš<EFBFBD>\ÝYžPÛÛ<EFBFBD>[<EFBFBD>H˜Ù[<EFBFBD>\ˆ‚‚Y[[Y[<EFBFBD> œÝ[K™Ø\HŒL‚‚Y[[Y[<EFBFBD> œÝ[KœY[™ÈHŒL‚ŸB‚‹ÊŠ‚ˆ
ˆÚÝÜÈH›Ú™XÝÙ[XÝ[Ûˆ[Ù[›Üˆ›Û\ˆ[\Ü<EFBFBD>ˆ
‹Â˜\Þ[˜È<EFBFBD>[˜Ý[ÛˆÚÝÑ›Û\”›Ú™XÝÙ[XÝ[Û“[Ù[
›ÛÚÛX\šÐÛÛXÝ[Û’YˆÝš[™ÊH‚X]ØZ]ØYÜXÙQÜ›Ý\ÚÑ›Û<EFBFBD>Ê
B‚‚XÛÛœÝ[Ù[HÜ™X]T›Ú™XÝÙ[XÝ[Û“[Ù[
‚BV×K‚BX\Þ[˜È
Ù[XÝY›Ú™XÝ
HOˆ‚BB[[Ù[ œ™[[Ý™J
B‚‚BB]žH‚BBBX]ØZ]œ›ÝÜÙ\‹œ<EFBFBD>[<EFBFBD>[YKœÙ[™Y\ÜØYÙJ‚BBBB]\NˆQTÔÐQÑWÕTTË<EFBFBD><EFBFBD>UÒÒSTÔ•ÐS ‚BBBBZ\Ñ›Û\’[\Ü<EFBFBD>ˆ<EFBFBD>YK‚BBBBX›ÛÚÛX\šÐÛÛXÝ[Û’Yˆ›ÛÚÛX\šÐÛÛXÝ[Û’Y ‚BBBB\Ù[XÝY›Ú™X݈Ù[XÝY›Ú™XÝ ‚BBB_JB‚BB_HØ]Ú
\œ›ÜŠH‚BBBXÛÛœÛÛK™\œ›ÜŠ‘\œ›Üˆ[\Ü<EFBFBD>[™È›ÛÚÛX\šÜΈ‹\œ›ÜŠB‚BB_B‚B_K‚BJ
HOˆ‚BB[[Ù[ œ™[[Ý™J
B‚B_K‚JB‚‚YØÝ[Y[<EFBFBD> ˜›ÙK˜\[™Ú[
[Ù[
B‚‚]žH‚BXÛۜݙ\ÜÛœÙHH]ØZ]œ›ÝÜÙ\‹œ<EFBFBD>[<EFBFBD>[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݈
‹Â™<EFBFBD>[˜Ý[Ûˆ\]S[Ù[Ú]›Ú™XÝÊ‚[[Ù[ˆS[[Y[<EFBFBD> ‚\›Ú™XÝΈ\œ˜^OÈYˆÝš[™ÎȘ[YNˆÝš[™ÎÈÛÛ<EFBFBD>Z[™\•YΈÝš[™ÈO‹ŠH‚XÛÛœÝÙ[XÝH[Ù[ œ]Y\žTÙ[XÝÜŠˆÜ›Ú™XÝ \Ù[XÝŠH\ÈSÙ[XÝ[[Y[<EFBFBD>‚ZYˆ
\Ù[XÝ
H™]\›‚‚‚]Ú[H
Ù[XÝ ˜Ú[™[‹›[™Ýˆ JH‚B\Ù[XÝ œ™[[Ý™PÚ[
Ù[XÝ ˜Ú[™[–ÌWJB‚_B‚‚ZYˆ
›Ú™XÝË›[™ÝOOH
H‚BXÛۜݛԛڙXÝÓÜ[ÛˆHØÝ[Y[<EFBFBD> ˜Ü™X]Q[[Y[<EFBFBD>
›Ü[ÛˆŠB‚B[›Ô›Ú™XÝÓÜ[Û‹<EFBFBD>˜[YHHˆ‚‚B[›Ô›Ú™XÝÓÜ[Û‹<EFBFBD>^ÛÛ<EFBFBD>[<EFBFBD>H“›È›Ú™XÝÈ]˜Z[X›H‚‚B[›Ô›Ú™XÝÓÜ[Û‹™\ØX›YH<EFBFBD>YB‚B\Ù[XÝ ˜\[™Ú[
›Ô›Ú™XÝÓÜ[ÛŠB‚‚BXÛÛœÝ[\Ü<EFBFBD><EFBFBD>]ÛˆH[Ù[ œ]Y\žTÙ[XÝÜŠ‚BBH˜<EFBFBD>]ÛŽ›\Ý XÚ[‹‚BJH\ÈS<EFBFBD>]Û‘[[Y[<EFBFBD>‚BZYˆ
[\Ü<EFBFBD><EFBFBD>]ÛŠH‚BBZ[\Ü<EFBFBD><EFBFBD>]Û‹™\ØX›YH<EFBFBD>YB‚BBZ[\Ü<EFBFBD><EFBFBD>]Û‹œÝ[K˜ÜÜÕ^H‚BBB\Y[™Îˆ L Mœ‚BBBX›Ü™\Žˆ \ÛÛY™Ø˜J <EFBFBD>MK <EFBFBD>MK <EFBFBD>MK ŒJN‚BBBX›Ü™\‹\˜Y]\Έ Lœ‚BBBX˜XÚÙÜ›Ý[™ˆ™Ø˜J <EFBFBD>MK <EFBFBD>MK <EFBFBD>MK Œ JN‚BBBXÛÛÜŽˆ™Ø˜J <EFBFBD>MK <EFBFBD>MK <EFBFBD>MK ŒÊN‚BBBY›Û<EFBFBD> \Ú^™Nˆ M‚BBBY›Û<EFBFBD> ]Ù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[<EFBFBD> ˜Ü™X]Q[[Y[<EFBFBD>
›Ü[ÛˆŠB‚BB[Ü[Û‹<EFBFBD>˜[YHH›Ú™XÝ šY‚BB[Ü[Û‹<EFBFBD>^ÛÛ<EFBFBD>[<EFBFBD>H›Ú™XÝ ›˜[YB‚BB[Ü[Û‹™]\Ù] ˜ÛÛ<EFBFBD>Z[™\•YÈH›Ú™XÝ ˜ÛÛ<EFBFBD>Z[™\•Y‚BB\Ù[XÝ ˜\[™Ú[
Ü[ÛŠB‚B_JB‚_BŸB
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 = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
`
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<typeof setTimeout> | 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)
})
}
}