@ethora/chat-component 26.9.6 → 26.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -3
- package/dist/{ChatProfileModal-DKuxJsdM.js → ChatProfileModal-Bf5jGPBZ.js} +566 -562
- package/dist/FilePreviewModal-Bwx0RZZw.js +1763 -0
- package/dist/{ManageDataModal-Dw4dCQll.js → ManageDataModal-Dhkp0LWH.js} +20 -20
- package/dist/{MarkdownBody-xtKDMR0h.js → MarkdownBody-_sUhW2O8.js} +1 -1
- package/dist/{MessageSearchModal-BVKU6H3v.js → MessageSearchModal-rwwWrILl.js} +106 -114
- package/dist/{PublicChatsModal-CscOnVaQ.js → PublicChatsModal-Byso6_vt.js} +13 -13
- package/dist/{SideDrawer-WneDrrWG.js → SideDrawer-CQPOf0ek.js} +38 -38
- package/dist/{UserProfileModal-CMHFq1I9.js → UserProfileModal-Vj2kICvc.js} +10 -10
- package/dist/{UserSettingsModal-BoZXZ0oR.js → UserSettingsModal-CioX4ZIV.js} +5 -5
- package/dist/{VideoCallOverlay-CQNi7wp5.js → VideoCallOverlay-DodAkiam.js} +3824 -3708
- package/dist/{VisibilityModal-BPsNiMzJ.js → VisibilityModal-CIl7rkBM.js} +12 -12
- package/dist/assets/icons.d.ts +2 -3
- package/dist/components/Files/fileCategory.d.ts +4 -2
- package/dist/components/InputComponents/MentionDropdown.d.ts +2 -0
- package/dist/components/MainComponents/AlbumGrid.d.ts +22 -0
- package/dist/components/MainComponents/AttachmentList.d.ts +6 -6
- package/dist/components/MainComponents/ImageUnavailable.d.ts +21 -0
- package/dist/components/MainComponents/MediaUploadOverlay.d.ts +31 -0
- package/dist/components/MainComponents/MessageContainer.d.ts +3 -0
- package/dist/components/MainComponents/scrollAnchor.d.ts +19 -0
- package/dist/components/MainComponents/useJumpToMessage.d.ts +42 -11
- package/dist/components/MainComponents/useScrollAnchor.d.ts +29 -0
- package/dist/components/MessageBubble/MediaCaption.d.ts +9 -0
- package/dist/components/MessageBubble/MessageAvatarPhoto.d.ts +17 -0
- package/dist/components/MessageBubble/MessageReaction.d.ts +6 -0
- package/dist/components/MessageBubble/QuickReplies.d.ts +2 -0
- package/dist/components/Modals/FilePreviewModal/FilePreviewModal.d.ts +8 -10
- package/dist/components/Modals/FilePreviewModal/ImageLightbox.d.ts +4 -0
- package/dist/components/Modals/FilePreviewModal/PdfView.d.ts +4 -0
- package/dist/components/Modals/FilePreviewModal/ViewerChrome.d.ts +64 -0
- package/dist/components/Modals/MessageSearchModal/MessageHitResults.d.ts +5 -0
- package/dist/components/Modals/styledModalComponents.d.ts +0 -8
- package/dist/components/RoomComponents/LastMessageItem.d.ts +2 -0
- package/dist/components/RoomComponents/styled/LastMessageMedia.d.ts +19 -0
- package/dist/components/RoomComponents/styled/LastTextMessage.d.ts +2 -2
- package/dist/components/RoomComponents/styled/StyledRoomComponents.d.ts +10 -3
- package/dist/components/VideoCalls/VideoCallSession.d.ts +13 -0
- package/dist/components/styled/FileCard.d.ts +20 -0
- package/dist/components/styled/MediaSurface.d.ts +50 -0
- package/dist/components/styled/MessageImage.d.ts +9 -10
- package/dist/components/styled/SendInput.d.ts +12 -2
- package/dist/components/styled/StyledComponents.d.ts +69 -2
- package/dist/components/styled/StyledInputComponents/MediaComponents.d.ts +36 -24
- package/dist/components/styled/UnsupportedType.d.ts +1 -0
- package/dist/components/styled/VideoMessage.d.ts +11 -6
- package/dist/e2ee/callKey.d.ts +32 -0
- package/dist/e2ee/fileEnvelope.d.ts +16 -5
- package/dist/e2ee/omemo.d.ts +18 -0
- package/dist/firebase-messaging-sw.js +20 -3
- package/dist/{handleCopyClick-B70vLjJD.js → handleCopyClick-DGaZhy_u.js} +1 -1
- package/dist/helpers/albumLayout.d.ts +53 -0
- package/dist/helpers/attachments.d.ts +22 -1
- package/dist/helpers/avatarImageCache.d.ts +9 -0
- package/dist/helpers/caption.d.ts +63 -0
- package/dist/helpers/checkUniqueUsers.d.ts +2 -2
- package/dist/helpers/composerFocus.d.ts +22 -0
- package/dist/helpers/createMainMessageForThread.d.ts +1 -1
- package/dist/helpers/dedupeMembers.d.ts +10 -0
- package/dist/helpers/downloadFile.d.ts +19 -0
- package/dist/helpers/fileKind.d.ts +17 -0
- package/dist/helpers/historyPreloadConfig.d.ts +25 -0
- package/dist/helpers/historyPreloadScheduler.d.ts +3 -0
- package/dist/helpers/insertMessageWithDelimiter.d.ts +1 -0
- package/dist/helpers/isMessageSearchEnabled.d.ts +9 -0
- package/dist/helpers/jumpThread.d.ts +56 -0
- package/dist/helpers/jumpWindow.d.ts +73 -0
- package/dist/helpers/lastMessagePreview.d.ts +55 -0
- package/dist/helpers/mapWithConcurrency.d.ts +8 -0
- package/dist/helpers/mediaBox.d.ts +110 -0
- package/dist/helpers/mediaBubble.d.ts +39 -0
- package/dist/helpers/mediaLabel.d.ts +10 -0
- package/dist/helpers/mediaSendRetry.d.ts +25 -0
- package/dist/helpers/messageRuns.d.ts +10 -0
- package/dist/helpers/openThreadForJump.d.ts +26 -0
- package/dist/helpers/pdf/loadPdfjs.d.ts +8 -1
- package/dist/helpers/repairFilenameEncoding.d.ts +11 -0
- package/dist/helpers/roomImageGallery.d.ts +34 -0
- package/dist/helpers/roomUserCount.d.ts +19 -0
- package/dist/helpers/safeUrl.d.ts +15 -0
- package/dist/helpers/secureFileUrl.d.ts +4 -1
- package/dist/helpers/userResolver.d.ts +57 -0
- package/dist/helpers/viewerSender.d.ts +47 -0
- package/dist/helpers/xmppIdShape.d.ts +1 -1
- package/dist/hooks/useAvatarImage.d.ts +21 -0
- package/dist/hooks/useChatWrapperInit.d.ts +6 -0
- package/dist/hooks/useIsMobileViewport.d.ts +5 -0
- package/dist/hooks/useMediaProbe.d.ts +40 -0
- package/dist/hooks/usePaneWidthVar.d.ts +11 -0
- package/dist/hooks/usePdfThumbnail.d.ts +2 -2
- package/dist/hooks/usePrefersReducedMotion.d.ts +2 -0
- package/dist/hooks/useRoomDirectory.d.ts +11 -0
- package/dist/hooks/useSendMessage.d.ts +4 -1
- package/dist/{main-rJpE4hh-.js → main-CUB_w8AZ.js} +29136 -24217
- package/dist/main.css +1 -1
- package/dist/main.js +16 -16
- package/dist/networking/api-requests/auth.api.d.ts +2 -0
- package/dist/networking/api-requests/roomMembers.api.d.ts +18 -0
- package/dist/networking/callKeyStanza.d.ts +7 -0
- package/dist/networking/ethoraEvents.d.ts +44 -0
- package/dist/networking/stanzaHandlers.d.ts +6 -4
- package/dist/networking/xmpp/allRoomPresences.xmpp.d.ts +18 -1
- package/dist/networking/xmpp/presenceInRoom.xmpp.d.ts +1 -1
- package/dist/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +2 -0
- package/dist/networking/xmpp/sendMediaMessage.xmpp.d.ts +8 -0
- package/dist/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +2 -0
- package/dist/networking/xmppClient.d.ts +61 -2
- package/dist/roomStore/Middleware/jumpThreadMiddleware.d.ts +2 -0
- package/dist/roomStore/callSlice.d.ts +6 -1
- package/dist/roomStore/index.d.ts +22 -0
- package/dist/roomStore/roomsSlice.d.ts +51 -4
- package/dist/roomStore/safeKey.d.ts +12 -0
- package/dist/roomStore/usersSetCap.d.ts +15 -0
- package/dist/styles/motion.d.ts +22 -2
- package/dist/types/models/attachment.model.d.ts +19 -0
- package/dist/types/models/config.model.d.ts +98 -5
- package/dist/types/models/customComponents.model.d.ts +2 -0
- package/dist/types/models/message.model.d.ts +9 -0
- package/dist/types/models/modal.model.d.ts +19 -0
- package/dist/types/models/room.model.d.ts +30 -0
- package/dist/types/models/xmpp.model.d.ts +24 -1
- package/dist/utils/messageNotificationContent.d.ts +34 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -1
- package/public/firebase-messaging-sw.js +20 -3
- package/dist/FilePreviewModal-BcaSoO3-.js +0 -743
- package/dist/components/MainComponents/computeAnchoredScrollTop.d.ts +0 -9
- package/dist/components/Modals/NewChatModal/UpdNewChatModal.d.ts +0 -3
- package/dist/components/RoomComponents/styled/LastAudioMessage.d.ts +0 -6
- package/dist/components/RoomComponents/styled/LastMessageFile.d.ts +0 -6
- package/dist/components/RoomComponents/styled/LastMessagePhoto.d.ts +0 -6
- package/dist/components/RoomComponents/styled/LastMessageVideo.d.ts +0 -6
- package/dist/components/SortDropDown/index.d.ts +0 -8
|
@@ -28,6 +28,27 @@ export declare const serializeAttachments: (attachments: IAttachment[]) => strin
|
|
|
28
28
|
* predates this feature - and every message from a client that does not
|
|
29
29
|
* speak it - takes the second path.
|
|
30
30
|
*/
|
|
31
|
-
export declare const getMessageAttachments: (message?: Pick<IMessage, "attachments" | "location" | "locationPreview" | "mimetype" | "originalName" | "fileName" | "size">
|
|
31
|
+
export declare const getMessageAttachments: (message?: (Pick<IMessage, "attachments" | "location" | "locationPreview" | "mimetype" | "originalName" | "fileName" | "size"> & {
|
|
32
|
+
duration?: unknown;
|
|
33
|
+
mediaDuration?: unknown;
|
|
34
|
+
}) | null) => IAttachment[];
|
|
35
|
+
/**
|
|
36
|
+
* True when the message carries a file: flagged `isMediafile`, or (older
|
|
37
|
+
* senders that never set the flag) with a real file URL or attachment. The
|
|
38
|
+
* one test every surface uses for "is this a media bubble".
|
|
39
|
+
*/
|
|
40
|
+
export declare const isMediaMessage: (message?: (Partial<Pick<IMessage, "isDeleted" | "isMediafile" | "attachments">> & {
|
|
41
|
+
location?: unknown;
|
|
42
|
+
mimetype?: unknown;
|
|
43
|
+
originalName?: unknown;
|
|
44
|
+
fileName?: unknown;
|
|
45
|
+
locationPreview?: unknown;
|
|
46
|
+
}) | null) => boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Maps one object from POST /files/ `results` to the attachment a sent
|
|
49
|
+
* message carries. Field names here follow the API's own spelling
|
|
50
|
+
* (`originalname`, `filename`), not the wire attribute names.
|
|
51
|
+
*/
|
|
52
|
+
export declare const toUploadedAttachment: (raw: any) => IAttachment;
|
|
32
53
|
/** Name to show for an attachment, with the URL tail as the last resort. */
|
|
33
54
|
export declare const getAttachmentName: (attachment: IAttachment) => string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const FAILED_TTL_MS = 60000;
|
|
2
|
+
export declare const avatarIdentity: (url: string) => string;
|
|
3
|
+
/** The URL that already loaded for this file (any token), if any. */
|
|
4
|
+
export declare const getLoadedAvatarUrl: (url: string) => string | undefined;
|
|
5
|
+
export declare const markAvatarLoaded: (url: string) => void;
|
|
6
|
+
export declare const markAvatarFailed: (url: string) => void;
|
|
7
|
+
export declare const hasAvatarFailed: (url: string) => boolean;
|
|
8
|
+
/** Test helper. */
|
|
9
|
+
export declare const resetAvatarCache: () => void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { IMentionSpan } from '../types/types';
|
|
2
|
+
/**
|
|
3
|
+
* The text typed with a set of attachments, sent as part of the media
|
|
4
|
+
* message itself (never as a separate text stanza).
|
|
5
|
+
*
|
|
6
|
+
* `mentions` index into `text`, the same way a text message's mention spans
|
|
7
|
+
* index into its body.
|
|
8
|
+
*/
|
|
9
|
+
export interface CaptionInput {
|
|
10
|
+
text: string;
|
|
11
|
+
mentions?: IMentionSpan[];
|
|
12
|
+
}
|
|
13
|
+
export interface NormalizedCaption {
|
|
14
|
+
caption: string;
|
|
15
|
+
mentions?: IMentionSpan[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Mention spans for a caption, checked against the caption text: a span that
|
|
19
|
+
* no longer reads "@Name" at its offset is dropped. Undefined when nothing is
|
|
20
|
+
* left, so a caption without mentions carries no empty attribute.
|
|
21
|
+
*/
|
|
22
|
+
export declare const normalizeCaptionMentions: (value: unknown, caption: string) => IMentionSpan[] | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Turns what the composer typed into the caption that travels with the
|
|
25
|
+
* media. Undefined for blank text, which means "no caption".
|
|
26
|
+
*/
|
|
27
|
+
export declare const normalizeCaptionInput: (input?: CaptionInput | null) => NormalizedCaption | undefined;
|
|
28
|
+
/** What a media message's text is, and the @-mention spans that index into it. */
|
|
29
|
+
export interface ResolvedCaption {
|
|
30
|
+
text: string;
|
|
31
|
+
mentions?: IMentionSpan[];
|
|
32
|
+
}
|
|
33
|
+
export interface CaptionOptions {
|
|
34
|
+
/**
|
|
35
|
+
* The caller already knows this is a media message (a thin `lastMessage`
|
|
36
|
+
* that only says so by its id). Otherwise it is read off the message.
|
|
37
|
+
*/
|
|
38
|
+
isMedia?: boolean;
|
|
39
|
+
/** The message is sealed: only a caption that came out of the envelope counts. */
|
|
40
|
+
sealed?: boolean;
|
|
41
|
+
/** Extra names of the sender, which a body may merely echo. */
|
|
42
|
+
senderNames?: string[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The text of a media message, from whichever format carried it. The one
|
|
46
|
+
* place that knows the formats, so every surface (bubble, room list preview,
|
|
47
|
+
* notification, reply quote, copy) agrees:
|
|
48
|
+
*
|
|
49
|
+
* 1. `caption` (+ `captionMentions`): what this SDK sends. The only source
|
|
50
|
+
* for a sealed message, whose caption comes out of the envelope.
|
|
51
|
+
* 2. The caption under another name on `<data>` (`captionText`, `text`,
|
|
52
|
+
* `message`, `description`, `title`) from other clients.
|
|
53
|
+
* 3. A real `body`: older clients and other SDKs put the text in the
|
|
54
|
+
* `<body>` of the media message itself. "media", the file's name or URL,
|
|
55
|
+
* the sender's own name or e-mail and generated labels are not text.
|
|
56
|
+
*
|
|
57
|
+
* Never derived for a sealed message, and never for a message with no file.
|
|
58
|
+
*/
|
|
59
|
+
export declare const resolveMessageCaption: (message?: object | null, options?: CaptionOptions) => ResolvedCaption | undefined;
|
|
60
|
+
/** The caption a message carries, or undefined when it has none. */
|
|
61
|
+
export declare const getMessageCaption: (message?: object | null, options?: CaptionOptions) => string | undefined;
|
|
62
|
+
/** The mention spans of `getMessageCaption`'s text. */
|
|
63
|
+
export declare const getMessageCaptionMentions: (message?: object | null, options?: CaptionOptions) => IMentionSpan[] | undefined;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { IMessage, RoomMember } from '../types/types';
|
|
2
|
-
export declare const checkUniqueUsers: (messages: IMessage[]) =>
|
|
3
|
-
export declare const checkSingleUser: (usersSet: Record<string, RoomMember>, xmppUsername: string) => Promise<
|
|
2
|
+
export declare const checkUniqueUsers: (messages: IMessage[]) => any;
|
|
3
|
+
export declare const checkSingleUser: (usersSet: Record<string, RoomMember>, xmppUsername: string) => Promise<any>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hands "focus the message input" from the room list to the composer.
|
|
3
|
+
*
|
|
4
|
+
* The room list and the composer live in different subtrees, and the
|
|
5
|
+
* composer may only mount after the switch (or re-render later), so a plain
|
|
6
|
+
* callback would race it. Instead the request is parked here until the
|
|
7
|
+
* mounted composer for that room claims it. A claim is one-shot, so a single
|
|
8
|
+
* click focuses exactly once.
|
|
9
|
+
*/
|
|
10
|
+
type Listener = () => void;
|
|
11
|
+
/** Ask the composer for `roomJid` to take focus. Wakes mounted composers. */
|
|
12
|
+
export declare const requestComposerFocus: (roomJid: string | null | undefined) => void;
|
|
13
|
+
/** Mounted composers subscribe here to learn that a request arrived. */
|
|
14
|
+
export declare const subscribeComposerFocus: (listener: Listener) => (() => void);
|
|
15
|
+
/**
|
|
16
|
+
* Claims a pending request for `roomJid`. Returns true exactly once per
|
|
17
|
+
* request, and only for the room the request was made for.
|
|
18
|
+
*/
|
|
19
|
+
export declare const takeComposerFocusRequest: (roomJid: string | null | undefined) => boolean;
|
|
20
|
+
/** Test helper: drops any parked request and all listeners. */
|
|
21
|
+
export declare const resetComposerFocusForTests: () => void;
|
|
22
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { IMessage } from '../types/types';
|
|
2
|
-
export declare const createMainMessageForThread: (message: IMessage) => string;
|
|
2
|
+
export declare const createMainMessageForThread: (message: IMessage, t?: (key: string, vars?: Record<string, string | number>) => string) => string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RoomMember } from '../types/types';
|
|
2
|
+
/** Stable identity of a member: normalised xmppUsername local part, else _id. */
|
|
3
|
+
export declare const memberKey: (m: Partial<RoomMember> | null | undefined) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Drops repeated members (keeps the first occurrence, order preserved).
|
|
6
|
+
* The room directory endpoint can return the same user on several pages;
|
|
7
|
+
* duplicates would produce duplicate React keys and inflated counts.
|
|
8
|
+
* Members without any identity are dropped.
|
|
9
|
+
*/
|
|
10
|
+
export declare const dedupeMembers: <T extends Partial<RoomMember>>(list: ReadonlyArray<T> | null | undefined) => T[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type DownloadResult = 'blob' | 'direct' | 'http-error';
|
|
2
|
+
export interface DownloadDeps {
|
|
3
|
+
fetchImpl?: typeof fetch;
|
|
4
|
+
/** Hands a blob to the browser as a download. */
|
|
5
|
+
saveBlob?: (blob: Blob, fileName: string) => void;
|
|
6
|
+
/** Hands a plain URL to the browser as a download / new tab. */
|
|
7
|
+
saveUrl?: (url: string, fileName: string) => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Saves a media file. The blob route is preferred (it keeps the user's file
|
|
11
|
+
* name), but it needs CORS: <img>/<video>/<a> to a host such as
|
|
12
|
+
* secure-files.* never did, so a host without Access-Control-Allow-Origin
|
|
13
|
+
* makes fetch() reject with a bare TypeError. That says nothing about the
|
|
14
|
+
* file, so fall back to handing the URL to the browser directly.
|
|
15
|
+
*
|
|
16
|
+
* A reachable server that answers with an error status is different: its
|
|
17
|
+
* body is an error page, not the file, so nothing is saved.
|
|
18
|
+
*/
|
|
19
|
+
export declare function downloadFile(url: string, fileName: string, deps?: DownloadDeps): Promise<DownloadResult>;
|
|
@@ -11,7 +11,24 @@ export type FileKind = 'image' | 'video' | 'audio' | 'pdf' | 'file';
|
|
|
11
11
|
/** Lowercase extension without the dot; '' when the name carries none. */
|
|
12
12
|
export declare const getFileExtension: (name?: string) => string;
|
|
13
13
|
export declare const isPdfFile: (mimetype?: string, name?: string) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* SVG is deliberately not an image here. It does not render reliably as a
|
|
16
|
+
* message picture, can be blocked by the host's CSP, and is a script
|
|
17
|
+
* injection vector when opened directly. It is treated as a plain file.
|
|
18
|
+
*/
|
|
19
|
+
export declare const isSvgFile: (mimetype?: string, name?: string) => boolean;
|
|
20
|
+
/** True when the file renders as a picture (never for SVG). */
|
|
21
|
+
export declare const isRasterImage: (mimetype?: string, name?: string) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The mimetype a file name's extension stands for, for the pictures, videos,
|
|
24
|
+
* audio and PDFs a bubble can show. Undefined for anything else.
|
|
25
|
+
*/
|
|
26
|
+
export declare const inferMimeFromName: (name?: string) => string | undefined;
|
|
27
|
+
/** A mimetype that says nothing: empty, or the storage's "could not sniff". */
|
|
28
|
+
export declare const isGenericMime: (mimetype?: string) => boolean;
|
|
14
29
|
export declare const getFileKind: (mimetype?: string, name?: string) => FileKind;
|
|
15
30
|
export declare const formatFileSize: (sizeInBytes?: string | number) => string;
|
|
16
31
|
/** Middle-truncates the base name, always keeping the extension visible. */
|
|
17
32
|
export declare const formatFileName: (name: string, maxLength: number) => string;
|
|
33
|
+
/** An animated-picture candidate: image/gif, or a name or URL ending in .gif. */
|
|
34
|
+
export declare const isGifFile: (mimetype?: string, nameOrUrl?: string) => boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IConfig } from '../types/types';
|
|
2
|
+
export type HistoryPreloadMode = 'staged' | 'all' | 'off';
|
|
3
|
+
export interface ResolvedHistoryPreload {
|
|
4
|
+
/**
|
|
5
|
+
* 'legacy' only when a host explicitly pinned
|
|
6
|
+
* `historyQoS.stagedPreloadEnabled: false`: the old cache catch-up path
|
|
7
|
+
* (updateMessagesTillLast). Everything else resolves to a public mode.
|
|
8
|
+
*/
|
|
9
|
+
mode: HistoryPreloadMode | 'legacy';
|
|
10
|
+
/** Rooms (by recent activity) the background sweep covers. 0 = no cap. */
|
|
11
|
+
topRooms: number;
|
|
12
|
+
concurrency: number;
|
|
13
|
+
firstPassSize: number;
|
|
14
|
+
secondPassSize: number;
|
|
15
|
+
}
|
|
16
|
+
export declare const DEFAULT_PRELOAD_TOP_ROOMS = 8;
|
|
17
|
+
export declare const DEFAULT_PRELOAD_CONCURRENCY = 3;
|
|
18
|
+
export declare const TEASER_ROOM_FACTOR = 4;
|
|
19
|
+
/**
|
|
20
|
+
* One place that turns `config.historyPreload` (and the older
|
|
21
|
+
* `historyQoS.stagedPreload*` / `preloadTopKRooms` names, still honoured)
|
|
22
|
+
* into the numbers the preload sweeps use. Staged preload is the default:
|
|
23
|
+
* the sweep covers the top N rooms, every other room loads when it is opened.
|
|
24
|
+
*/
|
|
25
|
+
export declare const resolveHistoryPreloadConfig: (config?: Pick<IConfig, "historyPreload" | "historyQoS"> | null) => ResolvedHistoryPreload;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { default as XmppClient } from '../networking/xmppClient';
|
|
2
|
+
import { IRoom } from '../types/types';
|
|
2
3
|
interface HistoryPreloadSchedulerOptions {
|
|
3
4
|
client: XmppClient;
|
|
4
5
|
signal?: AbortSignal;
|
|
@@ -10,6 +11,8 @@ interface HistoryPreloadSchedulerOptions {
|
|
|
10
11
|
defaultRoomJids?: string[];
|
|
11
12
|
forceReload?: boolean;
|
|
12
13
|
completionState?: 'done' | 'partial';
|
|
14
|
+
skipApiPreview?: boolean;
|
|
13
15
|
}
|
|
16
|
+
export declare const hasApiPreview: (room?: IRoom) => boolean;
|
|
14
17
|
export declare const runHistoryPreloadScheduler: (options: HistoryPreloadSchedulerOptions) => Promise<void>;
|
|
15
18
|
export default runHistoryPreloadScheduler;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IMessage } from '../types/types';
|
|
2
|
+
export declare function deepMerge(target: any, source: any): any;
|
|
2
3
|
export declare function insertMessageWithDelimiter(roomMessages: Partial<IMessage>[], message: IMessage, lastViewedTimestamp: {
|
|
3
4
|
toString: () => string;
|
|
4
5
|
}): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IConfig } from '../types/types';
|
|
2
|
+
/**
|
|
3
|
+
* The single rule for message search. It is opt-in: the host must set
|
|
4
|
+
* `enableMessageSearch` AND provide an `appId` (the archive endpoint is scoped
|
|
5
|
+
* by app). A leftover `disableMessageSearch: true` still wins as a hard off.
|
|
6
|
+
* Every entry point (header button, profile entry, chat-list matches, the
|
|
7
|
+
* Ctrl/Cmd+F shortcut, the modal itself) goes through this helper.
|
|
8
|
+
*/
|
|
9
|
+
export declare const isMessageSearchEnabled: (config?: Pick<IConfig, "enableMessageSearch" | "disableMessageSearch" | "appId"> | null) => boolean;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { IMessage } from '../types/types';
|
|
2
|
+
/** How long a jump request may stay pending before it is dropped. */
|
|
3
|
+
export declare const JUMP_TTL_MS = 60000;
|
|
4
|
+
/**
|
|
5
|
+
* Index of the message a jump names.
|
|
6
|
+
*
|
|
7
|
+
* By id first (message.id is the MAM stanza id for history, xmppId the client
|
|
8
|
+
* message id). When the request has no id at all, which is common for archive
|
|
9
|
+
* rows, by content: same text, sent within a few seconds of the same time.
|
|
10
|
+
*/
|
|
11
|
+
export declare const findMessageIndex: (messages: IMessage[], ids: string[], content?: {
|
|
12
|
+
createdAt?: string;
|
|
13
|
+
body?: string;
|
|
14
|
+
}) => number;
|
|
15
|
+
/**
|
|
16
|
+
* Where a jump to a thread REPLY lands, and the thread panel's parent message
|
|
17
|
+
* when that message is not in the room's live list.
|
|
18
|
+
*
|
|
19
|
+
* Replies never appear in the main list (it hides them unless they are also
|
|
20
|
+
* shown in the channel), so a jump to one has to open its parent's thread and
|
|
21
|
+
* highlight the reply there. Two things have to be remembered for that:
|
|
22
|
+
* - which list owns the jump: `at` is the pending request this entry serves,
|
|
23
|
+
* `parentId` the thread that owns it. The main list stays inert for a jump
|
|
24
|
+
* that has an owner thread, and a thread list stays inert for any jump that
|
|
25
|
+
* is not its own (see useJumpToMessage);
|
|
26
|
+
* - the parent and the replies when they exist only in a jump window or came
|
|
27
|
+
* from a separate fetch: the thread panel reads them from here, so it does
|
|
28
|
+
* not depend on the window that found them still being on screen.
|
|
29
|
+
*
|
|
30
|
+
* Kept outside redux on purpose: the room slice's thread flag lives on
|
|
31
|
+
* messages of the live list, which is exactly what these messages are not in.
|
|
32
|
+
*/
|
|
33
|
+
export interface JumpThreadState {
|
|
34
|
+
roomJID: string;
|
|
35
|
+
parentId: string;
|
|
36
|
+
/** The pending request this entry serves; null for a thread opened by hand. */
|
|
37
|
+
at: number | null;
|
|
38
|
+
/** Set only when the parent is not in the room's live messages. */
|
|
39
|
+
parent: IMessage | null;
|
|
40
|
+
/** Replies known to belong to the thread beyond the live list. */
|
|
41
|
+
replies: IMessage[];
|
|
42
|
+
}
|
|
43
|
+
export declare const getJumpThread: () => JumpThreadState | null;
|
|
44
|
+
export declare const setJumpThread: (next: JumpThreadState | null) => void;
|
|
45
|
+
export declare const clearJumpThread: () => void;
|
|
46
|
+
export declare const subscribeJumpThread: (listener: () => void) => (() => void);
|
|
47
|
+
export declare const useJumpThread: () => JumpThreadState | null;
|
|
48
|
+
/** The thread that owns the jump `at`, or null when the main list does. */
|
|
49
|
+
export declare const ownerThreadFor: (thread: JumpThreadState | null, at: number | undefined) => string | null;
|
|
50
|
+
/**
|
|
51
|
+
* The parent id of a message that is a thread reply and nothing else, or null:
|
|
52
|
+
* a reply also shown in the channel is in the main list, so it needs no thread.
|
|
53
|
+
*/
|
|
54
|
+
export declare const replyParentId: (message: IMessage) => string | null;
|
|
55
|
+
/** `a` then `b`, one entry per message id, oldest first. */
|
|
56
|
+
export declare const mergeById: (a: IMessage[], b: IMessage[]) => IMessage[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { HistoryWindowPage } from '../networking/xmppClient';
|
|
2
|
+
import { JumpWindow } from '../roomStore/roomsSlice';
|
|
3
|
+
import { IMessage } from '../types/types';
|
|
4
|
+
/** Messages shown above / below the target when a window opens (tunable). */
|
|
5
|
+
export declare const JUMP_WINDOW_BEFORE = 10;
|
|
6
|
+
export declare const JUMP_WINDOW_AFTER = 10;
|
|
7
|
+
/** Page size for loading more once the reader scrolls toward either edge. */
|
|
8
|
+
export declare const JUMP_WINDOW_PAGE = 20;
|
|
9
|
+
/** The slice of the XMPP client the window helpers need. */
|
|
10
|
+
export interface HistoryWindowClient {
|
|
11
|
+
getHistoryWindow: (chatJID: string, max: number, cursor: {
|
|
12
|
+
before?: number;
|
|
13
|
+
after?: number;
|
|
14
|
+
start?: string;
|
|
15
|
+
end?: string;
|
|
16
|
+
}) => Promise<HistoryWindowPage>;
|
|
17
|
+
}
|
|
18
|
+
export type JumpWindowResult =
|
|
19
|
+
/** The window around the target is ready to be shown. */
|
|
20
|
+
{
|
|
21
|
+
status: 'found';
|
|
22
|
+
window: JumpWindow;
|
|
23
|
+
}
|
|
24
|
+
/** The server answered, and the target is not in the archive. */
|
|
25
|
+
| {
|
|
26
|
+
status: 'missing';
|
|
27
|
+
}
|
|
28
|
+
/** No usable id, or a request failed: use the paging path instead. */
|
|
29
|
+
| {
|
|
30
|
+
status: 'unavailable';
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* How far apart the archive's timestamp and the transcript's may be for the
|
|
34
|
+
* same message. They are recorded by different components, so they are not
|
|
35
|
+
* identical, but never more than a moment apart.
|
|
36
|
+
*/
|
|
37
|
+
export declare const CONTENT_MATCH_WINDOW_MS = 5000;
|
|
38
|
+
/** What a hit with no archive id can be located by. */
|
|
39
|
+
export interface JumpContent {
|
|
40
|
+
createdAt?: string;
|
|
41
|
+
body?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare const mamIdCandidates: (ids: string[]) => string[];
|
|
44
|
+
/**
|
|
45
|
+
* Fetches about JUMP_WINDOW_BEFORE messages before the target, the target and
|
|
46
|
+
* about JUMP_WINDOW_AFTER after it: two MAM queries in parallel (the target
|
|
47
|
+
* itself comes with the "after" page, asked from one microsecond before it).
|
|
48
|
+
* Nothing is written to the store here.
|
|
49
|
+
*/
|
|
50
|
+
export declare function loadJumpWindow(client: HistoryWindowClient, roomJID: string, ids: string[], content?: JumpContent): Promise<JumpWindowResult>;
|
|
51
|
+
/**
|
|
52
|
+
* For a hit with no usable archive id (older archive documents carry neither a
|
|
53
|
+
* stanza id nor a message id): ask the server for the rows within a few
|
|
54
|
+
* seconds of its timestamp (MAM start/end filter), find the one with the same
|
|
55
|
+
* text, and continue exactly as the id-based window does with that row's id.
|
|
56
|
+
* The server's answer is final: 'missing' only when it said the range holds no
|
|
57
|
+
* such row.
|
|
58
|
+
*/
|
|
59
|
+
export declare function loadJumpWindowByTime(client: HistoryWindowClient, roomJID: string, content: JumpContent): Promise<JumpWindowResult>;
|
|
60
|
+
export interface OlderWindowPage {
|
|
61
|
+
messages: IMessage[];
|
|
62
|
+
olderCursor: number | null;
|
|
63
|
+
hasOlder: boolean;
|
|
64
|
+
}
|
|
65
|
+
export interface NewerWindowPage {
|
|
66
|
+
messages: IMessage[];
|
|
67
|
+
newerCursor: number | null;
|
|
68
|
+
hasNewer: boolean;
|
|
69
|
+
}
|
|
70
|
+
/** The page above the window; null when the request failed. */
|
|
71
|
+
export declare function loadOlderWindowPage(client: HistoryWindowClient, window: JumpWindow): Promise<OlderWindowPage | null>;
|
|
72
|
+
/** The page below the window; null when the request failed. */
|
|
73
|
+
export declare function loadNewerWindowPage(client: HistoryWindowClient, window: JumpWindow): Promise<NewerWindowPage | null>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LastMessage } from '../types/types';
|
|
2
|
+
/**
|
|
3
|
+
* What the room list draws for a media last message.
|
|
4
|
+
*
|
|
5
|
+
* One derivation for every source of a last message: the live stanza that
|
|
6
|
+
* updates roomsSlice, the history loaded from MAM, and the flattened
|
|
7
|
+
* `lastMessage` the backend embeds in `GET /chats/my`. All three reach the row
|
|
8
|
+
* as the same loose message object, and since multi-attachment messages the
|
|
9
|
+
* file fields may sit in `attachments` only, with no top level `location` or
|
|
10
|
+
* `mimetype`, so the row reads through getMessageAttachments like the bubble.
|
|
11
|
+
*/
|
|
12
|
+
export type MediaPreviewKind = 'image' | 'video' | 'voice' | 'audio' | 'file'
|
|
13
|
+
/** Media of unknown type: a thin backend payload that only says "media". */
|
|
14
|
+
| 'attachment' | 'sealed';
|
|
15
|
+
export interface MediaPreview {
|
|
16
|
+
kind: MediaPreviewKind;
|
|
17
|
+
/** How many attachments the message carries (at least 1). */
|
|
18
|
+
count: number;
|
|
19
|
+
/** Thumbnail source, before the viewer's file token is appended. */
|
|
20
|
+
thumbUrl?: string;
|
|
21
|
+
/** The single file's name, for the file kind. */
|
|
22
|
+
fileName?: string;
|
|
23
|
+
/** "0:12" for a voice message or audio file, when the duration is known. */
|
|
24
|
+
durationText?: string;
|
|
25
|
+
/** The text the sender typed next to the media, if any. */
|
|
26
|
+
caption?: string;
|
|
27
|
+
}
|
|
28
|
+
type PreviewSource = Partial<LastMessage> & {
|
|
29
|
+
attachments?: unknown;
|
|
30
|
+
fileName?: string;
|
|
31
|
+
location?: string;
|
|
32
|
+
/** The caption the sender typed with the files, carried on the message. */
|
|
33
|
+
caption?: unknown;
|
|
34
|
+
duration?: unknown;
|
|
35
|
+
mediaDuration?: unknown;
|
|
36
|
+
};
|
|
37
|
+
/** True when the last message should render as media rather than text. */
|
|
38
|
+
export declare const hasMediaPreview: (message?: PreviewSource | null) => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* `sealed` is true for an e2ee room or a message flagged `clientEncrypted`.
|
|
41
|
+
* Sealed content never gets a thumbnail: the server holds ciphertext, so the
|
|
42
|
+
* row shows the icon and the label only.
|
|
43
|
+
*/
|
|
44
|
+
export declare const deriveMediaPreview: (message?: PreviewSource | null, options?: {
|
|
45
|
+
sealed?: boolean;
|
|
46
|
+
senderNames?: string[];
|
|
47
|
+
}) => MediaPreview | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Which message the room row previews: the newest real message already in the
|
|
50
|
+
* room's `messages[]` (a live stanza or history), else the backend's
|
|
51
|
+
* `lastMessage` seed. Entries with neither text nor media (markers, receipts,
|
|
52
|
+
* state-only stanzas) are skipped.
|
|
53
|
+
*/
|
|
54
|
+
export declare const pickPreviewSource: <T extends PreviewSource>(messages: T[] | undefined, lastMessage?: T | null) => T | undefined;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `worker` over `items` with at most `limit` calls in flight, and
|
|
3
|
+
* resolves with the results in INPUT order, whatever order the calls finish
|
|
4
|
+
* in. Workers that throw are reported as rejected entries instead of
|
|
5
|
+
* aborting the rest of the batch, so one failed upload never stops its
|
|
6
|
+
* siblings.
|
|
7
|
+
*/
|
|
8
|
+
export declare function mapWithConcurrency<T, R>(items: readonly T[], limit: number, worker: (item: T, index: number) => Promise<R>): Promise<PromiseSettledResult<R>[]>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure sizing helpers for in-bubble media. Kept free of React and the DOM so
|
|
3
|
+
* the rules (clamping, never upscaling, duration formatting) are testable.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Reference box a solo picture is fitted into. It is the largest the
|
|
7
|
+
* responsive media width ever gets (see MEDIA_WIDTH_CSS), so a picture is
|
|
8
|
+
* never upscaled past what the widest pane could show; the real, smaller cap
|
|
9
|
+
* for the current pane is applied in CSS from the shared width variable.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SOLO_IMAGE_MAX_WIDTH_PX = 560;
|
|
12
|
+
export declare const SOLO_IMAGE_MAX_HEIGHT_PX = 784;
|
|
13
|
+
/** Box used while a solo image's dimensions are still unknown: 4:3. */
|
|
14
|
+
export declare const SOLO_DEFAULT_BOX: {
|
|
15
|
+
readonly width: 560;
|
|
16
|
+
readonly height: 420;
|
|
17
|
+
};
|
|
18
|
+
/** Box used while a solo video's dimensions are unknown: 16:9. */
|
|
19
|
+
export declare const SOLO_VIDEO_DEFAULT_BOX: {
|
|
20
|
+
readonly width: 560;
|
|
21
|
+
readonly height: 315;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The ONE width of the media block of a message: the album grid, a solo
|
|
25
|
+
* picture or video and the file cards under them all share it.
|
|
26
|
+
*
|
|
27
|
+
* `--ethora-pane-w` is the width of the message list pane (not the
|
|
28
|
+
* viewport), written by the message list's ResizeObserver, so a chat embedded
|
|
29
|
+
* in a narrow host sizes itself to that host. A container query would do the
|
|
30
|
+
* same, but `container-type` contains layout, which turns the list into the
|
|
31
|
+
* containing block of the context menu's `position: fixed` overlay.
|
|
32
|
+
*
|
|
33
|
+
* pane 390 -> 300 (the bubble then clips it to its own width)
|
|
34
|
+
* pane 700 -> 340
|
|
35
|
+
* pane 1000 -> 430
|
|
36
|
+
* pane 1400 -> 550
|
|
37
|
+
* pane 1600+ -> 560
|
|
38
|
+
*/
|
|
39
|
+
export declare const MEDIA_WIDTH_VAR = "--ethora-media-w";
|
|
40
|
+
export declare const PANE_WIDTH_VAR = "--ethora-pane-w";
|
|
41
|
+
export declare const MEDIA_WIDTH_RULE = "clamp(300px, calc(var(--ethora-pane-w, 800px) * 0.3 + 130px), 560px)";
|
|
42
|
+
/** Use inside a stylesheet or inline style; falls back when the list did not set it. */
|
|
43
|
+
export declare const MEDIA_WIDTH_CSS = "var(--ethora-media-w, 340px)";
|
|
44
|
+
/** A lone file card is capped so it does not become a giant bar. */
|
|
45
|
+
export declare const FILE_ONLY_MAX_WIDTH_PX = 360;
|
|
46
|
+
/** Tallest a solo picture gets, relative to the media width. */
|
|
47
|
+
export declare const SOLO_MAX_HEIGHT_FACTOR = 1.4;
|
|
48
|
+
/** CSS length of the tallest a solo picture may be: 60vh, or 1.4 x the media width. */
|
|
49
|
+
export declare const SOLO_MAX_HEIGHT_CSS = "min(60vh, calc(1.4 * var(--ethora-media-w, 340px)))";
|
|
50
|
+
export interface MediaBox {
|
|
51
|
+
width: number;
|
|
52
|
+
height: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Fit a natural size inside a max box, keeping its aspect ratio. Never
|
|
56
|
+
* upscales: a picture smaller than the box keeps its natural size. Returns
|
|
57
|
+
* null when either dimension is unknown or not positive.
|
|
58
|
+
*/
|
|
59
|
+
export declare const fitMediaBox: (naturalWidth: number | undefined, naturalHeight: number | undefined, maxWidth?: number, maxHeight?: number) => MediaBox | null;
|
|
60
|
+
/** A picture whose longest side is below this is shown at its natural size. */
|
|
61
|
+
export declare const SOLO_TINY_MAX_SIDE_PX = 120;
|
|
62
|
+
/** Smallest frame drawn around a tiny picture. */
|
|
63
|
+
export declare const SOLO_MIN_FRAME_PX = 64;
|
|
64
|
+
/** Beyond this long:short ratio a picture is cropped rather than letterboxed. */
|
|
65
|
+
export declare const SOLO_EXTREME_RATIO = 3;
|
|
66
|
+
/** Shorter side of the box of an extreme-ratio picture. */
|
|
67
|
+
export declare const SOLO_MIN_SHORT_SIDE_PX = 96;
|
|
68
|
+
/**
|
|
69
|
+
* How the picture sits in its box.
|
|
70
|
+
* fit - same aspect ratio as the box, fills it.
|
|
71
|
+
* cover - extreme ratio: fills the box and is gently cropped from the centre.
|
|
72
|
+
* natural - smaller than its frame: drawn at its own size, centred on a tile.
|
|
73
|
+
*/
|
|
74
|
+
export type SoloMode = 'fit' | 'cover' | 'natural';
|
|
75
|
+
export interface SoloBox {
|
|
76
|
+
/** The frame (stage) size in CSS px. */
|
|
77
|
+
width: number;
|
|
78
|
+
height: number;
|
|
79
|
+
/** The drawn picture size; equals the frame unless `mode` is natural. */
|
|
80
|
+
imageWidth: number;
|
|
81
|
+
imageHeight: number;
|
|
82
|
+
mode: SoloMode;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The size policy of a single picture in a bubble.
|
|
86
|
+
*
|
|
87
|
+
* - Inside the max box (560 x 784 by default) at its own aspect ratio,
|
|
88
|
+
* never upscaled. CSS narrows it to the pane's media width and height.
|
|
89
|
+
* - Tiny (longest side < 120): natural size on a tile of at least 64px.
|
|
90
|
+
* - Extreme ratio (beyond 3:1 or 1:3): the shorter side is lifted to 96px
|
|
91
|
+
* and the picture is cover-cropped (the viewer shows all of it).
|
|
92
|
+
*
|
|
93
|
+
* `ratioOnly` is for a size read from a downscaled preview: only its aspect
|
|
94
|
+
* ratio is trusted, so the box is scaled up to the real maximum instead of
|
|
95
|
+
* staying thumbnail-sized (which would jump when the full file arrives).
|
|
96
|
+
* Returns null while a dimension is unknown.
|
|
97
|
+
*/
|
|
98
|
+
export declare const computeSoloBox: (naturalWidth: number | undefined, naturalHeight: number | undefined, options?: {
|
|
99
|
+
ratioOnly?: boolean;
|
|
100
|
+
maxWidth?: number;
|
|
101
|
+
maxHeight?: number;
|
|
102
|
+
}) => SoloBox | null;
|
|
103
|
+
/** Parses a dimension that may arrive as a number or a numeric string. */
|
|
104
|
+
export declare const parseDimension: (value: unknown) => number | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Formats a duration in seconds as m:ss, or h:mm:ss past one hour. Returns
|
|
107
|
+
* an empty string when the value is missing or not a positive number, so
|
|
108
|
+
* callers can skip the chip entirely.
|
|
109
|
+
*/
|
|
110
|
+
export declare const formatDuration: (seconds: string | number | undefined) => string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { IAttachment } from '../types/types';
|
|
2
|
+
/**
|
|
3
|
+
* How a message's files share one column:
|
|
4
|
+
* - album: two or more pictures or videos, one grid; cards stretch to it.
|
|
5
|
+
* - solo: one picture or video; the block shrink-wraps it (at most the media
|
|
6
|
+
* width) and any cards stretch to that.
|
|
7
|
+
* - files: documents only; the media width, capped so a lone card is no bar.
|
|
8
|
+
* - plain: voice notes only, which keep their own width.
|
|
9
|
+
*/
|
|
10
|
+
export type MediaBlockMode = 'album' | 'solo' | 'files' | 'plain';
|
|
11
|
+
export interface AttachmentItem {
|
|
12
|
+
attachment: IAttachment;
|
|
13
|
+
/** Position in the message's attachments, used for retry. */
|
|
14
|
+
index: number;
|
|
15
|
+
}
|
|
16
|
+
export interface AttachmentPartition {
|
|
17
|
+
/** Pictures and videos rendered as one album grid (empty for a lone one). */
|
|
18
|
+
tiles: AttachmentItem[];
|
|
19
|
+
/** Everything else, rendered one after another below the grid. */
|
|
20
|
+
rows: AttachmentItem[];
|
|
21
|
+
}
|
|
22
|
+
export declare const attachmentKind: (attachment: IAttachment) => import('./fileKind').FileKind;
|
|
23
|
+
/**
|
|
24
|
+
* Splits a message's files into the album and the rows below it. Pictures and
|
|
25
|
+
* videos always come first, in the order they were picked; the remaining files
|
|
26
|
+
* (documents, svg, pdf, audio) follow, also in their picked order. A lone
|
|
27
|
+
* picture is not an album: it stays a row, but it still leads the message.
|
|
28
|
+
*/
|
|
29
|
+
export declare const partitionAttachments: (attachments: IAttachment[]) => AttachmentPartition;
|
|
30
|
+
/** The block mode for a message's files, from the partition it renders. */
|
|
31
|
+
export declare const getMediaBlockMode: (attachments: IAttachment[]) => MediaBlockMode;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the time can sit as a glass pill over the media (Telegram style):
|
|
34
|
+
* only when the bubble is pictures and videos and nothing else. A caption or
|
|
35
|
+
* any card or audio row puts the time on its own line at the bottom right.
|
|
36
|
+
*/
|
|
37
|
+
export declare const usesMediaOverlayTime: (attachments: IAttachment[], options?: {
|
|
38
|
+
hasCaption?: boolean;
|
|
39
|
+
}) => boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { getMessageAttachments } from './attachments';
|
|
2
|
+
type Translate = (key: string, vars?: Record<string, string | number>) => string;
|
|
3
|
+
/**
|
|
4
|
+
* What to call a media message's files when there is no text to show:
|
|
5
|
+
* "Photo", "Video", "Voice message", "File", "3 photos", "2 attachments".
|
|
6
|
+
* Empty when the message carries no file. The kind comes from the normalised
|
|
7
|
+
* attachment, so a file sent with no mimetype is still a photo by its name.
|
|
8
|
+
*/
|
|
9
|
+
export declare const getMediaLabel: (message: Parameters<typeof getMessageAttachments>[0], t: Translate) => string;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry hooks for media messages whose uploads did not all land.
|
|
3
|
+
*
|
|
4
|
+
* The send closure lives inside useSendMessage, but the bubble that shows
|
|
5
|
+
* "Retry" is somewhere else in the tree and has no access to it. The send
|
|
6
|
+
* registers its retry runner under the message id while the message is
|
|
7
|
+
* unsent, and the bubble looks it up here. The runner is removed as soon as
|
|
8
|
+
* the message is delivered.
|
|
9
|
+
*/
|
|
10
|
+
export type MediaRetryRunner = (indices?: number[]) => Promise<boolean>;
|
|
11
|
+
export declare const registerMediaRetry: (messageId: string, runner: MediaRetryRunner) => void;
|
|
12
|
+
export declare const unregisterMediaRetry: (messageId: string) => void;
|
|
13
|
+
export declare const hasMediaRetry: (messageId: string) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Re-runs the uploads still missing for `messageId` (only `index` / `indices`
|
|
16
|
+
* when given), then sends the message once every file is in. Resolves to
|
|
17
|
+
* false when nothing is registered or the attempt did not deliver.
|
|
18
|
+
*/
|
|
19
|
+
export declare const retryMediaSend: (messageId: string, index?: number | number[]) => Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Asks for one file of `messageId` to be retried. Requests made in the same
|
|
22
|
+
* tick are merged into one run: the runner takes a single run at a time, so
|
|
23
|
+
* a separate call per tile would drop every retry after the first.
|
|
24
|
+
*/
|
|
25
|
+
export declare const requestMediaRetry: (messageId: string, index: number) => void;
|