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);
|
||||
};
|
||||
|
||||
// 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
|
||||
window.preloadedImages = [];
|
||||
function preload(list) {
|
||||
|
@ -299,10 +354,11 @@
|
|||
// Stop processing incoming messages
|
||||
if (messageReceiver) {
|
||||
await messageReceiver.stopProcessing();
|
||||
|
||||
await window.waitForAllBatchers();
|
||||
messageReceiver.unregisterBatchers();
|
||||
}
|
||||
|
||||
if (messageReceiver) {
|
||||
messageReceiver.unregisterBatchers();
|
||||
messageReceiver = null;
|
||||
}
|
||||
|
||||
|
@ -522,6 +578,7 @@
|
|||
ourNumber: textsecure.storage.user.getNumber(),
|
||||
platform: window.platform,
|
||||
i18n: window.i18n,
|
||||
interactionMode: window.getInteractionMode(),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -668,6 +725,7 @@
|
|||
|
||||
// Navigate by section
|
||||
if (ctrlOrCommand && !shiftKey && (key === 't' || key === 'T')) {
|
||||
window.enterKeyboardMode();
|
||||
const focusedElement = document.activeElement;
|
||||
|
||||
const targets = [
|
||||
|
|
|
@ -80,7 +80,6 @@
|
|||
initialize(options = {}) {
|
||||
this.ready = false;
|
||||
this.render();
|
||||
this.$el.attr('tabindex', '1');
|
||||
|
||||
this.conversation_stack = new Whisper.ConversationStack({
|
||||
el: this.$('.conversation-stack'),
|
||||
|
|
|
@ -408,6 +408,13 @@
|
|||
padding: 5px 8px;
|
||||
border-radius: 5px;
|
||||
|
||||
outline: none;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
outline: -webkit-focus-ring-color auto 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@include light-theme {
|
||||
background-color: $color-gray-02;
|
||||
border: 1px solid $color-gray-15;
|
||||
|
|
|
@ -162,6 +162,13 @@ a {
|
|||
border: none;
|
||||
background: transparent;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
outline: none;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
|
@ -175,11 +182,6 @@ a {
|
|||
@include color-svg('../images/icons/v2/plus-24.svg', $color-gray-25);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@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 {
|
||||
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
|
||||
.module-message--selected .module-message__container--with-sticker {
|
||||
@include mouse-mode {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
.module-message:focus .module-message__container--with-sticker {
|
||||
box-shadow: none;
|
||||
@include keyboard-mode {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.module-message__container--with-sticker {
|
||||
@include light-theme {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
border: none;
|
||||
}
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
|
@ -603,9 +639,10 @@
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -762,9 +799,10 @@
|
|||
border-top-right-radius: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -934,33 +972,35 @@
|
|||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
outline: none;
|
||||
|
||||
@include light-theme {
|
||||
color: $color-gray-90;
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 2px solid $color-gray-90;
|
||||
}
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
outline: 1px solid $color-gray-90;
|
||||
}
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
color: $color-gray-05;
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 2px solid $color-gray-05;
|
||||
}
|
||||
@include dark-keyboard-mode {
|
||||
&:focus {
|
||||
outline: 1px solid $color-gray-05;
|
||||
}
|
||||
}
|
||||
|
||||
@include ios-theme {
|
||||
color: $color-white-alpha-90;
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 2px solid $color-white-alpha-90;
|
||||
}
|
||||
}
|
||||
@include ios-dark-theme {
|
||||
color: $color-white-alpha-90;
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 2px solid $color-white-alpha-90;
|
||||
}
|
||||
@include ios-keyboard-mode {
|
||||
&:focus {
|
||||
outline: 1px solid $color-white-alpha-90;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -982,33 +1022,41 @@
|
|||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
outline: none;
|
||||
|
||||
@include light-theme {
|
||||
color: $color-white;
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 2px solid $color-white;
|
||||
}
|
||||
}
|
||||
@include ios-theme {
|
||||
color: $color-gray-90;
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 2px solid $color-gray-90;
|
||||
}
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
outline: 1px solid $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
color: $color-white-alpha-90;
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 2px solid $color-white-alpha-90;
|
||||
}
|
||||
@include dark-keyboard-mode {
|
||||
&: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 {
|
||||
color: $color-gray-05;
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 2px solid $color-gray-05;
|
||||
}
|
||||
@include dark-ios-keyboard-mode {
|
||||
&:focus {
|
||||
outline: 1px solid $color-gray-05;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1235,9 +1283,10 @@
|
|||
border: 1px solid $color-gray-45;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
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-style: solid;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
@include ios-theme {
|
||||
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%;
|
||||
|
||||
background-color: $color-black-alpha-40;
|
||||
|
||||
@include keyboard-mode {
|
||||
&:focus-within {
|
||||
background-color: $color-signal-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.module-quote__close-button {
|
||||
|
@ -1799,9 +1862,18 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
|||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
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;
|
||||
padding: 5px;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
@include light-theme {
|
||||
background-color: $color-gray-02;
|
||||
}
|
||||
@include dark-theme {
|
||||
background-color: $color-gray-80;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
@include light-theme {
|
||||
background-color: $color-gray-02;
|
||||
}
|
||||
@include dark-theme {
|
||||
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 {
|
||||
background-color: $color-gray-02;
|
||||
|
||||
&:hover,
|
||||
}
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
background-color: $color-gray-15;
|
||||
}
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
background-color: $color-gray-75;
|
||||
|
||||
&:hover,
|
||||
}
|
||||
@include dark-keyboard-mode {
|
||||
&:focus {
|
||||
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 {
|
||||
@include button-reset;
|
||||
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
outline: -webkit-focus-ring-color auto 5px;
|
||||
}
|
||||
}
|
||||
|
||||
border-radius: 5px;
|
||||
margin: 1em auto;
|
||||
padding: 1em;
|
||||
|
@ -2708,6 +2788,19 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
|||
@include dark-theme {
|
||||
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 {
|
||||
|
@ -2763,12 +2856,19 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
|||
|
||||
.module-document-list-item__content {
|
||||
@include button-reset;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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-bottom: 4px;
|
||||
position: relative;
|
||||
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
border-radius: 50%;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 0px 2px $color-signal-blue;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
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
|
||||
border: none;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
|
||||
@include light-theme {
|
||||
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
|
||||
.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__container--with-sticker
|
||||
.module-image__border-overlay {
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
left: 1px;
|
||||
right: 1px;
|
||||
border-radius: 10px;
|
||||
@include keyboard-mode {
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
left: 1px;
|
||||
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:hover {
|
||||
box-shadow: inset 0px 0px 0px 2px $color-signal-blue;
|
||||
button.module-image__border-overlay:focus {
|
||||
@include keyboard-mode {
|
||||
box-shadow: inset 0px 0px 0px 2px $color-signal-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.module-image__border-overlay--dark {
|
||||
|
@ -3697,9 +3822,10 @@ button.module-image__border-overlay:hover {
|
|||
|
||||
background-image: url('../images/x-shadow-16.svg');
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 2px solid $color-signal-blue;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
outline: 2px solid $color-signal-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3854,8 +3980,10 @@ button.module-image__border-overlay:hover {
|
|||
z-index: 2;
|
||||
@include color-svg('../images/icons/v2/x-24.svg', $color-black);
|
||||
|
||||
&:focus {
|
||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4103,18 +4231,21 @@ button.module-image__border-overlay:hover {
|
|||
&:hover {
|
||||
background: $color-gray-05;
|
||||
}
|
||||
&:hover,
|
||||
}
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
box-shadow: inset 0 0 0 2px $color-signal-blue;
|
||||
}
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
border: 1px solid $color-gray-60;
|
||||
|
||||
&:hover {
|
||||
background: $color-gray-75;
|
||||
}
|
||||
&:hover,
|
||||
}
|
||||
@include dark-keyboard-mode {
|
||||
&:focus {
|
||||
box-shadow: inset 0 0 0 2px $color-signal-blue;
|
||||
}
|
||||
|
@ -4212,12 +4343,17 @@ button.module-image__border-overlay:hover {
|
|||
|
||||
@include light-theme {
|
||||
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-60);
|
||||
}
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||
}
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-25);
|
||||
}
|
||||
@include dark-keyboard-mode {
|
||||
&:focus {
|
||||
@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 {
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
@ -4381,6 +4518,7 @@ button.module-image__border-overlay:hover {
|
|||
padding-right: 1em;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.module-search-results__contacts-header {
|
||||
|
@ -4575,20 +4713,23 @@ button.module-image__border-overlay:hover {
|
|||
'../images/icons/v2/chevron-left-24.svg',
|
||||
$color-gray-60
|
||||
);
|
||||
&:focus,
|
||||
&:hover {
|
||||
}
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
@include color-svg(
|
||||
'../images/icons/v2/chevron-left-24.svg',
|
||||
$color-signal-blue
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
@include color-svg(
|
||||
'../images/icons/v2/chevron-left-24.svg',
|
||||
$color-gray-25
|
||||
);
|
||||
&:focus,
|
||||
}
|
||||
@include dark-keyboard-mode {
|
||||
&:hover {
|
||||
@include color-svg(
|
||||
'../images/icons/v2/chevron-left-24.svg',
|
||||
|
@ -4839,13 +4980,14 @@ button.module-image__border-overlay:hover {
|
|||
background: none;
|
||||
margin-right: 4px;
|
||||
|
||||
outline: none;
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
@include light-theme {
|
||||
@include keyboard-mode {
|
||||
background: $color-gray-05;
|
||||
}
|
||||
@include dark-theme {
|
||||
@include dark-keyboard-mode {
|
||||
background: $color-gray-60;
|
||||
}
|
||||
}
|
||||
|
@ -5040,6 +5182,10 @@ button.module-image__border-overlay:hover {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@include mouse-mode {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&__image,
|
||||
&__placeholder {
|
||||
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 {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
@ -5280,6 +5432,10 @@ button.module-image__border-overlay:hover {
|
|||
background: $color-gray-75;
|
||||
}
|
||||
|
||||
@include mouse-mode {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&--blue {
|
||||
@include light-theme {
|
||||
background: $color-signal-blue;
|
||||
|
@ -5503,10 +5659,13 @@ button.module-image__border-overlay:hover {
|
|||
align-items: center;
|
||||
opacity: 0.5;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
outline: none;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
content: '';
|
||||
|
@ -5716,8 +5875,8 @@ button.module-image__border-overlay:hover {
|
|||
|
||||
&__button {
|
||||
margin-left: 4px;
|
||||
border-radius: 14px;
|
||||
height: 28px;
|
||||
border-radius: 17px;
|
||||
height: 34px;
|
||||
padding: 5px 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -5725,6 +5884,10 @@ button.module-image__border-overlay:hover {
|
|||
|
||||
@include font-body-1-bold;
|
||||
|
||||
@include mouse-mode {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@include light-theme() {
|
||||
background: $color-white;
|
||||
color: $color-gray-60;
|
||||
|
@ -5884,6 +6047,10 @@ button.module-image__border-overlay:hover {
|
|||
align-items: center;
|
||||
background: none;
|
||||
|
||||
@include mouse-mode {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&--footer {
|
||||
&:not(:first-of-type) {
|
||||
margin-left: 4px;
|
||||
|
@ -6047,10 +6214,13 @@ button.module-image__border-overlay:hover {
|
|||
align-items: center;
|
||||
opacity: 0.5;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
outline: none;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
content: '';
|
||||
|
@ -6795,13 +6965,14 @@ button.module-image__border-overlay:hover {
|
|||
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-05);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
@include light-theme {
|
||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||
}
|
||||
@include dark-theme {
|
||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
@include light-theme {
|
||||
@include color-svg('../images/icons/v2/x-24.svg', $color-signal-blue);
|
||||
}
|
||||
@include dark-theme {
|
||||
@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;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
@include light-theme {
|
||||
background-color: $color-gray-05;
|
||||
}
|
||||
@include dark-theme {
|
||||
background-color: $color-gray-90;
|
||||
@include keyboard-mode {
|
||||
&:focus {
|
||||
@include light-theme {
|
||||
background-color: $color-gray-05;
|
||||
}
|
||||
@include dark-theme {
|
||||
background-color: $color-gray-90;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
outline: none;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
|
@ -43,7 +45,7 @@
|
|||
height: 32px;
|
||||
border-radius: 32px;
|
||||
margin-left: 5px;
|
||||
opacity: 0.5;
|
||||
opacity: 0.3;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
|
||||
|
@ -52,6 +54,8 @@
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
outline: none;
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
|
|
|
@ -213,6 +213,20 @@ export class SearchResults extends React.Component<PropsType, StateType> {
|
|||
};
|
||||
|
||||
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();
|
||||
if (!scrollContainer) {
|
||||
return;
|
||||
|
@ -513,9 +527,19 @@ export class SearchResults extends React.Component<PropsType, StateType> {
|
|||
|
||||
if (noResults) {
|
||||
return (
|
||||
<div className="module-search-results">
|
||||
<div
|
||||
className="module-search-results"
|
||||
tabIndex={-1}
|
||||
ref={this.containerRef}
|
||||
onFocus={this.handleFocus}
|
||||
>
|
||||
{!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 ? (
|
||||
<Intl
|
||||
id="noSearchResultsInConversation"
|
||||
|
|
|
@ -22,7 +22,7 @@ const contact = {
|
|||
onSendMessage: () => console.log('onSendMessage'),
|
||||
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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -92,7 +92,7 @@ const contact = {
|
|||
onSendMessage: () => console.log('onSendMessage'),
|
||||
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">
|
||||
<Message
|
||||
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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -182,7 +182,7 @@ const contact = {
|
|||
},
|
||||
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">
|
||||
<Message
|
||||
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">
|
||||
<Message
|
||||
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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -377,7 +377,7 @@ const contact = {
|
|||
},
|
||||
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">
|
||||
<Message
|
||||
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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -491,7 +491,7 @@ const contact = {
|
|||
|
||||
```jsx
|
||||
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">
|
||||
<Message
|
||||
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">
|
||||
<Message
|
||||
text="I want to introduce you to Someone..."
|
||||
|
|
|
@ -38,6 +38,7 @@ export class EmbeddedContact extends React.Component<Props> {
|
|||
<button
|
||||
className={classNames(
|
||||
'module-embedded-contact',
|
||||
`module-embedded-contact--${direction}`,
|
||||
withContentAbove
|
||||
? 'module-embedded-contact--with-content-above'
|
||||
: null,
|
||||
|
|
|
@ -4,7 +4,7 @@ export type PropsType = {
|
|||
id: string;
|
||||
conversationId: string;
|
||||
isSelected: boolean;
|
||||
selectMessage: (messageId: string, conversationId: string) => unknown;
|
||||
selectMessage?: (messageId: string, conversationId: string) => unknown;
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
const { id, conversationId, selectMessage } = this.props;
|
||||
|
||||
selectMessage(id, conversationId);
|
||||
if (selectMessage) {
|
||||
selectMessage(id, conversationId);
|
||||
}
|
||||
};
|
||||
|
||||
public componentDidMount() {
|
||||
|
@ -45,7 +54,7 @@ export class InlineNotificationWrapper extends React.Component<PropsType> {
|
|||
className="module-inline-notification-wrapper"
|
||||
tabIndex={0}
|
||||
ref={this.focusRef}
|
||||
onFocus={this.setSelected}
|
||||
onFocus={this.handleFocus}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Note that timestamp and status can be hidden with the `collapseMetadata` boolean property.
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -148,7 +148,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
|||
### Status
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="outgoing"
|
||||
|
@ -323,7 +323,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
|||
### All colors
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -450,7 +450,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
|||
### Long data
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="purple"
|
||||
|
@ -515,7 +515,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
|||
### Pending long message download
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="purple"
|
||||
|
@ -553,7 +553,7 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
|
|||
#### Image with caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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.
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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.
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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.
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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.
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -1062,7 +1062,7 @@ A sticker with no attachments (what our selectors produce for a pending sticker)
|
|||
#### Multiple images
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -1244,7 +1244,7 @@ A sticker with no attachments (what our selectors produce for a pending sticker)
|
|||
#### Multiple images with caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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.
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="outgoing"
|
||||
|
@ -1512,7 +1512,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
|||
#### 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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -1617,7 +1617,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
|||
#### Image with portrait aspect ratio
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -1819,7 +1819,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
|||
#### Image with landscape aspect ratio
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -1979,7 +1979,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
|||
#### Video with caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -2073,7 +2073,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
|||
#### Video
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -2170,7 +2170,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
|||
#### Missing images and videos
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -2532,7 +2532,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
|||
#### Image/video missing height/width
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -2619,7 +2619,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
|||
#### Audio with caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -2693,7 +2693,7 @@ Note that the delivered indicator is always Signal Blue, not the conversation co
|
|||
#### Audio
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -2767,7 +2767,7 @@ Voice notes are not shown any differently from audio attachments.
|
|||
#### Other file type with caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -2906,7 +2906,7 @@ Voice notes are not shown any differently from audio attachments.
|
|||
#### Other file type
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -2984,7 +2984,7 @@ Voice notes are not shown any differently from audio attachments.
|
|||
#### Other file type pending
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -3062,7 +3062,7 @@ Voice notes are not shown any differently from audio attachments.
|
|||
#### Dangerous file type
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -3108,7 +3108,7 @@ Voice notes are not shown any differently from audio attachments.
|
|||
#### Link previews, full-size image
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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.
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
authorColor="green"
|
||||
|
@ -3568,7 +3568,7 @@ Sticker link previews are forced to use the small link preview form, no matter t
|
|||
### Tap to view
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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.
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
|
|
@ -40,6 +40,7 @@ interface Trigger {
|
|||
// Same as MIN_WIDTH in ImageGrid.tsx
|
||||
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
||||
const STICKER_SIZE = 128;
|
||||
const SELECTED_TIMEOUT = 1000;
|
||||
|
||||
interface LinkPreviewType {
|
||||
title: string;
|
||||
|
@ -56,6 +57,8 @@ export type PropsData = {
|
|||
textPending?: boolean;
|
||||
isSticker: boolean;
|
||||
isSelected: boolean;
|
||||
isSelectedCounter: number;
|
||||
interactionMode: 'mouse' | 'keyboard';
|
||||
direction: 'incoming' | 'outgoing';
|
||||
timestamp: number;
|
||||
status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error';
|
||||
|
@ -130,7 +133,7 @@ export type PropsActions = {
|
|||
sentAt: number;
|
||||
}
|
||||
) => void;
|
||||
selectMessage: (messageId: string, conversationId: string) => unknown;
|
||||
selectMessage?: (messageId: string, conversationId: string) => unknown;
|
||||
};
|
||||
|
||||
export type Props = PropsData & PropsHousekeeping & PropsActions;
|
||||
|
@ -139,6 +142,9 @@ interface State {
|
|||
expiring: boolean;
|
||||
expired: boolean;
|
||||
imageBroken: boolean;
|
||||
|
||||
isSelected: boolean;
|
||||
prevSelectedCounter: number;
|
||||
}
|
||||
|
||||
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 audioRef: React.RefObject<HTMLAudioElement> = React.createRef();
|
||||
|
||||
public state = {
|
||||
expiring: false,
|
||||
expired: false,
|
||||
imageBroken: false,
|
||||
};
|
||||
|
||||
public expirationCheckInterval: any;
|
||||
public expiredTimeout: 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) => {
|
||||
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 = () => {
|
||||
const { id, conversationId, selectMessage } = this.props;
|
||||
|
||||
selectMessage(id, conversationId);
|
||||
if (selectMessage) {
|
||||
selectMessage(id, conversationId);
|
||||
}
|
||||
};
|
||||
|
||||
public setFocus = () => {
|
||||
|
@ -195,6 +241,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
public componentDidMount() {
|
||||
this.startSelectedTimer();
|
||||
|
||||
const { isSelected } = this.props;
|
||||
if (isSelected) {
|
||||
this.setFocus();
|
||||
|
@ -228,6 +276,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Props) {
|
||||
this.startSelectedTimer();
|
||||
|
||||
if (!prevProps.isSelected && this.props.isSelected) {
|
||||
this.setFocus();
|
||||
}
|
||||
|
@ -235,6 +285,23 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
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() {
|
||||
const now = Date.now();
|
||||
const { isExpired, expirationTimestamp, expirationLength } = this.props;
|
||||
|
@ -598,6 +665,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
<button
|
||||
className={classNames(
|
||||
'module-message__link-preview',
|
||||
`module-message__link-preview--${direction}`,
|
||||
withContentAbove
|
||||
? 'module-message__link-preview--with-content-above'
|
||||
: null
|
||||
|
@ -1389,12 +1457,12 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
const {
|
||||
authorColor,
|
||||
direction,
|
||||
isSelected,
|
||||
isSticker,
|
||||
isTapToView,
|
||||
isTapToViewExpired,
|
||||
isTapToViewError,
|
||||
} = this.props;
|
||||
const { isSelected } = this.state;
|
||||
|
||||
const isAttachmentPending = this.isAttachmentPending();
|
||||
|
||||
|
@ -1447,7 +1515,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
isSticker,
|
||||
timestamp,
|
||||
} = 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.
|
||||
// It needs to be unique.
|
||||
|
@ -1466,6 +1534,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
className={classNames(
|
||||
'module-message',
|
||||
`module-message--${direction}`,
|
||||
isSelected ? 'module-message--selected' : null,
|
||||
expiring ? 'module-message--expired' : null,
|
||||
conversationType === 'group' ? 'module-message--group' : null
|
||||
)}
|
||||
|
@ -1475,7 +1544,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
role="button"
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onClick={this.handleClick}
|
||||
onFocus={this.setSelected}
|
||||
onFocus={this.handleFocus}
|
||||
ref={this.focusRef}
|
||||
>
|
||||
{this.renderError(direction === 'incoming')}
|
||||
|
|
|
@ -35,12 +35,7 @@ interface Props {
|
|||
}
|
||||
|
||||
export class MessageDetail extends React.Component<Props> {
|
||||
private readonly focusRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.focusRef = React.createRef();
|
||||
}
|
||||
private readonly focusRef = React.createRef<HTMLDivElement>();
|
||||
|
||||
public componentDidMount() {
|
||||
// When this component is created, it's initially not part of the DOM, and then it's
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#### Plain text
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -41,7 +41,7 @@
|
|||
#### Name variations
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -112,7 +112,7 @@
|
|||
#### With emoji
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -148,7 +148,7 @@
|
|||
#### Replies to you or yourself
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -186,7 +186,12 @@
|
|||
#### In a group conversation
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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.
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -610,7 +615,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Referenced message not found
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -687,7 +692,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Long names and context
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
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
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -885,7 +890,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Image with caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -937,7 +942,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Image
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -987,7 +992,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Image with no thumbnail
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1030,7 +1035,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Pending image download
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1073,7 +1078,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Video with caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1125,7 +1130,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Video
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1175,7 +1180,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Video with no thumbnail
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1223,7 +1228,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Audio with caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1267,7 +1272,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Audio
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1309,7 +1314,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Voice message
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1355,7 +1360,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Other file type with caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1438,7 +1443,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Other file type
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1482,7 +1487,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Quote, image attachment, and caption
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1532,7 +1537,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Quote, image attachment
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1580,7 +1585,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Quote, portrait image attachment
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1628,7 +1633,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Quote, video attachment
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1686,7 +1691,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Quote, audio attachment
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1730,7 +1735,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Quote, file attachment
|
||||
|
||||
```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">
|
||||
<Message
|
||||
direction="incoming"
|
||||
|
@ -1778,7 +1783,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### Plain text
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||
<div className="bottom-bar">
|
||||
<Quote
|
||||
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
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||
<div className="bottom-bar">
|
||||
<Quote
|
||||
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
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||
<div className="bottom-bar">
|
||||
<Quote
|
||||
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
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||
<div className="bottom-bar">
|
||||
<Quote
|
||||
authorColor="blue"
|
||||
|
@ -1867,7 +1872,7 @@ messages the color is taken from the contact who wrote the quoted message.
|
|||
#### With generic attachment
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||
<div className="bottom-bar">
|
||||
<Quote
|
||||
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
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||
<div className="bottom-bar">
|
||||
<Quote
|
||||
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
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||
<div className="bottom-bar">
|
||||
<Quote
|
||||
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
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
||||
<util.ConversationContext theme={util.theme} ios={util.ios} mode={util.mode}>
|
||||
<div className="bottom-bar">
|
||||
<Quote
|
||||
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
|
||||
// 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.stopPropagation();
|
||||
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 = () => {
|
||||
// tslint:disable-next-line no-console
|
||||
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;
|
||||
}
|
||||
|
||||
// We don't want the overall click handler for the quote to fire, so we stop
|
||||
// propagation before handing control to the caller's callback.
|
||||
const onClick = (e: React.MouseEvent<{}>): void => {
|
||||
const clickHandler = (e: React.MouseEvent): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
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.
|
||||
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
|
||||
role="button"
|
||||
className="module-quote__close-button"
|
||||
onClick={onClick}
|
||||
onKeyDown={keyDownHandler}
|
||||
onClick={clickHandler}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -383,7 +383,6 @@ export class Quote extends React.Component<Props, State> {
|
|||
<button
|
||||
onClick={onClick}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
className={classNames(
|
||||
'module-quote',
|
||||
isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
|
||||
|
|
|
@ -55,6 +55,7 @@ const Tab = ({
|
|||
)}
|
||||
onClick={handleClick}
|
||||
role="tab"
|
||||
tabIndex={0}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
|
@ -81,7 +82,7 @@ export class MediaGallery extends React.Component<Props, State> {
|
|||
const { selectedTab } = this.state;
|
||||
|
||||
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">
|
||||
<Tab
|
||||
label="Media"
|
||||
|
|
|
@ -18,12 +18,6 @@ export type OwnProps = {
|
|||
|
||||
export type Props = OwnProps;
|
||||
|
||||
function focusOnRender(el: HTMLElement | null) {
|
||||
if (el) {
|
||||
el.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export const StickerManager = React.memo(
|
||||
// tslint:disable-next-line max-func-body-length
|
||||
({
|
||||
|
@ -36,6 +30,7 @@ export const StickerManager = React.memo(
|
|||
uninstallStickerPack,
|
||||
i18n,
|
||||
}: Props) => {
|
||||
const focusRef = React.createRef<HTMLDivElement>();
|
||||
const [
|
||||
packToPreview,
|
||||
setPackToPreview,
|
||||
|
@ -48,6 +43,14 @@ export const StickerManager = React.memo(
|
|||
knownPacks.forEach(pack => {
|
||||
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(
|
||||
|
@ -76,11 +79,7 @@ export const StickerManager = React.memo(
|
|||
uninstallStickerPack={uninstallStickerPack}
|
||||
/>
|
||||
) : null}
|
||||
<div
|
||||
className="module-sticker-manager"
|
||||
tabIndex={-1}
|
||||
ref={focusOnRender}
|
||||
>
|
||||
<div className="module-sticker-manager" tabIndex={-1} ref={focusRef}>
|
||||
{[
|
||||
{
|
||||
i18nKey: 'stickers--StickerManager--InstalledPacks',
|
||||
|
|
|
@ -81,6 +81,10 @@ export const StickerPreviewModal = React.memo(
|
|||
// Restore focus on teardown
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastFocused = document.activeElement as any;
|
||||
if (focusRef.current) {
|
||||
focusRef.current.focus();
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { AnyAction } from 'redux';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
// State
|
||||
|
@ -11,6 +10,7 @@ export type UserStateType = {
|
|||
platform: string;
|
||||
regionCode: string;
|
||||
i18n: LocalizerType;
|
||||
interactionMode: 'mouse' | 'keyboard';
|
||||
};
|
||||
|
||||
// Actions
|
||||
|
@ -18,12 +18,13 @@ export type UserStateType = {
|
|||
type UserChangedActionType = {
|
||||
type: 'USER_CHANGED';
|
||||
payload: {
|
||||
ourNumber: string;
|
||||
regionCode: string;
|
||||
ourNumber?: string;
|
||||
regionCode?: string;
|
||||
interactionMode?: 'mouse' | 'keyboard';
|
||||
};
|
||||
};
|
||||
|
||||
export type UserActionType = AnyAction | UserChangedActionType;
|
||||
export type UserActionType = UserChangedActionType;
|
||||
|
||||
// Action Creators
|
||||
|
||||
|
@ -51,6 +52,7 @@ function getEmptyState(): UserStateType {
|
|||
ourNumber: 'missing',
|
||||
regionCode: 'missing',
|
||||
platform: 'missing',
|
||||
interactionMode: 'mouse',
|
||||
i18n: () => 'missing',
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,7 +18,12 @@ import { getBubbleProps } from '../../shims/Whisper';
|
|||
import { PropsDataType as TimelinePropsType } from '../../components/conversation/Timeline';
|
||||
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 =>
|
||||
state.conversations;
|
||||
|
@ -245,6 +250,7 @@ export function _messageSelector(
|
|||
ourNumber: string,
|
||||
// @ts-ignore
|
||||
regionCode: string,
|
||||
interactionMode: 'mouse' | 'keyboard',
|
||||
// @ts-ignore
|
||||
conversation?: ConversationType,
|
||||
// @ts-ignore
|
||||
|
@ -263,13 +269,20 @@ export function _messageSelector(
|
|||
...props,
|
||||
data: {
|
||||
...props.data,
|
||||
interactionMode,
|
||||
isSelected: true,
|
||||
isSelectedCounter: selectedMessageCounter,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return props;
|
||||
return {
|
||||
...props,
|
||||
data: {
|
||||
...props.data,
|
||||
interactionMode,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// A little optimization to reset our selector cache whenever high-level application data
|
||||
|
@ -278,6 +291,7 @@ type CachedMessageSelectorType = (
|
|||
message: MessageType,
|
||||
ourNumber: string,
|
||||
regionCode: string,
|
||||
interactionMode: 'mouse' | 'keyboard',
|
||||
conversation?: ConversationType,
|
||||
author?: ConversationType,
|
||||
quoted?: ConversationType,
|
||||
|
@ -302,13 +316,15 @@ export const getMessageSelector = createSelector(
|
|||
getConversationSelector,
|
||||
getRegionCode,
|
||||
getUserNumber,
|
||||
getInteractionMode,
|
||||
(
|
||||
messageSelector: CachedMessageSelectorType,
|
||||
messageLookup: MessageLookupType,
|
||||
selectedMessage: SelectedMessageType | undefined,
|
||||
conversationSelector: GetConversationByIdType,
|
||||
regionCode: string,
|
||||
ourNumber: string
|
||||
ourNumber: string,
|
||||
interactionMode: 'keyboard' | 'mouse'
|
||||
): GetMessageByIdType => {
|
||||
return (id: string) => {
|
||||
const message = messageLookup[id];
|
||||
|
@ -335,6 +351,7 @@ export const getMessageSelector = createSelector(
|
|||
message,
|
||||
ourNumber,
|
||||
regionCode,
|
||||
interactionMode,
|
||||
conversation,
|
||||
author,
|
||||
quoted,
|
||||
|
|
|
@ -22,6 +22,11 @@ export const getIntl = createSelector(
|
|||
(state: UserStateType): LocalizerType => state.i18n
|
||||
);
|
||||
|
||||
export const getInteractionMode = createSelector(
|
||||
getUser,
|
||||
(state: UserStateType) => state.interactionMode
|
||||
);
|
||||
|
||||
export const getAttachmentsPath = createSelector(
|
||||
getUser,
|
||||
(state: UserStateType): string => state.attachmentsPath
|
||||
|
|
|
@ -7,6 +7,7 @@ interface Props {
|
|||
*/
|
||||
ios: boolean;
|
||||
theme: 'light-theme' | 'dark-theme';
|
||||
mode: 'mouse-mode' | 'keyboard-mode';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,11 +16,15 @@ interface Props {
|
|||
*/
|
||||
export class ConversationContext extends React.Component<Props> {
|
||||
public render() {
|
||||
const { ios, theme } = this.props;
|
||||
const { ios, theme, mode } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(theme || 'light-theme', ios ? 'ios-theme' : null)}
|
||||
className={classNames(
|
||||
theme || 'light-theme',
|
||||
ios ? 'ios-theme' : null,
|
||||
mode
|
||||
)}
|
||||
style={{
|
||||
backgroundColor: theme === 'dark-theme' ? 'black' : undefined,
|
||||
}}
|
||||
|
|
|
@ -119,6 +119,7 @@ const urlOptions = QueryString.parse(query);
|
|||
const theme = urlOptions.theme || 'light-theme';
|
||||
const ios = urlOptions.ios || false;
|
||||
const locale = urlOptions.locale || 'en';
|
||||
const mode = urlOptions.mode || 'mouse-mode';
|
||||
|
||||
// @ts-ignore
|
||||
import localeMessages from '../../_locales/en/messages.json';
|
||||
|
@ -127,7 +128,10 @@ import localeMessages from '../../_locales/en/messages.json';
|
|||
import { setup } from '../../js/modules/i18n';
|
||||
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
|
||||
// @ts-ignore
|
||||
|
|
|
@ -479,7 +479,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " el: this.$('.conversation-stack'),",
|
||||
"lineNumber": 86,
|
||||
"lineNumber": 85,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Hardcoded selector"
|
||||
|
@ -488,7 +488,7 @@
|
|||
"rule": "jQuery-prependTo(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.appLoadingScreen.$el.prependTo(this.el);",
|
||||
"lineNumber": 93,
|
||||
"lineNumber": 92,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Known DOM elements"
|
||||
|
@ -497,7 +497,7 @@
|
|||
"rule": "jQuery-append(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " .append(this.networkStatusView.render().el);",
|
||||
"lineNumber": 110,
|
||||
"lineNumber": 109,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Known DOM elements"
|
||||
|
@ -506,7 +506,7 @@
|
|||
"rule": "jQuery-prependTo(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " banner.$el.prependTo(this.$el);",
|
||||
"lineNumber": 114,
|
||||
"lineNumber": 113,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Known DOM elements"
|
||||
|
@ -515,7 +515,7 @@
|
|||
"rule": "jQuery-appendTo(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " toast.$el.appendTo(this.$el);",
|
||||
"lineNumber": 120,
|
||||
"lineNumber": 119,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Known DOM elements"
|
||||
|
@ -524,7 +524,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
||||
"lineNumber": 140,
|
||||
"lineNumber": 139,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Known DOM elements"
|
||||
|
@ -533,7 +533,7 @@
|
|||
"rule": "jQuery-append(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
||||
"lineNumber": 140,
|
||||
"lineNumber": 139,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Known DOM elements"
|
||||
|
@ -542,7 +542,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " if (e && this.$(e.target).closest('.placeholder').length) {",
|
||||
"lineNumber": 190,
|
||||
"lineNumber": 189,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Known DOM elements"
|
||||
|
@ -551,7 +551,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('#header, .gutter').addClass('inactive');",
|
||||
"lineNumber": 194,
|
||||
"lineNumber": 193,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Hardcoded selector"
|
||||
|
@ -560,7 +560,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.conversation-stack').addClass('inactive');",
|
||||
"lineNumber": 198,
|
||||
"lineNumber": 197,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Hardcoded selector"
|
||||
|
@ -569,7 +569,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.conversation:first .menu').trigger('close');",
|
||||
"lineNumber": 200,
|
||||
"lineNumber": 199,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Hardcoded selector"
|
||||
|
@ -578,7 +578,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " if (e && this.$(e.target).closest('.capture-audio').length > 0) {",
|
||||
"lineNumber": 220,
|
||||
"lineNumber": 219,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Known DOM elements"
|
||||
|
@ -587,7 +587,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.conversation:first .recorder').trigger('close');",
|
||||
"lineNumber": 223,
|
||||
"lineNumber": 222,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-10-21T22:30:15.622Z",
|
||||
"reasonDetail": "Hardcoded selector"
|
||||
|
@ -7623,7 +7623,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.js",
|
||||
"line": " this.focusRef = react_1.default.createRef();",
|
||||
"lineNumber": 31,
|
||||
"lineNumber": 32,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-11-01T22:46:33.013Z",
|
||||
"reasonDetail": "Used for setting focus only"
|
||||
|
@ -7632,7 +7632,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.tsx",
|
||||
"line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
||||
"lineNumber": 149,
|
||||
"lineNumber": 155,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-11-01T22:46:33.013Z",
|
||||
"reasonDetail": "Used for setting focus only"
|
||||
|
@ -7646,15 +7646,6 @@
|
|||
"updated": "2019-11-01T22:46:33.013Z",
|
||||
"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",
|
||||
"path": "ts/components/conversation/Timeline.js",
|
||||
|
@ -7677,11 +7668,20 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/media-gallery/MediaGallery.tsx",
|
||||
"line": " public readonly focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
||||
"lineNumber": 65,
|
||||
"lineNumber": 66,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-11-01T22:46:33.013Z",
|
||||
"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(",
|
||||
"path": "ts/shims/textsecure.js",
|
||||
|
|
Loading…
Add table
Reference in a new issue