Merge branch 'unstable' into feature/ses-476/remove-profile-picture

This commit is contained in:
Audric Ackermann 2023-08-18 16:26:33 +10:00
commit bc081df17d
472 changed files with 4906 additions and 7802 deletions

View File

@ -1,15 +1,17 @@
build/**
components/**
dist/**
mnemonic_languages/**
# editor
.vscode/**
.vscrof/**
# TypeScript generated files
ts/**/*.js
**/ts/**/*.js
playwright.config.js
preload.js
stylesheets/dist/
compiled.d.ts
.eslintrc.js
playwright.config.js

View File

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

View File

@ -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:
@ -27,45 +30,7 @@ jobs:
- name: Checkout git repo
uses: actions/checkout@v3
# we stay on v2 even if there is a v3 because the v3 logic is less flexible for our usecase
- name: Install node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Cache Desktop node_modules
id: cache-desktop-modules
uses: actions/cache@v3
if: runner.os != 'Windows'
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('package.json', 'yarn.lock', 'patches/**') }}
# Not having this will break the windows build because the PATH won't be set by msbuild.
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.3.1
if: runner.os == 'Windows'
- name: Setup node for windows
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'
run: yarn install --frozen-lockfile --network-timeout 600000 --force
- name: Generate and concat files
run: yarn build-everything
- name: Lint Files
# no need to lint files on all platforms. Just do it once on the quicker one
if: runner.os == 'Linux'
run: yarn lint-full
- name: Unit Test
run: yarn test
- uses: ./actions/setup_and_build
- name: Build windows production binaries
if: runner.os == 'Windows'

View File

@ -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:
@ -23,36 +28,7 @@ jobs:
- name: Checkout git repo
uses: actions/checkout@v3
# we stay on v2 even if there is a v3 because the v3 logic is less flexible for our usecase
- name: Install node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Cache Desktop node_modules
id: cache-desktop-modules
uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('package.json', 'yarn.lock', 'patches/**') }}
#Not having this will break the windows build because the PATH won't be set by msbuild.
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.3.1
if: runner.os == 'Windows'
- name: Setup node for windows
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'
run: yarn install --frozen-lockfile --network-timeout 600000 --force
- name: Generate and concat files
run: yarn build-everything
- uses: ./actions/setup_and_build
- name: Lint Files
# no need to lint files on all platforms. Just do it once on the quicker one

View File

@ -22,45 +22,7 @@ jobs:
- name: Checkout git repo
uses: actions/checkout@v3
# we stay on v2 even if there is a v3 because the v3 logic is less flexible for our usecase
- name: Install node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Cache Desktop node_modules
id: cache-desktop-modules
uses: actions/cache@v2
if: runner.os != 'Windows'
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('package.json', 'yarn.lock', 'patches/**') }}
#Not having this will break the windows build because the PATH won't be set by msbuild.
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.3.1
if: runner.os == 'Windows'
- name: Setup node for windows
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'
run: yarn install --frozen-lockfile --network-timeout 600000 --force
- name: Generate and concat files
run: yarn build-everything
- name: Lint Files
# no need to lint files on all platforms. Just do it once on the quicker one
if: runner.os == 'Linux'
run: yarn lint-full
- name: Unit Test
run: yarn test
- uses: ./actions/setup_and_build
- name: Build windows production binaries
if: runner.os == 'Windows'

1
.gitignore vendored
View File

@ -36,6 +36,7 @@ yarn-error.log
# editor
.vscode/
.vscrof/
playwright.config.js

2
.nvmrc
View File

@ -1 +1 @@
16.13.0
18.15.0

View File

@ -17,8 +17,8 @@ ts/test/automation/notes
# Third-party files
node_modules/**
mnemonic_languages/**
playwright.config.js
.vscode/
.vscrof/
# Managed by package manager (`yarn`/`npm`):
/package.json

View File

@ -3,7 +3,6 @@ __tests__
test
tests
powered-test
!@playwright/test/**
# asset directories
docs

View File

@ -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.

View File

@ -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?

View File

@ -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$",
@ -443,6 +442,8 @@
"clearAll": "Clear All",
"clearDataSettingsTitle": "Clear Data",
"messageRequests": "Message Requests",
"blindedMsgReqsSettingTitle": "Community Message Requests",
"blindedMsgReqsSettingDesc": "Allow message requests from Community conversations.",
"requestsSubtitle": "Pending Requests",
"requestsPlaceholder": "No requests",
"hideRequestBannerDescription": "Hide the Message Request banner until you receive a new message request.",
@ -490,6 +491,7 @@
"clearAllConfirmationTitle": "Clear All Message Requests",
"clearAllConfirmationBody": "Are you sure you want to clear all message requests?",
"noMessagesInReadOnly": "There are no messages in <b>$name$</b>.",
"noMessagesInBlindedDisabledMsgRequests": "<b>$name$</b> has message requests from Community conversations turned off, so you cannot send them a message.",
"noMessagesInNoteToSelf": "You have no messages in <b>$name$</b>.",
"noMessagesInEverythingElse": "You have no messages from <b>$name$</b>. Send a message to start the conversation!",
"hideBanner": "Hide",

View File

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

View File

@ -0,0 +1,37 @@
name: 'Setup and build'
description: 'Setup and build Session Desktop'
runs:
using: 'composite'
steps:
- name: Install node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
- name: Cache Desktop node_modules
id: cache-desktop-modules
uses: actions/cache@v3
if: runner.os != 'Windows'
with:
path: node_modules
key: ${{ runner.os }}-${{ hashFiles('package.json', 'yarn.lock', 'patches/**') }}
# Not having this will break the windows build because the PATH won't be set by msbuild.
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.3.1
if: runner.os == 'Windows'
- name: Setup node for windows
if: runner.os == 'Windows'
shell: bash
run: |
yarn global add node-gyp@latest
- name: Install dependencies
shell: bash
if: steps.cache-desktop-modules.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile --network-timeout 600000
- name: Generate and concat files
shell: bash
run: yarn build-everything

View File

@ -24,7 +24,7 @@
style-src 'self' 'unsafe-inline';"
/>
<title>Session</title>
<link href="images/sesion/session_icon_128.png" rel="shortcut icon" />
<link href="images/session/session_icon.png" rel="shortcut icon" />
<link href="stylesheets/dist/manifest.css" rel="stylesheet" type="text/css" />
</head>

View File

@ -1,4 +1,4 @@
const { notarize } = require('electron-notarize');
const { notarize } = require('@electron/notarize');
/*
Pre-requisites: https://github.com/electron/electron-notarize#prerequisites
@ -34,6 +34,9 @@ exports.default = async function notarizing(context) {
appleId: SIGNING_APPLE_ID,
appleIdPassword: SIGNING_APP_PASSWORD,
};
if (!isEmpty(SIGNING_TEAM_ID)) options.ascProvider = SIGNING_TEAM_ID;
if (!isEmpty(SIGNING_TEAM_ID)) {
options.ascProvider = SIGNING_TEAM_ID;
options.teamId = SIGNING_TEAM_ID;
}
return notarize(options);
};

View File

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

View File

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

View File

@ -2,7 +2,7 @@
"name": "session-desktop",
"productName": "Session",
"description": "Private messaging from your desktop",
"version": "1.11.0",
"version": "1.11.1",
"license": "GPL-3.0",
"author": {
"name": "Oxen Labs",
@ -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,14 @@
"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 +67,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.6/libsession_util_nodejs-v0.2.6.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 +114,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 +126,14 @@
"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": {
"@playwright/test": "1.16.3",
"@electron/notarize": "^2.1.0",
"@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 +141,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 +151,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 +163,45 @@
"@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 +256,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 +335,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"
]

View File

@ -23,7 +23,7 @@
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';"
/>
<link href="images/sesion/session_icon_128.png" rel="shortcut icon" />
<link href="images/session/session_icon.png" rel="shortcut icon" />
<link href="stylesheets/dist/manifest.css" rel="stylesheet" type="text/css" />
</head>
<body>

View File

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

View File

@ -1,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');
}

View File

@ -1,20 +0,0 @@
// tslint:disable-next-line: no-implicit-dependencies
import { PlaywrightTestConfig } from '@playwright/test';
import { toNumber } from 'lodash';
const config: PlaywrightTestConfig = {
timeout: 350000,
globalTimeout: 6000000,
reporter: 'list',
testDir: './ts/test/automation',
testIgnore: '*.js',
outputDir: './ts/test/automation/test-results',
retries: process.env.PLAYWRIGHT_RETRIES_COUNT
? toNumber(process.env.PLAYWRIGHT_RETRIES_COUNT)
: 1,
workers: toNumber(process.env.PLAYWRIGHT_WORKER_COUNT) || 1,
reportSlowTests: null,
};
module.exports = config;

View File

@ -1,3 +1,4 @@
// eslint:disable: no-require-imports no-var-requires
const { clipboard, ipcRenderer, webFrame } = require('electron/main');
const { Storage } = require('./ts/util/storage');
@ -15,7 +16,6 @@ if (config.environment !== 'production') {
if (config.appInstance) {
title += ` - ${config.appInstance}`;
}
// tslint:disable: no-require-imports no-var-requires
window.platform = process.platform;
window.getTitle = () => title;
@ -31,6 +31,9 @@ window.sessionFeatureFlags = {
useTestNet: Boolean(
process.env.NODE_APP_INSTANCE && process.env.NODE_APP_INSTANCE.includes('testnet')
),
integrationTestEnv: Boolean(
process.env.NODE_APP_INSTANCE && process.env.NODE_APP_INSTANCE.includes('test-integration')
),
useClosedGroupV3: false || process.env.USE_CLOSED_GROUP_V3,
debug: {
debugLogging: !_.isEmpty(process.env.SESSION_DEBUG),
@ -240,7 +243,6 @@ const { getConversationController } = require('./ts/session/conversations/Conver
window.getConversationController = getConversationController;
// Linux seems to periodically let the event loop stop, so this is a global workaround
setInterval(() => {
// tslint:disable-next-line: no-empty
window.nodeSetImmediate(() => {});
}, 1000);

View File

@ -224,6 +224,7 @@ message DataMessage {
optional OpenGroupInvitation openGroupInvitation = 102;
optional ClosedGroupControlMessage closedGroupControlMessage = 104;
optional string syncTarget = 105;
optional bool blocksCommunityMessageRequests = 106;
// optional GroupMessage groupMessage = 120;
}

View File

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

View File

@ -14,7 +14,7 @@ body {
height: 100%;
width: 100%;
margin: 0;
font-family: $session-font-default;
font-family: var(--font-default);
letter-spacing: 0.3px;
}

View File

@ -43,13 +43,6 @@ textarea {
background: var(--text-selection-color);
}
.shadowed {
opacity: $session-shadow-opacity;
}
.overlayed {
opacity: $session-overlay-opacity;
pointer-events: none;
}
.overlay {
display: flex !important;
z-index: 1;
@ -197,7 +190,7 @@ label {
box-sizing: border-box;
max-height: 70vh;
max-width: calc(min(70vw, 800px));
font-family: $session-font-default;
font-family: var(--font-default);
background-color: var(--modal-background-content-color);
color: var(--modal-text-color);
border: 1px solid var(--border-color);
@ -256,7 +249,7 @@ label {
&__body {
padding: 0px var(--margins-lg) var(--margins-lg) var(--margins-lg);
font-family: $session-font-default;
font-family: var(--font-default);
line-height: $session-font-md;
font-size: $session-font-md;
overflow-y: auto;

View File

@ -2,81 +2,84 @@
// /////////////////// Fonts ////////////////////
// //////////////////////////////////////////////
$session-font-default: 'Roboto';
$session-font-accent: 'Loor';
$session-font-mono: 'SpaceMono';
@font-face {
font-family: $session-font-mono;
font-family: SpaceMono;
src: url('../fonts/SpaceMono-Regular.ttf') format('truetype');
}
// Roboto is an open replacement for $session-font-default
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-Thin.ttf') format('truetype');
font-weight: 100;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-ThinItalic.ttf') format('truetype');
font-style: italic;
font-weight: 100;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-Light.ttf') format('truetype');
font-weight: 300;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-LightItalic.ttf') format('truetype');
font-style: italic;
font-weight: 300;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-Regular.ttf') format('truetype');
font-weight: 400;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-Italic.ttf') format('truetype');
font-style: italic;
font-weight: 400;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-Medium.ttf') format('truetype');
font-weight: 500;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-MediumItalic.ttf') format('truetype');
font-style: italic;
font-weight: 500;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-Bold.ttf') format('truetype');
font-weight: 700;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-Black.ttf') format('truetype');
font-weight: 900;
}
@font-face {
font-family: $session-font-default;
font-family: Roboto;
src: url('../fonts/Roboto-BlackItalic.ttf') format('truetype');
font-weight: 900;
font-style: italic;
@ -84,19 +87,11 @@ $session-font-mono: 'SpaceMono';
// Accented font
@font-face {
font-family: $session-font-accent;
font-family: Loor; // Loor does not support some Cyrillic ghyphs so where we use it, we add a fallback to Roboto
src: url('../fonts/Loor.ttf') format('truetype');
line-height: 1.4rem;
}
// //////////////////////////////////////////////
// ////////////////// Colors ////////////////////
// //////////////////////////////////////////////
// Opacity and Shadow
$session-shadow-opacity: 0.15;
$session-overlay-opacity: 0.3;
// //////////////////////////////////////////////
// /////////////////// Text /////////////////////
// //////////////////////////////////////////////
@ -106,13 +101,7 @@ $session-font-xs: 11px;
$session-font-sm: 13px;
$session-font-md: 15px;
$session-font-h2: 24px;
// Mixins
@mixin fontAccentBold {
font-weight: bold;
font-family: $session-font-accent;
}
@mixin text-highlight($color) {
background-color: $color;

View File

@ -8,7 +8,7 @@
&-content {
&-accent {
&-text {
font-family: $session-font-accent;
font-family: var(--font-accent), var(--font-default);
text-align: center;
.title {
font-size: 90px;
@ -91,7 +91,7 @@
&-input-with-label-container {
height: 46.5px;
width: 280px;
font-family: $session-font-default;
font-family: var(--font-default);
color: var(--text-primary-color);
padding: 2px 0 2px 0;
@ -126,7 +126,7 @@
background: transparent;
color: var(--input-text-color);
font-family: $session-font-default;
font-family: var(--font-default);
font-size: 12px;
line-height: 14px;
position: absolute;
@ -214,7 +214,7 @@
overflow-wrap: break-word;
padding: 0px 5px 20px 5px;
display: inline-block;
font-family: $session-font-mono;
font-family: var(--font-mono), var(--font-default);
user-select: all;
overflow: hidden;
resize: none;

View File

@ -99,7 +99,8 @@
}
@mixin session-h-title {
@include fontAccentBold();
font-weight: bold;
font-family: var(--font-accent), var(--font-default);
}
h1 {

View File

@ -1,5 +1,5 @@
import _ from 'lodash';
import os from 'os';
import _ from 'lodash';
import semver from 'semver';
export const isMacOS = () => process.platform === 'darwin';

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ export const SessionContextMenuContainer = styled.div.attrs({
// custom props
})`
.react-contexify {
// be sure it is more than the one set for the More Informations screen of messages
// be sure it is more than the one set for the More Information screen of messages
z-index: 30;
min-width: 200px;
box-shadow: 0px 0px 10px var(--context-menu-shadow-color) !important;

View File

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

View File

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

View File

@ -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 (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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} />;
};

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ import styled from 'styled-components';
import { resetOverlayMode, setOverlayMode } from '../../state/ducks/section';
import { getOverlayMode } from '../../state/selectors/section';
import { SessionIcon } from '../icon';
// tslint:disable: use-simple-attributes
const StyledMenuButton = styled.button`
position: relative;
@ -28,7 +27,7 @@ const StyledMenuButton = styled.button`
`;
/**
* This is the Session Menu Botton. i.e. the button on top of the conversation list to start a new conversation.
* This is the Session Menu Button. i.e. the button on top of the conversation list to start a new conversation.
* It has two state: selected or not and so we use an checkbox input to keep the state in sync.
*/
export const MenuButton = () => {

View File

@ -1,13 +1,14 @@
import { SessionIconButton } from '../icon';
import React, { useEffect, useState } from 'react';
import { animation, contextMenu, Item, Menu } from 'react-contexify';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { CallManager, ToastUtils } from '../../session/utils';
import { InputItem } from '../../session/utils/calling/CallManager';
import { setFullScreenCall } from '../../state/ducks/call';
import { CallManager, ToastUtils } from '../../session/utils';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getHasOngoingCallWithPubkey } from '../../state/selectors/call';
import { SessionIconButton } from '../icon';
import { DropDownAndToggleButton } from '../icon/DropDownAndToggleButton';
import styled from 'styled-components';
import { SessionContextMenuContainer } from '../SessionContextMenuContainer';
const VideoInputMenu = ({
@ -281,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"

View File

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

View File

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

View File

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

View File

@ -35,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);

View File

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

View File

@ -1,5 +1,6 @@
import React from 'react';
import classNames from 'classnames';
import { CSSProperties } from 'styled-components';
import { Emojify } from './Emojify';
import { useConversationUsernameOrShorten, useIsPrivate } from '../../hooks/useParamSelector';
@ -9,14 +10,14 @@ type Props = {
name?: string | null;
profileName?: string | null;
module?: string;
boldProfileName?: Boolean;
compact?: Boolean;
shouldShowPubkey: Boolean;
boldProfileName?: boolean;
compact?: boolean;
shouldShowPubkey: boolean;
};
export const ContactName = (props: Props) => {
const { pubkey, name, profileName, module, boldProfileName, compact, shouldShowPubkey } = props;
const prefix = module ? module : 'module-contact-name';
const prefix = module || 'module-contact-name';
const convoName = useConversationUsernameOrShorten(pubkey);
const isPrivate = useIsPrivate(pubkey);
@ -33,9 +34,10 @@ export const ContactName = (props: Props) => {
className={classNames(prefix, compact && 'compact')}
dir="auto"
data-testid={`${prefix}__profile-name`}
style={{ textOverflow: 'inherit' }}
>
{shouldShowProfile ? (
<span style={styles as any} className={`${prefix}__profile-name`}>
<span style={styles as CSSProperties} className={`${prefix}__profile-name`}>
<Emojify text={textProfile} sizeClass="small" isGroup={!isPrivate} />
</span>
) : null}

View File

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

View File

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

View File

@ -163,6 +163,20 @@ export const AudioPlayerWithEncryptedFile = (props: {
const nextMessageToPlayId = useSelector(getNextMessageToPlayId);
const multiSelectMode = useSelector(isMessageSelectionMode);
const dataTestId = `audio-${messageId}`;
useEffect(() => {
// Updates datatestId once rendered
if (
player.current?.audio.current &&
player.current?.container.current &&
player.current.container.current.dataset.testId !== dataTestId
) {
// NOTE we can't assign the value using dataset.testId because the result is data-test-id not data-testid which is our convention
player.current.container.current.setAttribute('data-testid', dataTestId);
}
}, [dataTestId, player]);
useEffect(() => {
// updates playback speed to value selected in context menu
if (
@ -175,7 +189,7 @@ export const AudioPlayerWithEncryptedFile = (props: {
useEffect(() => {
if (messageId !== undefined && messageId === nextMessageToPlayId) {
player.current?.audio.current?.play();
void player.current?.audio.current?.play();
}
}, [messageId, nextMessageToPlayId, player]);

View File

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

View File

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

View File

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

View File

@ -1,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;

View File

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

View File

@ -1,7 +1,6 @@
import React from 'react';
import { contextMenu } from 'react-contexify';
import { SessionScrollButton } from '../SessionScrollButton';
import { connect } from 'react-redux';
@ -14,6 +13,8 @@ import {
resetOldTopMessageId,
SortedMessageModelProps,
} from '../../state/ducks/conversations';
import { SessionScrollButton } from '../SessionScrollButton';
import { StateType } from '../../state/reducer';
import {
getQuotedMessageToAnimate,
@ -38,7 +39,6 @@ export type ScrollToLoadedReasons =
| 'load-more-bottom';
export const ScrollToLoadedMessageContext = React.createContext(
// tslint:disable-next-line: no-empty
(_loadedMessageIdToScrollTo: string, _reason: ScrollToLoadedReasons) => {}
);
@ -51,7 +51,7 @@ type Props = SessionMessageListProps & {
scrollToNow: () => Promise<void>;
};
const StyledMessagesContainer = styled.div<{}>`
const StyledMessagesContainer = styled.div`
display: flex;
flex-grow: 1;
gap: var(--margins-xxs);
@ -144,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"
/>
@ -250,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',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,7 @@ import {
import {
getSelectedCanWrite,
useSelectedConversationKey,
useSelectedHasDisabledBlindedMsgRequests,
useSelectedNicknameOrProfileNameOrShortenedPubkey,
useSelectedisNoteToSelf,
} from '../../state/selectors/selectedConversation';
@ -61,6 +62,7 @@ export const NoMessageInConversation = () => {
const isMe = useSelectedisNoteToSelf();
const canWrite = useSelector(getSelectedCanWrite);
const privateBlindedAndBlockingMsgReqs = useSelectedHasDisabledBlindedMsgRequests();
// TODOLATER use this selector accross the whole application (left pane excluded)
const nameToRender = useSelectedNicknameOrProfileNameOrShortenedPubkey();
@ -69,7 +71,11 @@ export const NoMessageInConversation = () => {
}
let localizedKey: LocalizerKeys = 'noMessagesInEverythingElse';
if (!canWrite) {
localizedKey = 'noMessagesInReadOnly';
if (privateBlindedAndBlockingMsgReqs) {
localizedKey = 'noMessagesInBlindedDisabledMsgRequests';
} else {
localizedKey = 'noMessagesInReadOnly';
}
} else if (isMe) {
localizedKey = 'noMessagesInNoteToSelf';
}

View File

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

View File

@ -1,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>
);

View File

@ -1,30 +1,18 @@
import React from 'react';
import _, { debounce, isEmpty } from 'lodash';
import React from 'react';
import { connect } from 'react-redux';
import styled from 'styled-components';
import { AbortController } from 'abort-controller';
import { Mention, MentionsInput, SuggestionDataItem } from 'react-mentions';
import autoBind from 'auto-bind';
import * as MIME from '../../../types/MIME';
import { SessionEmojiPanel, StyledEmojiPanel } from '../SessionEmojiPanel';
import { SessionRecording } from '../SessionRecording';
import {
getPreview,
LINK_PREVIEW_TIMEOUT,
SessionStagedLinkPreview,
} from '../SessionStagedLinkPreview';
import { AbortController } from 'abort-controller';
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
import { Mention, MentionsInput, SuggestionDataItem } from 'react-mentions';
import autoBind from 'auto-bind';
import { getMediaPermissionsSettings } from '../../settings/SessionSettings';
import { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts';
import {
AddStagedAttachmentButton,
SendMessageButton,
StartRecordingButton,
ToggleEmojiButton,
} from './CompositionButtons';
import { AttachmentType } from '../../../types/Attachment';
import { connect } from 'react-redux';
import { SettingsKey } from '../../../data/settings-key';
import { showLinkSharingConfirmationModalDialog } from '../../../interactions/conversationInteractions';
import { getConversationController } from '../../../session/conversations';
import { ToastUtils } from '../../../session/utils';
@ -36,30 +24,43 @@ import {
getQuotedMessage,
getSelectedConversation,
} from '../../../state/selectors/conversations';
import { AttachmentUtil } from '../../../util';
import { Flex } from '../../basic/Flex';
import { CaptionEditor } from '../../CaptionEditor';
import { StagedAttachmentList } from '../StagedAttachmentList';
import {
getSelectedCanWrite,
getSelectedConversationKey,
} from '../../../state/selectors/selectedConversation';
import { AttachmentType } from '../../../types/Attachment';
import { processNewAttachment } from '../../../types/MessageAttachment';
import { FixedBaseEmoji } from '../../../types/Reaction';
import { AttachmentUtil } from '../../../util';
import {
StagedAttachmentImportedType,
StagedPreviewImportedType,
} from '../../../util/attachmentsUtil';
import { LinkPreviews } from '../../../util/linkPreviews';
import { Flex } from '../../basic/Flex';
import { CaptionEditor } from '../../CaptionEditor';
import { getMediaPermissionsSettings } from '../../settings/SessionSettings';
import { getDraftForConversation, updateDraftForConversation } from '../SessionConversationDrafts';
import { SessionQuotedMessageComposition } from '../SessionQuotedMessageComposition';
import {
getPreview,
LINK_PREVIEW_TIMEOUT,
SessionStagedLinkPreview,
} from '../SessionStagedLinkPreview';
import { StagedAttachmentList } from '../StagedAttachmentList';
import {
AddStagedAttachmentButton,
SendMessageButton,
StartRecordingButton,
ToggleEmojiButton,
} from './CompositionButtons';
import { renderEmojiQuickResultRow, searchEmojiForQuery } from './EmojiQuickResult';
import {
cleanMentions,
mentionsRegex,
renderUserMentionRow,
styleForCompositionBoxSuggestions,
} from './UserMentions';
import { renderEmojiQuickResultRow, searchEmojiForQuery } from './EmojiQuickResult';
import { LinkPreviews } from '../../../util/linkPreviews';
import styled from 'styled-components';
import { FixedBaseEmoji } from '../../../types/Reaction';
import {
getSelectedCanWrite,
getSelectedConversationKey,
} from '../../../state/selectors/selectedConversation';
import { SettingsKey } from '../../../data/settings-key';
export interface ReplyingToMessageProps {
convoId: string;
@ -175,7 +176,7 @@ const getSelectionBasedOnMentions = (draft: string, index: number) => {
lastMatchEndIndex = currentMatchStartIndex + match.length;
const realLength = displayName.length + 1;
lastRealMatchEndIndex = lastRealMatchEndIndex + realLength;
lastRealMatchEndIndex += realLength;
// the +1 is for the @
return {
@ -348,6 +349,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
}
const { items } = e.clipboardData;
let imgBlob = null;
// eslint-disable-next-line no-restricted-syntax
for (const item of items as any) {
const pasteType = item.type.split('/')[0];
if (pasteType === 'image') {
@ -402,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 && <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) {

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -111,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>

View File

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

View File

@ -21,6 +21,7 @@ type Props = {
const StyledAuthorContainer = styled(Flex)`
color: var(--text-primary-color);
text-overflow: ellipsis;
`;
export const MessageAuthorText = (props: Props) => {
@ -36,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;

View File

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

View File

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

View File

@ -28,6 +28,8 @@ type Props = {
isDetailView?: boolean;
};
// TODO not too sure what is this doing? It is not preventDefault()
// or stopPropagation() so I think this is never cancelling a click event?
function onClickOnMessageInnerContainer(event: React.MouseEvent<HTMLDivElement>) {
const selection = window.getSelection();
// Text is being selected
@ -38,6 +40,7 @@ function onClickOnMessageInnerContainer(event: React.MouseEvent<HTMLDivElement>)
// User clicked on message body
const target = event.target as HTMLDivElement;
if (target.className === 'text-selectable' || window.contextMenuShown) {
// eslint-disable-next-line no-useless-return
return;
}
}
@ -83,10 +86,10 @@ const StyledMessageOpaqueContent = styled(StyledMessageHighlighter)<{
align-self: ${props => (props.messageDirection === 'incoming' ? 'flex-start' : 'flex-end')};
padding: var(--padding-message-content);
border-radius: var(--border-radius-message-box);
max-width: 100%;
`;
export const IsMessageVisibleContext = createContext(false);
// tslint:disable: use-simple-attributes
export const MessageContent = (props: Props) => {
const [highlight, setHighlight] = useState(false);
@ -101,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)
@ -123,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) {
@ -139,8 +142,14 @@ export const MessageContent = (props: Props) => {
if (didScroll) {
setDidScroll(false);
}
return;
});
}, [
isQuotedMessageToAnimate,
highlight,
didScroll,
scrollToLoadedMessage,
props.messageId,
shouldHighlightMessage,
]);
if (!contentProps) {
return null;

View File

@ -11,7 +11,7 @@ import {
isMessageSelectionMode,
} from '../../../../state/selectors/conversations';
import { Reactions } from '../../../../util/reactions';
import { MessageAvatar } from '../message-content/MessageAvatar';
import { MessageAvatar } from './MessageAvatar';
import { MessageAuthorText } from './MessageAuthorText';
import { MessageContent } from './MessageContent';
import { MessageContextMenu } from './MessageContextMenu';
@ -30,7 +30,6 @@ type Props = {
dataTestId?: string;
enableReactions: boolean;
};
// tslint:disable: use-simple-attributes
const StyledMessageContentContainer = styled.div<{ direction: 'left' | 'right' }>`
display: flex;
@ -48,6 +47,7 @@ const StyledMessageWithAuthor = styled.div<{ isIncoming: boolean }>`
max-width: ${props => (props.isIncoming ? '100%' : 'calc(100% - 17px)')};
display: flex;
flex-direction: column;
min-width: 0;
`;
export const MessageContentWithStatuses = (props: Props) => {
@ -60,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>) => {
@ -84,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;
@ -100,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);
};
@ -150,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}

View File

@ -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,22 @@ 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';
import { useIsPublic } from '../../../../hooks/useParamSelector';
export type MessageContextMenuSelectorProps = Pick<
MessageRenderingProps,
| 'attachments'
| 'sender'
| 'direction'
| 'status'
@ -48,7 +60,6 @@ export type MessageContextMenuSelectorProps = Pick<
| 'text'
| 'serverTimestamp'
| 'timestamp'
| 'isDeletableForEveryone'
>;
type Props = { messageId: string; contextMenuId: string; enableReactions: boolean };
@ -76,7 +87,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 isPublic = useIsPublic();
const weAreModerator = useSelectedWeAreModerator();
const weAreAdmin = useSelectedWeAreAdmin();
const showAdminActions = (weAreAdmin || weAreModerator) && isPublic;
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 +213,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 +261,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 +270,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 +297,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 +318,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 +340,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 +367,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 +379,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>

View File

@ -1,5 +1,5 @@
import { isEmpty, toNumber } from 'lodash';
import React, { useCallback } from 'react';
import React from 'react';
import { useSelector } from 'react-redux';
import { Data } from '../../../../data/data';
import { MessageRenderingProps } from '../../../../models/messageType';
@ -10,12 +10,9 @@ import { useMessageDirection } from '../../../../state/selectors';
import {
getMessageQuoteProps,
isMessageDetailView,
isMessageSelectionMode,
} from '../../../../state/selectors/conversations';
import { Quote } from './quote/Quote';
// tslint:disable: use-simple-attributes
type Props = {
messageId: string;
};
@ -25,7 +22,6 @@ export type MessageQuoteSelectorProps = Pick<MessageRenderingProps, 'quote' | 'd
export const MessageQuote = (props: Props) => {
const selected = useSelector((state: StateType) => getMessageQuoteProps(state, props.messageId));
const direction = useMessageDirection(props.messageId);
const multiSelectMode = useSelector(isMessageSelectionMode);
const isMessageDetailViewMode = useSelector(isMessageDetailView);
if (!selected || isEmpty(selected)) {
@ -42,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}

View File

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

View File

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

View File

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

View File

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

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