Outbound link previews
This commit is contained in:
parent
bb3ab816dd
commit
313faab774
25 changed files with 2136 additions and 641 deletions
23
ts/components/conversation/LinkPreviewDate.tsx
Normal file
23
ts/components/conversation/LinkPreviewDate.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import * as React from 'react';
|
||||
import moment, { Moment } from 'moment';
|
||||
import { isLinkPreviewDateValid } from '../../linkPreviews/isLinkPreviewDateValid';
|
||||
|
||||
interface Props {
|
||||
date: null | number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const LinkPreviewDate: React.FC<Props> = ({
|
||||
date,
|
||||
className = '',
|
||||
}: Props) => {
|
||||
const dateMoment: Moment | null = isLinkPreviewDateValid(date)
|
||||
? moment(date)
|
||||
: null;
|
||||
|
||||
return dateMoment ? (
|
||||
<time className={className} dateTime={dateMoment.toISOString()}>
|
||||
{dateMoment.format('ll')}
|
||||
</time>
|
||||
) : null;
|
||||
};
|
|
@ -5,7 +5,6 @@ import Measure from 'react-measure';
|
|||
import { drop, groupBy, orderBy, take } from 'lodash';
|
||||
import { ContextMenu, ContextMenuTrigger, MenuItem } from 'react-contextmenu';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import moment, { Moment } from 'moment';
|
||||
|
||||
import { Avatar } from '../Avatar';
|
||||
import { Spinner } from '../Spinner';
|
||||
|
@ -23,6 +22,7 @@ import {
|
|||
} from './ReactionViewer';
|
||||
import { Props as ReactionPickerProps, ReactionPicker } from './ReactionPicker';
|
||||
import { Emoji } from '../emoji/Emoji';
|
||||
import { LinkPreviewDate } from './LinkPreviewDate';
|
||||
|
||||
import {
|
||||
AttachmentType,
|
||||
|
@ -51,10 +51,8 @@ interface Trigger {
|
|||
|
||||
// Same as MIN_WIDTH in ImageGrid.tsx
|
||||
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
||||
const MINIMUM_LINK_PREVIEW_DATE = new Date(1990, 0, 1).valueOf();
|
||||
const STICKER_SIZE = 200;
|
||||
const SELECTED_TIMEOUT = 1000;
|
||||
const ONE_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
interface LinkPreviewType {
|
||||
title: string;
|
||||
|
@ -804,14 +802,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
width &&
|
||||
width >= MINIMUM_LINK_PREVIEW_IMAGE_WIDTH;
|
||||
|
||||
// Don't show old dates or dates too far in the future. This is predicated on the
|
||||
// idea that showing an invalid dates is worse than hiding valid ones.
|
||||
const maximumLinkPreviewDate = Date.now() + ONE_DAY;
|
||||
const isDateValid: boolean =
|
||||
typeof first.date === 'number' &&
|
||||
first.date > MINIMUM_LINK_PREVIEW_DATE &&
|
||||
first.date < maximumLinkPreviewDate;
|
||||
const dateMoment: Moment | null = isDateValid ? moment(first.date) : null;
|
||||
const linkPreviewDate = first.date || null;
|
||||
|
||||
return (
|
||||
<button
|
||||
|
@ -892,14 +883,10 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
<div className="module-message__link-preview__location">
|
||||
{first.domain}
|
||||
</div>
|
||||
{dateMoment && (
|
||||
<time
|
||||
className="module-message__link-preview__date"
|
||||
dateTime={dateMoment.toISOString()}
|
||||
>
|
||||
{dateMoment.format('ll')}
|
||||
</time>
|
||||
)}
|
||||
<LinkPreviewDate
|
||||
date={linkPreviewDate}
|
||||
className="module-message__link-preview__date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from 'react';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { boolean, text, withKnobs } from '@storybook/addon-knobs';
|
||||
import { boolean, date, text, withKnobs } from '@storybook/addon-knobs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { AttachmentType } from '../../types/Attachment';
|
||||
|
@ -9,6 +9,11 @@ import { setup as setupI18n } from '../../../js/modules/i18n';
|
|||
import enMessages from '../../../_locales/en/messages.json';
|
||||
import { Props, StagedLinkPreview } from './StagedLinkPreview';
|
||||
|
||||
const LONG_TITLE =
|
||||
"This is a super-sweet site. And it's got some really amazing content in store for you if you just click that link. Can you click that link for me?";
|
||||
const LONG_DESCRIPTION =
|
||||
"You're gonna love this description. Not only does it have a lot of characters, but it will also be truncated in the UI. How cool is that??";
|
||||
|
||||
const i18n = setupI18n('en', enMessages);
|
||||
|
||||
const story = storiesOf('Components/Conversation/StagedLinkPreview', module);
|
||||
|
@ -29,8 +34,20 @@ const createAttachment = (
|
|||
|
||||
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||
isLoaded: boolean('isLoaded', overrideProps.isLoaded !== false),
|
||||
title: text('title', overrideProps.title || ''),
|
||||
domain: text('domain', overrideProps.domain || ''),
|
||||
title: text(
|
||||
'title',
|
||||
typeof overrideProps.title === 'string'
|
||||
? overrideProps.title
|
||||
: 'This is a super-sweet site'
|
||||
),
|
||||
description: text(
|
||||
'description',
|
||||
typeof overrideProps.description === 'string'
|
||||
? overrideProps.description
|
||||
: 'This is a description'
|
||||
),
|
||||
date: date('date', new Date(overrideProps.date || 0)),
|
||||
domain: text('domain', overrideProps.domain || 'signal.org'),
|
||||
image: overrideProps.image,
|
||||
i18n,
|
||||
onClose: action('onClose'),
|
||||
|
@ -45,17 +62,28 @@ story.add('Loading', () => {
|
|||
});
|
||||
|
||||
story.add('No Image', () => {
|
||||
const props = createProps({
|
||||
title: 'This is a super-sweet site',
|
||||
domain: 'instagram.com',
|
||||
});
|
||||
return <StagedLinkPreview {...createProps()} />;
|
||||
});
|
||||
|
||||
return <StagedLinkPreview {...props} />;
|
||||
story.add('No Image', () => {
|
||||
return <StagedLinkPreview {...createProps()} />;
|
||||
});
|
||||
|
||||
story.add('Image', () => {
|
||||
const props = createProps({
|
||||
title: 'This is a super-sweet site',
|
||||
image: createAttachment({
|
||||
url: '/fixtures/kitten-4-112-112.jpg',
|
||||
contentType: 'image/jpeg' as MIMEType,
|
||||
}),
|
||||
});
|
||||
|
||||
return <StagedLinkPreview {...props} />;
|
||||
});
|
||||
|
||||
story.add('Image, No Title Or Description', () => {
|
||||
const props = createProps({
|
||||
title: '',
|
||||
description: '',
|
||||
domain: 'instagram.com',
|
||||
image: createAttachment({
|
||||
url: '/fixtures/kitten-4-112-112.jpg',
|
||||
|
@ -66,9 +94,17 @@ story.add('Image', () => {
|
|||
return <StagedLinkPreview {...props} />;
|
||||
});
|
||||
|
||||
story.add('Image, No Title', () => {
|
||||
story.add('No Image, Long Title With Description', () => {
|
||||
const props = createProps({
|
||||
domain: 'instagram.com',
|
||||
title: LONG_TITLE,
|
||||
});
|
||||
|
||||
return <StagedLinkPreview {...props} />;
|
||||
});
|
||||
|
||||
story.add('Image, Long Title With Description', () => {
|
||||
const props = createProps({
|
||||
title: LONG_TITLE,
|
||||
image: createAttachment({
|
||||
url: '/fixtures/kitten-4-112-112.jpg',
|
||||
contentType: 'image/jpeg' as MIMEType,
|
||||
|
@ -78,21 +114,45 @@ story.add('Image, No Title', () => {
|
|||
return <StagedLinkPreview {...props} />;
|
||||
});
|
||||
|
||||
story.add('No Image, Long Title', () => {
|
||||
story.add('No Image, Long Title Without Description', () => {
|
||||
const props = createProps({
|
||||
title:
|
||||
"This is a super-sweet site. And it's got some really amazing content in store for you if you just click that link. Can you click that link for me?",
|
||||
domain: 'instagram.com',
|
||||
title: LONG_TITLE,
|
||||
description: '',
|
||||
});
|
||||
|
||||
return <StagedLinkPreview {...props} />;
|
||||
});
|
||||
|
||||
story.add('Image, Long Title', () => {
|
||||
story.add('Image, Long Title With Description', () => {
|
||||
const props = createProps({
|
||||
title:
|
||||
"This is a super-sweet site. And it's got some really amazing content in store for you if you just click that link. Can you click that link for me?",
|
||||
domain: 'instagram.com',
|
||||
title: LONG_TITLE,
|
||||
image: createAttachment({
|
||||
url: '/fixtures/kitten-4-112-112.jpg',
|
||||
contentType: 'image/jpeg' as MIMEType,
|
||||
}),
|
||||
});
|
||||
|
||||
return <StagedLinkPreview {...props} />;
|
||||
});
|
||||
|
||||
story.add('Image, Long Title And Description', () => {
|
||||
const props = createProps({
|
||||
title: LONG_TITLE,
|
||||
description: LONG_DESCRIPTION,
|
||||
image: createAttachment({
|
||||
url: '/fixtures/kitten-4-112-112.jpg',
|
||||
contentType: 'image/jpeg' as MIMEType,
|
||||
}),
|
||||
});
|
||||
|
||||
return <StagedLinkPreview {...props} />;
|
||||
});
|
||||
|
||||
story.add('Everything: image, title, description, and date', () => {
|
||||
const props = createProps({
|
||||
title: LONG_TITLE,
|
||||
description: LONG_DESCRIPTION,
|
||||
date: Date.now(),
|
||||
image: createAttachment({
|
||||
url: '/fixtures/kitten-4-112-112.jpg',
|
||||
contentType: 'image/jpeg' as MIMEType,
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import { Image } from './Image';
|
||||
import { LinkPreviewDate } from './LinkPreviewDate';
|
||||
|
||||
import { AttachmentType, isImageAttachment } from '../../types/Attachment';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
@ -9,6 +10,8 @@ import { LocalizerType } from '../../types/Util';
|
|||
export interface Props {
|
||||
isLoaded: boolean;
|
||||
title: string;
|
||||
description: null | string;
|
||||
date: null | number;
|
||||
domain: string;
|
||||
image?: AttachmentType;
|
||||
|
||||
|
@ -16,14 +19,16 @@ export interface Props {
|
|||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export const StagedLinkPreview = ({
|
||||
export const StagedLinkPreview: React.FC<Props> = ({
|
||||
isLoaded,
|
||||
onClose,
|
||||
i18n,
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
date,
|
||||
domain,
|
||||
}: Props): JSX.Element => {
|
||||
}: Props) => {
|
||||
const isImage = image && isImageAttachment(image);
|
||||
|
||||
return (
|
||||
|
@ -54,7 +59,18 @@ export const StagedLinkPreview = ({
|
|||
{isLoaded ? (
|
||||
<div className="module-staged-link-preview__content">
|
||||
<div className="module-staged-link-preview__title">{title}</div>
|
||||
<div className="module-staged-link-preview__location">{domain}</div>
|
||||
{description && (
|
||||
<div className="module-staged-link-preview__description">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
<div className="module-staged-link-preview__footer">
|
||||
<div className="module-staged-link-preview__location">{domain}</div>
|
||||
<LinkPreviewDate
|
||||
date={date}
|
||||
className="module-message__link-preview__date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<button
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue