+ {props.icon}
{title}
{active && children ? children : null}
diff --git a/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx b/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx
index db0b21e9a617..2b3d0a261a20 100644
--- a/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx
+++ b/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx
@@ -36,7 +36,7 @@ const useStyles = makeStyles()((theme) => ({
},
}))
-export interface ProfileTabContentProps extends withClasses<'text' | 'button'> {}
+export interface ProfileTabContentProps extends withClasses<'text' | 'button' | 'root'> {}
export function ProfileTabContent(props: ProfileTabContentProps) {
const { t } = useI18N()
diff --git a/packages/mask/src/plugins/NextID/base.ts b/packages/mask/src/plugins/NextID/base.ts
index 67b7ef3db4a5..1daef79df319 100644
--- a/packages/mask/src/plugins/NextID/base.ts
+++ b/packages/mask/src/plugins/NextID/base.ts
@@ -1,4 +1,4 @@
-import type { Plugin } from '@masknet/plugin-infra'
+import { CurrentSNSNetwork, Plugin } from '@masknet/plugin-infra'
import { PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from './constants'
import { languages } from './locales/languages'
@@ -9,7 +9,13 @@ export const base: Plugin.Shared.Definition = {
publisher: { name: { fallback: 'Mask Network' }, link: 'https://mask.io/' },
enableRequirement: {
architecture: { app: false, web: true },
- networks: { type: 'opt-out', networks: {} },
+ networks: {
+ type: 'opt-in',
+ networks: {
+ [CurrentSNSNetwork.Twitter]: true,
+ [CurrentSNSNetwork.Facebook]: true,
+ },
+ },
target: 'stable',
},
experimentalMark: true,
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/collecting/identity.ts b/packages/mask/src/social-network-adaptor/instagram.com/collecting/identity.ts
new file mode 100644
index 000000000000..707d5242f814
--- /dev/null
+++ b/packages/mask/src/social-network-adaptor/instagram.com/collecting/identity.ts
@@ -0,0 +1,58 @@
+import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
+import { ProfileIdentifier, delay } from '@masknet/shared-base'
+import { creator, SocialNetworkUI as Next } from '../../../social-network'
+import { instagramBase } from '../base'
+import { searchAvatarSelector } from '../utils/selector'
+import { getAvatar, getBioDescription, getNickname, getPersonalHomepage, getUserId } from '../utils/user'
+
+function resolveCurrentVisitingIdentityInner(
+ ref: Next.CollectingCapabilities.IdentityResolveProvider['recognized'],
+ cancel: AbortSignal,
+) {
+ const avatarSelector = searchAvatarSelector()
+ const assign = async () => {
+ await delay(500)
+ const bio = getBioDescription()
+ const homepage = getPersonalHomepage()
+ const nickname = getNickname()
+ const handle = getUserId()
+ const avatar = getAvatar()
+
+ ref.value = {
+ identifier: new ProfileIdentifier(instagramBase.networkIdentifier, handle),
+ nickname,
+ avatar,
+ bio,
+ homepage,
+ }
+ }
+ const createWatcher = (selector: LiveSelector
) => {
+ const watcher = new MutationObserverWatcher(selector)
+ .addListener('onAdd', () => assign())
+ .addListener('onChange', () => assign())
+ .startWatch({
+ childList: true,
+ subtree: true,
+ attributes: true,
+ attributeFilter: ['src', 'content'],
+ })
+
+ window.addEventListener('locationchange', assign)
+ cancel.addEventListener('abort', () => {
+ window.removeEventListener('locationchange', assign)
+ watcher.stopWatch()
+ })
+ }
+
+ assign()
+
+ createWatcher(avatarSelector)
+}
+
+export const CurrentVisitingIdentityProviderInstagram: Next.CollectingCapabilities.IdentityResolveProvider = {
+ hasDeprecatedPlaceholderName: false,
+ recognized: creator.EmptyIdentityResolveProviderState(),
+ start(cancel) {
+ resolveCurrentVisitingIdentityInner(this.recognized, cancel)
+ },
+}
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx
new file mode 100644
index 000000000000..1ad518cb2b0d
--- /dev/null
+++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx
@@ -0,0 +1,119 @@
+import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
+import { CollectibleIcon } from '@masknet/icons'
+import { makeStyles } from '@masknet/theme'
+import { ProfileTab } from '../../../components/InjectedComponents/ProfileTab'
+import { createReactRootShadowed, startWatch, untilElementAvailable } from '../../../utils'
+import {
+ searchProfileActiveTabSelector,
+ searchProfileTabListLastChildSelector,
+ searchProfileTabPageSelector,
+ searchProfileTabSelector,
+ searchUserIdSelector,
+} from '../utils/selector'
+
+export function injectProfileTabAtInstagram(signal: AbortSignal) {
+ let tabInjected = false
+ const contentWatcher = new MutationObserverWatcher(searchProfileTabPageSelector()).useForeach(() => {
+ const elePage = searchProfileTabPageSelector().evaluate()
+ if (elePage && !tabInjected) {
+ const watcher = new MutationObserverWatcher(searchProfileTabListLastChildSelector())
+ startWatch(watcher, signal)
+ createReactRootShadowed(watcher.firstDOMProxy.afterShadow, { signal }).render()
+ tabInjected = true
+ }
+ })
+
+ startWatch(contentWatcher, signal)
+}
+
+function getStyleProps() {
+ const EMPTY_STYLE = {} as CSSStyleDeclaration
+ const eleTab = searchProfileTabSelector().evaluate()
+ const style = eleTab ? window.getComputedStyle(eleTab) : EMPTY_STYLE
+ const activeTab = searchUserIdSelector().evaluate()
+ const activeStyle = activeTab ? window.getComputedStyle(activeTab) : EMPTY_STYLE
+ return {
+ color: style.color,
+ font: style.font,
+ fontSize: style.fontSize,
+ padding: style.paddingBottom,
+ height: style.height,
+ hover: activeStyle.color,
+ line: activeStyle.color,
+ }
+}
+
+const useStyles = makeStyles()(() => {
+ const props = getStyleProps()
+ return {
+ root: {
+ '&:hover': {
+ cursor: 'pointer',
+ },
+ display: '-webkit-box',
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginRight: 60,
+ },
+ button: {
+ color: props.color,
+ font: props.font,
+ fontSize: props.fontSize,
+ height: props.height,
+ justifyContent: 'center',
+ alignItems: 'center',
+ display: 'flex',
+ borderTop: '1px solid transparent',
+ },
+ selected: {
+ borderTop: `1px solid ${props.hover}`,
+ color: props.hover,
+ },
+ line: {},
+ icon: {
+ fontSize: props.fontSize,
+ paddingRight: 4,
+ },
+ }
+})
+
+export function ProfileTabAtInstagram() {
+ const { classes } = useStyles()
+
+ const reset = () => {
+ const activeTab = searchProfileActiveTabSelector().evaluate()
+ if (activeTab?.style) {
+ activeTab.style.borderTop = ''
+ activeTab.style.color = ''
+ }
+
+ Array.from(searchProfileTabPageSelector().evaluate()?.childNodes ?? []).forEach((v) => {
+ const ele = v as HTMLDivElement
+ if (ele.tagName !== 'SPAN') ele.style.display = ''
+ })
+ }
+ const clear = async () => {
+ const style = getStyleProps()
+ const activeTab = searchProfileActiveTabSelector().evaluate()
+ if (activeTab?.style) {
+ activeTab.style.borderTop = 'none'
+ activeTab.style.color = style.color
+ }
+ // hide the content page
+ await untilElementAvailable(searchProfileTabPageSelector())
+
+ Array.from(searchProfileTabPageSelector().evaluate()?.childNodes ?? []).forEach((v) => {
+ const ele = v as HTMLDivElement
+ if (ele.tagName !== 'SPAN') ele.style.display = 'none'
+ })
+ }
+ return (
+ }
+ classes={classes}
+ reset={reset}
+ clear={clear}
+ />
+ )
+}
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTabContent.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTabContent.tsx
new file mode 100644
index 000000000000..4240830d2ce6
--- /dev/null
+++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTabContent.tsx
@@ -0,0 +1,68 @@
+import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
+import { getMaskColor, makeStyles } from '@masknet/theme'
+import { ProfileTabContent } from '../../../components/InjectedComponents/ProfileTabContent'
+import { createReactRootShadowed, startWatch } from '../../../utils'
+import {
+ searchProfileActiveTabSelector,
+ searchProfileTabArticlePageSelector,
+ searchProfileTabOtherArticlePageSelector,
+} from '../utils/selector'
+
+export function injectProfileTabContentAtInstagram(signal: AbortSignal) {
+ injectProfileTabContentHaveArticle(signal)
+
+ injectProfileTabContentEmptyArticle(signal)
+}
+
+function injectProfileTabContentHaveArticle(signal: AbortSignal) {
+ const watcher = new MutationObserverWatcher(searchProfileTabArticlePageSelector())
+ startWatch(watcher, signal)
+ createReactRootShadowed(watcher.firstDOMProxy.afterShadow, { signal }).render()
+}
+
+function injectProfileTabContentEmptyArticle(signal: AbortSignal) {
+ const watcher = new MutationObserverWatcher(searchProfileTabOtherArticlePageSelector())
+ startWatch(watcher, signal)
+ createReactRootShadowed(watcher.firstDOMProxy.afterShadow, { signal }).render()
+}
+
+function getStyleProps() {
+ const activeTab = searchProfileActiveTabSelector().evaluate() as HTMLDivElement
+ return {
+ backgroundColor: activeTab ? window.getComputedStyle(activeTab).backgroundColor : undefined,
+ fontFamily: activeTab ? window.getComputedStyle(activeTab as HTMLElement).fontFamily : undefined,
+ }
+}
+
+const useStyles = makeStyles()((theme) => {
+ const props = getStyleProps()
+
+ return {
+ root: {
+ position: 'relative',
+ },
+ text: {
+ paddingTop: 29,
+ paddingBottom: 29,
+ '& > p': {
+ fontSize: 28,
+ fontFamily: props.fontFamily,
+ fontWeight: 700,
+ color: getMaskColor(theme).textPrimary,
+ },
+ },
+ button: {
+ backgroundColor: props.backgroundColor,
+ color: 'white',
+ marginTop: 18,
+ '&:hover': {
+ backgroundColor: props.backgroundColor,
+ },
+ },
+ }
+})
+
+export function ProfileTabContentAtInstagram() {
+ const { classes } = useStyles()
+ return
+}
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/ui-provider.ts b/packages/mask/src/social-network-adaptor/instagram.com/ui-provider.ts
index 74a97c9c04e0..23e6c5e3412a 100644
--- a/packages/mask/src/social-network-adaptor/instagram.com/ui-provider.ts
+++ b/packages/mask/src/social-network-adaptor/instagram.com/ui-provider.ts
@@ -5,9 +5,11 @@ import { IdentityProviderInstagram } from './collecting/identity-provider'
import { PostProviderInstagram } from './collecting/posts'
import { createTaskStartSetupGuideDefault } from '../../social-network/defaults'
import { pasteInstagram } from '@masknet/injected-script'
-import { injectPostInspectorInstagram } from './injection/post-inspector'
-import { newPostCompositionInstagram } from './injection/newPostComposition'
import { InitAutonomousStateProfiles } from '../../social-network/defaults/state/InitProfiles'
+import { CurrentVisitingIdentityProviderInstagram } from './collecting/identity'
+import { injectPostInspectorInstagram } from './injection/post-inspector'
+import { injectProfileTabAtInstagram } from './injection/ProfileTab'
+import { injectProfileTabContentAtInstagram } from './injection/ProfileTabContent'
const define: SocialNetworkUI.Definition = {
...instagramShared,
@@ -22,6 +24,7 @@ const define: SocialNetworkUI.Definition = {
},
collecting: {
identityProvider: IdentityProviderInstagram,
+ currentVisitingIdentityProvider: CurrentVisitingIdentityProviderInstagram,
postsProvider: PostProviderInstagram,
},
configuration: {
@@ -40,11 +43,13 @@ const define: SocialNetworkUI.Definition = {
injection: {
setupWizard: createTaskStartSetupGuideDefault(),
postInspector: injectPostInspectorInstagram,
- newPostComposition: {
+ profileTab: injectProfileTabAtInstagram,
+ profileTabContent: injectProfileTabContentAtInstagram,
+ /* newPostComposition: {
start: newPostCompositionInstagram,
supportedInputTypes: { text: true, image: true },
supportedOutputTypes: { text: false, image: true },
- },
+ },*/
},
}
export default define
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/utils/selector.ts b/packages/mask/src/social-network-adaptor/instagram.com/utils/selector.ts
new file mode 100644
index 000000000000..1a5ed38db3cf
--- /dev/null
+++ b/packages/mask/src/social-network-adaptor/instagram.com/utils/selector.ts
@@ -0,0 +1,44 @@
+import { LiveSelector } from '@dimensiondev/holoflows-kit'
+
+type E = HTMLElement
+
+const querySelector = (selector: string, singleMode = true) => {
+ const ls = new LiveSelector().querySelector(selector)
+ return (singleMode ? ls.enableSingleMode() : ls) as LiveSelector
+}
+const querySelectorAll = (selector: string) => {
+ return new LiveSelector().querySelectorAll(selector)
+}
+
+export const searchProfileTabListLastChildSelector = () =>
+ querySelector('[id="react-root"] section main div[role="tablist"] > :last-child')
+
+export const searchProfileTabPageSelector = () =>
+ querySelector('[id="react-root"] section main > div > div:last-child')
+
+export const searchProfileTabSelector = () =>
+ querySelector('[id="react-root"] section main div[role="tablist"] a[aria-selected="false"]')
+
+export const searchProfileActiveTabSelector = () =>
+ querySelector('[id="react-root"] section main div[role="tablist"] a[aria-selected="true"]')
+
+export const searchAvatarSelector = () =>
+ querySelector('[id="react-root"] section main header button img')
+
+export const bioDescriptionSelector = () =>
+ querySelector('[id="react-root"] section main header section > div:last-child div')
+
+export const personalHomepageSelector = () =>
+ querySelector('[id="react-root"] section main header section > div:last-child a')
+
+export const searchNickNameSelector = () =>
+ querySelector('[id="react-root"] section main header section > div:last-child > span')
+
+export const searchUserIdSelector = () =>
+ querySelector('[id="react-root"] section main header section > div:first-child > :first-child')
+
+export const searchProfileTabArticlePageSelector = () =>
+ querySelector('[id="react-root"] section main > div > div:last-child > article')
+
+export const searchProfileTabOtherArticlePageSelector = () =>
+ querySelector('[id="react-root"] section main > div > div:last-child > div')
diff --git a/packages/mask/src/social-network-adaptor/instagram.com/utils/user.ts b/packages/mask/src/social-network-adaptor/instagram.com/utils/user.ts
new file mode 100644
index 000000000000..81ebe30fcec9
--- /dev/null
+++ b/packages/mask/src/social-network-adaptor/instagram.com/utils/user.ts
@@ -0,0 +1,36 @@
+import { collectNodeText } from '../../../utils'
+import {
+ bioDescriptionSelector,
+ personalHomepageSelector,
+ searchAvatarSelector,
+ searchNickNameSelector,
+ searchUserIdSelector,
+} from './selector'
+
+export function getBioDescription() {
+ const bio = bioDescriptionSelector().evaluate()
+ return bio ? collectNodeText(bio) : ''
+}
+
+export const getPersonalHomepage = () => {
+ const node = personalHomepageSelector().evaluate()
+ return node?.textContent || ''
+}
+
+export const getNickname = () => {
+ const node = searchNickNameSelector().evaluate()
+ return node ? collectNodeText(node) : ''
+}
+
+export const getUserId = () => {
+ const node = searchUserIdSelector().evaluate()
+ return node ? collectNodeText(node) : ''
+}
+
+export const getAvatar = () => {
+ const node = searchAvatarSelector().evaluate()
+ if (!node) return ''
+
+ const imageURL = node.getAttribute('src') ?? ''
+ return imageURL.trim()
+}
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/ProfileTab.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/ProfileTab.tsx
index a2a9f82deae3..3ebfb90446a1 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/injection/ProfileTab.tsx
+++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/ProfileTab.tsx
@@ -37,7 +37,7 @@ const useStyles = makeStyles()((theme) => {
const props = getStyleProps()
return {
- tab: {
+ root: {
'&:hover': {
backgroundColor: new Color(props.hover).alpha(0.1).toString(),
cursor: 'pointer',
diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/ProfileTabContent.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/ProfileTabContent.tsx
index a40a63a7ef19..69b29eb6b811 100644
--- a/packages/mask/src/social-network-adaptor/twitter.com/injection/ProfileTabContent.tsx
+++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/ProfileTabContent.tsx
@@ -39,6 +39,13 @@ const useStyles = makeStyles()((theme) => {
const props = getStyleProps()
return {
+ root: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ zIndex: 1,
+ },
text: {
paddingTop: 29,
paddingBottom: 29,