mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Merge remote-tracking branch 'upstream/unstable' into fix/long-username-quote-author-overflow
This commit is contained in:
commit
4b3c31de89
427 changed files with 4534 additions and 5572 deletions
|
@ -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', 'import/no-extraneous-dependencies': 'off' },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
4
.github/workflows/build-binaries.yml
vendored
4
.github/workflows/build-binaries.yml
vendored
|
@ -10,6 +10,9 @@ on:
|
|||
branches:
|
||||
- clearnet
|
||||
- unstable
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -50,7 +53,6 @@ jobs:
|
|||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
npm install --global yarn node-gyp@latest
|
||||
npm config set msvs_version 2022
|
||||
|
||||
- name: Install Desktop node_modules
|
||||
if: steps.cache-desktop-modules.outputs.cache-hit != 'true'
|
||||
|
|
9
.github/workflows/pull-request.yml
vendored
9
.github/workflows/pull-request.yml
vendored
|
@ -6,6 +6,11 @@ on:
|
|||
branches:
|
||||
- clearnet
|
||||
- unstable
|
||||
- unstable1
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -31,7 +36,8 @@ jobs:
|
|||
|
||||
- name: Cache Desktop node_modules
|
||||
id: cache-desktop-modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
if: runner.os != 'Windows'
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-${{ hashFiles('package.json', 'yarn.lock', 'patches/**') }}
|
||||
|
@ -45,7 +51,6 @@ jobs:
|
|||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
npm install --global node-gyp@latest
|
||||
npm config set msvs_version 2022
|
||||
|
||||
- name: Install Desktop node_modules
|
||||
if: steps.cache-desktop-modules.outputs.cache-hit != 'true'
|
||||
|
|
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
|
@ -30,7 +30,7 @@ jobs:
|
|||
|
||||
- name: Cache Desktop node_modules
|
||||
id: cache-desktop-modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
if: runner.os != 'Windows'
|
||||
with:
|
||||
path: node_modules
|
||||
|
@ -45,7 +45,6 @@ jobs:
|
|||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
npm install --global node-gyp@latest
|
||||
npm config set msvs_version 2022
|
||||
|
||||
- name: Install Desktop node_modules
|
||||
if: steps.cache-desktop-modules.outputs.cache-hit != 'true'
|
||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
|||
16.13.0
|
||||
18.15.0
|
|
@ -49,7 +49,7 @@ base64 -i certificate.p12 -o encoded.txt
|
|||
|
||||
### Node version
|
||||
|
||||
You will need node `16.13.0`.
|
||||
You will need node `18.15.0`.
|
||||
This can be done by using [nvm](https://github.com/nvm-sh/nvm) and running `nvm use` or you can install it manually.
|
||||
Once nvm is installed, just run `nvm install` to install the version from the `.nvmrc` file and then `nvm use` to use it.
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
# Session Desktop
|
||||
|
||||
[Download at getsession.org](https://getsession.org/download)
|
||||
|
||||
## Summary
|
||||
|
||||
Session integrates directly with [Oxen Service Nodes](https://docs.oxen.io/about-the-oxen-blockchain/oxen-service-nodes), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP Addresses. For a full understanding of how Session works, read the [Session Whitepaper](https://getsession.org/whitepaper).
|
||||
<br/><br/>
|
||||
![DesktopSession](https://i.imgur.com/ZnHvYjo.jpg)
|
||||
![DesktopSession](https://i.imgur.com/ydVhH00.png)
|
||||
|
||||
## Want to Contribute? Found a Bug or Have a feature request?
|
||||
|
||||
|
|
|
@ -181,7 +181,6 @@
|
|||
"messageBodyMissing": "Please enter a message body.",
|
||||
"messageBody": "Message body",
|
||||
"unblockToSend": "Unblock this contact to send a message.",
|
||||
"unblockGroupToSend": "This group is blocked. Unlock it if you would like to send a message.",
|
||||
"youChangedTheTimer": "You set the disappearing message timer to $time$",
|
||||
"timerSetOnSync": "Updated disappearing message timer to $time$",
|
||||
"theyChangedTheTimer": "$name$ set the disappearing message timer to $time$",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* global window */
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const { notarize } = require('electron-notarize');
|
||||
const { notarize } = require('@electron/notarize');
|
||||
|
||||
/*
|
||||
Pre-requisites: https://github.com/electron/electron-notarize#prerequisites
|
||||
|
|
|
@ -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 = {
|
||||
|
|
96
package.json
96
package.json
|
@ -22,7 +22,6 @@
|
|||
"lodash": "^4.17.20",
|
||||
"ini": "^1.3.6",
|
||||
"ejs": "^3.1.7",
|
||||
"electron": "^17.2.0",
|
||||
"react": "17.0.2",
|
||||
"@types/react": "17.0.2",
|
||||
"glob-parent": "^6.0.1",
|
||||
|
@ -40,18 +39,16 @@
|
|||
"build-everything": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass && tsc && yarn build:workers",
|
||||
"build-everything:watch": "yarn clean && yarn protobuf && yarn update-git-info && yarn sass && yarn build:workers && tsc -w",
|
||||
"build:workers": "yarn worker:utils && yarn worker:libsession",
|
||||
"watch": "yarn clean && yarn protobuf && yarn update-git-info && concurrently 'yarn build-everything:watch'",
|
||||
"watch": "yarn clean && yarn protobuf && yarn update-git-info && yarn build-everything:watch",
|
||||
"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 .",
|
||||
|
||||
"integration-test-snapshots": "npx playwright test -g 'profile picture' --update-snapshots",
|
||||
"test": "mocha -r jsdom-global/register --recursive --exit --timeout 10000 \"./ts/test/**/*_test.js\"",
|
||||
"coverage": "nyc --reporter=html mocha -r jsdom-global/register --recursive --exit --timeout 10000 \"./ts/test/**/*_test.js\"",
|
||||
"build-release": "run-script-os",
|
||||
"build-release-non-linux": "yarn build-everything && cross-env SIGNAL_ENV=production electron-builder --config.extraMetadata.environment=production --publish=never --config.directories.output=release",
|
||||
"build-release:win32": "yarn build-release-non-linux",
|
||||
|
@ -72,51 +69,46 @@
|
|||
"worker:libsession": "rimraf 'ts/webworker/workers/node/libsession/*.node' && webpack --config=./libsession.worker.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emoji-mart/data": "^1.0.6",
|
||||
"@emoji-mart/react": "^1.0.1",
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
"@reduxjs/toolkit": "1.8.5",
|
||||
"@signalapp/better-sqlite3": "^8.4.3",
|
||||
"@types/react-mentions": "^4.1.8",
|
||||
"abort-controller": "3.0.0",
|
||||
"auto-bind": "^4.0.0",
|
||||
"backbone": "1.3.3",
|
||||
"better-sqlite3": "https://github.com/oxen-io/session-better-sqlite3#af47530acea25800d22b5e1ae834b709d2bfca40",
|
||||
"blob-util": "2.0.2",
|
||||
"blueimp-canvas-to-blob": "^3.29.0",
|
||||
"blueimp-load-image": "5.14.0",
|
||||
"buffer-crc32": "0.2.13",
|
||||
"bunyan": "^1.8.15",
|
||||
"bytebuffer": "^5.0.1",
|
||||
"classnames": "2.2.5",
|
||||
"color": "^3.1.2",
|
||||
"config": "1.28.1",
|
||||
"country-code-lookup": "^0.0.19",
|
||||
"curve25519-js": "https://github.com/oxen-io/curve25519-js",
|
||||
"dompurify": "^2.0.7",
|
||||
"electron-is-dev": "^1.1.0",
|
||||
"electron-localshortcut": "^3.2.1",
|
||||
"electron-updater": "^4.2.2",
|
||||
"emoji-mart": "^5.2.2",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"filesize": "3.6.1",
|
||||
"firstline": "1.2.1",
|
||||
"fs-extra": "9.0.0",
|
||||
"glob": "7.1.2",
|
||||
"image-type": "^4.1.0",
|
||||
"ip2country": "1.0.1",
|
||||
"libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.2.2/libsession_util_nodejs-v0.2.2.tar.gz",
|
||||
"libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.2.5/libsession_util_nodejs-v0.2.5.tar.gz",
|
||||
"libsodium-wrappers-sumo": "^0.7.9",
|
||||
"linkify-it": "^4.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"long": "^4.0.0",
|
||||
"mic-recorder-to-mp3": "^2.2.2",
|
||||
"moment": "^2.29.4",
|
||||
"mustache": "2.3.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"os-locale": "5.0.0",
|
||||
"p-retry": "^4.2.0",
|
||||
"pify": "3.0.0",
|
||||
"protobufjs": "^6.11.2",
|
||||
"queue-promise": "^2.2.1",
|
||||
"rc-slider": "^8.7.1",
|
||||
"protobufjs": "^7.2.4",
|
||||
"rc-slider": "^10.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-contexify": "5.0.0",
|
||||
"react-dom": "^17.0.2",
|
||||
|
@ -124,12 +116,11 @@
|
|||
"react-h5-audio-player": "^3.2.0",
|
||||
"react-intersection-observer": "^8.30.3",
|
||||
"react-mentions": "^4.4.9",
|
||||
"react-portal": "^4.2.0",
|
||||
"react-qr-svg": "^2.2.1",
|
||||
"react-redux": "8.0.4",
|
||||
"react-toastify": "^6.0.9",
|
||||
"react-use": "^17.2.1",
|
||||
"react-virtualized": "9.22.3",
|
||||
"react-use": "^17.4.0",
|
||||
"react-virtualized": "^9.22.4",
|
||||
"read-last-lines-ts": "^1.2.1",
|
||||
"redux": "4.2.0",
|
||||
"redux-logger": "3.0.6",
|
||||
|
@ -137,14 +128,15 @@
|
|||
"redux-promise-middleware": "^6.1.2",
|
||||
"rimraf": "2.6.2",
|
||||
"sanitize.css": "^12.0.1",
|
||||
"semver": "5.4.1",
|
||||
"semver": "^7.5.4",
|
||||
"styled-components": "5.1.1",
|
||||
"uuid": "8.3.2"
|
||||
"uuid": "8.3.2",
|
||||
"webrtc-adapter": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "^2.1.0",
|
||||
"@playwright/test": "1.16.3",
|
||||
"@types/backbone": "1.4.2",
|
||||
"@types/better-sqlite3": "7.4.0",
|
||||
"@types/blueimp-load-image": "5.14.4",
|
||||
"@types/buffer-crc32": "^0.2.0",
|
||||
"@types/bunyan": "^1.8.8",
|
||||
|
@ -152,11 +144,9 @@
|
|||
"@types/chai": "4.2.18",
|
||||
"@types/chai-as-promised": "^7.1.2",
|
||||
"@types/classnames": "2.2.3",
|
||||
"@types/color": "^3.0.0",
|
||||
"@types/config": "0.0.34",
|
||||
"@types/dompurify": "^2.0.0",
|
||||
"@types/electron-localshortcut": "^3.1.0",
|
||||
"@types/emoji-mart": "3.0.9",
|
||||
"@types/filesize": "3.6.0",
|
||||
"@types/firstline": "^2.0.2",
|
||||
"@types/fs-extra": "5.0.5",
|
||||
|
@ -164,14 +154,10 @@
|
|||
"@types/linkify-it": "^3.0.2",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/mocha": "5.0.0",
|
||||
"@types/mustache": "^4.1.2",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/pify": "3.0.2",
|
||||
"@types/rc-slider": "^8.6.5",
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/react-dom": "^17.0.2",
|
||||
"@types/react-mic": "^12.4.1",
|
||||
"@types/react-portal": "^4.0.2",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-virtualized": "9.18.12",
|
||||
"@types/redux-logger": "3.0.7",
|
||||
|
@ -180,57 +166,46 @@
|
|||
"@types/sinon": "9.0.4",
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"@types/uuid": "8.3.4",
|
||||
"asar": "3.1.0",
|
||||
"buffer": "^6.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"chai": "^4.3.4",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-bytes": "^0.1.2",
|
||||
"cmake-js": "^7.2.1",
|
||||
"concurrently": "^7.4.0",
|
||||
"cross-env": "^6.0.3",
|
||||
"css-loader": "^6.7.2",
|
||||
"dmg-builder": "23.6.0",
|
||||
"electron": "^17.2.0",
|
||||
"electron-builder": "22.8.0",
|
||||
"electron-notarize": "^0.2.0",
|
||||
"esbuild": "^0.14.29",
|
||||
"eslint": "^8.15.0",
|
||||
"electron": "25.3.0",
|
||||
"electron-builder": "23.0.8",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-mocha": "^10.0.4",
|
||||
"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": "^22.1.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"mini-css-extract-plugin": "^2.7.5",
|
||||
"mocha": "10.0.0",
|
||||
"nan": "^2.17.0",
|
||||
"node-gyp": "9.0.0",
|
||||
"node-loader": "^2.0.0",
|
||||
"nyc": "^15.1.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"playwright": "1.16.3",
|
||||
"postcss-loader": "^7.0.2",
|
||||
"postinstall-prepare": "^1.0.1",
|
||||
"prettier": "1.19.0",
|
||||
"process": "^0.11.10",
|
||||
"protobufjs-cli": "^1.1.1",
|
||||
"run-script-os": "^1.1.6",
|
||||
"sass": "^1.60.0",
|
||||
"sass-loader": "^13.2.2",
|
||||
"sinon": "9.0.2",
|
||||
"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"
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.13.0"
|
||||
"node": "18.15.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.loki-project.messenger-desktop",
|
||||
|
@ -285,7 +260,7 @@
|
|||
"icon": "build/icon-linux.icns"
|
||||
},
|
||||
"asarUnpack": [
|
||||
"node_modules/better-sqlite3/build/Release/better_sqlite3.node",
|
||||
"node_modules/@signalapp/better-sqlite3/build/Release/better_sqlite3.node",
|
||||
"node_modules/libsession_util_nodejs/build/Release/libsession_util_nodejs.node",
|
||||
"ts/webworker/workers/node/libsession/*.node",
|
||||
"ts/mains/main_node.js"
|
||||
|
@ -364,9 +339,12 @@
|
|||
"!build/*.js",
|
||||
"build/afterPackHook.js",
|
||||
"build/launcher-script.sh",
|
||||
"!node_modules/better-sqlite3/deps/*",
|
||||
"!node_modules/better-sqlite3/src/*",
|
||||
"node_modules/better-sqlite3/build/Release/better_sqlite3.node",
|
||||
"!node_modules/@signalapp/better-sqlite3/deps/*",
|
||||
"!node_modules/@signalapp/better-sqlite3/src/*",
|
||||
"!node_modules/@signalapp/better-sqlite3/build/Release/obj/*",
|
||||
"node_modules/@signalapp/better-sqlite3/build/Release/better_sqlite3.node",
|
||||
"!node_modules/libsession_util_nodejs/libsession-util/*",
|
||||
"!node_modules/libsession_util_nodejs/src/*",
|
||||
"ts/webworker/workers/node/libsession/*.node",
|
||||
"!dev-app-update.yml"
|
||||
]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* global window */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const url = require('url');
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
diff --git a/node_modules/component-classes/index.js b/node_modules/component-classes/index.js
|
||||
index eb9d292..752ebf2 100644
|
||||
--- a/node_modules/component-classes/index.js
|
||||
+++ b/node_modules/component-classes/index.js
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
try {
|
||||
- var index = require('indexof');
|
||||
+ // var index = require('indexof');
|
||||
} catch (err) {
|
||||
var index = require('component-indexof');
|
||||
}
|
|
@ -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 = {
|
||||
|
|
2
ts/OS.ts
2
ts/OS.ts
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
import os from 'os';
|
||||
import _ from 'lodash';
|
||||
import semver from 'semver';
|
||||
|
||||
export const isMacOS = () => process.platform === 'darwin';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -4,6 +4,7 @@ import styled from 'styled-components';
|
|||
import { getShowScrollButton } from '../state/selectors/conversations';
|
||||
|
||||
import { SessionIconButton } from './icon';
|
||||
import { Noop } from '../types/Util';
|
||||
|
||||
const SessionScrollButtonDiv = styled.div`
|
||||
position: fixed;
|
||||
|
@ -17,7 +18,7 @@ const SessionScrollButtonDiv = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
export const SessionScrollButton = (props: { onClickScrollBottom: () => void }) => {
|
||||
export const SessionScrollButton = (props: { onClickScrollBottom: Noop }) => {
|
||||
const show = useSelector(getShowScrollButton);
|
||||
|
||||
return (
|
||||
|
|
|
@ -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?.();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
|||
|
||||
import classNames from 'classnames';
|
||||
import { SessionIconButton } from '../icon';
|
||||
import { Noop } from '../../types/Util';
|
||||
|
||||
type Props = {
|
||||
label?: string;
|
||||
|
@ -44,7 +45,7 @@ const ErrorItem = (props: { error: string | undefined }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const ShowHideButton = (props: { toggleForceShow: () => void }) => {
|
||||
const ShowHideButton = (props: { toggleForceShow: Noop }) => {
|
||||
return <SessionIconButton iconType="eye" iconSize="medium" onClick={props.toggleForceShow} />;
|
||||
};
|
||||
|
||||
|
|
|
@ -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,6 +282,7 @@ export const HangUpButton = ({ isFullScreen }: { isFullScreen: boolean }) => {
|
|||
iconType="hangup"
|
||||
iconColor="var(--danger-color)"
|
||||
borderRadius="50%"
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClick={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,6 +35,7 @@ export const IncomingCallDialog = () => {
|
|||
useEffect(() => {
|
||||
let timeout: NodeJS.Timeout;
|
||||
if (incomingCallFromPubkey) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
timeout = global.setTimeout(async () => {
|
||||
if (incomingCallFromPubkey) {
|
||||
window.log.info(
|
||||
|
@ -54,7 +55,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;
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { CSSProperties } from 'styled-components';
|
||||
|
||||
import { Emojify } from './Emojify';
|
||||
import { useConversationUsernameOrShorten, useIsPrivate } from '../../hooks/useParamSelector';
|
||||
import { CSSProperties } from 'styled-components';
|
||||
|
||||
type Props = {
|
||||
pubkey: string;
|
||||
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,21 +1,22 @@
|
|||
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 Picker from '@emoji-mart/react';
|
||||
|
||||
import { getTheme, isDarkTheme } from '../../state/selectors/theme';
|
||||
import { FixedBaseEmoji, FixedPickerProps } from '../../types/Reaction';
|
||||
import {
|
||||
COLORS,
|
||||
ColorsType,
|
||||
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';
|
||||
import { i18nEmojiData } from '../../util/emoji';
|
||||
import { FixedBaseEmoji } from '../../types/Reaction';
|
||||
|
||||
export const StyledEmojiPanel = styled.div<{
|
||||
isModal: boolean;
|
||||
|
@ -90,13 +91,14 @@ type Props = {
|
|||
onKeyDown?: (event: any) => void;
|
||||
};
|
||||
|
||||
const pickerProps: FixedPickerProps = {
|
||||
const pickerProps = {
|
||||
title: '',
|
||||
showPreview: true,
|
||||
autoFocus: true,
|
||||
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 +111,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,9 +51,10 @@ type Props = SessionMessageListProps & {
|
|||
scrollToNow: () => Promise<void>;
|
||||
};
|
||||
|
||||
const StyledMessagesContainer = styled.div<{}>`
|
||||
const StyledMessagesContainer = styled.div`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
gap: var(--margins-xxs);
|
||||
flex-direction: column-reverse;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
|
@ -143,6 +144,7 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
|
|||
</ScrollToLoadedMessageContext.Provider>
|
||||
|
||||
<SessionScrollButton
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClickScrollBottom={this.props.scrollToNow}
|
||||
key="scroll-down-button"
|
||||
/>
|
||||
|
@ -249,7 +251,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,6 +301,7 @@ export const SessionRightPanelWithDetails = () => {
|
|||
<StyledGroupSettingsItem
|
||||
className="group-settings-item"
|
||||
role="button"
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClick={async () => {
|
||||
await showUpdateGroupNameByConvoId(selectedConvoKey);
|
||||
}}
|
||||
|
@ -342,6 +336,7 @@ export const SessionRightPanelWithDetails = () => {
|
|||
<StyledGroupSettingsItem
|
||||
className="group-settings-item"
|
||||
role="button"
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClick={async () => {
|
||||
await showUpdateGroupMembersByConvoId(selectedConvoKey);
|
||||
}}
|
||||
|
@ -360,7 +355,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,16 +1,15 @@
|
|||
import React from 'react';
|
||||
|
||||
import { TypingAnimation } from './TypingAnimation';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ConversationTypeEnum } from '../../models/conversationAttributes';
|
||||
import { useSelectedIsGroup } from '../../state/selectors/selectedConversation';
|
||||
import { TypingAnimation } from './TypingAnimation';
|
||||
|
||||
interface TypingBubbleProps {
|
||||
conversationType: ConversationTypeEnum;
|
||||
isTyping: boolean;
|
||||
}
|
||||
|
||||
const TypingBubbleContainer = styled.div<TypingBubbleProps>`
|
||||
const TypingBubbleContainer = styled.div<Pick<TypingBubbleProps, 'isTyping'>>`
|
||||
height: ${props => (props.isTyping ? 'auto' : '0px')};
|
||||
display: flow-root;
|
||||
padding-bottom: ${props => (props.isTyping ? '4px' : '0px')};
|
||||
|
@ -23,13 +22,13 @@ const TypingBubbleContainer = styled.div<TypingBubbleProps>`
|
|||
`;
|
||||
|
||||
export const TypingBubble = (props: TypingBubbleProps) => {
|
||||
const isOpenOrClosedGroup = useSelectedIsGroup();
|
||||
if (!isOpenOrClosedGroup || !props.isTyping) {
|
||||
const isPrivate = props.conversationType === ConversationTypeEnum.PRIVATE;
|
||||
if (!isPrivate || !props.isTyping) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TypingBubbleContainer {...props}>
|
||||
<TypingBubbleContainer isTyping={props.isTyping}>
|
||||
<TypingAnimation />
|
||||
</TypingBubbleContainer>
|
||||
);
|
||||
|
|
|
@ -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,6 +404,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
return (
|
||||
<SessionRecording
|
||||
sendVoiceMessage={this.sendVoiceMessage}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
|
||||
onExitVoiceNoteView={this.onExitVoiceNoteView}
|
||||
/>
|
||||
|
@ -411,11 +414,11 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
private renderCompositionView() {
|
||||
const { showEmojiPanel } = this.state;
|
||||
const { typingEnabled } = this.props;
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
|
||||
return (
|
||||
<>
|
||||
{typingEnabled && <AddStagedAttachmentButton onClick={this.onChooseAttachment} />}
|
||||
|
||||
<input
|
||||
className="hidden"
|
||||
placeholder="Attachment"
|
||||
|
@ -424,9 +427,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
type="file"
|
||||
onChange={this.onChoseAttachment}
|
||||
/>
|
||||
|
||||
{typingEnabled && <StartRecordingButton onClick={this.onLoadVoiceNoteView} />}
|
||||
|
||||
<StyledSendMessageInput
|
||||
role="main"
|
||||
onClick={this.focusCompositionBox} // used to focus on the textarea when clicking in its container
|
||||
|
@ -437,12 +438,10 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
>
|
||||
{this.renderTextArea()}
|
||||
</StyledSendMessageInput>
|
||||
|
||||
{typingEnabled && (
|
||||
<ToggleEmojiButton ref={this.emojiPanelButton} onClick={this.toggleEmojiPanel} />
|
||||
)}
|
||||
<SendMessageButton onClick={this.onSendMessage} />
|
||||
|
||||
{typingEnabled && showEmojiPanel && (
|
||||
<StyledEmojiPanelContainer role="button">
|
||||
<SessionEmojiPanel
|
||||
|
@ -456,6 +455,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
</>
|
||||
);
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-misused-promises */
|
||||
|
||||
private renderTextArea() {
|
||||
const { i18n } = window;
|
||||
|
@ -472,16 +472,13 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
if (left) {
|
||||
return i18n('youLeftTheGroup');
|
||||
}
|
||||
if (isBlocked && isPrivate) {
|
||||
if (isBlocked) {
|
||||
return i18n('unblockToSend');
|
||||
}
|
||||
if (isBlocked && !isPrivate) {
|
||||
return i18n('unblockGroupToSend');
|
||||
}
|
||||
return i18n('sendMessage');
|
||||
};
|
||||
|
||||
const { isKickedFromGroup, left, isPrivate, isBlocked } = this.props.selectedConversation;
|
||||
const { isKickedFromGroup, left, isBlocked } = this.props.selectedConversation;
|
||||
const messagePlaceHolder = makeMessagePlaceHolderText();
|
||||
const { typingEnabled } = this.props;
|
||||
const neverMatchingRegex = /($a)/;
|
||||
|
@ -490,7 +487,9 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
<MentionsInput
|
||||
value={draft}
|
||||
onChange={this.onChange}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onKeyDown={this.onKeyDown}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onKeyUp={this.onKeyUp}
|
||||
placeholder={messagePlaceHolder}
|
||||
spellCheck={true}
|
||||
|
@ -554,14 +553,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 +676,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 +845,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');
|
||||
|
@ -858,14 +859,10 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (selectedConversation.isBlocked && selectedConversation.isPrivate) {
|
||||
if (selectedConversation.isBlocked) {
|
||||
ToastUtils.pushUnblockToSend();
|
||||
return;
|
||||
}
|
||||
if (selectedConversation.isBlocked && !selectedConversation.isPrivate) {
|
||||
ToastUtils.pushUnblockToSendGroup();
|
||||
return;
|
||||
}
|
||||
// Verify message length
|
||||
const msgLen = messagePlaintext?.length || 0;
|
||||
if (msgLen === 0 && this.props.stagedAttachments?.length === 0) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { SessionIconButton } from '../../icon';
|
||||
import { Noop } from '../../../types/Util';
|
||||
|
||||
const StyledChatButtonContainer = styled.div`
|
||||
.session-icon-button {
|
||||
|
@ -14,7 +15,7 @@ const StyledChatButtonContainer = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
export const AddStagedAttachmentButton = (props: { onClick: () => void }) => {
|
||||
export const AddStagedAttachmentButton = (props: { onClick: Noop }) => {
|
||||
return (
|
||||
<StyledChatButtonContainer>
|
||||
<SessionIconButton
|
||||
|
@ -31,7 +32,7 @@ export const AddStagedAttachmentButton = (props: { onClick: () => void }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const StartRecordingButton = (props: { onClick: () => void }) => {
|
||||
export const StartRecordingButton = (props: { onClick: Noop }) => {
|
||||
return (
|
||||
<StyledChatButtonContainer>
|
||||
<SessionIconButton
|
||||
|
@ -48,7 +49,8 @@ export const StartRecordingButton = (props: { onClick: () => void }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const ToggleEmojiButton = React.forwardRef<HTMLDivElement, { onClick: () => void }>(
|
||||
// eslint-disable-next-line react/display-name
|
||||
export const ToggleEmojiButton = React.forwardRef<HTMLDivElement, { onClick: Noop }>(
|
||||
(props, ref) => {
|
||||
return (
|
||||
<StyledChatButtonContainer>
|
||||
|
@ -68,7 +70,7 @@ export const ToggleEmojiButton = React.forwardRef<HTMLDivElement, { onClick: ()
|
|||
}
|
||||
);
|
||||
|
||||
export const SendMessageButton = (props: { onClick: () => void }) => {
|
||||
export const SendMessageButton = (props: { onClick: Noop }) => {
|
||||
return (
|
||||
<StyledChatButtonContainer className="send-message-button">
|
||||
<SessionIconButton
|
||||
|
|
|
@ -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,6 +111,7 @@ export const ClickToTrustSender = (props: { messageId: string }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
<StyledTrustSenderUI onClick={openConfirmationModal}>
|
||||
<SessionIcon iconSize="small" iconType="gallery" />
|
||||
<ClickToDownload>{window.i18n('clickToTrustContact')}</ClickToDownload>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -37,7 +37,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;
|
||||
|
|
|
@ -6,7 +6,7 @@ import { MessageRenderingProps } from '../../../../models/messageType';
|
|||
import { findCachedBlindedMatchOrLookItUp } from '../../../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
|
||||
import { getConversationController } from '../../../../session/conversations';
|
||||
import { getSodiumRenderer } from '../../../../session/crypto';
|
||||
import { PubKey } from '../../../../session/types';
|
||||
import { KeyPrefixType, PubKey } from '../../../../session/types';
|
||||
import { openConversationWithMessages } from '../../../../state/ducks/conversations';
|
||||
import { updateUserDetailsModal } from '../../../../state/ducks/modalDialog';
|
||||
import {
|
||||
|
@ -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,14 +53,13 @@ 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 (isPublic && !PubKey.hasBlindedPrefix(sender)) {
|
||||
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
|
||||
const convoWithSender = getConversationController().get(sender);
|
||||
|
@ -84,6 +82,11 @@ export const MessageAvatar = (props: Props) => {
|
|||
}
|
||||
|
||||
if (isPublic && selectedConvoKey) {
|
||||
if (sender.startsWith(KeyPrefixType.blinded25)) {
|
||||
window.log.info('onMessageAvatarClick: blinded25 convo click are disabled currently...');
|
||||
|
||||
return;
|
||||
}
|
||||
const convoOpen = getConversationController().get(selectedConvoKey);
|
||||
const room = OpenGroupData.getV2OpenGroupRoom(convoOpen.id);
|
||||
let privateConvoToOpen = sender;
|
||||
|
@ -107,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;
|
||||
|
@ -124,6 +131,7 @@ export const MessageAvatar = (props: Props) => {
|
|||
if (!lastMessageOfSeries) {
|
||||
return <div style={{ marginInlineEnd: '60px' }} key={`msg-avatar-${sender}`} />;
|
||||
}
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
|
||||
return (
|
||||
<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;
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +90,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);
|
||||
|
@ -102,7 +104,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)
|
||||
|
@ -124,7 +126,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) {
|
||||
|
@ -140,8 +142,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;
|
||||
|
@ -61,13 +60,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>) => {
|
||||
|
@ -85,12 +84,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;
|
||||
|
@ -101,8 +100,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);
|
||||
};
|
||||
|
@ -151,6 +148,7 @@ export const MessageContentWithStatuses = (props: Props) => {
|
|||
{enableReactions && (
|
||||
<MessageReactions
|
||||
messageId={messageId}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClick={handleMessageReaction}
|
||||
popupReaction={popupReaction}
|
||||
setPopupReaction={setPopupReaction}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
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 +24,6 @@ import {
|
|||
showMessageDetailsView,
|
||||
toggleSelectedMessageId,
|
||||
} from '../../../../state/ducks/conversations';
|
||||
import { StateType } from '../../../../state/reducer';
|
||||
import { getMessageContextMenuProps } from '../../../../state/selectors/conversations';
|
||||
import {
|
||||
useSelectedConversationKey,
|
||||
useSelectedIsBlocked,
|
||||
|
@ -36,10 +36,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 +59,6 @@ export type MessageContextMenuSelectorProps = Pick<
|
|||
| 'text'
|
||||
| 'serverTimestamp'
|
||||
| 'timestamp'
|
||||
| 'isDeletableForEveryone'
|
||||
>;
|
||||
|
||||
type Props = { messageId: string; contextMenuId: string; enableReactions: boolean };
|
||||
|
@ -76,7 +86,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={onRetry}>{window.i18n('resend')}</Item> : null;
|
||||
};
|
||||
|
||||
export const MessageContextMenu = (props: Props) => {
|
||||
const { messageId, contextMenuId, enableReactions } = props;
|
||||
const dispatch = useDispatch();
|
||||
|
@ -85,32 +212,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 +260,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 +269,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 +296,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 +317,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,14 +339,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}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onEmojiClicked={onEmojiClick}
|
||||
show={showEmojiPanel}
|
||||
isModal={true}
|
||||
|
@ -307,11 +366,10 @@ export const MessageContextMenu = (props: Props) => {
|
|||
animation={animation.fade}
|
||||
>
|
||||
{enableReactions && (
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
<MessageReactBar action={onEmojiClick} additionalAction={onShowEmoji} />
|
||||
)}
|
||||
{attachments?.length ? (
|
||||
<Item onClick={saveAttachment}>{window.i18n('downloadAttachment')}</Item>
|
||||
) : null}
|
||||
<SaveAttachment messageId={messageId} />
|
||||
|
||||
<Item onClick={copyText}>{window.i18n('copyMessage')}</Item>
|
||||
{(isSent || !isOutgoing) && (
|
||||
|
@ -320,33 +378,13 @@ export const MessageContextMenu = (props: Props) => {
|
|||
{(!isPublic || isOutgoing) && (
|
||||
<Item onClick={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,17 +1,17 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { isImageAttachment } from '../../../../types/Attachment';
|
||||
import { Image } from '../../Image';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { MessageRenderingProps } from '../../../../models/messageType';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getIsMessageSelectionMode } from '../../../../state/selectors/conversations';
|
||||
import { SessionIcon } from '../../../icon';
|
||||
import { showLinkVisitWarningDialog } from '../../../dialog/SessionConfirm';
|
||||
import {
|
||||
useMessageAttachments,
|
||||
useMessageDirection,
|
||||
useMessageLinkPreview,
|
||||
} from '../../../../state/selectors';
|
||||
import { useIsMessageSelectionMode } from '../../../../state/selectors/selectedConversation';
|
||||
import { isImageAttachment } from '../../../../types/Attachment';
|
||||
import { showLinkVisitWarningDialog } from '../../../dialog/SessionConfirm';
|
||||
import { SessionIcon } from '../../../icon';
|
||||
import { Image } from '../../Image';
|
||||
|
||||
export type MessageLinkPreviewSelectorProps = Pick<
|
||||
MessageRenderingProps,
|
||||
|
@ -30,7 +30,7 @@ export const MessageLinkPreview = (props: Props) => {
|
|||
const direction = useMessageDirection(props.messageId);
|
||||
const attachments = useMessageAttachments(props.messageId);
|
||||
const previews = useMessageLinkPreview(props.messageId);
|
||||
const isMessageSelectionMode = useSelector(getIsMessageSelectionMode);
|
||||
const isMessageSelectionMode = useIsMessageSelectionMode();
|
||||
|
||||
if (!props.messageId) {
|
||||
return null;
|
||||
|
|
|
@ -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,69 +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
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClick={onQuoteClick}
|
||||
text={quote?.text}
|
||||
attachment={quote?.attachment}
|
||||
|
|
|
@ -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,12 +1,12 @@
|
|||
import React, { MouseEvent, useState } from 'react';
|
||||
|
||||
import * as MIME from '../../../../../types/MIME';
|
||||
|
||||
import { QuoteAuthor } from './QuoteAuthor';
|
||||
import { QuoteText } from './QuoteText';
|
||||
import { QuoteIconContainer } from './QuoteIconContainer';
|
||||
import styled from 'styled-components';
|
||||
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
|
||||
|
@ -69,6 +69,7 @@ export interface QuotedAttachmentType {
|
|||
}
|
||||
|
||||
export const Quote = (props: QuoteProps) => {
|
||||
const isSelectionMode = useIsMessageSelectionMode();
|
||||
const { isIncoming, attachment, text, referencedMessageNotFound, onClick } = props;
|
||||
|
||||
const [imageBroken, setImageBroken] = useState(false);
|
||||
|
@ -81,7 +82,11 @@ export const Quote = (props: QuoteProps) => {
|
|||
<StyledQuote
|
||||
hasAttachment={Boolean(!isEmpty(attachment))}
|
||||
isIncoming={isIncoming}
|
||||
onClick={onClick}
|
||||
onClick={e => {
|
||||
if (onClick && !isSelectionMode) {
|
||||
onClick(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<QuoteIconContainer
|
||||
attachment={attachment}
|
||||
|
|
|
@ -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% {
|
||||
|
@ -118,8 +118,7 @@ const StyledReadableMessage = styled(ReadableMessage)<{
|
|||
align-items: center;
|
||||
width: 100%;
|
||||
letter-spacing: 0.03rem;
|
||||
padding: var(--margins-xs) var(--margins-lg) 0;
|
||||
margin: var(--margins-xxs) 0;
|
||||
padding: 0 var(--margins-lg) 0;
|
||||
|
||||
&.message-highlighted {
|
||||
animation: ${highlightedMessageAnimation} 1s ease-in-out;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import React, { ReactElement, useRef, useState } from 'react';
|
||||
import { SortedReactionList } from '../../../../types/Reaction';
|
||||
import { useMouse } from 'react-use';
|
||||
import styled from 'styled-components';
|
||||
import { isUsAnySogsFromCache } from '../../../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
|
||||
import { UserUtils } from '../../../../session/utils';
|
||||
import { useIsMessageSelectionMode } from '../../../../state/selectors/selectedConversation';
|
||||
import { SortedReactionList } from '../../../../types/Reaction';
|
||||
import { abbreviateNumber } from '../../../../util/abbreviateNumber';
|
||||
import { nativeEmojiData } from '../../../../util/emoji';
|
||||
import styled from 'styled-components';
|
||||
import { useMouse } from 'react-use';
|
||||
import { POPUP_WIDTH, ReactionPopup, TipPosition } from './ReactionPopup';
|
||||
import { popupXDefault, popupYDefault } from '../message-content/MessageReactions';
|
||||
import { isUsAnySogsFromCache } from '../../../../session/apis/open_group_api/sogsv3/knownBlindedkeys';
|
||||
import { POPUP_WIDTH, ReactionPopup, TipPosition } from './ReactionPopup';
|
||||
|
||||
const StyledReaction = styled.button<{ selected: boolean; inModal: boolean; showCount: boolean }>`
|
||||
display: flex;
|
||||
|
@ -51,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 {
|
||||
|
@ -68,6 +68,8 @@ export const Reaction = (props: ReactionProps): ReactElement => {
|
|||
handlePopupReaction,
|
||||
handlePopupClick,
|
||||
} = props;
|
||||
|
||||
const isMessageSelection = useIsMessageSelectionMode();
|
||||
const reactionsMap = (reactions && Object.fromEntries(reactions)) || {};
|
||||
const senders = reactionsMap[emoji]?.senders || [];
|
||||
const count = reactionsMap[emoji]?.count;
|
||||
|
@ -93,7 +95,9 @@ export const Reaction = (props: ReactionProps): ReactElement => {
|
|||
};
|
||||
|
||||
const handleReactionClick = () => {
|
||||
onClick(emoji);
|
||||
if (!isMessageSelection) {
|
||||
onClick(emoji);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -104,7 +108,7 @@ export const Reaction = (props: ReactionProps): ReactElement => {
|
|||
inModal={inModal}
|
||||
onClick={handleReactionClick}
|
||||
onMouseEnter={() => {
|
||||
if (inGroup) {
|
||||
if (inGroup && !isMessageSelection) {
|
||||
const { innerWidth: windowWidth } = window;
|
||||
if (handlePopupReaction) {
|
||||
// overflow on far right means we shift left
|
||||
|
|
|
@ -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,6 +163,7 @@ export class EditProfileDialog extends React.Component<{}, State> {
|
|||
<div
|
||||
className="image-upload-section"
|
||||
role="button"
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onClick={this.fireInputEvent}
|
||||
data-testid="image-upload-section"
|
||||
/>
|
||||
|
@ -303,6 +303,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 +316,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();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue