Changeset View
Changeset View
Standalone View
Standalone View
native/markdown/markdown-spoiler.react.js
// @flow | // @flow | ||||
import invariant from 'invariant'; | import invariant from 'invariant'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { Text } from 'react-native'; | import { Text } from 'react-native'; | ||||
import type { ReactElement } from 'lib/shared/markdown'; | import type { ReactElement } from 'lib/shared/markdown'; | ||||
import { TextMessageMarkdownContext } from '../chat/text-message-markdown-context'; | import { TextMessageMarkdownContext } from '../chat/text-message-markdown-context'; | ||||
import GestureTouchableOpacity from '../components/gesture-touchable-opacity.react'; | |||||
import { useStyles } from '../themes/colors'; | import { useStyles } from '../themes/colors'; | ||||
import { MarkdownContext } from './markdown-context'; | import { MarkdownContext } from './markdown-context'; | ||||
type MarkdownSpoilerProps = { | type MarkdownSpoilerProps = { | ||||
+spoilerIdentifier: string | number | void, | +spoilerIdentifier: string | number | void, | ||||
+text: ReactElement, | +text: ReactElement, | ||||
+children?: React.Node, | +children?: React.Node, | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | function MarkdownSpoiler(props: MarkdownSpoilerProps): React.Node { | ||||
}, [ | }, [ | ||||
isRevealed, | isRevealed, | ||||
spoilerRevealed, | spoilerRevealed, | ||||
setSpoilerRevealed, | setSpoilerRevealed, | ||||
messageKey, | messageKey, | ||||
parsedSpoilerIdentifier, | parsedSpoilerIdentifier, | ||||
]); | ]); | ||||
const memoizedSpoiler = React.useMemo(() => { | // The idea is to break down the spoiler text into individual words, | ||||
// and then encompass each word within a Text component, and | |||||
// each text component within a GestureTouchableOpacity. | |||||
// This is a way we can resolve the issue of long spoilers | |||||
// breaking the layout of the message, as the spoiler would force | |||||
// itself onto a new line if it exceeded the width of a text | |||||
// message. | |||||
// The conditional for {index !== arrayOfSpoilerText.length - 1 ? ' ' : ''} | |||||
// is a way to not add a space after the last word in the spoiler text. | |||||
// Sample text with nested markdown: | |||||
// Hey this is a spoiler **and this is bold text** | |||||
// This is converted to: | |||||
// [ | |||||
// "Hey this is a spoiler ", | |||||
// <Text style={{"fontWeight": "bold"}}>and this is bold text</Text> | |||||
// ] | |||||
// We split this ReactElement into an array of individual ReactElements | |||||
// and then map over each individual ReactElement and return it | |||||
// within a GestureTouchableOpacity. | |||||
const componentsArray: ReactElement[] = React.useMemo(() => { | |||||
return React.Children.map(text, (child, index) => { | |||||
// If the element is a string, we split it into an array of words | |||||
// and wrap each word in a Text within a GestureTouchableOpacity | |||||
if (typeof child === 'string') { | |||||
const words = child.split(' '); | |||||
ashoat: Can we split by `' '` but keep it in the resultant tokens? We then wouldn't have to append it… | |||||
rohanAuthorUnsubmitted Done Inline ActionsLet me look a little into this! It might be possible, it depends how the words and spaces will be rendered when mapped into Text components and I don't know the answer off the top of my head. rohan: Let me look a little into this! It might be possible, it depends how the words and spaces will… | |||||
rohanAuthorUnsubmitted Done Inline ActionsWe could probably do child.split(/(\s+)/) and store that into words to preserve all of the spacing. Context: https://stackoverflow.com/questions/26425637/javascript-split-string-with-white-space rohan: We could probably do `child.split(/(\s+)/)` and store that into `words` to preserve all of the… | |||||
return words.map((word, wordIndex) => { | |||||
return ( | return ( | ||||
<Text onPress={onSpoilerClick} style={styleBasedOnSpoilerState}> | <GestureTouchableOpacity | ||||
{text} | key={`${index}-${wordIndex}`} | ||||
onPress={onSpoilerClick} | |||||
> | |||||
<Text | |||||
key={`${index}-${wordIndex}`} | |||||
style={styleBasedOnSpoilerState} | |||||
> | |||||
{word} | |||||
{wordIndex !== words.length - 1 ? ' ' : ''} | |||||
</Text> | </Text> | ||||
</GestureTouchableOpacity> | |||||
); | ); | ||||
}, [onSpoilerClick, styleBasedOnSpoilerState, text]); | }); | ||||
} | |||||
// If it's a nested ReactElement, | |||||
// we return it within a GestureTouchableOpacity. | |||||
// We preserve the structure of the nested ReactElement | |||||
// since we don't want to break the markdown. | |||||
return ( | |||||
<GestureTouchableOpacity key={index} onPress={onSpoilerClick}> | |||||
<Text key={index} style={styleBasedOnSpoilerState}> | |||||
{child} | |||||
</Text> | |||||
</GestureTouchableOpacity> | |||||
); | |||||
}); | |||||
}, [text, onSpoilerClick, styleBasedOnSpoilerState]); | |||||
return memoizedSpoiler; | return componentsArray; | ||||
} | } | ||||
const unboundStyles = { | const unboundStyles = { | ||||
spoilerHidden: { | spoilerHidden: { | ||||
color: 'spoiler', | color: 'spoiler', | ||||
backgroundColor: 'spoiler', | backgroundColor: 'spoiler', | ||||
}, | }, | ||||
}; | }; | ||||
export default MarkdownSpoiler; | export default MarkdownSpoiler; |
Can we split by ' ' but keep it in the resultant tokens? We then wouldn't have to append it again later