diff --git a/packages/mask/src/components/CompositionDialog/Composition.tsx b/packages/mask/src/components/CompositionDialog/Composition.tsx index 94d152d95013..07561d218b9b 100644 --- a/packages/mask/src/components/CompositionDialog/Composition.tsx +++ b/packages/mask/src/components/CompositionDialog/Composition.tsx @@ -18,6 +18,7 @@ let openOnInitAnswered = false export function Composition({ type = 'timeline', requireClipboardPermission }: PostDialogProps) { const { t } = useI18N() + const [reason, setReason] = useState<'timeline' | 'popup' | 'reply'>('timeline') // #region Open const [open, setOpen] = useState(false) const onClose = useCallback(() => { @@ -45,8 +46,9 @@ export function Composition({ type = 'timeline', requireClipboardPermission }: P useEffect(() => { return MaskMessages.events.requestComposition.on(({ reason, open, content, options }) => { - if (reason !== type || globalUIState.profiles.value.length <= 0) return + if (reason !== 'reply' && (reason !== type || globalUIState.profiles.value.length <= 0)) return setOpen(open) + setReason(reason) if (content) UI.current?.setMessage(content) if (options?.target) UI.current?.setEncryptionKind(options.target) if (options?.startupPlugin) UI.current?.startPlugin(options.startupPlugin) @@ -63,11 +65,10 @@ export function Composition({ type = 'timeline', requireClipboardPermission }: P // #endregion // #region submit - const onSubmit_ = useSubmit(onClose) + const onSubmit_ = useSubmit(onClose, reason) // #endregion const UI = useRef(null) - const networkSupport = activatedSocialNetworkUI.injection.newPostComposition?.supportedOutputTypes return ( diff --git a/packages/mask/src/components/CompositionDialog/useSubmit.ts b/packages/mask/src/components/CompositionDialog/useSubmit.ts index 77f9b11bb798..2f6a83877657 100644 --- a/packages/mask/src/components/CompositionDialog/useSubmit.ts +++ b/packages/mask/src/components/CompositionDialog/useSubmit.ts @@ -12,7 +12,7 @@ import { unreachable } from '@dimensiondev/kit' import { useLastRecognizedIdentity } from '../DataSource/useActivatedUI' import { isFacebook } from '../../social-network-adaptor/facebook.com/base' -export function useSubmit(onClose: () => void) { +export function useSubmit(onClose: () => void, reason: 'timeline' | 'popup' | 'reply') { const { t } = useI18N() const whoAmI = useLastRecognizedIdentity() @@ -45,32 +45,40 @@ export function useSubmit(onClose: () => void) { random: new Date().toLocaleString(), }) if (redPacketMetadata.ok) { - await pasteImage(redPacketPreText.replace(encrypted, '') ?? defaultText, encrypted, 'eth') + await pasteImage(redPacketPreText.replace(encrypted, '') ?? defaultText, encrypted, 'eth', reason) } else { - await pasteImage(defaultText, encrypted, 'v2') + await pasteImage(defaultText, encrypted, 'v2', reason) } } else { pasteTextEncode( (redPacketMetadata.ok ? redPacketPreText : null) ?? t('additional_post_box__encrypted_post_pre', { encrypted }), + reason, ) } onClose() }, - [t, whoAmI, onClose], + [t, whoAmI, onClose, reason], ) } -function pasteTextEncode(text: string) { +function pasteTextEncode(text: string, reason: 'timeline' | 'popup' | 'reply') { activatedSocialNetworkUI.automation.nativeCompositionDialog?.appendText?.(text, { recover: true, + reason, }) } -async function pasteImage(relatedTextPayload: string, encrypted: string, template: ImageTemplateTypes) { +async function pasteImage( + relatedTextPayload: string, + encrypted: string, + template: ImageTemplateTypes, + reason: 'timeline' | 'popup' | 'reply', +) { const img = await SteganographyTextPayload(template, encrypted) // Don't await this, otherwise the dialog won't disappear activatedSocialNetworkUI.automation.nativeCompositionDialog!.attachImage!(img, { recover: true, relatedTextPayload, + reason, }) } diff --git a/packages/mask/src/components/InjectedComponents/PostDialogHint.tsx b/packages/mask/src/components/InjectedComponents/PostDialogHint.tsx index ed403063b9a3..c0c897428fbe 100644 --- a/packages/mask/src/components/InjectedComponents/PostDialogHint.tsx +++ b/packages/mask/src/components/InjectedComponents/PostDialogHint.tsx @@ -26,7 +26,7 @@ export interface PostDialogHintUIProps extends withClasses<'buttonTransform' | ' const useStyles = makeStyles()((theme) => ({ button: { // TODO: is it correct? (what about twitter?) - padding: isMobileFacebook ? 0 : '8px', + padding: isMobileFacebook ? 0 : '7px', }, text: { fontSize: 14, @@ -40,6 +40,9 @@ const useStyles = makeStyles()((theme) => ({ padding: '8px 10px', borderBottom: '1px solid #dadde1', }, + tooltip: { + color: 'white', + }, })) const EntryIconButton = memo((props: PostDialogHintUIProps) => { diff --git a/packages/mask/src/social-network-adaptor/twitter.com/automation/pasteTextToComposition.ts b/packages/mask/src/social-network-adaptor/twitter.com/automation/pasteTextToComposition.ts index cba151084488..3ee47fb3b156 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/automation/pasteTextToComposition.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/automation/pasteTextToComposition.ts @@ -1,8 +1,8 @@ import { abortSignalTimeout, delay } from '@dimensiondev/kit' import { inputText, pasteText } from '@masknet/injected-script' -import { postEditorDraftContentSelector, newPostButtonSelector } from '../utils/selector' +import { newPostButtonSelector, postEditorDraftContentSelector } from '../utils/selector' import type { SocialNetworkUI } from '../../../social-network' -import { getEditorContent, hasFocus, isCompose, hasEditor } from '../utils/postBox' +import { getEditorContent, hasEditor, hasFocus, isCompose } from '../utils/postBox' import { untilElementAvailable } from '../../../utils/dom' import { isMobileTwitter } from '../utils/isMobile' import { MaskMessages } from '../../../utils/messages' @@ -19,7 +19,8 @@ export const pasteTextToCompositionTwitter: SocialNetworkUI.AutomationCapabiliti const checkSignal = () => { if (abort.aborted) throw new Error('Aborted') } - if (!isCompose() && !hasEditor()) { + + if (!isCompose() && !hasEditor() && opt?.reason !== 'reply') { // open tweet window await untilElementAvailable(newPostButtonSelector()) newPostButtonSelector().evaluate()!.click() diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialog.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialog.tsx index 325fcd147f77..9512f5e65e8a 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialog.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialog.tsx @@ -1,8 +1,8 @@ import { MutationObserverWatcher, LiveSelector } from '@dimensiondev/holoflows-kit' import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot' import { Composition } from '../../../components/CompositionDialog/Composition' -import { postEditorContentInPopupSelector, rootSelector } from '../utils/selector' import { startWatch } from '../../../utils/watcher' +import { postEditorContentInPopupSelector, rootSelector } from '../utils/selector' function renderPostDialogTo(reason: 'timeline' | 'popup', ls: LiveSelector, signal: AbortSignal) { const watcher = new MutationObserverWatcher(ls) diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx index c4b5efbb4f9a..47d75f9044f8 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx @@ -1,6 +1,6 @@ -import { useCallback } from 'react' +import { useCallback, useEffect, useState } from 'react' import { MutationObserverWatcher, LiveSelector } from '@dimensiondev/holoflows-kit' -import { postEditorInTimelineSelector, postEditorInPopupSelector } from '../utils/selector' +import { isReplyPageSelector, postEditorInPopupSelector, searchReplyToolbarSelector } from '../utils/selector' import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot' import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint' import { MaskMessages } from '../../../utils/messages' @@ -29,7 +29,8 @@ const useStyles = makeStyles()((theme) => ({ export function injectPostDialogHintAtTwitter(signal: AbortSignal) { const emptyNode = document.createElement('div') - renderPostDialogHintTo('timeline', postEditorInTimelineSelector(), signal) + renderPostDialogHintTo('timeline', searchReplyToolbarSelector(), signal) + renderPostDialogHintTo( 'popup', postEditorInPopupSelector().map((x) => (isCompose() && hasEditor() ? x : emptyNode)), @@ -49,6 +50,8 @@ function renderPostDialogHintTo(reason: 'timeline' | 'popup', ls: LiveSelecto function PostDialogHintAtTwitter({ reason }: { reason: 'timeline' | 'popup' }) { const { classes } = useStyles() const { t } = useI18N() + const [isReply, setIsReply] = useState(false) + const onHintButtonClicked = useCallback(() => { const content = sayHelloShowed[twitterBase.networkIdentifier].value ? undefined @@ -56,13 +59,23 @@ function PostDialogHintAtTwitter({ reason }: { reason: 'timeline' | 'popup' }) { t('setup_guide_say_hello_content') + t('setup_guide_say_hello_follow', { account: '@realMaskNetwork' }), ) - MaskMessages.events.requestComposition.sendToLocal({ reason, open: true, content }) + + MaskMessages.events.requestComposition.sendToLocal({ + reason: isReplyPageSelector() ? 'reply' : reason, + open: true, + content, + }) sayHelloShowed[twitterBase.networkIdentifier].value = true - }, [reason]) + }, [reason, isReplyPageSelector]) + + useEffect(() => { + setIsReply(isReplyPageSelector()) + }, [location]) + return ( diff --git a/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts b/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts index dfd3382cd18d..f7eb64e4c158 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts @@ -95,12 +95,17 @@ export const sideBarProfileSelector: () => LiveSelector = () => querySelector('[role="banner"] [role="navigation"] [aria-label="Lists"] > div') export const postEditorInTimelineSelector: () => LiveSelector = () => querySelector('[role="main"] :not(aside) > [role="progressbar"] ~ div [role="button"][aria-label]:nth-child(6)') + +export const isReplyPageSelector = () => !!location.pathname.match(/^\/\w+\/status\/\d+$/) export const postEditorDraftContentSelector = () => { if (location.pathname === '/compose/tweet') { return querySelector( '[contenteditable][aria-label][spellcheck],textarea[aria-label][spellcheck]', ) } + if (isReplyPageSelector()) { + return querySelector('div[data-testid="tweetTextarea_0"]') + } return (isCompose() ? postEditorInPopupSelector() : postEditorInTimelineSelector()).querySelector( '.public-DraftEditor-content, [contenteditable][aria-label][spellcheck]', ) @@ -264,3 +269,11 @@ export const searchTwitterAvatarNFTSelector = () => querySelector('a[href$="/nft"]').closest(1).querySelector('a div:nth-child(3) > div') export const searchTwitterAvatarNFTLinkSelector = () => querySelector('a[href$="/nft"]') + +export const searchReplyToolbarSelector = () => + querySelector('div[data-testid="primaryColumn"] div[data-testid="toolBar"]').querySelector( + 'div[data-testid="geoButton"]', + ) + +export const searchRejectReplyTextSelector = () => + querySelector('div[data-testid="tweetTextarea_0"] > div > div > div > span') diff --git a/packages/mask/src/social-network/types.ts b/packages/mask/src/social-network/types.ts index 4eccad036093..500a2acaa705 100644 --- a/packages/mask/src/social-network/types.ts +++ b/packages/mask/src/social-network/types.ts @@ -192,9 +192,11 @@ export namespace SocialNetworkUI { export interface NativeCompositionAttachImageOptions { recover?: boolean relatedTextPayload?: string + reason?: 'timeline' | 'popup' | 'reply' } export interface NativeCompositionAttachTextOptions { recover?: boolean + reason?: 'timeline' | 'popup' | 'reply' } export interface MaskCompositionDialog { open?(content: SerializableTypedMessages, options?: MaskCompositionDialogOpenOptions): void diff --git a/packages/shared-base/src/Messages/Mask.ts b/packages/shared-base/src/Messages/Mask.ts index f5cf9664052c..65f394d795d9 100644 --- a/packages/shared-base/src/Messages/Mask.ts +++ b/packages/shared-base/src/Messages/Mask.ts @@ -78,7 +78,7 @@ export interface UpdateEvent { } export interface CompositionRequest { - readonly reason: 'timeline' | 'popup' + readonly reason: 'timeline' | 'popup' | 'reply' readonly open: boolean readonly content?: SerializableTypedMessages readonly options?: {