Support url-only link previews in stories
This commit is contained in:
parent
89e25fb7e3
commit
b950480d36
8 changed files with 92 additions and 37 deletions
|
@ -52,6 +52,10 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-right: 20px;
|
||||
|
||||
&--only-url {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
.module-staged-link-preview__title {
|
||||
@include font-body-1-bold;
|
||||
|
|
|
@ -14,6 +14,7 @@ import type { TextAttachmentType } from '../types/Attachment';
|
|||
import { Button, ButtonVariant } from './Button';
|
||||
import { ContextMenu } from './ContextMenu';
|
||||
import { LinkPreviewSourceType, findLinks } from '../types/LinkPreview';
|
||||
import type { MaybeGrabLinkPreviewOptionsType } from '../types/LinkPreview';
|
||||
import { Input } from './Input';
|
||||
import { Slider } from './Slider';
|
||||
import { StagedLinkPreview } from './conversation/StagedLinkPreview';
|
||||
|
@ -31,7 +32,8 @@ import { handleOutsideClick } from '../util/handleOutsideClick';
|
|||
export type PropsType = {
|
||||
debouncedMaybeGrabLinkPreview: (
|
||||
message: string,
|
||||
source: LinkPreviewSourceType
|
||||
source: LinkPreviewSourceType,
|
||||
options?: MaybeGrabLinkPreviewOptionsType
|
||||
) => unknown;
|
||||
i18n: LocalizerType;
|
||||
linkPreview?: LinkPreviewType;
|
||||
|
@ -178,7 +180,10 @@ export const TextStoryCreator = ({
|
|||
}
|
||||
debouncedMaybeGrabLinkPreview(
|
||||
linkPreviewInputValue,
|
||||
LinkPreviewSourceType.StoryCreator
|
||||
LinkPreviewSourceType.StoryCreator,
|
||||
{
|
||||
mode: 'story',
|
||||
}
|
||||
);
|
||||
}, [
|
||||
debouncedMaybeGrabLinkPreview,
|
||||
|
@ -525,12 +530,9 @@ export const TextStoryCreator = ({
|
|||
{linkPreview ? (
|
||||
<>
|
||||
<StagedLinkPreview
|
||||
domain={linkPreview.domain}
|
||||
{...linkPreview}
|
||||
i18n={i18n}
|
||||
image={linkPreview.image}
|
||||
moduleClassName="StoryCreator__link-preview"
|
||||
title={linkPreview.title}
|
||||
url={linkPreview.url}
|
||||
/>
|
||||
<Button
|
||||
className="StoryCreator__link-preview-button"
|
||||
|
|
|
@ -39,6 +39,38 @@ export const StagedLinkPreview: React.FC<Props> = ({
|
|||
moduleClassName
|
||||
);
|
||||
|
||||
let maybeContent: JSX.Element | undefined;
|
||||
if (isLoaded) {
|
||||
// No title, no description - display only domain
|
||||
if (!title && !description) {
|
||||
maybeContent = (
|
||||
<div
|
||||
className={classNames(
|
||||
getClassName('__content'),
|
||||
getClassName('__content--only-url')
|
||||
)}
|
||||
>
|
||||
<div className={getClassName('__title')}>{domain}</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
maybeContent = (
|
||||
<div className={getClassName('__content')}>
|
||||
<div className={getClassName('__title')}>{title}</div>
|
||||
{description && (
|
||||
<div className={getClassName('__description')}>
|
||||
{unescape(description)}
|
||||
</div>
|
||||
)}
|
||||
<div className={getClassName('__footer')}>
|
||||
<div className={getClassName('__location')}>{domain}</div>
|
||||
<LinkPreviewDate date={date} className={getClassName('__date')} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
|
@ -68,20 +100,7 @@ export const StagedLinkPreview: React.FC<Props> = ({
|
|||
</div>
|
||||
) : null}
|
||||
{isLoaded && !image && <div className={getClassName('__no-image')} />}
|
||||
{isLoaded ? (
|
||||
<div className={getClassName('__content')}>
|
||||
<div className={getClassName('__title')}>{title}</div>
|
||||
{description && (
|
||||
<div className={getClassName('__description')}>
|
||||
{unescape(description)}
|
||||
</div>
|
||||
)}
|
||||
<div className={getClassName('__footer')}>
|
||||
<div className={getClassName('__location')}>{domain}</div>
|
||||
<LinkPreviewDate date={date} className={getClassName('__date')} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{maybeContent}
|
||||
{onClose && (
|
||||
<button
|
||||
aria-label={i18n('close')}
|
||||
|
|
|
@ -8,6 +8,8 @@ import type {
|
|||
LinkPreviewImage,
|
||||
LinkPreviewResult,
|
||||
LinkPreviewSourceType,
|
||||
MaybeGrabLinkPreviewOptionsType,
|
||||
AddLinkPreviewOptionsType,
|
||||
} from '../types/LinkPreview';
|
||||
import type { StickerPackType as StickerPackDBType } from '../sql/Interface';
|
||||
import type { MIMEType } from '../types/MIME';
|
||||
|
@ -45,10 +47,11 @@ export const maybeGrabLinkPreview = debounce(_maybeGrabLinkPreview, 200);
|
|||
function _maybeGrabLinkPreview(
|
||||
message: string,
|
||||
source: LinkPreviewSourceType,
|
||||
caretLocation?: number
|
||||
{ caretLocation, mode = 'conversation' }: MaybeGrabLinkPreviewOptionsType = {}
|
||||
): void {
|
||||
// Don't generate link previews if user has turned them off
|
||||
if (!window.Events.getLinkPreviewSetting()) {
|
||||
// Don't generate link previews if user has turned them off. When posting a
|
||||
// story we should return minimal (url-only) link previews.
|
||||
if (!window.Events.getLinkPreviewSetting() && mode === 'conversation') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -88,7 +91,9 @@ function _maybeGrabLinkPreview(
|
|||
return;
|
||||
}
|
||||
|
||||
addLinkPreview(link, source);
|
||||
addLinkPreview(link, source, {
|
||||
disableFetch: !window.Events.getLinkPreviewSetting(),
|
||||
});
|
||||
}
|
||||
|
||||
export function resetLinkPreview(): void {
|
||||
|
@ -113,7 +118,8 @@ export function removeLinkPreview(): void {
|
|||
|
||||
export async function addLinkPreview(
|
||||
url: string,
|
||||
source: LinkPreviewSourceType
|
||||
source: LinkPreviewSourceType,
|
||||
{ disableFetch }: AddLinkPreviewOptionsType = {}
|
||||
): Promise<void> {
|
||||
if (currentlyMatchedLink === url) {
|
||||
log.warn('addLinkPreview should not be called with the same URL like this');
|
||||
|
@ -153,7 +159,17 @@ export async function addLinkPreview(
|
|||
);
|
||||
|
||||
try {
|
||||
const result = await getPreview(url, thisRequestAbortController.signal);
|
||||
let result: LinkPreviewResult | null;
|
||||
if (disableFetch) {
|
||||
result = {
|
||||
title: null,
|
||||
url,
|
||||
description: null,
|
||||
date: null,
|
||||
};
|
||||
} else {
|
||||
result = await getPreview(url, thisRequestAbortController.signal);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
log.info(
|
||||
|
@ -179,7 +195,7 @@ export async function addLinkPreview(
|
|||
type: result.image.contentType,
|
||||
});
|
||||
result.image.url = URL.createObjectURL(blob);
|
||||
} else if (!result.title) {
|
||||
} else if (!result.title && !disableFetch) {
|
||||
// A link preview isn't worth showing unless we have either a title or an image
|
||||
removeLinkPreview();
|
||||
return;
|
||||
|
@ -188,6 +204,7 @@ export async function addLinkPreview(
|
|||
window.reduxActions.linkPreviews.addLinkPreview(
|
||||
{
|
||||
...result,
|
||||
title: dropNull(result.title),
|
||||
description: dropNull(result.description),
|
||||
date: dropNull(result.date),
|
||||
domain: LinkPreview.getDomain(result.url),
|
||||
|
@ -232,6 +249,7 @@ export function getLinkPreviewForSend(message: string): Array<LinkPreviewType> {
|
|||
return {
|
||||
...item,
|
||||
image: omit(item.image, 'url'),
|
||||
title: dropNull(item.title),
|
||||
description: dropNull(item.description),
|
||||
date: dropNull(item.date),
|
||||
domain: LinkPreview.getDomain(item.url),
|
||||
|
@ -241,6 +259,7 @@ export function getLinkPreviewForSend(message: string): Array<LinkPreviewType> {
|
|||
|
||||
return {
|
||||
...item,
|
||||
title: dropNull(item.title),
|
||||
description: dropNull(item.description),
|
||||
date: dropNull(item.date),
|
||||
domain: LinkPreview.getDomain(item.url),
|
||||
|
|
|
@ -6,7 +6,10 @@ import type { ThunkAction } from 'redux-thunk';
|
|||
import type { NoopActionType } from './noop';
|
||||
import type { StateType as RootStateType } from '../reducer';
|
||||
import type { LinkPreviewType } from '../../types/message/LinkPreviews';
|
||||
import type { LinkPreviewSourceType } from '../../types/LinkPreview';
|
||||
import type {
|
||||
LinkPreviewSourceType,
|
||||
MaybeGrabLinkPreviewOptionsType,
|
||||
} from '../../types/LinkPreview';
|
||||
import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation';
|
||||
import { maybeGrabLinkPreview } from '../../services/LinkPreview';
|
||||
import { useBoundActions } from '../../hooks/useBoundActions';
|
||||
|
@ -43,10 +46,11 @@ type LinkPreviewsActionType =
|
|||
|
||||
function debouncedMaybeGrabLinkPreview(
|
||||
message: string,
|
||||
source: LinkPreviewSourceType
|
||||
source: LinkPreviewSourceType,
|
||||
options?: MaybeGrabLinkPreviewOptionsType
|
||||
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
|
||||
return dispatch => {
|
||||
maybeGrabLinkPreview(message, source);
|
||||
maybeGrabLinkPreview(message, source, options);
|
||||
|
||||
dispatch({
|
||||
type: 'NOOP',
|
||||
|
|
|
@ -128,7 +128,7 @@ export function SmartForwardMessageModal(): JSX.Element | null {
|
|||
maybeGrabLinkPreview(
|
||||
messageText,
|
||||
LinkPreviewSourceType.ForwardMessageModal,
|
||||
caretLocation
|
||||
{ caretLocation }
|
||||
);
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -15,7 +15,7 @@ export type LinkPreviewImage = AttachmentType & {
|
|||
};
|
||||
|
||||
export type LinkPreviewResult = {
|
||||
title: string;
|
||||
title: string | null;
|
||||
url: string;
|
||||
image?: LinkPreviewImage;
|
||||
description: string | null;
|
||||
|
@ -32,6 +32,15 @@ export enum LinkPreviewSourceType {
|
|||
StoryCreator,
|
||||
}
|
||||
|
||||
export type MaybeGrabLinkPreviewOptionsType = Readonly<{
|
||||
caretLocation?: number;
|
||||
mode?: 'conversation' | 'story';
|
||||
}>;
|
||||
|
||||
export type AddLinkPreviewOptionsType = Readonly<{
|
||||
disableFetch?: boolean;
|
||||
}>;
|
||||
|
||||
const linkify = LinkifyIt();
|
||||
|
||||
export function shouldPreviewHref(href: string): boolean {
|
||||
|
|
|
@ -2575,11 +2575,9 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|||
|
||||
// If we have attachments, don't add link preview
|
||||
if (!this.hasFiles({ includePending: true })) {
|
||||
maybeGrabLinkPreview(
|
||||
messageText,
|
||||
LinkPreviewSourceType.Composer,
|
||||
caretLocation
|
||||
);
|
||||
maybeGrabLinkPreview(messageText, LinkPreviewSourceType.Composer, {
|
||||
caretLocation,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue