Keyboard/mouse mode and keyboard support bugfixes
This commit is contained in:
parent
ed55006f20
commit
2a0a73cfc1
25 changed files with 686 additions and 274 deletions
|
@ -109,6 +109,61 @@
|
||||||
activeHandlers = activeHandlers.filter(item => item !== handler);
|
activeHandlers = activeHandlers.filter(item => item !== handler);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Keyboard/mouse mode
|
||||||
|
let interactionMode = 'mouse';
|
||||||
|
$(document.body).addClass('mouse-mode');
|
||||||
|
|
||||||
|
window.enterKeyboardMode = () => {
|
||||||
|
if (interactionMode === 'keyboard') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
interactionMode = 'keyboard';
|
||||||
|
$(document.body)
|
||||||
|
.addClass('keyboard-mode')
|
||||||
|
.removeClass('mouse-mode');
|
||||||
|
const { userChanged } = window.reduxActions.user;
|
||||||
|
const { clearSelectedMessage } = window.reduxActions.conversations;
|
||||||
|
if (clearSelectedMessage) {
|
||||||
|
clearSelectedMessage();
|
||||||
|
}
|
||||||
|
if (userChanged) {
|
||||||
|
userChanged({ interactionMode });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.enterMouseMode = () => {
|
||||||
|
if (interactionMode === 'mouse') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
interactionMode = 'mouse';
|
||||||
|
$(document.body)
|
||||||
|
.addClass('mouse-mode')
|
||||||
|
.removeClass('keyboard-mode');
|
||||||
|
const { userChanged } = window.reduxActions.user;
|
||||||
|
const { clearSelectedMessage } = window.reduxActions.conversations;
|
||||||
|
if (clearSelectedMessage) {
|
||||||
|
clearSelectedMessage();
|
||||||
|
}
|
||||||
|
if (userChanged) {
|
||||||
|
userChanged({ interactionMode });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
'keydown',
|
||||||
|
event => {
|
||||||
|
if (event.key === 'Tab') {
|
||||||
|
window.enterKeyboardMode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
document.addEventListener('wheel', window.enterMouseMode, true);
|
||||||
|
document.addEventListener('mousedown', window.enterMouseMode, true);
|
||||||
|
|
||||||
|
window.getInteractionMode = () => interactionMode;
|
||||||
|
|
||||||
// Load these images now to ensure that they don't flicker on first use
|
// Load these images now to ensure that they don't flicker on first use
|
||||||
window.preloadedImages = [];
|
window.preloadedImages = [];
|
||||||
function preload(list) {
|
function preload(list) {
|
||||||
|
@ -299,10 +354,11 @@
|
||||||
// Stop processing incoming messages
|
// Stop processing incoming messages
|
||||||
if (messageReceiver) {
|
if (messageReceiver) {
|
||||||
await messageReceiver.stopProcessing();
|
await messageReceiver.stopProcessing();
|
||||||
|
|
||||||
await window.waitForAllBatchers();
|
await window.waitForAllBatchers();
|
||||||
messageReceiver.unregisterBatchers();
|
}
|
||||||
|
|
||||||
|
if (messageReceiver) {
|
||||||
|
messageReceiver.unregisterBatchers();
|
||||||
messageReceiver = null;
|
messageReceiver = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,6 +578,7 @@
|
||||||
ourNumber: textsecure.storage.user.getNumber(),
|
ourNumber: textsecure.storage.user.getNumber(),
|
||||||
platform: window.platform,
|
platform: window.platform,
|
||||||
i18n: window.i18n,
|
i18n: window.i18n,
|
||||||
|
interactionMode: window.getInteractionMode(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -668,6 +725,7 @@
|
||||||
|
|
||||||
// Navigate by section
|
// Navigate by section
|
||||||
if (ctrlOrCommand && !shiftKey && (key === 't' || key === 'T')) {
|
if (ctrlOrCommand && !shiftKey && (key === 't' || key === 'T')) {
|
||||||
|
window.enterKeyboardMode();
|
||||||
const focusedElement = document.activeElement;
|
const focusedElement = document.activeElement;
|
||||||
|
|
||||||
const targets = [
|
const targets = [
|
||||||
|
|
|
@ -80,7 +80,6 @@
|
||||||
initialize(options = {}) {
|
initialize(options = {}) {
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.render();
|
this.render();
|
||||||
this.$el.attr('tabindex', '1');
|
|
||||||
|
|
||||||
this.conversation_stack = new Whisper.ConversationStack({
|
this.conversation_stack = new Whisper.ConversationStack({
|
||||||
el: this.$('.conversation-stack'),
|
el: this.$('.conversation-stack'),
|
||||||
|
|
|
@ -408,6 +408,13 @@
|
||||||
padding: 5px 8px;
|
padding: 5px 8px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
outline: -webkit-focus-ring-color auto 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
background-color: $color-gray-02;
|
background-color: $color-gray-02;
|
||||||
border: 1px solid $color-gray-15;
|
border: 1px solid $color-gray-15;
|
||||||
|
|
|
@ -162,6 +162,13 @@ a {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: '';
|
content: '';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -175,11 +182,6 @@ a {
|
||||||
@include color-svg('../images/icons/v2/plus-24.svg', $color-gray-25);
|
@include color-svg('../images/icons/v2/plus-24.svg', $color-gray-25);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='file'] {
|
input[type='file'] {
|
||||||
|
|
|
@ -128,6 +128,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keyboard
|
||||||
|
|
||||||
|
@mixin keyboard-mode() {
|
||||||
|
.keyboard-mode & {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin mouse-mode() {
|
||||||
|
.mouse-mode & {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dark-keyboard-mode() {
|
||||||
|
.dark-theme.keyboard-mode & {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@mixin ios-keyboard-mode() {
|
||||||
|
.ios-theme.keyboard-mode & {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@mixin dark-ios-keyboard-mode() {
|
||||||
|
.dark-theme.ios-theme.keyboard-mode & {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
|
|
||||||
@mixin popper-shadow() {
|
@mixin popper-shadow() {
|
||||||
|
|
|
@ -252,17 +252,53 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the component we put the outline around when the whole message is focused
|
// This is the component we put the outline around when the whole message is selected
|
||||||
|
.module-message--selected .module-message__container {
|
||||||
|
@include mouse-mode {
|
||||||
|
animation: message--mouse-selected 1s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
.module-message:focus .module-message__container {
|
.module-message:focus .module-message__container {
|
||||||
box-shadow: 0 0 0 5px $color-signal-blue;
|
@include keyboard-mode {
|
||||||
|
box-shadow: 0 0 0 3px $color-signal-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes message--mouse-selected {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 5px transparent;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
box-shadow: 0 0 0 5px $color-signal-blue;
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 5px $color-signal-blue;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 5px transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We disable this highlight for messages with stickers, instead highlighting the sticker
|
// We disable this highlight for messages with stickers, instead highlighting the sticker
|
||||||
|
.module-message--selected .module-message__container--with-sticker {
|
||||||
|
@include mouse-mode {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
.module-message:focus .module-message__container--with-sticker {
|
.module-message:focus .module-message__container--with-sticker {
|
||||||
box-shadow: none;
|
@include keyboard-mode {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-message__container--with-sticker {
|
.module-message__container--with-sticker {
|
||||||
|
@include light-theme {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include dark-theme {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,9 +639,10 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:hover,
|
@include keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -762,9 +799,10 @@
|
||||||
border-top-right-radius: 16px;
|
border-top-right-radius: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&:focus,
|
@include keyboard-mode {
|
||||||
&:hover {
|
&:focus {
|
||||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -934,33 +972,35 @@
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
color: $color-gray-90;
|
color: $color-gray-90;
|
||||||
&:focus,
|
}
|
||||||
&:hover {
|
@include keyboard-mode {
|
||||||
outline: 2px solid $color-gray-90;
|
&:focus {
|
||||||
|
outline: 1px solid $color-gray-90;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
color: $color-gray-05;
|
color: $color-gray-05;
|
||||||
&:focus,
|
}
|
||||||
&:hover {
|
@include dark-keyboard-mode {
|
||||||
outline: 2px solid $color-gray-05;
|
&:focus {
|
||||||
|
outline: 1px solid $color-gray-05;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include ios-theme {
|
@include ios-theme {
|
||||||
color: $color-white-alpha-90;
|
color: $color-white-alpha-90;
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
outline: 2px solid $color-white-alpha-90;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@include ios-dark-theme {
|
@include ios-dark-theme {
|
||||||
color: $color-white-alpha-90;
|
color: $color-white-alpha-90;
|
||||||
&:focus,
|
}
|
||||||
&:hover {
|
@include ios-keyboard-mode {
|
||||||
outline: 2px solid $color-white-alpha-90;
|
&:focus {
|
||||||
|
outline: 1px solid $color-white-alpha-90;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -982,33 +1022,41 @@
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
&:focus,
|
}
|
||||||
&:hover {
|
@include keyboard-mode {
|
||||||
outline: 2px solid $color-white;
|
&:focus {
|
||||||
}
|
outline: 1px solid $color-white;
|
||||||
}
|
|
||||||
@include ios-theme {
|
|
||||||
color: $color-gray-90;
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
outline: 2px solid $color-gray-90;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
color: $color-white-alpha-90;
|
color: $color-white-alpha-90;
|
||||||
&:focus,
|
}
|
||||||
&:hover {
|
@include dark-keyboard-mode {
|
||||||
outline: 2px solid $color-white-alpha-90;
|
&:focus {
|
||||||
|
outline: 1px solid $color-white-alpha-90;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include ios-theme {
|
||||||
|
color: $color-gray-90;
|
||||||
|
}
|
||||||
|
@include ios-keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
outline: 1px solid $color-gray-90;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@include ios-dark-theme {
|
@include ios-dark-theme {
|
||||||
color: $color-gray-05;
|
color: $color-gray-05;
|
||||||
&:focus,
|
}
|
||||||
&:hover {
|
@include dark-ios-keyboard-mode {
|
||||||
outline: 2px solid $color-gray-05;
|
&:focus {
|
||||||
|
outline: 1px solid $color-gray-05;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1235,9 +1283,10 @@
|
||||||
border: 1px solid $color-gray-45;
|
border: 1px solid $color-gray-45;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus,
|
@include keyboard-mode {
|
||||||
&:hover {
|
&:focus {
|
||||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1375,9 +1424,10 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
border-left-width: 4px;
|
border-left-width: 4px;
|
||||||
border-left-style: solid;
|
border-left-style: solid;
|
||||||
|
|
||||||
&:focus,
|
@include keyboard-mode {
|
||||||
&:hover {
|
&:focus {
|
||||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1404,6 +1454,13 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
background-color: $color-conversation-grey-shade;
|
background-color: $color-conversation-grey-shade;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To preserve contrast
|
||||||
|
@include ios-keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 2px $color-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Note: both of these override all of the specific color classes below
|
// Note: both of these override all of the specific color classes below
|
||||||
@include ios-theme {
|
@include ios-theme {
|
||||||
border-left-color: $color-white;
|
border-left-color: $color-white;
|
||||||
|
@ -1592,6 +1649,12 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
background-color: $color-black-alpha-40;
|
background-color: $color-black-alpha-40;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus-within {
|
||||||
|
background-color: $color-signal-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-quote__close-button {
|
.module-quote__close-button {
|
||||||
|
@ -1799,9 +1862,18 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:focus,
|
@include keyboard-mode {
|
||||||
&:hover {
|
&:focus {
|
||||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-embedded-contact--outgoing {
|
||||||
|
@include ios-keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 2px $color-white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1977,13 +2049,14 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
||||||
&:focus,
|
@include keyboard-mode {
|
||||||
&:hover {
|
&:focus {
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
background-color: $color-gray-02;
|
background-color: $color-gray-02;
|
||||||
}
|
}
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
background-color: $color-gray-80;
|
background-color: $color-gray-80;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2081,16 +2154,17 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
background-color: $color-gray-02;
|
background-color: $color-gray-02;
|
||||||
|
}
|
||||||
&:hover,
|
@include keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: $color-gray-15;
|
background-color: $color-gray-15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
background-color: $color-gray-75;
|
background-color: $color-gray-75;
|
||||||
|
}
|
||||||
&:hover,
|
@include dark-keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: $color-gray-60;
|
background-color: $color-gray-60;
|
||||||
}
|
}
|
||||||
|
@ -2526,6 +2600,12 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
.module-message-detail__delete-button {
|
.module-message-detail__delete-button {
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
outline: -webkit-focus-ring-color auto 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
@ -2708,6 +2788,19 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
background-color: $color-gray-90;
|
background-color: $color-gray-90;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
@include light-theme {
|
||||||
|
background-color: $color-gray-15;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
background-color: $color-gray-75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-media-gallery__tab--active {
|
.module-media-gallery__tab--active {
|
||||||
|
@ -2763,12 +2856,19 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
|
|
||||||
.module-document-list-item__content {
|
.module-document-list-item__content {
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-document-list-item__icon {
|
.module-document-list-item__icon {
|
||||||
|
@ -2810,6 +2910,12 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-media-grid-item__image {
|
.module-media-grid-item__image {
|
||||||
|
@ -3156,9 +3262,10 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
&:focus,
|
@include keyboard-mode {
|
||||||
&:hover {
|
&:focus {
|
||||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3440,6 +3547,7 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
// Overriding some default button styling
|
// Overriding some default button styling
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
background-color: $color-gray-15;
|
background-color: $color-gray-15;
|
||||||
|
@ -3574,21 +3682,38 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only if it's a sticker do we put the outline inside it
|
// Only if it's a sticker do we put the outline inside it
|
||||||
|
.module-message--selected
|
||||||
|
.module-message__container--with-sticker
|
||||||
|
.module-image__border-overlay {
|
||||||
|
@include mouse-mode {
|
||||||
|
top: 1px;
|
||||||
|
bottom: 1px;
|
||||||
|
left: 1px;
|
||||||
|
right: 1px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
animation: message--mouse-selected 1s cubic-bezier(0.19, 1, 0.22, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.module-message:focus
|
.module-message:focus
|
||||||
.module-message__container--with-sticker
|
.module-message__container--with-sticker
|
||||||
.module-image__border-overlay {
|
.module-image__border-overlay {
|
||||||
top: 1px;
|
@include keyboard-mode {
|
||||||
bottom: 1px;
|
top: 1px;
|
||||||
left: 1px;
|
bottom: 1px;
|
||||||
right: 1px;
|
left: 1px;
|
||||||
border-radius: 10px;
|
right: 1px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
box-shadow: 0 0 0 5px $color-signal-blue;
|
box-shadow: 0 0 0 3px $color-signal-blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button.module-image__border-overlay:focus,
|
button.module-image__border-overlay:focus {
|
||||||
button.module-image__border-overlay:hover {
|
@include keyboard-mode {
|
||||||
box-shadow: inset 0px 0px 0px 2px $color-signal-blue;
|
box-shadow: inset 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-image__border-overlay--dark {
|
.module-image__border-overlay--dark {
|
||||||
|
@ -3697,9 +3822,10 @@ button.module-image__border-overlay:hover {
|
||||||
|
|
||||||
background-image: url('../images/x-shadow-16.svg');
|
background-image: url('../images/x-shadow-16.svg');
|
||||||
|
|
||||||
&:focus,
|
@include keyboard-mode {
|
||||||
&:hover {
|
&:focus {
|
||||||
outline: 2px solid $color-signal-blue;
|
outline: 2px solid $color-signal-blue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3854,8 +3980,10 @@ button.module-image__border-overlay:hover {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-black);
|
@include color-svg('../images/icons/v2/x-24.svg', $color-black);
|
||||||
|
|
||||||
&:focus {
|
@include keyboard-mode {
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
&:focus {
|
||||||
|
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4103,18 +4231,21 @@ button.module-image__border-overlay:hover {
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $color-gray-05;
|
background: $color-gray-05;
|
||||||
}
|
}
|
||||||
&:hover,
|
}
|
||||||
|
@include keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: inset 0 0 0 2px $color-signal-blue;
|
box-shadow: inset 0 0 0 2px $color-signal-blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
border: 1px solid $color-gray-60;
|
border: 1px solid $color-gray-60;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $color-gray-75;
|
background: $color-gray-75;
|
||||||
}
|
}
|
||||||
&:hover,
|
}
|
||||||
|
@include dark-keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: inset 0 0 0 2px $color-signal-blue;
|
box-shadow: inset 0 0 0 2px $color-signal-blue;
|
||||||
}
|
}
|
||||||
|
@ -4212,12 +4343,17 @@ button.module-image__border-overlay:hover {
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-60);
|
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-60);
|
||||||
|
}
|
||||||
|
@include keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-25);
|
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-25);
|
||||||
|
}
|
||||||
|
@include dark-keyboard-mode {
|
||||||
&:focus {
|
&:focus {
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||||
}
|
}
|
||||||
|
@ -4360,6 +4496,7 @@ button.module-image__border-overlay:hover {
|
||||||
// Module: Search Results
|
// Module: Search Results
|
||||||
|
|
||||||
.module-search-results {
|
.module-search-results {
|
||||||
|
outline: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
@ -4381,6 +4518,7 @@ button.module-image__border-overlay:hover {
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-search-results__contacts-header {
|
.module-search-results__contacts-header {
|
||||||
|
@ -4575,20 +4713,23 @@ button.module-image__border-overlay:hover {
|
||||||
'../images/icons/v2/chevron-left-24.svg',
|
'../images/icons/v2/chevron-left-24.svg',
|
||||||
$color-gray-60
|
$color-gray-60
|
||||||
);
|
);
|
||||||
&:focus,
|
}
|
||||||
&:hover {
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
@include color-svg(
|
@include color-svg(
|
||||||
'../images/icons/v2/chevron-left-24.svg',
|
'../images/icons/v2/chevron-left-24.svg',
|
||||||
$color-signal-blue
|
$color-signal-blue
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
@include color-svg(
|
@include color-svg(
|
||||||
'../images/icons/v2/chevron-left-24.svg',
|
'../images/icons/v2/chevron-left-24.svg',
|
||||||
$color-gray-25
|
$color-gray-25
|
||||||
);
|
);
|
||||||
&:focus,
|
}
|
||||||
|
@include dark-keyboard-mode {
|
||||||
&:hover {
|
&:hover {
|
||||||
@include color-svg(
|
@include color-svg(
|
||||||
'../images/icons/v2/chevron-left-24.svg',
|
'../images/icons/v2/chevron-left-24.svg',
|
||||||
|
@ -4839,13 +4980,14 @@ button.module-image__border-overlay:hover {
|
||||||
background: none;
|
background: none;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
@include keyboard-mode {
|
||||||
@include light-theme {
|
|
||||||
background: $color-gray-05;
|
background: $color-gray-05;
|
||||||
}
|
}
|
||||||
@include dark-theme {
|
@include dark-keyboard-mode {
|
||||||
background: $color-gray-60;
|
background: $color-gray-60;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5040,6 +5182,10 @@ button.module-image__border-overlay:hover {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
@include mouse-mode {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
&__image,
|
&__image,
|
||||||
&__placeholder {
|
&__placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -5182,6 +5328,12 @@ button.module-image__border-overlay:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__cover {
|
&__cover {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
|
@ -5280,6 +5432,10 @@ button.module-image__border-overlay:hover {
|
||||||
background: $color-gray-75;
|
background: $color-gray-75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include mouse-mode {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
&--blue {
|
&--blue {
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
background: $color-signal-blue;
|
background: $color-signal-blue;
|
||||||
|
@ -5503,10 +5659,13 @@ button.module-image__border-overlay:hover {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
display: block;
|
display: block;
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -5716,8 +5875,8 @@ button.module-image__border-overlay:hover {
|
||||||
|
|
||||||
&__button {
|
&__button {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
border-radius: 14px;
|
border-radius: 17px;
|
||||||
height: 28px;
|
height: 34px;
|
||||||
padding: 5px 12px;
|
padding: 5px 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -5725,6 +5884,10 @@ button.module-image__border-overlay:hover {
|
||||||
|
|
||||||
@include font-body-1-bold;
|
@include font-body-1-bold;
|
||||||
|
|
||||||
|
@include mouse-mode {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
@include light-theme() {
|
@include light-theme() {
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
color: $color-gray-60;
|
color: $color-gray-60;
|
||||||
|
@ -5884,6 +6047,10 @@ button.module-image__border-overlay:hover {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: none;
|
background: none;
|
||||||
|
|
||||||
|
@include mouse-mode {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
&--footer {
|
&--footer {
|
||||||
&:not(:first-of-type) {
|
&:not(:first-of-type) {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
@ -6047,10 +6214,13 @@ button.module-image__border-overlay:hover {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
display: block;
|
display: block;
|
||||||
content: '';
|
content: '';
|
||||||
|
@ -6795,13 +6965,14 @@ button.module-image__border-overlay:hover {
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-05);
|
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-05);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus,
|
@include keyboard-mode {
|
||||||
&:hover {
|
&:focus {
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||||
}
|
}
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6841,12 +7012,14 @@ button.module-image__border-overlay:hover {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
&:focus {
|
@include keyboard-mode {
|
||||||
@include light-theme {
|
&:focus {
|
||||||
background-color: $color-gray-05;
|
@include light-theme {
|
||||||
}
|
background-color: $color-gray-05;
|
||||||
@include dark-theme {
|
}
|
||||||
background-color: $color-gray-90;
|
@include dark-theme {
|
||||||
|
background-color: $color-gray-90;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
content: '';
|
content: '';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -43,7 +45,7 @@
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 32px;
|
border-radius: 32px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
opacity: 0.5;
|
opacity: 0.3;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
|
@ -52,6 +54,8 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
|
|
|
@ -213,6 +213,20 @@ export class SearchResults extends React.Component<PropsType, StateType> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public setFocusToFirst = () => {
|
public setFocusToFirst = () => {
|
||||||
|
const { current: container } = this.containerRef;
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
// tslint:disable-next-line no-unnecessary-type-assertion
|
||||||
|
const noResultsItem = container.querySelector(
|
||||||
|
'.module-search-results__no-results'
|
||||||
|
) as any;
|
||||||
|
if (noResultsItem && noResultsItem.focus) {
|
||||||
|
noResultsItem.focus();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const scrollContainer = this.getScrollContainer();
|
const scrollContainer = this.getScrollContainer();
|
||||||
if (!scrollContainer) {
|
if (!scrollContainer) {
|
||||||
return;
|
return;
|
||||||
|
@ -513,9 +527,19 @@ export class SearchResults extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
if (noResults) {
|
if (noResults) {
|
||||||
return (
|
return (
|
||||||
<div className="module-search-results">
|
<div
|
||||||
|
className="module-search-results"
|
||||||
|
tabIndex={-1}
|
||||||
|
ref={this.containerRef}
|
||||||
|
onFocus={this.handleFocus}
|
||||||
|
>
|
||||||
{!searchConversationName || searchTerm ? (
|
{!searchConversationName || searchTerm ? (
|
||||||
<div className="module-search-results__no-results" key={searchTerm}>
|
<div
|
||||||
|
// We need this for Ctrl-T shortcut cycling through parts of app
|
||||||
|
tabIndex={-1}
|
||||||
|
className="module-search-results__no-results"
|
||||||
|
key={searchTerm}
|
||||||
|
>
|
||||||
{searchConversationName ? (
|
{searchConversationName ? (
|
||||||
<Intl
|
<Intl
|
||||||
id="noSearchResultsInConversation"
|
id="noSearchResultsInConversation"
|
||||||
|
|
|
@ -22,7 +22,7 @@ const contact = {
|
||||||
onSendMessage: () => console.log('onSendMessage'),
|
onSendMessage: () => console.log('onSendMessage'),
|
||||||
signalAccount: '+12025550000',
|
signalAccount: '+12025550000',
|
||||||
};
|
};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -92,7 +92,7 @@ const contact = {
|
||||||
onSendMessage: () => console.log('onSendMessage'),
|
onSendMessage: () => console.log('onSendMessage'),
|
||||||
signalAccount: '+12025550000',
|
signalAccount: '+12025550000',
|
||||||
};
|
};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -137,7 +137,7 @@ const contact = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -182,7 +182,7 @@ const contact = {
|
||||||
},
|
},
|
||||||
signalAccount: '+12025550000',
|
signalAccount: '+12025550000',
|
||||||
};
|
};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -244,7 +244,7 @@ const contact = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -309,7 +309,7 @@ const contact = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -377,7 +377,7 @@ const contact = {
|
||||||
},
|
},
|
||||||
signalAccount: '+12025551000',
|
signalAccount: '+12025551000',
|
||||||
};
|
};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -439,7 +439,7 @@ const contact = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -491,7 +491,7 @@ const contact = {
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
const contact = {};
|
const contact = {};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -575,7 +575,7 @@ const contactWithoutAccount = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
text="I want to introduce you to Someone..."
|
text="I want to introduce you to Someone..."
|
||||||
|
|
|
@ -38,6 +38,7 @@ export class EmbeddedContact extends React.Component<Props> {
|
||||||
<button
|
<button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-embedded-contact',
|
'module-embedded-contact',
|
||||||
|
`module-embedded-contact--${direction}`,
|
||||||
withContentAbove
|
withContentAbove
|
||||||
? 'module-embedded-contact--with-content-above'
|
? 'module-embedded-contact--with-content-above'
|
||||||
: null,
|
: null,
|
||||||
|
|
|
@ -4,7 +4,7 @@ export type PropsType = {
|
||||||
id: string;
|
id: string;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
selectMessage: (messageId: string, conversationId: string) => unknown;
|
selectMessage?: (messageId: string, conversationId: string) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class InlineNotificationWrapper extends React.Component<PropsType> {
|
export class InlineNotificationWrapper extends React.Component<PropsType> {
|
||||||
|
@ -18,10 +18,19 @@ export class InlineNotificationWrapper extends React.Component<PropsType> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public handleFocus = () => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (window.getInteractionMode() === 'keyboard') {
|
||||||
|
this.setSelected();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public setSelected = () => {
|
public setSelected = () => {
|
||||||
const { id, conversationId, selectMessage } = this.props;
|
const { id, conversationId, selectMessage } = this.props;
|
||||||
|
|
||||||
selectMessage(id, conversationId);
|
if (selectMessage) {
|
||||||
|
selectMessage(id, conversationId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
|
@ -45,7 +54,7 @@ export class InlineNotificationWrapper extends React.Component<PropsType> {
|
||||||
className="module-inline-notification-wrapper"
|
className="module-inline-notification-wrapper"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
ref={this.focusRef}
|
ref={this.focusRef}
|
||||||
onFocus={this.setSelected}
|
onFocus={this.handleFocus}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
Note that timestamp and status can be hidden with the `collapseMetadata` boolean property.
|
Note that timestamp and status can be hidden with the `collapseMetadata` boolean property.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -148,7 +148,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="outgoing"
|
direction="outgoing"
|
||||||
|
@ -323,7 +323,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
||||||
### All colors
|
### All colors
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -450,7 +450,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
||||||
### Long data
|
### Long data
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="purple"
|
authorColor="purple"
|
||||||
|
@ -515,7 +515,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
||||||
### Pending long message download
|
### Pending long message download
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="purple"
|
authorColor="purple"
|
||||||
|
@ -553,7 +553,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
||||||
#### Image with caption
|
#### Image with caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="blue"
|
authorColor="blue"
|
||||||
|
@ -645,7 +645,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
||||||
First, showing the metadata overlay on dark and light images, then a message with `collapseMetadata` set.
|
First, showing the metadata overlay on dark and light images, then a message with `collapseMetadata` set.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -765,7 +765,7 @@ First, showing the metadata overlay on dark and light images, then a message wit
|
||||||
Stickers have no background, but they have all the standard message bubble features.
|
Stickers have no background, but they have all the standard message bubble features.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -912,7 +912,7 @@ Stickers have no background, but they have all the standard message bubble featu
|
||||||
First set is in a 1:1 conversation, second set is in a group.
|
First set is in a 1:1 conversation, second set is in a group.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -1002,7 +1002,7 @@ First set is in a 1:1 conversation, second set is in a group.
|
||||||
A sticker with no attachments (what our selectors produce for a pending sticker) is not displayed at all.
|
A sticker with no attachments (what our selectors produce for a pending sticker) is not displayed at all.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -1062,7 +1062,7 @@ A sticker with no attachments (what our selectors produce for a pending sticker)
|
||||||
#### Multiple images
|
#### Multiple images
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -1244,7 +1244,7 @@ A sticker with no attachments (what our selectors produce for a pending sticker)
|
||||||
#### Multiple images with caption
|
#### Multiple images with caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -1433,7 +1433,7 @@ A sticker with no attachments (what our selectors produce for a pending sticker)
|
||||||
Note that the delivered indicator is always Signal Blue, not the conversation color.
|
Note that the delivered indicator is always Signal Blue, not the conversation color.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="outgoing"
|
direction="outgoing"
|
||||||
|
@ -1512,7 +1512,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Pending images
|
#### Pending images
|
||||||
|
|
||||||
```
|
```
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -1617,7 +1617,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Image with portrait aspect ratio
|
#### Image with portrait aspect ratio
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="purple"
|
authorColor="purple"
|
||||||
|
@ -1695,7 +1695,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Image with portrait aspect ratio and caption
|
#### Image with portrait aspect ratio and caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -1819,7 +1819,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Image with landscape aspect ratio
|
#### Image with landscape aspect ratio
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -1897,7 +1897,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Image with landscape aspect ratio and caption
|
#### Image with landscape aspect ratio and caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -1979,7 +1979,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Video with caption
|
#### Video with caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2073,7 +2073,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Video
|
#### Video
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2170,7 +2170,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Missing images and videos
|
#### Missing images and videos
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2349,7 +2349,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Broken source URL images and videos
|
#### Broken source URL images and videos
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2441,7 +2441,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Image/video which is too big
|
#### Image/video which is too big
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2532,7 +2532,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Image/video missing height/width
|
#### Image/video missing height/width
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2619,7 +2619,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Audio with caption
|
#### Audio with caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2693,7 +2693,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
||||||
#### Audio
|
#### Audio
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2767,7 +2767,7 @@ Voice notes are not shown any differently from audio attachments.
|
||||||
#### Other file type with caption
|
#### Other file type with caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2906,7 +2906,7 @@ Voice notes are not shown any differently from audio attachments.
|
||||||
#### Other file type
|
#### Other file type
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -2984,7 +2984,7 @@ Voice notes are not shown any differently from audio attachments.
|
||||||
#### Other file type pending
|
#### Other file type pending
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -3062,7 +3062,7 @@ Voice notes are not shown any differently from audio attachments.
|
||||||
#### Dangerous file type
|
#### Dangerous file type
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -3108,7 +3108,7 @@ Voice notes are not shown any differently from audio attachments.
|
||||||
#### Link previews, full-size image
|
#### Link previews, full-size image
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -3219,7 +3219,7 @@ Voice notes are not shown any differently from audio attachments.
|
||||||
Sticker link previews are forced to use the small link preview form, no matter the image size.
|
Sticker link previews are forced to use the small link preview form, no matter the image size.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -3273,7 +3273,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
|
||||||
#### Link previews, small image
|
#### Link previews, small image
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -3384,7 +3384,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
|
||||||
#### Link previews with pending image
|
#### Link previews with pending image
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -3481,7 +3481,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
|
||||||
#### Link previews, no image
|
#### Link previews, no image
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
authorColor="green"
|
authorColor="green"
|
||||||
|
@ -3568,7 +3568,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
|
||||||
### Tap to view
|
### Tap to view
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -4004,7 +4004,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
|
||||||
Note that the author avatar goes away if `collapseMetadata` is set.
|
Note that the author avatar goes away if `collapseMetadata` is set.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
|
|
@ -40,6 +40,7 @@ interface Trigger {
|
||||||
// Same as MIN_WIDTH in ImageGrid.tsx
|
// Same as MIN_WIDTH in ImageGrid.tsx
|
||||||
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
||||||
const STICKER_SIZE = 128;
|
const STICKER_SIZE = 128;
|
||||||
|
const SELECTED_TIMEOUT = 1000;
|
||||||
|
|
||||||
interface LinkPreviewType {
|
interface LinkPreviewType {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -56,6 +57,8 @@ export type PropsData = {
|
||||||
textPending?: boolean;
|
textPending?: boolean;
|
||||||
isSticker: boolean;
|
isSticker: boolean;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
|
isSelectedCounter: number;
|
||||||
|
interactionMode: 'mouse' | 'keyboard';
|
||||||
direction: 'incoming' | 'outgoing';
|
direction: 'incoming' | 'outgoing';
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error';
|
status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error';
|
||||||
|
@ -130,7 +133,7 @@ export type PropsActions = {
|
||||||
sentAt: number;
|
sentAt: number;
|
||||||
}
|
}
|
||||||
) => void;
|
) => void;
|
||||||
selectMessage: (messageId: string, conversationId: string) => unknown;
|
selectMessage?: (messageId: string, conversationId: string) => unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Props = PropsData & PropsHousekeeping & PropsActions;
|
export type Props = PropsData & PropsHousekeeping & PropsActions;
|
||||||
|
@ -139,6 +142,9 @@ interface State {
|
||||||
expiring: boolean;
|
expiring: boolean;
|
||||||
expired: boolean;
|
expired: boolean;
|
||||||
imageBroken: boolean;
|
imageBroken: boolean;
|
||||||
|
|
||||||
|
isSelected: boolean;
|
||||||
|
prevSelectedCounter: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EXPIRATION_CHECK_MINIMUM = 2000;
|
const EXPIRATION_CHECK_MINIMUM = 2000;
|
||||||
|
@ -149,16 +155,46 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
public focusRef: React.RefObject<HTMLDivElement> = React.createRef();
|
public focusRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||||
public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();
|
public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();
|
||||||
|
|
||||||
public state = {
|
|
||||||
expiring: false,
|
|
||||||
expired: false,
|
|
||||||
imageBroken: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
public expirationCheckInterval: any;
|
public expirationCheckInterval: any;
|
||||||
public expiredTimeout: any;
|
public expiredTimeout: any;
|
||||||
public selectedTimeout: any;
|
public selectedTimeout: any;
|
||||||
|
|
||||||
|
public constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
expiring: false,
|
||||||
|
expired: false,
|
||||||
|
imageBroken: false,
|
||||||
|
|
||||||
|
isSelected: props.isSelected,
|
||||||
|
prevSelectedCounter: props.isSelectedCounter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getDerivedStateFromProps(props: Props, state: State): State {
|
||||||
|
if (!props.isSelected) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isSelected: false,
|
||||||
|
prevSelectedCounter: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
props.isSelected &&
|
||||||
|
props.isSelectedCounter !== state.prevSelectedCounter
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isSelected: props.isSelected,
|
||||||
|
prevSelectedCounter: props.isSelectedCounter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
public captureMenuTrigger = (triggerRef: Trigger) => {
|
public captureMenuTrigger = (triggerRef: Trigger) => {
|
||||||
this.menuTriggerRef = triggerRef;
|
this.menuTriggerRef = triggerRef;
|
||||||
};
|
};
|
||||||
|
@ -180,10 +216,20 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public handleFocus = () => {
|
||||||
|
const { interactionMode } = this.props;
|
||||||
|
|
||||||
|
if (interactionMode === 'keyboard') {
|
||||||
|
this.setSelected();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public setSelected = () => {
|
public setSelected = () => {
|
||||||
const { id, conversationId, selectMessage } = this.props;
|
const { id, conversationId, selectMessage } = this.props;
|
||||||
|
|
||||||
selectMessage(id, conversationId);
|
if (selectMessage) {
|
||||||
|
selectMessage(id, conversationId);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public setFocus = () => {
|
public setFocus = () => {
|
||||||
|
@ -195,6 +241,8 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
|
this.startSelectedTimer();
|
||||||
|
|
||||||
const { isSelected } = this.props;
|
const { isSelected } = this.props;
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
this.setFocus();
|
this.setFocus();
|
||||||
|
@ -228,6 +276,8 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: Props) {
|
public componentDidUpdate(prevProps: Props) {
|
||||||
|
this.startSelectedTimer();
|
||||||
|
|
||||||
if (!prevProps.isSelected && this.props.isSelected) {
|
if (!prevProps.isSelected && this.props.isSelected) {
|
||||||
this.setFocus();
|
this.setFocus();
|
||||||
}
|
}
|
||||||
|
@ -235,6 +285,23 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
this.checkExpired();
|
this.checkExpired();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public startSelectedTimer() {
|
||||||
|
const { interactionMode } = this.props;
|
||||||
|
const { isSelected } = this.state;
|
||||||
|
|
||||||
|
if (interactionMode === 'keyboard' || !isSelected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.selectedTimeout) {
|
||||||
|
this.selectedTimeout = setTimeout(() => {
|
||||||
|
this.selectedTimeout = undefined;
|
||||||
|
this.setState({ isSelected: false });
|
||||||
|
this.props.clearSelectedMessage();
|
||||||
|
}, SELECTED_TIMEOUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public checkExpired() {
|
public checkExpired() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const { isExpired, expirationTimestamp, expirationLength } = this.props;
|
const { isExpired, expirationTimestamp, expirationLength } = this.props;
|
||||||
|
@ -598,6 +665,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
<button
|
<button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-message__link-preview',
|
'module-message__link-preview',
|
||||||
|
`module-message__link-preview--${direction}`,
|
||||||
withContentAbove
|
withContentAbove
|
||||||
? 'module-message__link-preview--with-content-above'
|
? 'module-message__link-preview--with-content-above'
|
||||||
: null
|
: null
|
||||||
|
@ -1389,12 +1457,12 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
const {
|
const {
|
||||||
authorColor,
|
authorColor,
|
||||||
direction,
|
direction,
|
||||||
isSelected,
|
|
||||||
isSticker,
|
isSticker,
|
||||||
isTapToView,
|
isTapToView,
|
||||||
isTapToViewExpired,
|
isTapToViewExpired,
|
||||||
isTapToViewError,
|
isTapToViewError,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const { isSelected } = this.state;
|
||||||
|
|
||||||
const isAttachmentPending = this.isAttachmentPending();
|
const isAttachmentPending = this.isAttachmentPending();
|
||||||
|
|
||||||
|
@ -1447,7 +1515,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
isSticker,
|
isSticker,
|
||||||
timestamp,
|
timestamp,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { expired, expiring, imageBroken } = this.state;
|
const { expired, expiring, imageBroken, isSelected } = this.state;
|
||||||
|
|
||||||
// This id is what connects our triple-dot click with our associated pop-up menu.
|
// This id is what connects our triple-dot click with our associated pop-up menu.
|
||||||
// It needs to be unique.
|
// It needs to be unique.
|
||||||
|
@ -1466,6 +1534,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-message',
|
'module-message',
|
||||||
`module-message--${direction}`,
|
`module-message--${direction}`,
|
||||||
|
isSelected ? 'module-message--selected' : null,
|
||||||
expiring ? 'module-message--expired' : null,
|
expiring ? 'module-message--expired' : null,
|
||||||
conversationType === 'group' ? 'module-message--group' : null
|
conversationType === 'group' ? 'module-message--group' : null
|
||||||
)}
|
)}
|
||||||
|
@ -1475,7 +1544,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
role="button"
|
role="button"
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
onFocus={this.setSelected}
|
onFocus={this.handleFocus}
|
||||||
ref={this.focusRef}
|
ref={this.focusRef}
|
||||||
>
|
>
|
||||||
{this.renderError(direction === 'incoming')}
|
{this.renderError(direction === 'incoming')}
|
||||||
|
|
|
@ -35,12 +35,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MessageDetail extends React.Component<Props> {
|
export class MessageDetail extends React.Component<Props> {
|
||||||
private readonly focusRef: React.RefObject<HTMLDivElement>;
|
private readonly focusRef = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.focusRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
// When this component is created, it's initially not part of the DOM, and then it's
|
// When this component is created, it's initially not part of the DOM, and then it's
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#### Plain text
|
#### Plain text
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
#### Name variations
|
#### Name variations
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
#### With emoji
|
#### With emoji
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
#### Replies to you or yourself
|
#### Replies to you or yourself
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -186,7 +186,12 @@
|
||||||
#### In a group conversation
|
#### In a group conversation
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} type="group" ios={util.ios}>
|
<util.ConversationContext
|
||||||
|
theme={util.theme}
|
||||||
|
type="group"
|
||||||
|
ios={util.ios}
|
||||||
|
mode={util.mode}
|
||||||
|
>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -231,7 +236,7 @@ Note: for incoming messages, quote color is taken from the parent message. For o
|
||||||
messages the color is taken from the contact who wrote the quoted message.
|
messages the color is taken from the contact who wrote the quoted message.
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -610,7 +615,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Referenced message not found
|
#### Referenced message not found
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -687,7 +692,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Long names and context
|
#### Long names and context
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -729,7 +734,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### A lot of text in quotation
|
#### A lot of text in quotation
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -773,7 +778,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### A lot of text in quotation, with icon
|
#### A lot of text in quotation, with icon
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -825,7 +830,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### A lot of text in quotation, with image
|
#### A lot of text in quotation, with image
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -885,7 +890,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Image with caption
|
#### Image with caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -937,7 +942,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Image
|
#### Image
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -987,7 +992,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Image with no thumbnail
|
#### Image with no thumbnail
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1030,7 +1035,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Pending image download
|
#### Pending image download
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1073,7 +1078,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Video with caption
|
#### Video with caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1125,7 +1130,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Video
|
#### Video
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1175,7 +1180,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Video with no thumbnail
|
#### Video with no thumbnail
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1223,7 +1228,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Audio with caption
|
#### Audio with caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1267,7 +1272,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Audio
|
#### Audio
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1309,7 +1314,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Voice message
|
#### Voice message
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1355,7 +1360,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Other file type with caption
|
#### Other file type with caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1438,7 +1443,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Other file type
|
#### Other file type
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1482,7 +1487,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Quote, image attachment, and caption
|
#### Quote, image attachment, and caption
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1532,7 +1537,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Quote, image attachment
|
#### Quote, image attachment
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1580,7 +1585,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Quote, portrait image attachment
|
#### Quote, portrait image attachment
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1628,7 +1633,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Quote, video attachment
|
#### Quote, video attachment
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1686,7 +1691,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Quote, audio attachment
|
#### Quote, audio attachment
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1730,7 +1735,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Quote, file attachment
|
#### Quote, file attachment
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="module-message-container">
|
<div className="module-message-container">
|
||||||
<Message
|
<Message
|
||||||
direction="incoming"
|
direction="incoming"
|
||||||
|
@ -1778,7 +1783,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### Plain text
|
#### Plain text
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="bottom-bar">
|
<div className="bottom-bar">
|
||||||
<Quote
|
<Quote
|
||||||
text="How many ferrets do you have?"
|
text="How many ferrets do you have?"
|
||||||
|
@ -1796,7 +1801,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### With an icon
|
#### With an icon
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="bottom-bar">
|
<div className="bottom-bar">
|
||||||
<Quote
|
<Quote
|
||||||
text="How many ferrets do you have?"
|
text="How many ferrets do you have?"
|
||||||
|
@ -1818,7 +1823,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### With an image
|
#### With an image
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="bottom-bar">
|
<div className="bottom-bar">
|
||||||
<Quote
|
<Quote
|
||||||
text="How many ferrets do you have?"
|
text="How many ferrets do you have?"
|
||||||
|
@ -1843,7 +1848,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### With attachment and no text
|
#### With attachment and no text
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="bottom-bar">
|
<div className="bottom-bar">
|
||||||
<Quote
|
<Quote
|
||||||
authorColor="blue"
|
authorColor="blue"
|
||||||
|
@ -1867,7 +1872,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### With generic attachment
|
#### With generic attachment
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="bottom-bar">
|
<div className="bottom-bar">
|
||||||
<Quote
|
<Quote
|
||||||
text="How many ferrets do you have?"
|
text="How many ferrets do you have?"
|
||||||
|
@ -1889,7 +1894,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### With a close button
|
#### With a close button
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="bottom-bar">
|
<div className="bottom-bar">
|
||||||
<Quote
|
<Quote
|
||||||
text="How many ferrets do you have?"
|
text="How many ferrets do you have?"
|
||||||
|
@ -1908,7 +1913,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### With a close button and icon
|
#### With a close button and icon
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="bottom-bar">
|
<div className="bottom-bar">
|
||||||
<Quote
|
<Quote
|
||||||
text="How many ferrets do you have?"
|
text="How many ferrets do you have?"
|
||||||
|
@ -1931,7 +1936,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
||||||
#### With a close button and image
|
#### With a close button and image
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||||
<div className="bottom-bar">
|
<div className="bottom-bar">
|
||||||
<Quote
|
<Quote
|
||||||
text="How many ferrets do you have?"
|
text="How many ferrets do you have?"
|
||||||
|
|
|
@ -99,20 +99,13 @@ export class Quote extends React.Component<Props, State> {
|
||||||
|
|
||||||
// This is important to ensure that using this quote to navigate to the referenced
|
// This is important to ensure that using this quote to navigate to the referenced
|
||||||
// message doesn't also trigger its parent message's keydown.
|
// message doesn't also trigger its parent message's keydown.
|
||||||
if (onClick && (event.key === 'Enter' || event.key === 'Space')) {
|
if (onClick && (event.key === 'Enter' || event.key === ' ')) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
onClick();
|
onClick();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// We prevent this from bubbling to prevent the focus flash around a message when
|
|
||||||
// you click a quote.
|
|
||||||
public handleMouseDown = (event: React.MouseEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
public handleImageError = () => {
|
public handleImageError = () => {
|
||||||
// tslint:disable-next-line no-console
|
// tslint:disable-next-line no-console
|
||||||
console.log('Message: Image failed to load; failing over to placeholder');
|
console.log('Message: Image failed to load; failing over to placeholder');
|
||||||
|
@ -271,14 +264,20 @@ export class Quote extends React.Component<Props, State> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't want the overall click handler for the quote to fire, so we stop
|
const clickHandler = (e: React.MouseEvent): void => {
|
||||||
// propagation before handing control to the caller's callback.
|
|
||||||
const onClick = (e: React.MouseEvent<{}>): void => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
const keyDownHandler = (e: React.KeyboardEvent): void => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// We need the container to give us the flexibility to implement the iOS design.
|
// We need the container to give us the flexibility to implement the iOS design.
|
||||||
return (
|
return (
|
||||||
|
@ -288,7 +287,8 @@ export class Quote extends React.Component<Props, State> {
|
||||||
// We can't be a button because the overall quote is a button; can't nest them
|
// We can't be a button because the overall quote is a button; can't nest them
|
||||||
role="button"
|
role="button"
|
||||||
className="module-quote__close-button"
|
className="module-quote__close-button"
|
||||||
onClick={onClick}
|
onKeyDown={keyDownHandler}
|
||||||
|
onClick={clickHandler}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -383,7 +383,6 @@ export class Quote extends React.Component<Props, State> {
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
onMouseDown={this.handleMouseDown}
|
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-quote',
|
'module-quote',
|
||||||
isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
|
isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
|
||||||
|
|
|
@ -55,6 +55,7 @@ const Tab = ({
|
||||||
)}
|
)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
role="tab"
|
role="tab"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
|
@ -81,7 +82,7 @@ export class MediaGallery extends React.Component<Props, State> {
|
||||||
const { selectedTab } = this.state;
|
const { selectedTab } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-media-gallery" tabIndex={0} ref={this.focusRef}>
|
<div className="module-media-gallery" tabIndex={-1} ref={this.focusRef}>
|
||||||
<div className="module-media-gallery__tab-container">
|
<div className="module-media-gallery__tab-container">
|
||||||
<Tab
|
<Tab
|
||||||
label="Media"
|
label="Media"
|
||||||
|
|
|
@ -18,12 +18,6 @@ export type OwnProps = {
|
||||||
|
|
||||||
export type Props = OwnProps;
|
export type Props = OwnProps;
|
||||||
|
|
||||||
function focusOnRender(el: HTMLElement | null) {
|
|
||||||
if (el) {
|
|
||||||
el.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const StickerManager = React.memo(
|
export const StickerManager = React.memo(
|
||||||
// tslint:disable-next-line max-func-body-length
|
// tslint:disable-next-line max-func-body-length
|
||||||
({
|
({
|
||||||
|
@ -36,6 +30,7 @@ export const StickerManager = React.memo(
|
||||||
uninstallStickerPack,
|
uninstallStickerPack,
|
||||||
i18n,
|
i18n,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const focusRef = React.createRef<HTMLDivElement>();
|
||||||
const [
|
const [
|
||||||
packToPreview,
|
packToPreview,
|
||||||
setPackToPreview,
|
setPackToPreview,
|
||||||
|
@ -48,6 +43,14 @@ export const StickerManager = React.memo(
|
||||||
knownPacks.forEach(pack => {
|
knownPacks.forEach(pack => {
|
||||||
downloadStickerPack(pack.id, pack.key);
|
downloadStickerPack(pack.id, pack.key);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When this component is created, it's initially not part of the DOM, and then it's
|
||||||
|
// added off-screen and animated in. This ensures that the focus takes.
|
||||||
|
setTimeout(() => {
|
||||||
|
if (focusRef.current) {
|
||||||
|
focusRef.current.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const clearPackToPreview = React.useCallback(
|
const clearPackToPreview = React.useCallback(
|
||||||
|
@ -76,11 +79,7 @@ export const StickerManager = React.memo(
|
||||||
uninstallStickerPack={uninstallStickerPack}
|
uninstallStickerPack={uninstallStickerPack}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div
|
<div className="module-sticker-manager" tabIndex={-1} ref={focusRef}>
|
||||||
className="module-sticker-manager"
|
|
||||||
tabIndex={-1}
|
|
||||||
ref={focusOnRender}
|
|
||||||
>
|
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
i18nKey: 'stickers--StickerManager--InstalledPacks',
|
i18nKey: 'stickers--StickerManager--InstalledPacks',
|
||||||
|
|
|
@ -81,6 +81,10 @@ export const StickerPreviewModal = React.memo(
|
||||||
// Restore focus on teardown
|
// Restore focus on teardown
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
() => {
|
() => {
|
||||||
|
if (!root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const lastFocused = document.activeElement as any;
|
const lastFocused = document.activeElement as any;
|
||||||
if (focusRef.current) {
|
if (focusRef.current) {
|
||||||
focusRef.current.focus();
|
focusRef.current.focus();
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { AnyAction } from 'redux';
|
|
||||||
import { LocalizerType } from '../../types/Util';
|
import { LocalizerType } from '../../types/Util';
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
@ -11,6 +10,7 @@ export type UserStateType = {
|
||||||
platform: string;
|
platform: string;
|
||||||
regionCode: string;
|
regionCode: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
|
interactionMode: 'mouse' | 'keyboard';
|
||||||
};
|
};
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
@ -18,12 +18,13 @@ export type UserStateType = {
|
||||||
type UserChangedActionType = {
|
type UserChangedActionType = {
|
||||||
type: 'USER_CHANGED';
|
type: 'USER_CHANGED';
|
||||||
payload: {
|
payload: {
|
||||||
ourNumber: string;
|
ourNumber?: string;
|
||||||
regionCode: string;
|
regionCode?: string;
|
||||||
|
interactionMode?: 'mouse' | 'keyboard';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserActionType = AnyAction | UserChangedActionType;
|
export type UserActionType = UserChangedActionType;
|
||||||
|
|
||||||
// Action Creators
|
// Action Creators
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ function getEmptyState(): UserStateType {
|
||||||
ourNumber: 'missing',
|
ourNumber: 'missing',
|
||||||
regionCode: 'missing',
|
regionCode: 'missing',
|
||||||
platform: 'missing',
|
platform: 'missing',
|
||||||
|
interactionMode: 'mouse',
|
||||||
i18n: () => 'missing',
|
i18n: () => 'missing',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,12 @@ import { getBubbleProps } from '../../shims/Whisper';
|
||||||
import { PropsDataType as TimelinePropsType } from '../../components/conversation/Timeline';
|
import { PropsDataType as TimelinePropsType } from '../../components/conversation/Timeline';
|
||||||
import { TimelineItemType } from '../../components/conversation/TimelineItem';
|
import { TimelineItemType } from '../../components/conversation/TimelineItem';
|
||||||
|
|
||||||
import { getIntl, getRegionCode, getUserNumber } from './user';
|
import {
|
||||||
|
getInteractionMode,
|
||||||
|
getIntl,
|
||||||
|
getRegionCode,
|
||||||
|
getUserNumber,
|
||||||
|
} from './user';
|
||||||
|
|
||||||
export const getConversations = (state: StateType): ConversationsStateType =>
|
export const getConversations = (state: StateType): ConversationsStateType =>
|
||||||
state.conversations;
|
state.conversations;
|
||||||
|
@ -245,6 +250,7 @@ export function _messageSelector(
|
||||||
ourNumber: string,
|
ourNumber: string,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
regionCode: string,
|
regionCode: string,
|
||||||
|
interactionMode: 'mouse' | 'keyboard',
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
conversation?: ConversationType,
|
conversation?: ConversationType,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -263,13 +269,20 @@ export function _messageSelector(
|
||||||
...props,
|
...props,
|
||||||
data: {
|
data: {
|
||||||
...props.data,
|
...props.data,
|
||||||
|
interactionMode,
|
||||||
isSelected: true,
|
isSelected: true,
|
||||||
isSelectedCounter: selectedMessageCounter,
|
isSelectedCounter: selectedMessageCounter,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return props;
|
return {
|
||||||
|
...props,
|
||||||
|
data: {
|
||||||
|
...props.data,
|
||||||
|
interactionMode,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// A little optimization to reset our selector cache whenever high-level application data
|
// A little optimization to reset our selector cache whenever high-level application data
|
||||||
|
@ -278,6 +291,7 @@ type CachedMessageSelectorType = (
|
||||||
message: MessageType,
|
message: MessageType,
|
||||||
ourNumber: string,
|
ourNumber: string,
|
||||||
regionCode: string,
|
regionCode: string,
|
||||||
|
interactionMode: 'mouse' | 'keyboard',
|
||||||
conversation?: ConversationType,
|
conversation?: ConversationType,
|
||||||
author?: ConversationType,
|
author?: ConversationType,
|
||||||
quoted?: ConversationType,
|
quoted?: ConversationType,
|
||||||
|
@ -302,13 +316,15 @@ export const getMessageSelector = createSelector(
|
||||||
getConversationSelector,
|
getConversationSelector,
|
||||||
getRegionCode,
|
getRegionCode,
|
||||||
getUserNumber,
|
getUserNumber,
|
||||||
|
getInteractionMode,
|
||||||
(
|
(
|
||||||
messageSelector: CachedMessageSelectorType,
|
messageSelector: CachedMessageSelectorType,
|
||||||
messageLookup: MessageLookupType,
|
messageLookup: MessageLookupType,
|
||||||
selectedMessage: SelectedMessageType | undefined,
|
selectedMessage: SelectedMessageType | undefined,
|
||||||
conversationSelector: GetConversationByIdType,
|
conversationSelector: GetConversationByIdType,
|
||||||
regionCode: string,
|
regionCode: string,
|
||||||
ourNumber: string
|
ourNumber: string,
|
||||||
|
interactionMode: 'keyboard' | 'mouse'
|
||||||
): GetMessageByIdType => {
|
): GetMessageByIdType => {
|
||||||
return (id: string) => {
|
return (id: string) => {
|
||||||
const message = messageLookup[id];
|
const message = messageLookup[id];
|
||||||
|
@ -335,6 +351,7 @@ export const getMessageSelector = createSelector(
|
||||||
message,
|
message,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
regionCode,
|
regionCode,
|
||||||
|
interactionMode,
|
||||||
conversation,
|
conversation,
|
||||||
author,
|
author,
|
||||||
quoted,
|
quoted,
|
||||||
|
|
|
@ -22,6 +22,11 @@ export const getIntl = createSelector(
|
||||||
(state: UserStateType): LocalizerType => state.i18n
|
(state: UserStateType): LocalizerType => state.i18n
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getInteractionMode = createSelector(
|
||||||
|
getUser,
|
||||||
|
(state: UserStateType) => state.interactionMode
|
||||||
|
);
|
||||||
|
|
||||||
export const getAttachmentsPath = createSelector(
|
export const getAttachmentsPath = createSelector(
|
||||||
getUser,
|
getUser,
|
||||||
(state: UserStateType): string => state.attachmentsPath
|
(state: UserStateType): string => state.attachmentsPath
|
||||||
|
|
|
@ -7,6 +7,7 @@ interface Props {
|
||||||
*/
|
*/
|
||||||
ios: boolean;
|
ios: boolean;
|
||||||
theme: 'light-theme' | 'dark-theme';
|
theme: 'light-theme' | 'dark-theme';
|
||||||
|
mode: 'mouse-mode' | 'keyboard-mode';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,11 +16,15 @@ interface Props {
|
||||||
*/
|
*/
|
||||||
export class ConversationContext extends React.Component<Props> {
|
export class ConversationContext extends React.Component<Props> {
|
||||||
public render() {
|
public render() {
|
||||||
const { ios, theme } = this.props;
|
const { ios, theme, mode } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(theme || 'light-theme', ios ? 'ios-theme' : null)}
|
className={classNames(
|
||||||
|
theme || 'light-theme',
|
||||||
|
ios ? 'ios-theme' : null,
|
||||||
|
mode
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: theme === 'dark-theme' ? 'black' : undefined,
|
backgroundColor: theme === 'dark-theme' ? 'black' : undefined,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -119,6 +119,7 @@ const urlOptions = QueryString.parse(query);
|
||||||
const theme = urlOptions.theme || 'light-theme';
|
const theme = urlOptions.theme || 'light-theme';
|
||||||
const ios = urlOptions.ios || false;
|
const ios = urlOptions.ios || false;
|
||||||
const locale = urlOptions.locale || 'en';
|
const locale = urlOptions.locale || 'en';
|
||||||
|
const mode = urlOptions.mode || 'mouse-mode';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import localeMessages from '../../_locales/en/messages.json';
|
import localeMessages from '../../_locales/en/messages.json';
|
||||||
|
@ -127,7 +128,10 @@ import localeMessages from '../../_locales/en/messages.json';
|
||||||
import { setup } from '../../js/modules/i18n';
|
import { setup } from '../../js/modules/i18n';
|
||||||
const i18n = setup(locale, localeMessages);
|
const i18n = setup(locale, localeMessages);
|
||||||
|
|
||||||
export { theme, ios, locale, i18n };
|
export { theme, ios, locale, mode, i18n };
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
window.getInteractionMode = () => mode;
|
||||||
|
|
||||||
// Telling Lodash to relinquish _ for use by underscore
|
// Telling Lodash to relinquish _ for use by underscore
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -479,7 +479,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " el: this.$('.conversation-stack'),",
|
"line": " el: this.$('.conversation-stack'),",
|
||||||
"lineNumber": 86,
|
"lineNumber": 85,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -488,7 +488,7 @@
|
||||||
"rule": "jQuery-prependTo(",
|
"rule": "jQuery-prependTo(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.appLoadingScreen.$el.prependTo(this.el);",
|
"line": " this.appLoadingScreen.$el.prependTo(this.el);",
|
||||||
"lineNumber": 93,
|
"lineNumber": 92,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -497,7 +497,7 @@
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " .append(this.networkStatusView.render().el);",
|
"line": " .append(this.networkStatusView.render().el);",
|
||||||
"lineNumber": 110,
|
"lineNumber": 109,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -506,7 +506,7 @@
|
||||||
"rule": "jQuery-prependTo(",
|
"rule": "jQuery-prependTo(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " banner.$el.prependTo(this.$el);",
|
"line": " banner.$el.prependTo(this.$el);",
|
||||||
"lineNumber": 114,
|
"lineNumber": 113,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -515,7 +515,7 @@
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " toast.$el.appendTo(this.$el);",
|
"line": " toast.$el.appendTo(this.$el);",
|
||||||
"lineNumber": 120,
|
"lineNumber": 119,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -524,7 +524,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
||||||
"lineNumber": 140,
|
"lineNumber": 139,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -533,7 +533,7 @@
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
||||||
"lineNumber": 140,
|
"lineNumber": 139,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -542,7 +542,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " if (e && this.$(e.target).closest('.placeholder').length) {",
|
"line": " if (e && this.$(e.target).closest('.placeholder').length) {",
|
||||||
"lineNumber": 190,
|
"lineNumber": 189,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -551,7 +551,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('#header, .gutter').addClass('inactive');",
|
"line": " this.$('#header, .gutter').addClass('inactive');",
|
||||||
"lineNumber": 194,
|
"lineNumber": 193,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -560,7 +560,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.conversation-stack').addClass('inactive');",
|
"line": " this.$('.conversation-stack').addClass('inactive');",
|
||||||
"lineNumber": 198,
|
"lineNumber": 197,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -569,7 +569,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.conversation:first .menu').trigger('close');",
|
"line": " this.$('.conversation:first .menu').trigger('close');",
|
||||||
"lineNumber": 200,
|
"lineNumber": 199,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -578,7 +578,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " if (e && this.$(e.target).closest('.capture-audio').length > 0) {",
|
"line": " if (e && this.$(e.target).closest('.capture-audio').length > 0) {",
|
||||||
"lineNumber": 220,
|
"lineNumber": 219,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
|
@ -587,7 +587,7 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/inbox_view.js",
|
"path": "js/views/inbox_view.js",
|
||||||
"line": " this.$('.conversation:first .recorder').trigger('close');",
|
"line": " this.$('.conversation:first .recorder').trigger('close');",
|
||||||
"lineNumber": 223,
|
"lineNumber": 222,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-10-21T22:30:15.622Z",
|
"updated": "2019-10-21T22:30:15.622Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
|
@ -7623,7 +7623,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Message.js",
|
"path": "ts/components/conversation/Message.js",
|
||||||
"line": " this.focusRef = react_1.default.createRef();",
|
"line": " this.focusRef = react_1.default.createRef();",
|
||||||
"lineNumber": 31,
|
"lineNumber": 32,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-11-01T22:46:33.013Z",
|
"updated": "2019-11-01T22:46:33.013Z",
|
||||||
"reasonDetail": "Used for setting focus only"
|
"reasonDetail": "Used for setting focus only"
|
||||||
|
@ -7632,7 +7632,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Message.tsx",
|
"path": "ts/components/conversation/Message.tsx",
|
||||||
"line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
"line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
||||||
"lineNumber": 149,
|
"lineNumber": 155,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-11-01T22:46:33.013Z",
|
"updated": "2019-11-01T22:46:33.013Z",
|
||||||
"reasonDetail": "Used for setting focus only"
|
"reasonDetail": "Used for setting focus only"
|
||||||
|
@ -7646,15 +7646,6 @@
|
||||||
"updated": "2019-11-01T22:46:33.013Z",
|
"updated": "2019-11-01T22:46:33.013Z",
|
||||||
"reasonDetail": "Used for setting focus only"
|
"reasonDetail": "Used for setting focus only"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"rule": "React-createRef",
|
|
||||||
"path": "ts/components/conversation/MessageDetail.tsx",
|
|
||||||
"line": " this.focusRef = React.createRef();",
|
|
||||||
"lineNumber": 42,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2019-11-01T22:46:33.013Z",
|
|
||||||
"reasonDetail": "Used for setting focus only"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Timeline.js",
|
"path": "ts/components/conversation/Timeline.js",
|
||||||
|
@ -7677,11 +7668,20 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/media-gallery/MediaGallery.tsx",
|
"path": "ts/components/conversation/media-gallery/MediaGallery.tsx",
|
||||||
"line": " public readonly focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
"line": " public readonly focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
||||||
"lineNumber": 65,
|
"lineNumber": 66,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-11-01T22:46:33.013Z",
|
"updated": "2019-11-01T22:46:33.013Z",
|
||||||
"reasonDetail": "Used for setting focus only"
|
"reasonDetail": "Used for setting focus only"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-createRef",
|
||||||
|
"path": "ts/components/stickers/StickerManager.js",
|
||||||
|
"line": " const focusRef = React.createRef();",
|
||||||
|
"lineNumber": 20,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2019-11-21T06:13:49.384Z",
|
||||||
|
"reasonDetail": "Used for setting focus only"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "ts/shims/textsecure.js",
|
"path": "ts/shims/textsecure.js",
|
||||||
|
|
Loading…
Add table
Reference in a new issue