diff --git a/desktop/src/features/market/lib/marketTimeline.test.mjs b/desktop/src/features/market/lib/marketTimeline.test.mjs index 97ae8cc433..0e5e6d5a97 100644 --- a/desktop/src/features/market/lib/marketTimeline.test.mjs +++ b/desktop/src/features/market/lib/marketTimeline.test.mjs @@ -104,18 +104,38 @@ test("market timeline hides protocol events but leaves negotiation in its bid th bid.id, bid.id, ); - const ordinary = message("5".repeat(64), "General market note"); + const imageUrl = "https://relay.example/media/product.png"; + const productImage = message( + "5".repeat(64), + `Product image for this offer\n![image](${imageUrl})`, + ); + const imageReply = message( + "6".repeat(64), + `Product image for this offer\n![image](${imageUrl})`, + bid.id, + bid.id, + ); + const ordinary = message("7".repeat(64), "General market note"); const projection = { listingEventId: contract.id, + contract: { listing: { imageUrl } }, bids: [{ eventId: bid.id }], }; assert.deepEqual( selectMarketTimelineMessages( - [contract, bid, negotiation, nestedProtocol, ordinary], + [ + contract, + bid, + negotiation, + nestedProtocol, + productImage, + imageReply, + ordinary, + ], projection, ).map(({ id }) => id), - [negotiation.id, ordinary.id], + [negotiation.id, imageReply.id, ordinary.id], ); assert.equal(selectMarketTimelineMessages([contract, bid], null).length, 2); }); diff --git a/desktop/src/features/market/lib/marketTimeline.ts b/desktop/src/features/market/lib/marketTimeline.ts index bbd78443c1..322a06b318 100644 --- a/desktop/src/features/market/lib/marketTimeline.ts +++ b/desktop/src/features/market/lib/marketTimeline.ts @@ -13,6 +13,20 @@ export function isMarketProtocolMessage( return parseMarketEnvelope(message.body) !== null; } +const MARKET_PRODUCT_IMAGE_LABEL = "Product image for this offer"; + +function isRedundantMarketImageMessage( + message: Pick, + projection: MarketProjection, +): boolean { + const imageUrl = projection.contract.listing.imageUrl; + if (!imageUrl || message.parentId != null) return false; + return ( + message.body.trim() === + `${MARKET_PRODUCT_IMAGE_LABEL}\n![image](${imageUrl})` + ); +} + export function marketTimelineAnchor( messages: TimelineMessage[], isChannelCreated: (message: TimelineMessage) => boolean, @@ -42,7 +56,11 @@ export function selectMarketTimelineMessages( projection: MarketProjection | null, ): TimelineMessage[] { if (!projection) return messages; - return messages.filter((message) => !isMarketProtocolMessage(message)); + return messages.filter( + (message) => + !isMarketProtocolMessage(message) && + !isRedundantMarketImageMessage(message, projection), + ); } export function useMarketTimelineMessages(messages: TimelineMessage[]) { diff --git a/desktop/src/features/market/ui/MarketChannelIntro.test.mjs b/desktop/src/features/market/ui/MarketChannelIntro.test.mjs index b852ada8f6..639ac4cbdf 100644 --- a/desktop/src/features/market/ui/MarketChannelIntro.test.mjs +++ b/desktop/src/features/market/ui/MarketChannelIntro.test.mjs @@ -70,6 +70,11 @@ test("the contract card leaves horizontal alignment to the board", () => { }), ); const sectionClass = html.match(/
-
+
{scenario.imageUrl ? ( {scenario.title} )}
-
-
- {scenario.eyebrow} -
-

+
+

{scenario.title}