chore: replace tslint with eslint and fix linting issues

This commit is contained in:
Audric Ackermann 2023-07-26 11:26:46 +02:00
parent 49955a3947
commit d43d6abbae
406 changed files with 2450 additions and 2479 deletions

View File

@ -1,5 +1,4 @@
build/**
components/**
dist/**
mnemonic_languages/**
@ -13,3 +12,5 @@ ts/**/*.js
playwright.config.js
preload.js
stylesheets/dist/
compiled.d.ts
.eslintrc.js

View File

@ -1,13 +1,23 @@
// For reference: https://github.com/airbnb/javascript
module.exports = {
root: true,
settings: {
'import/core-modules': ['electron'],
react: {
version: 'detect',
},
},
extends: ['airbnb-base', 'prettier'],
extends: [
'airbnb-base',
'prettier',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
plugins: ['mocha', 'more'],
plugins: ['mocha', 'more', '@typescript-eslint'],
parser: '@typescript-eslint/parser',
parserOptions: { project: ['tsconfig.json'] },
rules: {
'comma-dangle': [
@ -47,10 +57,38 @@ module.exports = {
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }],
'@typescript-eslint/no-floating-promises': ['error'],
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/array-type': ['error', { default: 'generic' }],
'@typescript-eslint/no-misused-promises': 'error',
// Prettier overrides:
'arrow-parens': 'off',
'no-nested-ternary': 'off',
'function-paren-newline': 'off',
'import/prefer-default-export': 'off',
'operator-linebreak': 'off',
'prefer-destructuring': 'off',
'max-classes-per-file': 'off',
'lines-between-class-members': 'off',
'@typescript-eslint/no-explicit-any': 'off', // to reenable later
'arrow-body-style': 'off',
'no-plusplus': 'off',
'no-continue': 'off',
'no-void': 'off',
'default-param-last': 'off',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
'class-methods-use-this': 'off',
camelcase: 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// 'no-unused-expressions': 'off',
// '@typescript-eslint/no-unused-expressions': 'error',
'max-len': [
'error',
{
@ -59,10 +97,28 @@ module.exports = {
// high value as a buffer to let Prettier control the line length:
code: 999,
// We still want to limit comments as before:
comments: 150,
comments: 200,
ignoreUrls: true,
ignoreRegExpLiterals: true,
},
],
},
overrides: [
{
files: ['*_test.ts'],
rules: {
'no-unused-expressions': 'off',
'no-await-in-loop': 'off',
'no-empty': 'off',
},
},
{
files: ['ts/state/ducks/*.tsx', 'ts/state/ducks/*.ts'],
rules: { 'no-param-reassign': ['error', { props: false }] },
},
{
files: ['ts/node/**/*.ts', 'ts/test/**/*.ts'],
rules: { 'no-console': 'off' },
},
],
};

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* global window */
const { ipcRenderer } = require('electron');

View File

@ -1,4 +1,5 @@
/* global window */
/* eslint-disable @typescript-eslint/no-var-requires */
const { ipcRenderer } = require('electron');
const url = require('url');

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
module.exports = {

View File

@ -43,7 +43,7 @@
"protobuf": "pbjs --target static-module --wrap commonjs --out ts/protobuf/compiled.js protos/*.proto && pbts --out ts/protobuf/compiled.d.ts ts/protobuf/compiled.js --force-long",
"sass": "rimraf 'stylesheets/dist/' && webpack --config=./sass.config.js",
"clean": "rimraf 'ts/**/*.js' 'ts/*.js' 'ts/*.js.map' 'ts/**/*.js.map' && rimraf tsconfig.tsbuildinfo;",
"lint-full": "yarn format-full && eslint . && tslint --format stylish --project .",
"lint-full": "yarn format-full && eslint .",
"format-full": "prettier --list-different --write \"*.{css,js,json,scss,ts,tsx}\" \"./**/*.{css,js,json,scss,ts,tsx}\"",
"integration-test": "npx playwright test",
"start-prod-test": "cross-env NODE_ENV=production NODE_APP_INSTANCE=$MULTI electron .",
@ -178,6 +178,8 @@
"@types/sinon": "9.0.4",
"@types/styled-components": "^5.1.4",
"@types/uuid": "8.3.4",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"asar": "3.1.0",
"buffer": "^6.0.3",
"chai": "^4.3.4",
@ -191,12 +193,14 @@
"electron": "25.3.0",
"electron-builder": "23.0.8",
"esbuild": "^0.14.29",
"eslint": "^8.15.0",
"eslint": "^8.45.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-mocha": "^10.1.0",
"eslint-plugin-more": "^1.0.5",
"eslint-plugin-react": "^7.33.0",
"eslint-plugin-react-hooks": "^4.6.0",
"events": "^3.3.0",
"jsdom": "^19.0.0",
"jsdom-global": "^3.0.2",
@ -219,10 +223,7 @@
"style-loader": "^3.3.1",
"ts-loader": "^9.4.2",
"ts-mock-imports": "^1.3.0",
"tslint": "5.19.0",
"tslint-microsoft-contrib": "6.0.0",
"tslint-react": "3.6.0",
"typescript": "^4.6.3",
"typescript": "^5.1.6",
"webpack": "^5.76.3",
"webpack-cli": "^5.0.1"
},

View File

@ -1,4 +1,5 @@
/* global window */
/* eslint-disable @typescript-eslint/no-var-requires */
const { ipcRenderer } = require('electron');
const url = require('url');

View File

@ -1,4 +1,5 @@
// tslint:disable-next-line: no-implicit-dependencies
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-import-module-exports */
import { PlaywrightTestConfig } from '@playwright/test';
import { toNumber } from 'lodash';

View File

@ -1,3 +1,4 @@
// eslint:disable: no-require-imports no-var-requires
const { clipboard, ipcRenderer, webFrame } = require('electron/main');
const { Storage } = require('./ts/util/storage');
@ -15,7 +16,6 @@ if (config.environment !== 'production') {
if (config.appInstance) {
title += ` - ${config.appInstance}`;
}
// tslint:disable: no-require-imports no-var-requires
window.platform = process.platform;
window.getTitle = () => title;
@ -240,7 +240,6 @@ const { getConversationController } = require('./ts/session/conversations/Conver
window.getConversationController = getConversationController;
// Linux seems to periodically let the event loop stop, so this is a global workaround
setInterval(() => {
// tslint:disable-next-line: no-empty
window.nodeSetImmediate(() => {});
}, 1000);

View File

@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable import/no-extraneous-dependencies */
const path = require('path');
// eslint-disable-next-line import/no-extraneous-dependencies
const sass = require('sass'); // Prefer `dart-sass`
// eslint-disable-next-line import/no-extraneous-dependencies
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {

View File

@ -40,7 +40,7 @@ export const AboutView = () => {
theme: window.theme,
});
}
}, [window.theme]);
}, []);
return (
<SessionTheme>

View File

@ -1,5 +1,3 @@
// tslint:disable:react-a11y-anchors
import React from 'react';
import * as GoogleChrome from '../util/GoogleChrome';

View File

@ -48,7 +48,6 @@ const StyledContent = styled.div`
`;
const DebugLogTextArea = (props: { content: string }) => {
// tslint:disable-next-line: react-a11y-input-elements
return <textarea spellCheck="false" rows={10} value={props.content} style={{ height: '100%' }} />;
};
@ -69,7 +68,6 @@ const DebugLogButtons = (props: { content: string }) => {
</div>
);
};
// tslint:disable: no-console
const DebugLogViewAndSave = () => {
const [content, setContent] = useState(window.i18n('loading'));
@ -85,6 +83,7 @@ const DebugLogViewAndSave = () => {
const debugLogWithSystemInfo = `${operatingSystemInfo} ${commitHashInfo} ${text}`;
setContent(debugLogWithSystemInfo);
})
// eslint-disable-next-line no-console
.catch(console.error);
}, []);
@ -103,7 +102,7 @@ export const DebugLogView = () => {
theme: window.theme,
});
}
}, [window.theme]);
}, []);
return (
<SessionTheme>

View File

@ -1,7 +1,8 @@
import React from 'react';
import styled from 'styled-components';
import { Avatar, AvatarSize, CrownIcon } from './avatar/Avatar';
import { useConversationUsernameOrShorten } from '../hooks/useParamSelector';
import styled from 'styled-components';
import { SessionRadio } from './basic/SessionRadio';
const AvatarContainer = styled.div`
@ -93,9 +94,9 @@ export const MemberListItem = (props: {
const memberName = useConversationUsernameOrShorten(pubkey);
return (
// tslint:disable-next-line: use-simple-attributes
<StyledSessionMemberItem
onClick={() => {
// eslint-disable-next-line no-unused-expressions
isSelected ? onUnselect?.(pubkey) : onSelect?.(pubkey);
}}
style={

View File

@ -1,10 +1,15 @@
import React, { useEffect } from 'react';
import moment from 'moment';
import React from 'react';
import { Provider } from 'react-redux';
import { LeftPane } from './leftpane/LeftPane';
// tslint:disable-next-line: no-submodule-imports
import styled from 'styled-components';
import { fromPairs, map } from 'lodash';
import { persistStore } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
import useUpdate from 'react-use/lib/useUpdate';
import useMount from 'react-use/lib/useMount';
import { LeftPane } from './leftpane/LeftPane';
// moment does not support es-419 correctly (and cause white screen on app start)
import { getConversationController } from '../session/conversations';
import { UserUtils } from '../session/utils';
import { createStore } from '../state/createStore';
@ -24,15 +29,23 @@ import { initialThemeState } from '../state/ducks/theme';
import { TimerOptionsArray } from '../state/ducks/timerOptions';
import { initialUserConfigState } from '../state/ducks/userConfig';
import { StateType } from '../state/reducer';
import { makeLookup } from '../util';
import { ExpirationTimerOptions } from '../util/expiringMessages';
import { SessionMainPanel } from './SessionMainPanel';
// moment does not support es-419 correctly (and cause white screen on app start)
import moment from 'moment';
import styled from 'styled-components';
import { SettingsKey } from '../data/settings-key';
import { getSettingsInitialState, updateAllOnStorageReady } from '../state/ducks/settings';
import { initialSogsRoomInfoState } from '../state/ducks/sogsRoomInfo';
import { useHasDeviceOutdatedSyncing } from '../state/selectors/settings';
import { Storage } from '../util/storage';
import { NoticeBanner } from './NoticeBanner';
import { Flex } from './basic/Flex';
function makeLookup<T>(items: Array<T>, key: string): { [key: string]: T } {
// Yep, we can't index into item without knowing what it is. True. But we want to.
const pairs = map(items, item => [(item as any)[key] as string, item]);
return fromPairs(pairs);
}
// Default to the locale from env. It will be overridden if moment
// does not recognize it with what moment knows which is the closest.
@ -40,15 +53,6 @@ import { Storage } from '../util/storage';
// We just need to use what we got from moment in getLocale on the updateLocale below
moment.locale((window.i18n as any).getLocale());
// Workaround: A react component's required properties are filtering up through connect()
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
import useUpdate from 'react-use/lib/useUpdate';
import { SettingsKey } from '../data/settings-key';
import { NoticeBanner } from './NoticeBanner';
import { Flex } from './basic/Flex';
import { useHasDeviceOutdatedSyncing } from '../state/selectors/settings';
import { getSettingsInitialState, updateAllOnStorageReady } from '../state/ducks/settings';
const StyledGutter = styled.div`
width: 380px !important;
transition: none;
@ -99,8 +103,8 @@ function setupLeftPane(forceUpdateInboxComponent: () => void) {
const SomeDeviceOutdatedSyncingNotice = () => {
const outdatedBannerShouldBeShown = useHasDeviceOutdatedSyncing();
const dismiss = async () => {
await Storage.put(SettingsKey.someDeviceOutdatedSyncing, false);
const dismiss = () => {
void Storage.put(SettingsKey.someDeviceOutdatedSyncing, false);
};
if (!outdatedBannerShouldBeShown) {
@ -117,9 +121,9 @@ const SomeDeviceOutdatedSyncingNotice = () => {
export const SessionInboxView = () => {
const update = useUpdate();
// run only on mount
useEffect(() => {
useMount(() => {
setupLeftPane(update);
}, []);
});
if (!window.inboxStore) {
return null;

View File

@ -1,14 +1,14 @@
import React, { useEffect } from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import autoBind from 'auto-bind';
import { isString } from 'lodash';
import { SessionButton, SessionButtonColor, SessionButtonType } from './basic/SessionButton';
import { SessionSpinner } from './basic/SessionSpinner';
import { SessionTheme } from '../themes/SessionTheme';
import { switchThemeTo } from '../themes/switchTheme';
import styled from 'styled-components';
import { ToastUtils } from '../session/utils';
import { isString } from 'lodash';
import { SessionToastContainer } from './SessionToastContainer';
import { SessionWrapperModal } from './SessionWrapperModal';
import { switchPrimaryColorTo } from '../themes/switchPrimaryColor';
@ -20,7 +20,6 @@ interface State {
}
export const MAX_LOGIN_TRIES = 3;
// tslint:disable: use-simple-attributes
const TextPleaseWait = (props: { isLoading: boolean }) => {
if (!props.isLoading) {
@ -35,7 +34,7 @@ const StyledContent = styled.div`
width: 100%;
`;
class SessionPasswordPromptInner extends React.PureComponent<{}, State> {
class SessionPasswordPromptInner extends React.PureComponent<unknown, State> {
private inputRef?: any;
constructor(props: any) {
@ -135,7 +134,9 @@ class SessionPasswordPromptInner extends React.PureComponent<{}, State> {
// this is to make sure a render has the time to happen before we lock the thread with all of the db work
// this might be removed once we get the db operations to a worker thread
global.setTimeout(() => this.onLogin(passPhrase), 100);
global.setTimeout(() => {
void this.onLogin(passPhrase);
}, 100);
}
private initClearDataView() {
@ -203,7 +204,7 @@ export const SessionPasswordPrompt = () => {
if (window.primaryColor) {
void switchPrimaryColorTo(window.primaryColor);
}
}, [window.theme, window.primaryColor]);
}, []);
return (
<SessionTheme>

View File

@ -29,7 +29,6 @@ const SessionToastContainerPrivate = () => {
);
};
// tslint:disable-next-line: no-default-export
export const SessionToastContainer = styled(SessionToastContainerPrivate).attrs({
// custom props
})`

View File

@ -1,10 +1,9 @@
import React, { useRef } from 'react';
import classNames from 'classnames';
import { SessionIconButton } from './icon/';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { SessionIconButton } from './icon';
import { SessionButton, SessionButtonColor, SessionButtonType } from './basic/SessionButton';
export type SessionWrapperModalType = {
@ -65,7 +64,7 @@ export const SessionWrapperModal = (props: SessionWrapperModalType) => {
return (
<div
className={classNames('loki-dialog modal', additionalClassName ? additionalClassName : null)}
className={classNames('loki-dialog modal', additionalClassName || null)}
onClick={handleClick}
role="dialog"
>

View File

@ -52,7 +52,7 @@ const TopSplitViewPanel = ({
React.useEffect(() => {
if (topRef.current) {
if (!topHeight) {
setTopHeight(Math.max(MIN_HEIGHT_TOP, topRef.current?.clientHeight / 2));
setTopHeight(Math.max(MIN_HEIGHT_TOP, (topRef.current?.clientHeight || 0) / 2));
return;
}

View File

@ -2,6 +2,8 @@ import classNames from 'classnames';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import { isEqual } from 'lodash';
import { useDisableDrag } from '../../hooks/useDisableDrag';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import {
@ -13,7 +15,6 @@ import { isMessageSelectionMode } from '../../state/selectors/conversations';
import { SessionIcon } from '../icon';
import { AvatarPlaceHolder } from './AvatarPlaceHolder/AvatarPlaceHolder';
import { ClosedGroupAvatar } from './AvatarPlaceHolder/ClosedGroupAvatar';
import { isEqual } from 'lodash';
export enum AvatarSize {
XS = 28,
@ -99,7 +100,6 @@ const AvatarImage = (
}
const dataToDisplay = base64Data ? `data:image/jpeg;base64,${base64Data}` : avatarPath;
// tslint:disable: react-a11y-img-has-alt
return (
<img
onError={handleImageError}
@ -165,7 +165,6 @@ const AvatarInner = (props: Props) => {
data-testid={dataTestId}
>
{hasImage ? (
// tslint:disable-next-line: use-simple-attributes
<AvatarImage
avatarPath={urlToLoad}
base64Data={base64Data}

View File

@ -14,9 +14,8 @@ const sha512FromPubkeyOneAtAtime = async (pubkey: string) => {
return allowOnlyOneAtATime(`sha512FromPubkey-${pubkey}`, async () => {
const buf = await crypto.subtle.digest('SHA-512', new TextEncoder().encode(pubkey));
// tslint:disable: prefer-template restrict-plus-operands
return Array.prototype.map
.call(new Uint8Array(buf), (x: any) => ('00' + x.toString(16)).slice(-2))
.call(new Uint8Array(buf), (x: any) => `00${x.toString(16)}`.slice(-2))
.join('');
});
};
@ -37,7 +36,7 @@ function useHashBasedOnPubkey(pubkey: string) {
if (cachedHash) {
setHash(cachedHash);
setIsLoading(false);
return;
return undefined;
}
setIsLoading(true);
let isInProgress = true;
@ -48,9 +47,10 @@ function useHashBasedOnPubkey(pubkey: string) {
setHash(undefined);
}
return;
return undefined;
}
// eslint-disable-next-line more/no-then
void sha512FromPubkeyOneAtAtime(pubkey).then(sha => {
if (isInProgress) {
setIsLoading(false);

View File

@ -1,7 +1,8 @@
import React from 'react';
import { isEmpty } from 'lodash';
import { assertUnreachable } from '../../../types/sqlSharedTypes';
import { Avatar, AvatarSize } from '../Avatar';
import { isEmpty } from 'lodash';
import { useIsClosedGroup, useSortedGroupMembers } from '../../../hooks/useParamSelector';
import { UserUtils } from '../../../session/utils';
@ -22,6 +23,7 @@ function getClosedGroupAvatarsSize(size: AvatarSize): AvatarSize {
return AvatarSize.XL;
default:
assertUnreachable(size, `Invalid size request for closed group avatar "${size}"`);
return AvatarSize.XL; // just to make eslint happy
}
}

View File

@ -5,7 +5,7 @@ export interface FlexProps {
className?: string;
container?: boolean;
dataTestId?: string;
/****** Container Props ********/
// Container Props
flexDirection?: 'row' | 'column';
justifyContent?:
| 'flex-start'
@ -24,11 +24,11 @@ export interface FlexProps {
| 'baseline'
| 'initial'
| 'inherit';
/****** Child Props ********/
// Child Props
flexGrow?: number;
flexShrink?: number;
flexBasis?: number;
/****** Common Layout Props ********/
// Common Layout Props
padding?: string;
margin?: string;
width?: string;

View File

@ -85,7 +85,6 @@ export const MessageBodyHighlight = (props: { text: string; isGroup: boolean })
</SnippetHighlight>
);
// @ts-ignore
last = FIND_BEGIN_END.lastIndex;
match = FIND_BEGIN_END.exec(text);
}

View File

@ -1,17 +1,14 @@
import React from 'react';
import DOMPurify from 'dompurify';
interface ReceivedProps {
type ReceivedProps = {
html: string;
tag?: string;
key?: any;
className?: string;
}
};
// Needed because of https://github.com/microsoft/tslint-microsoft-contrib/issues/339
type Props = ReceivedProps;
export const SessionHtmlRenderer: React.SFC<Props> = ({ tag = 'div', key, html, className }) => {
export const SessionHtmlRenderer = ({ tag = 'div', key, html, className }: ReceivedProps) => {
const clean = DOMPurify.sanitize(html, {
USE_PROFILES: { html: true },
FORBID_ATTR: ['script'],
@ -20,7 +17,7 @@ export const SessionHtmlRenderer: React.SFC<Props> = ({ tag = 'div', key, html,
return React.createElement(tag, {
key,
className,
// tslint:disable-next-line: react-no-dangerous-html
dangerouslySetInnerHTML: { __html: clean },
});
};

View File

@ -39,8 +39,8 @@ export const SessionIdEditable = (props: Props) => {
function handleKeyDown(e: KeyboardEvent<HTMLTextAreaElement>) {
if (editable && e.key === 'Enter') {
e.preventDefault();
// tslint:disable-next-line: no-unused-expression
onPressEnter && onPressEnter();
onPressEnter?.();
}
}

View File

@ -1,7 +1,6 @@
import React, { ChangeEvent } from 'react';
import styled from 'styled-components';
import { Flex } from '../basic/Flex';
// tslint:disable: react-unused-props-and-state
import { Flex } from './Flex';
type Props = {
label: string;
@ -27,7 +26,6 @@ const StyledInput = styled.input<{
background: ${props => (props.selectedColor ? props.selectedColor : 'var(--primary-color)')};
}
`;
// tslint:disable: use-simple-attributes
const StyledLabel = styled.label<{
filledSize: number;

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import useMount from 'react-use/lib/useMount';
import styled, { CSSProperties } from 'styled-components';
import { SessionRadio } from './SessionRadio';
interface Props {
// tslint:disable: react-unused-props-and-state
initialItem: string;
items: Array<{ value: string; label: string }>;
group: string;
@ -32,9 +32,9 @@ export const SessionRadioGroup = (props: Props) => {
const { items, group, initialItem, style } = props;
const [activeItem, setActiveItem] = useState('');
useEffect(() => {
useMount(() => {
setActiveItem(initialItem);
}, []);
});
return (
<StyledFieldSet id={group} style={style}>

View File

@ -1,9 +1,10 @@
import React from 'react';
import { Flex } from '../basic/Flex';
import styled from 'styled-components';
import { SessionIcon, SessionIconType } from '../icon';
import { noop } from 'lodash';
import React from 'react';
import styled from 'styled-components';
import { Flex } from './Flex';
import { SessionIcon, SessionIconType } from '../icon';
// NOTE We don't change the color strip on the left based on the type. 16/09/2022
export enum SessionToastType {
@ -45,12 +46,10 @@ const IconDiv = styled.div`
margin: 0 var(--margins-xs);
`;
// tslint:disable: use-simple-attributes
export const SessionToast = (props: Props) => {
const { title, description, type, icon } = props;
const toastDesc = description ? description : '';
const toastDesc = description || '';
const toastIconSize = toastDesc ? 'huge' : 'medium';
// Set a custom icon or allow the theme to define the icon
@ -77,7 +76,6 @@ export const SessionToast = (props: Props) => {
const onToastClick = props?.onToastClick || noop;
return (
// tslint:disable-next-line: use-simple-attributes
<Flex
container={true}
alignItems="center"

View File

@ -1,7 +1,7 @@
import React from 'react';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
const StyledKnob = styled.div<{ active: boolean }>`
position: absolute;

View File

@ -4,7 +4,6 @@ import styled from 'styled-components';
import { resetOverlayMode, setOverlayMode } from '../../state/ducks/section';
import { getOverlayMode } from '../../state/selectors/section';
import { SessionIcon } from '../icon';
// tslint:disable: use-simple-attributes
const StyledMenuButton = styled.button`
position: relative;

View File

@ -1,13 +1,14 @@
import { SessionIconButton } from '../icon';
import React, { useEffect, useState } from 'react';
import { animation, contextMenu, Item, Menu } from 'react-contexify';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { CallManager, ToastUtils } from '../../session/utils';
import { InputItem } from '../../session/utils/calling/CallManager';
import { setFullScreenCall } from '../../state/ducks/call';
import { CallManager, ToastUtils } from '../../session/utils';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getHasOngoingCallWithPubkey } from '../../state/selectors/call';
import { SessionIconButton } from '../icon';
import { DropDownAndToggleButton } from '../icon/DropDownAndToggleButton';
import styled from 'styled-components';
import { SessionContextMenuContainer } from '../SessionContextMenuContainer';
const VideoInputMenu = ({
@ -281,7 +282,7 @@ export const HangUpButton = ({ isFullScreen }: { isFullScreen: boolean }) => {
iconType="hangup"
iconColor="var(--danger-color)"
borderRadius="50%"
onClick={handleEndCall}
onClick={() => void handleEndCall()}
margin="10px"
dataTestId="end-call"
/>

View File

@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import styled from 'styled-components';
import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
@ -68,7 +68,7 @@ export const CallInFullScreenContainer = () => {
if (remoteStreamVideoIsMuted) {
dispatch(setFullScreenCall(false));
}
}, [remoteStreamVideoIsMuted]);
}, [remoteStreamVideoIsMuted, dispatch]);
if (!ongoingCallWithFocused || !hasOngoingCallFullScreen) {
return null;

View File

@ -76,14 +76,13 @@ export const DraggableCallContainer = () => {
);
const videoRefRemote = useRef<HTMLVideoElement>(null);
function onWindowResize() {
if (positionY + 50 > window.innerHeight || positionX + 50 > window.innerWidth) {
setPositionX(window.innerWidth / 2);
setPositionY(window.innerHeight / 2);
}
}
useEffect(() => {
function onWindowResize() {
if (positionY + 50 > window.innerHeight || positionX + 50 > window.innerWidth) {
setPositionX(window.innerWidth / 2);
setPositionY(window.innerHeight / 2);
}
}
window.addEventListener('resize', onWindowResize);
return () => {

View File

@ -2,6 +2,8 @@ import { useSelector } from 'react-redux';
import React, { useRef, useState } from 'react';
import styled from 'styled-components';
import useInterval from 'react-use/lib/useInterval';
import moment from 'moment';
import { CallManager, UserUtils } from '../../session/utils';
import {
getCallIsInFullScreen,
@ -18,9 +20,7 @@ import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
import { useModuloWithTripleDots } from '../../hooks/useModuloWithTripleDots';
import { CallWindowControls } from './CallButtons';
import { DEVICE_DISABLED_DEVICE_ID } from '../../session/utils/calling/CallManager';
// tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
import moment from 'moment';
import { SessionSpinner } from '../basic/SessionSpinner';
const VideoContainer = styled.div`
@ -112,7 +112,6 @@ const DurationLabel = () => {
const ms = callDuration * 1000;
const d = moment.duration(ms);
// tslint:disable-next-line: restrict-plus-operands
const dateString = Math.floor(d.asHours()) + moment.utc(ms).format(':mm:ss');
return <StyledCenteredLabel>{dateString}</StyledCenteredLabel>;
};
@ -135,7 +134,6 @@ export const VideoLoadingSpinner = (props: { fullWidth: boolean }) => {
);
};
// tslint:disable-next-line: max-func-body-length
export const InConversationCallContainer = () => {
const isInFullScreen = useSelector(getCallIsInFullScreen);

View File

@ -35,14 +35,14 @@ export const IncomingCallDialog = () => {
useEffect(() => {
let timeout: NodeJS.Timeout;
if (incomingCallFromPubkey) {
timeout = global.setTimeout(async () => {
timeout = global.setTimeout(() => {
if (incomingCallFromPubkey) {
window.log.info(
`call missed with ${ed25519Str(
incomingCallFromPubkey
)} as the dialog was not interacted with for ${callTimeoutMs} ms`
);
await CallManager.USER_rejectIncomingCallRequest(incomingCallFromPubkey);
void CallManager.USER_rejectIncomingCallRequest(incomingCallFromPubkey);
}
}, callTimeoutMs);
}
@ -54,7 +54,7 @@ export const IncomingCallDialog = () => {
};
}, [incomingCallFromPubkey]);
//#region input handlers
// #region input handlers
const handleAcceptIncomingCall = async () => {
if (incomingCallFromPubkey) {
await CallManager.USER_acceptIncomingCallRequest(incomingCallFromPubkey);

View File

@ -1,9 +1,9 @@
import React from 'react';
import styled from 'styled-components';
import { RenderTextCallbackType } from '../../types/Util';
import { PubKey } from '../../session/types';
import { getConversationController } from '../../session/conversations';
import React from 'react';
import { isUsAnySogsFromCache } from '../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import styled from 'styled-components';
interface MentionProps {
key: string;

View File

@ -9,14 +9,14 @@ type Props = {
name?: string | null;
profileName?: string | null;
module?: string;
boldProfileName?: Boolean;
compact?: Boolean;
shouldShowPubkey: Boolean;
boldProfileName?: boolean;
compact?: boolean;
shouldShowPubkey: boolean;
};
export const ContactName = (props: Props) => {
const { pubkey, name, profileName, module, boldProfileName, compact, shouldShowPubkey } = props;
const prefix = module ? module : 'module-contact-name';
const prefix = module || 'module-contact-name';
const convoName = useConversationUsernameOrShorten(pubkey);
const isPrivate = useIsPrivate(pubkey);

View File

@ -1,11 +1,11 @@
import React from 'react';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { contextMenu } from 'react-contexify';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { ConversationNotificationSettingType } from '../../models/conversationAttributes';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import {
getSelectedMessageIds,
isMessageDetailView,

View File

@ -1,10 +1,9 @@
import React, { useCallback, useState } from 'react';
import { getTimerBucketIcon } from '../../util/timer';
// tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
import styled from 'styled-components';
import { getTimerBucketIcon } from '../../util/timer';
import { SessionIcon } from '../icon/SessionIcon';
type Props = {

View File

@ -175,7 +175,7 @@ export const AudioPlayerWithEncryptedFile = (props: {
useEffect(() => {
if (messageId !== undefined && messageId === nextMessageToPlayId) {
player.current?.audio.current?.play();
void player.current?.audio.current?.play();
}
}, [messageId, nextMessageToPlayId, player]);

View File

@ -1,11 +1,11 @@
import React, { useCallback } from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
import { Spinner } from '../basic/Spinner';
import { AttachmentType, AttachmentTypeWithPath } from '../../types/Attachment';
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
import { useDisableDrag } from '../../hooks/useDisableDrag';
import styled from 'styled-components';
type Props = {
alt: string;
@ -41,7 +41,6 @@ const StyledOverlay = styled.div<Pick<Props, 'darkOverlay' | 'softCorners'>>`
props.darkOverlay ? 'var(--message-link-preview-background-color)' : 'unset'};
`;
export const Image = (props: Props) => {
// tslint:disable-next-line max-func-body-length cyclomatic-complexity
const {
alt,
attachment,
@ -64,7 +63,6 @@ export const Image = (props: Props) => {
if (url && onError) {
onError();
}
return;
}, [url, onError]);
const disableDrag = useDisableDrag();

View File

@ -1,4 +1,5 @@
import React, { useContext } from 'react';
import styled from 'styled-components';
import {
areAllAttachmentsVisual,
@ -11,7 +12,6 @@ import {
import { Image } from './Image';
import { IsMessageVisibleContext } from './message/message-content/MessageContent';
import styled from 'styled-components';
import { THUMBNAIL_SIDE } from '../../types/attachments/VisualAttachment';
type Props = {
@ -26,7 +26,6 @@ const StyledImageGrid = styled.div<{ flexDirection: 'row' | 'column' }>`
gap: var(--margins-sm);
flex-direction: ${props => props.flexDirection};
`;
// tslint:disable: cyclomatic-complexity max-func-body-length use-simple-attributes
const Row = (
props: Props & { renderedSize: number; startIndex: number; totalAttachmentsCount: number }

View File

@ -2,8 +2,10 @@ import _ from 'lodash';
import React from 'react';
import autoBind from 'auto-bind';
import { blobToArrayBuffer } from 'blob-util';
import loadImage from 'blueimp-load-image';
import classNames from 'classnames';
import styled from 'styled-components';
import {
CompositionBox,
SendMessageType,
@ -12,14 +14,10 @@ import {
import { perfEnd, perfStart } from '../../session/utils/Performance';
const DEFAULT_JPEG_QUALITY = 0.85;
import { SessionMessagesListContainer } from './SessionMessagesListContainer';
import { SessionFileDropzone } from './SessionFileDropzone';
import { blobToArrayBuffer } from 'blob-util';
import loadImage from 'blueimp-load-image';
import { Data } from '../../data/data';
import { markAllReadByConvoId } from '../../interactions/conversationInteractions';
import { MAX_ATTACHMENT_FILESIZE_BYTES } from '../../session/constants';
@ -55,9 +53,9 @@ import { SessionRightPanelWithDetails } from './SessionRightPanel';
import { NoMessageInConversation } from './SubtleNotification';
import { MessageDetail } from './message/message-item/MessageDetail';
import styled from 'styled-components';
import { SessionSpinner } from '../basic/SessionSpinner';
// tslint:disable: jsx-curly-spacing
const DEFAULT_JPEG_QUALITY = 0.85;
interface State {
isDraggingFile: boolean;
@ -289,6 +287,7 @@ export class SessionConversation extends React.Component<Props, State> {
<CompositionBox
sendMessage={this.sendMessageFn}
stagedAttachments={this.props.stagedAttachments}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onChoseAttachments={this.onChoseAttachments}
/>
</div>
@ -360,13 +359,12 @@ export class SessionConversation extends React.Component<Props, State> {
return;
}
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < attachmentsFileList.length; i++) {
// eslint-disable-next-line no-await-in-loop
await this.maybeAddAttachment(attachmentsFileList[i]);
}
}
// tslint:disable: max-func-body-length cyclomatic-complexity
private async maybeAddAttachment(file: any) {
if (!file) {
return;

View File

@ -1,7 +1,6 @@
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import styled from 'styled-components';
// @ts-ignore
import Picker from '@emoji-mart/react';
import { useSelector } from 'react-redux';
import { getTheme, isDarkTheme } from '../../state/selectors/theme';
@ -12,6 +11,7 @@ import {
PrimaryColorStateType,
THEMES,
ThemeStateType,
// eslint-disable-next-line import/extensions
} from '../../themes/constants/colors.js';
import { hexColorToRGB } from '../../util/hexColorToRGB';
import { getPrimaryColor } from '../../state/selectors/primaryColor';
@ -97,6 +97,7 @@ const pickerProps: FixedPickerProps = {
skinTonePosition: 'preview',
};
// eslint-disable-next-line react/display-name
export const SessionEmojiPanel = forwardRef<HTMLDivElement, Props>((props: Props, ref) => {
const { onEmojiClicked, show, isModal = false, onKeyDown } = props;
const primaryColor = useSelector(getPrimaryColor);
@ -109,11 +110,10 @@ export const SessionEmojiPanel = forwardRef<HTMLDivElement, Props>((props: Props
switch (theme) {
case 'ocean-dark':
panelBackgroundRGB = hexColorToRGB(THEMES.OCEAN_DARK.COLOR1);
// tslint:disable: no-non-null-assertion
panelTextRGB = hexColorToRGB(THEMES.OCEAN_DARK.COLOR7!);
break;
case 'ocean-light':
// tslint:disable: no-non-null-assertion
panelBackgroundRGB = hexColorToRGB(THEMES.OCEAN_LIGHT.COLOR7!);
panelTextRGB = hexColorToRGB(THEMES.OCEAN_LIGHT.COLOR1);
break;

View File

@ -1,6 +1,6 @@
import React, { useLayoutEffect, useState } from 'react';
import { useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import {
PropsForDataExtractionNotification,

View File

@ -1,7 +1,6 @@
import React from 'react';
import { contextMenu } from 'react-contexify';
import { SessionScrollButton } from '../SessionScrollButton';
import { connect } from 'react-redux';
@ -14,6 +13,8 @@ import {
resetOldTopMessageId,
SortedMessageModelProps,
} from '../../state/ducks/conversations';
import { SessionScrollButton } from '../SessionScrollButton';
import { StateType } from '../../state/reducer';
import {
getQuotedMessageToAnimate,
@ -38,7 +39,6 @@ export type ScrollToLoadedReasons =
| 'load-more-bottom';
export const ScrollToLoadedMessageContext = React.createContext(
// tslint:disable-next-line: no-empty
(_loadedMessageIdToScrollTo: string, _reason: ScrollToLoadedReasons) => {}
);
@ -51,7 +51,7 @@ type Props = SessionMessageListProps & {
scrollToNow: () => Promise<void>;
};
const StyledMessagesContainer = styled.div<{}>`
const StyledMessagesContainer = styled.div`
display: flex;
flex-grow: 1;
gap: var(--margins-xxs);
@ -144,7 +144,7 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
</ScrollToLoadedMessageContext.Provider>
<SessionScrollButton
onClickScrollBottom={this.props.scrollToNow}
onClickScrollBottom={() => void this.props.scrollToNow()}
key="scroll-down-button"
/>
</StyledMessagesContainer>
@ -250,7 +250,6 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
return;
}
// tslint:disable-next-line: restrict-plus-operands
messageContainer.scrollBy({
top: Math.floor(+messageContainer.clientHeight * 2) / 3,
behavior: 'smooth',

View File

@ -1,15 +1,16 @@
import React from 'react';
import { SessionIcon, SessionIconButton } from '../icon';
import styled from 'styled-components';
import { useDispatch, useSelector } from 'react-redux';
import useKey from 'react-use/lib/useKey';
import { SessionIcon, SessionIconButton } from '../icon';
import { quoteMessage } from '../../state/ducks/conversations';
import { getQuotedMessage } from '../../state/selectors/conversations';
import { getAlt, isAudio } from '../../types/Attachment';
import { AUDIO_MP3 } from '../../types/MIME';
import { Flex } from '../basic/Flex';
import { Image } from '../../../ts/components/conversation/Image';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { Image } from './Image';
import { getAbsoluteAttachmentPath } from '../../types/MessageAttachment';
import { GoogleChrome } from '../../util';
import { findAndFormatContact } from '../../models/message';

View File

@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import React from 'react';
import classNames from 'classnames';
import moment from 'moment';
import { SessionIconButton } from '../icon';
import autoBind from 'auto-bind';
import MicRecorder from 'mic-recorder-to-mp3';
import styled from 'styled-components';
import { SessionIconButton } from '../icon';
import { Constants } from '../../session';
import { ToastUtils } from '../../session/utils';
import { MAX_ATTACHMENT_FILESIZE_BYTES } from '../../session/constants';
@ -88,7 +89,6 @@ export class SessionRecording extends React.Component<Props, State> {
}
}
// tslint:disable-next-line: cyclomatic-complexity
public render() {
const { isPlaying, isPaused, isRecording, startTimestamp, nowTimestamp } = this.state;
@ -103,14 +103,12 @@ export class SessionRecording extends React.Component<Props, State> {
// otherwise display 0
const displayTimeMs = isRecording
? (nowTimestamp - startTimestamp) * 1000
: (this.audioElement &&
(this.audioElement?.currentTime * 1000 || this.audioElement?.duration)) ||
: (this.audioElement?.currentTime &&
(this.audioElement.currentTime * 1000 || this.audioElement?.duration)) ||
0;
const displayTimeString = moment.utc(displayTimeMs).format('m:ss');
const recordingDurationMs = this.audioElement?.duration
? this.audioElement?.duration * 1000
: 1;
const recordingDurationMs = this.audioElement?.duration ? this.audioElement.duration * 1000 : 1;
let remainingTimeString = '';
if (recordingDurationMs !== undefined) {
@ -287,6 +285,7 @@ export class SessionRecording extends React.Component<Props, State> {
this.recorder = new MicRecorder({
bitRate: 128,
});
// eslint-disable-next-line more/no-then
this.recorder
.start()
.then(() => {
@ -304,6 +303,7 @@ export class SessionRecording extends React.Component<Props, State> {
if (!this.recorder) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, blob] = await this.recorder.stop().getMp3();
this.recorder = undefined;

View File

@ -1,11 +1,12 @@
import _ from 'lodash';
import { compact, flatten } from 'lodash';
import React, { useEffect, useState } from 'react';
import { SessionIconButton } from '../icon';
// tslint:disable-next-line: no-submodule-imports
import { useDispatch, useSelector } from 'react-redux';
import useInterval from 'react-use/lib/useInterval';
import styled from 'styled-components';
import { Data } from '../../data/data';
import { SessionIconButton } from '../icon';
import {
deleteAllMessagesByConvoIdWithConfirmation,
setDisappearingMessagesByConvoId,
@ -58,7 +59,7 @@ async function getMediaGalleryProps(
Constants.CONVERSATION.DEFAULT_DOCUMENTS_FETCH_COUNT
);
const media = _.flatten(
const media = flatten(
rawMedia.map(attributes => {
const { attachments, source, id, timestamp, serverTimestamp, received_at } = attributes;
@ -110,7 +111,7 @@ async function getMediaGalleryProps(
return {
media,
documents: _.compact(documents), // remove null
documents: compact(documents), // remove null
};
}
@ -201,8 +202,6 @@ const StyledName = styled.h4`
font-size: var(--font-size-md);
`;
// tslint:disable: cyclomatic-complexity
// tslint:disable: max-func-body-length
export const SessionRightPanelWithDetails = () => {
const [documents, setDocuments] = useState<Array<MediaItemType>>([]);
const [media, setMedia] = useState<Array<MediaItemType>>([]);
@ -224,22 +223,17 @@ export const SessionRightPanelWithDetails = () => {
let isRunning = true;
if (isShowing && selectedConvoKey) {
// eslint-disable-next-line more/no-then
void getMediaGalleryProps(selectedConvoKey).then(results => {
if (isRunning) {
if (!_.isEqual(documents, results.documents)) {
setDocuments(results.documents);
}
if (!_.isEqual(media, results.media)) {
setMedia(results.media);
}
setDocuments(results.documents);
setMedia(results.media);
}
});
}
return () => {
isRunning = false;
return;
};
}, [isShowing, selectedConvoKey]);
@ -253,10 +247,6 @@ export const SessionRightPanelWithDetails = () => {
}
}, 10000);
if (!selectedConvoKey) {
return null;
}
const showMemberCount = !!(subscriberCount && subscriberCount > 0);
const commonNoShow = isKickedFromGroup || left || isBlocked || !isActive;
const hasDisappearingMessages = !isPublic && !commonNoShow;
@ -270,6 +260,9 @@ export const SessionRightPanelWithDetails = () => {
const timerOptions = useSelector(getTimerOptions).timerOptions;
if (!selectedConvoKey) {
return null;
}
const disappearingMessagesOptions = timerOptions.map(option => {
return {
content: option.name,
@ -308,8 +301,8 @@ export const SessionRightPanelWithDetails = () => {
<StyledGroupSettingsItem
className="group-settings-item"
role="button"
onClick={async () => {
await showUpdateGroupNameByConvoId(selectedConvoKey);
onClick={() => {
void showUpdateGroupNameByConvoId(selectedConvoKey);
}}
>
{isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')}
@ -342,8 +335,8 @@ export const SessionRightPanelWithDetails = () => {
<StyledGroupSettingsItem
className="group-settings-item"
role="button"
onClick={async () => {
await showUpdateGroupMembersByConvoId(selectedConvoKey);
onClick={() => {
void showUpdateGroupMembersByConvoId(selectedConvoKey);
}}
>
{window.i18n('groupMembers')}
@ -360,7 +353,6 @@ export const SessionRightPanelWithDetails = () => {
<MediaGallery documents={documents} media={media} />
{isGroup && (
// tslint:disable-next-line: use-simple-attributes
<StyledLeaveButton>
<SessionButton
text={leaveGroupString}

View File

@ -1,7 +1,9 @@
import React from 'react';
import { StagedLinkPreviewData } from './composition/CompositionBox';
// eslint-disable-next-line import/no-named-default
import { default as insecureNodeFetch } from 'node-fetch';
import { AbortSignal } from 'abort-controller';
import { StagedLinkPreviewData } from './composition/CompositionBox';
import { arrayBufferFromFile } from '../../types/Attachment';
import { AttachmentUtil, LinkPreviewUtil } from '../../util';
import { fetchLinkPreviewImage } from '../../util/linkPreviewFetch';
@ -52,7 +54,7 @@ export const getPreview = async (
let image;
if (imageHref && LinkPreviews.isLinkSafeToPreview(imageHref)) {
let objectUrl: void | string;
let objectUrl: undefined | string;
try {
window?.log?.info('insecureNodeFetch => plaintext for getPreview()');

View File

@ -1,4 +1,5 @@
import React from 'react';
import styled from 'styled-components';
import { Image } from './Image';
@ -6,7 +7,6 @@ import { SessionSpinner } from '../basic/SessionSpinner';
import { StagedLinkPreviewImage } from './composition/CompositionBox';
import { isImage } from '../../types/MIME';
import { fromArrayBufferToBase64 } from '../../session/utils/String';
import styled from 'styled-components';
import { Flex } from '../basic/Flex';
import { SessionIconButton } from '../icon';

View File

@ -1,6 +1,5 @@
import React, { MouseEvent } from 'react';
import styled from 'styled-components';
// tslint:disable: react-unused-props-and-state
interface Props {
onClick: (e: MouseEvent<HTMLDivElement>) => void;

View File

@ -1,7 +1,6 @@
import React from 'react';
import moment from 'moment';
// tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
import styled from 'styled-components';
import useUpdate from 'react-use/lib/useUpdate';

View File

@ -1,7 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { TypingAnimation } from './TypingAnimation';
import styled from 'styled-components';
import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { useSelectedIsGroup } from '../../state/selectors/selectedConversation';

View File

@ -1,30 +1,18 @@
import React from 'react';
import _, { debounce, isEmpty } from 'lodash';
import React from 'react';
import { connect } from 'react-redux';
import styled from 'styled-components';
import { AbortController } from 'abort-controller';
import { Mention, MentionsInput, SuggestionDataItem } from 'react-mentions';
import autoBind from 'auto-bind';
import * as MIME from '../../../types/MIME';
import { SessionEmojiPanel, StyledEmojiPanel } from '../SessionEmojiPanel';
import { SessionRecording } from '../SessionRecording';
import {
getPreview,
LINK_PREVIEW_TIMEOUT,
SessionStagedLinkPreview,
} from '../SessionStagedLinkPreview';
import { AbortController } from 'abort-controller';
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
import { Mention, MentionsInput, SuggestionDataItem } from 'react-mentions';
import autoBind from 'auto-bind';
import { getMediaPermissionsSettings } from '../../settings/SessionSettings';
import { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts';
import {
AddStagedAttachmentButton,
SendMessageButton,
StartRecordingButton,
ToggleEmojiButton,
} from './CompositionButtons';
import { AttachmentType } from '../../../types/Attachment';
import { connect } from 'react-redux';
import { SettingsKey } from '../../../data/settings-key';
import { showLinkSharingConfirmationModalDialog } from '../../../interactions/conversationInteractions';
import { getConversationController } from '../../../session/conversations';
import { ToastUtils } from '../../../session/utils';
@ -36,30 +24,43 @@ import {
getQuotedMessage,
getSelectedConversation,
} from '../../../state/selectors/conversations';
import { AttachmentUtil } from '../../../util';
import { Flex } from '../../basic/Flex';
import { CaptionEditor } from '../../CaptionEditor';
import { StagedAttachmentList } from '../StagedAttachmentList';
import {
getSelectedCanWrite,
getSelectedConversationKey,
} from '../../../state/selectors/selectedConversation';
import { AttachmentType } from '../../../types/Attachment';
import { processNewAttachment } from '../../../types/MessageAttachment';
import { FixedBaseEmoji } from '../../../types/Reaction';
import { AttachmentUtil } from '../../../util';
import {
StagedAttachmentImportedType,
StagedPreviewImportedType,
} from '../../../util/attachmentsUtil';
import { LinkPreviews } from '../../../util/linkPreviews';
import { Flex } from '../../basic/Flex';
import { CaptionEditor } from '../../CaptionEditor';
import { getMediaPermissionsSettings } from '../../settings/SessionSettings';
import { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts';
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
import {
getPreview,
LINK_PREVIEW_TIMEOUT,
SessionStagedLinkPreview,
} from '../SessionStagedLinkPreview';
import { StagedAttachmentList } from '../StagedAttachmentList';
import {
AddStagedAttachmentButton,
SendMessageButton,
StartRecordingButton,
ToggleEmojiButton,
} from './CompositionButtons';
import { renderEmojiQuickResultRow, searchEmojiForQuery } from './EmojiQuickResult';
import {
cleanMentions,
mentionsRegex,
renderUserMentionRow,
styleForCompositionBoxSuggestions,
} from './UserMentions';
import { renderEmojiQuickResultRow, searchEmojiForQuery } from './EmojiQuickResult';
import { LinkPreviews } from '../../../util/linkPreviews';
import styled from 'styled-components';
import { FixedBaseEmoji } from '../../../types/Reaction';
import {
getSelectedCanWrite,
getSelectedConversationKey,
} from '../../../state/selectors/selectedConversation';
import { SettingsKey } from '../../../data/settings-key';
export interface ReplyingToMessageProps {
convoId: string;
@ -175,7 +176,7 @@ const getSelectionBasedOnMentions = (draft: string, index: number) => {
lastMatchEndIndex = currentMatchStartIndex + match.length;
const realLength = displayName.length + 1;
lastRealMatchEndIndex = lastRealMatchEndIndex + realLength;
lastRealMatchEndIndex += realLength;
// the +1 is for the @
return {
@ -348,6 +349,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
}
const { items } = e.clipboardData;
let imgBlob = null;
// eslint-disable-next-line no-restricted-syntax
for (const item of items as any) {
const pasteType = item.type.split('/')[0];
if (pasteType === 'image') {
@ -402,7 +404,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
return (
<SessionRecording
sendVoiceMessage={this.sendVoiceMessage}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onLoadVoiceNoteView={() => void this.onLoadVoiceNoteView()}
onExitVoiceNoteView={this.onExitVoiceNoteView}
/>
);
@ -422,10 +424,10 @@ class CompositionBoxInner extends React.Component<Props, State> {
multiple={true}
ref={this.fileInput}
type="file"
onChange={this.onChoseAttachment}
onChange={() => void this.onChoseAttachment()}
/>
{typingEnabled && <StartRecordingButton onClick={this.onLoadVoiceNoteView} />}
{typingEnabled && <StartRecordingButton onClick={() => void this.onLoadVoiceNoteView()} />}
<StyledSendMessageInput
role="main"
@ -441,7 +443,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
{typingEnabled && (
<ToggleEmojiButton ref={this.emojiPanelButton} onClick={this.toggleEmojiPanel} />
)}
<SendMessageButton onClick={this.onSendMessage} />
<SendMessageButton onClick={() => void this.onSendMessage()} />
{typingEnabled && showEmojiPanel && (
<StyledEmojiPanelContainer role="button">
@ -449,7 +451,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
ref={this.emojiPanel}
show={showEmojiPanel}
onEmojiClicked={this.onEmojiClick}
onKeyDown={this.onKeyDown}
onKeyDown={e => void this.onKeyDown(e)}
/>
</StyledEmojiPanelContainer>
)}
@ -490,8 +492,8 @@ class CompositionBoxInner extends React.Component<Props, State> {
<MentionsInput
value={draft}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
onKeyDown={e => void this.onKeyDown(e)}
onKeyUp={() => void this.onKeyUp()}
placeholder={messagePlaceHolder}
spellCheck={true}
inputRef={this.textarea}
@ -554,14 +556,16 @@ class CompositionBoxInner extends React.Component<Props, State> {
return;
}
if (this.props.selectedConversation.isPrivate) {
return;
}
if (this.props.selectedConversation.isPublic) {
this.fetchUsersForOpenGroup(overridenQuery, callback);
return;
}
if (!this.props.selectedConversation.isPrivate) {
this.fetchUsersForClosedGroup(overridenQuery, callback);
return;
}
// can only be a closed group here
this.fetchUsersForClosedGroup(overridenQuery, callback);
}
private fetchUsersForClosedGroup(
@ -675,6 +679,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
abortController.abort();
}, LINK_PREVIEW_TIMEOUT);
// eslint-disable-next-line more/no-then
getPreview(firstLink, abortController.signal)
.then(ret => {
// we finished loading the preview, and checking the abortConrtoller, we are still not aborted.
@ -843,7 +848,6 @@ class CompositionBoxInner extends React.Component<Props, State> {
}
}
// tslint:disable-next-line: cyclomatic-complexity
private async onSendMessage() {
if (!this.props.selectedConversationKey) {
throw new Error('selectedConversationKey is needed');

View File

@ -48,6 +48,7 @@ export const StartRecordingButton = (props: { onClick: () => void }) => {
);
};
// eslint-disable-next-line react/display-name
export const ToggleEmojiButton = React.forwardRef<HTMLDivElement, { onClick: () => void }>(
(props, ref) => {
return (

View File

@ -1,8 +1,8 @@
import React from 'react';
import { SuggestionDataItem } from 'react-mentions';
import styled from 'styled-components';
// @ts-ignore
import { SearchIndex } from 'emoji-mart';
// eslint-disable-next-line import/extensions
import { searchSync } from '../../../util/emoji.js';
const EmojiQuickResult = styled.span`

View File

@ -2,7 +2,7 @@ import classNames from 'classnames';
import React, { useCallback } from 'react';
import moment from 'moment';
// tslint:disable-next-line:match-default-export-name
import formatFileSize from 'filesize';
import { saveAttachmentToDisk } from '../../../util/attachmentsUtil';
import { MediaItemType } from '../../lightbox/LightboxGallery';

View File

@ -28,7 +28,6 @@ const MediaGridItemContent = (props: Props) => {
const disableDrag = useDisableDrag();
const onImageError = () => {
// tslint:disable-next-line no-console
window.log.info('MediaGridItem: Image failed to load; failing over to placeholder');
setImageBroken(true);
};
@ -58,7 +57,8 @@ const MediaGridItemContent = (props: Props) => {
onDragStart={disableDrag}
/>
);
} else if (contentType && isVideoTypeSupported(contentType)) {
}
if (contentType && isVideoTypeSupported(contentType)) {
if (imageBroken || !srcData) {
return (
<div

View File

@ -45,12 +45,12 @@ const toSection = (
messagesWithSection: Array<MediaItemWithSection> | undefined
): Section | undefined => {
if (!messagesWithSection || messagesWithSection.length === 0) {
return;
return undefined;
}
const firstMediaItemWithSection: MediaItemWithSection = messagesWithSection[0];
if (!firstMediaItemWithSection) {
return;
return undefined;
}
const mediaItems = messagesWithSection.map(messageWithSection => messageWithSection.mediaItem);
@ -71,11 +71,7 @@ const toSection = (
mediaItems,
};
default:
// NOTE: Investigate why we get the following error:
// error TS2345: Argument of type 'any' is not assignable to parameter
// of type 'never'.
// return missingCaseError(firstMediaItemWithSection.type);
return;
return undefined;
}
};

View File

@ -111,7 +111,7 @@ export const ClickToTrustSender = (props: { messageId: string }) => {
};
return (
<StyledTrustSenderUI onClick={openConfirmationModal}>
<StyledTrustSenderUI onClick={e => void openConfirmationModal(e)}>
<SessionIcon iconSize="small" iconType="gallery" />
<ClickToDownload>{window.i18n('clickToTrustContact')}</ClickToDownload>
</StyledTrustSenderUI>

View File

@ -1,5 +1,7 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import classNames from 'classnames';
import { clone } from 'lodash';
import { Data } from '../../../../data/data';
import { MessageModelType, MessageRenderingProps } from '../../../../models/messageType';
@ -29,8 +31,6 @@ import { AudioPlayerWithEncryptedFile } from '../../H5AudioPlayer';
import { ImageGrid } from '../../ImageGrid';
import { LightBoxOptions } from '../../SessionConversation';
import { ClickToTrustSender } from './ClickToTrustSender';
import styled from 'styled-components';
import classNames from 'classnames';
import { StyledMessageHighlighter } from './MessageContent';
export type MessageAttachmentSelectorProps = Pick<
@ -51,7 +51,6 @@ type Props = {
handleImageError: () => void;
highlight?: boolean;
};
// tslint:disable: use-simple-attributes
const StyledAttachmentContainer = styled.div<{
messageDirection: MessageModelType;
@ -63,7 +62,6 @@ const StyledAttachmentContainer = styled.div<{
justify-content: ${props => (props.messageDirection === 'incoming' ? 'flex-start' : 'flex-end')};
`;
// tslint:disable-next-line max-func-body-length cyclomatic-complexity
export const MessageAttachment = (props: Props) => {
const { messageId, imageBroken, handleImageError, highlight = false } = props;
@ -82,21 +80,21 @@ export const MessageAttachment = (props: Props) => {
});
}
},
[messageId, multiSelectMode]
[dispatch, messageId, multiSelectMode]
);
const onClickOnGenericAttachment = useCallback(
(e: any) => {
e.stopPropagation();
e.preventDefault();
if (!attachments?.length) {
if (!attachmentProps?.attachments?.length) {
return;
}
const messageTimestamp = attachmentProps?.timestamp || attachmentProps?.serverTimestamp || 0;
if (attachmentProps?.sender && attachmentProps?.convoId) {
void saveAttachmentToDisk({
attachment: attachments[0],
attachment: attachmentProps?.attachments[0],
messageTimestamp,
messageSender: attachmentProps?.sender,
conversationId: attachmentProps?.convoId,

View File

@ -36,7 +36,7 @@ export const MessageAuthorText = (props: Props) => {
return null;
}
const title = authorName ? authorName : sender;
const title = authorName || sender;
if (direction !== 'incoming' || !isGroup || !title || !firstMessageOfSeries) {
return null;

View File

@ -16,14 +16,13 @@ import {
useLastMessageOfSeries,
useMessageAuthor,
useMessageSenderIsAdmin,
} from '../../../../state/selectors/';
} from '../../../../state/selectors';
import {
getSelectedCanWrite,
useSelectedConversationKey,
useSelectedIsPublic,
} from '../../../../state/selectors/selectedConversation';
import { Avatar, AvatarSize, CrownIcon } from '../../../avatar/Avatar';
// tslint:disable: use-simple-attributes
const StyledAvatar = styled.div`
position: relative;
@ -54,13 +53,12 @@ export const MessageAvatar = (props: Props) => {
const lastMessageOfSeries = useLastMessageOfSeries(messageId);
const isSenderAdmin = useMessageSenderIsAdmin(messageId);
if (!sender) {
return null;
}
const userName = authorName || authorProfileName || sender;
const onMessageAvatarClick = useCallback(async () => {
if (!sender) {
return;
}
if (isPublic && !PubKey.isBlinded(sender)) {
// public chat but session id not blinded. disable showing user details if we do not have an active convo with that user.
// an unactive convo with that user means that we never chatted with that id directyly, but only through a sogs
@ -112,15 +110,19 @@ export const MessageAvatar = (props: Props) => {
return;
}
//not public, i.e. closed group. Just open dialog for the user to do what he wants
// not public, i.e. closed group. Just open dialog for the user to do what he wants
dispatch(
updateUserDetailsModal({
conversationId: sender,
userName,
userName: userName || '',
authorAvatarPath,
})
);
}, [userName, sender, isPublic, authorAvatarPath, selectedConvoKey]);
}, [dispatch, isTypingEnabled, userName, sender, isPublic, authorAvatarPath, selectedConvoKey]);
if (!sender) {
return null;
}
if (isPrivate) {
return null;
@ -137,7 +139,11 @@ export const MessageAvatar = (props: Props) => {
visibility: hideAvatar ? 'hidden' : undefined,
}}
>
<Avatar size={AvatarSize.S} onAvatarClick={onMessageAvatarClick} pubkey={sender} />
<Avatar
size={AvatarSize.S}
onAvatarClick={() => void onMessageAvatarClick()}
pubkey={sender}
/>
{isSenderAdmin && <CrownIcon />}
</StyledAvatar>
);

View File

@ -1,15 +1,15 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import LinkifyIt from 'linkify-it';
import React from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { RenderTextCallbackType } from '../../../../types/Util';
import { getEmojiSizeClass, SizeClassType } from '../../../../util/emoji';
import { LinkPreviews } from '../../../../util/linkPreviews';
import { showLinkVisitWarningDialog } from '../../../dialog/SessionConfirm';
import { AddMentions } from '../../AddMentions';
import { AddNewLines } from '../../AddNewLines';
import { Emojify } from '../../Emojify';
import { LinkPreviews } from '../../../../util/linkPreviews';
import { showLinkVisitWarningDialog } from '../../../dialog/SessionConfirm';
import styled from 'styled-components';
const linkify = LinkifyIt();
@ -30,7 +30,6 @@ export const renderTextDefault: RenderTextCallbackType = ({ text }) => <>{text}<
const renderNewLines: RenderTextCallbackType = ({ text: textWithNewLines, key, isGroup }) => {
return (
// tslint:disable-next-line: use-simple-attributes
<AddNewLines
key={key}
text={textWithNewLines}
@ -106,17 +105,6 @@ const Linkify = (props: LinkifyProps): JSX.Element => {
const matchData = linkify.match(text) || [];
let last = 0;
// disable click on <a> elements so clicking a message containing a link doesn't
// select the message. The link will still be opened in the browser.
const handleClick = useCallback((e: any) => {
e.preventDefault();
e.stopPropagation();
const url = e.target.href;
showLinkVisitWarningDialog(url, dispatch);
}, []);
if (matchData.length === 0) {
return renderNonLink({ text, key: 0, isGroup });
}
@ -130,8 +118,19 @@ const Linkify = (props: LinkifyProps): JSX.Element => {
const { url, text: originalText } = match;
const isLink = SUPPORTED_PROTOCOLS.test(url) && !LinkPreviews.isLinkSneaky(url);
if (isLink) {
// disable click on <a> elements so clicking a message containing a link doesn't
// select the message. The link will still be opened in the browser.
results.push(
<a key={count++} href={url} onClick={handleClick}>
<a
key={count++}
href={url}
onClick={e => {
e.preventDefault();
e.stopPropagation();
showLinkVisitWarningDialog(url, dispatch);
}}
>
{originalText}
</a>
);

View File

@ -28,6 +28,8 @@ type Props = {
isDetailView?: boolean;
};
// TODO not too sure what is this doing? It is not preventDefault()
// or stopPropagation() so I think this is never cancelling a click event?
function onClickOnMessageInnerContainer(event: React.MouseEvent<HTMLDivElement>) {
const selection = window.getSelection();
// Text is being selected
@ -38,6 +40,7 @@ function onClickOnMessageInnerContainer(event: React.MouseEvent<HTMLDivElement>)
// User clicked on message body
const target = event.target as HTMLDivElement;
if (target.className === 'text-selectable' || window.contextMenuShown) {
// eslint-disable-next-line no-useless-return
return;
}
}
@ -86,7 +89,6 @@ const StyledMessageOpaqueContent = styled(StyledMessageHighlighter)<{
`;
export const IsMessageVisibleContext = createContext(false);
// tslint:disable: use-simple-attributes
export const MessageContent = (props: Props) => {
const [highlight, setHighlight] = useState(false);
@ -101,7 +103,7 @@ export const MessageContent = (props: Props) => {
const [imageBroken, setImageBroken] = useState(false);
const onVisible = (inView: boolean | Object) => {
const onVisible = (inView: boolean | object) => {
if (
inView === true ||
((inView as any).type === 'focus' && (inView as any).returnValue === true)
@ -123,7 +125,7 @@ export const MessageContent = (props: Props) => {
useLayoutEffect(() => {
if (isQuotedMessageToAnimate) {
if (!highlight && !didScroll) {
//scroll to me and flash me
// scroll to me and flash me
scrollToLoadedMessage(props.messageId, 'quote-or-search-result');
setDidScroll(true);
if (shouldHighlightMessage) {
@ -139,8 +141,14 @@ export const MessageContent = (props: Props) => {
if (didScroll) {
setDidScroll(false);
}
return;
});
}, [
isQuotedMessageToAnimate,
highlight,
didScroll,
scrollToLoadedMessage,
props.messageId,
shouldHighlightMessage,
]);
if (!contentProps) {
return null;

View File

@ -11,7 +11,7 @@ import {
isMessageSelectionMode,
} from '../../../../state/selectors/conversations';
import { Reactions } from '../../../../util/reactions';
import { MessageAvatar } from '../message-content/MessageAvatar';
import { MessageAvatar } from './MessageAvatar';
import { MessageAuthorText } from './MessageAuthorText';
import { MessageContent } from './MessageContent';
import { MessageContextMenu } from './MessageContextMenu';
@ -30,7 +30,6 @@ type Props = {
dataTestId?: string;
enableReactions: boolean;
};
// tslint:disable: use-simple-attributes
const StyledMessageContentContainer = styled.div<{ direction: 'left' | 'right' }>`
display: flex;
@ -60,13 +59,13 @@ export const MessageContentWithStatuses = (props: Props) => {
const onClickOnMessageOuterContainer = useCallback(
(event: React.MouseEvent<HTMLDivElement>) => {
if (multiSelectMode && messageId) {
if (multiSelectMode && props?.messageId) {
event.preventDefault();
event.stopPropagation();
dispatch(toggleSelectedMessageId(messageId));
dispatch(toggleSelectedMessageId(props?.messageId));
}
},
[window.contextMenuShown, props?.messageId, multiSelectMode, props?.isDetailView]
[dispatch, props?.messageId, multiSelectMode]
);
const onDoubleClickReplyToMessage = (e: React.MouseEvent<HTMLDivElement>) => {
@ -84,12 +83,12 @@ export const MessageContentWithStatuses = (props: Props) => {
void replyToMessage(messageId);
currentSelection?.empty();
e.preventDefault();
return;
}
}
};
const { messageId, ctxMenuID, isDetailView, dataTestId, enableReactions } = props;
const [popupReaction, setPopupReaction] = useState('');
if (!contentProps) {
return null;
@ -100,8 +99,6 @@ export const MessageContentWithStatuses = (props: Props) => {
const isPrivate = conversationType === 'private';
const hideAvatar = isPrivate || direction === 'outgoing';
const [popupReaction, setPopupReaction] = useState('');
const handleMessageReaction = async (emoji: string) => {
await Reactions.sendMessageReaction(messageId, emoji);
};
@ -150,7 +147,7 @@ export const MessageContentWithStatuses = (props: Props) => {
{enableReactions && (
<MessageReactions
messageId={messageId}
onClick={handleMessageReaction}
onClick={emoji => void handleMessageReaction(emoji)}
popupReaction={popupReaction}
setPopupReaction={setPopupReaction}
onPopupClick={handlePopupClick}

View File

@ -2,10 +2,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
import { animation, Item, Menu, useContextMenu } from 'react-contexify';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { useClickAway, useMouse } from 'react-use';
import styled from 'styled-components';
import { Data } from '../../../../data/data';
import { MessageInteraction } from '../../../../interactions';
import { replyToMessage } from '../../../../interactions/conversationInteractions';
import {
@ -22,8 +23,6 @@ import {
showMessageDetailsView,
toggleSelectedMessageId,
} from '../../../../state/ducks/conversations';
import { StateType } from '../../../../state/reducer';
import { getMessageContextMenuProps } from '../../../../state/selectors/conversations';
import {
useSelectedConversationKey,
useSelectedIsBlocked,
@ -36,10 +35,21 @@ import { Reactions } from '../../../../util/reactions';
import { SessionContextMenuContainer } from '../../../SessionContextMenuContainer';
import { SessionEmojiPanel, StyledEmojiPanel } from '../../SessionEmojiPanel';
import { MessageReactBar } from './MessageReactBar';
import {
useMessageAttachments,
useMessageBody,
useMessageDirection,
useMessageIsDeletable,
useMessageIsDeletableForEveryone,
useMessageSender,
useMessageSenderIsAdmin,
useMessageServerTimestamp,
useMessageStatus,
useMessageTimestamp,
} from '../../../../state/selectors';
export type MessageContextMenuSelectorProps = Pick<
MessageRenderingProps,
| 'attachments'
| 'sender'
| 'direction'
| 'status'
@ -48,7 +58,6 @@ export type MessageContextMenuSelectorProps = Pick<
| 'text'
| 'serverTimestamp'
| 'timestamp'
| 'isDeletableForEveryone'
>;
type Props = { messageId: string; contextMenuId: string; enableReactions: boolean };
@ -76,7 +85,124 @@ const StyledEmojiPanelContainer = styled.div<{ x: number; y: number }>`
}
`;
// tslint:disable: max-func-body-length cyclomatic-complexity
const DeleteForEveryone = ({ messageId }: { messageId: string }) => {
const convoId = useSelectedConversationKey();
const isDeletableForEveryone = useMessageIsDeletableForEveryone(messageId);
if (!convoId || !isDeletableForEveryone) {
return null;
}
const onDeleteForEveryone = () => {
void deleteMessagesByIdForEveryone([messageId], convoId);
};
const unsendMessageText = window.i18n('deleteForEveryone');
return <Item onClick={onDeleteForEveryone}>{unsendMessageText}</Item>;
};
type MessageId = { messageId: string };
const SaveAttachment = ({ messageId }: MessageId) => {
const convoId = useSelectedConversationKey();
const attachments = useMessageAttachments(messageId);
const timestamp = useMessageTimestamp(messageId);
const serverTimestamp = useMessageServerTimestamp(messageId);
const sender = useMessageSender(messageId);
const saveAttachment = useCallback(
(e: any) => {
// this is quite dirty but considering that we want the context menu of the message to show on click on the attachment
// and the context menu save attachment item to save the right attachment I did not find a better way for now.
let targetAttachmentIndex = e.triggerEvent.path[1].getAttribute('data-attachmentindex');
e.event.stopPropagation();
if (!attachments?.length || !convoId || !sender) {
return;
}
if (!targetAttachmentIndex) {
targetAttachmentIndex = 0;
}
if (targetAttachmentIndex > attachments.length) {
return;
}
const messageTimestamp = timestamp || serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: attachments[targetAttachmentIndex],
messageTimestamp,
messageSender: sender,
conversationId: convoId,
});
},
[convoId, sender, attachments, serverTimestamp, timestamp]
);
if (!convoId) {
return null;
}
return attachments?.length ? (
<Item onClick={saveAttachment}>{window.i18n('downloadAttachment')}</Item>
) : null;
};
const AdminActionItems = ({ messageId }: MessageId) => {
const convoId = useSelectedConversationKey();
const weAreModerator = useSelectedWeAreModerator();
const weAreAdmin = useSelectedWeAreAdmin();
const showAdminActions = weAreAdmin || weAreModerator;
const sender = useMessageSender(messageId);
const isSenderAdmin = useMessageSenderIsAdmin(messageId);
if (!convoId || !sender) {
return null;
}
const addModerator = () => {
void addSenderAsModerator(sender, convoId);
};
const removeModerator = () => {
void removeSenderFromModerator(sender, convoId);
};
const onBan = () => {
MessageInteraction.banUser(sender, convoId);
};
const onUnban = () => {
MessageInteraction.unbanUser(sender, convoId);
};
return showAdminActions ? (
<>
<Item onClick={onBan}>{window.i18n('banUser')}</Item>
<Item onClick={onUnban}>{window.i18n('unbanUser')}</Item>
{isSenderAdmin ? (
<Item onClick={removeModerator}>{window.i18n('removeFromModerators')}</Item>
) : (
<Item onClick={addModerator}>{window.i18n('addAsModerator')}</Item>
)}
</>
) : null;
};
const RetryItem = ({ messageId }: MessageId) => {
const direction = useMessageDirection(messageId);
const status = useMessageStatus(messageId);
const isOutgoing = direction === 'outgoing';
const showRetry = status === 'error' && isOutgoing;
const onRetry = useCallback(async () => {
const found = await Data.getMessageById(messageId);
if (found) {
await found.retrySend();
}
}, [messageId]);
return showRetry ? <Item onClick={() => void onRetry()}>{window.i18n('resend')}</Item> : null;
};
export const MessageContextMenu = (props: Props) => {
const { messageId, contextMenuId, enableReactions } = props;
const dispatch = useDispatch();
@ -85,32 +211,13 @@ export const MessageContextMenu = (props: Props) => {
const isSelectedBlocked = useSelectedIsBlocked();
const convoId = useSelectedConversationKey();
const isPublic = useSelectedIsPublic();
const weAreModerator = useSelectedWeAreModerator();
const weAreAdmin = useSelectedWeAreAdmin();
const showAdminActions = weAreAdmin || weAreModerator;
const selected = useSelector((state: StateType) => getMessageContextMenuProps(state, messageId));
if (!selected || !convoId) {
return null;
}
const {
attachments,
sender,
direction,
status,
isDeletable,
isDeletableForEveryone,
isSenderAdmin,
text,
serverTimestamp,
timestamp,
} = selected;
const direction = useMessageDirection(messageId);
const status = useMessageStatus(messageId);
const isDeletable = useMessageIsDeletable(messageId);
const text = useMessageBody(messageId);
const isOutgoing = direction === 'outgoing';
const showRetry = status === 'error' && isOutgoing;
const isSent = status === 'sent' || status === 'read'; // a read message should be replyable
const emojiPanelRef = useRef<HTMLDivElement>(null);
@ -152,15 +259,6 @@ export const MessageContextMenu = (props: Props) => {
const selectMessageText = window.i18n('selectMessage');
const deleteMessageJustForMeText = window.i18n('deleteJustForMe');
const unsendMessageText = window.i18n('deleteForEveryone');
const addModerator = useCallback(() => {
void addSenderAsModerator(sender, convoId);
}, [sender, convoId]);
const removeModerator = useCallback(() => {
void removeSenderFromModerator(sender, convoId);
}, [sender, convoId]);
const onReply = useCallback(() => {
if (isSelectedBlocked) {
@ -170,62 +268,18 @@ export const MessageContextMenu = (props: Props) => {
void replyToMessage(messageId);
}, [isSelectedBlocked, messageId]);
const saveAttachment = useCallback(
(e: any) => {
// this is quite dirty but considering that we want the context menu of the message to show on click on the attachment
// and the context menu save attachment item to save the right attachment I did not find a better way for now.
let targetAttachmentIndex = e.triggerEvent.path[1].getAttribute('data-attachmentindex');
e.event.stopPropagation();
if (!attachments?.length) {
return;
}
if (!targetAttachmentIndex) {
targetAttachmentIndex = 0;
}
if (targetAttachmentIndex > attachments.length) {
return;
}
const messageTimestamp = timestamp || serverTimestamp || 0;
void saveAttachmentToDisk({
attachment: attachments[targetAttachmentIndex],
messageTimestamp,
messageSender: sender,
conversationId: convoId,
});
},
[convoId, sender, timestamp, serverTimestamp, convoId, attachments]
);
const copyText = useCallback(() => {
MessageInteraction.copyBodyToClipboard(text);
}, [text]);
const onRetry = useCallback(async () => {
const found = await Data.getMessageById(messageId);
if (found) {
await found.retrySend();
}
}, [messageId]);
const onBan = useCallback(() => {
MessageInteraction.banUser(sender, convoId);
}, [sender, convoId]);
const onUnban = useCallback(() => {
MessageInteraction.unbanUser(sender, convoId);
}, [sender, convoId]);
const onSelect = useCallback(() => {
dispatch(toggleSelectedMessageId(messageId));
}, [messageId]);
}, [dispatch, messageId]);
const onDelete = useCallback(() => {
void deleteMessagesById([messageId], convoId);
}, [convoId, messageId]);
const onDeleteForEveryone = useCallback(() => {
void deleteMessagesByIdForEveryone([messageId], convoId);
if (convoId) {
void deleteMessagesById([messageId], convoId);
}
}, [convoId, messageId]);
const onShowEmoji = () => {
@ -241,7 +295,7 @@ export const MessageContextMenu = (props: Props) => {
};
const onEmojiLoseFocus = () => {
window.log.info('closed due to lost focus');
window.log.debug('closed due to lost focus');
onCloseEmoji();
};
@ -262,7 +316,7 @@ export const MessageContextMenu = (props: Props) => {
});
useEffect(() => {
if (emojiPanelRef.current && emojiPanelRef.current) {
if (emojiPanelRef.current) {
const { innerWidth: windowWidth, innerHeight: windowHeight } = window;
if (mouseX + emojiPanelWidth > windowWidth) {
@ -284,15 +338,18 @@ export const MessageContextMenu = (props: Props) => {
setMouseY(mouseY - y);
}
}
}, [emojiPanelRef.current, emojiPanelWidth, emojiPanelHeight, mouseX, mouseY]);
}, [emojiPanelWidth, emojiPanelHeight, mouseX, mouseY]);
if (!convoId) {
return null;
}
return (
<StyledMessageContextMenu ref={contextMenuRef}>
{enableReactions && showEmojiPanel && (
<StyledEmojiPanelContainer role="button" x={mouseX} y={mouseY}>
<SessionEmojiPanel
ref={emojiPanelRef}
onEmojiClicked={onEmojiClick}
onEmojiClicked={e => void onEmojiClick(e)}
show={showEmojiPanel}
isModal={true}
onKeyDown={onEmojiKeyDown}
@ -307,46 +364,24 @@ export const MessageContextMenu = (props: Props) => {
animation={animation.fade}
>
{enableReactions && (
<MessageReactBar action={onEmojiClick} additionalAction={onShowEmoji} />
<MessageReactBar action={e => void onEmojiClick(e)} additionalAction={onShowEmoji} />
)}
{attachments?.length ? (
<Item onClick={saveAttachment}>{window.i18n('downloadAttachment')}</Item>
) : null}
<SaveAttachment messageId={messageId} />
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
{(isSent || !isOutgoing) && (
<Item onClick={onReply}>{window.i18n('replyToMessage')}</Item>
)}
{(!isPublic || isOutgoing) && (
<Item onClick={onShowDetail}>{window.i18n('moreInformation')}</Item>
<Item onClick={() => void onShowDetail()}>{window.i18n('moreInformation')}</Item>
)}
{showRetry ? <Item onClick={onRetry}>{window.i18n('resend')}</Item> : null}
{isDeletable ? (
<>
<Item onClick={onSelect}>{selectMessageText}</Item>
</>
) : null}
<RetryItem messageId={messageId} />
{isDeletable ? <Item onClick={onSelect}>{selectMessageText}</Item> : null}
{isDeletable && !isPublic ? (
<>
<Item onClick={onDelete}>{deleteMessageJustForMeText}</Item>
</>
) : null}
{isDeletableForEveryone ? (
<>
<Item onClick={onDeleteForEveryone}>{unsendMessageText}</Item>
</>
) : null}
{showAdminActions ? (
<>
<Item onClick={onBan}>{window.i18n('banUser')}</Item>
<Item onClick={onUnban}>{window.i18n('unbanUser')}</Item>
{isSenderAdmin ? (
<Item onClick={removeModerator}>{window.i18n('removeFromModerators')}</Item>
) : (
<Item onClick={addModerator}>{window.i18n('addAsModerator')}</Item>
)}
</>
<Item onClick={onDelete}>{deleteMessageJustForMeText}</Item>
) : null}
<DeleteForEveryone messageId={messageId} />
<AdminActionItems messageId={messageId} />
</Menu>
</SessionContextMenuContainer>
</StyledMessageContextMenu>

View File

@ -1,5 +1,5 @@
import { isEmpty, toNumber } from 'lodash';
import React, { useCallback } from 'react';
import React from 'react';
import { useSelector } from 'react-redux';
import { Data } from '../../../../data/data';
import { MessageRenderingProps } from '../../../../models/messageType';
@ -10,12 +10,9 @@ import { useMessageDirection } from '../../../../state/selectors';
import {
getMessageQuoteProps,
isMessageDetailView,
isMessageSelectionMode,
} from '../../../../state/selectors/conversations';
import { Quote } from './quote/Quote';
// tslint:disable: use-simple-attributes
type Props = {
messageId: string;
};
@ -25,7 +22,6 @@ export type MessageQuoteSelectorProps = Pick<MessageRenderingProps, 'quote' | 'd
export const MessageQuote = (props: Props) => {
const selected = useSelector((state: StateType) => getMessageQuoteProps(state, props.messageId));
const direction = useMessageDirection(props.messageId);
const multiSelectMode = useSelector(isMessageSelectionMode);
const isMessageDetailViewMode = useSelector(isMessageDetailView);
if (!selected || isEmpty(selected)) {
@ -42,70 +38,67 @@ export const MessageQuote = (props: Props) => {
quote.referencedMessageNotFound || !quote?.author || !quote.id || !quote.convoId
);
const onQuoteClick = useCallback(
async (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
const onQuoteClick = async (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
event.stopPropagation();
if (!quote) {
ToastUtils.pushOriginalNotFound();
window.log.warn('onQuoteClick: quote not valid');
return;
}
if (!quote) {
ToastUtils.pushOriginalNotFound();
window.log.warn('onQuoteClick: quote not valid');
return;
}
if (isMessageDetailViewMode) {
// trying to scroll while in the container while the message detail view is shown has unknown effects
return;
}
if (isMessageDetailViewMode) {
// trying to scroll while in the container while the message detail view is shown has unknown effects
return;
}
let conversationKey = String(quote.convoId);
let messageIdToNavigateTo = String(quote.id);
let quoteNotFoundInDB = false;
let conversationKey = String(quote.convoId);
let messageIdToNavigateTo = String(quote.id);
let quoteNotFoundInDB = false;
// If the quote is not found in memory, we try to find it in the DB
if (quoteNotFound && quote.id && quote.author) {
// We always look for the quote by sentAt timestamp, for opengroups, closed groups and session chats
// this will return an array of sent messages by id that we have locally.
const quotedMessagesCollection = await Data.getMessagesBySenderAndSentAt([
{
timestamp: toNumber(quote.id),
source: quote.author,
},
]);
// If the quote is not found in memory, we try to find it in the DB
if (quoteNotFound && quote.id && quote.author) {
// We always look for the quote by sentAt timestamp, for opengroups, closed groups and session chats
// this will return an array of sent messages by id that we have locally.
const quotedMessagesCollection = await Data.getMessagesBySenderAndSentAt([
{
timestamp: toNumber(quote.id),
source: quote.author,
},
]);
if (quotedMessagesCollection?.length) {
const quotedMessage = quotedMessagesCollection.at(0);
// If found, we navigate to the quoted message which also refreshes the message quote component
if (quotedMessage) {
conversationKey = String(quotedMessage.get('conversationId'));
messageIdToNavigateTo = String(quotedMessage.id);
} else {
quoteNotFoundInDB = true;
}
if (quotedMessagesCollection?.length) {
const quotedMessage = quotedMessagesCollection.at(0);
// If found, we navigate to the quoted message which also refreshes the message quote component
if (quotedMessage) {
conversationKey = String(quotedMessage.get('conversationId'));
messageIdToNavigateTo = String(quotedMessage.id);
} else {
quoteNotFoundInDB = true;
}
} else {
quoteNotFoundInDB = true;
}
}
// For simplicity's sake, we show the 'not found' toast no matter what if we were
// not able to find the referenced message when the quote was received or if the conversation no longer exists.
if (quoteNotFoundInDB) {
ToastUtils.pushOriginalNotFound();
return;
}
// For simplicity's sake, we show the 'not found' toast no matter what if we were
// not able to find the referenced message when the quote was received or if the conversation no longer exists.
if (quoteNotFoundInDB) {
ToastUtils.pushOriginalNotFound();
return;
}
void openConversationToSpecificMessage({
conversationKey,
messageIdToNavigateTo,
shouldHighlightMessage: true,
});
},
[isMessageDetailViewMode, multiSelectMode, quote, quoteNotFound]
);
void openConversationToSpecificMessage({
conversationKey,
messageIdToNavigateTo,
shouldHighlightMessage: true,
});
};
return (
<Quote
onClick={onQuoteClick}
onClick={e => void onQuoteClick(e)}
text={quote?.text}
attachment={quote?.attachment}
isIncoming={direction === 'incoming'}

View File

@ -1,10 +1,12 @@
import React, { ReactElement, useEffect, useState } from 'react';
import { isEqual } from 'lodash';
import React, { ReactElement, useState } from 'react';
import useMount from 'react-use/lib/useMount';
import styled from 'styled-components';
import { RecentReactions } from '../../../../types/Reaction';
import { nativeEmojiData } from '../../../../util/emoji';
import { getRecentReactions } from '../../../../util/storage';
import { SessionIconButton } from '../../../icon';
import { nativeEmojiData } from '../../../../util/emoji';
import { isEqual } from 'lodash';
import { RecentReactions } from '../../../../types/Reaction';
type Props = {
action: (...args: Array<any>) => void;
@ -53,12 +55,12 @@ export const MessageReactBar = (props: Props): ReactElement => {
const { action, additionalAction } = props;
const [recentReactions, setRecentReactions] = useState<RecentReactions>();
useEffect(() => {
useMount(() => {
const reactions = new RecentReactions(getRecentReactions());
if (reactions && !isEqual(reactions, recentReactions)) {
setRecentReactions(reactions);
}
}, []);
});
if (!recentReactions) {
return <></>;

View File

@ -1,10 +1,10 @@
import classNames from 'classnames';
import React, { useState } from 'react';
import * as MIME from '../../../../../ts/types/MIME';
import * as GoogleChrome from '../../../../../ts/util/GoogleChrome';
import { noop } from 'lodash';
import * as MIME from '../../../../types/MIME';
import * as GoogleChrome from '../../../../util/GoogleChrome';
import { useDisableDrag } from '../../../../hooks/useDisableDrag';
import { useEncryptedFileFetch } from '../../../../hooks/useEncryptedFileFetch';
import { PubKey } from '../../../../session/types';
@ -61,7 +61,7 @@ function getObjectUrl(thumbnail: Attachment | undefined): string | undefined {
return thumbnail.objectUrl;
}
return;
return undefined;
}
function getTypeLabel({
@ -84,7 +84,7 @@ function getTypeLabel({
return window.i18n('audio');
}
return;
return undefined;
}
export const QuoteIcon = (props: any) => {
const { icon } = props;

View File

@ -1,13 +1,12 @@
import React, { MouseEvent, useState } from 'react';
import * as MIME from '../../../../../types/MIME';
import { isEmpty } from 'lodash';
import styled from 'styled-components';
import { useIsMessageSelectionMode } from '../../../../../state/selectors/selectedConversation';
import { QuoteAuthor } from './QuoteAuthor';
import { QuoteIconContainer } from './QuoteIconContainer';
import { QuoteText } from './QuoteText';
import * as MIME from '../../../../../types/MIME';
const StyledQuoteContainer = styled.div`
min-width: 300px; // if the quoted content is small it doesn't look very good so we set a minimum

View File

@ -1,11 +1,12 @@
import React from 'react';
import { isEmpty, noop } from 'lodash';
import styled from 'styled-components';
import { QuotedAttachmentThumbnailType, QuoteProps } from './Quote';
import { GoogleChrome } from '../../../../../util';
import { MIME } from '../../../../../types';
import { isEmpty, noop } from 'lodash';
import { QuoteImage } from './QuoteImage';
import styled from 'styled-components';
import { icons, SessionIconType } from '../../../../icon';
function getObjectUrl(thumbnail: QuotedAttachmentThumbnailType | undefined): string | undefined {
@ -13,7 +14,7 @@ function getObjectUrl(thumbnail: QuotedAttachmentThumbnailType | undefined): str
return thumbnail.objectUrl;
}
return;
return undefined;
}
const StyledQuoteIconContainer = styled.div`

View File

@ -1,9 +1,11 @@
import React from 'react';
import styled from 'styled-components';
import { isEmpty } from 'lodash';
import { useDisableDrag } from '../../../../../hooks/useDisableDrag';
import { useEncryptedFileFetch } from '../../../../../hooks/useEncryptedFileFetch';
import styled from 'styled-components';
import { icons } from '../../../../icon';
import { isEmpty } from 'lodash';
import { QuoteIcon } from './QuoteIconContainer';
const StyledQuoteImage = styled.div`

View File

@ -2,9 +2,11 @@ import classNames from 'classnames';
import React, { useCallback, useEffect, useState } from 'react';
import { contextMenu } from 'react-contexify';
import { useDispatch, useSelector } from 'react-redux';
// tslint:disable-next-line: no-submodule-imports
import styled, { keyframes } from 'styled-components';
import useInterval from 'react-use/lib/useInterval';
import _ from 'lodash';
import useMount from 'react-use/lib/useMount';
import { Data } from '../../../../data/data';
import { MessageRenderingProps } from '../../../../models/messageType';
import { getConversationController } from '../../../../session/conversations';
@ -17,11 +19,10 @@ import {
import { getIncrement } from '../../../../util/timer';
import { ExpireTimer } from '../../ExpireTimer';
import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus';
import { ReadableMessage } from './ReadableMessage';
import styled, { keyframes } from 'styled-components';
import { isOpenOrClosedGroup } from '../../../../models/conversationAttributes';
import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus';
import { StyledMessageReactionsContainer } from '../message-content/MessageReactions';
import { ReadableMessage } from './ReadableMessage';
export type GenericReadableMessageSelectorProps = Pick<
MessageRenderingProps,
@ -81,7 +82,7 @@ function useIsExpired(props: ExpiringProps) {
convo?.updateLastMessage();
}
}
}, [expirationTimestamp, expirationLength, isExpired, messageId, convoId]);
}, [dispatch, expirationTimestamp, expirationLength, isExpired, messageId, convoId]);
let checkFrequency: number | null = null;
if (expirationLength) {
@ -89,9 +90,9 @@ function useIsExpired(props: ExpiringProps) {
checkFrequency = Math.max(EXPIRATION_CHECK_MINIMUM, increment);
}
useEffect(() => {
useMount(() => {
void checkExpired();
}, []); // check on mount
});
useInterval(checkExpired, checkFrequency); // check every 2sec or sooner if needed
return { isExpired };
@ -102,7 +103,6 @@ type Props = {
ctxMenuID: string;
isDetailView?: boolean;
};
// tslint:disable: use-simple-attributes
const highlightedMessageAnimation = keyframes`
1% {

View File

@ -1,10 +1,11 @@
import React from 'react';
import classNames from 'classnames';
import { PropsForGroupInvitation } from '../../../../state/ducks/conversations';
import React from 'react';
import styled from 'styled-components';
import { acceptOpenGroupInvitation } from '../../../../interactions/messageInteractions';
import { PropsForGroupInvitation } from '../../../../state/ducks/conversations';
import { SessionIconButton } from '../../../icon';
import { ReadableMessage } from './ReadableMessage';
import styled from 'styled-components';
const StyledIconContainer = styled.div`
background-color: var(--message-link-preview-background-color);

View File

@ -50,7 +50,6 @@ const ChangeItemLeft = (left: Array<string>): string => {
return window.i18n(leftKey, [names.join(', ')]);
};
// tslint:disable-next-line: cyclomatic-complexity
const ChangeItem = (change: PropsForGroupUpdateType): string => {
const { type } = change;
switch (type) {
@ -69,6 +68,7 @@ const ChangeItem = (change: PropsForGroupUpdateType): string => {
return window.i18n('updatedTheGroup');
default:
assertUnreachable(type, `ChangeItem: Missing case error "${type}"`);
return '';
}
};

View File

@ -1,11 +1,10 @@
import React from 'react';
import _ from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { useSelector } from 'react-redux';
import { v4 as uuidv4 } from 'uuid';
import { getGenericReadableMessageSelectorProps } from '../../../../state/selectors/conversations';
import { GenericReadableMessage } from './GenericReadableMessage';
import { THUMBNAIL_SIDE } from '../../../../types/attachments/VisualAttachment';
import { GenericReadableMessage } from './GenericReadableMessage';
// Same as MIN_WIDTH in ImageGrid.tsx
export const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = THUMBNAIL_SIDE;

View File

@ -1,21 +1,22 @@
import React from 'react';
import classNames from 'classnames';
import moment from 'moment';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import useKey from 'react-use/lib/useKey';
import { Message } from './Message';
import { useDispatch, useSelector } from 'react-redux';
import { Avatar, AvatarSize } from '../../../avatar/Avatar';
import { deleteMessagesById } from '../../../../interactions/conversations/unsendingInteractions';
import {
closeMessageDetailsView,
ContactPropsMessageDetail,
closeMessageDetailsView,
} from '../../../../state/ducks/conversations';
import { getMessageDetailsViewProps } from '../../../../state/selectors/conversations';
import { Avatar, AvatarSize } from '../../../avatar/Avatar';
import { ContactName } from '../../ContactName';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../../../basic/SessionButton';
import { useMessageIsDeletable } from '../../../../state/selectors';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../../../basic/SessionButton';
const AvatarItem = (props: { pubkey: string }) => {
const { pubkey } = props;

View File

@ -13,7 +13,6 @@ import {
} from '../../../../state/ducks/conversations';
import {
areMoreMessagesBeingFetched,
getLoadedMessagesLength,
getMostRecentMessageId,
getOldestMessageId,
getQuotedMessageToAnimate,
@ -64,7 +63,6 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
const dispatch = useDispatch();
const selectedConversationKey = useSelectedConversationKey();
const loadedMessagesLength = useSelector(getLoadedMessagesLength);
const mostRecentMessageId = useSelector(getMostRecentMessageId);
const oldestMessageId = useSelector(getOldestMessageId);
const youngestMessageId = useSelector(getYoungestMessageId);
@ -80,6 +78,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
// if this unread-indicator is rendered,
// we want to scroll here only if the conversation was not opened to a specific message
// eslint-disable-next-line react-hooks/exhaustive-deps
useLayoutEffect(() => {
if (
props.messageId === youngestMessageId &&
@ -96,8 +95,7 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
});
const onVisible = useCallback(
// tslint:disable-next-line: cyclomatic-complexity
async (inView: boolean | Object) => {
async (inView: boolean | object) => {
if (!selectedConversationKey) {
return;
}
@ -160,20 +158,20 @@ export const ReadableMessage = (props: ReadableMessageProps) => {
}
},
[
dispatch,
selectedConversationKey,
mostRecentMessageId,
oldestMessageId,
fetchingMoreInProgress,
isAppFocused,
loadedMessagesLength,
receivedAt,
messageId,
isUnread,
youngestMessageId,
]
);
return (
// tslint:disable-next-line: use-simple-attributes
<InView
id={`msg-${messageId}`}
onContextMenu={onContextMenu}

View File

@ -52,7 +52,6 @@ export type ReactionProps = {
handlePopupReaction?: (emoji: string) => void;
handlePopupClick?: () => void;
};
// tslint:disable-next-line: use-simple-attributes
export const Reaction = (props: ReactionProps): ReactElement => {
const {

View File

@ -99,8 +99,8 @@ const generateContactsString = async (
const Contacts = (contacts: Array<string>, count: number) => {
const darkMode = useSelector(isDarkTheme);
if (!Boolean(contacts?.length > 0)) {
return;
if (!(contacts?.length > 0)) {
return null;
}
const reactors = contacts.length;
@ -118,7 +118,8 @@ const Contacts = (contacts: Array<string>, count: number) => {
<span>{window.i18n('reactionPopup')}</span>
</StyledContacts>
);
} else if (reactors > 3) {
}
if (reactors > 3) {
return (
<StyledContacts>
{window.i18n('reactionPopupMany', [contacts[0], contacts[1], contacts[3]])}{' '}
@ -128,9 +129,8 @@ const Contacts = (contacts: Array<string>, count: number) => {
<span>{window.i18n('reactionPopup')}</span>
</StyledContacts>
);
} else {
return null;
}
return null;
};
type Props = {
@ -149,6 +149,7 @@ export const ReactionPopup = (props: Props): ReactElement => {
useEffect(() => {
let isCancelled = false;
// eslint-disable-next-line more/no-then
generateContactsString(messageId, senders)
.then(async results => {
if (isCancelled) {
@ -158,11 +159,7 @@ export const ReactionPopup = (props: Props): ReactElement => {
setContacts(results);
}
})
.catch(() => {
if (isCancelled) {
return;
}
});
.catch(() => {});
return () => {
isCancelled = true;

View File

@ -1,25 +1,24 @@
import React, { useRef, useState } from 'react';
import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils';
import { Flex } from '../basic/Flex';
import { useDispatch, useSelector } from 'react-redux';
import { BanType, updateBanOrUnbanUserModal } from '../../state/ducks/modalDialog';
import { SpacerSM } from '../basic/Text';
import { getConversationController } from '../../session/conversations/ConversationController';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { ConversationModel } from '../../models/conversation';
import { useFocusMount } from '../../hooks/useFocusMount';
import { useConversationPropsById } from '../../hooks/useParamSelector';
import { ConversationModel } from '../../models/conversation';
import {
sogsV3BanUser,
sogsV3UnbanUser,
} from '../../session/apis/open_group_api/sogsv3/sogsV3BanUnban';
import { SessionHeaderSearchInput } from '../SessionHeaderSearchInput';
import { getConversationController } from '../../session/conversations/ConversationController';
import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils';
import { BanType, updateBanOrUnbanUserModal } from '../../state/ducks/modalDialog';
import { isDarkTheme } from '../../state/selectors/theme';
// tslint:disable: use-simple-attributes
import { SessionHeaderSearchInput } from '../SessionHeaderSearchInput';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { Flex } from '../basic/Flex';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SpacerSM } from '../basic/Text';
async function banOrUnBanUserCall(
convo: ConversationModel,
@ -45,10 +44,12 @@ async function banOrUnBanUserCall(
if (!isChangeApplied) {
window?.log?.warn(`failed to ${banType} user: ${isChangeApplied}`);
// eslint-disable-next-line no-unused-expressions
banType === 'ban' ? ToastUtils.pushUserBanFailure() : ToastUtils.pushUserUnbanSuccess();
return false;
}
window?.log?.info(`${pubkey.key} user ${banType}ned successfully...`);
// eslint-disable-next-line no-unused-expressions
banType === 'ban' ? ToastUtils.pushUserBanSuccess() : ToastUtils.pushUserUnbanSuccess();
return true;
} catch (e) {

View File

@ -211,7 +211,7 @@ export const DeleteAccountModal = () => {
*/
const onClickCancelHandler = useCallback(() => {
dispatch(updateDeleteAccountModal(null));
}, []);
}, [dispatch]);
return (
<SessionWrapperModal

View File

@ -1,6 +1,7 @@
import autoBind from 'auto-bind';
import React, { ChangeEvent, MouseEvent } from 'react';
import { QRCode } from 'react-qr-svg';
import styled from 'styled-components';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SyncUtils, ToastUtils, UserUtils } from '../../session/utils';
@ -8,8 +9,6 @@ import { YourSessionIDPill, YourSessionIDSelectable } from '../basic/YourSession
import { ConversationModel } from '../../models/conversation';
import autoBind from 'auto-bind';
import styled from 'styled-components';
import { uploadOurAvatar } from '../../interactions/conversationInteractions';
import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { MAX_USERNAME_BYTES } from '../../session/constants';
@ -60,10 +59,10 @@ interface State {
loading: boolean;
}
export class EditProfileDialog extends React.Component<{}, State> {
export class EditProfileDialog extends React.Component<object, State> {
private readonly convo: ConversationModel;
constructor(props: any) {
constructor(props: object) {
super(props);
autoBind(this);
@ -164,7 +163,7 @@ export class EditProfileDialog extends React.Component<{}, State> {
<div
className="image-upload-section"
role="button"
onClick={this.fireInputEvent}
onClick={() => void this.fireInputEvent()}
data-testid="image-upload-section"
/>
<div
@ -303,6 +302,7 @@ export class EditProfileDialog extends React.Component<{}, State> {
profileName: trimName,
loading: true,
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
async () => {
await commitProfileEdits(newName, newAvatarObjectUrl);
this.setState({
@ -315,8 +315,6 @@ export class EditProfileDialog extends React.Component<{}, State> {
);
} catch (e) {
ToastUtils.pushToastError('nameTooLong', window.i18n('displayNameTooLong'));
return;
}
}

View File

@ -1,4 +1,5 @@
import React from 'react';
import useKey from 'react-use/lib/useKey';
import _ from 'lodash';
import { useDispatch, useSelector } from 'react-redux';
@ -8,16 +9,15 @@ import { getConversationController } from '../../session/conversations';
import { ToastUtils, UserUtils } from '../../session/utils';
import { updateInviteContactModal } from '../../state/ducks/modalDialog';
import { SpacerLG } from '../basic/Text';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { useConversationPropsById } from '../../hooks/useParamSelector';
import { useSet } from '../../hooks/useSet';
import { initiateClosedGroupUpdate } from '../../session/group/closed-group';
import { SessionUtilUserGroups } from '../../session/utils/libsession/libsession_utils_user_groups';
import { getPrivateContactsPubkeys } from '../../state/selectors/conversations';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { MemberListItem } from '../MemberListItem';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionUtilUserGroups } from '../../session/utils/libsession/libsession_utils_user_groups';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
type Props = {
conversationId: string;
@ -37,6 +37,7 @@ async function submitForOpenGroup(convoId: string, pubkeys: Array<string>) {
url: roomDetails?.fullUrlWithPubkey,
name: convo.getNicknameOrRealUsernameOrPlaceholder(),
};
// eslint-disable-next-line @typescript-eslint/no-misused-promises
pubkeys.forEach(async pubkeyStr => {
const privateConvo = await getConversationController().getOrCreateAndWait(
pubkeyStr,
@ -96,7 +97,6 @@ const submitForClosedGroup = async (convoId: string, pubkeys: Array<string>) =>
}
};
// tslint:disable-next-line: max-func-body-length
const InviteContactsDialogInner = (props: Props) => {
const { conversationId } = props;
const dispatch = useDispatch();

View File

@ -1,9 +1,10 @@
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils';
import { Flex } from '../basic/Flex';
import { getConversationController } from '../../session/conversations';
import { useDispatch, useSelector } from 'react-redux';
import { updateAddModeratorsModal } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
@ -39,10 +40,10 @@ export const AddModeratorsDialog = (props: Props) => {
try {
setAddingInProgress(true);
let isAdded: any;
// this is a v2 opengroup
const roomInfos = convo.toOpenGroupV2();
isAdded = await sogsV3AddAdmin([pubkey], roomInfos);
const isAdded = await sogsV3AddAdmin([pubkey], roomInfos);
if (!isAdded) {
window?.log?.warn('failed to add moderators:', isAdded);

View File

@ -1,15 +1,17 @@
import React, { useState } from 'react';
import { compact } from 'lodash';
import { useDispatch } from 'react-redux';
import { getConversationController } from '../../session/conversations';
import { PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils';
import { Flex } from '../basic/Flex';
import { compact } from 'lodash';
import { updateRemoveModeratorsModal } from '../../state/ducks/modalDialog';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { MemberListItem } from '../MemberListItem';
import { useDispatch } from 'react-redux';
import { useConversationPropsById } from '../../hooks/useParamSelector';
import { sogsV3RemoveAdmins } from '../../session/apis/open_group_api/sogsv3/sogsV3AddRemoveMods';
@ -25,23 +27,21 @@ async function removeMods(convoId: string, modsToRemove: Array<string>) {
window?.log?.info(`asked to remove moderators: ${modsToRemove}`);
try {
let res;
const convo = getConversationController().get(convoId);
const roomInfos = convo.toOpenGroupV2();
const modsToRemovePubkey = compact(modsToRemove.map(m => PubKey.from(m)));
res = await sogsV3RemoveAdmins(modsToRemovePubkey, roomInfos);
const res = await sogsV3RemoveAdmins(modsToRemovePubkey, roomInfos);
if (!res) {
window?.log?.warn('failed to remove moderators:', res);
ToastUtils.pushFailedToRemoveFromModerator();
return false;
} else {
window?.log?.info(`${modsToRemove} removed from moderators...`);
ToastUtils.pushUserRemovedFromModerators();
return true;
}
window?.log?.info(`${modsToRemove} removed from moderators...`);
ToastUtils.pushUserRemovedFromModerators();
return true;
} catch (e) {
window?.log?.error('Got error while removing moderator:', e);
return false;

View File

@ -3,9 +3,11 @@ import React from 'react';
import { shell } from 'electron';
import { useDispatch, useSelector } from 'react-redux';
import useHover from 'react-use/lib/useHover';
import styled from 'styled-components';
import ip2country from 'ip2country';
import countryLookup from 'country-code-lookup';
import ip2country from 'ip2country';
import { Snode } from '../../data/data';
import { onionPathModal } from '../../state/ducks/modalDialog';
import {
@ -15,12 +17,10 @@ import {
getOnionPathsCount,
} from '../../state/selectors/onions';
import { Flex } from '../basic/Flex';
// tslint:disable-next-line: no-submodule-imports
import useHover from 'react-use/lib/useHover';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIcon, SessionIconButton } from '../icon';
import { SessionWrapperModal } from '../SessionWrapperModal';
import styled from 'styled-components';
export type StatusLightType = {
glowStartDelay: number;
@ -202,7 +202,7 @@ export const ActionPanelOnionStatusLight = (props: {
// start with red
let iconColor = errorColor;
//if we are not online or the first path is not valid, we keep red as color
// if we are not online or the first path is not valid, we keep red as color
if (isOnline && firstPathLength > 1) {
iconColor =
onionPathsCount >= 2 ? defaultColor : onionPathsCount >= 1 ? connectingColor : errorColor;
@ -230,7 +230,6 @@ export const OnionPathModal = () => {
};
const dispatch = useDispatch();
return (
// tslint:disable-next-line: use-simple-attributes
<SessionWrapperModal
title={window.i18n('onionPathIndicatorTitle')}
confirmText={window.i18n('learnMore')}

View File

@ -46,7 +46,6 @@ const StyledReactClearAllContainer = styled(Flex)`
}
`;
// tslint:disable-next-line: max-func-body-length
export const ReactClearAllModal = (props: Props): ReactElement => {
const { reaction, messageId } = props;

View File

@ -1,5 +1,5 @@
import { isEmpty, isEqual } from 'lodash';
import React, { ReactElement, useEffect, useState } from 'react';
import React, { ReactElement, useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { Data } from '../../data/data';
@ -136,8 +136,8 @@ const ReactionSenders = (props: ReactionSendersProps) => {
<Avatar
size={AvatarSize.XS}
pubkey={sender}
onAvatarClick={async () => {
await handleAvatarClick(sender);
onAvatarClick={() => {
void handleAvatarClick(sender);
}}
/>
{sender === me ? (
@ -154,8 +154,8 @@ const ReactionSenders = (props: ReactionSendersProps) => {
<SessionIconButton
iconType="exit"
iconSize="small"
onClick={async () => {
await handleRemoveReaction();
onClick={() => {
void handleRemoveReaction();
}}
/>
)}
@ -174,7 +174,6 @@ const StyledCountText = styled.p`
color: var(--text-primary);
}
`;
// tslint:disable: use-simple-attributes
const CountText = ({ count, emoji }: { count: number; emoji: string }) => {
return (
@ -219,13 +218,12 @@ const handleSenders = (senders: Array<string>, me: string) => {
return updatedSenders;
};
// tslint:disable-next-line: max-func-body-length
export const ReactListModal = (props: Props): ReactElement => {
const { reaction, messageId } = props;
const dispatch = useDispatch();
const [reactions, setReactions] = useState<SortedReactionList>([]);
const reactionsMap = (reactions && Object.fromEntries(reactions)) || {};
const [currentReact, setCurrentReact] = useState('');
const [reactAriaLabel, setReactAriaLabel] = useState<string | undefined>();
const [count, setCount] = useState<number | null>(null);
@ -236,7 +234,12 @@ export const ReactListModal = (props: Props): ReactElement => {
const weAreModerator = useSelectedWeAreModerator();
const me = UserUtils.getOurPubKeyStrFromCache();
// tslint:disable: cyclomatic-complexity
const reactionsMap = useMemo(() => {
return (reactions && Object.fromEntries(reactions)) || {};
}, [reactions]);
const reactionsCount = reactionsMap[currentReact]?.count;
// TODO we should break down this useEffect, it is hard to read.
useEffect(() => {
if (currentReact === '' && currentReact !== reaction) {
setReactAriaLabel(
@ -278,7 +281,7 @@ export const ReactListModal = (props: Props): ReactElement => {
setSenders([]);
}
if (reactionsMap[currentReact]?.count && count !== reactionsMap[currentReact]?.count) {
if (reactionsCount && count !== reactionsCount) {
setCount(reactionsMap[currentReact].count);
}
}, [
@ -286,10 +289,11 @@ export const ReactListModal = (props: Props): ReactElement => {
currentReact,
me,
reaction,
reactionsMap[currentReact]?.count,
reactionsCount,
msgProps?.sortedReacts,
reactionsMap,
senders,
reactions,
]);
if (!msgProps) {

View File

@ -1,14 +1,14 @@
import React, { useState } from 'react';
import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { SpacerLG } from '../basic/Text';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionIcon, SessionIconSize, SessionIconType } from '../icon';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { Dispatch } from '@reduxjs/toolkit';
import { shell } from 'electron';
import React, { useState } from 'react';
import { MessageInteraction } from '../../interactions';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
import { SessionSpinner } from '../basic/SessionSpinner';
import { SpacerLG } from '../basic/Text';
import { SessionIcon, SessionIconSize, SessionIconType } from '../icon';
export interface SessionConfirmDialogProps {
message?: string;

View File

@ -1,136 +0,0 @@
import React from 'react';
import classNames from 'classnames';
import { SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionIconButton, SessionIconType } from '../icon';
interface Props {
title: string;
onClose: any;
showExitIcon?: boolean;
showHeader?: boolean;
headerReverse?: boolean;
//Maximum of two icons or buttons in header
headerIconButtons?: Array<{
iconType: SessionIconType;
iconRotation: number;
onClick?: any;
}>;
headerButtons?: Array<{
buttonType: SessionButtonType;
buttonColor: SessionButtonColor;
text: string;
onClick?: any;
}>;
}
interface State {
isVisible: boolean;
}
// NOTE This is currently unused.
export class SessionModal extends React.PureComponent<Props, State> {
public static defaultProps = {
showExitIcon: true,
showHeader: true,
headerReverse: false,
};
private node: HTMLDivElement | null;
constructor(props: any) {
super(props);
this.state = {
isVisible: true,
};
this.close = this.close.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.node = null;
}
public componentDidMount() {
window.addEventListener('keyup', this.onKeyUp);
document.addEventListener('mousedown', this.handleClick, false);
}
public componentWillUnmount() {
window.removeEventListener('keyup', this.onKeyUp);
document.removeEventListener('mousedown', this.handleClick, false);
}
public handleClick = (e: any) => {
if (this.node && this.node.contains(e.target)) {
return;
}
this.close();
};
public render() {
const { title, headerIconButtons, showExitIcon, showHeader, headerReverse } = this.props;
const { isVisible } = this.state;
return isVisible ? (
<div ref={node => (this.node = node)} className={'session-modal'}>
{showHeader ? (
<>
<div className={classNames('session-modal__header', headerReverse && 'reverse')}>
<div className="session-modal__header__close">
{showExitIcon ? (
<SessionIconButton
iconType="exit"
iconSize="small"
onClick={this.close}
dataTestId="modal-close-button"
/>
) : null}
</div>
<div className="session-modal__header__title">{title}</div>
<div className="session-modal__header__icons">
{headerIconButtons
? headerIconButtons.map((iconItem: any) => {
return (
<SessionIconButton
key={iconItem.iconType}
iconType={iconItem.iconType}
iconSize={'large'}
iconRotation={iconItem.iconRotation}
onClick={iconItem.onClick}
/>
);
})
: null}
</div>
</div>
</>
) : null}
<div className="session-modal__body">{this.props.children}</div>
</div>
) : null;
}
public close() {
this.setState({
isVisible: false,
});
document.removeEventListener('mousedown', this.handleClick, false);
if (this.props.onClose) {
this.props.onClose();
}
}
public onKeyUp(event: any) {
switch (event.key) {
case 'Esc':
case 'Escape':
this.close();
break;
default:
}
}
}

View File

@ -1,9 +1,10 @@
import React, { useState } from 'react';
import _ from 'lodash';
import { useDispatch } from 'react-redux';
import { getConversationController } from '../../session/conversations';
import _ from 'lodash';
import { SpacerLG } from '../basic/Text';
import { useDispatch } from 'react-redux';
import { changeNickNameModal } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';

View File

@ -1,9 +1,10 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import React from 'react';
import autoBind from 'auto-bind';
import { ToastUtils } from '../../session/utils';
import { Data } from '../../data/data';
import { SpacerSM } from '../basic/Text';
import autoBind from 'auto-bind';
import { sessionPassword } from '../../state/ducks/modalDialog';
import { LocalizerKeys } from '../../types/LocalizerKeys';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
@ -45,8 +46,7 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
document.addEventListener('keyup', this.onEnterPressed);
setTimeout(() => {
// tslint:disable-next-line: no-unused-expression
this.passportInput && this.passportInput.focus();
this.passportInput?.focus();
}, 1);
}
@ -270,7 +270,7 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
private async onEnterPressed(event: any) {
if (event.key === 'Enter') {
event.stopPropagation();
return this.setPassword();
await this.setPassword();
}
}
@ -293,7 +293,6 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.closeDialog();
}
// tslint:disable-next-line: cyclomatic-complexity
private async setPassword() {
const { passwordAction } = this.props;
const {

View File

@ -1,18 +1,21 @@
import React, { MouseEvent, useEffect, useState } from 'react';
import React, { MouseEvent, useState } from 'react';
import { QRCode } from 'react-qr-svg';
import { useDispatch } from 'react-redux';
import useMount from 'react-use/lib/useMount';
import styled from 'styled-components';
import { Data } from '../../data/data';
import { ToastUtils } from '../../session/utils';
import { matchesHash } from '../../util/passwordUtils';
import { Data } from '../../data/data';
import { QRCode } from 'react-qr-svg';
import { mn_decode } from '../../session/crypto/mnemonic';
import { SpacerSM } from '../basic/Text';
import { mnDecode } from '../../session/crypto/mnemonic';
import { recoveryPhraseModal } from '../../state/ducks/modalDialog';
import { useDispatch } from 'react-redux';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { getCurrentRecoveryPhrase } from '../../util/storage';
import styled from 'styled-components';
import { SpacerSM } from '../basic/Text';
import { saveQRCode } from '../../util/saveQRCode';
import { getCurrentRecoveryPhrase } from '../../util/storage';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
interface PasswordProps {
setPasswordValid: (val: boolean) => any;
@ -117,7 +120,7 @@ const Seed = (props: SeedProps) => {
const fgColor = 'var(--black-color)';
const dispatch = useDispatch();
const hexEncodedSeed = mn_decode(recoveryPhrase, 'english');
const hexEncodedSeed = mnDecode(recoveryPhrase, 'english');
const copyRecoveryPhrase = (recoveryPhraseToCopy: string) => {
window.clipboard.writeText(recoveryPhraseToCopy);
@ -193,43 +196,40 @@ const SessionSeedModalInner = (props: ModalInnerProps) => {
const [passwordHash, setPasswordHash] = useState('');
const dispatch = useDispatch();
useEffect(() => {
useMount(() => {
async function checkHasPassword() {
if (!loadingPassword) {
return;
}
const hash = await Data.getPasswordHash();
setHasPassword(!!hash);
setPasswordHash(hash || '');
setLoadingPassword(false);
}
async function getRecoveryPhrase() {
if (recoveryPhrase) {
return false;
}
const newRecoveryPhrase = getCurrentRecoveryPhrase();
setRecoveryPhrase(newRecoveryPhrase);
setLoadingSeed(false);
return true;
}
setTimeout(() => (document.getElementById('seed-input-password') as any)?.focus(), 100);
void checkHasPassword();
void getRecoveryPhrase();
}, []);
const i18n = window.i18n;
});
const onClose = () => dispatch(recoveryPhraseModal(null));
const checkHasPassword = async () => {
if (!loadingPassword) {
return;
}
const hash = await Data.getPasswordHash();
setHasPassword(!!hash);
setPasswordHash(hash || '');
setLoadingPassword(false);
};
const getRecoveryPhrase = async () => {
if (recoveryPhrase) {
return false;
}
const newRecoveryPhrase = getCurrentRecoveryPhrase();
setRecoveryPhrase(newRecoveryPhrase);
setLoadingSeed(false);
return true;
};
return (
<>
{!loadingSeed && (
<SessionWrapperModal
title={i18n('showRecoveryPhrase')}
title={window.i18n('showRecoveryPhrase')}
onClose={onClose}
showExitIcon={true}
>

View File

@ -1,21 +1,22 @@
import React from 'react';
import _ from 'lodash';
import { useDispatch } from 'react-redux';
import useKey from 'react-use/lib/useKey';
import styled from 'styled-components';
import { ToastUtils, UserUtils } from '../../session/utils';
import _ from 'lodash';
import { SpacerLG, Text } from '../basic/Text';
import { updateGroupMembersModal } from '../../state/ducks/modalDialog';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
import { MemberListItem } from '../MemberListItem';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { useDispatch } from 'react-redux';
import { useConversationPropsById, useWeAreAdmin } from '../../hooks/useParamSelector';
// tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey';
import { useSet } from '../../hooks/useSet';
import { getConversationController } from '../../session/conversations';
import { initiateClosedGroupUpdate } from '../../session/group/closed-group';
import styled from 'styled-components';
type Props = {
conversationId: string;
@ -110,7 +111,6 @@ const ZombiesList = ({ convoId }: { convoId: string }) => {
);
};
// tslint:disable-next-line: max-func-body-length
async function onSubmit(convoId: string, membersAfterUpdate: Array<string>) {
const convoFound = getConversationController().get(convoId);
if (!convoFound || !convoFound.isGroup()) {

View File

@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import React from 'react';
import classNames from 'classnames';
import autoBind from 'auto-bind';
import { Avatar, AvatarSize } from '../avatar/Avatar';
import { SpacerMD } from '../basic/Text';
import { updateGroupNameModal } from '../../state/ducks/modalDialog';
import autoBind from 'auto-bind';
import { ConversationModel } from '../../models/conversation';
import { getConversationController } from '../../session/conversations';
import { SessionWrapperModal } from '../SessionWrapperModal';
@ -91,14 +92,11 @@ export class UpdateGroupNameDialog extends React.Component<Props, State> {
this.state.errorDisplayed ? 'error-shown' : 'error-faded'
);
const isAdmin = this.convo.isPublic()
? false // disable editing of opengroup rooms as we don't handle them for now
: true;
const isAdmin = !this.convo.isPublic();
return (
<SessionWrapperModal
title={titleText}
// tslint:disable-next-line: no-void-expression
onClose={() => this.closeDialog()}
additionalClassName="update-group-dialog"
>
@ -200,7 +198,6 @@ export class UpdateGroupNameDialog extends React.Component<Props, State> {
if (!isPublic) {
return undefined;
}
// tslint:disable: use-simple-attributes
return (
<div className="avatar-center">

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react';
// tslint:disable no-submodule-imports
import useCopyToClipboard from 'react-use/lib/useCopyToClipboard';
@ -20,6 +19,7 @@ export const UserDetailsDialog = (props: UserDetailsModalState) => {
const size = isEnlargedImageShown ? AvatarSize.HUGE : AvatarSize.XL;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, copyToClipboard] = useCopyToClipboard();
function closeDialog() {

Some files were not shown because too many files have changed in this diff Show More