Hide long message attachments in quotes
This commit is contained in:
parent
6f404648d7
commit
afe135df0c
7 changed files with 76 additions and 42 deletions
|
@ -1030,7 +1030,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
onClick={clickHandler}
|
onClick={clickHandler}
|
||||||
text={quote.text}
|
text={quote.text}
|
||||||
attachment={quote.attachment}
|
rawAttachment={quote.attachment}
|
||||||
isIncoming={direction === 'incoming'}
|
isIncoming={direction === 'incoming'}
|
||||||
authorPhoneNumber={quote.authorPhoneNumber}
|
authorPhoneNumber={quote.authorPhoneNumber}
|
||||||
authorProfileName={quote.authorProfileName}
|
authorProfileName={quote.authorProfileName}
|
||||||
|
|
|
@ -10,7 +10,13 @@ import { storiesOf } from '@storybook/react';
|
||||||
import { Colors } from '../../types/Colors';
|
import { Colors } from '../../types/Colors';
|
||||||
import { pngUrl } from '../../storybook/Fixtures';
|
import { pngUrl } from '../../storybook/Fixtures';
|
||||||
import { Message, Props as MessagesProps } from './Message';
|
import { Message, Props as MessagesProps } from './Message';
|
||||||
import { AUDIO_MP3, IMAGE_PNG, MIMEType, VIDEO_MP4 } from '../../types/MIME';
|
import {
|
||||||
|
AUDIO_MP3,
|
||||||
|
IMAGE_PNG,
|
||||||
|
LONG_MESSAGE,
|
||||||
|
MIMEType,
|
||||||
|
VIDEO_MP4,
|
||||||
|
} from '../../types/MIME';
|
||||||
import { Props, Quote } from './Quote';
|
import { Props, Quote } from './Quote';
|
||||||
import { setup as setupI18n } from '../../../js/modules/i18n';
|
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||||
import enMessages from '../../../_locales/en/messages.json';
|
import enMessages from '../../../_locales/en/messages.json';
|
||||||
|
@ -62,13 +68,13 @@ const defaultMessageProps: MessagesProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderInMessage = ({
|
const renderInMessage = ({
|
||||||
attachment,
|
|
||||||
authorColor,
|
authorColor,
|
||||||
authorName,
|
authorName,
|
||||||
authorPhoneNumber,
|
authorPhoneNumber,
|
||||||
authorProfileName,
|
authorProfileName,
|
||||||
authorTitle,
|
authorTitle,
|
||||||
isFromMe,
|
isFromMe,
|
||||||
|
rawAttachment,
|
||||||
referencedMessageNotFound,
|
referencedMessageNotFound,
|
||||||
text: quoteText,
|
text: quoteText,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
@ -76,7 +82,6 @@ const renderInMessage = ({
|
||||||
...defaultMessageProps,
|
...defaultMessageProps,
|
||||||
authorColor,
|
authorColor,
|
||||||
quote: {
|
quote: {
|
||||||
attachment,
|
|
||||||
authorId: 'an-author',
|
authorId: 'an-author',
|
||||||
authorColor,
|
authorColor,
|
||||||
authorName,
|
authorName,
|
||||||
|
@ -84,6 +89,7 @@ const renderInMessage = ({
|
||||||
authorProfileName,
|
authorProfileName,
|
||||||
authorTitle,
|
authorTitle,
|
||||||
isFromMe,
|
isFromMe,
|
||||||
|
rawAttachment,
|
||||||
referencedMessageNotFound,
|
referencedMessageNotFound,
|
||||||
sentAt: Date.now() - 30 * 1000,
|
sentAt: Date.now() - 30 * 1000,
|
||||||
text: quoteText,
|
text: quoteText,
|
||||||
|
@ -100,7 +106,6 @@ const renderInMessage = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
attachment: overrideProps.attachment || undefined,
|
|
||||||
authorColor: overrideProps.authorColor || 'green',
|
authorColor: overrideProps.authorColor || 'green',
|
||||||
authorName: text('authorName', overrideProps.authorName || ''),
|
authorName: text('authorName', overrideProps.authorName || ''),
|
||||||
authorPhoneNumber: text(
|
authorPhoneNumber: text(
|
||||||
|
@ -117,6 +122,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
isIncoming: boolean('isIncoming', overrideProps.isIncoming || false),
|
isIncoming: boolean('isIncoming', overrideProps.isIncoming || false),
|
||||||
onClick: action('onClick'),
|
onClick: action('onClick'),
|
||||||
onClose: action('onClose'),
|
onClose: action('onClose'),
|
||||||
|
rawAttachment: overrideProps.rawAttachment || undefined,
|
||||||
referencedMessageNotFound: boolean(
|
referencedMessageNotFound: boolean(
|
||||||
'referencedMessageNotFound',
|
'referencedMessageNotFound',
|
||||||
overrideProps.referencedMessageNotFound || false
|
overrideProps.referencedMessageNotFound || false
|
||||||
|
@ -186,7 +192,7 @@ story.add('Content Above', () => {
|
||||||
|
|
||||||
story.add('Image Only', () => {
|
story.add('Image Only', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: IMAGE_PNG,
|
contentType: IMAGE_PNG,
|
||||||
fileName: 'sax.png',
|
fileName: 'sax.png',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -203,7 +209,7 @@ story.add('Image Only', () => {
|
||||||
});
|
});
|
||||||
story.add('Image Attachment', () => {
|
story.add('Image Attachment', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: IMAGE_PNG,
|
contentType: IMAGE_PNG,
|
||||||
fileName: 'sax.png',
|
fileName: 'sax.png',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -219,7 +225,7 @@ story.add('Image Attachment', () => {
|
||||||
|
|
||||||
story.add('Image Attachment w/o Thumbnail', () => {
|
story.add('Image Attachment w/o Thumbnail', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: IMAGE_PNG,
|
contentType: IMAGE_PNG,
|
||||||
fileName: 'sax.png',
|
fileName: 'sax.png',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -231,7 +237,7 @@ story.add('Image Attachment w/o Thumbnail', () => {
|
||||||
|
|
||||||
story.add('Video Only', () => {
|
story.add('Video Only', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: VIDEO_MP4,
|
contentType: VIDEO_MP4,
|
||||||
fileName: 'great-video.mp4',
|
fileName: 'great-video.mp4',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -249,7 +255,7 @@ story.add('Video Only', () => {
|
||||||
|
|
||||||
story.add('Video Attachment', () => {
|
story.add('Video Attachment', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: VIDEO_MP4,
|
contentType: VIDEO_MP4,
|
||||||
fileName: 'great-video.mp4',
|
fileName: 'great-video.mp4',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -265,7 +271,7 @@ story.add('Video Attachment', () => {
|
||||||
|
|
||||||
story.add('Video Attachment w/o Thumbnail', () => {
|
story.add('Video Attachment w/o Thumbnail', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: VIDEO_MP4,
|
contentType: VIDEO_MP4,
|
||||||
fileName: 'great-video.mp4',
|
fileName: 'great-video.mp4',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -277,7 +283,7 @@ story.add('Video Attachment w/o Thumbnail', () => {
|
||||||
|
|
||||||
story.add('Audio Only', () => {
|
story.add('Audio Only', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: AUDIO_MP3,
|
contentType: AUDIO_MP3,
|
||||||
fileName: 'great-video.mp3',
|
fileName: 'great-video.mp3',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -291,7 +297,7 @@ story.add('Audio Only', () => {
|
||||||
|
|
||||||
story.add('Audio Attachment', () => {
|
story.add('Audio Attachment', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: AUDIO_MP3,
|
contentType: AUDIO_MP3,
|
||||||
fileName: 'great-video.mp3',
|
fileName: 'great-video.mp3',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -303,7 +309,7 @@ story.add('Audio Attachment', () => {
|
||||||
|
|
||||||
story.add('Voice Message Only', () => {
|
story.add('Voice Message Only', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: AUDIO_MP3,
|
contentType: AUDIO_MP3,
|
||||||
fileName: 'great-video.mp3',
|
fileName: 'great-video.mp3',
|
||||||
isVoiceMessage: true,
|
isVoiceMessage: true,
|
||||||
|
@ -317,7 +323,7 @@ story.add('Voice Message Only', () => {
|
||||||
|
|
||||||
story.add('Voice Message Attachment', () => {
|
story.add('Voice Message Attachment', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: AUDIO_MP3,
|
contentType: AUDIO_MP3,
|
||||||
fileName: 'great-video.mp3',
|
fileName: 'great-video.mp3',
|
||||||
isVoiceMessage: true,
|
isVoiceMessage: true,
|
||||||
|
@ -329,7 +335,7 @@ story.add('Voice Message Attachment', () => {
|
||||||
|
|
||||||
story.add('Other File Only', () => {
|
story.add('Other File Only', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: 'application/json' as MIMEType,
|
contentType: 'application/json' as MIMEType,
|
||||||
fileName: 'great-data.json',
|
fileName: 'great-data.json',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -343,7 +349,7 @@ story.add('Other File Only', () => {
|
||||||
|
|
||||||
story.add('Other File Attachment', () => {
|
story.add('Other File Attachment', () => {
|
||||||
const props = createProps({
|
const props = createProps({
|
||||||
attachment: {
|
rawAttachment: {
|
||||||
contentType: 'application/json' as MIMEType,
|
contentType: 'application/json' as MIMEType,
|
||||||
fileName: 'great-data.json',
|
fileName: 'great-data.json',
|
||||||
isVoiceMessage: false,
|
isVoiceMessage: false,
|
||||||
|
@ -353,6 +359,18 @@ story.add('Other File Attachment', () => {
|
||||||
return <Quote {...props} />;
|
return <Quote {...props} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
story.add('Long message attachment (should be hidden)', () => {
|
||||||
|
const props = createProps({
|
||||||
|
rawAttachment: {
|
||||||
|
contentType: LONG_MESSAGE,
|
||||||
|
fileName: 'signal-long-message-123.txt',
|
||||||
|
isVoiceMessage: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return <Quote {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
story.add('No Close Button', () => {
|
story.add('No Close Button', () => {
|
||||||
const props = createProps();
|
const props = createProps();
|
||||||
props.onClose = undefined;
|
props.onClose = undefined;
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { ContactName } from './ContactName';
|
||||||
import { getTextWithMentions } from '../../util/getTextWithMentions';
|
import { getTextWithMentions } from '../../util/getTextWithMentions';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
attachment?: QuotedAttachmentType;
|
|
||||||
authorTitle: string;
|
authorTitle: string;
|
||||||
authorPhoneNumber?: string;
|
authorPhoneNumber?: string;
|
||||||
authorProfileName?: string;
|
authorProfileName?: string;
|
||||||
|
@ -29,6 +28,7 @@ export type Props = {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
text: string;
|
text: string;
|
||||||
|
rawAttachment?: QuotedAttachmentType;
|
||||||
referencedMessageNotFound: boolean;
|
referencedMessageNotFound: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,13 +55,22 @@ function validateQuote(quote: Props): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (quote.attachment) {
|
if (quote.rawAttachment) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Long message attachments should not be shown.
|
||||||
|
function getAttachment(
|
||||||
|
rawAttachment: undefined | QuotedAttachmentType
|
||||||
|
): undefined | QuotedAttachmentType {
|
||||||
|
return rawAttachment && !MIME.isLongMessage(rawAttachment.contentType)
|
||||||
|
? rawAttachment
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function getObjectUrl(thumbnail: Attachment | undefined): string | undefined {
|
function getObjectUrl(thumbnail: Attachment | undefined): string | undefined {
|
||||||
if (thumbnail && thumbnail.objectUrl) {
|
if (thumbnail && thumbnail.objectUrl) {
|
||||||
return thumbnail.objectUrl;
|
return thumbnail.objectUrl;
|
||||||
|
@ -173,7 +182,8 @@ export class Quote extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderGenericFile(): JSX.Element | null {
|
public renderGenericFile(): JSX.Element | null {
|
||||||
const { attachment, isIncoming } = this.props;
|
const { rawAttachment, isIncoming } = this.props;
|
||||||
|
const attachment = getAttachment(rawAttachment);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -205,8 +215,9 @@ export class Quote extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderIconContainer(): JSX.Element | null {
|
public renderIconContainer(): JSX.Element | null {
|
||||||
const { attachment } = this.props;
|
const { rawAttachment } = this.props;
|
||||||
const { imageBroken } = this.state;
|
const { imageBroken } = this.state;
|
||||||
|
const attachment = getAttachment(rawAttachment);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -233,7 +244,7 @@ export class Quote extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderText(): JSX.Element | null {
|
public renderText(): JSX.Element | null {
|
||||||
const { bodyRanges, i18n, text, attachment, isIncoming } = this.props;
|
const { bodyRanges, i18n, text, rawAttachment, isIncoming } = this.props;
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
const quoteText = bodyRanges
|
const quoteText = bodyRanges
|
||||||
|
@ -253,6 +264,8 @@ export class Quote extends React.Component<Props, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const attachment = getAttachment(rawAttachment);
|
||||||
|
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,8 +132,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
attachment: typeof window.WhatIsThis
|
attachment: typeof window.WhatIsThis
|
||||||
) => typeof window.WhatIsThis;
|
) => typeof window.WhatIsThis;
|
||||||
|
|
||||||
static LONG_MESSAGE_CONTENT_TYPE: string;
|
|
||||||
|
|
||||||
CURRENT_PROTOCOL_VERSION?: number;
|
CURRENT_PROTOCOL_VERSION?: number;
|
||||||
|
|
||||||
// Set when sending some sync messages, so we get the functionality of
|
// Set when sending some sync messages, so we get the functionality of
|
||||||
|
@ -2580,10 +2578,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
this.get('attachments') || [];
|
this.get('attachments') || [];
|
||||||
|
|
||||||
const hasLongMessageAttachments = attachments.some(attachment => {
|
const hasLongMessageAttachments = attachments.some(attachment => {
|
||||||
return (
|
return MIME.isLongMessage(attachment.contentType);
|
||||||
attachment.contentType ===
|
|
||||||
window.Whisper.Message.LONG_MESSAGE_CONTENT_TYPE
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasLongMessageAttachments) {
|
if (hasLongMessageAttachments) {
|
||||||
|
@ -2605,9 +2600,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
const [longMessageAttachments, normalAttachments] = _.partition(
|
const [longMessageAttachments, normalAttachments] = _.partition(
|
||||||
attachments,
|
attachments,
|
||||||
attachment =>
|
attachment => MIME.isLongMessage(attachment.contentType)
|
||||||
attachment.contentType ===
|
|
||||||
window.Whisper.Message.LONG_MESSAGE_CONTENT_TYPE
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (longMessageAttachments.length > 0) {
|
if (longMessageAttachments.length > 0) {
|
||||||
|
@ -2698,11 +2691,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
} attachment downloads for message ${this.idForLogging()}`
|
} attachment downloads for message ${this.idForLogging()}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const [longMessageAttachments, normalAttachments] = _.partition(
|
const [
|
||||||
attachmentsToQueue,
|
longMessageAttachments,
|
||||||
attachment =>
|
normalAttachments,
|
||||||
attachment.contentType ===
|
] = _.partition(attachmentsToQueue, attachment =>
|
||||||
window.Whisper.Message.LONG_MESSAGE_CONTENT_TYPE
|
MIME.isLongMessage(attachment.contentType)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (longMessageAttachments.length > 1) {
|
if (longMessageAttachments.length > 1) {
|
||||||
|
@ -3975,9 +3968,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
window.Whisper.Message = MessageModel as typeof window.WhatIsThis;
|
window.Whisper.Message = MessageModel as typeof window.WhatIsThis;
|
||||||
|
|
||||||
// Receive will be enabled before we enable send
|
|
||||||
window.Whisper.Message.LONG_MESSAGE_CONTENT_TYPE = 'text/x-signal-plain';
|
|
||||||
|
|
||||||
window.Whisper.Message.getLongMessageAttachment = ({
|
window.Whisper.Message.getLongMessageAttachment = ({
|
||||||
body,
|
body,
|
||||||
attachments,
|
attachments,
|
||||||
|
@ -3992,7 +3982,7 @@ window.Whisper.Message.getLongMessageAttachment = ({
|
||||||
|
|
||||||
const data = bytesFromString(body);
|
const data = bytesFromString(body);
|
||||||
const attachment = {
|
const attachment = {
|
||||||
contentType: window.Whisper.Message.LONG_MESSAGE_CONTENT_TYPE,
|
contentType: MIME.LONG_MESSAGE,
|
||||||
fileName: `long-message-${now}.txt`,
|
fileName: `long-message-${now}.txt`,
|
||||||
data,
|
data,
|
||||||
size: data.byteLength,
|
size: data.byteLength,
|
||||||
|
|
|
@ -16,4 +16,15 @@ describe('MIME', () => {
|
||||||
assert.isFalse(MIME.isGif('text/plain'));
|
assert.isFalse(MIME.isGif('text/plain'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isLongMessage', () => {
|
||||||
|
it('returns true for long messages', () => {
|
||||||
|
assert.isTrue(MIME.isLongMessage('text/x-signal-plain'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true for other content types', () => {
|
||||||
|
assert.isFalse(MIME.isLongMessage('text/plain'));
|
||||||
|
assert.isFalse(MIME.isLongMessage('image/gif'));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,3 +29,5 @@ export const isVideo = (value: string): value is MIMEType =>
|
||||||
// recognize them as file attachments.
|
// recognize them as file attachments.
|
||||||
export const isAudio = (value: string): value is MIMEType =>
|
export const isAudio = (value: string): value is MIMEType =>
|
||||||
Boolean(value) && value.startsWith('audio/') && !value.endsWith('aiff');
|
Boolean(value) && value.startsWith('audio/') && !value.endsWith('aiff');
|
||||||
|
export const isLongMessage = (value: unknown): value is MIMEType =>
|
||||||
|
value === LONG_MESSAGE;
|
||||||
|
|
|
@ -14685,7 +14685,7 @@
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/conversation/Quote.js",
|
"path": "ts/components/conversation/Quote.js",
|
||||||
"line": " const imageRef = react_1.useRef(new Image());",
|
"line": " const imageRef = react_1.useRef(new Image());",
|
||||||
"lineNumber": 227,
|
"lineNumber": 236,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2021-01-20T21:30:08.430Z",
|
"updated": "2021-01-20T21:30:08.430Z",
|
||||||
"reasonDetail": "Doesn't touch the DOM."
|
"reasonDetail": "Doesn't touch the DOM."
|
||||||
|
@ -14694,7 +14694,7 @@
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/conversation/Quote.tsx",
|
"path": "ts/components/conversation/Quote.tsx",
|
||||||
"line": " const imageRef = useRef(new Image());",
|
"line": " const imageRef = useRef(new Image());",
|
||||||
"lineNumber": 446,
|
"lineNumber": 459,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2021-01-20T21:30:08.430Z",
|
"updated": "2021-01-20T21:30:08.430Z",
|
||||||
"reasonDetail": "Doesn't touch the DOM."
|
"reasonDetail": "Doesn't touch the DOM."
|
||||||
|
|
Loading…
Reference in a new issue