Spoilers: Keep non-overlapping bodyRanges separate

This commit is contained in:
Scott Nonnenberg 2023-08-01 12:06:20 -07:00 committed by GitHub
parent 1c9651f557
commit 269cd9b51d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 55 additions and 20 deletions

View file

@ -278,7 +278,19 @@ export function FormattingSpoiler(): JSX.Element {
bodyRanges: [ bodyRanges: [
{ {
start: 8, 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, style: BodyRange.Style.SPOILER,
}, },
{ {
@ -322,7 +334,11 @@ export function FormattingSpoiler(): JSX.Element {
<hr /> <hr />
<MessageBody {...props} disableLinks /> <MessageBody {...props} disableLinks />
<hr /> <hr />
<MessageBody {...props} isSpoilerExpanded={{}} /> <MessageBody
{...props}
onExpandSpoiler={() => null}
isSpoilerExpanded={{}}
/>
<hr /> <hr />
<MessageBody {...props} disableLinks isSpoilerExpanded={{}} /> <MessageBody {...props} disableLinks isSpoilerExpanded={{}} />
</> </>

View file

@ -75,8 +75,22 @@ export function MessageTextRenderer({
// Create range tree, dropping bodyRanges that don't apply. Read More means truncated // Create range tree, dropping bodyRanges that don't apply. Read More means truncated
// strings. // strings.
let spoilerCount = 0;
const tree = sortedRanges.reduce<ReadonlyArray<RangeNode>>( const tree = sortedRanges.reduce<ReadonlyArray<RangeNode>>(
(acc, range) => { (acc, range) => {
if (
BodyRange.isFormatting(range) &&
range.style === BodyRange.Style.SPOILER
) {
spoilerCount += 1;
return insertRange(
{
...range,
spoilerId: spoilerCount,
},
acc
);
}
if (range.start < textLength) { if (range.start < textLength) {
return insertRange(range, acc); return insertRange(range, acc);
} }
@ -139,7 +153,7 @@ function renderNode({
if (node.isSpoiler && node.spoilerChildren?.length) { if (node.isSpoiler && node.spoilerChildren?.length) {
const isSpoilerHidden = Boolean( const isSpoilerHidden = Boolean(
node.isSpoiler && !isSpoilerExpanded[node.spoilerIndex || 0] node.isSpoiler && !isSpoilerExpanded[node.spoilerId || 0]
); );
const content = node.spoilerChildren?.map(spoilerNode => const content = node.spoilerChildren?.map(spoilerNode =>
renderNode({ renderNode({
@ -193,7 +207,7 @@ function renderNode({
event.stopPropagation(); event.stopPropagation();
onExpandSpoiler({ onExpandSpoiler({
...isSpoilerExpanded, ...isSpoilerExpanded,
[node.spoilerIndex || 0]: true, [node.spoilerId || 0]: true,
}); });
} }
} }
@ -209,7 +223,7 @@ function renderNode({
event.stopPropagation(); event.stopPropagation();
onExpandSpoiler?.({ onExpandSpoiler?.({
...isSpoilerExpanded, ...isSpoilerExpanded,
[node.spoilerIndex || 0]: true, [node.spoilerId || 0]: true,
}); });
} }
} }

View file

@ -44,6 +44,7 @@ export namespace BodyRange {
}; };
export type Formatting = { export type Formatting = {
style: Style; style: Style;
spoilerId?: number;
}; };
export type DisplayOnly = { export type DisplayOnly = {
displayStyle: DisplayStyle; displayStyle: DisplayStyle;
@ -356,8 +357,8 @@ export type DisplayNode = {
// DisplayOnly // DisplayOnly
isKeywordHighlight?: boolean; isKeywordHighlight?: boolean;
// Only for spoilers, only to represent contiguous groupings // Only for spoilers, only to make sure we honor original spoiler breakdown
spoilerIndex?: number; spoilerId?: number;
spoilerChildren?: ReadonlyArray<DisplayNode>; spoilerChildren?: ReadonlyArray<DisplayNode>;
}; };
type PartialDisplayNode = Omit< type PartialDisplayNode = Omit<
@ -381,7 +382,7 @@ function rangeToPartialNode(
return { isMonospace: true }; return { isMonospace: true };
} }
if (range.style === BodyRange.Style.SPOILER) { if (range.style === BodyRange.Style.SPOILER) {
return { isSpoiler: true }; return { isSpoiler: true, spoilerId: range.spoilerId };
} }
if (range.style === BodyRange.Style.STRIKETHROUGH) { if (range.style === BodyRange.Style.STRIKETHROUGH) {
return { isStrikethrough: true }; return { isStrikethrough: true };
@ -482,25 +483,29 @@ export function groupContiguousSpoilers(
const result: Array<DisplayNode> = []; const result: Array<DisplayNode> = [];
let spoilerContainer: DisplayNode | undefined; let spoilerContainer: DisplayNode | undefined;
let spoilerIndex = 0;
nodes.forEach(node => { nodes.forEach(node => {
if (node.isSpoiler) { if (node.isSpoiler) {
if (!spoilerContainer) { if (
spoilerContainer = { spoilerContainer &&
...node, isNumber(spoilerContainer.spoilerId) &&
spoilerIndex, spoilerContainer.spoilerId === node.spoilerId
isSpoiler: true, ) {
spoilerChildren: [],
};
spoilerIndex += 1;
result.push(spoilerContainer);
}
if (spoilerContainer) {
spoilerContainer.spoilerChildren = [ spoilerContainer.spoilerChildren = [
...(spoilerContainer.spoilerChildren || []), ...(spoilerContainer.spoilerChildren || []),
node, node,
]; ];
} else {
spoilerContainer = undefined;
}
if (!spoilerContainer) {
spoilerContainer = {
...node,
isSpoiler: true,
spoilerChildren: [node],
};
result.push(spoilerContainer);
} }
} else { } else {
spoilerContainer = undefined; spoilerContainer = undefined;