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
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// 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 { Modal } from '../Modal';
|
||||||
import { LocalizerType } from '../../types/Util';
|
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 = {
|
export type PropsType = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
@ -21,13 +25,16 @@ export const GroupDescription = ({
|
||||||
const [hasReadMore, setHasReadMore] = useState(false);
|
const [hasReadMore, setHasReadMore] = useState(false);
|
||||||
const [showFullDescription, setShowFullDescription] = useState(false);
|
const [showFullDescription, setShowFullDescription] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!textRef || !textRef.current) {
|
if (!textRef || !textRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setHasReadMore(textRef.current.scrollHeight > textRef.current.clientHeight);
|
setHasReadMore(
|
||||||
}, [setHasReadMore, textRef]);
|
textRef.current.scrollHeight - SHOW_READ_MORE_THRESHOLD >
|
||||||
|
textRef.current.clientHeight
|
||||||
|
);
|
||||||
|
}, [setHasReadMore, text, textRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -38,11 +45,11 @@ export const GroupDescription = ({
|
||||||
onClose={() => setShowFullDescription(false)}
|
onClose={() => setShowFullDescription(false)}
|
||||||
title={title}
|
title={title}
|
||||||
>
|
>
|
||||||
<AddNewLines text={text} />
|
<GroupDescriptionText text={text} />
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
<div className="GroupDescription__text" ref={textRef}>
|
<div className="GroupDescription__text" ref={textRef}>
|
||||||
{text}
|
<GroupDescriptionText text={text} />
|
||||||
</div>
|
</div>
|
||||||
{hasReadMore && (
|
{hasReadMore && (
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020-2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* eslint-disable-next-line max-classes-per-file */
|
/* eslint-disable-next-line max-classes-per-file */
|
||||||
|
@ -1368,7 +1368,7 @@ storiesOf('Components/Conversation/GroupV2Change', module)
|
||||||
{
|
{
|
||||||
type: 'description',
|
type: 'description',
|
||||||
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',
|
type: 'description',
|
||||||
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',
|
type: 'description',
|
||||||
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
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, { ReactElement, useState } from 'react';
|
import React, { ReactElement, useState } from 'react';
|
||||||
|
@ -6,7 +6,7 @@ import React, { ReactElement, useState } from 'react';
|
||||||
import { ReplacementValuesType } from '../../types/I18N';
|
import { ReplacementValuesType } from '../../types/I18N';
|
||||||
import { FullJSXType, Intl } from '../Intl';
|
import { FullJSXType, Intl } from '../Intl';
|
||||||
import { LocalizerType } from '../../types/Util';
|
import { LocalizerType } from '../../types/Util';
|
||||||
import { AddNewLines } from './AddNewLines';
|
import { GroupDescriptionText } from '../GroupDescriptionText';
|
||||||
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
import { Button, ButtonSize, ButtonVariant } from '../Button';
|
||||||
|
|
||||||
import { GroupV2ChangeType, GroupV2DescriptionChangeType } from '../../groups';
|
import { GroupV2ChangeType, GroupV2DescriptionChangeType } from '../../groups';
|
||||||
|
@ -55,10 +55,10 @@ export function GroupV2Change(props: PropsType): ReactElement {
|
||||||
setIsGroupDescriptionDialogOpen,
|
setIsGroupDescriptionDialogOpen,
|
||||||
] = useState<boolean>(false);
|
] = useState<boolean>(false);
|
||||||
|
|
||||||
const groupDescriptionChange = change.details.find(
|
const newGroupDescription = change.details.find(
|
||||||
(item): item is GroupV2DescriptionChangeType =>
|
(item): item is GroupV2DescriptionChangeType =>
|
||||||
Boolean(item.type === 'description' && item.description)
|
Boolean(item.type === 'description' && item.description)
|
||||||
);
|
)?.description;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-group-v2-change">
|
<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
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
<div key={index}>{item}</div>
|
<div key={index}>{item}</div>
|
||||||
))}
|
))}
|
||||||
{groupDescriptionChange ? (
|
{newGroupDescription ? (
|
||||||
<div className="module-group-v2-change--button-container">
|
<div className="module-group-v2-change--button-container">
|
||||||
<Button
|
<Button
|
||||||
size={ButtonSize.Small}
|
size={ButtonSize.Small}
|
||||||
|
@ -86,14 +86,14 @@ export function GroupV2Change(props: PropsType): ReactElement {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{groupDescriptionChange && isGroupDescriptionDialogOpen ? (
|
{newGroupDescription && isGroupDescriptionDialogOpen ? (
|
||||||
<Modal
|
<Modal
|
||||||
hasXButton
|
hasXButton
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
title={groupName}
|
title={groupName}
|
||||||
onClose={() => setIsGroupDescriptionDialogOpen(false)}
|
onClose={() => setIsGroupDescriptionDialogOpen(false)}
|
||||||
>
|
>
|
||||||
<AddNewLines text={groupDescriptionChange.description} />
|
<GroupDescriptionText text={newGroupDescription} />
|
||||||
</Modal>
|
</Modal>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -79,6 +79,10 @@ export const ConversationDetailsHeader: React.ComponentType<Props> = ({
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={ev => {
|
onClick={ev => {
|
||||||
|
if (ev.target instanceof HTMLAnchorElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
startEditing(false);
|
startEditing(false);
|
||||||
|
|
Loading…
Add table
Reference in a new issue