ConversationView: Improve types
This commit is contained in:
parent
c765d3202c
commit
dcf29078f4
21 changed files with 1101 additions and 941 deletions
|
@ -44,6 +44,7 @@ const rules = {
|
|||
],
|
||||
|
||||
'no-continue': 'off',
|
||||
'lines-between-class-members': 'off',
|
||||
|
||||
// Prettier overrides:
|
||||
'arrow-parens': 'off',
|
||||
|
|
|
@ -57,19 +57,6 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="conversation-loading-screen">
|
||||
<div class='module-title-bar-drag-area'></div>
|
||||
|
||||
<div class='content'>
|
||||
<div class="module-splash-screen__logo module-img--128"></div>
|
||||
<div class='container'>
|
||||
<span class='dot'></span>
|
||||
<span class='dot'></span>
|
||||
<span class='dot'></span>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="two-column">
|
||||
<div class='module-title-bar-drag-area'></div>
|
||||
|
||||
|
@ -94,14 +81,6 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="banner">
|
||||
<div class='body'>
|
||||
<span class='icon warning'></span>
|
||||
{{ message }}
|
||||
<span class='icon dismiss'></span>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="toast">
|
||||
{{ toastMessage }}
|
||||
</script>
|
||||
|
@ -121,11 +100,6 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="message-list">
|
||||
<div class='messages'></div>
|
||||
<div class='typing-container'></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="recorder">
|
||||
<button class='close' tabIndex='2'><span class='icon'></span></button>
|
||||
<span class='time'>0:00</span>
|
||||
|
@ -145,10 +119,6 @@
|
|||
({{ limit }}{{ units }})
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="attachment-type-modal">
|
||||
Sorry, your attachment has a type, {{type}}, that is not currently supported.
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="group-member-list">
|
||||
<div class='container' tabindex='0'>
|
||||
{{ #summary }} <div class='summary'>{{ summary }}</div>{{ /summary }}
|
||||
|
|
|
@ -181,7 +181,7 @@
|
|||
"@storybook/addon-knobs": "5.1.11",
|
||||
"@storybook/addons": "5.1.11",
|
||||
"@storybook/react": "5.1.11",
|
||||
"@types/backbone": "1.4.3",
|
||||
"@types/backbone": "1.4.5",
|
||||
"@types/better-sqlite3": "7.4.0",
|
||||
"@types/blueimp-load-image": "5.14.1",
|
||||
"@types/chai": "4.2.18",
|
||||
|
@ -195,7 +195,7 @@
|
|||
"@types/got": "9.4.1",
|
||||
"@types/history": "4.7.2",
|
||||
"@types/humanize-duration": "^3.18.1",
|
||||
"@types/jquery": "3.5.0",
|
||||
"@types/jquery": "3.5.6",
|
||||
"@types/js-yaml": "3.12.0",
|
||||
"@types/linkify-it": "2.1.0",
|
||||
"@types/lodash": "4.14.106",
|
||||
|
@ -204,6 +204,7 @@
|
|||
"@types/memoizee": "0.4.2",
|
||||
"@types/mkdirp": "0.5.2",
|
||||
"@types/mocha": "5.0.0",
|
||||
"@types/mustache": "4.1.2",
|
||||
"@types/node": "14.14.37",
|
||||
"@types/node-fetch": "2.5.7",
|
||||
"@types/node-forge": "0.9.5",
|
||||
|
|
|
@ -110,16 +110,6 @@
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.discussion-container {
|
||||
@include light-theme {
|
||||
background-color: $color-white;
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
background-color: $color-gray-95;
|
||||
}
|
||||
}
|
||||
|
||||
.typing-bubble-wrapper {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
|
|
@ -26,19 +26,6 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="conversation-loading-screen">
|
||||
<div class='module-title-bar-drag-area'></div>
|
||||
|
||||
<div class='content'>
|
||||
<div class="module-splash-screen__logo module-img--128"></div>
|
||||
<div class='container'>
|
||||
<span class='dot'></span>
|
||||
<span class='dot'></span>
|
||||
<span class='dot'></span>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="two-column">
|
||||
<div class='module-title-bar-drag-area'></div>
|
||||
|
||||
|
@ -63,27 +50,6 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="scroll-down-button-view">
|
||||
<button class='text module-scroll-down__button {{ buttonClass }}' alt='{{ moreBelow }}'>
|
||||
<div class='module-scroll-down__icon'></div>
|
||||
</button>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="last-seen-indicator-view">
|
||||
<div class='module-last-seen-indicator__bar'/>
|
||||
<div class='module-last-seen-indicator__text'>
|
||||
{{ unreadMessages }}
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="banner">
|
||||
<div class='body'>
|
||||
<span class='icon warning'></span>
|
||||
{{ message }}
|
||||
<span class='icon dismiss'></span>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="toast">
|
||||
{{ toastMessage }}
|
||||
</script>
|
||||
|
@ -103,11 +69,6 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="message-list">
|
||||
<div class='messages'></div>
|
||||
<div class='typing-container'></div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="recorder">
|
||||
<button class='finish'><span class='icon'></span></button>
|
||||
<span class='time'>0:00</span>
|
||||
|
@ -127,10 +88,6 @@
|
|||
({{ limit }}{{ units }})
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="attachment-type-modal">
|
||||
Sorry, your attachment has a type, {{type}}, that is not currently supported.
|
||||
</script>
|
||||
|
||||
<script type="text/x-tmpl-mustache" id="group-member-list">
|
||||
<div class='container'>
|
||||
{{ #summary }} <div class='summary'>{{ summary }}</div>{{ /summary }}
|
||||
|
|
|
@ -40,6 +40,16 @@ import { LinkPreviewWithDomain } from '../types/LinkPreview';
|
|||
import { ConversationType } from '../state/ducks/conversations';
|
||||
import { AnnouncementsOnlyGroupBanner } from './AnnouncementsOnlyGroupBanner';
|
||||
|
||||
export type CompositionAPIType = {
|
||||
focusInput: () => void;
|
||||
isDirty: () => boolean;
|
||||
setDisabled: (disabled: boolean) => void;
|
||||
setShowMic: (showMic: boolean) => void;
|
||||
setMicActive: (micActive: boolean) => void;
|
||||
reset: InputApi['reset'];
|
||||
resetEmojiResults: InputApi['resetEmojiResults'];
|
||||
};
|
||||
|
||||
export type OwnProps = {
|
||||
readonly i18n: LocalizerType;
|
||||
readonly areWePending?: boolean;
|
||||
|
@ -55,15 +65,7 @@ export type OwnProps = {
|
|||
readonly left?: boolean;
|
||||
readonly messageRequestsEnabled?: boolean;
|
||||
readonly acceptedMessageRequest?: boolean;
|
||||
readonly compositionApi?: React.MutableRefObject<{
|
||||
focusInput: () => void;
|
||||
isDirty: () => boolean;
|
||||
setDisabled: (disabled: boolean) => void;
|
||||
setShowMic: (showMic: boolean) => void;
|
||||
setMicActive: (micActive: boolean) => void;
|
||||
reset: InputApi['reset'];
|
||||
resetEmojiResults: InputApi['resetEmojiResults'];
|
||||
}>;
|
||||
readonly compositionApi?: React.MutableRefObject<CompositionAPIType>;
|
||||
readonly micCellEl?: HTMLElement;
|
||||
readonly draftAttachments: Array<AttachmentType>;
|
||||
readonly shouldSendHighQualityAttachments: boolean;
|
||||
|
|
|
@ -66,10 +66,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
|
||||
checkForAccount: action('checkForAccount'),
|
||||
clearSelectedMessage: action('clearSelectedMessage'),
|
||||
deleteMessage: action('deleteMessage'),
|
||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||
displayTapToViewMessage: action('displayTapToViewMessage'),
|
||||
downloadAttachment: action('downloadAttachment'),
|
||||
doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'),
|
||||
kickOffAttachmentDownload: action('kickOffAttachmentDownload'),
|
||||
markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'),
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
} from './Message';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
import { ConversationType } from '../../state/ducks/conversations';
|
||||
import { assert } from '../../util/assert';
|
||||
import { groupBy } from '../../util/mapUtil';
|
||||
import { ContactNameColorType } from '../../types/Colors';
|
||||
import { SendStatus } from '../../messages/MessageSendState';
|
||||
|
@ -42,7 +41,7 @@ export type Contact = Pick<
|
|||
errors?: Array<Error>;
|
||||
};
|
||||
|
||||
export type Props = {
|
||||
export type PropsData = {
|
||||
// An undefined status means they were the sender and it's an incoming message. If
|
||||
// `undefined` is a status, there should be no other items in the array; if there are
|
||||
// any defined statuses, `undefined` shouldn't be present.
|
||||
|
@ -57,16 +56,11 @@ export type Props = {
|
|||
sendAnyway: (contactId: string, messageId: string) => unknown;
|
||||
showSafetyNumber: (contactId: string) => void;
|
||||
i18n: LocalizerType;
|
||||
} & Pick<
|
||||
} & Pick<MessagePropsType, 'interactionMode'>;
|
||||
|
||||
export type PropsBackboneActions = Pick<
|
||||
MessagePropsType,
|
||||
| 'checkForAccount'
|
||||
| 'clearSelectedMessage'
|
||||
| 'deleteMessage'
|
||||
| 'deleteMessageForEveryone'
|
||||
| 'displayTapToViewMessage'
|
||||
| 'downloadAttachment'
|
||||
| 'doubleCheckMissingQuoteReference'
|
||||
| 'interactionMode'
|
||||
| 'kickOffAttachmentDownload'
|
||||
| 'markAttachmentAsCorrupted'
|
||||
| 'markViewed'
|
||||
|
@ -85,6 +79,16 @@ export type Props = {
|
|||
| 'showVisualAttachment'
|
||||
>;
|
||||
|
||||
export type PropsReduxActions = Pick<
|
||||
MessagePropsType,
|
||||
| 'clearSelectedMessage'
|
||||
| 'doubleCheckMissingQuoteReference'
|
||||
| 'checkForAccount'
|
||||
>;
|
||||
|
||||
export type ExternalProps = PropsData & PropsBackboneActions;
|
||||
export type Props = PropsData & PropsBackboneActions & PropsReduxActions;
|
||||
|
||||
const contactSortCollator = new Intl.Collator();
|
||||
|
||||
const _keyForError = (error: Error): string => {
|
||||
|
@ -263,10 +267,7 @@ export class MessageDetail extends React.Component<Props> {
|
|||
checkForAccount,
|
||||
clearSelectedMessage,
|
||||
contactNameColor,
|
||||
deleteMessage,
|
||||
deleteMessageForEveryone,
|
||||
displayTapToViewMessage,
|
||||
downloadAttachment,
|
||||
doubleCheckMissingQuoteReference,
|
||||
i18n,
|
||||
interactionMode,
|
||||
|
@ -302,12 +303,18 @@ export class MessageDetail extends React.Component<Props> {
|
|||
clearSelectedMessage={clearSelectedMessage}
|
||||
contactNameColor={contactNameColor}
|
||||
containerElementRef={this.messageContainerRef}
|
||||
deleteMessage={deleteMessage}
|
||||
deleteMessageForEveryone={deleteMessageForEveryone}
|
||||
deleteMessage={() =>
|
||||
window.log.warn('MessageDetail: deleteMessage called!')
|
||||
}
|
||||
deleteMessageForEveryone={() =>
|
||||
window.log.warn('MessageDetail: deleteMessageForEveryone called!')
|
||||
}
|
||||
disableMenu
|
||||
disableScroll
|
||||
displayTapToViewMessage={displayTapToViewMessage}
|
||||
downloadAttachment={downloadAttachment}
|
||||
downloadAttachment={() =>
|
||||
window.log.warn('MessageDetail: deleteMessageForEveryone called!')
|
||||
}
|
||||
doubleCheckMissingQuoteReference={doubleCheckMissingQuoteReference}
|
||||
i18n={i18n}
|
||||
interactionMode={interactionMode}
|
||||
|
@ -324,10 +331,7 @@ export class MessageDetail extends React.Component<Props> {
|
|||
retrySend={retrySend}
|
||||
showForwardMessageModal={showForwardMessageModal}
|
||||
scrollToQuotedMessage={() => {
|
||||
assert(
|
||||
false,
|
||||
'scrollToQuotedMessage should never be called because scrolling is disabled'
|
||||
);
|
||||
window.log.warn('MessageDetail: scrollToQuotedMessage called!');
|
||||
}}
|
||||
showContactDetail={showContactDetail}
|
||||
showContactModal={showContactModal}
|
||||
|
@ -338,9 +342,8 @@ export class MessageDetail extends React.Component<Props> {
|
|||
showExpiredOutgoingTapToViewToast
|
||||
}
|
||||
showMessageDetail={() => {
|
||||
assert(
|
||||
false,
|
||||
"showMessageDetail should never be called because the menu is disabled (and we're already in the message detail!)"
|
||||
window.log.warn(
|
||||
'MessageDetail: deleteMessageForEveryone called!'
|
||||
);
|
||||
}}
|
||||
showVisualAttachment={showVisualAttachment}
|
||||
|
|
|
@ -341,7 +341,9 @@ export async function joinViaLink(hash: string): Promise<void> {
|
|||
|
||||
window.log.info(`joinViaLink/${logId}: Showing modal`);
|
||||
|
||||
let groupV2InfoDialog = new window.Whisper.ReactWrapperView({
|
||||
let groupV2InfoDialog:
|
||||
| Backbone.View
|
||||
| undefined = new window.Whisper.ReactWrapperView({
|
||||
className: 'group-v2-join-dialog-wrapper',
|
||||
JSX: window.Signal.State.Roots.createGroupV2JoinModal(window.reduxStore, {
|
||||
join,
|
||||
|
|
14
ts/model-types.d.ts
vendored
14
ts/model-types.d.ts
vendored
|
@ -22,7 +22,11 @@ import {
|
|||
} from './messages/MessageSendState';
|
||||
import { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions';
|
||||
import { ConversationColorType } from './types/Colors';
|
||||
import { AttachmentType, ThumbnailType } from './types/Attachment';
|
||||
import {
|
||||
AttachmentType,
|
||||
ThumbnailType,
|
||||
OnDiskAttachmentDraftType,
|
||||
} from './types/Attachment';
|
||||
import { EmbeddedContactType } from './types/EmbeddedContact';
|
||||
import { SignalService as Proto } from './protobuf';
|
||||
import { AvatarDataType } from './types/Avatar';
|
||||
|
@ -209,12 +213,8 @@ export type ConversationAttributesType = {
|
|||
customColor?: CustomColorType;
|
||||
customColorId?: string;
|
||||
discoveredUnregisteredAt?: number;
|
||||
draftAttachments?: Array<{
|
||||
fileName?: string;
|
||||
path?: string;
|
||||
pending?: boolean;
|
||||
screenshotPath?: string;
|
||||
}>;
|
||||
draftChanged?: boolean;
|
||||
draftAttachments?: Array<OnDiskAttachmentDraftType>;
|
||||
draftBodyRanges?: Array<BodyRangeType>;
|
||||
draftTimestamp?: number | null;
|
||||
inbox_position: number;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { compact } from 'lodash';
|
||||
import {
|
||||
ConversationAttributesType,
|
||||
ConversationModelCollectionType,
|
||||
MessageAttributesType,
|
||||
MessageModelCollectionType,
|
||||
QuotedMessageType,
|
||||
|
@ -163,12 +164,14 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
storeName?: string | null;
|
||||
|
||||
throttledBumpTyping: unknown;
|
||||
throttledBumpTyping?: () => void;
|
||||
|
||||
throttledFetchSMSOnlyUUID?: () => Promise<void> | void;
|
||||
|
||||
throttledMaybeMigrateV1Group?: () => Promise<void> | void;
|
||||
|
||||
throttledGetProfiles?: () => Promise<void>;
|
||||
|
||||
typingRefreshTimer?: NodeJS.Timer | null;
|
||||
|
||||
typingPauseTimer?: NodeJS.Timer | null;
|
||||
|
@ -2329,13 +2332,13 @@ export class ConversationModel extends window.Backbone
|
|||
});
|
||||
}
|
||||
|
||||
getUnverified(): Backbone.Collection<ConversationModel> {
|
||||
getUnverified(): ConversationModelCollectionType {
|
||||
if (isDirectConversation(this.attributes)) {
|
||||
return this.isUnverified()
|
||||
? new window.Backbone.Collection([this])
|
||||
: new window.Backbone.Collection();
|
||||
? new window.Whisper.ConversationCollection([this])
|
||||
: new window.Whisper.ConversationCollection();
|
||||
}
|
||||
return new window.Backbone.Collection(
|
||||
return new window.Whisper.ConversationCollection(
|
||||
this.contactCollection?.filter(contact => {
|
||||
if (isMe(contact.attributes)) {
|
||||
return false;
|
||||
|
@ -2382,15 +2385,15 @@ export class ConversationModel extends window.Backbone
|
|||
});
|
||||
}
|
||||
|
||||
getUntrusted(): Backbone.Collection<ConversationModel> {
|
||||
getUntrusted(): ConversationModelCollectionType {
|
||||
if (isDirectConversation(this.attributes)) {
|
||||
if (this.isUntrusted()) {
|
||||
return new window.Backbone.Collection([this]);
|
||||
return new window.Whisper.ConversationCollection([this]);
|
||||
}
|
||||
return new window.Backbone.Collection();
|
||||
return new window.Whisper.ConversationCollection();
|
||||
}
|
||||
|
||||
return new window.Backbone.Collection(
|
||||
return new window.Whisper.ConversationCollection(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.contactCollection!.filter(contact => {
|
||||
if (isMe(contact.attributes)) {
|
||||
|
@ -2470,7 +2473,7 @@ export class ConversationModel extends window.Backbone
|
|||
readStatus: ReadStatus.Unread,
|
||||
// TODO: DESKTOP-722
|
||||
// this type does not fully implement the interface it is expected to
|
||||
} as unknown) as typeof window.Whisper.MessageAttributesType;
|
||||
} as unknown) as MessageAttributesType;
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message);
|
||||
const model = window.MessageController.register(
|
||||
|
@ -2510,7 +2513,7 @@ export class ConversationModel extends window.Backbone
|
|||
readStatus: ReadStatus.Unread,
|
||||
// TODO: DESKTOP-722
|
||||
// this type does not fully implement the interface it is expected to
|
||||
} as unknown) as typeof window.Whisper.MessageAttributesType;
|
||||
} as unknown) as MessageAttributesType;
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message);
|
||||
const model = window.MessageController.register(
|
||||
|
@ -2546,7 +2549,7 @@ export class ConversationModel extends window.Backbone
|
|||
schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY,
|
||||
// TODO: DESKTOP-722
|
||||
// this type does not fully implement the interface it is expected to
|
||||
} as unknown) as typeof window.Whisper.MessageAttributesType;
|
||||
} as unknown) as MessageAttributesType;
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message);
|
||||
const model = window.MessageController.register(
|
||||
|
@ -2604,7 +2607,7 @@ export class ConversationModel extends window.Backbone
|
|||
local: options.local,
|
||||
readStatus: ReadStatus.Unread,
|
||||
// TODO: DESKTOP-722
|
||||
} as unknown) as typeof window.Whisper.MessageAttributesType;
|
||||
} as unknown) as MessageAttributesType;
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message);
|
||||
const model = window.MessageController.register(
|
||||
|
@ -2663,7 +2666,7 @@ export class ConversationModel extends window.Backbone
|
|||
readStatus: unread ? ReadStatus.Unread : ReadStatus.Read,
|
||||
callHistoryDetails: detailsToSave,
|
||||
// TODO: DESKTOP-722
|
||||
} as unknown) as typeof window.Whisper.MessageAttributesType;
|
||||
} as unknown) as MessageAttributesType;
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message);
|
||||
const model = window.MessageController.register(
|
||||
|
@ -2714,7 +2717,7 @@ export class ConversationModel extends window.Backbone
|
|||
changedId: conversationId || this.id,
|
||||
profileChange,
|
||||
// TODO: DESKTOP-722
|
||||
} as unknown) as typeof window.Whisper.MessageAttributesType;
|
||||
} as unknown) as MessageAttributesType;
|
||||
|
||||
const id = await window.Signal.Data.saveMessage(message);
|
||||
const model = window.MessageController.register(
|
||||
|
@ -3185,9 +3188,7 @@ export class ConversationModel extends window.Backbone
|
|||
return [];
|
||||
}
|
||||
|
||||
async makeQuote(
|
||||
quotedMessage: typeof window.Whisper.MessageType
|
||||
): Promise<QuotedMessageType> {
|
||||
async makeQuote(quotedMessage: MessageModel): Promise<QuotedMessageType> {
|
||||
const { getName } = EmbeddedContact;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const contact = quotedMessage.getContact()!;
|
||||
|
@ -4452,10 +4453,10 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
}
|
||||
|
||||
getProfiles(): Promise<Array<void>> {
|
||||
async getProfiles(): Promise<void> {
|
||||
// request all conversation members' keys
|
||||
const conversations = (this.getMembers() as unknown) as Array<ConversationModel>;
|
||||
return Promise.all(
|
||||
await Promise.all(
|
||||
window._.map(conversations, conversation =>
|
||||
getProfile(conversation.get('uuid'), conversation.get('e164'))
|
||||
)
|
||||
|
|
|
@ -5624,6 +5624,10 @@ function getExternalDraftFilesForConversation(
|
|||
const files: Array<string> = [];
|
||||
|
||||
forEach(draftAttachments, attachment => {
|
||||
if (attachment.pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { path: file, screenshotPath } = attachment;
|
||||
if (file) {
|
||||
files.push(file);
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
|
||||
type ExternalProps = {
|
||||
id: string;
|
||||
onClickQuotedMessage: (id?: string) => unknown;
|
||||
onClickQuotedMessage: (id: string) => unknown;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
||||
|
@ -92,8 +92,12 @@ const mapStateToProps = (state: StateType, props: ExternalProps) => {
|
|||
ourConversationId: getUserConversationId(state),
|
||||
})
|
||||
: undefined,
|
||||
onClickQuotedMessage: () =>
|
||||
onClickQuotedMessage(quotedMessage?.quote?.messageId),
|
||||
onClickQuotedMessage: () => {
|
||||
const messageId = quotedMessage?.quote?.messageId;
|
||||
if (messageId) {
|
||||
onClickQuotedMessage(messageId);
|
||||
}
|
||||
},
|
||||
// Emojis
|
||||
recentEmojis,
|
||||
skinTone: get(state, ['items', 'skinTone'], 0),
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { ComponentProps } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { MessageDetail } from '../../components/conversation/MessageDetail';
|
||||
import {
|
||||
MessageDetail,
|
||||
ExternalProps as MessageDetailProps,
|
||||
} from '../../components/conversation/MessageDetail';
|
||||
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { StateType } from '../reducer';
|
||||
|
@ -13,40 +15,10 @@ import { renderAudioAttachment } from './renderAudioAttachment';
|
|||
import { renderEmojiPicker } from './renderEmojiPicker';
|
||||
import { getContactNameColorSelector } from '../selectors/conversations';
|
||||
|
||||
type MessageDetailProps = ComponentProps<typeof MessageDetail>;
|
||||
|
||||
export { Contact } from '../../components/conversation/MessageDetail';
|
||||
|
||||
export type OwnProps = Pick<
|
||||
export type OwnProps = Omit<
|
||||
MessageDetailProps,
|
||||
| 'clearSelectedMessage'
|
||||
| 'checkForAccount'
|
||||
| 'contacts'
|
||||
| 'deleteMessage'
|
||||
| 'deleteMessageForEveryone'
|
||||
| 'displayTapToViewMessage'
|
||||
| 'downloadAttachment'
|
||||
| 'doubleCheckMissingQuoteReference'
|
||||
| 'errors'
|
||||
| 'kickOffAttachmentDownload'
|
||||
| 'markAttachmentAsCorrupted'
|
||||
| 'markViewed'
|
||||
| 'message'
|
||||
| 'openConversation'
|
||||
| 'openLink'
|
||||
| 'reactToMessage'
|
||||
| 'receivedAt'
|
||||
| 'replyToMessage'
|
||||
| 'retrySend'
|
||||
| 'sendAnyway'
|
||||
| 'sentAt'
|
||||
| 'showContactDetail'
|
||||
| 'showContactModal'
|
||||
| 'showExpiredIncomingTapToViewToast'
|
||||
| 'showExpiredOutgoingTapToViewToast'
|
||||
| 'showForwardMessageModal'
|
||||
| 'showSafetyNumber'
|
||||
| 'showVisualAttachment'
|
||||
'i18n' | 'interactionMode' | 'renderAudioAttachment' | 'renderEmojiPicker'
|
||||
>;
|
||||
|
||||
const mapStateToProps = (
|
||||
|
@ -63,13 +35,7 @@ const mapStateToProps = (
|
|||
sendAnyway,
|
||||
showSafetyNumber,
|
||||
|
||||
checkForAccount,
|
||||
clearSelectedMessage,
|
||||
deleteMessage,
|
||||
deleteMessageForEveryone,
|
||||
displayTapToViewMessage,
|
||||
downloadAttachment,
|
||||
doubleCheckMissingQuoteReference,
|
||||
kickOffAttachmentDownload,
|
||||
markAttachmentAsCorrupted,
|
||||
markViewed,
|
||||
|
@ -108,13 +74,7 @@ const mapStateToProps = (
|
|||
sendAnyway,
|
||||
showSafetyNumber,
|
||||
|
||||
checkForAccount,
|
||||
clearSelectedMessage,
|
||||
deleteMessage,
|
||||
deleteMessageForEveryone,
|
||||
displayTapToViewMessage,
|
||||
downloadAttachment,
|
||||
doubleCheckMissingQuoteReference,
|
||||
kickOffAttachmentDownload,
|
||||
markAttachmentAsCorrupted,
|
||||
markViewed,
|
||||
|
|
|
@ -73,28 +73,55 @@ export type DownloadedAttachmentType = AttachmentType & {
|
|||
data: ArrayBuffer;
|
||||
};
|
||||
|
||||
type BaseAttachmentDraftType = {
|
||||
export type BaseAttachmentDraftType = {
|
||||
blurHash?: string;
|
||||
contentType: MIME.MIMEType;
|
||||
fileName: string;
|
||||
path: string;
|
||||
screenshotContentType?: string;
|
||||
screenshotSize?: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
export type InMemoryAttachmentDraftType = {
|
||||
data?: ArrayBuffer;
|
||||
screenshotData?: ArrayBuffer;
|
||||
} & BaseAttachmentDraftType;
|
||||
export type InMemoryAttachmentDraftType =
|
||||
| ({
|
||||
data?: ArrayBuffer;
|
||||
pending: false;
|
||||
screenshotData?: ArrayBuffer;
|
||||
} & BaseAttachmentDraftType)
|
||||
| {
|
||||
contentType: MIME.MIMEType;
|
||||
fileName: string;
|
||||
path: string;
|
||||
pending: true;
|
||||
};
|
||||
|
||||
export type OnDiskAttachmentDraftType = {
|
||||
path?: string;
|
||||
screenshotPath?: string;
|
||||
} & BaseAttachmentDraftType;
|
||||
export type OnDiskAttachmentDraftType =
|
||||
| ({
|
||||
caption?: string;
|
||||
pending: false;
|
||||
screenshotPath?: string;
|
||||
} & BaseAttachmentDraftType)
|
||||
| {
|
||||
contentType: MIME.MIMEType;
|
||||
fileName: string;
|
||||
path: string;
|
||||
pending: true;
|
||||
};
|
||||
|
||||
export type AttachmentDraftType = {
|
||||
url: string;
|
||||
} & BaseAttachmentDraftType;
|
||||
export type AttachmentDraftType =
|
||||
| ({
|
||||
url: string;
|
||||
screenshotPath?: string;
|
||||
caption?: string;
|
||||
pending: false;
|
||||
} & BaseAttachmentDraftType)
|
||||
| {
|
||||
contentType: MIME.MIMEType;
|
||||
fileName: string;
|
||||
path: string;
|
||||
pending: true;
|
||||
};
|
||||
|
||||
export type ThumbnailType = {
|
||||
height: number;
|
||||
|
|
|
@ -69,9 +69,13 @@ export function findLinks(text: string, caretLocation?: number): Array<string> {
|
|||
);
|
||||
}
|
||||
|
||||
export function getDomain(href: string): string | undefined {
|
||||
export function getDomain(href: string): string {
|
||||
const url = maybeParseUrl(href);
|
||||
return url ? url.hostname : undefined;
|
||||
if (!url || !url.hostname) {
|
||||
throw new Error('getDomain: Unable to extract hostname from href');
|
||||
}
|
||||
|
||||
return url.hostname;
|
||||
}
|
||||
|
||||
// See <https://tools.ietf.org/html/rfc3986>.
|
||||
|
|
|
@ -48,11 +48,13 @@ export async function handleImageAttachment(
|
|||
const blurHash = await imageToBlurHash(resizedBlob);
|
||||
|
||||
return {
|
||||
fileName: fileName || file.name,
|
||||
blurHash,
|
||||
contentType,
|
||||
data,
|
||||
fileName: fileName || file.name,
|
||||
path: file.name,
|
||||
pending: false,
|
||||
size: data.byteLength,
|
||||
blurHash,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ export async function longRunningTaskWrapper<T>({
|
|||
const ONE_SECOND = 1000;
|
||||
const TWO_SECONDS = 2000;
|
||||
|
||||
let progressView: typeof window.Whisper.ReactWrapperView | undefined;
|
||||
let progressView: Backbone.View | undefined;
|
||||
let spinnerStart;
|
||||
let progressTimeout: NodeJS.Timeout | undefined = setTimeout(() => {
|
||||
window.log.info(`longRunningTaskWrapper/${idLog}: Creating spinner`);
|
||||
|
@ -76,11 +76,13 @@ export async function longRunningTaskWrapper<T>({
|
|||
|
||||
// Note: this component uses a portal to render itself into the top-level DOM. No
|
||||
// need to attach it to the DOM here.
|
||||
const errorView = new window.Whisper.ReactWrapperView({
|
||||
const errorView: Backbone.View = new window.Whisper.ReactWrapperView({
|
||||
className: 'error-modal-wrapper',
|
||||
Component: window.Signal.Components.ErrorModal,
|
||||
props: {
|
||||
onClose: () => errorView.remove(),
|
||||
onClose: (): void => {
|
||||
errorView.remove();
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
151
ts/window.d.ts
vendored
151
ts/window.d.ts
vendored
|
@ -120,6 +120,7 @@ import { StateType } from './state/reducer';
|
|||
import { SystemTraySetting } from './types/SystemTraySetting';
|
||||
import { CI } from './CI';
|
||||
import { IPCEventsType } from './util/createIPCEvents';
|
||||
import { ConversationView } from './views/conversation_view';
|
||||
|
||||
export { Long } from 'long';
|
||||
|
||||
|
@ -576,8 +577,33 @@ export type LoggerType = {
|
|||
|
||||
export type LogFunctionType = (...args: Array<unknown>) => void;
|
||||
|
||||
export class AnyViewClass extends window.Backbone.View<any> {
|
||||
public headerTitle?: string;
|
||||
static show(view: typeof AnyViewClass, element: Element): void;
|
||||
|
||||
constructor(options?: any);
|
||||
}
|
||||
|
||||
export class BasicReactWrapperViewClass extends AnyViewClass {
|
||||
public update(options: any): void;
|
||||
}
|
||||
|
||||
export type WhisperType = {
|
||||
Conversation: typeof ConversationModel;
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
Message: typeof MessageModel;
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
|
||||
GroupMemberConversation: WhatIsThis;
|
||||
KeyChangeListener: WhatIsThis;
|
||||
RotateSignedPreKeyListener: WhatIsThis;
|
||||
WallClockListener: WhatIsThis;
|
||||
|
||||
deliveryReceiptQueue: PQueue;
|
||||
deliveryReceiptBatcher: BatcherType<DeliveryReceiptBatcherItemType>;
|
||||
events: Backbone.Events;
|
||||
activeConfirmationView: WhatIsThis;
|
||||
|
||||
Database: {
|
||||
open: () => Promise<IDBDatabase>;
|
||||
handleDOMException: (
|
||||
|
@ -586,38 +612,6 @@ export type WhisperType = {
|
|||
reject: Function
|
||||
) => void;
|
||||
};
|
||||
ConversationCollection: typeof ConversationModelCollectionType;
|
||||
ConversationCollectionType: ConversationModelCollectionType;
|
||||
Conversation: typeof ConversationModel;
|
||||
ConversationType: ConversationModel;
|
||||
MessageCollection: typeof MessageModelCollectionType;
|
||||
MessageCollectionType: MessageModelCollectionType;
|
||||
MessageAttributesType: MessageAttributesType;
|
||||
Message: typeof MessageModel;
|
||||
MessageType: MessageModel;
|
||||
GroupMemberConversation: WhatIsThis;
|
||||
KeyChangeListener: WhatIsThis;
|
||||
ClearDataView: WhatIsThis;
|
||||
ReactWrapperView: WhatIsThis;
|
||||
activeConfirmationView: WhatIsThis;
|
||||
ToastView: typeof window.Whisper.View & {
|
||||
show: (view: typeof Backbone.View, el: Element) => void;
|
||||
};
|
||||
ConversationArchivedToast: WhatIsThis;
|
||||
ConversationUnarchivedToast: WhatIsThis;
|
||||
ConversationMarkedUnreadToast: WhatIsThis;
|
||||
WallClockListener: WhatIsThis;
|
||||
BannerView: any;
|
||||
RecorderView: any;
|
||||
GroupMemberList: any;
|
||||
GroupLinkCopiedToast: typeof Backbone.View;
|
||||
InboxView: typeof window.Whisper.View;
|
||||
InstallView: typeof window.Whisper.View;
|
||||
StandaloneRegistrationView: typeof window.Whisper.View;
|
||||
KeyVerificationPanelView: any;
|
||||
SafetyNumberChangeDialogView: any;
|
||||
BodyRangesType: BodyRangesType;
|
||||
BodyRangeType: BodyRangeType;
|
||||
|
||||
Notifications: {
|
||||
isEnabled: boolean;
|
||||
|
@ -643,45 +637,60 @@ export type WhisperType = {
|
|||
update: () => void;
|
||||
};
|
||||
|
||||
deliveryReceiptQueue: PQueue;
|
||||
deliveryReceiptBatcher: BatcherType<DeliveryReceiptBatcherItemType>;
|
||||
RotateSignedPreKeyListener: WhatIsThis;
|
||||
// Backbone views
|
||||
|
||||
AlreadyGroupMemberToast: typeof window.Whisper.ToastView;
|
||||
AlreadyRequestedToJoinToast: typeof window.Whisper.ToastView;
|
||||
BlockedGroupToast: typeof window.Whisper.ToastView;
|
||||
BlockedToast: typeof window.Whisper.ToastView;
|
||||
CannotMixImageAndNonImageAttachmentsToast: typeof window.Whisper.ToastView;
|
||||
CaptchaSolvedToast: typeof window.Whisper.ToastView;
|
||||
CaptchaFailedToast: typeof window.Whisper.ToastView;
|
||||
CannotStartGroupCallToast: typeof window.Whisper.ToastView;
|
||||
DangerousFileTypeToast: typeof window.Whisper.ToastView;
|
||||
DecryptionErrorToast: typeof window.Whisper.ToastView;
|
||||
ExpiredToast: typeof window.Whisper.ToastView;
|
||||
FileSavedToast: typeof window.Whisper.ToastView;
|
||||
FileSizeToast: any;
|
||||
FoundButNotLoadedToast: typeof window.Whisper.ToastView;
|
||||
InvalidConversationToast: typeof window.Whisper.ToastView;
|
||||
LeftGroupToast: typeof window.Whisper.ToastView;
|
||||
MaxAttachmentsToast: typeof window.Whisper.ToastView;
|
||||
MessageBodyTooLongToast: typeof window.Whisper.ToastView;
|
||||
OneNonImageAtATimeToast: typeof window.Whisper.ToastView;
|
||||
OriginalNoLongerAvailableToast: typeof window.Whisper.ToastView;
|
||||
OriginalNotFoundToast: typeof window.Whisper.ToastView;
|
||||
PinnedConversationsFullToast: typeof window.Whisper.ToastView;
|
||||
ReactionFailedToast: typeof window.Whisper.ToastView;
|
||||
DeleteForEveryoneFailedToast: typeof window.Whisper.ToastView;
|
||||
TapToViewExpiredIncomingToast: typeof window.Whisper.ToastView;
|
||||
TapToViewExpiredOutgoingToast: typeof window.Whisper.ToastView;
|
||||
TimerConflictToast: typeof window.Whisper.ToastView;
|
||||
UnableToLoadToast: typeof window.Whisper.ToastView;
|
||||
VoiceNoteLimit: typeof window.Whisper.ToastView;
|
||||
VoiceNoteMustBeOnlyAttachmentToast: typeof window.Whisper.ToastView;
|
||||
// Modernized
|
||||
ConversationView: typeof ConversationView;
|
||||
|
||||
ConversationLoadingScreen: typeof window.Whisper.View;
|
||||
ConversationView: typeof window.Whisper.View;
|
||||
View: typeof Backbone.View & {
|
||||
Templates: Record<string, string>;
|
||||
};
|
||||
DisappearingTimeDialog: typeof window.Whisper.View | undefined;
|
||||
// Note: we can no longer use 'View.extend' once we've moved to Typescript's preferred
|
||||
// 'extend View' syntax. Thus, we'll need to typescriptify most of it at once.
|
||||
|
||||
// Toast
|
||||
AlreadyGroupMemberToast: typeof AnyViewClass;
|
||||
AlreadyRequestedToJoinToast: typeof AnyViewClass;
|
||||
BlockedGroupToast: typeof AnyViewClass;
|
||||
BlockedToast: typeof AnyViewClass;
|
||||
CannotMixImageAndNonImageAttachmentsToast: typeof AnyViewClass;
|
||||
CaptchaSolvedToast: typeof AnyViewClass;
|
||||
CaptchaFailedToast: typeof AnyViewClass;
|
||||
CannotStartGroupCallToast: typeof AnyViewClass;
|
||||
ConversationArchivedToast: typeof AnyViewClass;
|
||||
ConversationUnarchivedToast: typeof AnyViewClass;
|
||||
ConversationMarkedUnreadToast: typeof AnyViewClass;
|
||||
DangerousFileTypeToast: typeof AnyViewClass;
|
||||
DecryptionErrorToast: typeof AnyViewClass;
|
||||
ExpiredToast: typeof AnyViewClass;
|
||||
FileSavedToast: typeof AnyViewClass;
|
||||
FileSizeToast: typeof AnyViewClass;
|
||||
FoundButNotLoadedToast: typeof AnyViewClass;
|
||||
GroupLinkCopiedToast: typeof AnyViewClass;
|
||||
InvalidConversationToast: typeof AnyViewClass;
|
||||
LeftGroupToast: typeof AnyViewClass;
|
||||
MaxAttachmentsToast: typeof AnyViewClass;
|
||||
MessageBodyTooLongToast: typeof AnyViewClass;
|
||||
OneNonImageAtATimeToast: typeof AnyViewClass;
|
||||
OriginalNoLongerAvailableToast: typeof AnyViewClass;
|
||||
OriginalNotFoundToast: typeof AnyViewClass;
|
||||
PinnedConversationsFullToast: typeof AnyViewClass;
|
||||
ReactionFailedToast: typeof AnyViewClass;
|
||||
DeleteForEveryoneFailedToast: typeof AnyViewClass;
|
||||
TapToViewExpiredIncomingToast: typeof AnyViewClass;
|
||||
TapToViewExpiredOutgoingToast: typeof AnyViewClass;
|
||||
TimerConflictToast: typeof AnyViewClass;
|
||||
UnableToLoadToast: typeof AnyViewClass;
|
||||
VoiceNoteLimit: typeof AnyViewClass;
|
||||
VoiceNoteMustBeOnlyAttachmentToast: typeof AnyViewClass;
|
||||
|
||||
ClearDataView: typeof AnyViewClass;
|
||||
ConversationLoadingScreen: typeof AnyViewClass;
|
||||
GroupMemberList: typeof AnyViewClass;
|
||||
InboxView: typeof AnyViewClass;
|
||||
InstallView: typeof AnyViewClass;
|
||||
KeyVerificationPanelView: typeof AnyViewClass;
|
||||
ReactWrapperView: typeof BasicReactWrapperViewClass;
|
||||
RecorderView: typeof AnyViewClass;
|
||||
SafetyNumberChangeDialogView: typeof AnyViewClass;
|
||||
StandaloneRegistrationView: typeof AnyViewClass;
|
||||
ToastView: typeof AnyViewClass;
|
||||
View: typeof AnyViewClass;
|
||||
};
|
||||
|
|
22
yarn.lock
22
yarn.lock
|
@ -2363,10 +2363,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
|
||||
integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
|
||||
|
||||
"@types/backbone@1.4.3":
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/backbone/-/backbone-1.4.3.tgz#75dc6e55382e226788db8d796de346891d6b2256"
|
||||
integrity sha512-PZVw2FckEbEJ+qh2hvtgpI/4p8yD3sRbA8FEO72k01/90SSH73GcLW3CqcYP5epwDpLl3cKrgK0yypQY4qiuEw==
|
||||
"@types/backbone@1.4.5":
|
||||
version "1.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/backbone/-/backbone-1.4.5.tgz#057d89987fb672a20b896b1df5cc802f7b87c624"
|
||||
integrity sha512-pSqM0eryp6V3G0srBtndUd9IJmiG2BAwYLQGPDcEPMjbfbgitlrN40+Lc1rrMjNMbV5QWywe6WPmNjdqyNTyIw==
|
||||
dependencies:
|
||||
"@types/jquery" "*"
|
||||
"@types/underscore" "*"
|
||||
|
@ -2574,13 +2574,20 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.18.1.tgz#10090d596053703e7de0ac43a37b96cd9fc78309"
|
||||
integrity sha512-MUgbY3CF7hg/a/jogixmAufLjJBQT7WEf8Q+kYJkOc47ytngg1IuZobCngdTjAgY83JWEogippge5O5fplaQlw==
|
||||
|
||||
"@types/jquery@*", "@types/jquery@3.5.0":
|
||||
"@types/jquery@*":
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.0.tgz#ccb7dfd317d02d4227dd3803c75297d0c10dad68"
|
||||
integrity sha512-C7qQUjpMWDUNYQRTXsP5nbYYwCwwgy84yPgoTT7fPN69NH92wLeCtFaMsWeolJD1AF/6uQw3pYt62rzv83sMmw==
|
||||
dependencies:
|
||||
"@types/sizzle" "*"
|
||||
|
||||
"@types/jquery@3.5.6":
|
||||
version "3.5.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.6.tgz#97ac8e36dccd8ad8ed3f3f3b48933614d9fd8cf0"
|
||||
integrity sha512-SmgCQRzGPId4MZQKDj9Hqc6kSXFNWZFHpELkyK8AQhf8Zr6HKfCzFv9ZC1Fv3FyQttJZOlap3qYb12h61iZAIg==
|
||||
dependencies:
|
||||
"@types/sizzle" "*"
|
||||
|
||||
"@types/js-yaml@3.12.0":
|
||||
version "3.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.0.tgz#3494ce97358e2675e24e97a747ec23478eeaf8b6"
|
||||
|
@ -2658,6 +2665,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.0.0.tgz#a3014921991066193f6c8e47290d4d598dfd19e6"
|
||||
integrity sha512-ZS0vBV7Jn5Z/Q4T3VXauEKMDCV8nWOtJJg90OsDylkYJiQwcWtKuLzohWzrthBkerUF7DLMmJcwOPEP0i/AOXw==
|
||||
|
||||
"@types/mustache@4.1.2":
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.1.2.tgz#d0e158013c81674a5b6d8780bc3fe234e1804eaf"
|
||||
integrity sha512-c4OVMMcyodKQ9dpwBwh3ofK9P6U9ZktKU9S+p33UqwMNN1vlv2P0zJZUScTshnx7OEoIIRcCFNQ904sYxZz8kg==
|
||||
|
||||
"@types/node-fetch@2.5.7":
|
||||
version "2.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c"
|
||||
|
|
Loading…
Reference in a new issue