diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 0a9c6c3da4..bd3d9f669f 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -10091,17 +10091,6 @@ $contact-modal-padding: 18px; // Module: Delivery Issue Dialog -.module-delivery-issue-dialog { - // margin-left: auto; - // margin-right: auto; - - @include light-theme { - background-color: $color-white; - } - @include dark-theme { - background-color: $color-gray-95; - } -} .module-delivery-issue-dialog__image { text-align: center; } @@ -10110,31 +10099,6 @@ $contact-modal-padding: 18px; margin-top: 10px; margin-bottom: 3px; } -.module-delivery-issue-dialog__buttons { - text-align: right; - margin-top: 20px; - padding: 3px; -} -.module-delivery-issue-dialog__button { -} - -.module-delivery-issue-dialog__learn-more-button { - @include button-reset; - @include button-secondary; - @include font-body-1-bold; - - border-radius: 4px; - padding: 7px 14px; -} -.module-delivery-issue-dialog__close-button { - @include button-reset; - @include button-primary; - @include font-body-1-bold; - - border-radius: 4px; - padding: 7px 14px; - margin-left: 12px; -} /* Third-party module: react-contextmenu*/ diff --git a/stylesheets/components/Modal.scss b/stylesheets/components/Modal.scss index 0faa697893..db3dddb9e1 100644 --- a/stylesheets/components/Modal.scss +++ b/stylesheets/components/Modal.scss @@ -78,13 +78,14 @@ &__body { @include font-body-1; margin: 0; - overflow: auto; } &--has-header { .module-Modal__body { padding: 0 16px 16px 16px; border-top: 1px solid transparent; + // If there's a header, just the body scrolls + overflow: auto; &--scrolled { @include light-theme { @@ -100,6 +101,8 @@ &--no-header { padding: 16px; + // If there's no header, the whole thing scrolls + overflow: auto; } &__button-footer { diff --git a/ts/components/AvatarPopup.tsx b/ts/components/AvatarPopup.tsx index f1295703f3..192f6f61c0 100644 --- a/ts/components/AvatarPopup.tsx +++ b/ts/components/AvatarPopup.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import classNames from 'classnames'; import { Avatar, Props as AvatarProps } from './Avatar'; -import { useRestoreFocus } from '../util/hooks'; +import { useRestoreFocus } from '../util/hooks/useRestoreFocus'; import { LocalizerType } from '../types/Util'; diff --git a/ts/components/Modal.stories.tsx b/ts/components/Modal.stories.tsx index a8dc473e5d..0413800150 100644 --- a/ts/components/Modal.stories.tsx +++ b/ts/components/Modal.stories.tsx @@ -32,6 +32,18 @@ story.add('Bare bones, long', () => ( )); +story.add('Bare bones, long, with button', () => ( + +

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+ + + +
+)); + story.add('Title, X button, body, and button footer', () => ( {LOREM_IPSUM} @@ -69,6 +81,18 @@ story.add('Long body with title', () => ( )); +story.add('Long body with title and button', () => ( + +

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+

{LOREM_IPSUM}

+ + + +
+)); + story.add('Long body with long title and X button', () => ( -
+
-
- - -
-
+ + + + +
); } diff --git a/ts/components/conversation/ReactionPicker.tsx b/ts/components/conversation/ReactionPicker.tsx index 8042c61874..45f98091c8 100644 --- a/ts/components/conversation/ReactionPicker.tsx +++ b/ts/components/conversation/ReactionPicker.tsx @@ -6,7 +6,7 @@ import classNames from 'classnames'; import { Emoji } from '../emoji/Emoji'; import { convertShortName } from '../emoji/lib'; import { Props as EmojiPickerProps } from '../emoji/EmojiPicker'; -import { useRestoreFocus } from '../../util/hooks'; +import { useRestoreFocus } from '../../util/hooks/useRestoreFocus'; import { LocalizerType } from '../../types/Util'; export type RenderEmojiPickerProps = Pick & diff --git a/ts/components/conversation/ReactionViewer.tsx b/ts/components/conversation/ReactionViewer.tsx index 2f9d81df2e..81cb375b77 100644 --- a/ts/components/conversation/ReactionViewer.tsx +++ b/ts/components/conversation/ReactionViewer.tsx @@ -7,7 +7,7 @@ import classNames from 'classnames'; import { ContactName } from './ContactName'; import { Avatar, Props as AvatarProps } from '../Avatar'; import { Emoji } from '../emoji/Emoji'; -import { useRestoreFocus } from '../../util/hooks'; +import { useRestoreFocus } from '../../util/hooks/useRestoreFocus'; import { ConversationType } from '../../state/ducks/conversations'; import { emojiToData, EmojiData } from '../emoji/lib'; diff --git a/ts/components/stickers/StickerPicker.tsx b/ts/components/stickers/StickerPicker.tsx index e6b21a2308..004caa47cd 100644 --- a/ts/components/stickers/StickerPicker.tsx +++ b/ts/components/stickers/StickerPicker.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import classNames from 'classnames'; -import { useRestoreFocus } from '../../util/hooks'; +import { useRestoreFocus } from '../../util/hooks/useRestoreFocus'; import { StickerPackType, StickerType } from '../../state/ducks/stickers'; import { LocalizerType } from '../../types/Util'; diff --git a/ts/components/stickers/StickerPreviewModal.tsx b/ts/components/stickers/StickerPreviewModal.tsx index 1808f7ca49..8ff3224922 100644 --- a/ts/components/stickers/StickerPreviewModal.tsx +++ b/ts/components/stickers/StickerPreviewModal.tsx @@ -10,7 +10,7 @@ import { ConfirmationDialog } from '../ConfirmationDialog'; import { LocalizerType } from '../../types/Util'; import { StickerPackType } from '../../state/ducks/stickers'; import { Spinner } from '../Spinner'; -import { useRestoreFocus } from '../../util/hooks'; +import { useRestoreFocus } from '../../util/hooks/useRestoreFocus'; export type OwnProps = { readonly onClose: () => unknown; diff --git a/ts/models/messages.ts b/ts/models/messages.ts index 64a4f26b33..905aa6ce65 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -1060,6 +1060,11 @@ export class MessageModel extends window.Backbone.Model { if (isIncoming(this.attributes)) { return this.get('source'); } + if (!isOutgoing(this.attributes)) { + window.log.warn( + 'Message.getSource: Called for non-incoming/non-outoing message' + ); + } return this.OUR_NUMBER; } @@ -1070,6 +1075,11 @@ export class MessageModel extends window.Backbone.Model { if (isIncoming(this.attributes)) { return sourceDevice; } + if (!isOutgoing(this.attributes)) { + window.log.warn( + 'Message.getSourceDevice: Called for non-incoming/non-outoing message' + ); + } return sourceDevice || window.textsecure.storage.user.getDeviceId(); } @@ -1078,6 +1088,11 @@ export class MessageModel extends window.Backbone.Model { if (isIncoming(this.attributes)) { return this.get('sourceUuid'); } + if (!isOutgoing(this.attributes)) { + window.log.warn( + 'Message.getSourceUuid: Called for non-incoming/non-outoing message' + ); + } return this.OUR_UUID; } diff --git a/ts/state/selectors/message.ts b/ts/state/selectors/message.ts index a8ff2a0d15..2150ea389a 100644 --- a/ts/state/selectors/message.ts +++ b/ts/state/selectors/message.ts @@ -242,6 +242,11 @@ export function getSource( if (isIncoming(message)) { return message.source; } + if (!isOutgoing(message)) { + window.log.warn( + 'message.getSource: Called for non-incoming/non-outoing message' + ); + } return ourNumber; } @@ -255,6 +260,11 @@ export function getSourceDevice( if (isIncoming(message)) { return sourceDevice; } + if (!isOutgoing(message)) { + window.log.warn( + 'message.getSourceDevice: Called for non-incoming/non-outoing message' + ); + } return sourceDevice || ourDeviceId; } @@ -266,6 +276,11 @@ export function getSourceUuid( if (isIncoming(message)) { return message.sourceUuid; } + if (!isOutgoing(message)) { + window.log.warn( + 'message.getSourceUuid: Called for non-incoming/non-outoing message' + ); + } return ourUuid; } diff --git a/ts/util/hooks.ts b/ts/util/hooks/index.ts similarity index 75% rename from ts/util/hooks.ts rename to ts/util/hooks/index.ts index 8567a96dca..10931a475e 100644 --- a/ts/util/hooks.ts +++ b/ts/util/hooks/index.ts @@ -13,49 +13,6 @@ export function usePrevious(initialValue: T, currentValue: T): T { return result; } -type CallbackType = (toFocus: HTMLElement | null | undefined) => void; - -// Restore focus on teardown -export const useRestoreFocus = (): Array => { - const toFocusRef = React.useRef(null); - const lastFocusedRef = React.useRef(null); - - // We need to use a callback here because refs aren't necessarily populated on first - // render. For example, ModalHost makes a top-level parent div first, and then renders - // into it. And the children you pass it don't have access to that root div. - const setFocusRef = React.useCallback( - (toFocus: HTMLElement | null | undefined) => { - if (!toFocus) { - return; - } - - // We only want to do this once. - if (toFocusRef.current) { - return; - } - toFocusRef.current = toFocus; - - // Remember last-focused element, focus this new target element. - lastFocusedRef.current = document.activeElement as HTMLElement; - toFocus.focus(); - }, - [] - ); - - React.useEffect(() => { - return () => { - // On unmount, returned focus to element focused before we set the focus - setTimeout(() => { - if (lastFocusedRef.current && lastFocusedRef.current.focus) { - lastFocusedRef.current.focus(); - } - }); - }; - }, []); - - return [setFocusRef]; -}; - export const useBoundActions = ( actions: T ): T => { diff --git a/ts/util/hooks/useRestoreFocus.ts b/ts/util/hooks/useRestoreFocus.ts new file mode 100644 index 0000000000..02f82db0c7 --- /dev/null +++ b/ts/util/hooks/useRestoreFocus.ts @@ -0,0 +1,47 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import * as React from 'react'; + +type CallbackType = (toFocus: HTMLElement | null | undefined) => void; + +// Restore focus on teardown +export const useRestoreFocus = (): Array => { + const toFocusRef = React.useRef(null); + const lastFocusedRef = React.useRef(null); + + // We need to use a callback here because refs aren't necessarily populated on first + // render. For example, ModalHost makes a top-level parent div first, and then renders + // into it. And the children you pass it don't have access to that root div. + const setFocusRef = React.useCallback( + (toFocus: HTMLElement | null | undefined) => { + if (!toFocus) { + return; + } + + // We only want to do this once. + if (toFocusRef.current) { + return; + } + toFocusRef.current = toFocus; + + // Remember last-focused element, focus this new target element. + lastFocusedRef.current = document.activeElement as HTMLElement; + toFocus.focus(); + }, + [] + ); + + React.useEffect(() => { + return () => { + // On unmount, returned focus to element focused before we set the focus + setTimeout(() => { + if (lastFocusedRef.current && lastFocusedRef.current.focus) { + lastFocusedRef.current.focus(); + } + }); + }; + }, []); + + return [setFocusRef]; +}; diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 15c9da006c..f1272a2009 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -14398,7 +14398,7 @@ }, { "rule": "React-useRef", - "path": "ts/util/hooks.js", + "path": "ts/util/hooks/index.js", "line": " const unobserveRef = React.useRef(null);", "reasonCategory": "usageTrusted", "updated": "2021-01-08T15:46:32.143Z", @@ -14406,7 +14406,7 @@ }, { "rule": "React-useRef", - "path": "ts/util/hooks.js", + "path": "ts/util/hooks/index.js", "line": " const previousValueRef = React.useRef(initialValue);", "reasonCategory": "usageTrusted", "updated": "2021-03-18T21:41:28.361Z", @@ -14414,7 +14414,21 @@ }, { "rule": "React-useRef", - "path": "ts/util/hooks.js", + "path": "ts/util/hooks/index.ts", + "line": " const previousValueRef = React.useRef(initialValue);", + "reasonCategory": "usageTrusted", + "updated": "2021-07-30T16:57:33.618Z" + }, + { + "rule": "React-useRef", + "path": "ts/util/hooks/index.ts", + "line": " const unobserveRef = React.useRef<(() => unknown) | null>(null);", + "reasonCategory": "usageTrusted", + "updated": "2021-07-30T16:57:33.618Z" + }, + { + "rule": "React-useRef", + "path": "ts/util/hooks/useRestoreFocus.js", "line": " const toFocusRef = React.useRef(null);", "reasonCategory": "usageTrusted", "updated": "2021-07-30T01:08:01.309Z", @@ -14422,7 +14436,7 @@ }, { "rule": "React-useRef", - "path": "ts/util/hooks.js", + "path": "ts/util/hooks/useRestoreFocus.js", "line": " const lastFocusedRef = React.useRef(null);", "reasonCategory": "usageTrusted", "updated": "2021-07-30T01:08:01.309Z", @@ -14430,30 +14444,16 @@ }, { "rule": "React-useRef", - "path": "ts/util/hooks.ts", - "line": " const previousValueRef = React.useRef(initialValue);", - "reasonCategory": "usageTrusted", - "updated": "2021-07-30T16:57:33.618Z" - }, - { - "rule": "React-useRef", - "path": "ts/util/hooks.ts", + "path": "ts/util/hooks/useRestoreFocus.ts", "line": " const toFocusRef = React.useRef(null);", "reasonCategory": "usageTrusted", "updated": "2021-07-30T16:57:33.618Z" }, { "rule": "React-useRef", - "path": "ts/util/hooks.ts", + "path": "ts/util/hooks/useRestoreFocus.ts", "line": " const lastFocusedRef = React.useRef(null);", "reasonCategory": "usageTrusted", "updated": "2021-07-30T16:57:33.618Z" - }, - { - "rule": "React-useRef", - "path": "ts/util/hooks.ts", - "line": " const unobserveRef = React.useRef<(() => unknown) | null>(null);", - "reasonCategory": "usageTrusted", - "updated": "2021-07-30T16:57:33.618Z" } ] \ No newline at end of file