diff --git a/package.json b/package.json
index 1ec98a6dfefb..af2aa19c799d 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,7 @@
"yarn": ">=999.0.0",
"npm": ">=999.0.0"
},
- "version": "2.29.0",
+ "version": "2.29.1",
"private": true,
"license": "AGPL-3.0-or-later",
"scripts": {
diff --git a/packages/mask/content-script/site-adaptor-infra/ui.ts b/packages/mask/content-script/site-adaptor-infra/ui.ts
index e898b735edc8..a947ec7c5915 100644
--- a/packages/mask/content-script/site-adaptor-infra/ui.ts
+++ b/packages/mask/content-script/site-adaptor-infra/ui.ts
@@ -54,8 +54,14 @@ export async function activateSiteAdaptorUIInner(ui_deferred: SiteAdaptorUI.Defe
sharedUIComponentOverwrite.value = ui.customization.sharedComponentOverwrite
}
- console.log('[Mask] Provider activated. globalThis.ui =', ui)
+ console.log(
+ '[Mask] Provider activated. globalThis.ui =',
+ ui,
+ 'globalThis.currentPosts =',
+ ui.collecting.postsProvider?.posts,
+ )
setDebugObject('ui', ui)
+ setDebugObject('currentPosts', ui.collecting.postsProvider?.posts)
const abort = new AbortController()
const { signal } = abort
diff --git a/packages/mask/content-script/site-adaptors/twitter.com/collecting/post.ts b/packages/mask/content-script/site-adaptors/twitter.com/collecting/post.ts
index e64cbaac2484..e26dbbd41650 100644
--- a/packages/mask/content-script/site-adaptors/twitter.com/collecting/post.ts
+++ b/packages/mask/content-script/site-adaptors/twitter.com/collecting/post.ts
@@ -7,7 +7,6 @@ import {
FlattenTypedMessage,
extractTextFromTypedMessage,
makeTypedMessageEmpty,
- makeTypedMessageImage,
makeTypedMessagePromise,
makeTypedMessageTuple,
makeTypedMessageTupleFromList,
@@ -191,9 +190,12 @@ function collectPostInfo(
// don't add await on this
const images = untilElementAvailable(postsImageSelector(tweetNode), 10000)
.then(() => postImagesParser(tweetNode))
- .then((urls) => {
- for (const url of urls) info.postMetadataImages.add(url)
- if (urls.length) return makeTypedMessageTupleFromList(...urls.map((x) => makeTypedMessageImage(x)))
+ .then((images) => {
+ for (const image of images) {
+ if (typeof image.image === 'string') info.postMetadataImages.add(image.image)
+ }
+ if (images.length === 1) return images[0]
+ if (images.length) return makeTypedMessageTupleFromList(...images)
return makeTypedMessageEmpty()
})
.catch(() => makeTypedMessageEmpty())
diff --git a/packages/mask/content-script/site-adaptors/twitter.com/customization/render-fragments.tsx b/packages/mask/content-script/site-adaptors/twitter.com/customization/render-fragments.tsx
index b9dacb5f8e5e..172d6bdaf2d3 100644
--- a/packages/mask/content-script/site-adaptors/twitter.com/customization/render-fragments.tsx
+++ b/packages/mask/content-script/site-adaptors/twitter.com/customization/render-fragments.tsx
@@ -3,6 +3,10 @@ import { Link } from '@mui/material'
import type { RenderFragmentsContextType } from '@masknet/typed-message-react'
import { useTagEnhancer } from '../../../../shared-ui/TypedMessageRender/Components/Text.js'
+/**
+ * For images that are rendered at the end of the post (parsed by postImagesParser), hide it in the render fragment so it won't render twice.
+ */
+export const IMAGE_RENDER_IGNORE = 'IMAGE_RENDER_IGNORE'
export const TwitterRenderFragments: RenderFragmentsContextType = {
AtLink: memo(function (props) {
const target = '/' + props.children.slice(1)
@@ -24,4 +28,9 @@ export const TwitterRenderFragments: RenderFragmentsContextType = {
const { hasMatch, ...events } = useTagEnhancer('cash', props.children.slice(1))
return
}),
+ Image: memo(function ImageFragment(props: RenderFragmentsContextType.ImageProps) {
+ return props.width === 0 || props.meta?.get(IMAGE_RENDER_IGNORE) ?
+ null
+ :
+ }),
}
diff --git a/packages/mask/content-script/site-adaptors/twitter.com/utils/fetch.ts b/packages/mask/content-script/site-adaptors/twitter.com/utils/fetch.ts
index 84e7862e4bc6..84ff7653bedc 100644
--- a/packages/mask/content-script/site-adaptors/twitter.com/utils/fetch.ts
+++ b/packages/mask/content-script/site-adaptors/twitter.com/utils/fetch.ts
@@ -15,6 +15,7 @@ import {
type TypedMessageImage,
} from '@masknet/typed-message'
import { collectNodeText, collectTwitterEmoji } from '../../../utils/index.js'
+import { IMAGE_RENDER_IGNORE } from '../customization/render-fragments.js'
/**
* Get post id from dom, including normal tweet, quoted tweet and retweet one
@@ -175,16 +176,24 @@ function getElementStyle(element: Element | null): Meta | undefined {
return undefined
}
-export async function postImagesParser(node: HTMLElement): Promise {
+export async function postImagesParser(node: HTMLElement): Promise {
const isQuotedTweet = !!node.closest('div[role="link"]')
const imgNodes = node.querySelectorAll('img[src*="twimg.com/media"]')
if (!imgNodes.length) return []
- const imgUrls = Array.from(imgNodes)
+ const tms = Array.from(imgNodes)
.filter((node) => isQuotedTweet || !node.closest('div[role="link"]'))
- .flatMap((node) => normalizeImageURL(node.getAttribute('src') ?? ''))
- .filter(Boolean)
- if (!imgUrls.length) return []
- return imgUrls
+ .flatMap((node) => {
+ let src = normalizeImageURL(node.getAttribute('src') ?? '')
+ if (Array.isArray(src)) src = src.filter(Boolean)
+ if (!src.length) return []
+ // TODO: the parser may return 2 different URLs for png and jpeg
+ return makeTypedMessageImage(
+ Array.isArray(src) ? src[0] : src,
+ { width: node.width, height: node.height },
+ new Map([[IMAGE_RENDER_IGNORE, true]]),
+ )
+ })
+ return tms
}
export function postParser(node: HTMLElement) {
diff --git a/packages/typed-message/react/src/Renderer/Core/Image.tsx b/packages/typed-message/react/src/Renderer/Core/Image.tsx
index 2f70d968ec9d..f2310ac90375 100644
--- a/packages/typed-message/react/src/Renderer/Core/Image.tsx
+++ b/packages/typed-message/react/src/Renderer/Core/Image.tsx
@@ -26,7 +26,13 @@ export const TypedMessageImageRender = memo(function TypedMessageImageRender(pro
return (
<>
-
+
{meta}
>
)
diff --git a/packages/typed-message/react/src/Renderer/utils/RenderFragments.tsx b/packages/typed-message/react/src/Renderer/utils/RenderFragments.tsx
index b7ab6e1a91d9..3e6cd2a20105 100644
--- a/packages/typed-message/react/src/Renderer/utils/RenderFragments.tsx
+++ b/packages/typed-message/react/src/Renderer/utils/RenderFragments.tsx
@@ -1,5 +1,6 @@
import { createContext, memo } from 'react'
import type { MetadataRenderProps } from '../MetadataRender.js'
+import type { Meta } from '@masknet/typed-message'
export const DefaultRenderFragments = {
Text: memo(function TextFragment(props: RenderFragmentsContextType.TextProps) {
@@ -72,6 +73,7 @@ export declare namespace RenderFragmentsContextType {
width?: number
height?: number
aspectRatio?: number
+ meta?: Meta
}
}
export const RenderFragmentsContext = createContext(DefaultRenderFragments)