Migrate to private class properties/methods
This commit is contained in:
parent
7dbe57084b
commit
aa9f53df57
100 changed files with 3795 additions and 3944 deletions
|
@ -47,8 +47,8 @@ export class ErrorBoundary extends React.PureComponent<Props, State> {
|
|||
return (
|
||||
<div
|
||||
className={CSS_MODULE}
|
||||
onClick={this.onClick.bind(this)}
|
||||
onKeyDown={this.onKeyDown.bind(this)}
|
||||
onClick={this.#onClick.bind(this)}
|
||||
onKeyDown={this.#onKeyDown.bind(this)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
|
@ -62,24 +62,24 @@ export class ErrorBoundary extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private onClick(event: React.MouseEvent): void {
|
||||
#onClick(event: React.MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.onAction();
|
||||
this.#onAction();
|
||||
}
|
||||
|
||||
private onKeyDown(event: React.KeyboardEvent): void {
|
||||
#onKeyDown(event: React.KeyboardEvent): void {
|
||||
if (event.key !== 'Enter' && event.key !== ' ') {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.onAction();
|
||||
this.#onAction();
|
||||
}
|
||||
|
||||
private onAction(): void {
|
||||
#onAction(): void {
|
||||
const { showDebugLog } = this.props;
|
||||
showDebugLog();
|
||||
}
|
||||
|
|
|
@ -410,11 +410,11 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
public reactionsContainerRef: React.RefObject<HTMLDivElement> =
|
||||
React.createRef();
|
||||
|
||||
private hasSelectedTextRef: React.MutableRefObject<boolean> = {
|
||||
#hasSelectedTextRef: React.MutableRefObject<boolean> = {
|
||||
current: false,
|
||||
};
|
||||
|
||||
private metadataRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
#metadataRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||
|
||||
public reactionsContainerRefMerger = createRefMerger();
|
||||
|
||||
|
@ -432,7 +432,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
metadataWidth: this.guessMetadataWidth(),
|
||||
metadataWidth: this.#guessMetadataWidth(),
|
||||
|
||||
expiring: false,
|
||||
expired: false,
|
||||
|
@ -447,7 +447,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
showOutgoingGiftBadgeModal: false,
|
||||
|
||||
hasDeleteForEveryoneTimerExpired:
|
||||
this.getTimeRemainingForDeleteForEveryone() <= 0,
|
||||
this.#getTimeRemainingForDeleteForEveryone() <= 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -474,7 +474,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
return state;
|
||||
}
|
||||
|
||||
private hasReactions(): boolean {
|
||||
#hasReactions(): boolean {
|
||||
const { reactions } = this.props;
|
||||
return Boolean(reactions && reactions.length);
|
||||
}
|
||||
|
@ -518,7 +518,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
window.ConversationController?.onConvoMessageMount(conversationId);
|
||||
|
||||
this.startTargetedTimer();
|
||||
this.startDeleteForEveryoneTimerIfApplicable();
|
||||
this.#startDeleteForEveryoneTimerIfApplicable();
|
||||
this.startGiftBadgeInterval();
|
||||
|
||||
const { isTargeted } = this.props;
|
||||
|
@ -543,7 +543,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
checkForAccount(contact.firstNumber);
|
||||
}
|
||||
|
||||
document.addEventListener('selectionchange', this.handleSelectionChange);
|
||||
document.addEventListener('selectionchange', this.#handleSelectionChange);
|
||||
}
|
||||
|
||||
public override componentWillUnmount(): void {
|
||||
|
@ -553,14 +553,17 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
clearTimeoutIfNecessary(this.deleteForEveryoneTimeout);
|
||||
clearTimeoutIfNecessary(this.giftBadgeInterval);
|
||||
this.toggleReactionViewer(true);
|
||||
document.removeEventListener('selectionchange', this.handleSelectionChange);
|
||||
document.removeEventListener(
|
||||
'selectionchange',
|
||||
this.#handleSelectionChange
|
||||
);
|
||||
}
|
||||
|
||||
public override componentDidUpdate(prevProps: Readonly<Props>): void {
|
||||
const { isTargeted, status, timestamp } = this.props;
|
||||
|
||||
this.startTargetedTimer();
|
||||
this.startDeleteForEveryoneTimerIfApplicable();
|
||||
this.#startDeleteForEveryoneTimerIfApplicable();
|
||||
|
||||
if (!prevProps.isTargeted && isTargeted) {
|
||||
this.setFocus();
|
||||
|
@ -586,7 +589,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
private getMetadataPlacement(
|
||||
#getMetadataPlacement(
|
||||
{
|
||||
attachments,
|
||||
attachmentDroppedDueToSize,
|
||||
|
@ -634,11 +637,11 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
return MetadataPlacement.InlineWithText;
|
||||
}
|
||||
|
||||
if (this.canRenderStickerLikeEmoji()) {
|
||||
if (this.#canRenderStickerLikeEmoji()) {
|
||||
return MetadataPlacement.Bottom;
|
||||
}
|
||||
|
||||
if (this.shouldShowJoinButton()) {
|
||||
if (this.#shouldShowJoinButton()) {
|
||||
return MetadataPlacement.Bottom;
|
||||
}
|
||||
|
||||
|
@ -653,7 +656,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
* This will probably guess wrong, but it's valuable to get close to the real value
|
||||
* because it can reduce layout jumpiness.
|
||||
*/
|
||||
private guessMetadataWidth(): number {
|
||||
#guessMetadataWidth(): number {
|
||||
const { direction, expirationLength, isSMS, status, isEditedMessage } =
|
||||
this.props;
|
||||
|
||||
|
@ -714,12 +717,12 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}));
|
||||
}
|
||||
|
||||
private getTimeRemainingForDeleteForEveryone(): number {
|
||||
#getTimeRemainingForDeleteForEveryone(): number {
|
||||
const { timestamp } = this.props;
|
||||
return Math.max(timestamp - Date.now() + DAY, 0);
|
||||
}
|
||||
|
||||
private startDeleteForEveryoneTimerIfApplicable(): void {
|
||||
#startDeleteForEveryoneTimerIfApplicable(): void {
|
||||
const { canDeleteForEveryone } = this.props;
|
||||
const { hasDeleteForEveryoneTimerExpired } = this.state;
|
||||
if (
|
||||
|
@ -733,7 +736,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
this.deleteForEveryoneTimeout = setTimeout(() => {
|
||||
this.setState({ hasDeleteForEveryoneTimerExpired: true });
|
||||
delete this.deleteForEveryoneTimeout;
|
||||
}, this.getTimeRemainingForDeleteForEveryone());
|
||||
}, this.#getTimeRemainingForDeleteForEveryone());
|
||||
}
|
||||
|
||||
public checkExpired(): void {
|
||||
|
@ -761,12 +764,12 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
private areLinksEnabled(): boolean {
|
||||
#areLinksEnabled(): boolean {
|
||||
const { isMessageRequestAccepted, isBlocked } = this.props;
|
||||
return isMessageRequestAccepted && !isBlocked;
|
||||
}
|
||||
|
||||
private shouldRenderAuthor(): boolean {
|
||||
#shouldRenderAuthor(): boolean {
|
||||
const { author, conversationType, direction, shouldCollapseAbove } =
|
||||
this.props;
|
||||
return Boolean(
|
||||
|
@ -777,7 +780,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private canRenderStickerLikeEmoji(): boolean {
|
||||
#canRenderStickerLikeEmoji(): boolean {
|
||||
const {
|
||||
attachments,
|
||||
bodyRanges,
|
||||
|
@ -799,7 +802,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private updateMetadataWidth = (newMetadataWidth: number): void => {
|
||||
#updateMetadataWidth = (newMetadataWidth: number): void => {
|
||||
this.setState(({ metadataWidth }) => ({
|
||||
// We don't want text to jump around if the metadata shrinks, but we want to make
|
||||
// sure we have enough room.
|
||||
|
@ -807,16 +810,16 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}));
|
||||
};
|
||||
|
||||
private handleSelectionChange = () => {
|
||||
#handleSelectionChange = () => {
|
||||
const selection = document.getSelection();
|
||||
if (selection != null && !selection.isCollapsed) {
|
||||
this.hasSelectedTextRef.current = true;
|
||||
this.#hasSelectedTextRef.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
private renderMetadata(): ReactNode {
|
||||
#renderMetadata(): ReactNode {
|
||||
let isInline: boolean;
|
||||
const metadataPlacement = this.getMetadataPlacement();
|
||||
const metadataPlacement = this.#getMetadataPlacement();
|
||||
switch (metadataPlacement) {
|
||||
case MetadataPlacement.NotRendered:
|
||||
case MetadataPlacement.RenderedByMessageAudioComponent:
|
||||
|
@ -854,7 +857,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
timestamp,
|
||||
} = this.props;
|
||||
|
||||
const isStickerLike = isSticker || this.canRenderStickerLikeEmoji();
|
||||
const isStickerLike = isSticker || this.#canRenderStickerLikeEmoji();
|
||||
|
||||
return (
|
||||
<MessageMetadata
|
||||
|
@ -874,9 +877,9 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
isShowingImage={this.isShowingImage()}
|
||||
isSticker={isStickerLike}
|
||||
isTapToViewExpired={isTapToViewExpired}
|
||||
onWidthMeasured={isInline ? this.updateMetadataWidth : undefined}
|
||||
onWidthMeasured={isInline ? this.#updateMetadataWidth : undefined}
|
||||
pushPanelForConversation={pushPanelForConversation}
|
||||
ref={this.metadataRef}
|
||||
ref={this.#metadataRef}
|
||||
retryMessageSend={retryMessageSend}
|
||||
showEditHistoryModal={showEditHistoryModal}
|
||||
status={status}
|
||||
|
@ -886,7 +889,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private renderAuthor(): ReactNode {
|
||||
#renderAuthor(): ReactNode {
|
||||
const {
|
||||
author,
|
||||
contactNameColor,
|
||||
|
@ -896,7 +899,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
isTapToViewExpired,
|
||||
} = this.props;
|
||||
|
||||
if (!this.shouldRenderAuthor()) {
|
||||
if (!this.#shouldRenderAuthor()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -951,7 +954,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
const { imageBroken } = this.state;
|
||||
|
||||
const collapseMetadata =
|
||||
this.getMetadataPlacement() === MetadataPlacement.NotRendered;
|
||||
this.#getMetadataPlacement() === MetadataPlacement.NotRendered;
|
||||
|
||||
if (!attachments || !attachments[0]) {
|
||||
return null;
|
||||
|
@ -960,7 +963,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
|
||||
// For attachments which aren't full-frame
|
||||
const withContentBelow = Boolean(text || attachmentDroppedDueToSize);
|
||||
const withContentAbove = Boolean(quote) || this.shouldRenderAuthor();
|
||||
const withContentAbove = Boolean(quote) || this.#shouldRenderAuthor();
|
||||
const displayImage = canDisplayImage(attachments);
|
||||
|
||||
if (displayImage && !imageBroken) {
|
||||
|
@ -1203,7 +1206,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
? i18n('icu:message--call-link-description')
|
||||
: undefined);
|
||||
|
||||
const isClickable = this.areLinksEnabled();
|
||||
const isClickable = this.#areLinksEnabled();
|
||||
|
||||
const className = classNames(
|
||||
'module-message__link-preview',
|
||||
|
@ -1371,7 +1374,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
|
||||
const maybeSpacer = text
|
||||
? undefined
|
||||
: this.getMetadataPlacement() === MetadataPlacement.InlineWithText && (
|
||||
: this.#getMetadataPlacement() === MetadataPlacement.InlineWithText && (
|
||||
<MessageTextMetadataSpacer metadataWidth={metadataWidth} />
|
||||
);
|
||||
|
||||
|
@ -1456,12 +1459,12 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
)}
|
||||
>
|
||||
{description}
|
||||
{this.getMetadataPlacement() ===
|
||||
{this.#getMetadataPlacement() ===
|
||||
MetadataPlacement.InlineWithText && (
|
||||
<MessageTextMetadataSpacer metadataWidth={metadataWidth} />
|
||||
)}
|
||||
</div>
|
||||
{this.renderMetadata()}
|
||||
{this.#renderMetadata()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1569,7 +1572,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
{buttonContents}
|
||||
</div>
|
||||
</button>
|
||||
{this.renderMetadata()}
|
||||
{this.#renderMetadata()}
|
||||
{showOutgoingGiftBadgeModal ? (
|
||||
<OutgoingGiftBadgeModal
|
||||
i18n={i18n}
|
||||
|
@ -1763,7 +1766,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
conversationType === 'group' && direction === 'incoming';
|
||||
const withContentBelow =
|
||||
withCaption ||
|
||||
this.getMetadataPlacement() !== MetadataPlacement.NotRendered;
|
||||
this.#getMetadataPlacement() !== MetadataPlacement.NotRendered;
|
||||
|
||||
const otherContent =
|
||||
(contact && contact.firstNumber && contact.serviceId) || withCaption;
|
||||
|
@ -1833,7 +1836,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private renderAvatar(): ReactNode {
|
||||
#renderAvatar(): ReactNode {
|
||||
const {
|
||||
author,
|
||||
conversationId,
|
||||
|
@ -1854,7 +1857,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
<div
|
||||
className={classNames('module-message__author-avatar-container', {
|
||||
'module-message__author-avatar-container--with-reactions':
|
||||
this.hasReactions(),
|
||||
this.#hasReactions(),
|
||||
})}
|
||||
>
|
||||
{shouldCollapseBelow ? (
|
||||
|
@ -1887,7 +1890,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private getContents(): string | undefined {
|
||||
#getContents(): string | undefined {
|
||||
const { deletedForEveryone, direction, i18n, status, text } = this.props;
|
||||
|
||||
if (deletedForEveryone) {
|
||||
|
@ -1920,7 +1923,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
} = this.props;
|
||||
const { metadataWidth } = this.state;
|
||||
|
||||
const contents = this.getContents();
|
||||
const contents = this.#getContents();
|
||||
|
||||
if (!contents) {
|
||||
return null;
|
||||
|
@ -1950,10 +1953,10 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
const range = window.getSelection()?.getRangeAt(0);
|
||||
if (
|
||||
clickCount === 3 &&
|
||||
this.metadataRef.current &&
|
||||
range?.intersectsNode(this.metadataRef.current)
|
||||
this.#metadataRef.current &&
|
||||
range?.intersectsNode(this.#metadataRef.current)
|
||||
) {
|
||||
range.setEndBefore(this.metadataRef.current);
|
||||
range.setEndBefore(this.#metadataRef.current);
|
||||
}
|
||||
}}
|
||||
onDoubleClick={(event: React.MouseEvent) => {
|
||||
|
@ -1965,7 +1968,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
<MessageBodyReadMore
|
||||
bodyRanges={bodyRanges}
|
||||
direction={direction}
|
||||
disableLinks={!this.areLinksEnabled()}
|
||||
disableLinks={!this.#areLinksEnabled()}
|
||||
displayLimit={displayLimit}
|
||||
i18n={i18n}
|
||||
id={id}
|
||||
|
@ -1988,14 +1991,14 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
text={contents || ''}
|
||||
textAttachment={textAttachment}
|
||||
/>
|
||||
{this.getMetadataPlacement() === MetadataPlacement.InlineWithText && (
|
||||
{this.#getMetadataPlacement() === MetadataPlacement.InlineWithText && (
|
||||
<MessageTextMetadataSpacer metadataWidth={metadataWidth} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private shouldShowJoinButton(): boolean {
|
||||
#shouldShowJoinButton(): boolean {
|
||||
const { previews } = this.props;
|
||||
|
||||
if (previews?.length !== 1) {
|
||||
|
@ -2006,10 +2009,10 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
return Boolean(onlyPreview.isCallLink);
|
||||
}
|
||||
|
||||
private renderAction(): JSX.Element | null {
|
||||
#renderAction(): JSX.Element | null {
|
||||
const { direction, activeCallConversationId, i18n, previews } = this.props;
|
||||
|
||||
if (this.shouldShowJoinButton()) {
|
||||
if (this.#shouldShowJoinButton()) {
|
||||
const firstPreview = previews[0];
|
||||
const inAnotherCall = Boolean(
|
||||
activeCallConversationId &&
|
||||
|
@ -2044,7 +2047,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
return null;
|
||||
}
|
||||
|
||||
private renderError(): ReactNode {
|
||||
#renderError(): ReactNode {
|
||||
const { status, direction } = this.props;
|
||||
|
||||
if (
|
||||
|
@ -2205,7 +2208,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
} = this.props;
|
||||
|
||||
const collapseMetadata =
|
||||
this.getMetadataPlacement() === MetadataPlacement.NotRendered;
|
||||
this.#getMetadataPlacement() === MetadataPlacement.NotRendered;
|
||||
const withContentBelow = !collapseMetadata;
|
||||
const withContentAbove =
|
||||
!collapseMetadata &&
|
||||
|
@ -2243,7 +2246,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private popperPreventOverflowModifier(): Partial<PreventOverflowModifier> {
|
||||
#popperPreventOverflowModifier(): Partial<PreventOverflowModifier> {
|
||||
const { containerElementRef } = this.props;
|
||||
return {
|
||||
name: 'preventOverflow',
|
||||
|
@ -2302,7 +2305,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
public renderReactions(outgoing: boolean): JSX.Element | null {
|
||||
const { getPreferredBadge, reactions = [], i18n, theme } = this.props;
|
||||
|
||||
if (!this.hasReactions()) {
|
||||
if (!this.#hasReactions()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -2465,7 +2468,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
<Popper
|
||||
placement={popperPlacement}
|
||||
strategy="fixed"
|
||||
modifiers={[this.popperPreventOverflowModifier()]}
|
||||
modifiers={[this.#popperPreventOverflowModifier()]}
|
||||
>
|
||||
{({ ref, style }) => (
|
||||
<ReactionViewer
|
||||
|
@ -2495,7 +2498,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
return (
|
||||
<>
|
||||
{this.renderText()}
|
||||
{this.renderMetadata()}
|
||||
{this.#renderMetadata()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -2508,7 +2511,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
return (
|
||||
<>
|
||||
{this.renderTapToView()}
|
||||
{this.renderMetadata()}
|
||||
{this.#renderMetadata()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -2523,8 +2526,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
{this.renderPayment()}
|
||||
{this.renderEmbeddedContact()}
|
||||
{this.renderText()}
|
||||
{this.renderAction()}
|
||||
{this.renderMetadata()}
|
||||
{this.#renderAction()}
|
||||
{this.#renderMetadata()}
|
||||
{this.renderSendMessageButton()}
|
||||
</>
|
||||
);
|
||||
|
@ -2740,7 +2743,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
|
||||
const isAttachmentPending = this.isAttachmentPending();
|
||||
const width = this.getWidth();
|
||||
const isEmojiOnly = this.canRenderStickerLikeEmoji();
|
||||
const isEmojiOnly = this.#canRenderStickerLikeEmoji();
|
||||
const isStickerLike = isSticker || isEmojiOnly;
|
||||
|
||||
// If it's a mostly-normal gray incoming text box, we don't want to darken it as much
|
||||
|
@ -2773,7 +2776,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
isTapToViewError
|
||||
? 'module-message__container--with-tap-to-view-error'
|
||||
: null,
|
||||
this.hasReactions() ? 'module-message__container--with-reactions' : null,
|
||||
this.#hasReactions() ? 'module-message__container--with-reactions' : null,
|
||||
deletedForEveryone
|
||||
? 'module-message__container--deleted-for-everyone'
|
||||
: null
|
||||
|
@ -2806,7 +2809,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{this.renderAuthor()}
|
||||
{this.#renderAuthor()}
|
||||
<div dir={TextDirectionToDirAttribute[textDirection]}>
|
||||
{this.renderContents()}
|
||||
</div>
|
||||
|
@ -2890,13 +2893,13 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
} else {
|
||||
wrapperProps = {
|
||||
onMouseDown: () => {
|
||||
this.hasSelectedTextRef.current = false;
|
||||
this.#hasSelectedTextRef.current = false;
|
||||
},
|
||||
// We use `onClickCapture` here and preven default/stop propagation to
|
||||
// prevent other click handlers from firing.
|
||||
onClickCapture: event => {
|
||||
if (isMacOS ? event.metaKey : event.ctrlKey) {
|
||||
if (this.hasSelectedTextRef.current) {
|
||||
if (this.#hasSelectedTextRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2964,8 +2967,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
// eslint-disable-next-line react/no-unknown-property
|
||||
inert={isSelectMode ? '' : undefined}
|
||||
>
|
||||
{this.renderError()}
|
||||
{this.renderAvatar()}
|
||||
{this.#renderError()}
|
||||
{this.#renderAvatar()}
|
||||
{this.renderContainer()}
|
||||
{renderMenu?.()}
|
||||
</div>
|
||||
|
|
|
@ -189,18 +189,18 @@ export class Timeline extends React.Component<
|
|||
StateType,
|
||||
SnapshotType
|
||||
> {
|
||||
private readonly containerRef = React.createRef<HTMLDivElement>();
|
||||
private readonly messagesRef = React.createRef<HTMLDivElement>();
|
||||
private readonly atBottomDetectorRef = React.createRef<HTMLDivElement>();
|
||||
private readonly lastSeenIndicatorRef = React.createRef<HTMLDivElement>();
|
||||
private intersectionObserver?: IntersectionObserver;
|
||||
readonly #containerRef = React.createRef<HTMLDivElement>();
|
||||
readonly #messagesRef = React.createRef<HTMLDivElement>();
|
||||
readonly #atBottomDetectorRef = React.createRef<HTMLDivElement>();
|
||||
readonly #lastSeenIndicatorRef = React.createRef<HTMLDivElement>();
|
||||
#intersectionObserver?: IntersectionObserver;
|
||||
|
||||
// This is a best guess. It will likely be overridden when the timeline is measured.
|
||||
private maxVisibleRows = Math.ceil(window.innerHeight / MIN_ROW_HEIGHT);
|
||||
#maxVisibleRows = Math.ceil(window.innerHeight / MIN_ROW_HEIGHT);
|
||||
|
||||
private hasRecentlyScrolledTimeout?: NodeJS.Timeout;
|
||||
private delayedPeekTimeout?: NodeJS.Timeout;
|
||||
private peekInterval?: NodeJS.Timeout;
|
||||
#hasRecentlyScrolledTimeout?: NodeJS.Timeout;
|
||||
#delayedPeekTimeout?: NodeJS.Timeout;
|
||||
#peekInterval?: NodeJS.Timeout;
|
||||
|
||||
// eslint-disable-next-line react/state-in-constructor
|
||||
override state: StateType = {
|
||||
|
@ -213,31 +213,28 @@ export class Timeline extends React.Component<
|
|||
widthBreakpoint: WidthBreakpoint.Wide,
|
||||
};
|
||||
|
||||
private onScrollLockChange = (): void => {
|
||||
#onScrollLockChange = (): void => {
|
||||
this.setState({
|
||||
scrollLocked: this.scrollerLock.isLocked(),
|
||||
scrollLocked: this.#scrollerLock.isLocked(),
|
||||
});
|
||||
};
|
||||
|
||||
private scrollerLock = createScrollerLock(
|
||||
'Timeline',
|
||||
this.onScrollLockChange
|
||||
);
|
||||
#scrollerLock = createScrollerLock('Timeline', this.#onScrollLockChange);
|
||||
|
||||
private onScroll = (event: UIEvent): void => {
|
||||
#onScroll = (event: UIEvent): void => {
|
||||
// When content is removed from the viewport, such as typing indicators leaving
|
||||
// or messages being edited smaller or deleted, scroll events are generated and
|
||||
// they are marked as user-generated (isTrusted === true). Actual user generated
|
||||
// scroll events with movement must scroll a nonbottom state at some point.
|
||||
const isAtBottom = this.isAtBottom();
|
||||
const isAtBottom = this.#isAtBottom();
|
||||
if (event.isTrusted && !isAtBottom) {
|
||||
this.scrollerLock.onUserInterrupt('onScroll');
|
||||
this.#scrollerLock.onUserInterrupt('onScroll');
|
||||
}
|
||||
|
||||
// hasRecentlyScrolled is used to show the floating date header, which we only
|
||||
// want to show when scrolling through history or on conversation first open.
|
||||
// Checking bottom prevents new messages and typing from showing the header.
|
||||
if (!this.state.hasRecentlyScrolled && this.isAtBottom()) {
|
||||
if (!this.state.hasRecentlyScrolled && this.#isAtBottom()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -248,24 +245,24 @@ export class Timeline extends React.Component<
|
|||
// [0]: https://github.com/facebook/react/blob/29b7b775f2ecf878eaf605be959d959030598b07/packages/react-reconciler/src/ReactUpdateQueue.js#L401-L404
|
||||
oldState.hasRecentlyScrolled ? null : { hasRecentlyScrolled: true }
|
||||
);
|
||||
clearTimeoutIfNecessary(this.hasRecentlyScrolledTimeout);
|
||||
this.hasRecentlyScrolledTimeout = setTimeout(() => {
|
||||
clearTimeoutIfNecessary(this.#hasRecentlyScrolledTimeout);
|
||||
this.#hasRecentlyScrolledTimeout = setTimeout(() => {
|
||||
this.setState({ hasRecentlyScrolled: false });
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
private scrollToItemIndex(itemIndex: number): void {
|
||||
if (this.scrollerLock.isLocked()) {
|
||||
#scrollToItemIndex(itemIndex: number): void {
|
||||
if (this.#scrollerLock.isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messagesRef.current
|
||||
this.#messagesRef.current
|
||||
?.querySelector(`[data-item-index="${itemIndex}"]`)
|
||||
?.scrollIntoViewIfNeeded();
|
||||
}
|
||||
|
||||
private scrollToBottom = (setFocus?: boolean): void => {
|
||||
if (this.scrollerLock.isLocked()) {
|
||||
#scrollToBottom = (setFocus?: boolean): void => {
|
||||
if (this.#scrollerLock.isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -276,20 +273,20 @@ export class Timeline extends React.Component<
|
|||
const lastMessageId = items[lastIndex];
|
||||
targetMessage(lastMessageId, id);
|
||||
} else {
|
||||
const containerEl = this.containerRef.current;
|
||||
const containerEl = this.#containerRef.current;
|
||||
if (containerEl) {
|
||||
scrollToBottom(containerEl);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onClickScrollDownButton = (): void => {
|
||||
this.scrollerLock.onUserInterrupt('onClickScrollDownButton');
|
||||
this.scrollDown(false);
|
||||
#onClickScrollDownButton = (): void => {
|
||||
this.#scrollerLock.onUserInterrupt('onClickScrollDownButton');
|
||||
this.#scrollDown(false);
|
||||
};
|
||||
|
||||
private scrollDown = (setFocus?: boolean): void => {
|
||||
if (this.scrollerLock.isLocked()) {
|
||||
#scrollDown = (setFocus?: boolean): void => {
|
||||
if (this.#scrollerLock.isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -309,7 +306,7 @@ export class Timeline extends React.Component<
|
|||
}
|
||||
|
||||
if (messageLoadingState) {
|
||||
this.scrollToBottom(setFocus);
|
||||
this.#scrollToBottom(setFocus);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -323,10 +320,10 @@ export class Timeline extends React.Component<
|
|||
const messageId = items[oldestUnseenIndex];
|
||||
targetMessage(messageId, id);
|
||||
} else {
|
||||
this.scrollToItemIndex(oldestUnseenIndex);
|
||||
this.#scrollToItemIndex(oldestUnseenIndex);
|
||||
}
|
||||
} else if (haveNewest) {
|
||||
this.scrollToBottom(setFocus);
|
||||
this.#scrollToBottom(setFocus);
|
||||
} else {
|
||||
const lastId = last(items);
|
||||
if (lastId) {
|
||||
|
@ -335,8 +332,8 @@ export class Timeline extends React.Component<
|
|||
}
|
||||
};
|
||||
|
||||
private isAtBottom(): boolean {
|
||||
const containerEl = this.containerRef.current;
|
||||
#isAtBottom(): boolean {
|
||||
const containerEl = this.#containerRef.current;
|
||||
if (!containerEl) {
|
||||
return false;
|
||||
}
|
||||
|
@ -346,10 +343,10 @@ export class Timeline extends React.Component<
|
|||
return isScrolledNearBottom || !hasScrollbars;
|
||||
}
|
||||
|
||||
private updateIntersectionObserver(): void {
|
||||
const containerEl = this.containerRef.current;
|
||||
const messagesEl = this.messagesRef.current;
|
||||
const atBottomDetectorEl = this.atBottomDetectorRef.current;
|
||||
#updateIntersectionObserver(): void {
|
||||
const containerEl = this.#containerRef.current;
|
||||
const messagesEl = this.#messagesRef.current;
|
||||
const atBottomDetectorEl = this.#atBottomDetectorRef.current;
|
||||
if (!containerEl || !messagesEl || !atBottomDetectorEl) {
|
||||
return;
|
||||
}
|
||||
|
@ -368,7 +365,7 @@ export class Timeline extends React.Component<
|
|||
// We re-initialize the `IntersectionObserver`. We don't want stale references to old
|
||||
// props, and we care about the order of `IntersectionObserverEntry`s. (We could do
|
||||
// this another way, but this approach works.)
|
||||
this.intersectionObserver?.disconnect();
|
||||
this.#intersectionObserver?.disconnect();
|
||||
|
||||
const intersectionRatios = new Map<Element, number>();
|
||||
|
||||
|
@ -445,7 +442,7 @@ export class Timeline extends React.Component<
|
|||
setIsNearBottom(id, newIsNearBottom);
|
||||
|
||||
if (newestBottomVisibleMessageId) {
|
||||
this.markNewestBottomVisibleMessageRead();
|
||||
this.#markNewestBottomVisibleMessageRead();
|
||||
|
||||
const rowIndex = getRowIndexFromElement(newestBottomVisible);
|
||||
const maxRowIndex = items.length - 1;
|
||||
|
@ -471,15 +468,15 @@ export class Timeline extends React.Component<
|
|||
}
|
||||
};
|
||||
|
||||
this.intersectionObserver = new IntersectionObserver(
|
||||
this.#intersectionObserver = new IntersectionObserver(
|
||||
(entries, observer) => {
|
||||
assertDev(
|
||||
this.intersectionObserver === observer,
|
||||
this.#intersectionObserver === observer,
|
||||
'observer.disconnect() should prevent callbacks from firing'
|
||||
);
|
||||
|
||||
// Observer was updated from under us
|
||||
if (this.intersectionObserver !== observer) {
|
||||
if (this.#intersectionObserver !== observer) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -493,13 +490,13 @@ export class Timeline extends React.Component<
|
|||
|
||||
for (const child of messagesEl.children) {
|
||||
if ((child as HTMLElement).dataset.messageId) {
|
||||
this.intersectionObserver.observe(child);
|
||||
this.#intersectionObserver.observe(child);
|
||||
}
|
||||
}
|
||||
this.intersectionObserver.observe(atBottomDetectorEl);
|
||||
this.#intersectionObserver.observe(atBottomDetectorEl);
|
||||
}
|
||||
|
||||
private markNewestBottomVisibleMessageRead = throttle((): void => {
|
||||
#markNewestBottomVisibleMessageRead = throttle((): void => {
|
||||
const { id, markMessageRead } = this.props;
|
||||
const { newestBottomVisibleMessageId } = this.state;
|
||||
if (newestBottomVisibleMessageId) {
|
||||
|
@ -507,36 +504,37 @@ export class Timeline extends React.Component<
|
|||
}
|
||||
}, 500);
|
||||
|
||||
private setupGroupCallPeekTimeouts(): void {
|
||||
this.cleanupGroupCallPeekTimeouts();
|
||||
#setupGroupCallPeekTimeouts(): void {
|
||||
this.#cleanupGroupCallPeekTimeouts();
|
||||
|
||||
this.delayedPeekTimeout = setTimeout(() => {
|
||||
this.#delayedPeekTimeout = setTimeout(() => {
|
||||
const { id, peekGroupCallForTheFirstTime } = this.props;
|
||||
this.delayedPeekTimeout = undefined;
|
||||
this.#delayedPeekTimeout = undefined;
|
||||
peekGroupCallForTheFirstTime(id);
|
||||
}, 500);
|
||||
|
||||
this.peekInterval = setInterval(() => {
|
||||
this.#peekInterval = setInterval(() => {
|
||||
const { id, peekGroupCallIfItHasMembers } = this.props;
|
||||
peekGroupCallIfItHasMembers(id);
|
||||
}, MINUTE);
|
||||
}
|
||||
|
||||
private cleanupGroupCallPeekTimeouts(): void {
|
||||
const { delayedPeekTimeout, peekInterval } = this;
|
||||
#cleanupGroupCallPeekTimeouts(): void {
|
||||
const peekInterval = this.#peekInterval;
|
||||
const delayedPeekTimeout = this.#delayedPeekTimeout;
|
||||
|
||||
clearTimeoutIfNecessary(delayedPeekTimeout);
|
||||
this.delayedPeekTimeout = undefined;
|
||||
this.#delayedPeekTimeout = undefined;
|
||||
|
||||
if (peekInterval) {
|
||||
clearInterval(peekInterval);
|
||||
this.peekInterval = undefined;
|
||||
this.#peekInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public override componentDidMount(): void {
|
||||
const containerEl = this.containerRef.current;
|
||||
const messagesEl = this.messagesRef.current;
|
||||
const containerEl = this.#containerRef.current;
|
||||
const messagesEl = this.#messagesRef.current;
|
||||
const { conversationType, isConversationSelected } = this.props;
|
||||
strictAssert(
|
||||
// We don't render anything unless the conversation is selected
|
||||
|
@ -544,31 +542,31 @@ export class Timeline extends React.Component<
|
|||
'<Timeline> mounted without some refs'
|
||||
);
|
||||
|
||||
this.updateIntersectionObserver();
|
||||
this.#updateIntersectionObserver();
|
||||
|
||||
window.SignalContext.activeWindowService.registerForActive(
|
||||
this.markNewestBottomVisibleMessageRead
|
||||
this.#markNewestBottomVisibleMessageRead
|
||||
);
|
||||
|
||||
if (conversationType === 'group') {
|
||||
this.setupGroupCallPeekTimeouts();
|
||||
this.#setupGroupCallPeekTimeouts();
|
||||
}
|
||||
}
|
||||
|
||||
public override componentWillUnmount(): void {
|
||||
window.SignalContext.activeWindowService.unregisterForActive(
|
||||
this.markNewestBottomVisibleMessageRead
|
||||
this.#markNewestBottomVisibleMessageRead
|
||||
);
|
||||
|
||||
this.intersectionObserver?.disconnect();
|
||||
this.cleanupGroupCallPeekTimeouts();
|
||||
this.#intersectionObserver?.disconnect();
|
||||
this.#cleanupGroupCallPeekTimeouts();
|
||||
this.props.updateVisibleMessages?.([]);
|
||||
}
|
||||
|
||||
public override getSnapshotBeforeUpdate(
|
||||
prevProps: Readonly<PropsType>
|
||||
): SnapshotType {
|
||||
const containerEl = this.containerRef.current;
|
||||
const containerEl = this.#containerRef.current;
|
||||
if (!containerEl) {
|
||||
return null;
|
||||
}
|
||||
|
@ -579,7 +577,7 @@ export class Timeline extends React.Component<
|
|||
const scrollAnchor = getScrollAnchorBeforeUpdate(
|
||||
prevProps,
|
||||
props,
|
||||
this.isAtBottom()
|
||||
this.#isAtBottom()
|
||||
);
|
||||
|
||||
switch (scrollAnchor) {
|
||||
|
@ -627,10 +625,10 @@ export class Timeline extends React.Component<
|
|||
messageLoadingState,
|
||||
} = this.props;
|
||||
|
||||
const containerEl = this.containerRef.current;
|
||||
if (!this.scrollerLock.isLocked() && containerEl && snapshot) {
|
||||
const containerEl = this.#containerRef.current;
|
||||
if (!this.#scrollerLock.isLocked() && containerEl && snapshot) {
|
||||
if (snapshot === scrollToUnreadIndicator) {
|
||||
const lastSeenIndicatorEl = this.lastSeenIndicatorRef.current;
|
||||
const lastSeenIndicatorEl = this.#lastSeenIndicatorRef.current;
|
||||
if (lastSeenIndicatorEl) {
|
||||
lastSeenIndicatorEl.scrollIntoView();
|
||||
} else {
|
||||
|
@ -641,7 +639,7 @@ export class Timeline extends React.Component<
|
|||
);
|
||||
}
|
||||
} else if ('scrollToIndex' in snapshot) {
|
||||
this.scrollToItemIndex(snapshot.scrollToIndex);
|
||||
this.#scrollToItemIndex(snapshot.scrollToIndex);
|
||||
} else if ('scrollTop' in snapshot) {
|
||||
containerEl.scrollTop = snapshot.scrollTop;
|
||||
} else {
|
||||
|
@ -657,12 +655,12 @@ export class Timeline extends React.Component<
|
|||
oldItems.at(-1) !== newItems.at(-1);
|
||||
|
||||
if (haveItemsChanged) {
|
||||
this.updateIntersectionObserver();
|
||||
this.#updateIntersectionObserver();
|
||||
|
||||
// This condition is somewhat arbitrary.
|
||||
const numberToKeepAtBottom = this.maxVisibleRows * 2;
|
||||
const numberToKeepAtBottom = this.#maxVisibleRows * 2;
|
||||
const shouldDiscardOlderMessages: boolean =
|
||||
this.isAtBottom() && newItems.length > numberToKeepAtBottom;
|
||||
this.#isAtBottom() && newItems.length > numberToKeepAtBottom;
|
||||
if (shouldDiscardOlderMessages) {
|
||||
discardMessages({
|
||||
conversationId: id,
|
||||
|
@ -676,9 +674,9 @@ export class Timeline extends React.Component<
|
|||
!messageLoadingState && previousMessageLoadingState
|
||||
? previousMessageLoadingState
|
||||
: undefined;
|
||||
const numberToKeepAtTop = this.maxVisibleRows * 5;
|
||||
const numberToKeepAtTop = this.#maxVisibleRows * 5;
|
||||
const shouldDiscardNewerMessages: boolean =
|
||||
!this.isAtBottom() &&
|
||||
!this.#isAtBottom() &&
|
||||
loadingStateThatJustFinished ===
|
||||
TimelineMessageLoadingState.LoadingOlderMessages &&
|
||||
newItems.length > numberToKeepAtTop;
|
||||
|
@ -691,18 +689,18 @@ export class Timeline extends React.Component<
|
|||
}
|
||||
}
|
||||
if (previousMessageChangeCounter !== messageChangeCounter) {
|
||||
this.markNewestBottomVisibleMessageRead();
|
||||
this.#markNewestBottomVisibleMessageRead();
|
||||
}
|
||||
|
||||
if (previousConversationType !== conversationType) {
|
||||
this.cleanupGroupCallPeekTimeouts();
|
||||
this.#cleanupGroupCallPeekTimeouts();
|
||||
if (conversationType === 'group') {
|
||||
this.setupGroupCallPeekTimeouts();
|
||||
this.#setupGroupCallPeekTimeouts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleBlur = (event: React.FocusEvent): void => {
|
||||
#handleBlur = (event: React.FocusEvent): void => {
|
||||
const { clearTargetedMessage } = this.props;
|
||||
|
||||
const { currentTarget } = event;
|
||||
|
@ -726,9 +724,7 @@ export class Timeline extends React.Component<
|
|||
}, 0);
|
||||
};
|
||||
|
||||
private handleKeyDown = (
|
||||
event: React.KeyboardEvent<HTMLDivElement>
|
||||
): void => {
|
||||
#handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
||||
const { targetMessage, targetedMessageId, items, id } = this.props;
|
||||
const commandKey = get(window, 'platform') === 'darwin' && event.metaKey;
|
||||
const controlKey = get(window, 'platform') !== 'darwin' && event.ctrlKey;
|
||||
|
@ -803,7 +799,7 @@ export class Timeline extends React.Component<
|
|||
}
|
||||
|
||||
if (event.key === 'End' || (commandOrCtrl && event.key === 'ArrowDown')) {
|
||||
this.scrollDown(true);
|
||||
this.#scrollDown(true);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
@ -939,7 +935,7 @@ export class Timeline extends React.Component<
|
|||
key="last seen indicator"
|
||||
count={totalUnseen}
|
||||
i18n={i18n}
|
||||
ref={this.lastSeenIndicatorRef}
|
||||
ref={this.#lastSeenIndicatorRef}
|
||||
/>
|
||||
);
|
||||
} else if (oldestUnseenIndex === nextItemIndex) {
|
||||
|
@ -964,7 +960,7 @@ export class Timeline extends React.Component<
|
|||
>
|
||||
<ErrorBoundary i18n={i18n} showDebugLog={showDebugLog}>
|
||||
{renderItem({
|
||||
containerElementRef: this.containerRef,
|
||||
containerElementRef: this.#containerRef,
|
||||
containerWidthBreakpoint: widthBreakpoint,
|
||||
conversationId: id,
|
||||
isBlocked,
|
||||
|
@ -1098,7 +1094,7 @@ export class Timeline extends React.Component<
|
|||
}
|
||||
|
||||
return (
|
||||
<ScrollerLockContext.Provider value={this.scrollerLock}>
|
||||
<ScrollerLockContext.Provider value={this.#scrollerLock}>
|
||||
<SizeObserver
|
||||
onSizeChange={size => {
|
||||
const { isNearBottom } = this.props;
|
||||
|
@ -1107,9 +1103,9 @@ export class Timeline extends React.Component<
|
|||
widthBreakpoint: getWidthBreakpoint(size.width),
|
||||
});
|
||||
|
||||
this.maxVisibleRows = Math.ceil(size.height / MIN_ROW_HEIGHT);
|
||||
this.#maxVisibleRows = Math.ceil(size.height / MIN_ROW_HEIGHT);
|
||||
|
||||
const containerEl = this.containerRef.current;
|
||||
const containerEl = this.#containerRef.current;
|
||||
if (containerEl && isNearBottom) {
|
||||
scrollToBottom(containerEl);
|
||||
}
|
||||
|
@ -1124,8 +1120,8 @@ export class Timeline extends React.Component<
|
|||
)}
|
||||
role="presentation"
|
||||
tabIndex={-1}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onBlur={this.#handleBlur}
|
||||
onKeyDown={this.#handleKeyDown}
|
||||
ref={ref}
|
||||
>
|
||||
{headerElements}
|
||||
|
@ -1134,8 +1130,8 @@ export class Timeline extends React.Component<
|
|||
|
||||
<main
|
||||
className="module-timeline__messages__container"
|
||||
onScroll={this.onScroll}
|
||||
ref={this.containerRef}
|
||||
onScroll={this.#onScroll}
|
||||
ref={this.#containerRef}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
|
@ -1144,7 +1140,7 @@ export class Timeline extends React.Component<
|
|||
haveOldest && 'module-timeline__messages--have-oldest',
|
||||
scrollLocked && 'module-timeline__messages--scroll-locked'
|
||||
)}
|
||||
ref={this.messagesRef}
|
||||
ref={this.#messagesRef}
|
||||
role="list"
|
||||
>
|
||||
{haveOldest && (
|
||||
|
@ -1162,7 +1158,7 @@ export class Timeline extends React.Component<
|
|||
|
||||
<div
|
||||
className="module-timeline__messages__at-bottom-detector"
|
||||
ref={this.atBottomDetectorRef}
|
||||
ref={this.#atBottomDetectorRef}
|
||||
style={AT_BOTTOM_DETECTOR_STYLE}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1181,7 +1177,7 @@ export class Timeline extends React.Component<
|
|||
<ScrollDownButton
|
||||
variant={ScrollDownButtonVariant.UNREAD_MESSAGES}
|
||||
count={areUnreadBelowCurrentPosition ? unreadCount : 0}
|
||||
onClick={this.onClickScrollDownButton}
|
||||
onClick={this.#onClickScrollDownButton}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -34,29 +34,24 @@ export type LeftPaneArchivePropsType =
|
|||
| (LeftPaneArchiveBasePropsType & LeftPaneSearchPropsType);
|
||||
|
||||
export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsType> {
|
||||
private readonly archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
|
||||
private readonly isSearchingGlobally: boolean;
|
||||
|
||||
private readonly searchConversation: undefined | ConversationType;
|
||||
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly searchHelper: undefined | LeftPaneSearchHelper;
|
||||
|
||||
private readonly startSearchCounter: number;
|
||||
readonly #archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
readonly #isSearchingGlobally: boolean;
|
||||
readonly #searchConversation: undefined | ConversationType;
|
||||
readonly #searchTerm: string;
|
||||
readonly #searchHelper: undefined | LeftPaneSearchHelper;
|
||||
readonly #startSearchCounter: number;
|
||||
|
||||
constructor(props: Readonly<LeftPaneArchivePropsType>) {
|
||||
super();
|
||||
|
||||
this.archivedConversations = props.archivedConversations;
|
||||
this.isSearchingGlobally = props.isSearchingGlobally;
|
||||
this.searchConversation = props.searchConversation;
|
||||
this.searchTerm = props.searchTerm;
|
||||
this.startSearchCounter = props.startSearchCounter;
|
||||
this.#archivedConversations = props.archivedConversations;
|
||||
this.#isSearchingGlobally = props.isSearchingGlobally;
|
||||
this.#searchConversation = props.searchConversation;
|
||||
this.#searchTerm = props.searchTerm;
|
||||
this.#startSearchCounter = props.startSearchCounter;
|
||||
|
||||
if ('conversationResults' in props) {
|
||||
this.searchHelper = new LeftPaneSearchHelper(props);
|
||||
this.#searchHelper = new LeftPaneSearchHelper(props);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +95,7 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
updateSearchTerm: (searchTerm: string) => unknown;
|
||||
showConversation: ShowConversationType;
|
||||
}>): ReactChild | null {
|
||||
if (!this.searchConversation) {
|
||||
if (!this.#searchConversation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -111,11 +106,11 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
endConversationSearch={endConversationSearch}
|
||||
endSearch={endSearch}
|
||||
i18n={i18n}
|
||||
isSearchingGlobally={this.isSearchingGlobally}
|
||||
searchConversation={this.searchConversation}
|
||||
searchTerm={this.searchTerm}
|
||||
isSearchingGlobally={this.#isSearchingGlobally}
|
||||
searchConversation={this.#searchConversation}
|
||||
searchTerm={this.#searchTerm}
|
||||
showConversation={showConversation}
|
||||
startSearchCounter={this.startSearchCounter}
|
||||
startSearchCounter={this.#startSearchCounter}
|
||||
updateSearchTerm={updateSearchTerm}
|
||||
/>
|
||||
);
|
||||
|
@ -128,8 +123,8 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
override getPreRowsNode({
|
||||
i18n,
|
||||
}: Readonly<{ i18n: LocalizerType }>): ReactChild | null {
|
||||
if (this.searchHelper) {
|
||||
return this.searchHelper.getPreRowsNode({ i18n });
|
||||
if (this.#searchHelper) {
|
||||
return this.#searchHelper.getPreRowsNode({ i18n });
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -143,16 +138,16 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
|
||||
getRowCount(): number {
|
||||
return (
|
||||
this.searchHelper?.getRowCount() ?? this.archivedConversations.length
|
||||
this.#searchHelper?.getRowCount() ?? this.#archivedConversations.length
|
||||
);
|
||||
}
|
||||
|
||||
getRow(rowIndex: number): undefined | Row {
|
||||
if (this.searchHelper) {
|
||||
return this.searchHelper.getRow(rowIndex);
|
||||
if (this.#searchHelper) {
|
||||
return this.#searchHelper.getRow(rowIndex);
|
||||
}
|
||||
|
||||
const conversation = this.archivedConversations[rowIndex];
|
||||
const conversation = this.#archivedConversations[rowIndex];
|
||||
return conversation
|
||||
? {
|
||||
type: RowType.Conversation,
|
||||
|
@ -164,14 +159,14 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
override getRowIndexToScrollTo(
|
||||
selectedConversationId: undefined | string
|
||||
): undefined | number {
|
||||
if (this.searchHelper) {
|
||||
return this.searchHelper.getRowIndexToScrollTo(selectedConversationId);
|
||||
if (this.#searchHelper) {
|
||||
return this.#searchHelper.getRowIndexToScrollTo(selectedConversationId);
|
||||
}
|
||||
|
||||
if (!selectedConversationId) {
|
||||
return undefined;
|
||||
}
|
||||
const result = this.archivedConversations.findIndex(
|
||||
const result = this.#archivedConversations.findIndex(
|
||||
conversation => conversation.id === selectedConversationId
|
||||
);
|
||||
return result === -1 ? undefined : result;
|
||||
|
@ -180,7 +175,8 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
getConversationAndMessageAtIndex(
|
||||
conversationIndex: number
|
||||
): undefined | { conversationId: string } {
|
||||
const { archivedConversations, searchHelper } = this;
|
||||
const searchHelper = this.#searchHelper;
|
||||
const archivedConversations = this.#archivedConversations;
|
||||
|
||||
if (searchHelper) {
|
||||
return searchHelper.getConversationAndMessageAtIndex(conversationIndex);
|
||||
|
@ -196,8 +192,8 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
selectedConversationId: undefined | string,
|
||||
targetedMessageId: unknown
|
||||
): undefined | { conversationId: string } {
|
||||
if (this.searchHelper) {
|
||||
return this.searchHelper.getConversationAndMessageInDirection(
|
||||
if (this.#searchHelper) {
|
||||
return this.#searchHelper.getConversationAndMessageInDirection(
|
||||
toFind,
|
||||
selectedConversationId,
|
||||
targetedMessageId
|
||||
|
@ -205,7 +201,7 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
}
|
||||
|
||||
return getConversationInDirection(
|
||||
this.archivedConversations,
|
||||
this.#archivedConversations,
|
||||
toFind,
|
||||
selectedConversationId
|
||||
);
|
||||
|
@ -213,13 +209,13 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
|
||||
shouldRecomputeRowHeights(old: Readonly<LeftPaneArchivePropsType>): boolean {
|
||||
const hasSearchingChanged =
|
||||
'conversationResults' in old !== Boolean(this.searchHelper);
|
||||
'conversationResults' in old !== Boolean(this.#searchHelper);
|
||||
if (hasSearchingChanged) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('conversationResults' in old && this.searchHelper) {
|
||||
return this.searchHelper.shouldRecomputeRowHeights(old);
|
||||
if ('conversationResults' in old && this.#searchHelper) {
|
||||
return this.#searchHelper.shouldRecomputeRowHeights(old);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -251,7 +247,9 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper<LeftPaneArchivePropsTy
|
|||
!commandAndCtrl &&
|
||||
shiftKey &&
|
||||
(key === 'f' || key === 'F') &&
|
||||
this.archivedConversations.some(({ id }) => id === selectedConversationId)
|
||||
this.#archivedConversations.some(
|
||||
({ id }) => id === selectedConversationId
|
||||
)
|
||||
) {
|
||||
searchInConversation(selectedConversationId);
|
||||
|
||||
|
|
|
@ -42,31 +42,19 @@ export type LeftPaneChooseGroupMembersPropsType = {
|
|||
};
|
||||
|
||||
export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneChooseGroupMembersPropsType> {
|
||||
private readonly candidateContacts: ReadonlyArray<ConversationType>;
|
||||
|
||||
private readonly isPhoneNumberChecked: boolean;
|
||||
|
||||
private readonly isUsernameChecked: boolean;
|
||||
|
||||
private readonly isShowingMaximumGroupSizeModal: boolean;
|
||||
|
||||
private readonly isShowingRecommendedGroupSizeModal: boolean;
|
||||
|
||||
private readonly groupSizeRecommendedLimit: number;
|
||||
|
||||
private readonly groupSizeHardLimit: number;
|
||||
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly phoneNumber: ParsedE164Type | undefined;
|
||||
|
||||
private readonly username: string | undefined;
|
||||
|
||||
private readonly selectedContacts: Array<ConversationType>;
|
||||
|
||||
private readonly selectedConversationIdsSet: Set<string>;
|
||||
|
||||
private readonly uuidFetchState: UUIDFetchStateType;
|
||||
readonly #candidateContacts: ReadonlyArray<ConversationType>;
|
||||
readonly #isPhoneNumberChecked: boolean;
|
||||
readonly #isUsernameChecked: boolean;
|
||||
readonly #isShowingMaximumGroupSizeModal: boolean;
|
||||
readonly #isShowingRecommendedGroupSizeModal: boolean;
|
||||
readonly #groupSizeRecommendedLimit: number;
|
||||
readonly #groupSizeHardLimit: number;
|
||||
readonly #searchTerm: string;
|
||||
readonly #phoneNumber: ParsedE164Type | undefined;
|
||||
readonly #username: string | undefined;
|
||||
readonly #selectedContacts: Array<ConversationType>;
|
||||
readonly #selectedConversationIdsSet: Set<string>;
|
||||
readonly #uuidFetchState: UUIDFetchStateType;
|
||||
|
||||
constructor({
|
||||
candidateContacts,
|
||||
|
@ -84,27 +72,27 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
}: Readonly<LeftPaneChooseGroupMembersPropsType>) {
|
||||
super();
|
||||
|
||||
this.uuidFetchState = uuidFetchState;
|
||||
this.groupSizeRecommendedLimit = groupSizeRecommendedLimit - 1;
|
||||
this.groupSizeHardLimit = groupSizeHardLimit - 1;
|
||||
this.#uuidFetchState = uuidFetchState;
|
||||
this.#groupSizeRecommendedLimit = groupSizeRecommendedLimit - 1;
|
||||
this.#groupSizeHardLimit = groupSizeHardLimit - 1;
|
||||
|
||||
this.candidateContacts = candidateContacts;
|
||||
this.isShowingMaximumGroupSizeModal = isShowingMaximumGroupSizeModal;
|
||||
this.isShowingRecommendedGroupSizeModal =
|
||||
this.#candidateContacts = candidateContacts;
|
||||
this.#isShowingMaximumGroupSizeModal = isShowingMaximumGroupSizeModal;
|
||||
this.#isShowingRecommendedGroupSizeModal =
|
||||
isShowingRecommendedGroupSizeModal;
|
||||
this.searchTerm = searchTerm;
|
||||
this.#searchTerm = searchTerm;
|
||||
|
||||
const isUsernameVisible =
|
||||
username !== undefined &&
|
||||
username !== ourUsername &&
|
||||
this.candidateContacts.every(contact => contact.username !== username);
|
||||
this.#candidateContacts.every(contact => contact.username !== username);
|
||||
|
||||
if (isUsernameVisible) {
|
||||
this.username = username;
|
||||
this.#username = username;
|
||||
}
|
||||
|
||||
this.isUsernameChecked = selectedContacts.some(
|
||||
contact => contact.username === this.username
|
||||
this.#isUsernameChecked = selectedContacts.some(
|
||||
contact => contact.username === this.#username
|
||||
);
|
||||
|
||||
const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
||||
|
@ -114,22 +102,22 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
phoneNumber
|
||||
) {
|
||||
const { e164 } = phoneNumber;
|
||||
this.isPhoneNumberChecked =
|
||||
this.#isPhoneNumberChecked =
|
||||
phoneNumber.isValid &&
|
||||
selectedContacts.some(contact => contact.e164 === e164);
|
||||
|
||||
const isVisible =
|
||||
e164 !== ourE164 &&
|
||||
this.candidateContacts.every(contact => contact.e164 !== e164);
|
||||
this.#candidateContacts.every(contact => contact.e164 !== e164);
|
||||
if (isVisible) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.#phoneNumber = phoneNumber;
|
||||
}
|
||||
} else {
|
||||
this.isPhoneNumberChecked = false;
|
||||
this.#isPhoneNumberChecked = false;
|
||||
}
|
||||
this.selectedContacts = selectedContacts;
|
||||
this.#selectedContacts = selectedContacts;
|
||||
|
||||
this.selectedConversationIdsSet = new Set(
|
||||
this.#selectedConversationIdsSet = new Set(
|
||||
selectedContacts.map(contact => contact.id)
|
||||
);
|
||||
}
|
||||
|
@ -183,7 +171,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
onChange={onChangeComposeSearchTerm}
|
||||
placeholder={i18n('icu:contactSearchPlaceholder')}
|
||||
ref={focusRef}
|
||||
value={this.searchTerm}
|
||||
value={this.#searchTerm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -200,20 +188,20 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
removeSelectedContact: (conversationId: string) => unknown;
|
||||
}>): ReactChild {
|
||||
let modalNode: undefined | ReactChild;
|
||||
if (this.isShowingMaximumGroupSizeModal) {
|
||||
if (this.#isShowingMaximumGroupSizeModal) {
|
||||
modalNode = (
|
||||
<AddGroupMemberErrorDialog
|
||||
i18n={i18n}
|
||||
maximumNumberOfContacts={this.groupSizeHardLimit}
|
||||
maximumNumberOfContacts={this.#groupSizeHardLimit}
|
||||
mode={AddGroupMemberErrorDialogMode.MaximumGroupSize}
|
||||
onClose={closeMaximumGroupSizeModal}
|
||||
/>
|
||||
);
|
||||
} else if (this.isShowingRecommendedGroupSizeModal) {
|
||||
} else if (this.#isShowingRecommendedGroupSizeModal) {
|
||||
modalNode = (
|
||||
<AddGroupMemberErrorDialog
|
||||
i18n={i18n}
|
||||
recommendedMaximumNumberOfContacts={this.groupSizeRecommendedLimit}
|
||||
recommendedMaximumNumberOfContacts={this.#groupSizeRecommendedLimit}
|
||||
mode={AddGroupMemberErrorDialogMode.RecommendedMaximumGroupSize}
|
||||
onClose={closeRecommendedGroupSizeModal}
|
||||
/>
|
||||
|
@ -222,9 +210,9 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
|
||||
return (
|
||||
<>
|
||||
{Boolean(this.selectedContacts.length) && (
|
||||
{Boolean(this.#selectedContacts.length) && (
|
||||
<ContactPills>
|
||||
{this.selectedContacts.map(contact => (
|
||||
{this.#selectedContacts.map(contact => (
|
||||
<ContactPill
|
||||
key={contact.id}
|
||||
acceptedMessageRequest={contact.acceptedMessageRequest}
|
||||
|
@ -264,10 +252,10 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
}>): ReactChild {
|
||||
return (
|
||||
<Button
|
||||
disabled={this.hasExceededMaximumNumberOfContacts()}
|
||||
disabled={this.#hasExceededMaximumNumberOfContacts()}
|
||||
onClick={startSettingGroupMetadata}
|
||||
>
|
||||
{this.selectedContacts.length
|
||||
{this.#selectedContacts.length
|
||||
? i18n('icu:chooseGroupMembers__next')
|
||||
: i18n('icu:chooseGroupMembers__skip')}
|
||||
</Button>
|
||||
|
@ -278,18 +266,18 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
let rowCount = 0;
|
||||
|
||||
// Header + Phone Number
|
||||
if (this.phoneNumber) {
|
||||
if (this.#phoneNumber) {
|
||||
rowCount += 2;
|
||||
}
|
||||
|
||||
// Header + Username
|
||||
if (this.username) {
|
||||
if (this.#username) {
|
||||
rowCount += 2;
|
||||
}
|
||||
|
||||
// Header + Contacts
|
||||
if (this.candidateContacts.length) {
|
||||
rowCount += 1 + this.candidateContacts.length;
|
||||
if (this.#candidateContacts.length) {
|
||||
rowCount += 1 + this.#candidateContacts.length;
|
||||
}
|
||||
|
||||
// Footer
|
||||
|
@ -301,7 +289,11 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
}
|
||||
|
||||
getRow(actualRowIndex: number): undefined | Row {
|
||||
if (!this.candidateContacts.length && !this.phoneNumber && !this.username) {
|
||||
if (
|
||||
!this.#candidateContacts.length &&
|
||||
!this.#phoneNumber &&
|
||||
!this.#username
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -314,7 +306,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
|
||||
let virtualRowIndex = actualRowIndex;
|
||||
|
||||
if (this.candidateContacts.length) {
|
||||
if (this.#candidateContacts.length) {
|
||||
if (virtualRowIndex === 0) {
|
||||
return {
|
||||
type: RowType.Header,
|
||||
|
@ -322,12 +314,12 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
};
|
||||
}
|
||||
|
||||
if (virtualRowIndex <= this.candidateContacts.length) {
|
||||
const contact = this.candidateContacts[virtualRowIndex - 1];
|
||||
if (virtualRowIndex <= this.#candidateContacts.length) {
|
||||
const contact = this.#candidateContacts[virtualRowIndex - 1];
|
||||
|
||||
const isChecked = this.selectedConversationIdsSet.has(contact.id);
|
||||
const isChecked = this.#selectedConversationIdsSet.has(contact.id);
|
||||
const disabledReason =
|
||||
!isChecked && this.hasSelectedMaximumNumberOfContacts()
|
||||
!isChecked && this.#hasSelectedMaximumNumberOfContacts()
|
||||
? ContactCheckboxDisabledReason.MaximumContactsSelected
|
||||
: undefined;
|
||||
|
||||
|
@ -339,10 +331,10 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
};
|
||||
}
|
||||
|
||||
virtualRowIndex -= 1 + this.candidateContacts.length;
|
||||
virtualRowIndex -= 1 + this.#candidateContacts.length;
|
||||
}
|
||||
|
||||
if (this.phoneNumber) {
|
||||
if (this.#phoneNumber) {
|
||||
if (virtualRowIndex === 0) {
|
||||
return {
|
||||
type: RowType.Header,
|
||||
|
@ -352,18 +344,18 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
if (virtualRowIndex === 1) {
|
||||
return {
|
||||
type: RowType.PhoneNumberCheckbox,
|
||||
isChecked: this.isPhoneNumberChecked,
|
||||
isChecked: this.#isPhoneNumberChecked,
|
||||
isFetching: isFetchingByE164(
|
||||
this.uuidFetchState,
|
||||
this.phoneNumber.e164
|
||||
this.#uuidFetchState,
|
||||
this.#phoneNumber.e164
|
||||
),
|
||||
phoneNumber: this.phoneNumber,
|
||||
phoneNumber: this.#phoneNumber,
|
||||
};
|
||||
}
|
||||
virtualRowIndex -= 2;
|
||||
}
|
||||
|
||||
if (this.username) {
|
||||
if (this.#username) {
|
||||
if (virtualRowIndex === 0) {
|
||||
return {
|
||||
type: RowType.Header,
|
||||
|
@ -373,9 +365,12 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
if (virtualRowIndex === 1) {
|
||||
return {
|
||||
type: RowType.UsernameCheckbox,
|
||||
isChecked: this.isUsernameChecked,
|
||||
isFetching: isFetchingByUsername(this.uuidFetchState, this.username),
|
||||
username: this.username,
|
||||
isChecked: this.#isUsernameChecked,
|
||||
isFetching: isFetchingByUsername(
|
||||
this.#uuidFetchState,
|
||||
this.#username
|
||||
),
|
||||
username: this.#username,
|
||||
};
|
||||
}
|
||||
virtualRowIndex -= 2;
|
||||
|
@ -402,13 +397,13 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
|||
return false;
|
||||
}
|
||||
|
||||
private hasSelectedMaximumNumberOfContacts(): boolean {
|
||||
return this.selectedContacts.length >= this.groupSizeHardLimit;
|
||||
#hasSelectedMaximumNumberOfContacts(): boolean {
|
||||
return this.#selectedContacts.length >= this.#groupSizeHardLimit;
|
||||
}
|
||||
|
||||
private hasExceededMaximumNumberOfContacts(): boolean {
|
||||
#hasExceededMaximumNumberOfContacts(): boolean {
|
||||
// It should be impossible to reach this state. This is here as a failsafe.
|
||||
return this.selectedContacts.length > this.groupSizeHardLimit;
|
||||
return this.#selectedContacts.length > this.#groupSizeHardLimit;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,21 +35,14 @@ enum TopButtons {
|
|||
}
|
||||
|
||||
export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsType> {
|
||||
private readonly composeContacts: ReadonlyArray<ContactListItemConversationType>;
|
||||
|
||||
private readonly composeGroups: ReadonlyArray<GroupListItemConversationType>;
|
||||
|
||||
private readonly uuidFetchState: UUIDFetchStateType;
|
||||
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly phoneNumber: ParsedE164Type | undefined;
|
||||
|
||||
private readonly isPhoneNumberVisible: boolean;
|
||||
|
||||
private readonly username: string | undefined;
|
||||
|
||||
private readonly isUsernameVisible: boolean;
|
||||
readonly #composeContacts: ReadonlyArray<ContactListItemConversationType>;
|
||||
readonly #composeGroups: ReadonlyArray<GroupListItemConversationType>;
|
||||
readonly #uuidFetchState: UUIDFetchStateType;
|
||||
readonly #searchTerm: string;
|
||||
readonly #phoneNumber: ParsedE164Type | undefined;
|
||||
readonly #isPhoneNumberVisible: boolean;
|
||||
readonly #username: string | undefined;
|
||||
readonly #isUsernameVisible: boolean;
|
||||
|
||||
constructor({
|
||||
composeContacts,
|
||||
|
@ -61,24 +54,24 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
}: Readonly<LeftPaneComposePropsType>) {
|
||||
super();
|
||||
|
||||
this.composeContacts = composeContacts;
|
||||
this.composeGroups = composeGroups;
|
||||
this.searchTerm = searchTerm;
|
||||
this.uuidFetchState = uuidFetchState;
|
||||
this.#composeContacts = composeContacts;
|
||||
this.#composeGroups = composeGroups;
|
||||
this.#searchTerm = searchTerm;
|
||||
this.#uuidFetchState = uuidFetchState;
|
||||
|
||||
this.username = username;
|
||||
this.isUsernameVisible =
|
||||
this.#username = username;
|
||||
this.#isUsernameVisible =
|
||||
Boolean(username) &&
|
||||
this.composeContacts.every(contact => contact.username !== username);
|
||||
this.#composeContacts.every(contact => contact.username !== username);
|
||||
|
||||
const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
||||
if (!username && phoneNumber) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.isPhoneNumberVisible = this.composeContacts.every(
|
||||
this.#phoneNumber = phoneNumber;
|
||||
this.#isPhoneNumberVisible = this.#composeContacts.every(
|
||||
contact => contact.e164 !== phoneNumber.e164
|
||||
);
|
||||
} else {
|
||||
this.isPhoneNumberVisible = false;
|
||||
this.#isPhoneNumberVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +118,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
onChange={onChangeComposeSearchTerm}
|
||||
placeholder={i18n('icu:contactSearchPlaceholder')}
|
||||
ref={focusRef}
|
||||
value={this.searchTerm}
|
||||
value={this.#searchTerm}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -143,20 +136,20 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
}
|
||||
|
||||
getRowCount(): number {
|
||||
let result = this.composeContacts.length + this.composeGroups.length;
|
||||
if (this.hasTopButtons()) {
|
||||
let result = this.#composeContacts.length + this.#composeGroups.length;
|
||||
if (this.#hasTopButtons()) {
|
||||
result += 3;
|
||||
}
|
||||
if (this.hasContactsHeader()) {
|
||||
if (this.#hasContactsHeader()) {
|
||||
result += 1;
|
||||
}
|
||||
if (this.hasGroupsHeader()) {
|
||||
if (this.#hasGroupsHeader()) {
|
||||
result += 1;
|
||||
}
|
||||
if (this.isUsernameVisible) {
|
||||
if (this.#isUsernameVisible) {
|
||||
result += 2;
|
||||
}
|
||||
if (this.isPhoneNumberVisible) {
|
||||
if (this.#isPhoneNumberVisible) {
|
||||
result += 2;
|
||||
}
|
||||
|
||||
|
@ -165,7 +158,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
|
||||
getRow(actualRowIndex: number): undefined | Row {
|
||||
let virtualRowIndex = actualRowIndex;
|
||||
if (this.hasTopButtons()) {
|
||||
if (this.#hasTopButtons()) {
|
||||
if (virtualRowIndex === 0) {
|
||||
return { type: RowType.CreateNewGroup };
|
||||
}
|
||||
|
@ -179,7 +172,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
virtualRowIndex -= 3;
|
||||
}
|
||||
|
||||
if (this.hasContactsHeader()) {
|
||||
if (this.#hasContactsHeader()) {
|
||||
if (virtualRowIndex === 0) {
|
||||
return {
|
||||
type: RowType.Header,
|
||||
|
@ -189,7 +182,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
|
||||
virtualRowIndex -= 1;
|
||||
|
||||
const contact = this.composeContacts[virtualRowIndex];
|
||||
const contact = this.#composeContacts[virtualRowIndex];
|
||||
if (contact) {
|
||||
return {
|
||||
type: RowType.Contact,
|
||||
|
@ -198,10 +191,10 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
};
|
||||
}
|
||||
|
||||
virtualRowIndex -= this.composeContacts.length;
|
||||
virtualRowIndex -= this.#composeContacts.length;
|
||||
}
|
||||
|
||||
if (this.hasGroupsHeader()) {
|
||||
if (this.#hasGroupsHeader()) {
|
||||
if (virtualRowIndex === 0) {
|
||||
return {
|
||||
type: RowType.Header,
|
||||
|
@ -211,7 +204,7 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
|
||||
virtualRowIndex -= 1;
|
||||
|
||||
const group = this.composeGroups[virtualRowIndex];
|
||||
const group = this.#composeGroups[virtualRowIndex];
|
||||
if (group) {
|
||||
return {
|
||||
type: RowType.SelectSingleGroup,
|
||||
|
@ -219,10 +212,10 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
};
|
||||
}
|
||||
|
||||
virtualRowIndex -= this.composeGroups.length;
|
||||
virtualRowIndex -= this.#composeGroups.length;
|
||||
}
|
||||
|
||||
if (this.username && this.isUsernameVisible) {
|
||||
if (this.#username && this.#isUsernameVisible) {
|
||||
if (virtualRowIndex === 0) {
|
||||
return {
|
||||
type: RowType.Header,
|
||||
|
@ -235,16 +228,16 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
if (virtualRowIndex === 0) {
|
||||
return {
|
||||
type: RowType.UsernameSearchResult,
|
||||
username: this.username,
|
||||
username: this.#username,
|
||||
isFetchingUsername: isFetchingByUsername(
|
||||
this.uuidFetchState,
|
||||
this.username
|
||||
this.#uuidFetchState,
|
||||
this.#username
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (this.phoneNumber && this.isPhoneNumberVisible) {
|
||||
if (this.#phoneNumber && this.#isPhoneNumberVisible) {
|
||||
if (virtualRowIndex === 0) {
|
||||
return {
|
||||
type: RowType.Header,
|
||||
|
@ -257,10 +250,10 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
if (virtualRowIndex === 0) {
|
||||
return {
|
||||
type: RowType.StartNewConversation,
|
||||
phoneNumber: this.phoneNumber,
|
||||
phoneNumber: this.#phoneNumber,
|
||||
isFetching: isFetchingByE164(
|
||||
this.uuidFetchState,
|
||||
this.phoneNumber.e164
|
||||
this.#uuidFetchState,
|
||||
this.#phoneNumber.e164
|
||||
),
|
||||
};
|
||||
}
|
||||
|
@ -287,8 +280,8 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
exProps: Readonly<LeftPaneComposePropsType>
|
||||
): boolean {
|
||||
const prev = new LeftPaneComposeHelper(exProps);
|
||||
const currHeaderIndices = this.getHeaderIndices();
|
||||
const prevHeaderIndices = prev.getHeaderIndices();
|
||||
const currHeaderIndices = this.#getHeaderIndices();
|
||||
const prevHeaderIndices = prev.#getHeaderIndices();
|
||||
|
||||
return (
|
||||
currHeaderIndices.top !== prevHeaderIndices.top ||
|
||||
|
@ -299,26 +292,26 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
);
|
||||
}
|
||||
|
||||
private getTopButtons(): TopButtons {
|
||||
if (this.searchTerm) {
|
||||
#getTopButtons(): TopButtons {
|
||||
if (this.#searchTerm) {
|
||||
return TopButtons.None;
|
||||
}
|
||||
return TopButtons.Visible;
|
||||
}
|
||||
|
||||
private hasTopButtons(): boolean {
|
||||
return this.getTopButtons() !== TopButtons.None;
|
||||
#hasTopButtons(): boolean {
|
||||
return this.#getTopButtons() !== TopButtons.None;
|
||||
}
|
||||
|
||||
private hasContactsHeader(): boolean {
|
||||
return Boolean(this.composeContacts.length);
|
||||
#hasContactsHeader(): boolean {
|
||||
return Boolean(this.#composeContacts.length);
|
||||
}
|
||||
|
||||
private hasGroupsHeader(): boolean {
|
||||
return Boolean(this.composeGroups.length);
|
||||
#hasGroupsHeader(): boolean {
|
||||
return Boolean(this.#composeGroups.length);
|
||||
}
|
||||
|
||||
private getHeaderIndices(): {
|
||||
#getHeaderIndices(): {
|
||||
top?: number;
|
||||
contact?: number;
|
||||
group?: number;
|
||||
|
@ -333,22 +326,22 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
|||
|
||||
let rowCount = 0;
|
||||
|
||||
if (this.hasTopButtons()) {
|
||||
if (this.#hasTopButtons()) {
|
||||
top = 0;
|
||||
rowCount += 3;
|
||||
}
|
||||
if (this.hasContactsHeader()) {
|
||||
if (this.#hasContactsHeader()) {
|
||||
contact = rowCount;
|
||||
rowCount += this.composeContacts.length;
|
||||
rowCount += this.#composeContacts.length;
|
||||
}
|
||||
if (this.hasGroupsHeader()) {
|
||||
if (this.#hasGroupsHeader()) {
|
||||
group = rowCount;
|
||||
rowCount += this.composeContacts.length;
|
||||
rowCount += this.#composeContacts.length;
|
||||
}
|
||||
if (this.phoneNumber) {
|
||||
if (this.#phoneNumber) {
|
||||
phoneNumber = rowCount;
|
||||
}
|
||||
if (this.username) {
|
||||
if (this.#username) {
|
||||
username = rowCount;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,17 +36,12 @@ type DoLookupActionsType = Readonly<{
|
|||
LookupConversationWithoutServiceIdActionsType;
|
||||
|
||||
export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFindByPhoneNumberPropsType> {
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly phoneNumber: ParsedE164Type | undefined;
|
||||
|
||||
private readonly regionCode: string | undefined;
|
||||
|
||||
private readonly uuidFetchState: UUIDFetchStateType;
|
||||
|
||||
private readonly countries: ReadonlyArray<CountryDataType>;
|
||||
|
||||
private readonly selectedRegion: string;
|
||||
readonly #searchTerm: string;
|
||||
readonly #phoneNumber: ParsedE164Type | undefined;
|
||||
readonly #regionCode: string | undefined;
|
||||
readonly #uuidFetchState: UUIDFetchStateType;
|
||||
readonly #countries: ReadonlyArray<CountryDataType>;
|
||||
readonly #selectedRegion: string;
|
||||
|
||||
constructor({
|
||||
searchTerm,
|
||||
|
@ -57,14 +52,14 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
|
|||
}: Readonly<LeftPaneFindByPhoneNumberPropsType>) {
|
||||
super();
|
||||
|
||||
this.searchTerm = searchTerm;
|
||||
this.uuidFetchState = uuidFetchState;
|
||||
this.regionCode = regionCode;
|
||||
this.countries = countries;
|
||||
this.selectedRegion = selectedRegion;
|
||||
this.#searchTerm = searchTerm;
|
||||
this.#uuidFetchState = uuidFetchState;
|
||||
this.#regionCode = regionCode;
|
||||
this.#countries = countries;
|
||||
this.#selectedRegion = selectedRegion;
|
||||
|
||||
this.phoneNumber = parseAndFormatPhoneNumber(
|
||||
this.searchTerm,
|
||||
this.#phoneNumber = parseAndFormatPhoneNumber(
|
||||
this.#searchTerm,
|
||||
selectedRegion || regionCode
|
||||
);
|
||||
}
|
||||
|
@ -83,7 +78,7 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
|
|||
<button
|
||||
aria-label={backButtonLabel}
|
||||
className="module-left-pane__header__contents__back-button"
|
||||
disabled={this.isFetching()}
|
||||
disabled={this.#isFetching()}
|
||||
onClick={this.getBackAction({ startComposing })}
|
||||
title={backButtonLabel}
|
||||
type="button"
|
||||
|
@ -100,7 +95,7 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
|
|||
}: {
|
||||
startComposing: () => void;
|
||||
}): undefined | (() => void) {
|
||||
return this.isFetching() ? undefined : startComposing;
|
||||
return this.#isFetching() ? undefined : startComposing;
|
||||
}
|
||||
|
||||
override getSearchInput({
|
||||
|
@ -122,25 +117,25 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
|
|||
return (
|
||||
<div className="LeftPaneFindByPhoneNumberHelper__container">
|
||||
<CountryCodeSelect
|
||||
countries={this.countries}
|
||||
countries={this.#countries}
|
||||
i18n={i18n}
|
||||
defaultRegion={this.regionCode ?? ''}
|
||||
value={this.selectedRegion}
|
||||
defaultRegion={this.#regionCode ?? ''}
|
||||
value={this.#selectedRegion}
|
||||
onChange={onChangeComposeSelectedRegion}
|
||||
/>
|
||||
|
||||
<SearchInput
|
||||
hasSearchIcon={false}
|
||||
disabled={this.isFetching()}
|
||||
disabled={this.#isFetching()}
|
||||
i18n={i18n}
|
||||
moduleClassName="LeftPaneFindByPhoneNumberHelper__search-input"
|
||||
onChange={onChangeComposeSearchTerm}
|
||||
placeholder={placeholder}
|
||||
ref={focusRef}
|
||||
value={this.searchTerm}
|
||||
value={this.#searchTerm}
|
||||
onKeyDown={ev => {
|
||||
if (ev.key === 'Enter') {
|
||||
drop(this.doLookup(lookupActions));
|
||||
drop(this.#doLookup(lookupActions));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -157,10 +152,10 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
|
|||
DoLookupActionsType): ReactChild {
|
||||
return (
|
||||
<Button
|
||||
disabled={this.isLookupDisabled()}
|
||||
onClick={() => drop(this.doLookup(lookupActions))}
|
||||
disabled={this.#isLookupDisabled()}
|
||||
onClick={() => drop(this.#doLookup(lookupActions))}
|
||||
>
|
||||
{this.isFetching() ? (
|
||||
{this.#isFetching() ? (
|
||||
<span aria-label={i18n('icu:loading')} role="status">
|
||||
<Spinner size="20px" svgSize="small" direction="on-avatar" />
|
||||
</span>
|
||||
|
@ -198,14 +193,14 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
|
|||
return false;
|
||||
}
|
||||
|
||||
private async doLookup({
|
||||
async #doLookup({
|
||||
lookupConversationWithoutServiceId,
|
||||
showUserNotFoundModal,
|
||||
setIsFetchingUUID,
|
||||
showInbox,
|
||||
showConversation,
|
||||
}: DoLookupActionsType): Promise<void> {
|
||||
if (!this.phoneNumber || this.isLookupDisabled()) {
|
||||
if (!this.#phoneNumber || this.#isLookupDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -213,8 +208,8 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
|
|||
showUserNotFoundModal,
|
||||
setIsFetchingUUID,
|
||||
type: 'e164',
|
||||
e164: this.phoneNumber.e164,
|
||||
phoneNumber: this.searchTerm,
|
||||
e164: this.#phoneNumber.e164,
|
||||
phoneNumber: this.#searchTerm,
|
||||
});
|
||||
|
||||
if (conversationId != null) {
|
||||
|
@ -223,20 +218,20 @@ export class LeftPaneFindByPhoneNumberHelper extends LeftPaneHelper<LeftPaneFind
|
|||
}
|
||||
}
|
||||
|
||||
private isFetching(): boolean {
|
||||
if (this.phoneNumber != null) {
|
||||
return isFetchingByE164(this.uuidFetchState, this.phoneNumber.e164);
|
||||
#isFetching(): boolean {
|
||||
if (this.#phoneNumber != null) {
|
||||
return isFetchingByE164(this.#uuidFetchState, this.#phoneNumber.e164);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private isLookupDisabled(): boolean {
|
||||
if (this.isFetching()) {
|
||||
#isLookupDisabled(): boolean {
|
||||
if (this.#isFetching()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !this.phoneNumber?.isValid;
|
||||
return !this.#phoneNumber?.isValid;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,11 +30,9 @@ type DoLookupActionsType = Readonly<{
|
|||
LookupConversationWithoutServiceIdActionsType;
|
||||
|
||||
export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByUsernamePropsType> {
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly username: string | undefined;
|
||||
|
||||
private readonly uuidFetchState: UUIDFetchStateType;
|
||||
readonly #searchTerm: string;
|
||||
readonly #username: string | undefined;
|
||||
readonly #uuidFetchState: UUIDFetchStateType;
|
||||
|
||||
constructor({
|
||||
searchTerm,
|
||||
|
@ -43,10 +41,10 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
|
|||
}: Readonly<LeftPaneFindByUsernamePropsType>) {
|
||||
super();
|
||||
|
||||
this.searchTerm = searchTerm;
|
||||
this.uuidFetchState = uuidFetchState;
|
||||
this.#searchTerm = searchTerm;
|
||||
this.#uuidFetchState = uuidFetchState;
|
||||
|
||||
this.username = username;
|
||||
this.#username = username;
|
||||
}
|
||||
|
||||
override getHeaderContents({
|
||||
|
@ -63,7 +61,7 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
|
|||
<button
|
||||
aria-label={backButtonLabel}
|
||||
className="module-left-pane__header__contents__back-button"
|
||||
disabled={this.isFetching()}
|
||||
disabled={this.#isFetching()}
|
||||
onClick={this.getBackAction({ startComposing })}
|
||||
title={backButtonLabel}
|
||||
type="button"
|
||||
|
@ -80,7 +78,7 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
|
|||
}: {
|
||||
startComposing: () => void;
|
||||
}): undefined | (() => void) {
|
||||
return this.isFetching() ? undefined : startComposing;
|
||||
return this.#isFetching() ? undefined : startComposing;
|
||||
}
|
||||
|
||||
override getSearchInput({
|
||||
|
@ -103,17 +101,17 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
|
|||
return (
|
||||
<SearchInput
|
||||
hasSearchIcon={false}
|
||||
disabled={this.isFetching()}
|
||||
disabled={this.#isFetching()}
|
||||
i18n={i18n}
|
||||
moduleClassName="LeftPaneFindByUsernameHelper__search-input"
|
||||
onChange={onChangeComposeSearchTerm}
|
||||
placeholder={placeholder}
|
||||
ref={focusRef}
|
||||
value={this.searchTerm}
|
||||
value={this.#searchTerm}
|
||||
description={description}
|
||||
onKeyDown={ev => {
|
||||
if (ev.key === 'Enter') {
|
||||
drop(this.doLookup(lookupActions));
|
||||
drop(this.#doLookup(lookupActions));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
@ -129,10 +127,10 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
|
|||
DoLookupActionsType): ReactChild {
|
||||
return (
|
||||
<Button
|
||||
disabled={this.isLookupDisabled()}
|
||||
onClick={() => drop(this.doLookup(lookupActions))}
|
||||
disabled={this.#isLookupDisabled()}
|
||||
onClick={() => drop(this.#doLookup(lookupActions))}
|
||||
>
|
||||
{this.isFetching() ? (
|
||||
{this.#isFetching() ? (
|
||||
<span aria-label={i18n('icu:loading')} role="status">
|
||||
<Spinner size="20px" svgSize="small" direction="on-avatar" />
|
||||
</span>
|
||||
|
@ -170,14 +168,14 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
|
|||
return false;
|
||||
}
|
||||
|
||||
private async doLookup({
|
||||
async #doLookup({
|
||||
lookupConversationWithoutServiceId,
|
||||
showUserNotFoundModal,
|
||||
setIsFetchingUUID,
|
||||
showInbox,
|
||||
showConversation,
|
||||
}: DoLookupActionsType): Promise<void> {
|
||||
if (!this.username || this.isLookupDisabled()) {
|
||||
if (!this.#username || this.#isLookupDisabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -185,7 +183,7 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
|
|||
showUserNotFoundModal,
|
||||
setIsFetchingUUID,
|
||||
type: 'username',
|
||||
username: this.username,
|
||||
username: this.#username,
|
||||
});
|
||||
|
||||
if (conversationId != null) {
|
||||
|
@ -194,20 +192,20 @@ export class LeftPaneFindByUsernameHelper extends LeftPaneHelper<LeftPaneFindByU
|
|||
}
|
||||
}
|
||||
|
||||
private isFetching(): boolean {
|
||||
if (this.username != null) {
|
||||
return isFetchingByUsername(this.uuidFetchState, this.username);
|
||||
#isFetching(): boolean {
|
||||
if (this.#username != null) {
|
||||
return isFetchingByUsername(this.#uuidFetchState, this.#username);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private isLookupDisabled(): boolean {
|
||||
if (this.isFetching()) {
|
||||
#isLookupDisabled(): boolean {
|
||||
if (this.#isFetching()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.username == null;
|
||||
return this.#username == null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,25 +34,16 @@ export type LeftPaneInboxPropsType = {
|
|||
};
|
||||
|
||||
export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType> {
|
||||
private readonly conversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
|
||||
private readonly archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
|
||||
private readonly pinnedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
|
||||
private readonly isAboutToSearch: boolean;
|
||||
|
||||
private readonly isSearchingGlobally: boolean;
|
||||
|
||||
private readonly startSearchCounter: number;
|
||||
|
||||
private readonly searchDisabled: boolean;
|
||||
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly searchConversation: undefined | ConversationType;
|
||||
|
||||
private readonly filterByUnread: boolean;
|
||||
readonly #conversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
readonly #archivedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
readonly #pinnedConversations: ReadonlyArray<ConversationListItemPropsType>;
|
||||
readonly #isAboutToSearch: boolean;
|
||||
readonly #isSearchingGlobally: boolean;
|
||||
readonly #startSearchCounter: number;
|
||||
readonly #searchDisabled: boolean;
|
||||
readonly #searchTerm: string;
|
||||
readonly #searchConversation: undefined | ConversationType;
|
||||
readonly #filterByUnread: boolean;
|
||||
|
||||
constructor({
|
||||
conversations,
|
||||
|
@ -68,25 +59,25 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
}: Readonly<LeftPaneInboxPropsType>) {
|
||||
super();
|
||||
|
||||
this.conversations = conversations;
|
||||
this.archivedConversations = archivedConversations;
|
||||
this.pinnedConversations = pinnedConversations;
|
||||
this.isAboutToSearch = isAboutToSearch;
|
||||
this.isSearchingGlobally = isSearchingGlobally;
|
||||
this.startSearchCounter = startSearchCounter;
|
||||
this.searchDisabled = searchDisabled;
|
||||
this.searchTerm = searchTerm;
|
||||
this.searchConversation = searchConversation;
|
||||
this.filterByUnread = filterByUnread;
|
||||
this.#conversations = conversations;
|
||||
this.#archivedConversations = archivedConversations;
|
||||
this.#pinnedConversations = pinnedConversations;
|
||||
this.#isAboutToSearch = isAboutToSearch;
|
||||
this.#isSearchingGlobally = isSearchingGlobally;
|
||||
this.#startSearchCounter = startSearchCounter;
|
||||
this.#searchDisabled = searchDisabled;
|
||||
this.#searchTerm = searchTerm;
|
||||
this.#searchConversation = searchConversation;
|
||||
this.#filterByUnread = filterByUnread;
|
||||
}
|
||||
|
||||
getRowCount(): number {
|
||||
const headerCount = this.hasPinnedAndNonpinned() ? 2 : 0;
|
||||
const buttonCount = this.archivedConversations.length ? 1 : 0;
|
||||
const headerCount = this.#hasPinnedAndNonpinned() ? 2 : 0;
|
||||
const buttonCount = this.#archivedConversations.length ? 1 : 0;
|
||||
return (
|
||||
headerCount +
|
||||
this.pinnedConversations.length +
|
||||
this.conversations.length +
|
||||
this.#pinnedConversations.length +
|
||||
this.#conversations.length +
|
||||
buttonCount
|
||||
);
|
||||
}
|
||||
|
@ -116,17 +107,17 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
clearSearchQuery={clearSearchQuery}
|
||||
endConversationSearch={endConversationSearch}
|
||||
endSearch={endSearch}
|
||||
disabled={this.searchDisabled}
|
||||
disabled={this.#searchDisabled}
|
||||
i18n={i18n}
|
||||
isSearchingGlobally={this.isSearchingGlobally}
|
||||
searchConversation={this.searchConversation}
|
||||
searchTerm={this.searchTerm}
|
||||
isSearchingGlobally={this.#isSearchingGlobally}
|
||||
searchConversation={this.#searchConversation}
|
||||
searchTerm={this.#searchTerm}
|
||||
showConversation={showConversation}
|
||||
startSearchCounter={this.startSearchCounter}
|
||||
startSearchCounter={this.#startSearchCounter}
|
||||
updateSearchTerm={updateSearchTerm}
|
||||
onFilterClick={updateFilterByUnread}
|
||||
filterButtonEnabled={!this.searchConversation}
|
||||
filterPressed={this.filterByUnread}
|
||||
filterButtonEnabled={!this.#searchConversation}
|
||||
filterPressed={this.#filterByUnread}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -149,11 +140,13 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
}
|
||||
|
||||
getRow(rowIndex: number): undefined | Row {
|
||||
const { conversations, archivedConversations, pinnedConversations } = this;
|
||||
const pinnedConversations = this.#pinnedConversations;
|
||||
const archivedConversations = this.#archivedConversations;
|
||||
const conversations = this.#conversations;
|
||||
|
||||
const archivedConversationsCount = archivedConversations.length;
|
||||
|
||||
if (this.hasPinnedAndNonpinned()) {
|
||||
if (this.#hasPinnedAndNonpinned()) {
|
||||
switch (rowIndex) {
|
||||
case 0:
|
||||
return {
|
||||
|
@ -226,9 +219,9 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
const isConversationSelected = (
|
||||
conversation: Readonly<ConversationListItemPropsType>
|
||||
) => conversation.id === selectedConversationId;
|
||||
const hasHeaders = this.hasPinnedAndNonpinned();
|
||||
const hasHeaders = this.#hasPinnedAndNonpinned();
|
||||
|
||||
const pinnedConversationIndex = this.pinnedConversations.findIndex(
|
||||
const pinnedConversationIndex = this.#pinnedConversations.findIndex(
|
||||
isConversationSelected
|
||||
);
|
||||
if (pinnedConversationIndex !== -1) {
|
||||
|
@ -236,11 +229,11 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
return pinnedConversationIndex + headerOffset;
|
||||
}
|
||||
|
||||
const conversationIndex = this.conversations.findIndex(
|
||||
const conversationIndex = this.#conversations.findIndex(
|
||||
isConversationSelected
|
||||
);
|
||||
if (conversationIndex !== -1) {
|
||||
const pinnedOffset = this.pinnedConversations.length;
|
||||
const pinnedOffset = this.#pinnedConversations.length;
|
||||
const headerOffset = hasHeaders ? 2 : 0;
|
||||
return conversationIndex + pinnedOffset + headerOffset;
|
||||
}
|
||||
|
@ -250,20 +243,21 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
|
||||
override requiresFullWidth(): boolean {
|
||||
const hasNoConversations =
|
||||
!this.conversations.length &&
|
||||
!this.pinnedConversations.length &&
|
||||
!this.archivedConversations.length;
|
||||
return hasNoConversations || this.isAboutToSearch;
|
||||
!this.#conversations.length &&
|
||||
!this.#pinnedConversations.length &&
|
||||
!this.#archivedConversations.length;
|
||||
return hasNoConversations || this.#isAboutToSearch;
|
||||
}
|
||||
|
||||
shouldRecomputeRowHeights(old: Readonly<LeftPaneInboxPropsType>): boolean {
|
||||
return old.pinnedConversations.length !== this.pinnedConversations.length;
|
||||
return old.pinnedConversations.length !== this.#pinnedConversations.length;
|
||||
}
|
||||
|
||||
getConversationAndMessageAtIndex(
|
||||
conversationIndex: number
|
||||
): undefined | { conversationId: string } {
|
||||
const { conversations, pinnedConversations } = this;
|
||||
const pinnedConversations = this.#pinnedConversations;
|
||||
const conversations = this.#conversations;
|
||||
const conversation =
|
||||
pinnedConversations[conversationIndex] ||
|
||||
conversations[conversationIndex - pinnedConversations.length] ||
|
||||
|
@ -278,7 +272,7 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
_targetedMessageId: unknown
|
||||
): undefined | { conversationId: string } {
|
||||
return getConversationInDirection(
|
||||
[...this.pinnedConversations, ...this.conversations],
|
||||
[...this.#pinnedConversations, ...this.#conversations],
|
||||
toFind,
|
||||
selectedConversationId
|
||||
);
|
||||
|
@ -295,9 +289,9 @@ export class LeftPaneInboxHelper extends LeftPaneHelper<LeftPaneInboxPropsType>
|
|||
handleKeydownForSearch(event, options);
|
||||
}
|
||||
|
||||
private hasPinnedAndNonpinned(): boolean {
|
||||
#hasPinnedAndNonpinned(): boolean {
|
||||
return Boolean(
|
||||
this.pinnedConversations.length && this.conversations.length
|
||||
this.#pinnedConversations.length && this.#conversations.length
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,36 +50,24 @@ export type LeftPaneSearchPropsType = {
|
|||
searchConversation: undefined | ConversationType;
|
||||
};
|
||||
|
||||
const searchResultKeys: Array<
|
||||
'conversationResults' | 'contactResults' | 'messageResults'
|
||||
> = ['conversationResults', 'contactResults', 'messageResults'];
|
||||
|
||||
export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType> {
|
||||
private readonly conversationResults: MaybeLoadedSearchResultsType<ConversationListItemPropsType>;
|
||||
readonly #conversationResults: MaybeLoadedSearchResultsType<ConversationListItemPropsType>;
|
||||
readonly #contactResults: MaybeLoadedSearchResultsType<ConversationListItemPropsType>;
|
||||
readonly #isSearchingGlobally: boolean;
|
||||
|
||||
private readonly contactResults: MaybeLoadedSearchResultsType<ConversationListItemPropsType>;
|
||||
|
||||
private readonly isSearchingGlobally: boolean;
|
||||
|
||||
private readonly messageResults: MaybeLoadedSearchResultsType<{
|
||||
readonly #messageResults: MaybeLoadedSearchResultsType<{
|
||||
id: string;
|
||||
conversationId: string;
|
||||
type: string;
|
||||
}>;
|
||||
|
||||
private readonly searchConversationName?: string;
|
||||
|
||||
private readonly primarySendsSms: boolean;
|
||||
|
||||
private readonly searchTerm: string;
|
||||
|
||||
private readonly startSearchCounter: number;
|
||||
|
||||
private readonly searchDisabled: boolean;
|
||||
|
||||
private readonly searchConversation: undefined | ConversationType;
|
||||
|
||||
private readonly filterByUnread: boolean;
|
||||
readonly #searchConversationName?: string;
|
||||
readonly #primarySendsSms: boolean;
|
||||
readonly #searchTerm: string;
|
||||
readonly #startSearchCounter: number;
|
||||
readonly #searchDisabled: boolean;
|
||||
readonly #searchConversation: undefined | ConversationType;
|
||||
readonly #filterByUnread: boolean;
|
||||
|
||||
constructor({
|
||||
contactResults,
|
||||
|
@ -96,18 +84,17 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
}: Readonly<LeftPaneSearchPropsType>) {
|
||||
super();
|
||||
|
||||
this.contactResults = contactResults;
|
||||
this.conversationResults = conversationResults;
|
||||
this.isSearchingGlobally = isSearchingGlobally;
|
||||
this.messageResults = messageResults;
|
||||
this.primarySendsSms = primarySendsSms;
|
||||
this.searchConversation = searchConversation;
|
||||
this.searchConversationName = searchConversationName;
|
||||
this.searchDisabled = searchDisabled;
|
||||
this.searchTerm = searchTerm;
|
||||
this.startSearchCounter = startSearchCounter;
|
||||
this.filterByUnread = filterByUnread;
|
||||
this.onEnterKeyDown = this.onEnterKeyDown.bind(this);
|
||||
this.#contactResults = contactResults;
|
||||
this.#conversationResults = conversationResults;
|
||||
this.#isSearchingGlobally = isSearchingGlobally;
|
||||
this.#messageResults = messageResults;
|
||||
this.#primarySendsSms = primarySendsSms;
|
||||
this.#searchConversation = searchConversation;
|
||||
this.#searchConversationName = searchConversationName;
|
||||
this.#searchDisabled = searchDisabled;
|
||||
this.#searchTerm = searchTerm;
|
||||
this.#startSearchCounter = startSearchCounter;
|
||||
this.#filterByUnread = filterByUnread;
|
||||
}
|
||||
|
||||
override getSearchInput({
|
||||
|
@ -135,17 +122,17 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
clearSearchQuery={clearSearchQuery}
|
||||
endConversationSearch={endConversationSearch}
|
||||
endSearch={endSearch}
|
||||
disabled={this.searchDisabled}
|
||||
disabled={this.#searchDisabled}
|
||||
i18n={i18n}
|
||||
isSearchingGlobally={this.isSearchingGlobally}
|
||||
onEnterKeyDown={this.onEnterKeyDown}
|
||||
searchConversation={this.searchConversation}
|
||||
searchTerm={this.searchTerm}
|
||||
isSearchingGlobally={this.#isSearchingGlobally}
|
||||
onEnterKeyDown={this.#onEnterKeyDown}
|
||||
searchConversation={this.#searchConversation}
|
||||
searchTerm={this.#searchTerm}
|
||||
showConversation={showConversation}
|
||||
startSearchCounter={this.startSearchCounter}
|
||||
startSearchCounter={this.#startSearchCounter}
|
||||
updateSearchTerm={updateSearchTerm}
|
||||
filterButtonEnabled={!this.searchConversation}
|
||||
filterPressed={this.filterByUnread}
|
||||
filterButtonEnabled={!this.#searchConversation}
|
||||
filterPressed={this.#filterByUnread}
|
||||
onFilterClick={updateFilterByUnread}
|
||||
/>
|
||||
);
|
||||
|
@ -156,7 +143,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
}: Readonly<{
|
||||
i18n: LocalizerType;
|
||||
}>): ReactChild | null {
|
||||
const mightHaveSearchResults = this.allResults().some(
|
||||
const mightHaveSearchResults = this.#allResults().some(
|
||||
searchResult => searchResult.isLoading || searchResult.results.length
|
||||
);
|
||||
|
||||
|
@ -164,7 +151,9 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
return null;
|
||||
}
|
||||
|
||||
const { searchConversationName, primarySendsSms, searchTerm } = this;
|
||||
const searchTerm = this.#searchTerm;
|
||||
const primarySendsSms = this.#primarySendsSms;
|
||||
const searchConversationName = this.#searchConversationName;
|
||||
|
||||
let noResults: ReactChild;
|
||||
if (searchConversationName) {
|
||||
|
@ -182,11 +171,11 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
);
|
||||
} else {
|
||||
let noResultsMessage: string;
|
||||
if (this.filterByUnread && this.searchTerm.length > 0) {
|
||||
if (this.#filterByUnread && this.#searchTerm.length > 0) {
|
||||
noResultsMessage = i18n('icu:noSearchResultsWithUnreadFilter', {
|
||||
searchTerm,
|
||||
});
|
||||
} else if (this.filterByUnread) {
|
||||
} else if (this.#filterByUnread) {
|
||||
noResultsMessage = i18n('icu:noSearchResultsOnlyUnreadFilter');
|
||||
} else {
|
||||
noResultsMessage = i18n('icu:noSearchResults', {
|
||||
|
@ -195,7 +184,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
}
|
||||
noResults = (
|
||||
<>
|
||||
{this.filterByUnread && (
|
||||
{this.#filterByUnread && (
|
||||
<div
|
||||
className="module-conversation-list__item--header module-left-pane__no-search-results__unread-header"
|
||||
aria-label={i18n('icu:conversationsUnreadHeader')}
|
||||
|
@ -218,7 +207,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
// We need this for Ctrl-T shortcut cycling through parts of app
|
||||
tabIndex={-1}
|
||||
className={
|
||||
this.filterByUnread
|
||||
this.#filterByUnread
|
||||
? 'module-left-pane__no-search-results--withHeader'
|
||||
: 'module-left-pane__no-search-results'
|
||||
}
|
||||
|
@ -230,19 +219,19 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
}
|
||||
|
||||
getRowCount(): number {
|
||||
if (this.isLoading()) {
|
||||
if (this.#isLoading()) {
|
||||
// 1 for the header.
|
||||
return 1 + SEARCH_RESULTS_FAKE_ROW_COUNT;
|
||||
}
|
||||
|
||||
let count = this.allResults().reduce(
|
||||
let count = this.#allResults().reduce(
|
||||
(result: number, searchResults) =>
|
||||
result + getRowCountForLoadedSearchResults(searchResults),
|
||||
0
|
||||
);
|
||||
|
||||
// The clear unread filter button adds an extra row
|
||||
if (this.filterByUnread) {
|
||||
if (this.#filterByUnread) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
|
@ -257,9 +246,11 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
}
|
||||
|
||||
getRow(rowIndex: number): undefined | Row {
|
||||
const { conversationResults, contactResults, messageResults } = this;
|
||||
const messageResults = this.#messageResults;
|
||||
const contactResults = this.#contactResults;
|
||||
const conversationResults = this.#conversationResults;
|
||||
|
||||
if (this.isLoading()) {
|
||||
if (this.#isLoading()) {
|
||||
if (rowIndex === 0) {
|
||||
return { type: RowType.SearchResultsLoadingFakeHeader };
|
||||
}
|
||||
|
@ -273,7 +264,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
getRowCountForLoadedSearchResults(conversationResults);
|
||||
const contactRowCount = getRowCountForLoadedSearchResults(contactResults);
|
||||
const messageRowCount = getRowCountForLoadedSearchResults(messageResults);
|
||||
const clearFilterButtonRowCount = this.filterByUnread ? 1 : 0;
|
||||
const clearFilterButtonRowCount = this.#filterByUnread ? 1 : 0;
|
||||
|
||||
let rowOffset = 0;
|
||||
|
||||
|
@ -283,7 +274,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
return {
|
||||
type: RowType.Header,
|
||||
getHeaderText: i18n =>
|
||||
this.filterByUnread
|
||||
this.#filterByUnread
|
||||
? i18n('icu:conversationsUnreadHeader')
|
||||
: i18n('icu:conversationsHeader'),
|
||||
};
|
||||
|
@ -350,7 +341,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
if (rowIndex < rowOffset) {
|
||||
return {
|
||||
type: RowType.ClearFilterButton,
|
||||
isOnNoResultsPage: this.allResults().every(
|
||||
isOnNoResultsPage: this.#allResults().every(
|
||||
searchResult =>
|
||||
searchResult.isLoading || searchResult.results.length === 0
|
||||
),
|
||||
|
@ -361,24 +352,30 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
}
|
||||
|
||||
override isScrollable(): boolean {
|
||||
return !this.isLoading();
|
||||
return !this.#isLoading();
|
||||
}
|
||||
|
||||
shouldRecomputeRowHeights(old: Readonly<LeftPaneSearchPropsType>): boolean {
|
||||
const oldSearchPaneHelper = new LeftPaneSearchHelper(old);
|
||||
const oldIsLoading = oldSearchPaneHelper.isLoading();
|
||||
const newIsLoading = this.isLoading();
|
||||
const oldIsLoading = oldSearchPaneHelper.#isLoading();
|
||||
const newIsLoading = this.#isLoading();
|
||||
if (oldIsLoading && newIsLoading) {
|
||||
return false;
|
||||
}
|
||||
if (oldIsLoading !== newIsLoading) {
|
||||
return true;
|
||||
}
|
||||
return searchResultKeys.some(
|
||||
key =>
|
||||
getRowCountForLoadedSearchResults(old[key]) !==
|
||||
getRowCountForLoadedSearchResults(this[key])
|
||||
);
|
||||
const searchResultsByKey = [
|
||||
{ current: this.#conversationResults, prev: old.conversationResults },
|
||||
{ current: this.#contactResults, prev: old.contactResults },
|
||||
{ current: this.#messageResults, prev: old.messageResults },
|
||||
];
|
||||
return searchResultsByKey.some(item => {
|
||||
return (
|
||||
getRowCountForLoadedSearchResults(item.prev) !==
|
||||
getRowCountForLoadedSearchResults(item.current)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getConversationAndMessageAtIndex(
|
||||
|
@ -388,7 +385,7 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
return undefined;
|
||||
}
|
||||
let pointer = conversationIndex;
|
||||
for (const list of this.allResults()) {
|
||||
for (const list of this.#allResults()) {
|
||||
if (list.isLoading) {
|
||||
continue;
|
||||
}
|
||||
|
@ -426,25 +423,29 @@ export class LeftPaneSearchHelper extends LeftPaneHelper<LeftPaneSearchPropsType
|
|||
handleKeydownForSearch(event, options);
|
||||
}
|
||||
|
||||
private allResults() {
|
||||
return [this.conversationResults, this.contactResults, this.messageResults];
|
||||
#allResults() {
|
||||
return [
|
||||
this.#conversationResults,
|
||||
this.#contactResults,
|
||||
this.#messageResults,
|
||||
];
|
||||
}
|
||||
|
||||
private isLoading(): boolean {
|
||||
return this.allResults().some(results => results.isLoading);
|
||||
#isLoading(): boolean {
|
||||
return this.#allResults().some(results => results.isLoading);
|
||||
}
|
||||
|
||||
private onEnterKeyDown(
|
||||
#onEnterKeyDown = (
|
||||
clearSearchQuery: () => unknown,
|
||||
showConversation: ShowConversationType
|
||||
): void {
|
||||
): void => {
|
||||
const conversation = this.getConversationAndMessageAtIndex(0);
|
||||
if (!conversation) {
|
||||
return;
|
||||
}
|
||||
showConversation(conversation);
|
||||
clearSearchQuery();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getRowCountForLoadedSearchResults(
|
||||
|
|
|
@ -38,21 +38,14 @@ export type LeftPaneSetGroupMetadataPropsType = {
|
|||
};
|
||||
|
||||
export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGroupMetadataPropsType> {
|
||||
private readonly groupAvatar: undefined | Uint8Array;
|
||||
|
||||
private readonly groupName: string;
|
||||
|
||||
private readonly groupExpireTimer: DurationInSeconds;
|
||||
|
||||
private readonly hasError: boolean;
|
||||
|
||||
private readonly isCreating: boolean;
|
||||
|
||||
private readonly isEditingAvatar: boolean;
|
||||
|
||||
private readonly selectedContacts: ReadonlyArray<ContactListItemConversationType>;
|
||||
|
||||
private readonly userAvatarData: ReadonlyArray<AvatarDataType>;
|
||||
readonly #groupAvatar: undefined | Uint8Array;
|
||||
readonly #groupName: string;
|
||||
readonly #groupExpireTimer: DurationInSeconds;
|
||||
readonly #hasError: boolean;
|
||||
readonly #isCreating: boolean;
|
||||
readonly #isEditingAvatar: boolean;
|
||||
readonly #selectedContacts: ReadonlyArray<ContactListItemConversationType>;
|
||||
readonly #userAvatarData: ReadonlyArray<AvatarDataType>;
|
||||
|
||||
constructor({
|
||||
groupAvatar,
|
||||
|
@ -66,14 +59,14 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
}: Readonly<LeftPaneSetGroupMetadataPropsType>) {
|
||||
super();
|
||||
|
||||
this.groupAvatar = groupAvatar;
|
||||
this.groupName = groupName;
|
||||
this.groupExpireTimer = groupExpireTimer;
|
||||
this.hasError = hasError;
|
||||
this.isCreating = isCreating;
|
||||
this.isEditingAvatar = isEditingAvatar;
|
||||
this.selectedContacts = selectedContacts;
|
||||
this.userAvatarData = userAvatarData;
|
||||
this.#groupAvatar = groupAvatar;
|
||||
this.#groupName = groupName;
|
||||
this.#groupExpireTimer = groupExpireTimer;
|
||||
this.#hasError = hasError;
|
||||
this.#isCreating = isCreating;
|
||||
this.#isEditingAvatar = isEditingAvatar;
|
||||
this.#selectedContacts = selectedContacts;
|
||||
this.#userAvatarData = userAvatarData;
|
||||
}
|
||||
|
||||
override getHeaderContents({
|
||||
|
@ -90,7 +83,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
<button
|
||||
aria-label={backButtonLabel}
|
||||
className="module-left-pane__header__contents__back-button"
|
||||
disabled={this.isCreating}
|
||||
disabled={this.#isCreating}
|
||||
onClick={this.getBackAction({ showChooseGroupMembers })}
|
||||
title={backButtonLabel}
|
||||
type="button"
|
||||
|
@ -107,7 +100,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
}: {
|
||||
showChooseGroupMembers: () => void;
|
||||
}): undefined | (() => void) {
|
||||
return this.isCreating ? undefined : showChooseGroupMembers;
|
||||
return this.#isCreating ? undefined : showChooseGroupMembers;
|
||||
}
|
||||
|
||||
override getPreRowsNode({
|
||||
|
@ -134,7 +127,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
toggleComposeEditingAvatar: () => unknown;
|
||||
}>): ReactChild {
|
||||
const [avatarColor] = AvatarColors;
|
||||
const disabled = this.isCreating;
|
||||
const disabled = this.#isCreating;
|
||||
|
||||
return (
|
||||
<form
|
||||
|
@ -143,14 +136,14 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (!this.canCreateGroup()) {
|
||||
if (!this.#canCreateGroup()) {
|
||||
return;
|
||||
}
|
||||
|
||||
createGroup();
|
||||
}}
|
||||
>
|
||||
{this.isEditingAvatar && (
|
||||
{this.#isEditingAvatar && (
|
||||
<Modal
|
||||
modalName="LeftPaneSetGroupMetadataHelper.AvatarEditor"
|
||||
hasXButton
|
||||
|
@ -162,7 +155,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
>
|
||||
<AvatarEditor
|
||||
avatarColor={avatarColor}
|
||||
avatarValue={this.groupAvatar}
|
||||
avatarValue={this.#groupAvatar}
|
||||
deleteAvatarFromDisk={composeDeleteAvatarFromDisk}
|
||||
i18n={i18n}
|
||||
isGroup
|
||||
|
@ -171,7 +164,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
setComposeGroupAvatar(newAvatar);
|
||||
toggleComposeEditingAvatar();
|
||||
}}
|
||||
userAvatarData={this.userAvatarData}
|
||||
userAvatarData={this.#userAvatarData}
|
||||
replaceAvatar={composeReplaceAvatar}
|
||||
saveAvatarToDisk={composeSaveAvatarToDisk}
|
||||
/>
|
||||
|
@ -179,7 +172,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
)}
|
||||
<AvatarPreview
|
||||
avatarColor={avatarColor}
|
||||
avatarValue={this.groupAvatar}
|
||||
avatarValue={this.#groupAvatar}
|
||||
i18n={i18n}
|
||||
isEditable
|
||||
isGroup
|
||||
|
@ -196,7 +189,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
i18n={i18n}
|
||||
onChangeValue={setComposeGroupName}
|
||||
ref={focusRef}
|
||||
value={this.groupName}
|
||||
value={this.#groupName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -206,12 +199,12 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
</div>
|
||||
<DisappearingTimerSelect
|
||||
i18n={i18n}
|
||||
value={this.groupExpireTimer}
|
||||
value={this.#groupExpireTimer}
|
||||
onChange={setComposeGroupExpireTimer}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{this.hasError && (
|
||||
{this.#hasError && (
|
||||
<Alert
|
||||
body={i18n('icu:setGroupMetadata__error-message')}
|
||||
i18n={i18n}
|
||||
|
@ -231,12 +224,12 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
}>): ReactChild {
|
||||
return (
|
||||
<Button
|
||||
disabled={!this.canCreateGroup()}
|
||||
disabled={!this.#canCreateGroup()}
|
||||
onClick={() => {
|
||||
createGroup();
|
||||
}}
|
||||
>
|
||||
{this.isCreating ? (
|
||||
{this.#isCreating ? (
|
||||
<span aria-label={i18n('icu:loading')} role="status">
|
||||
<Spinner size="20px" svgSize="small" direction="on-avatar" />
|
||||
</span>
|
||||
|
@ -248,14 +241,14 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
}
|
||||
|
||||
getRowCount(): number {
|
||||
if (!this.selectedContacts.length) {
|
||||
if (!this.#selectedContacts.length) {
|
||||
return 0;
|
||||
}
|
||||
return this.selectedContacts.length + 2;
|
||||
return this.#selectedContacts.length + 2;
|
||||
}
|
||||
|
||||
getRow(rowIndex: number): undefined | Row {
|
||||
if (!this.selectedContacts.length) {
|
||||
if (!this.#selectedContacts.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -267,11 +260,11 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
}
|
||||
|
||||
// This puts a blank row for the footer.
|
||||
if (rowIndex === this.selectedContacts.length + 1) {
|
||||
if (rowIndex === this.#selectedContacts.length + 1) {
|
||||
return { type: RowType.Blank };
|
||||
}
|
||||
|
||||
const contact = this.selectedContacts[rowIndex - 1];
|
||||
const contact = this.#selectedContacts[rowIndex - 1];
|
||||
return contact
|
||||
? {
|
||||
type: RowType.Contact,
|
||||
|
@ -299,8 +292,8 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
return false;
|
||||
}
|
||||
|
||||
private canCreateGroup(): boolean {
|
||||
return !this.isCreating && Boolean(this.groupName.trim());
|
||||
#canCreateGroup(): boolean {
|
||||
return !this.#isCreating && Boolean(this.#groupName.trim());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue