chore: replace tslint with eslint and fix linting issues
This commit is contained in:
parent
49955a3947
commit
d43d6abbae
|
@ -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
|
66
.eslintrc.js
66
.eslintrc.js
|
@ -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' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* global window */
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* global window */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const url = require('url');
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
|
|
13
package.json
13
package.json
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* global window */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const url = require('url');
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -40,7 +40,7 @@ export const AboutView = () => {
|
|||
theme: window.theme,
|
||||
});
|
||||
}
|
||||
}, [window.theme]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SessionTheme>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// tslint:disable:react-a11y-anchors
|
||||
|
||||
import React from 'react';
|
||||
import * as GoogleChrome from '../util/GoogleChrome';
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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={
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -29,7 +29,6 @@ const SessionToastContainerPrivate = () => {
|
|||
);
|
||||
};
|
||||
|
||||
// tslint:disable-next-line: no-default-export
|
||||
export const SessionToastContainer = styled(SessionToastContainerPrivate).attrs({
|
||||
// custom props
|
||||
})`
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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?.();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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()');
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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 <></>;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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% {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 '';
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -211,7 +211,7 @@ export const DeleteAccountModal = () => {
|
|||
*/
|
||||
const onClickCancelHandler = useCallback(() => {
|
||||
dispatch(updateDeleteAccountModal(null));
|
||||
}, []);
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<SessionWrapperModal
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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')}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue