Emojify and linkify group descriptions
This commit is contained in:
parent
68f1023946
commit
65a1e82857
6 changed files with 87 additions and 18 deletions
24
ts/components/GroupDescriptionText.tsx
Normal file
24
ts/components/GroupDescriptionText.tsx
Normal 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} />;
|
|
@ -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',
|
||||
})}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue