diff --git a/ts/components/conversation/MessageBody.stories.tsx b/ts/components/conversation/MessageBody.stories.tsx
index 916a6bfd5..7d6a750b7 100644
--- a/ts/components/conversation/MessageBody.stories.tsx
+++ b/ts/components/conversation/MessageBody.stories.tsx
@@ -278,7 +278,19 @@ export function FormattingSpoiler(): JSX.Element {
bodyRanges: [
{
start: 8,
- length: 89,
+ length: 60,
+ style: BodyRange.Style.SPOILER,
+ },
+ // This is touching, but not overlapping; they should not reveal together
+ {
+ start: 68,
+ length: 29,
+ style: BodyRange.Style.SPOILER,
+ },
+ // Note: in overlaps, the last spoiler wins
+ {
+ start: 94,
+ length: 6,
style: BodyRange.Style.SPOILER,
},
{
@@ -322,7 +334,11 @@ export function FormattingSpoiler(): JSX.Element {
-
+ null}
+ isSpoilerExpanded={{}}
+ />
>
diff --git a/ts/components/conversation/MessageTextRenderer.tsx b/ts/components/conversation/MessageTextRenderer.tsx
index a4be2200c..f815a66bb 100644
--- a/ts/components/conversation/MessageTextRenderer.tsx
+++ b/ts/components/conversation/MessageTextRenderer.tsx
@@ -75,8 +75,22 @@ export function MessageTextRenderer({
// Create range tree, dropping bodyRanges that don't apply. Read More means truncated
// strings.
+ let spoilerCount = 0;
const tree = sortedRanges.reduce>(
(acc, range) => {
+ if (
+ BodyRange.isFormatting(range) &&
+ range.style === BodyRange.Style.SPOILER
+ ) {
+ spoilerCount += 1;
+ return insertRange(
+ {
+ ...range,
+ spoilerId: spoilerCount,
+ },
+ acc
+ );
+ }
if (range.start < textLength) {
return insertRange(range, acc);
}
@@ -139,7 +153,7 @@ function renderNode({
if (node.isSpoiler && node.spoilerChildren?.length) {
const isSpoilerHidden = Boolean(
- node.isSpoiler && !isSpoilerExpanded[node.spoilerIndex || 0]
+ node.isSpoiler && !isSpoilerExpanded[node.spoilerId || 0]
);
const content = node.spoilerChildren?.map(spoilerNode =>
renderNode({
@@ -193,7 +207,7 @@ function renderNode({
event.stopPropagation();
onExpandSpoiler({
...isSpoilerExpanded,
- [node.spoilerIndex || 0]: true,
+ [node.spoilerId || 0]: true,
});
}
}
@@ -209,7 +223,7 @@ function renderNode({
event.stopPropagation();
onExpandSpoiler?.({
...isSpoilerExpanded,
- [node.spoilerIndex || 0]: true,
+ [node.spoilerId || 0]: true,
});
}
}
diff --git a/ts/types/BodyRange.ts b/ts/types/BodyRange.ts
index 09dc1e039..46b80ef28 100644
--- a/ts/types/BodyRange.ts
+++ b/ts/types/BodyRange.ts
@@ -44,6 +44,7 @@ export namespace BodyRange {
};
export type Formatting = {
style: Style;
+ spoilerId?: number;
};
export type DisplayOnly = {
displayStyle: DisplayStyle;
@@ -356,8 +357,8 @@ export type DisplayNode = {
// DisplayOnly
isKeywordHighlight?: boolean;
- // Only for spoilers, only to represent contiguous groupings
- spoilerIndex?: number;
+ // Only for spoilers, only to make sure we honor original spoiler breakdown
+ spoilerId?: number;
spoilerChildren?: ReadonlyArray;
};
type PartialDisplayNode = Omit<
@@ -381,7 +382,7 @@ function rangeToPartialNode(
return { isMonospace: true };
}
if (range.style === BodyRange.Style.SPOILER) {
- return { isSpoiler: true };
+ return { isSpoiler: true, spoilerId: range.spoilerId };
}
if (range.style === BodyRange.Style.STRIKETHROUGH) {
return { isStrikethrough: true };
@@ -482,25 +483,29 @@ export function groupContiguousSpoilers(
const result: Array = [];
let spoilerContainer: DisplayNode | undefined;
- let spoilerIndex = 0;
nodes.forEach(node => {
if (node.isSpoiler) {
- if (!spoilerContainer) {
- spoilerContainer = {
- ...node,
- spoilerIndex,
- isSpoiler: true,
- spoilerChildren: [],
- };
- spoilerIndex += 1;
- result.push(spoilerContainer);
- }
- if (spoilerContainer) {
+ if (
+ spoilerContainer &&
+ isNumber(spoilerContainer.spoilerId) &&
+ spoilerContainer.spoilerId === node.spoilerId
+ ) {
spoilerContainer.spoilerChildren = [
...(spoilerContainer.spoilerChildren || []),
node,
];
+ } else {
+ spoilerContainer = undefined;
+ }
+
+ if (!spoilerContainer) {
+ spoilerContainer = {
+ ...node,
+ isSpoiler: true,
+ spoilerChildren: [node],
+ };
+ result.push(spoilerContainer);
}
} else {
spoilerContainer = undefined;