diff --git a/cspell.json b/cspell.json index 802ff9b768d2..2206d4c57230 100644 --- a/cspell.json +++ b/cspell.json @@ -236,5 +236,6 @@ ], "ignoreRegExpList": ["/[A-Za-z0-9]{44}/", "/[A-Za-z0-9]{46}/", "/[A-Za-z0-9]{59}/"], "overrides": [], - "words": [] + "words": ["endregion"] } + diff --git a/packages/mask/content-script/site-adaptor-infra/ui.ts b/packages/mask/content-script/site-adaptor-infra/ui.ts index f30c49dbc5db..e898b735edc8 100644 --- a/packages/mask/content-script/site-adaptor-infra/ui.ts +++ b/packages/mask/content-script/site-adaptor-infra/ui.ts @@ -96,9 +96,7 @@ export async function activateSiteAdaptorUIInner(ui_deferred: SiteAdaptorUI.Defe ui.injection.userAvatar?.(signal) ui.injection.profileAvatar?.(signal) ui.injection.tips?.(signal) - ui.injection.nameWidget?.(signal) - ui.injection.farcaster?.(signal) - ui.injection.lens?.(signal) + ui.injection.badges?.(signal) ui.injection.enhancedProfileNFTAvatar?.(signal) ui.injection.openNFTAvatar?.(signal) diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/index.tsx new file mode 100644 index 000000000000..8b0acb51ac85 --- /dev/null +++ b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/index.tsx @@ -0,0 +1,13 @@ +import { injectBadgesOnConversation } from './injectBadgesOnConversation.js' +import { injectBadgesOnPost } from './injectBadgesOnPost.js' +import { injectBadgesOnProfile } from './injectBadgesOnProfile.js' +import { injectBadgesOnSpaceDock } from './injectBadgesOnSpaceDock.js' +import { injectBadgesOnUserCell } from './injectBadgesOnUserCell.js' + +export function injectBadges(signal: AbortSignal) { + injectBadgesOnProfile(signal) + injectBadgesOnPost(signal) + injectBadgesOnUserCell(signal) + injectBadgesOnConversation(signal) + injectBadgesOnSpaceDock(signal) +} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnConversation.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnConversation.tsx similarity index 89% rename from packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnConversation.tsx rename to packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnConversation.tsx index 4417aa7a2ca2..341679c691ee 100644 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnConversation.tsx +++ b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnConversation.tsx @@ -42,14 +42,14 @@ function createRootElement() { return span } -const ConversationFarcasterSlot = memo(function ConversationFarcasterSlot({ userId }: Props) { +const ConversationBadgesSlot = memo(function ConversationBadgesSlot({ userId }: Props) { const [disabled, setDisabled] = useState(true) const { classes, cx } = useStyles() const component = useMemo(() => { const Component = createInjectHooksRenderer( useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, + (plugin) => plugin.Badges?.UI?.Content, undefined, createRootElement, ) @@ -59,7 +59,7 @@ const ConversationFarcasterSlot = memo(function ConversationFarcasterSlot({ user return ( ) @@ -73,7 +73,7 @@ const ConversationFarcasterSlot = memo(function ConversationFarcasterSlot({ user /** * Inject on conversation, including both DM drawer and message page (/messages/xxx) */ -export function injectFarcasterOnConversation(signal: AbortSignal) { +export function injectBadgesOnConversation(signal: AbortSignal) { const watcher = new MutationObserverWatcher(selector()) startWatch(watcher, signal) watcher.useForeach((node, _, proxy) => { @@ -90,7 +90,7 @@ export function injectFarcasterOnConversation(signal: AbortSignal) { }, '') if (!userId) return attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , + , ) }) } diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnPost.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnPost.tsx similarity index 88% rename from packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnPost.tsx rename to packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnPost.tsx index 4238552ce692..5e4cb58824a3 100644 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnPost.tsx +++ b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnPost.tsx @@ -14,7 +14,7 @@ function selector() { } // structure: -export function injectLensOnPost(signal: AbortSignal) { +export function injectBadgesOnPost(signal: AbortSignal) { const watcher = new MutationObserverWatcher(selector()) startWatch(watcher, signal) watcher.useForeach((node, _, proxy) => { @@ -26,7 +26,7 @@ export function injectLensOnPost(signal: AbortSignal) { const userId = href?.split('/')[1] if (!userId) return attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , + , ) }) } @@ -58,21 +58,23 @@ function createRootElement() { }) return span } -function PostLensSlot({ userId }: Props) { +function PostBadgesSlot({ userId }: Props) { const [disabled, setDisabled] = useState(true) const { classes, cx } = useStyles() const component = useMemo(() => { const Component = createInjectHooksRenderer( useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, + (plugin) => plugin.Badges?.UI?.Content, undefined, createRootElement, ) const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() if (!identifier) return null - return + return ( + + ) }, [userId]) if (!component) return null diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnProfile.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnProfile.tsx similarity index 88% rename from packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnProfile.tsx rename to packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnProfile.tsx index 308d72966689..2276795fcb1e 100644 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnProfile.tsx +++ b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnProfile.tsx @@ -11,10 +11,10 @@ function selector() { return querySelector('[data-testid=UserName] div[dir]') } -export function injectLensOnProfile(signal: AbortSignal) { +export function injectBadgesOnProfile(signal: AbortSignal) { const watcher = new MutationObserverWatcher(selector()) startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() + attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() } const useStyles = makeStyles()((theme) => ({ @@ -32,7 +32,7 @@ const useStyles = makeStyles()((theme) => ({ }, })) -function ProfileLensSlot() { +function ProfileBadgesSlot() { const visitingIdentity = useCurrentVisitingIdentity() const [disabled, setDisabled] = useState(true) const { classes, cx } = useStyles() @@ -40,13 +40,13 @@ function ProfileLensSlot() { const component = useMemo(() => { const Component = createInjectHooksRenderer( useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, + (plugin) => plugin.Badges?.UI?.Content, ) return ( ) diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnSpaceDock.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnSpaceDock.tsx similarity index 91% rename from packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnSpaceDock.tsx rename to packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnSpaceDock.tsx index 062aa96b02a6..e66841e81081 100644 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnSpaceDock.tsx +++ b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnSpaceDock.tsx @@ -19,7 +19,7 @@ function avatarSelector() { /** * Inject on space dock */ -export function injectFarcasterOnSpaceDock(signal: AbortSignal) { +export function injectBadgesOnSpaceDock(signal: AbortSignal) { const watcher = new MutationObserverWatcher(avatarSelector()) startWatch(watcher, signal) watcher.useForeach((node, _, proxy) => { @@ -30,7 +30,7 @@ export function injectFarcasterOnSpaceDock(signal: AbortSignal) { const userId = avatar.dataset.testid?.slice('UserAvatar-Container-'.length) if (!userId) return attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , + , ) }) } @@ -66,14 +66,14 @@ function createRootElement() { return span } -function SpaceDockFarcasterSlot({ userId }: Props) { +function SpaceDockBadgesSlot({ userId }: Props) { const [disabled, setDisabled] = useState(true) const { classes, cx } = useStyles() const component = useMemo(() => { const Component = createInjectHooksRenderer( useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, + (plugin) => plugin.Badges?.UI?.Content, undefined, createRootElement, ) @@ -83,7 +83,7 @@ function SpaceDockFarcasterSlot({ userId }: Props) { return ( ) diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnUserCell.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnUserCell.tsx similarity index 90% rename from packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnUserCell.tsx rename to packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnUserCell.tsx index 145dfc4e01ab..1e53280db9fa 100644 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnUserCell.tsx +++ b/packages/mask/content-script/site-adaptors/twitter.com/injection/Badges/injectBadgesOnUserCell.tsx @@ -17,14 +17,14 @@ function selector() { /** * Inject on sidebar user cell */ -export function injectFarcasterOnUserCell(signal: AbortSignal) { +export function injectBadgesOnUserCell(signal: AbortSignal) { const watcher = new MutationObserverWatcher(selector()) startWatch(watcher, signal) watcher.useForeach((node, _, proxy) => { const userId = node.closest('[role=link]')?.getAttribute('href')?.slice(1) if (!userId) return // Intended to set `untilVisible` to true, but mostly user cells are fixed and visible - attachReactTreeWithContainer(proxy.afterShadow, { signal }).render() + attachReactTreeWithContainer(proxy.afterShadow, { signal }).render() }) } @@ -59,14 +59,14 @@ function createRootElement() { return span } -function UserCellFarcasterSlot({ userId }: Props) { +function UserCellBadgesSlot({ userId }: Props) { const [disabled, setDisabled] = useState(true) const { classes, cx } = useStyles() const component = useMemo(() => { const Component = createInjectHooksRenderer( useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, + (plugin) => plugin.Badges?.UI?.Content, undefined, createRootElement, ) @@ -77,7 +77,7 @@ function UserCellFarcasterSlot({ userId }: Props) { return ( ) diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/index.tsx deleted file mode 100644 index 34e296b395d5..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { injectFarcasterOnConversation } from './injectFarcasterOnConversation.js' -import { injectFarcasterOnPost } from './injectFarcasterOnPost.js' -import { injectFarcasterOnProfile } from './injectFarcasterOnProfile.js' -import { injectFarcasterOnSpaceDock } from './injectFarcasterOnSpaceDock.js' -import { injectFarcasterOnUserCell } from './injectFarcasterOnUserCell.js' - -export function injectFarcaster(signal: AbortSignal) { - injectFarcasterOnProfile(signal) - injectFarcasterOnPost(signal) - injectFarcasterOnUserCell(signal) - injectFarcasterOnConversation(signal) - injectFarcasterOnSpaceDock(signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnPost.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnPost.tsx deleted file mode 100644 index bd45efd24e4a..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnPost.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useMemo, useState } from 'react' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' - -function selector() { - return querySelectorAll('[data-testid=User-Name] div').filter((node) => { - return node.firstElementChild?.matches('a[role=link]:not([tabindex])') - }) -} - -// structure: -export function injectFarcasterOnPost(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const link = node.querySelector('a[href][role=link]') - // To simplify the selector above, we do this checking manually - // has tabindex=-1, has a child time element - if (link?.hasAttribute('tabindex') || link?.querySelector('time')) return - const href = link?.getAttribute('href') - const userId = href?.split('/')[1] - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - alignItems: 'center', - display: 'flex', - }) - return span -} -function PostFarcasterSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, - undefined, - createRootElement, - ) - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnProfile.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnProfile.tsx deleted file mode 100644 index 0e941017376f..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Farcaster/injectFarcasterOnProfile.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { makeStyles } from '@masknet/theme' -import { startWatch } from '../../../../utils/startWatch.js' -import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelector } from '../../utils/selector.js' - -function selector() { - return querySelector('[data-testid=UserName] div[dir]') -} - -export function injectFarcasterOnProfile(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - attachReactTreeWithContainer(watcher.firstDOMProxy.afterShadow, { signal }).render() -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -function ProfileFarcasterSlot() { - const visitingIdentity = useCurrentVisitingIdentity() - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Farcaster?.UI?.Content, - ) - - return ( - - ) - }, [visitingIdentity.identifier]) - - if (!component || !visitingIdentity.identifier) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/index.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/index.tsx deleted file mode 100644 index f88a03d46af4..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { injectLensOnConversation } from './injectLensOnConversation.js' -import { injectLensOnPost } from './injectLensOnPost.js' -import { injectLensOnProfile } from './injectLensOnProfile.js' -import { injectLensOnSpaceDock } from './injectLensOnSpaceDock.js' -import { injectLensOnUserCell } from './injectLensOnUserCell.js' - -export function injectLens(signal: AbortSignal) { - injectLensOnProfile(signal) - injectLensOnPost(signal) - injectLensOnUserCell(signal) - injectLensOnConversation(signal) - injectLensOnSpaceDock(signal) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnConversation.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnConversation.tsx deleted file mode 100644 index 5866bb3ad745..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnConversation.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { memo, useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' -import { startWatch } from '../../../../utils/startWatch.js' - -function selector() { - return querySelectorAll('[data-testid=conversation] div:not([tabindex]) div[dir] + div[dir]') -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - verticalAlign: 'bottom', - height: '21px', - alignItems: 'center', - justifyContent: 'center', - display: 'inline-flex', - } as CSSStyleDeclaration) - return span -} - -const ConversationLensSlot = memo(function ConversationLensSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, - undefined, - createRootElement, - ) - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrapOr(null) - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -}) - -/** - * Inject on conversation, including both DM drawer and message page (/messages/xxx) - */ -export function injectLensOnConversation(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const spans = node - .closest('[data-testid=conversation]') - ?.querySelectorAll('[tabindex] [dir] span:not([data-testid=tweetText])') - if (!spans) return - const userId = [...spans].reduce((id, node) => { - if (id) return id - if (node.textContent?.match(/@\w/)) { - return node.textContent.trim().slice(1) - } - return '' - }, '') - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnSpaceDock.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnSpaceDock.tsx deleted file mode 100644 index 9adf543d77ab..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnSpaceDock.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useMemo, useState } from 'react' -import { makeStyles } from '@masknet/theme' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { startWatch } from '../../../../utils/startWatch.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { querySelectorAll } from '../../utils/selector.js' - -function avatarSelector() { - return querySelectorAll( - '[data-testid=SpaceDockExpanded] [data-testid^=UserAvatar-Container-],[data-testid=sheetDialog] [data-testid^=UserAvatar-Container-]', - ).map((node) => { - const span = node.parentElement?.parentElement?.nextElementSibling?.querySelector('div > span + span > span') - return span - }) -} - -/** - * Inject on space dock - */ -export function injectLensOnSpaceDock(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(avatarSelector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const avatar = node - .closest('div[dir]') - ?.previousElementSibling?.querySelector('[data-testid^=UserAvatar-Container-]') - if (!avatar) return - const userId = avatar.dataset.testid?.slice('UserAvatar-Container-'.length) - if (!userId) return - attachReactTreeWithContainer(proxy.afterShadow, { signal, untilVisible: true }).render( - , - ) - }) -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - verticalAlign: 'bottom', - height: '21px', - alignItems: 'center', - justifyContent: 'center', - display: 'inline-flex', - } as CSSStyleDeclaration) - return span -} - -function SpaceDockLensSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, - undefined, - createRootElement, - ) - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnUserCell.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnUserCell.tsx deleted file mode 100644 index 81efeaf3f762..000000000000 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/Lens/injectLensOnUserCell.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useMemo, useState } from 'react' -import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { createInjectHooksRenderer, Plugin, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import { EnhanceableSite, ProfileIdentifier } from '@masknet/shared-base' -import { makeStyles } from '@masknet/theme' -import { querySelectorAll } from '../../utils/selector.js' -import { attachReactTreeWithContainer } from '../../../../utils/shadow-root/renderInShadowRoot.js' -import { startWatch } from '../../../../utils/startWatch.js' - -function selector() { - // [href^="/search"] is a hash tag - return querySelectorAll( - '[data-testid=UserCell] div > a[role=link]:not([tabindex]):not([href^="/search"]) [dir]:last-of-type', - ) -} - -/** - * Inject on sidebar user cell - */ -export function injectLensOnUserCell(signal: AbortSignal) { - const watcher = new MutationObserverWatcher(selector()) - startWatch(watcher, signal) - watcher.useForeach((node, _, proxy) => { - const userId = node.closest('[role=link]')?.getAttribute('href')?.slice(1) - if (!userId) return - // Intended to set `untilVisible` to true, but mostly user cells are fixed and visible - attachReactTreeWithContainer(proxy.afterShadow, { signal }).render() - }) -} - -const useStyles = makeStyles()((theme) => ({ - hide: { - display: 'none', - }, - slot: { - position: 'relative', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - borderRadius: 999, - marginLeft: theme.spacing(0.5), - verticalAlign: 'top', - }, -})) - -interface Props { - userId: string -} - -function createRootElement() { - const span = document.createElement('span') - Object.assign(span.style, { - verticalAlign: 'bottom', - height: '21px', - alignItems: 'center', - justifyContent: 'center', - display: 'flex', - } as CSSStyleDeclaration) - return span -} - -function UserCellLensSlot({ userId }: Props) { - const [disabled, setDisabled] = useState(true) - const { classes, cx } = useStyles() - - const component = useMemo(() => { - const Component = createInjectHooksRenderer( - useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, - (plugin) => plugin.Lens?.UI?.Content, - undefined, - createRootElement, - ) - if (userId.includes('/')) return null - const identifier = ProfileIdentifier.of(EnhanceableSite.Twitter, userId).unwrap() - if (!identifier) return null - - return ( - - ) - }, [userId]) - - if (!component) return null - - return {component} -} diff --git a/packages/mask/content-script/site-adaptors/twitter.com/ui-provider.ts b/packages/mask/content-script/site-adaptors/twitter.com/ui-provider.ts index 02d53696a046..7f7e22678188 100644 --- a/packages/mask/content-script/site-adaptors/twitter.com/ui-provider.ts +++ b/packages/mask/content-script/site-adaptors/twitter.com/ui-provider.ts @@ -40,12 +40,10 @@ import { TwitterRenderFragments } from './customization/render-fragments.js' import { injectProfileCover } from './injection/ProfileCover.js' import { injectProfileCardHolder } from './injection/ProfileCard/index.js' import { injectAvatar } from './injection/Avatar/index.js' -import { injectLens } from './injection/Lens/index.js' import { injectNFTAvatarInTwitter } from './injection/NFT/index.js' import { injectSwitchLogoButton } from './injection/SwitchLogo.js' import { injectCalendar } from './injection/Calendar.js' -import { injectNameWidget } from './injection/NameWidget/index.js' -import { injectFarcaster } from './injection/Farcaster/index.js' +import { injectBadges } from './injection/Badges/index.js' const useInjectedDialogClassesOverwriteTwitter = makeStyles()((theme) => { const smallQuery = `@media (max-width: ${theme.breakpoints.values.sm}px)` @@ -205,9 +203,7 @@ const twitterUI: SiteAdaptorUI.Definition = { openNFTAvatarSettingDialog, avatar: injectAvatar, tips: injectTips, - lens: injectLens, - farcaster: injectFarcaster, - nameWidget: injectNameWidget, + badges: injectBadges, profileCard: injectProfileCardHolder, switchLogo: injectSwitchLogoButton, calendar: injectCalendar, diff --git a/packages/plugin-infra/src/types.ts b/packages/plugin-infra/src/types.ts index 3717f3eedb95..fdc1aa4927b7 100644 --- a/packages/plugin-infra/src/types.ts +++ b/packages/plugin-infra/src/types.ts @@ -260,6 +260,7 @@ export namespace Plugin.SiteAdaptor { Lens?: LensWidget /** This UI will be rendered components on the Lens realm */ Farcaster?: FarcasterWidget + Badges?: BadgesWidget NameWidget?: NameWidget /** This UI will be rendered as plugin wrapper page */ wrapperProps?: PluginWrapperProps @@ -540,6 +541,13 @@ export namespace Plugin.SiteAdaptor { Sidebar = 'sidebar', } + export enum BadgesSlot { + ProfileName = 'profile-name', + Post = 'post', + Sidebar = 'sidebar', + } + + /** @deprecated */ export interface LensOptions { identity?: ProfileIdentifier slot: LensSlot @@ -547,6 +555,7 @@ export namespace Plugin.SiteAdaptor { /** To update enabled/disabled status */ onStatusUpdate?(disabled: boolean): void } + /** @deprecated */ export interface FarcasterOptions { identity?: ProfileIdentifier slot: FarcasterSlot @@ -555,6 +564,14 @@ export namespace Plugin.SiteAdaptor { onStatusUpdate?(disabled: boolean): void } + export interface SocialBadgeOptions { + identity?: ProfileIdentifier + slot: BadgesSlot + /** To update enabled/disabled status */ + onStatusUpdate?(disabled: boolean): void + } + + /** @deprecated */ export interface LensWidget { ID: string UI?: { @@ -564,6 +581,7 @@ export namespace Plugin.SiteAdaptor { Content: InjectUI } } + /** @deprecated */ export interface FarcasterWidget { ID: string UI?: { @@ -573,6 +591,16 @@ export namespace Plugin.SiteAdaptor { Content: InjectUI } } + export interface BadgesWidget { + ID: string + UI?: { + /** + * The injected Social Badge Content component + */ + Content: InjectUI + } + } + export enum NameWidgetSlot { ProfileName = 'profile-name', Post = 'post', diff --git a/packages/plugins/RSS3/src/SiteAdaptor/SocialFeeds/SocialFeed.tsx b/packages/plugins/RSS3/src/SiteAdaptor/SocialFeeds/SocialFeed.tsx index 864b5d1f34de..7b0e77d000ef 100644 --- a/packages/plugins/RSS3/src/SiteAdaptor/SocialFeeds/SocialFeed.tsx +++ b/packages/plugins/RSS3/src/SiteAdaptor/SocialFeeds/SocialFeed.tsx @@ -178,7 +178,7 @@ const useStyles = makeStyles { diff --git a/packages/plugins/RSS3/src/SiteAdaptor/components/share.ts b/packages/plugins/RSS3/src/SiteAdaptor/components/share.ts index 78360bdb1690..066ea0859a21 100644 --- a/packages/plugins/RSS3/src/SiteAdaptor/components/share.ts +++ b/packages/plugins/RSS3/src/SiteAdaptor/components/share.ts @@ -139,7 +139,7 @@ export const hostIconMap: Record = { 'polygonscan.com': Icons.PolygonScan, 'crossbell.io': Icons.Crossbell, 'scan.crossbell.io': Icons.Crossbell, - 'lenster.xyz': Icons.Lens, + 'lenster.xyz': Icons.DarkLens, 'looksrare.org': Icons.LooksRare, 'gitcoin.co': Icons.Gitcoin, 'bscscan.com': Icons.BSC, diff --git a/packages/plugins/RedPacket/src/SiteAdaptor/FireflyRedPacketDetailsItem.tsx b/packages/plugins/RedPacket/src/SiteAdaptor/FireflyRedPacketDetailsItem.tsx index a2d5df872a87..0bb95a6cae65 100644 --- a/packages/plugins/RedPacket/src/SiteAdaptor/FireflyRedPacketDetailsItem.tsx +++ b/packages/plugins/RedPacket/src/SiteAdaptor/FireflyRedPacketDetailsItem.tsx @@ -171,7 +171,7 @@ const useStyles = makeStyles<{ listItemBackground?: string; listItemBackgroundIc const platformIconMap = { [FireflyRedPacketAPI.PlatformType.twitter]: , - [FireflyRedPacketAPI.PlatformType.lens]: , + [FireflyRedPacketAPI.PlatformType.lens]: , [FireflyRedPacketAPI.PlatformType.farcaster]: , } diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/Web3ProfileGlobalInjection.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/Web3ProfileGlobalInjection.tsx index fbf3ecd40d92..b0e9c517ee58 100644 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/Web3ProfileGlobalInjection.tsx +++ b/packages/plugins/Web3Profile/src/SiteAdaptor/Web3ProfileGlobalInjection.tsx @@ -1,12 +1,11 @@ -import { memo, useEffect, useState } from 'react' -import { ChainId } from '@masknet/web3-shared-evm' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import { CrossIsolationMessages } from '@masknet/shared-base' +import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import { EVMWeb3ContextProvider } from '@masknet/web3-hooks-base' -import { LensPopup } from './components/Lens/LensPopup.js' +import { ChainId } from '@masknet/web3-shared-evm' +import { memo, useEffect, useState } from 'react' import { FollowLensDialog } from './components/Lens/FollowLensDialog.js' +import { SocialPopup } from './components/SocialBadges/SocialPopup.js' import { Web3ProfileDialog } from './components/Web3ProfileDialog.js' -import { FarcasterPopup } from './components/Farcaster/FarcasterPopup.js' export const Web3ProfileGlobalInjection = memo(function Web3ProfileGlobalInjection() { const [profileOpen, setProfileOpen] = useState(false) @@ -39,8 +38,7 @@ export const Web3ProfileGlobalInjection = memo(function Web3ProfileGlobalInjecti : null} - - + ) }) diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterBadge.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterBadge.tsx deleted file mode 100644 index 503a5cc18338..000000000000 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterBadge.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Icons } from '@masknet/icons' -import { Plugin } from '@masknet/plugin-infra' -import { makeStyles } from '@masknet/theme' -import type { FireflyConfigAPI } from '@masknet/web3-providers/types' -import { IconButton } from '@mui/material' -import { memo, useEffect, useRef } from 'react' -import { closeFarcasterPopup, openFarcasterPopup } from '../../emitter.js' - -const FarcasterIconSizeMap: Record = { - [Plugin.SiteAdaptor.FarcasterSlot.Post]: 18, - [Plugin.SiteAdaptor.FarcasterSlot.ProfileName]: 18, - [Plugin.SiteAdaptor.FarcasterSlot.Sidebar]: 16, -} - -const useStyles = makeStyles()({ - badge: { - padding: 0, - verticalAlign: 'baseline', - }, -}) -interface Props { - slot: Plugin.SiteAdaptor.FarcasterSlot - accounts: FireflyConfigAPI.FarcasterProfile[] - userId: string -} - -export const FarcasterBadge = memo(({ slot, accounts, userId }: Props) => { - const buttonRef = useRef(null) - const { classes } = useStyles() - - useEffect(() => { - const button = buttonRef.current - if (!button) return - let openTimer: ReturnType - const enter = () => { - clearTimeout(openTimer) - - openTimer = setTimeout(() => { - openFarcasterPopup({ - accounts, - userId, - popupAnchorEl: buttonRef.current, - }) - }, 200) - } - const leave = () => { - clearTimeout(openTimer) - } - button.addEventListener('mouseenter', enter) - button.addEventListener('mouseleave', leave) - return () => { - clearTimeout(openTimer) - button.removeEventListener('mouseenter', enter) - button.removeEventListener('mouseleave', leave) - } - }, [accounts, userId]) - - useEffect(() => { - function hide() { - closeFarcasterPopup({ - popupAnchorEl: buttonRef.current, - }) - } - const ob = new IntersectionObserver((entries) => { - if (entries[0].intersectionRatio !== 0) return - hide() - }) - if (buttonRef.current) { - ob.observe(buttonRef.current) - } - return () => { - hide() - ob.disconnect() - } - // eslint-disable-next-line react-compiler/react-compiler - }, [buttonRef.current]) - - return ( - - - - ) -}) - -FarcasterBadge.displayName = 'FarcasterBadge' diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterPopup.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterPopup.tsx deleted file mode 100644 index 37ae7c236963..000000000000 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterPopup.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { isEqual } from 'lodash-es' -import { memo, useEffect, useRef, useState } from 'react' -import { ShadowRootPopper, makeStyles } from '@masknet/theme' -import type { FireflyConfigAPI } from '@masknet/web3-providers/types' -import { Fade } from '@mui/material' -import { emitter } from '../../emitter.js' -import { useControlFarcasterPopup } from '../../hooks/Farcaster/useControlFarcasterPopup.js' -import { FarcasterList } from './FarcasterList.js' - -const useStyles = makeStyles()((theme) => ({ - popup: { - position: 'absolute', - zIndex: 99, - borderRadius: 16, - boxShadow: - theme.palette.mode === 'light' ? - '0px 4px 30px rgba(0, 0, 0, 0.1)' - : '0px 4px 30px rgba(255, 255, 255, 0.15)', - }, -})) - -export const FarcasterPopup = memo(() => { - const { classes } = useStyles() - const holderRef = useRef(null) - const [accounts, setAccounts] = useState([]) - const active = useControlFarcasterPopup(holderRef) - const [anchorEl, setAnchorEl] = useState() - const anchorElRef = useRef(undefined) - - useEffect(() => { - const unsubscribeOpen = emitter.on('open-farcaster', async ({ accounts, popupAnchorEl }) => { - setAccounts((oldAccounts) => { - return isEqual(oldAccounts, accounts) ? oldAccounts : accounts - }) - setAnchorEl(popupAnchorEl) - anchorElRef.current = popupAnchorEl - }) - const unsubscribeClose = emitter.on('close-farcaster', ({ popupAnchorEl }) => { - if (popupAnchorEl !== anchorElRef.current) return - setAnchorEl(null) - }) - return () => { - unsubscribeOpen() - unsubscribeClose() - } - }, []) - - return ( - - - - - - ) -}) - -FarcasterPopup.displayName = 'FarcasterPopup' diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/FollowLensDialog.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/FollowLensDialog.tsx index ce34c95522a0..724a62e887cd 100644 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/FollowLensDialog.tsx +++ b/packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/FollowLensDialog.tsx @@ -1,3 +1,4 @@ +import { Trans } from '@lingui/macro' import { Icons } from '@masknet/icons' import { ChainBoundary, @@ -21,9 +22,8 @@ import { getFireflyLensProfileLink, getProfileAvatar } from '../../../utils.js' import { useConfettiExplosion } from '../../hooks/ConfettiExplosion/index.js' import { useFollow } from '../../hooks/Lens/useFollow.js' import { useUnfollow } from '../../hooks/Lens/useUnfollow.js' -import { HandlerDescription } from './HandlerDescription.js' import { useUpdateFollowingStatus } from '../../hooks/Lens/useUpdateFollowingStatus.js' -import { Trans } from '@lingui/macro' +import { HandlerDescription } from './HandlerDescription.js' const useStyles = makeStyles<{ account: boolean }>()((theme, { account }) => ({ container: { diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/LensPopup.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/LensPopup.tsx deleted file mode 100644 index aeb6891745b8..000000000000 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/LensPopup.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { isEqual, sortBy, uniqBy } from 'lodash-es' -import { memo, useEffect, useRef, useState } from 'react' -import { ShadowRootPopper, makeStyles } from '@masknet/theme' -import { NextIDProof } from '@masknet/web3-providers' -import type { FireflyConfigAPI, NextIDBaseAPI } from '@masknet/web3-providers/types' -import { Fade } from '@mui/material' -import { emitter } from '../../emitter.js' -import { useControlLensPopup } from '../../hooks/Lens/useControlLensPopup.js' -import { LensList } from './LensList.js' - -const useStyles = makeStyles()((theme) => ({ - popup: { - position: 'absolute', - zIndex: 99, - borderRadius: 16, - boxShadow: - theme.palette.mode === 'light' ? - '0px 4px 30px rgba(0, 0, 0, 0.1)' - : '0px 4px 30px rgba(255, 255, 255, 0.15)', - }, -})) - -export const NextIdLensToFireflyLens = (account: NextIDBaseAPI.LensAccount): FireflyConfigAPI.LensAccount => { - return { - address: account.address, - name: account.displayName, - handle: account.handle, - bio: '', - url: '', - profileUri: [], - } -} - -export const LensPopup = memo(() => { - const { classes } = useStyles() - const holderRef = useRef(null) - const [lens, setLens] = useState([]) - const active = useControlLensPopup(holderRef) - const [anchorEl, setAnchorEl] = useState() - const anchorElRef = useRef(undefined) - - useEffect(() => { - const unsubscribeOpen = emitter.on('open', async ({ lensAccounts, popupAnchorEl }) => { - setLens((oldAccounts) => { - return isEqual(oldAccounts, lensAccounts) ? oldAccounts : lensAccounts - }) - setAnchorEl(popupAnchorEl) - anchorElRef.current = popupAnchorEl - if (!lens[0]?.handle) return - const accounts = await NextIDProof.queryAllLens(lens[0].handle) - if (!accounts.length) return - setLens((oldAccounts) => { - if (accounts.length <= oldAccounts.length) return oldAccounts - const merged = uniqBy([...oldAccounts, ...accounts.map(NextIdLensToFireflyLens)], (x) => x.handle) - return sortBy(merged, [(x) => -accounts.findIndex((y) => x.handle === y.handle)]) - }) - }) - const unsubscribeClose = emitter.on('close', ({ popupAnchorEl }) => { - if (popupAnchorEl !== anchorElRef.current) return - setAnchorEl(null) - }) - return () => { - unsubscribeOpen() - unsubscribeClose() - } - }, []) - - return ( - - - - - - ) -}) - -LensPopup.displayName = 'LensPopup' diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/components/ProfilePopup.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/components/ProfilePopup.tsx index e0e18336eb26..0ad1717ca371 100644 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/components/ProfilePopup.tsx +++ b/packages/plugins/Web3Profile/src/SiteAdaptor/components/ProfilePopup.tsx @@ -1,6 +1,12 @@ +import { Trans } from '@lingui/macro' import { Icons } from '@masknet/icons' +import { Image, SelectProviderModal, WalletIcon } from '@masknet/shared' +import { NetworkPluginID } from '@masknet/shared-base' import { makeStyles, usePortalShadowRoot } from '@masknet/theme' +import { useChainContext, useProviderDescriptor, useWeb3Utils } from '@masknet/web3-hooks-base' import type { LensBaseAPI } from '@masknet/web3-providers/types' +import { isSameAddress } from '@masknet/web3-shared-base' +import { ChainId, formatEthereumAddress } from '@masknet/web3-shared-evm' import { Box, Button, @@ -14,13 +20,7 @@ import { Typography, } from '@mui/material' import { memo } from 'react' -import { Image, SelectProviderModal, WalletIcon } from '@masknet/shared' -import { ChainId, formatEthereumAddress } from '@masknet/web3-shared-evm' -import { NetworkPluginID } from '@masknet/shared-base' -import { useChainContext, useProviderDescriptor, useWeb3Utils } from '@masknet/web3-hooks-base' -import { isSameAddress } from '@masknet/web3-shared-base' import { getProfileAvatar } from '../../utils.js' -import { Trans } from '@lingui/macro' const useStyles = makeStyles()((theme) => ({ paper: { @@ -171,9 +171,9 @@ export const ProfilePopup = memo(function ProfilePopup({ className={classes.avatar} size={36} src={avatar} - fallback={} + fallback={} /> - : } + : } = { - [Plugin.SiteAdaptor.LensSlot.Post]: 18, - [Plugin.SiteAdaptor.LensSlot.ProfileName]: 18, - [Plugin.SiteAdaptor.LensSlot.Sidebar]: 16, +const BadgesIconSizeMap: Record = { + [Plugin.SiteAdaptor.BadgesSlot.Post]: 18, + [Plugin.SiteAdaptor.BadgesSlot.ProfileName]: 18, + [Plugin.SiteAdaptor.BadgesSlot.Sidebar]: 16, } const useStyles = makeStyles()({ @@ -17,14 +17,23 @@ const useStyles = makeStyles()({ padding: 0, verticalAlign: 'baseline', }, + farcaster: { + marginLeft: -5, + }, }) interface Props { - slot: Plugin.SiteAdaptor.LensSlot - accounts: FireflyConfigAPI.LensAccount[] + slot: Plugin.SiteAdaptor.BadgesSlot + lensAccounts: FireflyConfigAPI.LensAccount[] + farcasterAccounts: FireflyConfigAPI.FarcasterProfile[] userId: string } -export const LensBadge = memo(({ slot, accounts, userId }: Props) => { +export const SocialBadges = memo(function SocialBadges({ + slot, + lensAccounts, + farcasterAccounts, + userId, +}: Props) { const buttonRef = useRef(null) const { classes } = useStyles() @@ -37,7 +46,8 @@ export const LensBadge = memo(({ slot, accounts, userId }: Props) => { openTimer = setTimeout(() => { openPopup({ - lensAccounts: accounts, + lensAccounts, + farcasterAccounts, userId, popupAnchorEl: buttonRef.current, }) @@ -53,7 +63,7 @@ export const LensBadge = memo(({ slot, accounts, userId }: Props) => { button.removeEventListener('mouseenter', enter) button.removeEventListener('mouseleave', leave) } - }, [accounts, userId]) + }, [lensAccounts, userId, farcasterAccounts]) useEffect(() => { function hide() { @@ -75,11 +85,15 @@ export const LensBadge = memo(({ slot, accounts, userId }: Props) => { // eslint-disable-next-line react-compiler/react-compiler }, [buttonRef.current]) + const size = BadgesIconSizeMap[slot] return ( - + {lensAccounts.length ? + + : null} + {farcasterAccounts.length ? + + : null} ) }) - -LensBadge.displayName = 'LensBadge' diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterList.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/components/SocialBadges/FarcasterList.tsx similarity index 87% rename from packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterList.tsx rename to packages/plugins/Web3Profile/src/SiteAdaptor/components/SocialBadges/FarcasterList.tsx index 35d1cf2a5303..ccd99bc37779 100644 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Farcaster/FarcasterList.tsx +++ b/packages/plugins/Web3Profile/src/SiteAdaptor/components/SocialBadges/FarcasterList.tsx @@ -2,7 +2,7 @@ import { Icons } from '@masknet/icons' import { Image } from '@masknet/shared' import { makeStyles } from '@masknet/theme' import type { FireflyConfigAPI } from '@masknet/web3-providers/types' -import { List, ListItem, Typography, type ListProps, Link } from '@mui/material' +import { ListItem, Typography, Link } from '@mui/material' import { memo } from 'react' const useStyles = makeStyles()((theme) => { @@ -69,29 +69,25 @@ const useStyles = makeStyles()((theme) => { }, } }) -interface Props extends ListProps { +interface Props { accounts: FireflyConfigAPI.FarcasterProfile[] } -export const FarcasterList = memo(({ className, accounts, ...rest }: Props) => { - const { classes, cx } = useStyles() - +export const FarcasterList = memo(function FarcasterList({ accounts }: Props) { return ( - + <> {accounts.map((account, key) => { return })} - + ) }) -FarcasterList.displayName = 'FarcasterList' - interface FarcasterListItemProps { account: FireflyConfigAPI.FarcasterProfile } -const FarcasterListItem = memo(({ account }) => { +const FarcasterListItem = memo(function FarcasterListItem({ account }) { const { classes } = useStyles() const profileUri = `https://firefly.mask.social/profile/farcaster/${account.fid}` const farcasterIcon = @@ -117,5 +113,3 @@ const FarcasterListItem = memo(({ account }) => { ) }) - -FarcasterListItem.displayName = 'FarcasterListItem' diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/LensList.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/components/SocialBadges/LensList.tsx similarity index 87% rename from packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/LensList.tsx rename to packages/plugins/Web3Profile/src/SiteAdaptor/components/SocialBadges/LensList.tsx index 3a6503ee404c..ad09dad2ff58 100644 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/components/Lens/LensList.tsx +++ b/packages/plugins/Web3Profile/src/SiteAdaptor/components/SocialBadges/LensList.tsx @@ -1,35 +1,20 @@ +import { Trans } from '@lingui/macro' import { Icons } from '@masknet/icons' import { Image } from '@masknet/shared' import { CrossIsolationMessages, EMPTY_LIST, PersistentStorages } from '@masknet/shared-base' import { ActionButton, makeStyles } from '@masknet/theme' -import { memo } from 'react' import { useChainContext } from '@masknet/web3-hooks-base' import { Lens } from '@masknet/web3-providers' import type { FireflyConfigAPI } from '@masknet/web3-providers/types' import { isSameAddress } from '@masknet/web3-shared-base' -import { List, ListItem, Typography, type ListProps } from '@mui/material' +import { ListItem, Typography } from '@mui/material' import { useQuery } from '@tanstack/react-query' import { compact, first } from 'lodash-es' +import { memo } from 'react' import { useSubscription } from 'use-subscription' -import { Trans } from '@lingui/macro' const useStyles = makeStyles()((theme) => { - const isDark = theme.palette.mode === 'dark' return { - list: { - backgroundColor: isDark ? '#030303' : theme.palette.common.white, - maxWidth: 320, - // Show up to 6 item - maxHeight: 244, - overflow: 'auto', - minWidth: 240, - padding: theme.spacing(1.5), - boxSizing: 'border-box', - borderRadius: 16, - '&::-webkit-scrollbar': { - display: 'none', - }, - }, listItem: { cursor: 'default', display: 'flex', @@ -90,12 +75,11 @@ const useStyles = makeStyles()((theme) => { }, } }) -interface Props extends ListProps { +interface Props { accounts: FireflyConfigAPI.LensAccount[] } -export const LensList = memo(({ className, accounts, ...rest }: Props) => { - const { classes, cx } = useStyles() +export const LensList = memo(function LensList({ accounts }: Props) { const { account: wallet } = useChainContext() const latestProfile = useSubscription(PersistentStorages.Settings.storage.latestLensProfile.subscription) @@ -152,22 +136,20 @@ export const LensList = memo(({ className, accounts, ...rest }: Props) => { }) return ( - + <> {data.map((account, key) => { return })} - + ) }) -LensList.displayName = 'LensList' - interface LensListItemProps { account: FireflyConfigAPI.LensAccount loading: boolean } -const LensListItem = memo(({ account, loading }) => { +const LensListItem = memo(function LensListItem({ account, loading }) { const { classes } = useStyles() const { account: wallet } = useChainContext() const profileUri = account.profileUri.filter(Boolean) @@ -207,5 +189,3 @@ const LensListItem = memo(({ account, loading }) => { ) }) - -LensListItem.displayName = 'LensListItem' diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/components/SocialBadges/SocialPopup.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/components/SocialBadges/SocialPopup.tsx new file mode 100644 index 000000000000..77745582cd04 --- /dev/null +++ b/packages/plugins/Web3Profile/src/SiteAdaptor/components/SocialBadges/SocialPopup.tsx @@ -0,0 +1,96 @@ +import { isEqual, sortBy, uniqBy } from 'lodash-es' +import { memo, useEffect, useRef, useState } from 'react' +import { ShadowRootPopper, makeStyles } from '@masknet/theme' +import { NextIDProof } from '@masknet/web3-providers' +import type { FireflyConfigAPI } from '@masknet/web3-providers/types' +import { Fade, List } from '@mui/material' +import { emitter } from '../../emitter.js' +import { useControlLensPopup } from '../../hooks/Lens/useControlLensPopup.js' +import { LensList } from './LensList.js' +import { FarcasterList } from './FarcasterList.js' +import { NextIdLensToFireflyLens } from '../../../utils.js' + +const useStyles = makeStyles()((theme) => { + const isDark = theme.palette.mode === 'dark' + return { + popup: { + position: 'absolute', + zIndex: 99, + borderRadius: 16, + boxShadow: + theme.palette.mode === 'light' ? + '0px 4px 30px rgba(0, 0, 0, 0.1)' + : '0px 4px 30px rgba(255, 255, 255, 0.15)', + }, + list: { + backgroundColor: isDark ? '#030303' : theme.palette.common.white, + maxWidth: 320, + // Show up to 6 item + maxHeight: 244, + overflow: 'auto', + minWidth: 240, + padding: theme.spacing(1.5), + boxSizing: 'border-box', + borderRadius: 16, + '&::-webkit-scrollbar': { + display: 'none', + }, + }, + } +}) + +export const SocialPopup = memo(function SocialPopup() { + const { classes } = useStyles() + const holderRef = useRef(null) + const [lensAccounts, setLensAccounts] = useState([]) + const [farcasterAccounts, setFarcasterAccounts] = useState([]) + const active = useControlLensPopup(holderRef) + const [anchorEl, setAnchorEl] = useState() + const anchorElRef = useRef(undefined) + + useEffect(() => { + const unsubscribeOpen = emitter.on('open', async ({ lensAccounts, farcasterAccounts, popupAnchorEl }) => { + setLensAccounts((old) => (isEqual(old, lensAccounts) ? old : lensAccounts)) + setFarcasterAccounts((old) => (isEqual(old, farcasterAccounts) ? old : farcasterAccounts)) + setAnchorEl(popupAnchorEl) + anchorElRef.current = popupAnchorEl + if (lensAccounts[0]?.handle) { + const accounts = await NextIDProof.queryAllLens(lensAccounts[0].handle) + if (!accounts.length) return + setLensAccounts((oldAccounts) => { + if (accounts.length <= oldAccounts.length) return oldAccounts + const merged = uniqBy([...oldAccounts, ...accounts.map(NextIdLensToFireflyLens)], (x) => x.handle) + return sortBy(merged, [(x) => -accounts.findIndex((y) => x.handle === y.handle)]) + }) + } + }) + const unsubscribeClose = emitter.on('close', ({ popupAnchorEl }) => { + if (popupAnchorEl !== anchorElRef.current) return + setAnchorEl(null) + }) + return () => { + unsubscribeOpen() + unsubscribeClose() + } + }, []) + + return ( + + + + {lensAccounts.length ? + + : null} + {farcasterAccounts.length ? + + : null} + + + + ) +}) diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/emitter.ts b/packages/plugins/Web3Profile/src/SiteAdaptor/emitter.ts index 3b859562dd3b..3c00a3ea49eb 100644 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/emitter.ts +++ b/packages/plugins/Web3Profile/src/SiteAdaptor/emitter.ts @@ -3,6 +3,7 @@ import { Emitter } from '@servie/events' interface OpenPopupOptions { lensAccounts: FireflyConfigAPI.LensAccount[] + farcasterAccounts: FireflyConfigAPI.FarcasterProfile[] /** For lazy load lens accounts from NextID */ userId: string popupAnchorEl: HTMLElement | null diff --git a/packages/plugins/Web3Profile/src/SiteAdaptor/index.tsx b/packages/plugins/Web3Profile/src/SiteAdaptor/index.tsx index 1c09c9e09f06..cf32ff11e753 100644 --- a/packages/plugins/Web3Profile/src/SiteAdaptor/index.tsx +++ b/packages/plugins/Web3Profile/src/SiteAdaptor/index.tsx @@ -11,10 +11,9 @@ import { useEffect, useMemo } from 'react' import { Trans } from '@lingui/macro' import { base } from '../base.js' import { Web3ProfileGlobalInjection } from './Web3ProfileGlobalInjection.js' -import { FarcasterBadge } from './components/Farcaster/FarcasterBadge.js' -import { LensBadge } from './components/Lens/LensBadge.js' -import { NextIdLensToFireflyLens } from './components/Lens/LensPopup.js' import { setupStorage } from './context.js' +import { SocialBadges } from './components/SocialBadges/Badges.js' +import { NextIdLensToFireflyLens } from '../utils.js' const site: Plugin.SiteAdaptor.Definition = { ...base, @@ -62,13 +61,15 @@ const site: Plugin.SiteAdaptor.Definition = { } })(), ], - Lens: { - ID: `${base.ID}_lens`, + Badges: { + ID: `${base.ID}_badges`, UI: { Content({ identity, slot, onStatusUpdate }) { const userId = identity?.userId + + // #region lens const { data: accounts = EMPTY_LIST } = useFireflyLensAccounts(userId, true) - const isProfile = slot === Plugin.SiteAdaptor.LensSlot.ProfileName + const isProfile = slot === Plugin.SiteAdaptor.BadgesSlot.ProfileName const handle = accounts[0]?.handle const { data: nextIdLens = EMPTY_LIST } = useQuery({ @@ -84,32 +85,27 @@ const site: Plugin.SiteAdaptor.Definition = { () => (isProfile ? uniqBy([...accounts, ...nextIdLens], (x) => x.handle) : accounts), [isProfile, accounts, nextIdLens], ) + // #endregion + + // #region farcaster + const { data: farcasterAccounts = EMPTY_LIST } = useFireflyFarcasterAccounts(userId) + // #endregion - const disabled = !lensAccounts.length + const disabled = !lensAccounts.length && !farcasterAccounts.length useEffect(() => { onStatusUpdate?.(disabled) }, [onStatusUpdate, disabled]) if (!accounts.length || !userId) return null - return - }, - }, - }, - Farcaster: { - ID: `${base.ID}_farcaster`, - UI: { - Content({ identity, slot, onStatusUpdate }) { - const userId = identity?.userId - - const { data: profiles = EMPTY_LIST } = useFireflyFarcasterAccounts(userId) - const disabled = !profiles.length - - useEffect(() => { - onStatusUpdate?.(disabled) - }, [onStatusUpdate, disabled]) - if (!userId) return null - return + return ( + + ) }, }, }, diff --git a/packages/plugins/Web3Profile/src/utils.ts b/packages/plugins/Web3Profile/src/utils.ts index 9a2bd42f30ef..ec12e7281f1d 100644 --- a/packages/plugins/Web3Profile/src/utils.ts +++ b/packages/plugins/Web3Profile/src/utils.ts @@ -1,4 +1,4 @@ -import type { LensBaseAPI } from '@masknet/web3-providers/types' +import type { FireflyConfigAPI, LensBaseAPI, NextIDBaseAPI } from '@masknet/web3-providers/types' import urlcat from 'urlcat' export function getFireflyLensProfileLink(handle: string) { @@ -11,3 +11,14 @@ export function getProfileAvatar(profile: LensBaseAPI.Profile | undefined) { if ('optimized' in picture) return picture.optimized?.uri || picture.raw.uri return picture.image.optimized?.uri || picture.image.raw.uri } + +export const NextIdLensToFireflyLens = (account: NextIDBaseAPI.LensAccount): FireflyConfigAPI.LensAccount => { + return { + address: account.address, + name: account.displayName, + handle: account.handle, + bio: '', + url: '', + profileUri: [], + } +} diff --git a/packages/shared/src/UI/components/AccountIcons/index.tsx b/packages/shared/src/UI/components/AccountIcons/index.tsx index 44311128fafd..e96f621bf06a 100644 --- a/packages/shared/src/UI/components/AccountIcons/index.tsx +++ b/packages/shared/src/UI/components/AccountIcons/index.tsx @@ -1,12 +1,12 @@ +import { Select, Trans } from '@lingui/macro' import { Icons } from '@masknet/icons' import { SocialAddressType, type SocialAccount } from '@masknet/shared-base' import { makeStyles, ShadowRootTooltip } from '@masknet/theme' import type { Web3Helper } from '@masknet/web3-helpers' import { resolveSocialAddressLink } from '@masknet/web3-shared-base' -import { Typography, type TooltipProps, Link } from '@mui/material' +import { Link, Typography, type TooltipProps } from '@mui/material' import { compact } from 'lodash-es' import { Linking } from '../../../index.js' -import { Select, Trans } from '@lingui/macro' const useStyles = makeStyles()((theme) => { return { @@ -191,7 +191,7 @@ export function AccountIcons({ socialAccount, classes: externalClasses }: Accoun supportedAddressTypes.includes(SocialAddressType.Lens) ? { type: SocialAddressType.Lens, - icon: , + icon: , } : null, ]) diff --git a/packages/shared/src/UI/components/SocialAccountList/utils.tsx b/packages/shared/src/UI/components/SocialAccountList/utils.tsx index 1c150f64cf2a..ab2e50672483 100644 --- a/packages/shared/src/UI/components/SocialAccountList/utils.tsx +++ b/packages/shared/src/UI/components/SocialAccountList/utils.tsx @@ -10,7 +10,7 @@ export const resolveNextIDPlatformIcon = createLookupTableResolver