Emojify and linkify group descriptions

This commit is contained in:
Evan Hahn 2021-06-17 12:15:51 -05:00 committed by GitHub
parent 68f1023946
commit 65a1e82857
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 18 deletions

View file

@ -0,0 +1,24 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { FunctionComponent } from 'react';
import { RenderTextCallbackType } from '../types/Util';
import { AddNewLines } from './conversation/AddNewLines';
import { Emojify } from './conversation/Emojify';
import { Linkify } from './conversation/Linkify';
type PropsType = {
text: string;
};
const renderNonLink: RenderTextCallbackType = ({ key, text }) => (
<Emojify key={key} text={text} />
);
const renderNonNewLine: RenderTextCallbackType = ({ key, text }) => (
<Linkify key={key} text={text} renderNonLink={renderNonLink} />
);
export const GroupDescriptionText: FunctionComponent<PropsType> = ({
text,
}) => <AddNewLines text={text} renderNonNewLine={renderNonNewLine} />;

View file

@ -30,3 +30,37 @@ story.add('Long', () => (
})}
/>
));
story.add('With newlines', () => (
<GroupDescription
{...createProps({
text: 'This is long\n\nSo many lines\n\nToo many lines?',
})}
/>
));
story.add('With emoji', () => (
<GroupDescription
{...createProps({
text: '🍒🍩🌭',
})}
/>
));
story.add('With link', () => (
<GroupDescription
{...createProps({
text:
'I love https://example.com and http://example.com and example.com, but not https://user:bar@example.com',
})}
/>
));
story.add('Kitchen sink', () => (
<GroupDescription
{...createProps({
text:
'🍒 https://example.com this is a long thing\nhttps://example.com on another line\nhttps://example.com',
})}
/>
));

View file

@ -1,10 +1,14 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useEffect, useRef, useState } from 'react';
import React, { useLayoutEffect, useRef, useState } from 'react';
import { Modal } from '../Modal';
import { LocalizerType } from '../../types/Util';
import { AddNewLines } from './AddNewLines';
import { GroupDescriptionText } from '../GroupDescriptionText';
// Emojification can cause the scroll height to be *slightly* larger than the client
// height, so we add a little wiggle room.
const SHOW_READ_MORE_THRESHOLD = 5;
export type PropsType = {
i18n: LocalizerType;
@ -21,13 +25,16 @@ export const GroupDescription = ({
const [hasReadMore, setHasReadMore] = useState(false);
const [showFullDescription, setShowFullDescription] = useState(false);
useEffect(() => {
useLayoutEffect(() => {
if (!textRef || !textRef.current) {
return;
}
setHasReadMore(textRef.current.scrollHeight > textRef.current.clientHeight);
}, [setHasReadMore, textRef]);
setHasReadMore(
textRef.current.scrollHeight - SHOW_READ_MORE_THRESHOLD >
textRef.current.clientHeight
);
}, [setHasReadMore, text, textRef]);
return (
<>
@ -38,11 +45,11 @@ export const GroupDescription = ({
onClose={() => setShowFullDescription(false)}
title={title}
>
<AddNewLines text={text} />
<GroupDescriptionText text={text} />
</Modal>
)}
<div className="GroupDescription__text" ref={textRef}>
{text}
<GroupDescriptionText text={text} />
</div>
{hasReadMore && (
<button

View file

@ -1,4 +1,4 @@
// Copyright 2020 Signal Messenger, LLC
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable-next-line max-classes-per-file */
@ -1368,7 +1368,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
{
type: 'description',
description:
'This is a long description.\n\nWe need a dialog to view it all!',
'This is a long description.\n\nWe need a dialog to view it all!\n\nIt has a link to https://example.com',
},
],
},
@ -1381,7 +1381,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
{
type: 'description',
description:
'This is a long description.\n\nWe need a dialog to view it all!',
'This is a long description.\n\nWe need a dialog to view it all!\n\nIt has a link to https://example.com',
},
],
},
@ -1393,7 +1393,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
{
type: 'description',
description:
'This is a long description.\n\nWe need a dialog to view it all!',
'This is a long description.\n\nWe need a dialog to view it all!\n\nIt has a link to https://example.com',
},
],
},

View file

@ -1,4 +1,4 @@
// Copyright 2020 Signal Messenger, LLC
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { ReactElement, useState } from 'react';
@ -6,7 +6,7 @@ import React, { ReactElement, useState } from 'react';
import { ReplacementValuesType } from '../../types/I18N';
import { FullJSXType, Intl } from '../Intl';
import { LocalizerType } from '../../types/Util';
import { AddNewLines } from './AddNewLines';
import { GroupDescriptionText } from '../GroupDescriptionText';
import { Button, ButtonSize, ButtonVariant } from '../Button';
import { GroupV2ChangeType, GroupV2DescriptionChangeType } from '../../groups';
@ -55,10 +55,10 @@ export function GroupV2Change(props: PropsType): ReactElement {
setIsGroupDescriptionDialogOpen,
] = useState<boolean>(false);
const groupDescriptionChange = change.details.find(
const newGroupDescription = change.details.find(
(item): item is GroupV2DescriptionChangeType =>
Boolean(item.type === 'description' && item.description)
);
)?.description;
return (
<div className="module-group-v2-change">
@ -75,7 +75,7 @@ export function GroupV2Change(props: PropsType): ReactElement {
// eslint-disable-next-line react/no-array-index-key
<div key={index}>{item}</div>
))}
{groupDescriptionChange ? (
{newGroupDescription ? (
<div className="module-group-v2-change--button-container">
<Button
size={ButtonSize.Small}
@ -86,14 +86,14 @@ export function GroupV2Change(props: PropsType): ReactElement {
</Button>
</div>
) : null}
{groupDescriptionChange && isGroupDescriptionDialogOpen ? (
{newGroupDescription && isGroupDescriptionDialogOpen ? (
<Modal
hasXButton
i18n={i18n}
title={groupName}
onClose={() => setIsGroupDescriptionDialogOpen(false)}
>
<AddNewLines text={groupDescriptionChange.description} />
<GroupDescriptionText text={newGroupDescription} />
</Modal>
) : null}
</div>

View file

@ -79,6 +79,10 @@ export const ConversationDetailsHeader: React.ComponentType<Props> = ({
<button
type="button"
onClick={ev => {
if (ev.target instanceof HTMLAnchorElement) {
return;
}
ev.preventDefault();
ev.stopPropagation();
startEditing(false);