// Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; import { action } from '@storybook/addon-actions'; import type { Meta } from '@storybook/react'; import type { Props } from './MessageBody'; import { MessageBody } from './MessageBody'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; import { BodyRange } from '../../types/BodyRange'; import { generateAci } from '../../types/ServiceId'; import { RenderLocation } from './MessageTextRenderer'; const SERVICE_ID_1 = generateAci(); const SERVICE_ID_2 = generateAci(); const SERVICE_ID_3 = generateAci(); const SERVICE_ID_4 = generateAci(); const SERVICE_ID_5 = generateAci(); const SERVICE_ID_6 = generateAci(); const SERVICE_ID_7 = generateAci(); const SERVICE_ID_8 = generateAci(); const SERVICE_ID_9 = generateAci(); const SERVICE_ID_10 = generateAci(); const SERVICE_ID_11 = generateAci(); const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/MessageBody', } satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ bodyRanges: overrideProps.bodyRanges, disableJumbomoji: overrideProps.disableJumbomoji || false, disableLinks: overrideProps.disableLinks || false, direction: 'incoming', i18n, isSpoilerExpanded: overrideProps.isSpoilerExpanded || {}, onExpandSpoiler: overrideProps.onExpandSpoiler || action('onExpandSpoiler'), renderLocation: RenderLocation.Timeline, showConversation: overrideProps.showConversation || action('showConversation'), text: overrideProps.text || '', textAttachment: overrideProps.textAttachment || { pending: false, }, }); export function LinksEnabled(): JSX.Element { const props = createProps({ text: 'Check out https://www.signal.org', }); return ; } export function LinksDisabled(): JSX.Element { const props = createProps({ disableLinks: true, text: 'Check out https://www.signal.org', }); return ; } export function EmojiSizeBasedOnCount(): JSX.Element { const props = createProps(); return ( <>



); } export function JumbomojiEnabled(): JSX.Element { const props = createProps({ text: '😹', }); return ; } export function JumbomojiDisabled(): JSX.Element { const props = createProps({ disableJumbomoji: true, text: '😹', }); return ; } export function JumbomojiDisabledByText(): JSX.Element { const props = createProps({ text: 'not a jumbo kitty 😹', }); return ; } export function TextPending(): JSX.Element { const props = createProps({ text: 'Check out https://www.signal.org', textAttachment: { pending: true, }, }); return ; } export function MessageTooLong(): JSX.Element { const props = createProps({ text: 'Check out https://www.signal.org', textAttachment: { wasTooBig: true, }, }); return ; } export function Mention(): JSX.Element { const props = createProps({ bodyRanges: [ { start: 5, length: 1, mentionAci: SERVICE_ID_1, replacementText: 'Bender B Rodriguez 🤖', conversationID: 'x', }, ], text: 'Like \uFFFC once said: My story is a lot like yours, only more interesting because it involves robots', }); return ; } export function MultipleMentions(): JSX.Element { const props = createProps({ // These are intentionally in a mixed order to test how we deal with that bodyRanges: [ { start: 2, length: 1, mentionAci: SERVICE_ID_2, replacementText: 'Philip J Fry', conversationID: 'x', }, { start: 4, length: 1, mentionAci: SERVICE_ID_3, replacementText: 'Professor Farnsworth', conversationID: 'x', }, { start: 0, length: 1, mentionAci: SERVICE_ID_4, replacementText: 'Yancy Fry', conversationID: 'x', }, ], text: '\uFFFC \uFFFC \uFFFC', }); return ( <>
); } export function ComplexMessageBody(): JSX.Element { const props = createProps({ bodyRanges: [ // These are intentionally in a mixed order to test how we deal with that { start: 78, length: 1, mentionAci: SERVICE_ID_5, replacementText: 'Acid Burn', conversationID: 'x', }, { start: 80, length: 1, mentionAci: SERVICE_ID_6, replacementText: 'Cereal Killer', conversationID: 'x', }, { start: 4, length: 1, mentionAci: SERVICE_ID_6, replacementText: 'Zero Cool', conversationID: 'x', }, ], direction: 'outgoing', text: 'Hey \uFFFC\nCheck out https://www.signal.org I think you will really like it 😍\n\ncc \uFFFC \uFFFC', }); return ( <>
); } export function FormattingBasic(): JSX.Element { const [isSpoilerExpanded, setIsSpoilerExpanded] = React.useState({}); const props = createProps({ bodyRanges: [ // Abracadabra { start: 36, length: 11, style: BodyRange.Style.BOLD, }, // Open Sesame { start: 46, length: 10, style: BodyRange.Style.ITALIC, }, // This is the key! And the treasure, too, if we can only get our hands on it! { start: 357, length: 75, style: BodyRange.Style.MONOSPACE, }, // The real magic is to understand which words work, and when, and for what { start: 138, length: 73, style: BodyRange.Style.STRIKETHROUGH, }, // as if the key to the treasure is the treasure! { start: 446, length: 46, style: BodyRange.Style.SPOILER, }, { start: 110, length: 27, style: BodyRange.Style.NONE, }, ], isSpoilerExpanded, onExpandSpoiler: data => setIsSpoilerExpanded(data), text: '… It’s in words that the magic is – Abracadabra, Open Sesame, and the rest – but the magic words in one story aren’t magical in the next. The real magic is to understand which words work, and when, and for what; the trick is to learn the trick. … And those words are made from the letters of our alphabet: a couple-dozen squiggles we can draw with the pen. This is the key! And the treasure, too, if we can only get our hands on it! It’s as if – as if the key to the treasure is the treasure!', }); return ( <>
); } export function FormattingSpoiler(): JSX.Element { const [isSpoilerExpanded, setIsSpoilerExpanded] = React.useState({}); const props = createProps({ bodyRanges: [ { start: 8, 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, }, { start: 46, length: 22, style: BodyRange.Style.MONOSPACE, }, { start: 72, length: 12, style: BodyRange.Style.BOLD, }, { start: 90, length: 7, style: BodyRange.Style.ITALIC, }, { start: 54, length: 1, mentionAci: SERVICE_ID_7, conversationID: 'a', replacementText: '🅰️ Alice', }, { start: 60, length: 1, mentionAci: SERVICE_ID_8, conversationID: 'b', replacementText: '🅱️ Bob', }, ], isSpoilerExpanded, onExpandSpoiler: data => setIsSpoilerExpanded(data), text: "This is a very secret https://somewhere.com 💡 thing, \uFFFC and \uFFFC, that you shouldn't be able to read. Stay away!", }); return ( <>

null} isSpoilerExpanded={{}} />
); } export function FormattingNesting(): JSX.Element { const props = createProps({ bodyRanges: [ { start: 0, length: 40, style: BodyRange.Style.BOLD, }, { start: 0, length: 111, style: BodyRange.Style.ITALIC, }, { start: 40, length: 60, style: BodyRange.Style.STRIKETHROUGH, }, { start: 64, length: 14, style: BodyRange.Style.MONOSPACE, }, { start: 29, length: 1, mentionAci: SERVICE_ID_7, conversationID: 'a', replacementText: '🅰️ Alice', }, { start: 61, length: 1, mentionAci: SERVICE_ID_8, conversationID: 'b', replacementText: '🅱️ Bob', }, { start: 68, length: 1, mentionAci: SERVICE_ID_9, conversationID: 'c', replacementText: 'Charlie', }, { start: 80, length: 1, mentionAci: SERVICE_ID_10, conversationID: 'd', replacementText: 'Dan', }, { start: 105, length: 1, mentionAci: SERVICE_ID_11, conversationID: 'e', replacementText: 'Eve', }, ], /* eslint-disable max-len */ // m m // b bs s // i i /* eslint-enable max-len */ text: 'Italic Start and Bold Start .\uFFFC. Bold EndStrikethrough Start .\uFFFC. Mono\uFFFCpace Pop! .\uFFFC. Strikethrough End Ital\uFFFCc End', }); return ( <>
); } export function FormattingComplex(): JSX.Element { const [isSpoilerExpanded, setIsSpoilerExpanded] = React.useState({}); const text = 'Computational processes \uFFFC are abstract beings that inhabit computers. ' + 'As they evolve, processes manipulate other abstract things called data. ' + 'The evolution of a process is directed by a pattern of rules called a program. ' + 'People create programs to direct processes. In effect, we conjure the spirits of ' + 'the computer with our spells.\n\n' + 'link preceded by emoji: 🤖https://signal.org/\n\n' + 'link overlapping strikethrough: https://signal.org/ (up to "...//signal")\n\n' + 'strikethrough going through mention \uFFFC all the way'; const props = createProps({ bodyRanges: [ // mention { start: 24, length: 1, mentionAci: SERVICE_ID_3, conversationID: 'x', replacementText: '🤖 Hello', }, // bold wraps mention { start: 14, length: 31, style: BodyRange.Style.BOLD, }, // italic overlaps with bold { start: 29, length: 39, style: BodyRange.Style.ITALIC, }, // strikethrough overlaps link { start: 397, length: 29, style: BodyRange.Style.STRIKETHROUGH, }, // strikethrough over mention { start: 465, length: 31, style: BodyRange.Style.STRIKETHROUGH, }, // mention 2 { start: 491, length: 1, mentionAci: SERVICE_ID_3, conversationID: 'x', replacementText: '🤖 Hello', }, ], isSpoilerExpanded, onExpandSpoiler: data => setIsSpoilerExpanded(data), text, }); return ; } export function ZalgoText(): JSX.Element { const text = 'T̸͎̆̏̇̊̄͜ͅh̸͙̟͎̯̍͋͜͜i̸̪͚̼̜̦̲̇͒̇͝͝ś̴̡̩͙͜͝ ̴̼̣̩͂͑͠i̸̡̞̯͗s̵͙̔͛͊͑̔ ̶͇̒͝f̴̗͇͙̳͕̅̈́̏̉ò̵̲͉̤̬̖̱ȓ̶̳̫͗͝m̶̗͚̓ą̶̘̳͉̣̿̋t̴͎͎̞̤̱̅̓͝͝t̶̝͊͗é̵̛̥̔̃̀d̸̢̘̹̥̋͆ ̸̘͓͐̓̅̚ẕ̸͉̊̊͝a̴̙̖͎̥̥̅̽́͑͘ͅl̴͔̪͙͔̑̈́g̴͔̝̙̰̊͆̎͌́ǫ̵̪̤̖̖͗̑̎̿̄̎ ̵̪͈̲͇̫̼͌̌͛̚t̸̠́ẽ̴̡̺̖͘x̵͈̰̮͔̃̔͗̑̓͘t'; const props = createProps({ bodyRanges: [ // This { start: 0, length: 39, style: BodyRange.Style.BOLD, }, // is { start: 49, length: 13, style: BodyRange.Style.ITALIC, }, // formatted { start: 65, length: 73, style: BodyRange.Style.STRIKETHROUGH, }, // zalgo text { start: 145, length: 92, style: BodyRange.Style.MONOSPACE, }, ], text, }); return ; }