@ethora/chat-component 26.9.8 → 26.10.3
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 +2 -1
- package/dist/{ChatProfileModal-3UH0H5Ec.js → ChatProfileModal-D73ioMj9.js} +44 -44
- package/dist/FilePreviewModal-BeGa8s4u.js +1763 -0
- package/dist/{ManageDataModal-BblTuE9g.js → ManageDataModal-DxhvKwLo.js} +17 -17
- package/dist/{MarkdownBody-RMEx8yW2.js → MarkdownBody-fzjy9KUP.js} +1 -1
- package/dist/{MessageSearchModal-CrYR2EH8.js → MessageSearchModal-3I0b3Nut.js} +40 -40
- package/dist/{PublicChatsModal-Bi5r4mrV.js → PublicChatsModal-Dzwl63q-.js} +19 -19
- package/dist/{SideDrawer-CDAoobvX.js → SideDrawer-BPebE2tZ.js} +37 -37
- package/dist/{UserProfileModal-4Frgvam-.js → UserProfileModal-BS5FZdJb.js} +10 -10
- package/dist/{UserSettingsModal-C-mb-9cb.js → UserSettingsModal-pBqmF_r2.js} +5 -5
- package/dist/{VideoCallOverlay-92dFLb9U.js → VideoCallOverlay-PIg8Z5Xr.js} +3824 -3708
- package/dist/{VisibilityModal-zIAV6dAP.js → VisibilityModal-CnBRSRQ9.js} +11 -11
- package/dist/api.config.d.ts +0 -23
- package/dist/assets/icons.d.ts +2 -3
- package/dist/components/ContextMenu/ContextMenuComponents.d.ts +24 -8
- package/dist/components/ExampleComponents/CustomMessage.d.ts +27 -9
- package/dist/components/Files/fileCategory.d.ts +4 -2
- package/dist/components/MainComponents/AlbumGrid.d.ts +22 -0
- package/dist/components/MainComponents/AttachmentList.d.ts +6 -6
- package/dist/components/MainComponents/ChooseChatMessage.d.ts +12 -4
- package/dist/components/MainComponents/EditWrapper.d.ts +12 -4
- 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/MessageList.d.ts +2 -1
- package/dist/components/MainComponents/SystemMessage.d.ts +3 -1
- package/dist/components/MessageBubble/CustomDivider.d.ts +3 -1
- 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/MessageReply.d.ts +3 -1
- 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 +100 -0
- package/dist/components/Modals/MessageSearchModal/MessageHitResults.d.ts +3 -1
- package/dist/components/Modals/ModalReportChat/ModalReportChat.d.ts +3 -1
- package/dist/components/Modals/NewChatModal/styledNewChatComponents.d.ts +15 -5
- package/dist/components/Modals/SettingsModals/SharedStyledComponents.d.ts +27 -15
- package/dist/components/Modals/SettingsModals/Visibility/StyledComponents.d.ts +18 -6
- package/dist/components/Modals/SideDrawer/DrawerProfileParts.d.ts +27 -9
- package/dist/components/Modals/SideDrawer/SideDrawer.d.ts +39 -13
- package/dist/components/Modals/motionVariants.d.ts +8 -10
- package/dist/components/Modals/styledModalComponents.d.ts +53 -25
- package/dist/components/Notification/index.d.ts +3 -1
- 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 +29 -8
- package/dist/components/UsersList/StyledComponents.d.ts +18 -6
- package/dist/components/VideoCalls/VideoCallSession.d.ts +13 -0
- package/dist/components/styled/ChatWrapperBox.d.ts +9 -2
- package/dist/components/styled/DateLabel.d.ts +3 -1
- package/dist/components/styled/FileCard.d.ts +20 -0
- package/dist/components/styled/Loader.d.ts +3 -1
- package/dist/components/styled/MediaModal.d.ts +6 -2
- package/dist/components/styled/MediaSurface.d.ts +56 -0
- package/dist/components/styled/MessageImage.d.ts +9 -10
- package/dist/components/styled/NewMessageLabel.d.ts +3 -1
- package/dist/components/styled/RoomListComponents/index.d.ts +66 -22
- package/dist/components/styled/SendInput.d.ts +12 -2
- package/dist/components/styled/StyledComponents.d.ts +199 -41
- package/dist/components/styled/StyledInputComponents/MediaComponents.d.ts +78 -24
- package/dist/components/styled/StyledInputComponents/StyledInputComponents.d.ts +60 -22
- package/dist/components/styled/StyledInputComponents/VideoComponents.d.ts +18 -6
- package/dist/components/styled/TreadLabel.d.ts +3 -1
- package/dist/components/styled/UnsupportedType.d.ts +1 -0
- package/dist/components/styled/VideoMessage.d.ts +13 -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-BtXos0AJ.js → handleCopyClick-vPvh9hOU.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/composerFocus.d.ts +22 -0
- package/dist/helpers/createMainMessageForThread.d.ts +1 -1
- package/dist/helpers/downloadFile.d.ts +19 -0
- package/dist/helpers/fileKind.d.ts +17 -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 +67 -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/receiptState.d.ts +48 -0
- package/dist/helpers/repairFilenameEncoding.d.ts +11 -0
- package/dist/helpers/roomImageGallery.d.ts +34 -0
- package/dist/helpers/safeUrl.d.ts +15 -0
- package/dist/helpers/secureFileUrl.d.ts +4 -1
- package/dist/helpers/textareaFit.d.ts +31 -0
- package/dist/helpers/timestamp.d.ts +7 -0
- package/dist/helpers/userEntry.d.ts +14 -0
- package/dist/helpers/viewerSender.d.ts +47 -0
- package/dist/hooks/useAvatarImage.d.ts +21 -0
- package/dist/hooks/useIsMobileViewport.d.ts +5 -0
- package/dist/hooks/useMediaProbe.d.ts +41 -0
- package/dist/hooks/usePaneWidthVar.d.ts +11 -0
- package/dist/hooks/usePdfThumbnail.d.ts +0 -1
- package/dist/hooks/usePrefersReducedMotion.d.ts +2 -0
- package/dist/hooks/useRoomInitialization.d.ts +2 -2
- package/dist/hooks/useRoomUrl.d.ts +2 -2
- package/dist/hooks/useSendMessage.d.ts +4 -1
- package/dist/{main-BnLtessp.js → main-CghdNDlB.js} +20803 -16260
- package/dist/main.js +10 -10
- package/dist/networking/api-requests/auth.api.d.ts +13 -5
- package/dist/networking/api-requests/user.api.d.ts +4 -4
- package/dist/networking/callKeyStanza.d.ts +7 -0
- package/dist/networking/deliveryReceiptBatcher.d.ts +53 -0
- package/dist/networking/displayedMarkerThrottle.d.ts +29 -0
- package/dist/networking/receiptsPolicy.d.ts +32 -0
- package/dist/networking/stanzaHandlers.d.ts +4 -2
- 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 +5 -0
- package/dist/roomStore/callSlice.d.ts +6 -1
- package/dist/roomStore/index.d.ts +10 -0
- package/dist/roomStore/roomsSlice.d.ts +22 -3
- 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 +56 -2
- 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 +17 -0
- package/dist/types/models/xmpp.model.d.ts +3 -0
- package/dist/utils/messageNotificationContent.d.ts +34 -0
- package/dist/version.d.ts +1 -1
- package/package.json +8 -7
- package/public/firebase-messaging-sw.js +20 -3
- package/dist/FilePreviewModal-Cgj1iHry.js +0 -743
- 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
|
@@ -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;
|
|
@@ -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,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,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,67 @@
|
|
|
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;
|
|
40
|
+
export type TimeMode = 'overlay' | 'row';
|
|
41
|
+
export interface MediaTimeFacts {
|
|
42
|
+
attachments: IAttachment[];
|
|
43
|
+
/** The caption the bubble really shows (see getMessageCaption), if any. */
|
|
44
|
+
hasCaption: boolean;
|
|
45
|
+
/** The sender's own bubble is in the failed state. */
|
|
46
|
+
failed?: boolean;
|
|
47
|
+
/** Quick reply buttons are shown under the bubble content. */
|
|
48
|
+
hasQuickReplies?: boolean;
|
|
49
|
+
/** A URL preview card is shown (only ever from a caption, never the body). */
|
|
50
|
+
hasUrlPreview?: boolean;
|
|
51
|
+
/** The manual translate row is shown. */
|
|
52
|
+
hasTranslateRow?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The one rule for where a media bubble's time goes, read by both the time
|
|
56
|
+
* element and the bubble itself (data-time-mode), so they cannot disagree.
|
|
57
|
+
*
|
|
58
|
+
* `mediaOnly`: pictures and videos and nothing else (no caption, no card, no
|
|
59
|
+
* voice note). Such a bubble always shows the time as the glass pill over the
|
|
60
|
+
* media, unless something else needs the bottom of the bubble (a failed
|
|
61
|
+
* notice, quick replies, a preview card or the translate row), in which case
|
|
62
|
+
* the time takes the normal bottom right row.
|
|
63
|
+
*/
|
|
64
|
+
export declare const getMediaTimeLayout: (facts: MediaTimeFacts) => {
|
|
65
|
+
mediaOnly: boolean;
|
|
66
|
+
mode: TimeMode;
|
|
67
|
+
};
|
|
@@ -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;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IMessage } from '../types/types';
|
|
2
|
+
/**
|
|
3
|
+
* Ids of the messages that end a run of one sender: the next row is another
|
|
4
|
+
* sender, a system row, or a message on the next day. A run is a block of
|
|
5
|
+
* consecutive messages from one person, which the bubbles group visually.
|
|
6
|
+
*
|
|
7
|
+
* Pass the full list, not a window of it, so the last row of a window still
|
|
8
|
+
* knows what follows it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const messageRunEnds: (list: IMessage[]) => Set<string>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { IMessage } from '../types/types';
|
|
2
|
+
/**
|
|
3
|
+
* Per-room lookup tables for the delivery/read ticks, built once per
|
|
4
|
+
* `room.messages` array (WeakMap keyed by the array reference) and shared by
|
|
5
|
+
* every own bubble in that room. A bubble then costs O(viewers), not O(n).
|
|
6
|
+
*/
|
|
7
|
+
export interface ReceiptIndex {
|
|
8
|
+
/** message id -> position in the loaded list. */
|
|
9
|
+
indexById: Map<string, number>;
|
|
10
|
+
/** message position -> timestamp in ms (0 when unknown). */
|
|
11
|
+
tsByIndex: number[];
|
|
12
|
+
/** author local part -> position of their newest loaded message. */
|
|
13
|
+
lastAuthoredIndexByUser: Map<string, number>;
|
|
14
|
+
}
|
|
15
|
+
/** The lookup tables for `messages`, built on first use per array. */
|
|
16
|
+
export declare const getReceiptIndex: (messages: readonly IMessage[]) => ReceiptIndex;
|
|
17
|
+
/** Number of index builds so far (tests check one build per messages change). */
|
|
18
|
+
export declare const getReceiptIndexBuildCount: () => number;
|
|
19
|
+
export interface ReceiptState {
|
|
20
|
+
/** Other members who have read this message. */
|
|
21
|
+
seenBy: string[];
|
|
22
|
+
/** Other members who have received it (readers included). */
|
|
23
|
+
deliveredBy: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare const NO_RECEIPTS: ReceiptState;
|
|
26
|
+
export declare const receiptStateEqual: (a: ReceiptState, b: ReceiptState) => boolean;
|
|
27
|
+
export interface ComputeReceiptsInput {
|
|
28
|
+
index: ReceiptIndex;
|
|
29
|
+
messageId: string;
|
|
30
|
+
/** The bubble's own timestamp in ms (0 when unknown). */
|
|
31
|
+
messageTs: number;
|
|
32
|
+
/** Per-message receipts: xmppUsername -> ms (from message.receivedBy). */
|
|
33
|
+
receivedBy?: Record<string, number>;
|
|
34
|
+
/** Room delivery watermarks: xmppUsername -> stanza-id. */
|
|
35
|
+
lastReceivedByUser?: Record<string, string>;
|
|
36
|
+
/** Room read watermarks: xmppUsername -> stanza-id. */
|
|
37
|
+
lastDisplayedByUser?: Record<string, string>;
|
|
38
|
+
/** The current user's xmppUsername (never listed as a viewer). */
|
|
39
|
+
selfId?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Who has read and who has received one of our messages. A viewer has read
|
|
43
|
+
* it when their read watermark covers it, or when they have authored a
|
|
44
|
+
* message at or after it (posting in the room implies they saw what came
|
|
45
|
+
* before). A viewer has received it when a per-message receipt exists or
|
|
46
|
+
* their delivery watermark covers it; reading implies receiving.
|
|
47
|
+
*/
|
|
48
|
+
export declare const computeReceiptState: ({ index, messageId, messageTs, receivedBy, lastReceivedByUser, lastDisplayedByUser, selfId, }: ComputeReceiptsInput) => ReceiptState;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repairs file names the backend returns as UTF-8 bytes read as Latin-1
|
|
3
|
+
* (the classic multer/busboy `originalname` problem): "Список.pdf" arrives
|
|
4
|
+
* as "СпиÑок.pdf".
|
|
5
|
+
*
|
|
6
|
+
* Pure and conservative: a name is only replaced when every character fits
|
|
7
|
+
* in one byte, the bytes decode as strict UTF-8, the result differs, holds
|
|
8
|
+
* no replacement or control characters and contains at least one letter
|
|
9
|
+
* outside basic Latin. A correct name is returned untouched.
|
|
10
|
+
*/
|
|
11
|
+
export declare const repairFilenameEncoding: (name: string) => string;
|
|
@@ -7,9 +7,27 @@ import { IMessage, ModalFile } from '../types/types';
|
|
|
7
7
|
* download action, the secure `?ft=` token treatment, the alt text - keeps
|
|
8
8
|
* reading exactly the fields it read when a single file was opened by hand.
|
|
9
9
|
*/
|
|
10
|
+
export interface GallerySender {
|
|
11
|
+
id?: string;
|
|
12
|
+
username?: string;
|
|
13
|
+
/** The message's own display name, when it has one. */
|
|
14
|
+
name?: string;
|
|
15
|
+
firstName?: string;
|
|
16
|
+
lastName?: string;
|
|
17
|
+
/** Profile photo URL, when the sender has one. */
|
|
18
|
+
photo?: string;
|
|
19
|
+
}
|
|
10
20
|
export interface GalleryImage extends ModalFile {
|
|
21
|
+
/** What the entry renders as. Images zoom; videos play. */
|
|
22
|
+
kind: 'image' | 'video';
|
|
11
23
|
/** Stable per message+slot, so two identical URLs are still two entries. */
|
|
12
24
|
key: string;
|
|
25
|
+
/** Low-resolution copy shown blurred while the full image loads. */
|
|
26
|
+
previewURL?: string;
|
|
27
|
+
/** Message timestamp (ISO), shown in the viewer's header. */
|
|
28
|
+
date?: string;
|
|
29
|
+
/** Who posted it; the viewer header shows their name and avatar. */
|
|
30
|
+
sender?: GallerySender;
|
|
13
31
|
}
|
|
14
32
|
/**
|
|
15
33
|
* The gallery the lightbox pages through.
|
|
@@ -34,7 +52,17 @@ export interface GalleryImage extends ModalFile {
|
|
|
34
52
|
* Non-images (video, PDF, audio, arbitrary files) are excluded, so opening a
|
|
35
53
|
* PDF simply produces an empty gallery and no navigation chrome.
|
|
36
54
|
*/
|
|
55
|
+
/** The sender fields the viewer header needs, or undefined for no sender. */
|
|
56
|
+
export declare const senderFromMessage: (message?: Partial<IMessage> | null) => GallerySender | undefined;
|
|
57
|
+
/** ISO date string for a message, as the viewer header wants it. */
|
|
58
|
+
export declare const isoDateFromMessage: (message?: Partial<IMessage> | null) => string | undefined;
|
|
37
59
|
export declare const collectRoomImages: (messages?: Partial<IMessage>[] | null) => GalleryImage[];
|
|
60
|
+
/**
|
|
61
|
+
* Same walk as `collectRoomImages`, but the viewer's gallery also pages
|
|
62
|
+
* through videos, so an album of photos and clips opens as one sequence.
|
|
63
|
+
* PDFs, audio and other files are still excluded.
|
|
64
|
+
*/
|
|
65
|
+
export declare const collectRoomMedia: (messages?: Partial<IMessage>[] | null, kinds?: Array<"image" | "video">) => GalleryImage[];
|
|
38
66
|
/**
|
|
39
67
|
* Position of the open file inside the gallery, or -1 when it is not part of
|
|
40
68
|
* it (a PDF, a video, a file opened from the Files panel of another room).
|
|
@@ -51,3 +79,9 @@ export declare const collectRoomImages: (messages?: Partial<IMessage>[] | null)
|
|
|
51
79
|
* were stuck.
|
|
52
80
|
*/
|
|
53
81
|
export declare const findGalleryIndex: (images: GalleryImage[], activeFile?: ModalFile | null) => number;
|
|
82
|
+
/**
|
|
83
|
+
* Builds gallery entries from the file list a caller handed over with the
|
|
84
|
+
* open request (an album grid passes all of its tiles). Items without a
|
|
85
|
+
* location, or that are neither image nor video, are dropped.
|
|
86
|
+
*/
|
|
87
|
+
export declare const galleryFromFiles: (files?: ModalFile[] | null) => GalleryImage[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image URLs that reach the DOM (`<img src>`, CSS `url()`) can come from
|
|
3
|
+
* user-controlled data: profile photos, message stanzas, link previews,
|
|
4
|
+
* attachment locations. Anything that is not a plain web or blob image URL is
|
|
5
|
+
* dropped here so the sink never receives an active or document-bearing URL.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Returns the trimmed input when it is a safe image URL, otherwise ''.
|
|
9
|
+
*
|
|
10
|
+
* Allowed: absolute or relative (resolved against the page origin) http:,
|
|
11
|
+
* https: and blob: URLs, and base64 data URLs of png, jpeg, gif, webp or
|
|
12
|
+
* avif images. Blocked: javascript:, vbscript:, data:text/html, data:image/svg+xml
|
|
13
|
+
* and anything that does not parse. Non-string input yields ''.
|
|
14
|
+
*/
|
|
15
|
+
export declare const safeImageUrl: (value: unknown) => string;
|
|
@@ -9,4 +9,7 @@ export declare const appendFileToken: (url: string | null | undefined, fileToken
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const withoutFileToken: (url?: string | null) => string;
|
|
11
11
|
export declare const withFileToken: (url?: string | null) => string;
|
|
12
|
-
export declare const
|
|
12
|
+
export declare const resetFileTokenRecovery: () => void;
|
|
13
|
+
export declare const requestFileTokenRecovery: (options?: {
|
|
14
|
+
force?: boolean;
|
|
15
|
+
}) => Promise<boolean>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-sizing for the composer textarea.
|
|
3
|
+
*
|
|
4
|
+
* The field is sized from its real content (soft wraps included), not from
|
|
5
|
+
* the newline count. One line keeps the original 40px field (34px textarea
|
|
6
|
+
* plus the 6px wrapper border and padding). Two lines or more use a tighter
|
|
7
|
+
* vertical padding so the text hugs the field, and the textarea scrolls
|
|
8
|
+
* internally once it reaches the cap.
|
|
9
|
+
*/
|
|
10
|
+
export declare const COMPOSER_LINE_HEIGHT = 20;
|
|
11
|
+
/** Vertical padding per side for a single line (keeps the 40px field). */
|
|
12
|
+
export declare const COMPOSER_PAD_SINGLE = 7;
|
|
13
|
+
/** Vertical padding per side once the text wraps to two lines or more. */
|
|
14
|
+
export declare const COMPOSER_PAD_MULTI = 5;
|
|
15
|
+
/** Textarea box cap (about five lines), after which it scrolls. */
|
|
16
|
+
export declare const COMPOSER_MAX_HEIGHT = 140;
|
|
17
|
+
export interface TextareaFit {
|
|
18
|
+
/** Textarea border-box height in px. */
|
|
19
|
+
height: number;
|
|
20
|
+
/** True when the text occupies two lines or more. */
|
|
21
|
+
multiline: boolean;
|
|
22
|
+
/** True when the content is taller than the cap and must scroll. */
|
|
23
|
+
scrollable: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @param scrollHeight the textarea's scrollHeight, measured with height:auto
|
|
27
|
+
* and the single-line padding applied (so it is content + 14px).
|
|
28
|
+
*/
|
|
29
|
+
export declare function computeTextareaFit(scrollHeight: number): TextareaFit;
|
|
30
|
+
/** Re-measures a textarea from its content and applies the result to the DOM. */
|
|
31
|
+
export declare function fitTextareaToContent(el: HTMLTextAreaElement): TextareaFit;
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
export declare const normalizeTimestampValue: (value: number) => number;
|
|
2
2
|
export declare const getTimestampFromUnknown: (value: unknown) => number;
|
|
3
|
+
/**
|
|
4
|
+
* Time encoded in a message or stanza id, 0 when the id carries none. Only an
|
|
5
|
+
* all-digit id (server stanza-id, microseconds) encodes a time: ids such as
|
|
6
|
+
* `send-text-message:<uuid>` must not be read as one, because a uuid can hold
|
|
7
|
+
* a run of ten or more digits that would parse as a far-future time.
|
|
8
|
+
*/
|
|
9
|
+
export declare const getTimestampFromId: (id: unknown) => number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RoomMember } from '../types/types';
|
|
2
|
+
/**
|
|
3
|
+
* Narrow lookup into `rooms.usersSet` for one user key.
|
|
4
|
+
*
|
|
5
|
+
* Returns the entry only for an own property of the map. Prototype keys
|
|
6
|
+
* (`__proto__`, `constructor`, `prototype`) are refused outright: a plain
|
|
7
|
+
* dictionary lookup would hand back `Object.prototype` members for them,
|
|
8
|
+
* which look like a user entry to every caller that checks truthiness.
|
|
9
|
+
*
|
|
10
|
+
* Components use this with a `useSelector` on `state.rooms.usersSet` so they
|
|
11
|
+
* subscribe to one entry (reference-stable under Immer) instead of the whole
|
|
12
|
+
* map.
|
|
13
|
+
*/
|
|
14
|
+
export declare const lookupUserEntry: (usersSet: Record<string, RoomMember> | null | undefined, key: string | null | undefined) => RoomMember | undefined;
|