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
		Add a link
		
	
		Reference in a new issue