From 1012b18b0216055249a6c724f2eea53b6f5cf64d Mon Sep 17 00:00:00 2001 From: Wukong Sun Date: Mon, 6 Jan 2025 12:17:45 +0800 Subject: [PATCH] feat: mf-6533 hide decrypt payload image --- .../DecryptedPost/DecryptedPost.tsx | 12 +++++-- .../DecryptedPost/DecryptedPostSuccess.tsx | 32 +++++++++---------- .../DecryptedPostMetadataRender.tsx | 5 +-- .../DisabledPluginSuggestion.tsx | 23 +++++++------ .../InjectedComponents/PostInspector.tsx | 28 +++++++++++----- .../defaults/inject/PostInspector.tsx | 18 +++++++---- .../twitter.com/injection/PostInspector.tsx | 19 ++++++++--- 7 files changed, 87 insertions(+), 50 deletions(-) diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx index de46e98b2ec4..a57e8ac1799b 100644 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx +++ b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPost.tsx @@ -50,6 +50,8 @@ function progressReducer( interface DecryptPostProps { whoAmI: ProfileIdentifier | null + imageDecryptedResults: Record + onImageDecrypted: (decryptedResults: Record) => void } function isProgressEqual(a: PossibleProgress, b: PossibleProgress) { if (a.type !== b.type) return false @@ -60,8 +62,7 @@ function isProgressEqual(a: PossibleProgress, b: PossibleProgress) { safeUnreachable(a) return false } -export function DecryptPost(props: DecryptPostProps) { - const { whoAmI } = props +export function DecryptPost({ whoAmI, imageDecryptedResults, onImageDecrypted }: DecryptPostProps) { const deconstructedPayload = usePostInfoDetails.hasMaskPayload() const currentPostBy = usePostInfoDetails.author() // TODO: we should read this from the payload. @@ -148,6 +149,11 @@ export function DecryptPost(props: DecryptPostProps) { iv: encodeArrayBuffer(iv), }, }) + onImageDecrypted({ + ...imageDecryptedResults, + // For now, we only want to know if an image has been decrypted. + [url]: true, + }) }, postInfo.decryptedReport, report(url), @@ -168,7 +174,7 @@ export function DecryptPost(props: DecryptPostProps) { {uniqWith(progress, (a, b) => isProgressEqual(a.progress, b.progress)) // the internal progress should not display to the end-user .filter(({ progress }) => !progress.internal) - .map(({ progress, key }, index) => ( + .map(({ progress, key }) => ( {renderProgress(progress)} ))} diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx index 97c90177aef9..32f1fe0eea97 100644 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx +++ b/packages/mask/content-script/components/InjectedComponents/DecryptedPost/DecryptedPostSuccess.tsx @@ -1,10 +1,8 @@ -import { memo, useContext, useEffect, useState } from 'react' -import { attachNextIDToProfile } from '../../../../shared-ui/index.js' -import { AdditionalContent } from '../AdditionalPostContent.js' -import { SelectProfileDialog } from '../SelectPeopleDialog.js' -import { makeStyles } from '@masknet/theme' -import { Typography, useTheme } from '@mui/material' -import type { TypedMessage } from '@masknet/typed-message' +import Services from '#services' +import { Trans } from '@lingui/macro' +import { Icons } from '@masknet/icons' +import { delay } from '@masknet/kit' +import { PostInfoContext, usePostInfoDetails, usePostInfoPostIVIdentifier } from '@masknet/plugin-infra/content-script' import { EMPTY_LIST, MaskMessages, @@ -12,18 +10,20 @@ import { type ProfileInformation, type ProfileInformationFromNextID, } from '@masknet/shared-base' -import { useAuthorDifferentMessage } from './authorDifferentMessage.js' -import { DecryptedUIPluginRendererWithSuggestion } from '../DecryptedPostMetadataRender.js' -import { PostInfoContext, usePostInfoDetails, usePostInfoPostIVIdentifier } from '@masknet/plugin-infra/content-script' +import { makeStyles } from '@masknet/theme' +import type { TypedMessage } from '@masknet/typed-message' +import { Typography, useTheme } from '@mui/material' +import { memo, useContext, useEffect, useState } from 'react' +import { attachNextIDToProfile } from '../../../../shared-ui/index.js' +import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/index.js' +import type { LazyRecipients } from '../../CompositionDialog/CompositionUI.js' import { useRecipientsList } from '../../CompositionDialog/useRecipientsList.js' import { useSelectedRecipientsList } from '../../CompositionDialog/useSelectedRecipientsList.js' -import Services from '#services' -import type { LazyRecipients } from '../../CompositionDialog/CompositionUI.js' -import { delay } from '@masknet/kit' -import { activatedSiteAdaptorUI } from '../../../site-adaptor-infra/index.js' +import { AdditionalContent } from '../AdditionalPostContent.js' +import { DecryptedUIPluginRendererWithSuggestion } from '../DecryptedPostMetadataRender.js' +import { SelectProfileDialog } from '../SelectPeopleDialog.js' +import { useAuthorDifferentMessage } from './authorDifferentMessage.js' import { RecipientsToolTip } from './RecipientsToolTip.js' -import { Icons } from '@masknet/icons' -import { Trans } from '@lingui/macro' interface DecryptPostSuccessProps { message: TypedMessage diff --git a/packages/mask/content-script/components/InjectedComponents/DecryptedPostMetadataRender.tsx b/packages/mask/content-script/components/InjectedComponents/DecryptedPostMetadataRender.tsx index 9d790bd62b05..aaf06f44d009 100644 --- a/packages/mask/content-script/components/InjectedComponents/DecryptedPostMetadataRender.tsx +++ b/packages/mask/content-script/components/InjectedComponents/DecryptedPostMetadataRender.tsx @@ -1,6 +1,7 @@ import { createInjectHooksRenderer, useActivatedPluginsSiteAdaptor } from '@masknet/plugin-infra/content-script' -import type { MetadataRenderProps } from '@masknet/typed-message-react' import { extractTextFromTypedMessage } from '@masknet/typed-message' +import type { MetadataRenderProps } from '@masknet/typed-message-react' +import { useMemo } from 'react' import { PossiblePluginSuggestionUI, useDisabledPluginSuggestionFromMeta, @@ -17,7 +18,7 @@ const Decrypted = createInjectHooksRenderer( export function DecryptedUIPluginRendererWithSuggestion(props: MetadataRenderProps) { const a = useDisabledPluginSuggestionFromMeta(props.metadata) const b = useDisabledPluginSuggestionFromPost(extractTextFromTypedMessage(props.message), []) - const suggest = Array.from(new Set(a.concat(b))) + const suggest = useMemo(() => Array.from(new Set(a.concat(b))), [a, b]) return ( <> diff --git a/packages/mask/content-script/components/InjectedComponents/DisabledPluginSuggestion.tsx b/packages/mask/content-script/components/InjectedComponents/DisabledPluginSuggestion.tsx index f661b16d4f0a..df055bb4a253 100644 --- a/packages/mask/content-script/components/InjectedComponents/DisabledPluginSuggestion.tsx +++ b/packages/mask/content-script/components/InjectedComponents/DisabledPluginSuggestion.tsx @@ -1,7 +1,5 @@ -import { type ReactNode, useCallback } from 'react' -import { useAsync } from 'react-use' -import type { Option } from 'ts-results-es' -import { useSubscription } from 'use-subscription' +import Services from '#services' +import { Trans } from '@lingui/macro' import { Icons } from '@masknet/icons' import { type Plugin, @@ -16,8 +14,10 @@ import { BooleanPreference, EMPTY_LIST } from '@masknet/shared-base' import { makeStyles, MaskLightTheme } from '@masknet/theme' import { extractTextFromTypedMessage } from '@masknet/typed-message' import { Box, type BoxProps, Button, Skeleton, Typography, useTheme } from '@mui/material' -import Services from '#services' -import { Trans } from '@lingui/macro' +import { useQuery } from '@tanstack/react-query' +import { type ReactNode, useCallback } from 'react' +import type { Option } from 'ts-results-es' +import { useSubscription } from 'use-subscription' function useDisabledPlugins() { const activated = new Set(useActivatedPluginsSiteAdaptor('any').map((x) => x.ID)) @@ -95,10 +95,13 @@ export function PossiblePluginSuggestionUISingle(props: { } }, [lackHostPermission, define]) - const { value: disabled } = useAsync(async () => { - const status = await Services.Settings.getPluginMinimalModeEnabled(define.ID) - return status === BooleanPreference.True - }, [define.ID]) + const { data: disabled } = useQuery({ + queryKey: ['system', 'plugin-disabled', define.ID], + queryFn: async () => { + const status = await Services.Settings.getPluginMinimalModeEnabled(define.ID) + return status === BooleanPreference.True + }, + }) const ButtonIcon = lackHostPermission ? Icons.Approve : Icons.Plugin const wrapperContent = content ?? diff --git a/packages/mask/content-script/components/InjectedComponents/PostInspector.tsx b/packages/mask/content-script/components/InjectedComponents/PostInspector.tsx index f2dea2814c6d..7f8337602969 100644 --- a/packages/mask/content-script/components/InjectedComponents/PostInspector.tsx +++ b/packages/mask/content-script/components/InjectedComponents/PostInspector.tsx @@ -1,15 +1,15 @@ -import { useSubscription } from 'use-subscription' import { - usePostInfoDetails, createInjectHooksRenderer, useActivatedPluginsSiteAdaptor, + usePostInfoDetails, } from '@masknet/plugin-infra/content-script' -import { DecryptPost } from './DecryptedPost/DecryptedPost.js' +import { PersistentStorages } from '@masknet/shared-base' +import { useState, type JSX } from 'react' +import { useSubscription } from 'use-subscription' import { useCurrentIdentity } from '../DataSource/useActivatedUI.js' +import { DecryptPost } from './DecryptedPost/DecryptedPost.js' import { PossiblePluginSuggestionPostInspector } from './DisabledPluginSuggestion.js' import { MaskPostExtraPluginWrapperWithPermission } from './PermissionBoundary.js' -import { PersistentStorages } from '@masknet/shared-base' -import type { JSX } from 'react' const PluginHooksRenderer = createInjectHooksRenderer( useActivatedPluginsSiteAdaptor.visibility.useNotMinimalMode, @@ -17,8 +17,12 @@ const PluginHooksRenderer = createInjectHooksRenderer( MaskPostExtraPluginWrapperWithPermission, ) +export interface PostPayloadContext { + imageDecryptedResults: Record +} + export interface PostInspectorProps { - zipPost(): void + zipPost(payloadContext: PostPayloadContext): void /** @default 'before' */ slotPosition?: 'before' | 'after' } @@ -29,9 +33,17 @@ export function PostInspector(props: PostInspectorProps) { const isDebugging = useSubscription(PersistentStorages.Settings.storage.debugging.subscription) const whoAmI = useCurrentIdentity() + const [imageDecryptedResults, setImageDecryptedResults] = useState>({}) + if (hasEncryptedPost || postImages.length) { - if (!isDebugging) props.zipPost() - return withAdditionalContent() + if (!isDebugging) props.zipPost({ imageDecryptedResults }) + return withAdditionalContent( + , + ) } return withAdditionalContent(null) function withAdditionalContent(x: JSX.Element | null) { diff --git a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostInspector.tsx b/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostInspector.tsx index 70c6d48e9776..8e5fbcc79802 100644 --- a/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostInspector.tsx +++ b/packages/mask/content-script/site-adaptor-infra/defaults/inject/PostInspector.tsx @@ -2,27 +2,31 @@ import { memo } from 'react' import type { DOMProxy } from '@dimensiondev/holoflows-kit' import { type PostInfo, PostInfoContext } from '@masknet/plugin-infra/content-script' import { attachReactTreeWithContainer } from '../../../utils/shadow-root/renderInShadowRoot.js' -import { PostInspector, type PostInspectorProps } from '../../../components/InjectedComponents/PostInspector.js' +import { + PostInspector, + type PostInspectorProps, + type PostPayloadContext, +} from '../../../components/InjectedComponents/PostInspector.js' import { noop } from 'lodash-es' export function injectPostInspectorDefault( config: InjectPostInspectorDefaultConfig = {}, props?: Pick, ) { - const PostInspectorDefault = memo(function PostInspectorDefault(props: { zipPost(): void }) { + const PostInspectorDefault = memo(function PostInspectorDefault(props: Pick) { return }) const { zipPost, injectionPoint } = config const zipPostF = zipPost || noop - return function injectPostInspector(current: PostInfo, signal: AbortSignal) { + return function injectPostInspector(postInfo: PostInfo, signal: AbortSignal) { const jsx = ( - - zipPostF(current.rootElement)} /> + + zipPostF(postInfo.rootElement, context)} /> ) - const root = attachReactTreeWithContainer(injectionPoint?.(current) ?? current.rootElement.afterShadow, { + const root = attachReactTreeWithContainer(injectionPoint?.(postInfo) ?? postInfo.rootElement.afterShadow, { key: 'post-inspector', untilVisible: true, signal, @@ -33,6 +37,6 @@ export function injectPostInspectorDefault( } interface InjectPostInspectorDefaultConfig { - zipPost?(node: DOMProxy): void + zipPost?(node: DOMProxy, postPayloadContext?: PostPayloadContext): void injectionPoint?: (postInfo: PostInfo) => ShadowRoot } diff --git a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostInspector.tsx b/packages/mask/content-script/site-adaptors/twitter.com/injection/PostInspector.tsx index 5062a831532a..327543338501 100644 --- a/packages/mask/content-script/site-adaptors/twitter.com/injection/PostInspector.tsx +++ b/packages/mask/content-script/site-adaptors/twitter.com/injection/PostInspector.tsx @@ -1,11 +1,12 @@ /* eslint @masknet/unicode-specific-set: ["error", { "only": "code" }] */ import { TwitterDecoder } from '@masknet/encryption' import type { PostInfo } from '@masknet/plugin-infra/content-script' -import { injectPostInspectorDefault } from '../../../site-adaptor-infra/defaults/inject/PostInspector.js' import { getOrAttachShadowRoot } from '@masknet/shared-base-ui' +import { isEmpty } from 'lodash-es' +import { injectPostInspectorDefault } from '../../../site-adaptor-infra/defaults/inject/PostInspector.js' export function injectPostInspectorAtTwitter(signal: AbortSignal, current: PostInfo) { - return injectPostInspectorDefault({ + const inject = injectPostInspectorDefault({ injectionPoint(postInfo) { if (postInfo.rootElement.realCurrent!.dataset.testid === 'tweetPhoto') { const root = postInfo.rootElement.realCurrent!.closest('div[aria-labelledby]') as HTMLDivElement @@ -13,7 +14,7 @@ export function injectPostInspectorAtTwitter(signal: AbortSignal, current: PostI } return postInfo.rootElement.afterShadow }, - zipPost(node) { + zipPost(node, payloadContext) { if (node.destroyed) return const contentContainer = node.current.parentElement if (!contentContainer) return @@ -34,6 +35,15 @@ export function injectPostInspectorAtTwitter(signal: AbortSignal, current: PostI // match (any space) (*/) (any space) if (span.innerText.match(/^ +\*\/ ?$/)) hideDOM(span) } + const article = contentContainer.closest('article') + if (article && !isEmpty(payloadContext?.imageDecryptedResults)) { + for (const img of article.querySelectorAll('img')) { + if (payloadContext.imageDecryptedResults[img.src]) { + const a = img.closest('a[href*="/photo/"][role=link]') + a && hideDOM(a as HTMLElement) + } + } + } const parent = content.parentElement?.nextElementSibling as HTMLElement if (parent && matches(parent.innerText)) { @@ -48,7 +58,8 @@ export function injectPostInspectorAtTwitter(signal: AbortSignal, current: PostI cardWrapper.setAttribute('aria-hidden', 'true') } }, - })(current, signal) + }) + return inject(current, signal) } function matches(input: string) { input = input.toLowerCase()