mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-08 03:08:21 +00:00
fix(extension): restore complete X import UI source
This commit is contained in:
parent
8b3456d051
commit
7301480830
1 changed files with 624 additions and 52 deletions
|
|
@ -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[™ÎˆLMœÂ‚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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue