Compare commits

...

77 Commits

Author SHA1 Message Date
Krishan
f05037c7d6 v2.0.4 (#590) 2022-05-29 10:48:20 +05:30
Krishan
d0fd654bf7 Add support for building docker image for linux/arm64 (#494)
* Update docker-pr.yml

* setup docker build for linux/arm64

* Update prod-deploy.yml

* Apply PR suggestion
2022-05-29 10:15:31 +05:30
Ajay Bura
7165bd91cd Don't show verify button if CS is not enable 2022-05-29 09:47:30 +05:30
Ajay Bura
d3431a5d53 Fix emoji autocomplete in some cases (#565) 2022-05-29 09:36:46 +05:30
Krishan
fa6c865000 Update typo in string (#586) 2022-05-28 18:29:15 +05:30
Ajay Bura
fd680a93e0 Add alt text to sheilds 2022-05-27 14:09:53 +05:30
Krishan
38b604ad41 Add PGP public key and fix engine versions in package.json (#583)
* nodejs 17.9.0 also works

* Add github sponser link

* Add Public PGP key of signed tarball

* Update README.md

* Add download badge also.

* Add docker pulls
2022-05-27 13:09:36 +05:30
Ajay Bura
2ca67bb61a Consistent job naming 2022-05-26 20:20:28 +05:30
Matt Corallo
95b814b751 Reduce third-party build script dependencies and reduce GITHUB_TOKEN perms in CI (#541)
* Reduce dependence on third-party build scripts in release pipeline

This removes one third-party build script from the release
pipeline for the release tar.gz, though one is still used in the
now-separate netlify deploy.

* Reduce GITHUB_TOKEN perms in actions when using 3rd party scripts

This avoids allowing third parties to arbitrarily overwrite the
repository.

* Replace PGP signing action with the bash script from the same

The PGP signing action ultimately just calls gpg with arguments
set in
https://github.com/actionhippie/gpgsign/blob/v1/overlay/usr/local/bin/entrypoint
so its rather trivial to simply take the required arguments and
put them directly in CI.

This is substantially safer than the PGP signing action used as the
action currently downloads, unverified and un-pinned, a docker
image in order to access PGP.
2022-05-26 20:17:41 +05:30
Krishan
9963f3f988 Set minimum and maximum engine versions (#580) 2022-05-24 20:07:11 +05:30
dependabot[bot]
fde7d4a25a Bump css-minimizer-webpack-plugin from 3.4.1 to 4.0.0 (#573)
Bumps [css-minimizer-webpack-plugin](https://github.com/webpack-contrib/css-minimizer-webpack-plugin) from 3.4.1 to 4.0.0.
- [Release notes](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-minimizer-webpack-plugin/compare/v3.4.1...v4.0.0)

---
updated-dependencies:
- dependency-name: css-minimizer-webpack-plugin
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 20:06:17 +05:30
dependabot[bot]
895b2c4f19 Bump copy-webpack-plugin from 10.2.4 to 11.0.0 (#571)
Bumps [copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin) from 10.2.4 to 11.0.0.
- [Release notes](https://github.com/webpack-contrib/copy-webpack-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v10.2.4...v11.0.0)

---
updated-dependencies:
- dependency-name: copy-webpack-plugin
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 19:53:37 +05:30
dependabot[bot]
427ea9baab Bump sass-loader from 12.6.0 to 13.0.0 (#576)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 12.6.0 to 13.0.0.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v12.6.0...v13.0.0)

---
updated-dependencies:
- dependency-name: sass-loader
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 19:51:19 +05:30
dependabot[bot]
df718e4498 Bump eslint-plugin-react from 7.29.4 to 7.30.0 (#575)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.29.4 to 7.30.0.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.29.4...v7.30.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 19:47:49 +05:30
dependabot[bot]
00956f5bba Bump eslint from 8.15.0 to 8.16.0 (#574)
Bumps [eslint](https://github.com/eslint/eslint) from 8.15.0 to 8.16.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.15.0...v8.16.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 19:46:35 +05:30
dependabot[bot]
e48d216d79 Bump sass from 1.51.0 to 1.52.1 (#572)
Bumps [sass](https://github.com/sass/dart-sass) from 1.51.0 to 1.52.1.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.51.0...1.52.1)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 19:45:33 +05:30
dependabot[bot]
489f178c7c Bump katex from 0.15.3 to 0.15.6 (#577)
Bumps [katex](https://github.com/KaTeX/KaTeX) from 0.15.3 to 0.15.6.
- [Release notes](https://github.com/KaTeX/KaTeX/releases)
- [Changelog](https://github.com/KaTeX/KaTeX/blob/main/CHANGELOG.md)
- [Commits](https://github.com/KaTeX/KaTeX/compare/v0.15.3...v0.15.6)

---
updated-dependencies:
- dependency-name: katex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 19:43:21 +05:30
dependabot[bot]
3bd4eda789 Bump actions/upload-artifact from 3.0.0 to 3.1.0 (#578)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-24 19:40:24 +05:30
Ajay Bura
fc6c7b8dc6 Add recommended ways to install node and node version 2022-05-21 17:33:01 +05:30
dependabot[bot]
deef1f2c8a Bump @babel/preset-env from 7.17.10 to 7.18.0 (#569)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.17.10 to 7.18.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.18.0/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 10:06:10 +05:30
dependabot[bot]
38bd38a487 Bump @babel/core from 7.17.10 to 7.18.0 (#568)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.17.10 to 7.18.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.18.0/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 10:01:37 +05:30
dependabot[bot]
40de64078a Bump docker/build-push-action from 2.10.0 to 3.0.0 (#538)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2.10.0 to 3.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2.10.0...v3.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 09:59:40 +05:30
dependabot[bot]
780bd5e65a Bump docker/metadata-action from 3.8.0 to 4.0.1 (#539)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 3.8.0 to 4.0.1.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Upgrade guide](https://github.com/docker/metadata-action/blob/master/UPGRADE.md)
- [Commits](https://github.com/docker/metadata-action/compare/v3.8.0...v4.0.1)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 09:59:00 +05:30
dependabot[bot]
2cd74b4ea9 Bump docker/login-action from 1.14.1 to 2.0.0 (#540)
Bumps [docker/login-action](https://github.com/docker/login-action) from 1.14.1 to 2.0.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v1.14.1...v2.0.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 09:58:34 +05:30
dependabot[bot]
0cd3df391e Bump eslint from 8.14.0 to 8.15.0 (#536)
Bumps [eslint](https://github.com/eslint/eslint) from 8.14.0 to 8.15.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.14.0...v8.15.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 09:25:57 +05:30
dependabot[bot]
854d2b4c27 Bump @fontsource/roboto from 4.5.5 to 4.5.7 (#556)
Bumps [@fontsource/roboto](https://github.com/fontsource/fontsource/tree/HEAD/fonts/google/roboto) from 4.5.5 to 4.5.7.
- [Release notes](https://github.com/fontsource/fontsource/releases)
- [Changelog](https://github.com/fontsource/fontsource/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fontsource/fontsource/commits/HEAD/fonts/google/roboto)

---
updated-dependencies:
- dependency-name: "@fontsource/roboto"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 09:24:28 +05:30
dependabot[bot]
7227fc7c43 Bump @babel/preset-react from 7.16.7 to 7.17.12 (#559)
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.16.7 to 7.17.12.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.17.12/packages/babel-preset-react)

---
updated-dependencies:
- dependency-name: "@babel/preset-react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 09:20:54 +05:30
dependabot[bot]
73dcb44121 Bump matrix-js-sdk from 17.1.0 to 17.2.0 (#560)
Bumps [matrix-js-sdk](https://github.com/matrix-org/matrix-js-sdk) from 17.1.0 to 17.2.0.
- [Release notes](https://github.com/matrix-org/matrix-js-sdk/releases)
- [Changelog](https://github.com/matrix-org/matrix-js-sdk/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/matrix-org/matrix-js-sdk/compare/v17.1.0...v17.2.0)

---
updated-dependencies:
- dependency-name: matrix-js-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 09:20:01 +05:30
dependabot[bot]
54fd394ef5 Bump webpack from 5.72.0 to 5.72.1 (#561)
Bumps [webpack](https://github.com/webpack/webpack) from 5.72.0 to 5.72.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.72.0...v5.72.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 09:17:47 +05:30
dependabot[bot]
fda71166df Bump actions/github-script from 6.0.0 to 6.1.0 (#562)
Bumps [actions/github-script](https://github.com/actions/github-script) from 6.0.0 to 6.1.0.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v6.0.0...v6.1.0)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-20 09:16:55 +05:30
Ajay Bura
69b6055353 v2.0.3 2022-05-15 10:39:42 +05:30
Ajay Bura
1bdd0449e0 Fix edit message not working (#552) 2022-05-14 20:05:43 +05:30
Ajay Bura
a6fdf9010b v2.0.2 2022-05-14 09:38:58 +05:30
Ajay Bura
941dae0625 Remove globally exposed var 2022-05-14 08:28:31 +05:30
Ajay Bura
4a715bfd17 Fix pasting not working #551 2022-05-14 08:24:21 +05:30
Ajay Bura
0b70c7e490 v2.0.1 2022-05-13 16:39:54 +05:30
Ajay Bura
0539836714 Fix space and enter focus message field 2022-05-13 15:38:18 +05:30
Ash
c08b0e654b Add allowCustomHomeservers config option (#525)
* feat: Add allowCustomHomeservers config option

* fix: Do not lock the homeserver input when the selection is changed
2022-05-12 17:13:14 +05:30
Dean Bassett
b3cb48319a Add the ability to focus on paste (#545)
* pasting should focus the message field

also refactored a small amount to use KeyEvent.code
instead of KeyEvent.keyCode, which is deprecated.

fixes ajbura/cinny#544

* fix lint

* comments
2022-05-12 16:58:19 +05:30
Ajay Bura
44553cc375 Fix crash in room without create state event (#546) 2022-05-12 16:32:39 +05:30
Ajay Bura
fbe287a702 Fix message edit isn't reflected in reply #421 2022-05-12 13:45:23 +05:30
Ajay Bura
5863dcdf67 Fix join with alias (#533) 2022-05-11 20:56:49 +05:30
Ajay Bura
f77bee25ef Remove forget room on leave 2022-05-11 20:53:21 +05:30
Ajay Bura
c11328a064 Fix crash on leaving room (#532) 2022-05-11 20:25:54 +05:30
Ajay Bura
d04de2fba0 Add badges 2022-05-08 13:52:05 +05:30
Ajay Bura
d2b435618c v2.0.0 2022-05-08 13:23:31 +05:30
Ajay Bura
7525bb78e5 Fix emoji verificaition not working with some client 2022-05-08 12:26:31 +05:30
Ajay Bura
2075a572fe Fixed cinny verified device failed to verify other 2022-05-08 11:55:41 +05:30
Ajay Bura
73723ba6ba Fix own cross siging trust before verification without key #514 2022-05-07 09:50:29 +05:30
Ajay Bura
0791820a6c Merge branch 'dev' of https://github.com/ajbura/cinny into dev 2022-05-05 19:58:29 +05:30
Ajay Bura
931f352873 Fix space path visible in DM's 2022-05-05 19:58:16 +05:30
dependabot[bot]
7c7d2e0fa4 Bump webpack-dev-server from 4.8.1 to 4.9.0 (#524)
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.8.1 to 4.9.0.
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v4.8.1...v4.9.0)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-05 10:47:11 +05:30
Ajay Bura
3372fb6f74 Fix public room showing leaved room as joined 2022-05-04 14:54:43 +05:30
Ajay Bura
bc856269ff Merge branch 'dev' of https://github.com/ajbura/cinny into dev 2022-05-04 14:22:20 +05:30
Ajay Bura
06bae231ef Fix bugs in dm tab 2022-05-04 14:22:16 +05:30
Rubin Elezi
65a0edc3a6 Don't enable e2ee for bridged platform (#476)
* Don't enable e2ee for bridged platform

* remove comments

* Change function name

Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
2022-05-04 10:58:30 +05:30
Ajay Bura
b7c322d473 Sign release tarball with PGP key (#392) 2022-05-03 16:43:16 +05:30
dependabot[bot]
0776a04362 Bump sass from 1.50.1 to 1.51.0 (#522)
Bumps [sass](https://github.com/sass/dart-sass) from 1.50.1 to 1.51.0.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.50.1...1.51.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 16:02:14 +05:30
Ajay Bura
e51fc5a585 Add join with address option (#420, #447) 2022-05-03 16:01:50 +05:30
Ajay Bura
3afc068a02 Fixes #430, #434, #455 2022-05-03 14:05:56 +05:30
Ajay Bura
5cdad44abf Load sound file on startup (#444) 2022-05-03 13:18:27 +05:30
dependabot[bot]
43762df998 Bump @babel/core from 7.17.9 to 7.17.10 (#521)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.17.9 to 7.17.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.17.10/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 13:07:51 +05:30
dependabot[bot]
95228c6dd9 Bump @babel/preset-env from 7.16.11 to 7.17.10 (#520)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.16.11 to 7.17.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.17.10/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 13:02:49 +05:30
dependabot[bot]
205fcf8487 Bump @fontsource/inter from 4.5.7 to 4.5.10 (#519)
Bumps [@fontsource/inter](https://github.com/fontsource/fontsource/tree/HEAD/fonts/google/inter) from 4.5.7 to 4.5.10.
- [Release notes](https://github.com/fontsource/fontsource/releases)
- [Changelog](https://github.com/fontsource/fontsource/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fontsource/fontsource/commits/HEAD/fonts/google/inter)

---
updated-dependencies:
- dependency-name: "@fontsource/inter"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 13:00:30 +05:30
dependabot[bot]
336e8921ee Bump react-modal from 3.14.4 to 3.15.1 (#518)
Bumps [react-modal](https://github.com/reactjs/react-modal) from 3.14.4 to 3.15.1.
- [Release notes](https://github.com/reactjs/react-modal/releases)
- [Changelog](https://github.com/reactjs/react-modal/blob/master/CHANGELOG.md)
- [Commits](https://github.com/reactjs/react-modal/compare/v3.14.4...v3.15.1)

---
updated-dependencies:
- dependency-name: react-modal
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 12:58:25 +05:30
dependabot[bot]
ef149b9fcf Bump matrix-js-sdk from 17.0.0 to 17.1.0 (#517)
Bumps [matrix-js-sdk](https://github.com/matrix-org/matrix-js-sdk) from 17.0.0 to 17.1.0.
- [Release notes](https://github.com/matrix-org/matrix-js-sdk/releases)
- [Changelog](https://github.com/matrix-org/matrix-js-sdk/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/matrix-org/matrix-js-sdk/compare/v17.0.0...v17.1.0)

---
updated-dependencies:
- dependency-name: matrix-js-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 12:56:42 +05:30
dependabot[bot]
766b4c13c3 Bump eslint-plugin-react-hooks from 4.4.0 to 4.5.0 (#516)
Bumps [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/HEAD/packages/eslint-plugin-react-hooks)

---
updated-dependencies:
- dependency-name: eslint-plugin-react-hooks
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 12:54:22 +05:30
Ajay Bura
f5605258e3 Merge branch 'dev' of https://github.com/ajbura/cinny into dev 2022-05-03 12:52:33 +05:30
Ajay Bura
2ba4d2f2b7 Bug fixes in emoji verificaiton 2022-05-03 12:52:26 +05:30
dependabot[bot]
2e050c066e Bump docker/metadata-action from 3.7.0 to 3.8.0 (#523)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 3.7.0 to 3.8.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v3.7.0...v3.8.0)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-03 12:36:14 +05:30
Ajay Bura
3f83514427 Fix #514 2022-05-01 20:56:30 +05:30
Ajay Bura
8c227843c9 Show error on wrong security key 2022-05-01 17:40:47 +05:30
Ajay Bura
ba084c0a10 Fix key backup not working without phrase 2022-05-01 17:32:29 +05:30
Ajay Bura
3fdd42706d Fix branch name in readme 2022-05-01 13:38:31 +05:30
Ajay Bura
b49b51a671 Fix link to screenshot 2022-05-01 13:37:29 +05:30
Ajay Bura
e5bb386dd2 Use SHA instead of tag for 3rd party actions (#498) 2022-05-01 13:23:42 +05:30
Ajay Bura
2867bb3bc3 Session verification by emojis (#513)
* Add option to verify with security key/phrase

* Manually merge #311 by @ginnyTheCat
2022-05-01 13:22:55 +05:30
43 changed files with 2422 additions and 1742 deletions

3
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,3 @@
github: ajbura
liberapay: ajbura
open_collective: cinny open_collective: cinny
liberapay: ajbura

View File

@@ -15,19 +15,19 @@ jobs:
- name: Build app - name: Build app
run: npm ci && npm run build run: npm ci && npm run build
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v3.0.0 uses: actions/upload-artifact@v3.1.0
with: with:
name: previewbuild name: previewbuild
path: dist path: dist
retention-days: 1 retention-days: 1
- name: Get PR info - name: Get PR info
uses: actions/github-script@v6.0.0 uses: actions/github-script@v6.1.0
with: with:
script: | script: |
var fs = require('fs'); var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.json', JSON.stringify(context.payload.pull_request)); fs.writeFileSync('${{github.workspace}}/pr.json', JSON.stringify(context.payload.pull_request));
- name: Upload PR Info - name: Upload PR Info
uses: actions/upload-artifact@v3.0.0 uses: actions/upload-artifact@v3.1.0
with: with:
name: pr.json name: pr.json
path: pr.json path: pr.json

View File

@@ -6,6 +6,9 @@ on:
- completed - completed
jobs: jobs:
get-build-and-deploy: get-build-and-deploy:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: > if: >
${{ github.event.workflow_run.conclusion == 'success' }} ${{ github.event.workflow_run.conclusion == 'success' }}
@@ -14,7 +17,7 @@ jobs:
# workflow_run action (https://github.com/actions/download-artifact/issues/60) # workflow_run action (https://github.com/actions/download-artifact/issues/60)
# so instead we get this mess: # so instead we get this mess:
- name: Download artifact - name: Download artifact
uses: actions/github-script@v6.0.0 uses: actions/github-script@v6.1.0
with: with:
script: | script: |
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
@@ -48,7 +51,7 @@ jobs:
run: unzip -d dist previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip run: unzip -d dist previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip
- name: Read PR Info - name: Read PR Info
id: readctx id: readctx
uses: actions/github-script@v6.0.0 uses: actions/github-script@v6.1.0
with: with:
script: | script: |
var fs = require('fs'); var fs = require('fs');
@@ -56,7 +59,7 @@ jobs:
console.log(`::set-output name=prnumber::${pr.number}`); console.log(`::set-output name=prnumber::${pr.number}`);
- name: Deploy to Netlify - name: Deploy to Netlify
id: netlify id: netlify
uses: nwtgck/actions-netlify@v1.2.3 uses: nwtgck/actions-netlify@b7c1504e00c6b8a249d1848cc1b522a4865eed99
with: with:
publish-dir: dist publish-dir: dist
deploy-message: "Deploy from GitHub Actions" deploy-message: "Deploy from GitHub Actions"
@@ -68,7 +71,7 @@ jobs:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }}
timeout-minutes: 1 timeout-minutes: 1
- name: Edit PR Description - name: Edit PR Description
uses: Beakyn/gha-comment-pull-request@v1.0.2 uses: Beakyn/gha-comment-pull-request@2167a7aee24f9e61ce76a23039f322e49a990409
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:

View File

@@ -15,7 +15,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Build Docker image - name: Build Docker image
uses: docker/build-push-action@v2.10.0 uses: docker/build-push-action@v3.0.0
with: with:
context: . context: .
push: false push: false

View File

@@ -9,12 +9,13 @@ jobs:
deploy-to-netlify: deploy-to-netlify:
name: 'Deploy' name: 'Deploy'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Build and deploy to Netlify - name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@v1.7.2 uses: jsmrcaga/action-netlify-deploy@fb6a5f936a4b06a8f7793e69fc5a022ffe39807a
with: with:
install_command: "npm ci" install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}

View File

@@ -5,14 +5,48 @@ on:
types: [published] types: [published]
jobs: jobs:
deploy-to-netlify: create-release-tar:
name: 'Deploy to Netlify' name: 'Create release tar'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Build
run: |
npm ci
npm run build
- name: Get version from tag
id: vars
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Create tar.gz
run: tar -czvf cinny-${{ steps.vars.outputs.tag }}.tar.gz dist
- name: Sign tar.gz
run: |
echo '${{ secrets.GNUPG_KEY }}' | gpg --batch --import
# Sadly a few lines in the private key match a few lines in the public key,
# As a result just --export --armor gives us a few lines replaced with ***
# making it useless for importing the signing key. Instead, we dump it as
# non-armored and hex-encode it so that its printable.
echo "PGP Signing key, in raw PGP format in hex. Import with cat ... | xxd -r -p - | gpg --import"
gpg --export | xxd -p
echo '${{ secrets.GNUPG_PASSPHRASE }}' | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --armor --detach-sign cinny-${{ steps.vars.outputs.tag }}.tar.gz
- name: Upload tagged release
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
with:
files: |
cinny-${{ steps.vars.outputs.tag }}.tar.gz
cinny-${{ steps.vars.outputs.tag }}.tar.gz.asc
deploy-to-netlify:
name: 'Deploy to Netlify'
runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Build and deploy to Netlify - name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@v1.7.2 uses: jsmrcaga/action-netlify-deploy@fb6a5f936a4b06a8f7793e69fc5a022ffe39807a
with: with:
install_command: "npm ci" install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
@@ -20,37 +54,34 @@ jobs:
BUILD_DIRECTORY: "dist" BUILD_DIRECTORY: "dist"
NETLIFY_DEPLOY_MESSAGE: "Prod deploy v${{ github.ref }}" NETLIFY_DEPLOY_MESSAGE: "Prod deploy v${{ github.ref }}"
NETLIFY_DEPLOY_TO_PROD: true NETLIFY_DEPLOY_TO_PROD: true
- name: Get version from tag
id: vars
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Create tar.gz
run: tar -czvf cinny-${{ steps.vars.outputs.tag }}.tar.gz dist
- name: Upload tagged release
uses: softprops/action-gh-release@v0.1.14
with:
files: |
cinny-${{ steps.vars.outputs.tag }}.tar.gz
push_to_dockerhub: push-to-dockerhub:
name: Push Docker image to Docker Hub name: Push Docker image to Docker Hub
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1.6.0
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v1.14.1 uses: docker/login-action@v2.0.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@v3.7.0 uses: docker/metadata-action@v4.0.1
with: with:
images: ajbura/cinny images: ajbura/cinny
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v2.10.0 uses: docker/build-push-action@v3.0.0
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 Ajay Bura (ajbura) and contributors Copyright (c) 2021 Ajay Bura (ajbura)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,18 +1,27 @@
# Cinny <p align="center">
<img src="https://raw.githubusercontent.com/ajbura/cinny/dev/public/res/svg/cinny.svg?sanitize=true"
height="16">
<span><b>Cinny</b></span>
</p>
<p align="center">
<a href="https://github.com/ajbura/cinny/releases">
<img alt="GitHub release downloads" src="https://img.shields.io/github/downloads/ajbura/cinny/total?logo=github&style=social"></a>
<a href="https://hub.docker.com/r/ajbura/cinny">
<img alt="DockerHub downloads" src="https://img.shields.io/docker/pulls/ajbura/cinny?logo=docker&style=social"></a>
<a href="https://fosstodon.org/@cinnyapp">
<img alt="Follow on Mastodon" src="https://img.shields.io/mastodon/follow/106845779685925461?domain=https%3A%2F%2Ffosstodon.org&logo=mastodon&style=social"></a>
<a href="https://twitter.com/intent/follow?screen_name=cinnyapp">
<img alt="Follow on Twitter" src="https://img.shields.io/twitter/follow/cinnyapp?logo=twitter&style=social"></a>
<a href="https://cinny.in/#sponsor">
<img alt="Sponsor Cinny" src="https://img.shields.io/opencollective/all/cinny?logo=opencollective&style=social"></a>
</p>
## Table of Contents **Cinny** is a Matrix client focusing primarily on simple, elegant and secure interface. The main goal is to have a client that is easy on end user
and feels a modern chat application.
- [About](#about)
- [Getting Started](https://cinny.in)
- [Contributing](./CONTRIBUTING.md) - [Contributing](./CONTRIBUTING.md)
- [Roadmap](https://github.com/ajbura/cinny/projects/11) - [Roadmap](https://github.com/ajbura/cinny/projects/11)
## About <a name = "about"></a>
Cinny is a [Matrix](https://matrix.org) client focusing primarily on simple, elegant and secure interface.
![preview](https://github.com/ajbura/cinny-site/blob/master/assets/preview-light.png)
## Building and Running ## Building and Running
### Running pre-compiled ### Running pre-compiled
@@ -20,7 +29,57 @@ Cinny is a [Matrix](https://matrix.org) client focusing primarily on simple, ele
A tarball of pre-compiled version of the app is provided with each [release](https://github.com/ajbura/cinny/releases). A tarball of pre-compiled version of the app is provided with each [release](https://github.com/ajbura/cinny/releases).
You can serve the application with a webserver of your choosing by simply copying `dist/` directory to the webroot. You can serve the application with a webserver of your choosing by simply copying `dist/` directory to the webroot.
<details>
<summary>PGP Public Key to verify pre-compiled tarball</summary>
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBGJw/g0BDAC8qQeLqDMzYzfPyOmRlHVEoguVTo+eo1aVdQH2X7OELdjjBlyj
6d6c1adv/uF2g83NNMoQY7GEeHjRnXE4m8kYSaarb840pxrYUagDc0dAbJOGaCBY
FKTo7U1Kvg0vdiaRuus0pvc1NVdXSxRNQbFXBSwduD+zn66TI3HfcEHNN62FG1cE
K1jWDwLAU0P3kKmj8+CAc3h9ZklPu0k/+t5bf/LJkvdBJAUzGZpehbPL5f3u3BZ0
leZLIrR8uV7PiV5jKFahxlKR5KQHld8qQm+qVhYbUzpuMBGmh419I6UvTzxuRcvU
Frn9ttCEzV55Y+so4X2e4ZnB+5gOnNw+ecifGVdj/+UyWnqvqqDvLrEjjK890nLb
Pil4siecNMEpiwAN6WSmKpWaCwQAHEGDVeZCc/kT0iYfj5FBcsTVqWiO6eaxkUlm
jnulqWqRrlB8CJQQvih/g//uSEBdzIibo+ro+3Jpe120U/XVUH62i9HoRQEm6ADG
4zS5hIq4xyA8fL8AEQEAAbQdQ2lubnlBcHAgPGNpbm55YXBwQGdtYWlsLmNvbT6J
AdQEEwEIAD4WIQSRri2MHidaaZv+vvuUMwx6UK/M8wUCYnD+DQIbAwUJA8JnAAUL
CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCUMwx6UK/M88ApC/9HAdbum1lYBC0s
1k7GwP2A7B4sQtBWjy771BzybWlHeaeG+BGJwg4YiuowXZMm5dubFJFoI/CfeY07
B5aK40/bmT6Xcfkp0VA74c1wUpubBUEJN7tH5HG/OGd9BKeq9E/HHtVaJLVT1k3w
Rhv9VuHO6nR30EEp7IDthftotl5S4lio3+W0pKk4TAKV8vjaCNp3y/lAHzoP1BU9
bUSao+7GXVeArKBjuqxN+t1uuiaxPH4L0oe2pMVjTig04zGJM5fTVoly859MEcC/
R7Taq9RWGfXFmgCXy8Dviz3eOD90vqpCzhX4+ypK0cp2X0UwhMH4dpKUzExmdbhl
eBO5GcHB4VxvloRBNf9/Lr7YOTgWejMUw+MlhZE2RE8unfW1LnM/cjL4dhXzO/XB
FUHHNq8d6d4e02rfWqw7mZo2/NVJgFRcvzw2rgx7w7CKtCNwF4lNjUetB2waZzDb
fAE0kwhK4Iuwvy12JOBzL0Yy9MxANtwUryr/LQz9AmdT4Rwnp0S5AY0EYnD+DQEM
ANOu/d6ZMF8bW+Df9RDCUQKytbaZfa+ZbIHBus7whCD/SQMOhPKntv3HX7SmMCs+
5i27kJMu4YN623JCS7hdCoXVO1R5kXCEcneW/rPBMDutaM472YvIWMIqK9Wwl5+0
Piu2N+uTkKhe9uS2u7eN+Khef3d7xfjGRxoppM+xI9dZO+jhYiy8LuC0oBohTjJq
QPqfGDpowBwRkkOsGz/XVcesJ1Pzg4bKivTS9kZjZSyT9RRSY8As0sVUN57AwYul
s1+eh00n/tVpi2Jj9pCm7S0csSXvXj8v2OTdK1jt4YjpzR0/rwh4+/xlOjDjZEqH
vMPhpzpbgnwkxZ3X8BFne9dJ3maC5zQ3LAeCP5m1W0hXzagYhfyjo74slJgD1O8c
LDf2Oxc5MyM8Y/UK497zfqSPfgT3NhQmhHzk83DjXw3I6Z3A3U+Jp61w0eBRI1nx
H1UIG+gldcAKUTcfwL0lghoT3nmi9JAbvek0Smhz00Bbo8/dx8vwQRxDUxlt7Exx
NwARAQABiQG8BBgBCAAmFiEEka4tjB4nWmmb/r77lDMMelCvzPMFAmJw/g0CGwwF
CQPCZwAACgkQlDMMelCvzPPT7Qv8CjXUEhphZFLwpBfaNOzRNfIXJST9aDit8zHW
IMmfSpORVfpU71IyIB3o/DtTUPwCeb8nvNJs7aj1QT1ZUSsqFa3yY2S16V/g8+WN
sHca6oDSc1J+A0eEpEL1HbG1b5OPBC0AeGvvMOoqrbqThBZVKg1Jc/0SD3cvKElv
aHeCZCNNmfcZ2Ib4HYhhc8//ZtC9TeI+5J/YesctY1M12EoWMxMrc27Y3P5Pa0BI
Uc3qxWggPq1vOFYsEshL0w99HyJvREJmQA7Fa0crV+rICxyrBxJeNnEvjH/0KCBU
LCkEonLY1QwrxyeeV3VpxGE3zHHE3azOdAjTIoAdzX5f/qhbgYlM68GL2f8xdDkp
O0igSGHWhO4F8BfmE7IOTx1Bi7daczp8nCFxh73cKpKB0RUsd9xxrqYpovjmEAlo
w7aHpdzt64NQcsrbK10OSVDF3gFa9Vz20/NQvdUrp8jGmAb/8+nYqI94Jsc28H36
UeGsouhyuITLwEhScounZDqop+Dx
=Zg+6
-----END PGP PUBLIC KEY BLOCK-----
```
</details>
### Building from source ### Building from source
> We recommend using a version manager as versions change very quickly. You will likely need to switch
between multiple Node.js versions based on the needs of different projects you're working on. [NVM on windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) on Windows and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are pretty good choices. Also recommended nodejs version is 16.15.0 LTS.
Execute the following commands to compile the app from its source code: Execute the following commands to compile the app from its source code:
@@ -31,7 +90,7 @@ npm run build # Compiles the app into the dist/ directory
You can then copy the files to a webserver's webroot of your choice. You can then copy the files to a webserver's webroot of your choice.
To serve a development version of the app locally for testing, you may also use the command `npm start`. To serve a development version of the app locally for testing, you need to use the command `npm start`.
### Running with Docker ### Running with Docker
@@ -59,7 +118,7 @@ To set default Homeserver on login and register page, place a customized [`confi
## License ## License
Copyright (c) 2021 Ajay Bura (ajbura) and contributors Copyright (c) 2021 Ajay Bura (ajbura)
Code licensed under the MIT License: <http://opensource.org/licenses/MIT> Code licensed under the MIT License: <http://opensource.org/licenses/MIT>

View File

@@ -7,5 +7,6 @@
"kde.org", "kde.org",
"matrix.org", "matrix.org",
"chat.mozilla.org" "chat.mozilla.org"
] ],
"allowCustomHomeservers": true
} }

2994
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
{ {
"name": "cinny", "name": "cinny",
"version": "1.8.2", "version": "2.0.4",
"description": "Yet another matrix client", "description": "Yet another matrix client",
"main": "index.js", "main": "index.js",
"engines": { "engines": {
"npm": ">=6.14.11", "npm": ">=6.14.8 <=8.5.5",
"node": ">=14.6.0" "node": ">=14.15.0 <=17.9.0"
}, },
"scripts": { "scripts": {
"start": "webpack serve --config ./webpack.dev.js --open", "start": "webpack serve --config ./webpack.dev.js --open",
@@ -15,8 +15,8 @@
"author": "Ajay Bura", "author": "Ajay Bura",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fontsource/inter": "^4.5.7", "@fontsource/inter": "^4.5.10",
"@fontsource/roboto": "^4.5.5", "@fontsource/roboto": "^4.5.7",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
@@ -27,9 +27,9 @@
"flux": "^4.0.3", "flux": "^4.0.3",
"formik": "^2.2.9", "formik": "^2.2.9",
"html-react-parser": "^1.4.12", "html-react-parser": "^1.4.12",
"katex": "^0.15.3", "katex": "^0.15.6",
"linkifyjs": "^2.1.9", "linkifyjs": "^2.1.9",
"matrix-js-sdk": "^17.0.0", "matrix-js-sdk": "^17.2.0",
"micromark": "^3.0.10", "micromark": "^3.0.10",
"micromark-extension-gfm": "^2.0.1", "micromark-extension-gfm": "^2.0.1",
"micromark-extension-math": "^2.0.2", "micromark-extension-math": "^2.0.2",
@@ -43,45 +43,45 @@
"react-dnd-html5-backend": "^15.1.3", "react-dnd-html5-backend": "^15.1.3",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-google-recaptcha": "^2.1.0", "react-google-recaptcha": "^2.1.0",
"react-modal": "^3.14.4", "react-modal": "^3.15.1",
"sanitize-html": "^2.7.0", "sanitize-html": "^2.7.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"twemoji": "^14.0.2" "twemoji": "^14.0.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.9", "@babel/core": "^7.18.0",
"@babel/preset-env": "^7.16.11", "@babel/preset-env": "^7.18.0",
"@babel/preset-react": "^7.16.7", "@babel/preset-react": "^7.17.12",
"assert": "^2.0.0", "assert": "^2.0.0",
"babel-loader": "^8.2.5", "babel-loader": "^8.2.5",
"browserify-fs": "^1.0.0", "browserify-fs": "^1.0.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^11.0.0",
"crypto-browserify": "^3.12.0", "crypto-browserify": "^3.12.0",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1", "css-minimizer-webpack-plugin": "^4.0.0",
"eslint": "^8.14.0", "eslint": "^8.16.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.29.4", "eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.4.0", "eslint-plugin-react-hooks": "^4.5.0",
"favicons": "^6.2.2", "favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2", "favicons-webpack-plugin": "^5.0.2",
"html-loader": "^3.1.0", "html-loader": "^3.1.0",
"html-webpack-plugin": "^5.3.1", "html-webpack-plugin": "^5.3.1",
"mini-css-extract-plugin": "^2.6.0", "mini-css-extract-plugin": "^2.6.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"sass": "^1.50.1", "sass": "^1.52.1",
"sass-loader": "^12.6.0", "sass-loader": "^13.0.0",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"url": "^0.11.0", "url": "^0.11.0",
"util": "^0.12.4", "util": "^0.12.4",
"webpack": "^5.72.0", "webpack": "^5.72.1",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.8.1", "webpack-dev-server": "^4.9.0",
"webpack-merge": "^5.7.3" "webpack-merge": "^5.7.3"
} }
} }

View File

@@ -18,5 +18,11 @@
</head> </head>
<body id="appBody"> <body id="appBody">
<div id="root"></div> <div id="root"></div>
<audio id="notificationSound">
<source src="./sound/notification.ogg" type="audio/ogg" />
</audio>
<audio id="inviteSound">
<source src="./sound/invite.ogg" type="audio/ogg" />
</audio>
</body> </body>
</html> </html>

View File

@@ -123,17 +123,26 @@ const MessageReplyWrapper = React.memo(({ roomTimeline, eventId }) => {
const eTimeline = await mx.getEventTimeline(timelineSet, eventId); const eTimeline = await mx.getEventTimeline(timelineSet, eventId);
await roomTimeline.decryptAllEventsOfTimeline(eTimeline); await roomTimeline.decryptAllEventsOfTimeline(eTimeline);
const mEvent = eTimeline.getTimelineSet().findEventById(eventId); let mEvent = eTimeline.getTimelineSet().findEventById(eventId);
const editedList = roomTimeline.editedTimeline.get(mEvent.getId());
if (editedList) {
mEvent = editedList[editedList.length - 1];
}
const rawBody = mEvent.getContent().body; const rawBody = mEvent.getContent().body;
const username = getUsernameOfRoomMember(mEvent.sender); const username = getUsernameOfRoomMember(mEvent.sender);
if (isMountedRef.current === false) return; if (isMountedRef.current === false) return;
const fallbackBody = mEvent.isRedacted() ? '*** This message has been deleted ***' : '*** Unable to load reply ***'; const fallbackBody = mEvent.isRedacted() ? '*** This message has been deleted ***' : '*** Unable to load reply ***';
let parsedBody = parseReply(rawBody)?.body ?? rawBody ?? fallbackBody;
if (editedList && parsedBody.startsWith(' * ')) {
parsedBody = parsedBody.slice(3);
}
setReply({ setReply({
to: username, to: username,
color: colorMXID(mEvent.getSender()), color: colorMXID(mEvent.getSender()),
body: parseReply(rawBody)?.body ?? rawBody ?? fallbackBody, body: parsedBody,
event: mEvent, event: mEvent,
}); });
} catch { } catch {

View File

@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomIntro.scss'; import './RoomIntro.scss';
import { twemojify } from '../../../util/twemojify';
import colorMXID from '../../../util/colorMXID'; import colorMXID from '../../../util/colorMXID';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
@@ -15,8 +14,8 @@ function RoomIntro({
<div className="room-intro"> <div className="room-intro">
<Avatar imageSrc={avatarSrc} text={name} bgColor={colorMXID(roomId)} size="large" /> <Avatar imageSrc={avatarSrc} text={name} bgColor={colorMXID(roomId)} size="large" />
<div className="room-intro__content"> <div className="room-intro__content">
<Text className="room-intro__name" variant="h1" weight="medium" primary>{twemojify(heading)}</Text> <Text className="room-intro__name" variant="h1" weight="medium" primary>{heading}</Text>
<Text className="room-intro__desc" variant="b1">{twemojify(desc, undefined, true)}</Text> <Text className="room-intro__desc" variant="b1">{desc}</Text>
{ time !== null && <Text className="room-intro__time" variant="b3">{time}</Text>} { time !== null && <Text className="room-intro__time" variant="b3">{time}</Text>}
</div> </div>
</div> </div>
@@ -35,9 +34,9 @@ RoomIntro.propTypes = {
PropTypes.bool, PropTypes.bool,
]), ]),
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
heading: PropTypes.string.isRequired, heading: PropTypes.node.isRequired,
desc: PropTypes.string.isRequired, desc: PropTypes.node.isRequired,
time: PropTypes.string, time: PropTypes.node,
}; };
export default RoomIntro; export default RoomIntro;

View File

@@ -20,7 +20,7 @@ const items = [{
type: cons.notifs.DEFAULT, type: cons.notifs.DEFAULT,
}, { }, {
iconSrc: BellRingIC, iconSrc: BellRingIC,
text: 'All message', text: 'All messages',
type: cons.notifs.ALL_MESSAGES, type: cons.notifs.ALL_MESSAGES,
}, { }, {
iconSrc: BellPingIC, iconSrc: BellPingIC,

View File

@@ -70,7 +70,7 @@ function RoomVisibility({ roomId }) {
const noSpaceParent = currentState.getStateEvents('m.space.parent').length === 0; const noSpaceParent = currentState.getStateEvents('m.space.parent').length === 0;
const mCreate = currentState.getStateEvents('m.room.create')[0]?.getContent(); const mCreate = currentState.getStateEvents('m.room.create')[0]?.getContent();
const roomVersion = Number(mCreate.room_version); const roomVersion = Number(mCreate?.room_version ?? 0);
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0; const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0;
const canChange = room.currentState.hasSufficientPowerLevelFor('state_default', myPowerlevel); const canChange = room.currentState.hasSufficientPowerLevelFor('state_default', myPowerlevel);

View File

@@ -4,7 +4,7 @@ import { emojis } from './emoji';
const eventType = 'io.element.recent_emoji'; const eventType = 'io.element.recent_emoji';
function getRecentEmojisRaw() { function getRecentEmojisRaw() {
return initMatrix.matrixClient.getAccountData(eventType).getContent().recent_emoji ?? []; return initMatrix.matrixClient.getAccountData(eventType)?.getContent().recent_emoji ?? [];
} }
export function getRecentEmojis(limit) { export function getRecentEmojis(limit) {

View File

@@ -0,0 +1,200 @@
/* eslint-disable react/prop-types */
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './EmojiVerification.scss';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
import { hasPrivateKey } from '../../../client/state/secretStorageKeys';
import { getDefaultSSKey, isCrossVerified } from '../../../util/matrixUtil';
import Text from '../../atoms/text/Text';
import IconButton from '../../atoms/button/IconButton';
import Button from '../../atoms/button/Button';
import Spinner from '../../atoms/spinner/Spinner';
import Dialog from '../../molecules/dialog/Dialog';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useStore } from '../../hooks/useStore';
import { accessSecretStorage } from '../settings/SecretStorageAccess';
function EmojiVerificationContent({ data, requestClose }) {
const [sas, setSas] = useState(null);
const [process, setProcess] = useState(false);
const { request, targetDevice } = data;
const mx = initMatrix.matrixClient;
const mountStore = useStore();
const beginStore = useStore();
const beginVerification = async () => {
if (
isCrossVerified(mx.deviceId)
&& (mx.getCrossSigningId() === null || await mx.crypto.crossSigningInfo.isStoredInKeyCache('self_signing') === false)
) {
if (!hasPrivateKey(getDefaultSSKey())) {
const keyData = await accessSecretStorage('Emoji verification');
if (!keyData) {
request.cancel();
return;
}
}
await mx.checkOwnCrossSigningTrust();
}
setProcess(true);
await request.accept();
const verifier = request.beginKeyVerification('m.sas.v1', targetDevice);
const handleVerifier = (sasData) => {
verifier.off('show_sas', handleVerifier);
if (!mountStore.getItem()) return;
setSas(sasData);
setProcess(false);
};
verifier.on('show_sas', handleVerifier);
await verifier.verify();
};
const sasMismatch = () => {
sas.mismatch();
setProcess(true);
};
const sasConfirm = () => {
sas.confirm();
setProcess(true);
};
useEffect(() => {
mountStore.setItem(true);
const handleChange = () => {
if (request.done || request.cancelled) {
requestClose();
return;
}
if (targetDevice && !beginStore.getItem()) {
beginStore.setItem(true);
beginVerification();
}
};
if (request === null) return null;
const req = request;
req.on('change', handleChange);
return () => {
req.off('change', handleChange);
if (req.cancelled === false && req.done === false) {
req.cancel();
}
};
}, [request]);
const renderWait = () => (
<>
<Spinner size="small" />
<Text>Waiting for response from other device...</Text>
</>
);
if (sas !== null) {
return (
<div className="emoji-verification__content">
<Text>Confirm the emoji below are displayed on both devices, in the same order:</Text>
<div className="emoji-verification__emojis">
{sas.sas.emoji.map((emoji, i) => (
// eslint-disable-next-line react/no-array-index-key
<div className="emoji-verification__emoji-block" key={`${emoji[1]}-${i}`}>
<Text variant="h1">{twemojify(emoji[0])}</Text>
<Text>{emoji[1]}</Text>
</div>
))}
</div>
<div className="emoji-verification__buttons">
{process ? renderWait() : (
<>
<Button variant="primary" onClick={sasConfirm}>They match</Button>
<Button onClick={sasMismatch}>{'They don\'t match'}</Button>
</>
)}
</div>
</div>
);
}
if (targetDevice) {
return (
<div className="emoji-verification__content">
<Text>Please accept the request from other device.</Text>
<div className="emoji-verification__buttons">
{renderWait()}
</div>
</div>
);
}
return (
<div className="emoji-verification__content">
<Text>Click accept to start the verification process.</Text>
<div className="emoji-verification__buttons">
{
process
? renderWait()
: <Button variant="primary" onClick={beginVerification}>Accept</Button>
}
</div>
</div>
);
}
EmojiVerificationContent.propTypes = {
data: PropTypes.shape({}).isRequired,
requestClose: PropTypes.func.isRequired,
};
function useVisibilityToggle() {
const [data, setData] = useState(null);
const mx = initMatrix.matrixClient;
useEffect(() => {
const handleOpen = (request, targetDevice) => {
setData({ request, targetDevice });
};
navigation.on(cons.events.navigation.EMOJI_VERIFICATION_OPENED, handleOpen);
mx.on('crypto.verification.request', handleOpen);
return () => {
navigation.removeListener(cons.events.navigation.EMOJI_VERIFICATION_OPENED, handleOpen);
mx.removeListener('crypto.verification.request', handleOpen);
};
}, []);
const requestClose = () => setData(null);
return [data, requestClose];
}
function EmojiVerification() {
const [data, requestClose] = useVisibilityToggle();
return (
<Dialog
isOpen={data !== null}
className="emoji-verification"
title={(
<Text variant="s1" weight="medium" primary>
Emoji verification
</Text>
)}
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
onRequestClose={requestClose}
>
{
data !== null
? <EmojiVerificationContent data={data} requestClose={requestClose} />
: <div />
}
</Dialog>
);
}
export default EmojiVerification;

View File

@@ -0,0 +1,35 @@
@use '../../partials/flex';
@use '../../partials/dir';
.emoji-verification {
&__content {
padding: var(--sp-normal);
@include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
display: flex;
flex-direction: column;
gap: var(--sp-normal);
}
&__emojis {
margin: var(--sp-loose) 0;
display: flex;
align-items: center;
justify-content: space-around;
gap: var(--sp-extra-tight);
flex-wrap: wrap;
}
&__emoji-block {
@extend .cp-fx__column;
flex: 1;
align-items: center;
gap: var(--sp-extra-tight);
white-space: nowrap;
text-transform: capitalize;
}
&__buttons {
display: flex;
gap: var(--sp-normal);
}
}

View File

@@ -56,9 +56,10 @@ function InviteList({ isOpen, onRequestClose }) {
function renderRoomTile(roomId) { function renderRoomTile(roomId) {
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const myRoom = mx.getRoom(roomId); const myRoom = mx.getRoom(roomId);
if (!myRoom) return null;
const roomName = myRoom.name; const roomName = myRoom.name;
let roomAlias = myRoom.getCanonicalAlias(); let roomAlias = myRoom.getCanonicalAlias();
if (roomAlias === null) roomAlias = myRoom.roomId; if (!roomAlias) roomAlias = myRoom.roomId;
const inviterName = myRoom.getMember(mx.getUserId())?.events?.member?.getSender?.() ?? ''; const inviterName = myRoom.getMember(mx.getUserId())?.events?.member?.getSender?.() ?? '';
return ( return (
<RoomTile <RoomTile
@@ -97,12 +98,13 @@ function InviteList({ isOpen, onRequestClose }) {
{ {
Array.from(initMatrix.roomList.inviteDirects).map((roomId) => { Array.from(initMatrix.roomList.inviteDirects).map((roomId) => {
const myRoom = initMatrix.matrixClient.getRoom(roomId); const myRoom = initMatrix.matrixClient.getRoom(roomId);
if (myRoom === null) return null;
const roomName = myRoom.name; const roomName = myRoom.name;
return ( return (
<RoomTile <RoomTile
key={myRoom.roomId} key={myRoom.roomId}
name={roomName} name={roomName}
id={myRoom.getDMInviter()} id={myRoom.getDMInviter() || roomId}
options={ options={
procInvite.has(myRoom.roomId) procInvite.has(myRoom.roomId)
? (<Spinner size="small" />) ? (<Spinner size="small" />)

View File

@@ -103,6 +103,18 @@ function InviteUser({
updateIsSearching(false); updateIsSearching(false);
} }
async function hasDevices(userId) {
try {
const usersDeviceMap = await mx.downloadKeys([userId, mx.getUserId()]);
return Object.values(usersDeviceMap).every((userDevices) =>
Object.keys(userDevices).length > 0,
);
} catch (e) {
console.error("Error determining if it's possible to encrypt to all users: ", e);
return false;
}
}
async function createDM(userId) { async function createDM(userId) {
if (mx.getUserId() === userId) return; if (mx.getUserId() === userId) return;
const dmRoomId = hasDMWith(userId); const dmRoomId = hasDMWith(userId);
@@ -117,7 +129,7 @@ function InviteUser({
procUserError.delete(userId); procUserError.delete(userId);
updateUserProcError(getMapCopy(procUserError)); updateUserProcError(getMapCopy(procUserError));
const result = await roomActions.createDM(userId); const result = await roomActions.createDM(userId, await hasDevices(userId));
roomIdToUserId.set(result.room_id, userId); roomIdToUserId.set(result.room_id, userId);
updateRoomIdToUserId(getMapCopy(roomIdToUserId)); updateRoomIdToUserId(getMapCopy(roomIdToUserId));
} catch (e) { } catch (e) {

View File

@@ -0,0 +1,155 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import './JoinAlias.scss';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
import { join } from '../../../client/action/room';
import { selectRoom, selectSpace } from '../../../client/action/navigation';
import Text from '../../atoms/text/Text';
import IconButton from '../../atoms/button/IconButton';
import Button from '../../atoms/button/Button';
import Input from '../../atoms/input/Input';
import Spinner from '../../atoms/spinner/Spinner';
import Dialog from '../../molecules/dialog/Dialog';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useStore } from '../../hooks/useStore';
const ALIAS_OR_ID_REG = /^[#|!].+:.+\..+$/;
function JoinAliasContent({ term, requestClose }) {
const [process, setProcess] = useState(false);
const [error, setError] = useState(undefined);
const [lastJoinId, setLastJoinId] = useState(undefined);
const mx = initMatrix.matrixClient;
const mountStore = useStore();
const openRoom = (roomId) => {
const room = mx.getRoom(roomId);
if (!room) return;
if (room.isSpaceRoom()) selectSpace(roomId);
else selectRoom(roomId);
requestClose();
};
useEffect(() => {
const handleJoin = (roomId) => {
if (lastJoinId !== roomId) return;
openRoom(roomId);
};
initMatrix.roomList.on(cons.events.roomList.ROOM_JOINED, handleJoin);
return () => {
initMatrix.roomList.removeListener(cons.events.roomList.ROOM_JOINED, handleJoin);
};
}, [lastJoinId]);
const handleSubmit = async (e) => {
e.preventDefault();
mountStore.setItem(true);
const alias = e.target.alias.value;
if (alias?.trim() === '') return;
if (alias.match(ALIAS_OR_ID_REG) === null) {
setError('Invalid address.');
return;
}
setProcess('Looking for address...');
setError(undefined);
let via;
if (alias.startsWith('#')) {
try {
const aliasData = await mx.resolveRoomAlias(alias);
via = aliasData?.servers.slice(0, 3) || [];
if (mountStore.getItem()) {
setProcess(`Joining ${alias}...`);
}
} catch (err) {
if (!mountStore.getItem()) return;
setProcess(false);
setError(`Unable to find room/space with ${alias}. Either room/space is private or doesn't exist.`);
}
}
try {
const roomId = await join(alias, false, via);
if (!mountStore.getItem()) return;
setLastJoinId(roomId);
openRoom(roomId);
} catch {
if (!mountStore.getItem()) return;
setProcess(false);
setError(`Unable to join ${alias}. Either room/space is private or doesn't exist.`);
}
};
return (
<form className="join-alias" onSubmit={handleSubmit}>
<Input
label="Address"
value={term}
name="alias"
required
/>
{error && <Text className="join-alias__error" variant="b3">{error}</Text>}
<div className="join-alias__btn">
{
process
? (
<>
<Spinner size="small" />
<Text>{process}</Text>
</>
)
: <Button variant="primary" type="submit">Join</Button>
}
</div>
</form>
);
}
JoinAliasContent.defaultProps = {
term: undefined,
};
JoinAliasContent.propTypes = {
term: PropTypes.string,
requestClose: PropTypes.func.isRequired,
};
function useWindowToggle() {
const [data, setData] = useState(null);
useEffect(() => {
const handleOpen = (term) => {
setData({ term });
};
navigation.on(cons.events.navigation.JOIN_ALIAS_OPENED, handleOpen);
return () => {
navigation.removeListener(cons.events.navigation.JOIN_ALIAS_OPENED, handleOpen);
};
}, []);
const onRequestClose = () => setData(null);
return [data, onRequestClose];
}
function JoinAlias() {
const [data, requestClose] = useWindowToggle();
return (
<Dialog
isOpen={data !== null}
title={(
<Text variant="s1" weight="medium" primary>Join with address</Text>
)}
contentOptions={<IconButton src={CrossIC} onClick={requestClose} tooltip="Close" />}
onRequestClose={requestClose}
>
{ data ? <JoinAliasContent term={data.term} requestClose={requestClose} /> : <div /> }
</Dialog>
);
}
export default JoinAlias;

View File

@@ -0,0 +1,20 @@
@use '../../partials/dir';
.join-alias {
padding: var(--sp-normal);
@include dir.side(padding, var(--sp-normal), var(--sp-extra-tight));
& > *:not(:first-child) {
margin-top: var(--sp-normal);
}
&__error {
color: var(--tc-danger-high);
margin-top: var(--sp-extra-tight) !important;
}
&__btn {
display: flex;
gap: var(--sp-normal);
}
}

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
@@ -9,12 +10,12 @@ import { roomIdByActivity } from '../../../util/sort';
import RoomsCategory from './RoomsCategory'; import RoomsCategory from './RoomsCategory';
const drawerPostie = new Postie(); const drawerPostie = new Postie();
function Directs() { function Directs({ size }) {
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const { roomList, notifications } = initMatrix; const { roomList, notifications } = initMatrix;
const [directIds, setDirectIds] = useState([]); const [directIds, setDirectIds] = useState([]);
useEffect(() => setDirectIds([...roomList.directs].sort(roomIdByActivity)), []); useEffect(() => setDirectIds([...roomList.directs].sort(roomIdByActivity)), [size]);
useEffect(() => { useEffect(() => {
const handleTimeline = (event, room, toStartOfTimeline, removed, data) => { const handleTimeline = (event, room, toStartOfTimeline, removed, data) => {
@@ -63,5 +64,8 @@ function Directs() {
return <RoomsCategory name="People" hideHeader roomIds={directIds} drawerPostie={drawerPostie} />; return <RoomsCategory name="People" hideHeader roomIds={directIds} drawerPostie={drawerPostie} />;
} }
Directs.propTypes = {
size: PropTypes.number.isRequired,
};
export default Directs; export default Directs;

View File

@@ -42,12 +42,15 @@ function Drawer() {
const [spaceId] = useSelectedSpace(); const [spaceId] = useSelectedSpace();
const [, forceUpdate] = useForceUpdate(); const [, forceUpdate] = useForceUpdate();
const scrollRef = useRef(null); const scrollRef = useRef(null);
const { roomList } = initMatrix;
useEffect(() => { useEffect(() => {
const { roomList } = initMatrix; const handleUpdate = () => {
roomList.on(cons.events.roomList.ROOMLIST_UPDATED, forceUpdate); forceUpdate();
};
roomList.on(cons.events.roomList.ROOMLIST_UPDATED, handleUpdate);
return () => { return () => {
roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, forceUpdate); roomList.removeListener(cons.events.roomList.ROOMLIST_UPDATED, handleUpdate);
}; };
}, []); }, []);
@@ -61,14 +64,16 @@ function Drawer() {
<div className="drawer"> <div className="drawer">
<DrawerHeader selectedTab={selectedTab} spaceId={spaceId} /> <DrawerHeader selectedTab={selectedTab} spaceId={spaceId} />
<div className="drawer__content-wrapper"> <div className="drawer__content-wrapper">
{navigation.selectedSpacePath.length > 1 && <DrawerBreadcrumb spaceId={spaceId} />} {navigation.selectedSpacePath.length > 1 && selectedTab !== cons.tabs.DIRECTS && (
<DrawerBreadcrumb spaceId={spaceId} />
)}
<div className="rooms__wrapper"> <div className="rooms__wrapper">
<ScrollView ref={scrollRef} autoHide> <ScrollView ref={scrollRef} autoHide>
<div className="rooms-container"> <div className="rooms-container">
{ {
selectedTab !== cons.tabs.DIRECTS selectedTab !== cons.tabs.DIRECTS
? <Home spaceId={spaceId} /> ? <Home spaceId={spaceId} />
: <Directs /> : <Directs size={roomList.directs.size} />
} }
</div> </div>
</ScrollView> </ScrollView>

View File

@@ -7,7 +7,7 @@ import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import { import {
openPublicRooms, openCreateRoom, openSpaceManage, openPublicRooms, openCreateRoom, openSpaceManage, openJoinAlias,
openSpaceAddExisting, openInviteUser, openReusableContextMenu, openSpaceAddExisting, openInviteUser, openReusableContextMenu,
} from '../../../client/action/navigation'; } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common'; import { getEventCords } from '../../../util/common';
@@ -60,6 +60,14 @@ export function HomeSpaceOptions({ spaceId, afterOptionSelect }) {
Join public room Join public room
</MenuItem> </MenuItem>
)} )}
{ !spaceId && (
<MenuItem
iconSrc={PlusIC}
onClick={() => { afterOptionSelect(); openJoinAlias(); }}
>
Join with address
</MenuItem>
)}
{ spaceId && ( { spaceId && (
<MenuItem <MenuItem
iconSrc={PlusIC} iconSrc={PlusIC}

View File

@@ -195,7 +195,7 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) {
return rooms.map((room) => { return rooms.map((room) => {
const alias = typeof room.canonical_alias === 'string' ? room.canonical_alias : room.room_id; const alias = typeof room.canonical_alias === 'string' ? room.canonical_alias : room.room_id;
const name = typeof room.name === 'string' ? room.name : alias; const name = typeof room.name === 'string' ? room.name : alias;
const isJoined = initMatrix.matrixClient.getRoom(room.room_id) !== null; const isJoined = initMatrix.matrixClient.getRoom(room.room_id)?.getMyMembership() === 'join';
return ( return (
<RoomTile <RoomTile
key={room.room_id} key={room.room_id}

View File

@@ -7,6 +7,8 @@ import SpaceAddExisting from '../../molecules/space-add-existing/SpaceAddExistin
import Search from '../search/Search'; import Search from '../search/Search';
import ViewSource from '../view-source/ViewSource'; import ViewSource from '../view-source/ViewSource';
import CreateRoom from '../create-room/CreateRoom'; import CreateRoom from '../create-room/CreateRoom';
import JoinAlias from '../join-alias/JoinAlias';
import EmojiVerification from '../emoji-verification/EmojiVerification';
import ReusableDialog from '../../molecules/dialog/ReusableDialog'; import ReusableDialog from '../../molecules/dialog/ReusableDialog';
@@ -18,8 +20,10 @@ function Dialogs() {
<ProfileViewer /> <ProfileViewer />
<ShortcutSpaces /> <ShortcutSpaces />
<CreateRoom /> <CreateRoom />
<JoinAlias />
<SpaceAddExisting /> <SpaceAddExisting />
<Search /> <Search />
<EmojiVerification />
<ReusableDialog /> <ReusableDialog />
</> </>

View File

@@ -8,6 +8,7 @@ import PropTypes from 'prop-types';
import './RoomViewContent.scss'; import './RoomViewContent.scss';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
@@ -50,21 +51,54 @@ function loadingMsgPlaceholders(key, count = 2) {
); );
} }
function genRoomIntro(mEvent, roomTimeline) { function RoomIntroContainer({ event, timeline }) {
const [, nameForceUpdate] = useForceUpdate();
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const roomTopic = roomTimeline.room.currentState.getStateEvents('m.room.topic')[0]?.getContent().topic; const { roomList } = initMatrix;
const isDM = initMatrix.roomList.directs.has(roomTimeline.roomId); const { room } = timeline;
let avatarSrc = roomTimeline.room.getAvatarUrl(mx.baseUrl, 80, 80, 'crop'); const roomTopic = room.currentState.getStateEvents('m.room.topic')[0]?.getContent().topic;
avatarSrc = isDM ? roomTimeline.room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 80, 80, 'crop') : avatarSrc; const isDM = roomList.directs.has(timeline.roomId);
let avatarSrc = room.getAvatarUrl(mx.baseUrl, 80, 80, 'crop');
avatarSrc = isDM ? room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 80, 80, 'crop') : avatarSrc;
const heading = isDM ? room.name : `Welcome to ${room.name}`;
const topic = twemojify(roomTopic || '', undefined, true);
const nameJsx = twemojify(room.name);
const desc = isDM
? (
<>
This is the beginning of your direct message history with @
<b>{nameJsx}</b>
{'. '}
{topic}
</>
)
: (
<>
{'This is the beginning of the '}
<b>{nameJsx}</b>
{' room. '}
{topic}
</>
);
useEffect(() => {
const handleUpdate = () => nameForceUpdate();
roomList.on(cons.events.roomList.ROOM_PROFILE_UPDATED, handleUpdate);
return () => {
roomList.removeListener(cons.events.roomList.ROOM_PROFILE_UPDATED, handleUpdate);
};
}, []);
return ( return (
<RoomIntro <RoomIntro
key={mEvent ? mEvent.getId() : 'room-intro'} roomId={timeline.roomId}
roomId={roomTimeline.roomId}
avatarSrc={avatarSrc} avatarSrc={avatarSrc}
name={roomTimeline.room.name} name={room.name}
heading={`Welcome to ${roomTimeline.room.name}`} heading={twemojify(heading)}
desc={`This is the beginning of the ${roomTimeline.room.name} room.${typeof roomTopic !== 'undefined' ? (` Topic: ${roomTopic}`) : ''}`} desc={desc}
time={mEvent ? `Created at ${dateFormat(mEvent.getDate(), 'dd mmmm yyyy, hh:MM TT')}` : null} time={event ? `Created at ${dateFormat(event.getDate(), 'dd mmmm yyyy, hh:MM TT')}` : null}
/> />
); );
} }
@@ -199,7 +233,7 @@ function usePaginate(
}; };
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer); roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer);
return () => { return () => {
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer); roomTimeline.removeListener(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer);
}; };
}, [roomTimeline]); }, [roomTimeline]);
@@ -470,12 +504,14 @@ function RoomViewContent({ eventId, roomTimeline }) {
if (i === 0 && !roomTimeline.canPaginateBackward()) { if (i === 0 && !roomTimeline.canPaginateBackward()) {
if (mEvent.getType() === 'm.room.create') { if (mEvent.getType() === 'm.room.create') {
tl.push(genRoomIntro(mEvent, roomTimeline)); tl.push(
<RoomIntroContainer key={mEvent.getId()} event={mEvent} timeline={roomTimeline} />,
);
itemCountIndex += 1; itemCountIndex += 1;
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
continue; continue;
} else { } else {
tl.push(genRoomIntro(undefined, roomTimeline)); tl.push(<RoomIntroContainer key="room-intro" event={null} timeline={roomTimeline} />);
itemCountIndex += 1; itemCountIndex += 1;
} }
} }

View File

@@ -4,7 +4,7 @@ import dateFormat from 'dateformat';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import { isCrossVerified } from '../../../util/matrixUtil'; import { isCrossVerified } from '../../../util/matrixUtil';
import { openReusableDialog } from '../../../client/action/navigation'; import { openReusableDialog, openEmojiVerification } from '../../../client/action/navigation';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button'; import Button from '../../atoms/button/Button';
@@ -25,6 +25,7 @@ import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useDeviceList } from '../../hooks/useDeviceList'; import { useDeviceList } from '../../hooks/useDeviceList';
import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus'; import { useCrossSigningStatus } from '../../hooks/useCrossSigningStatus';
import { accessSecretStorage } from './SecretStorageAccess';
const promptDeviceName = async (deviceName) => new Promise((resolve) => { const promptDeviceName = async (deviceName) => new Promise((resolve) => {
let isCompleted = false; let isCompleted = false;
@@ -69,6 +70,7 @@ function DeviceManage() {
const [truncated, setTruncated] = useState(true); const [truncated, setTruncated] = useState(true);
const mountStore = useStore(); const mountStore = useStore();
mountStore.setItem(true); mountStore.setItem(true);
const isMeVerified = isCrossVerified(mx.deviceId);
useEffect(() => { useEffect(() => {
setProcessing([]); setProcessing([]);
@@ -127,18 +129,42 @@ function DeviceManage() {
removeFromProcessing(device); removeFromProcessing(device);
}; };
const verifyWithKey = async (device) => {
const keyData = await accessSecretStorage('Session verification');
if (!keyData) return;
addToProcessing(device);
await mx.checkOwnCrossSigningTrust();
};
const verifyWithEmojis = async (deviceId) => {
const req = await mx.requestVerification(mx.getUserId(), [deviceId]);
openEmojiVerification(req, { userId: mx.getUserId(), deviceId });
};
const verify = (deviceId, isCurrentDevice) => {
if (isCurrentDevice) {
verifyWithKey(deviceId);
return;
}
verifyWithEmojis(deviceId);
};
const renderDevice = (device, isVerified) => { const renderDevice = (device, isVerified) => {
const deviceId = device.device_id; const deviceId = device.device_id;
const displayName = device.display_name; const displayName = device.display_name;
const lastIP = device.last_seen_ip; const lastIP = device.last_seen_ip;
const lastTS = device.last_seen_ts; const lastTS = device.last_seen_ts;
const isCurrentDevice = mx.deviceId === deviceId;
const canVerify = isVerified === false && (isMeVerified || isCurrentDevice);
return ( return (
<SettingTile <SettingTile
key={deviceId} key={deviceId}
title={( title={(
<Text style={{ color: isVerified ? '' : 'var(--tc-danger-high)' }}> <Text style={{ color: isVerified !== false ? '' : 'var(--tc-danger-high)' }}>
{displayName} {displayName}
<Text variant="b3" span>{`${deviceId}${mx.deviceId === deviceId ? ' (current)' : ''}`}</Text> <Text variant="b3" span>{`${displayName ? ' — ' : ''}${deviceId}`}</Text>
{isCurrentDevice && <Text span className="device-manage__current-label" variant="b3">Current</Text>}
</Text> </Text>
)} )}
options={ options={
@@ -146,19 +172,27 @@ function DeviceManage() {
? <Spinner size="small" /> ? <Spinner size="small" />
: ( : (
<> <>
{(isCSEnabled && canVerify) && <Button onClick={() => verify(deviceId, isCurrentDevice)} variant="positive">Verify</Button>}
<IconButton size="small" onClick={() => handleRename(device)} src={PencilIC} tooltip="Rename" /> <IconButton size="small" onClick={() => handleRename(device)} src={PencilIC} tooltip="Rename" />
<IconButton size="small" onClick={() => handleRemove(device)} src={BinIC} tooltip="Remove session" /> <IconButton size="small" onClick={() => handleRemove(device)} src={BinIC} tooltip="Remove session" />
</> </>
) )
} }
content={( content={(
<Text variant="b3"> <>
Last activity <Text variant="b3">
<span style={{ color: 'var(--tc-surface-normal)' }}> Last activity
{dateFormat(new Date(lastTS), ' hh:MM TT, dd/mm/yyyy')} <span style={{ color: 'var(--tc-surface-normal)' }}>
</span> {dateFormat(new Date(lastTS), ' hh:MM TT, dd/mm/yyyy')}
{lastIP ? ` at ${lastIP}` : ''} </span>
</Text> {lastIP ? ` at ${lastIP}` : ''}
</Text>
{isCurrentDevice && (
<Text style={{ marginTop: 'var(--sp-ultra-tight)' }} variant="b3">
{`Session Key: ${initMatrix.matrixClient.getDeviceEd25519Key().match(/.{1,4}/g).join(' ')}`}
</Text>
)}
</>
)} )}
/> />
); );
@@ -200,7 +234,7 @@ function DeviceManage() {
{noEncryption.length > 0 && ( {noEncryption.length > 0 && (
<div> <div>
<MenuHeader>Sessions without encryption support</MenuHeader> <MenuHeader>Sessions without encryption support</MenuHeader>
{noEncryption.map((device) => renderDevice(device, true))} {noEncryption.map((device) => renderDevice(device, null))}
</div> </div>
)} )}
<div> <div>
@@ -211,7 +245,7 @@ function DeviceManage() {
if (truncated && index >= TRUNCATED_COUNT) return null; if (truncated && index >= TRUNCATED_COUNT) return null;
return renderDevice(device, true); return renderDevice(device, true);
}) })
: <Text className="device-manage__info">No verified session</Text> : <Text className="device-manage__info">No verified sessions</Text>
} }
{ verified.length > TRUNCATED_COUNT && ( { verified.length > TRUNCATED_COUNT && (
<Button className="device-manage__info" onClick={() => setTruncated(!truncated)}> <Button className="device-manage__info" onClick={() => setTruncated(!truncated)}>

View File

@@ -15,6 +15,23 @@
& .setting-tile:last-of-type { & .setting-tile:last-of-type {
border-bottom: none; border-bottom: none;
} }
& .setting-tile__options {
display: flex;
align-items: center;
gap: var(--sp-ultra-tight);
& .btn-positive {
padding: 6px var(--sp-tight);
min-width: 0;
}
}
&__current-label {
margin: 0 var(--sp-extra-tight);
padding: 2px var(--sp-ultra-tight);
color: var(--bg-surface);
background-color: var(--tc-surface-low);
border-radius: 4px;
}
&__rename { &__rename {
padding: var(--sp-normal); padding: var(--sp-normal);

View File

@@ -159,9 +159,9 @@ function DeleteKeyBackupDialog({ requestClose }) {
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const mountStore = useStore(); const mountStore = useStore();
mountStore.setItem(true);
const deleteBackup = async () => { const deleteBackup = async () => {
mountStore.setItem(true);
setIsDeleting(true); setIsDeleting(true);
try { try {
const backupInfo = await mx.getKeyBackupVersion(); const backupInfo = await mx.getKeyBackupVersion();

View File

@@ -24,14 +24,14 @@ function SecretStorageAccess({ onComplete }) {
const [process, setProcess] = useState(false); const [process, setProcess] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const mountStore = useStore(); const mountStore = useStore();
mountStore.setItem(true);
const toggleWithPhrase = () => setWithPhrase(!withPhrase); const toggleWithPhrase = () => setWithPhrase(!withPhrase);
const processInput = async ({ key, phrase }) => { const processInput = async ({ key, phrase }) => {
mountStore.setItem(true);
setProcess(true); setProcess(true);
try { try {
const { salt, iterations } = sSKeyInfo.passphrase; const { salt, iterations } = sSKeyInfo.passphrase || {};
const privateKey = key const privateKey = key
? mx.keyBackupKeyFromRecoveryKey(key) ? mx.keyBackupKeyFromRecoveryKey(key)
: await deriveKey(phrase, salt, iterations); : await deriveKey(phrase, salt, iterations);

View File

@@ -93,12 +93,13 @@ function Homeserver({ onChange }) {
const result = await (await fetch(configFileUrl, { method: 'GET' })).json(); const result = await (await fetch(configFileUrl, { method: 'GET' })).json();
const selectedHs = result?.defaultHomeserver; const selectedHs = result?.defaultHomeserver;
const hsList = result?.homeserverList; const hsList = result?.homeserverList;
const allowCustom = result?.allowCustomHomeservers ?? true;
if (!hsList?.length > 0 || selectedHs < 0 || selectedHs >= hsList?.length) { if (!hsList?.length > 0 || selectedHs < 0 || selectedHs >= hsList?.length) {
throw new Error(); throw new Error();
} }
setHs({ selected: hsList[selectedHs], list: hsList }); setHs({ selected: hsList[selectedHs], list: hsList, allowCustom: allowCustom });
} catch { } catch {
setHs({ selected: 'matrix.org', list: ['matrix.org'] }); setHs({ selected: 'matrix.org', list: ['matrix.org'], allowCustom: true });
} }
}, []); }, []);
@@ -106,14 +107,15 @@ function Homeserver({ onChange }) {
const { value } = e.target; const { value } = e.target;
setProcess({ isLoading: false }); setProcess({ isLoading: false });
debounce._(async () => { debounce._(async () => {
setHs({ selected: value.trim(), list: hs.list }); setHs({ ...hs, selected: value.trim() });
}, 700)(); }, 700)();
}; };
return ( return (
<> <>
<div className="homeserver-form"> <div className="homeserver-form">
<Input name="homeserver" onChange={handleHsInput} value={hs?.selected} forwardRef={hsRef} label="Homeserver" /> <Input name="homeserver" onChange={handleHsInput} value={hs?.selected} forwardRef={hsRef} label="Homeserver"
disabled={hs === null || !hs.allowCustom} />
<ContextMenu <ContextMenu
placement="right" placement="right"
content={(hideMenu) => ( content={(hideMenu) => (
@@ -126,7 +128,7 @@ function Homeserver({ onChange }) {
onClick={() => { onClick={() => {
hideMenu(); hideMenu();
hsRef.current.value = hsName; hsRef.current.value = hsName;
setHs({ selected: hsName, list: hs.list }); setHs({ ...hs, selected: hsName });
}} }}
> >
{hsName} {hsName}

View File

@@ -86,6 +86,13 @@ export function openCreateRoom(isSpace = false, parentId = null) {
}); });
} }
export function openJoinAlias(term) {
appDispatcher.dispatch({
type: cons.actions.navigation.OPEN_JOIN_ALIAS,
term,
});
}
export function openInviteUser(roomId, searchTerm) { export function openInviteUser(roomId, searchTerm) {
appDispatcher.dispatch({ appDispatcher.dispatch({
type: cons.actions.navigation.OPEN_INVITE_USER, type: cons.actions.navigation.OPEN_INVITE_USER,
@@ -166,3 +173,11 @@ export function openReusableDialog(title, render, afterClose) {
afterClose, afterClose,
}); });
} }
export function openEmojiVerification(request, targetDevice) {
appDispatcher.dispatch({
type: cons.actions.navigation.OPEN_EMOJI_VERIFICATION,
request,
targetDevice,
});
}

View File

@@ -113,17 +113,19 @@ async function join(roomIdOrAlias, isDM, via) {
* @param {string} roomId * @param {string} roomId
* @param {boolean} isDM * @param {boolean} isDM
*/ */
function leave(roomId) { async function leave(roomId) {
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const isDM = initMatrix.roomList.directs.has(roomId); const isDM = initMatrix.roomList.directs.has(roomId);
mx.leave(roomId) try {
.then(() => { await mx.leave(roomId);
appDispatcher.dispatch({ appDispatcher.dispatch({
type: cons.actions.room.LEAVE, type: cons.actions.room.LEAVE,
roomId, roomId,
isDM, isDM,
}); });
}).catch(); } catch {
console.error('Unable to leave room.');
}
} }
async function create(options, isDM = false) { async function create(options, isDM = false) {

View File

@@ -2,25 +2,60 @@ import { openSearch, toggleRoomSettings } from '../action/navigation';
import navigation from '../state/navigation'; import navigation from '../state/navigation';
import { markAsRead } from '../action/notifications'; import { markAsRead } from '../action/notifications';
function shouldFocusMessageField(code) {
// do not focus on F keys
if (/^F\d+$/.test(code)) return false;
// do not focus on numlock/scroll lock
if (
code.metaKey
|| code.startsWith('OS')
|| code.startsWith('Meta')
|| code.startsWith('Shift')
|| code.startsWith('Alt')
|| code.startsWith('Control')
|| code.startsWith('Arrow')
|| code === 'Tab'
|| code === 'Space'
|| code === 'Enter'
|| code === 'NumLock'
|| code === 'ScrollLock'
) {
return false;
}
return true;
}
function listenKeyboard(event) { function listenKeyboard(event) {
// Ctrl/Cmd + // Ctrl/Cmd +
if (event.ctrlKey || event.metaKey) { if (event.ctrlKey || event.metaKey) {
// k - for search Modal // open search modal
if (event.keyCode === 75) { if (event.code === 'KeyK') {
event.preventDefault(); event.preventDefault();
if (navigation.isRawModalVisible) return; if (navigation.isRawModalVisible) return;
openSearch(); openSearch();
} }
// focus message field on paste
if (event.code === 'KeyV') {
if (navigation.isRawModalVisible) return;
const msgTextarea = document.getElementById('message-textarea');
const { activeElement } = document;
if (activeElement !== msgTextarea
&& ['input', 'textarea'].includes(activeElement.tagName.toLowerCase())
) return;
msgTextarea?.focus();
}
} }
if (!event.ctrlKey && !event.altKey) { if (!event.ctrlKey && !event.altKey && !event.metaKey) {
if (navigation.isRawModalVisible) return; if (navigation.isRawModalVisible) return;
if (['text', 'textarea'].includes(document.activeElement.type)) { if (['input', 'textarea'].includes(document.activeElement.tagName.toLowerCase())) {
return; return;
} }
// esc if (event.code === 'Escape') {
if (event.keyCode === 27) {
if (navigation.isRoomSettings) { if (navigation.isRoomSettings) {
toggleRoomSettings(); toggleRoomSettings();
return; return;
@@ -31,16 +66,12 @@ function listenKeyboard(event) {
} }
} }
// Don't allow these keys to type/focus message field // focus the text field on most keypresses
if ((event.keyCode !== 8 && event.keyCode < 48) if (shouldFocusMessageField(event.code)) {
|| (event.keyCode >= 91 && event.keyCode <= 93) // press any key to focus and type in message field
|| (event.keyCode >= 112 && event.keyCode <= 183)) { const msgTextarea = document.getElementById('message-textarea');
return; msgTextarea?.focus();
} }
// press any key to focus and type in message field
const msgTextarea = document.getElementById('message-textarea');
msgTextarea?.focus();
} }
} }

View File

@@ -38,6 +38,9 @@ class InitMatrix extends EventEmitter {
deviceId: secret.deviceId, deviceId: secret.deviceId,
timelineSupport: true, timelineSupport: true,
cryptoCallbacks, cryptoCallbacks,
verificationMethods: [
'm.sas.v1',
],
}); });
await this.matrixClient.initCrypto(); await this.matrixClient.initCrypto();

View File

@@ -6,9 +6,6 @@ import cons from './cons';
import navigation from './navigation'; import navigation from './navigation';
import settings from './settings'; import settings from './settings';
import NotificationSound from '../../../public/sound/notification.ogg';
import InviteSound from '../../../public/sound/invite.ogg';
function isNotifEvent(mEvent) { function isNotifEvent(mEvent) {
const eType = mEvent.getType(); const eType = mEvent.getType();
if (!cons.supportEventTypes.includes(eType)) return false; if (!cons.supportEventTypes.includes(eType)) return false;
@@ -238,14 +235,14 @@ class Notifications extends EventEmitter {
_playNotiSound() { _playNotiSound() {
if (!this._notiAudio) { if (!this._notiAudio) {
this._notiAudio = new Audio(NotificationSound); this._notiAudio = document.getElementById('notificationSound');
} }
this._notiAudio.play(); this._notiAudio.play();
} }
_playInviteSound() { _playInviteSound() {
if (!this._inviteAudio) { if (!this._inviteAudio) {
this._inviteAudio = new Audio(InviteSound); this._inviteAudio = document.getElementById('inviteSound');
} }
this._inviteAudio.play(); this._inviteAudio.play();
} }

View File

@@ -6,6 +6,21 @@ function isMEventSpaceChild(mEvent) {
return mEvent.getType() === 'm.space.child' && Object.keys(mEvent.getContent()).length > 0; return mEvent.getType() === 'm.space.child' && Object.keys(mEvent.getContent()).length > 0;
} }
/**
* @param {() => boolean} callback if return true wait will over else callback will be called again.
* @param {number} timeout timeout to callback
* @param {number} maxTry maximum callback try > 0. -1 means no limit
*/
async function waitFor(callback, timeout = 400, maxTry = -1) {
if (maxTry === 0) return false;
const isOver = async () => new Promise((resolve) => {
setTimeout(() => resolve(callback()), timeout);
});
if (await isOver()) return true;
return waitFor(callback, timeout, maxTry - 1);
}
class RoomList extends EventEmitter { class RoomList extends EventEmitter {
constructor(matrixClient) { constructor(matrixClient) {
super(); super();
@@ -228,6 +243,7 @@ class RoomList extends EventEmitter {
} }
_isDMInvite(room) { _isDMInvite(room) {
if (this.mDirects.has(room.roomId)) return true;
const me = room.getMember(this.matrixClient.getUserId()); const me = room.getMember(this.matrixClient.getUserId());
const myEventContent = me.events.member.getContent(); const myEventContent = me.events.member.getContent();
return myEventContent.membership === 'invite' && myEventContent.is_direct; return myEventContent.membership === 'invite' && myEventContent.is_direct;
@@ -243,22 +259,11 @@ class RoomList extends EventEmitter {
latestMDirects.forEach((directId) => { latestMDirects.forEach((directId) => {
const myRoom = this.matrixClient.getRoom(directId); const myRoom = this.matrixClient.getRoom(directId);
if (this.mDirects.has(directId)) return; if (this.mDirects.has(directId)) return;
// Update mDirects
this.mDirects.add(directId); this.mDirects.add(directId);
if (myRoom === null) return; if (myRoom === null) return;
if (myRoom.getMyMembership() === 'join') {
if (this._isDMInvite(myRoom)) return;
if (myRoom.getMyMembership === 'join' && !this.directs.has(directId)) {
this.directs.add(directId); this.directs.add(directId);
}
// Newly added room.
// at this time my membership can be invite | join
if (myRoom.getMyMembership() === 'join' && this.rooms.has(directId)) {
// found a DM which accidentally gets added to this.rooms
this.rooms.delete(directId); this.rooms.delete(directId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED); this.emit(cons.events.roomList.ROOMLIST_UPDATED);
} }
@@ -298,23 +303,17 @@ class RoomList extends EventEmitter {
} }
}); });
this.matrixClient.on('Room.myMembership', (room, membership, prevMembership) => { this.matrixClient.on('Room.myMembership', async (room, membership, prevMembership) => {
// room => prevMembership = null | invite | join | leave | kick | ban | unban // room => prevMembership = null | invite | join | leave | kick | ban | unban
// room => membership = invite | join | leave | kick | ban | unban // room => membership = invite | join | leave | kick | ban | unban
const { roomId } = room; const { roomId } = room;
const isRoomReady = () => this.matrixClient.getRoom(roomId) !== null;
if (['join', 'invite'].includes(membership) && isRoomReady() === false) {
if (await waitFor(isRoomReady, 200, 100) === false) return;
}
if (membership === 'unban') return; if (membership === 'unban') return;
// When user_reject/sender_undo room invite
if (prevMembership === 'invite') {
if (this.inviteDirects.has(roomId)) this.inviteDirects.delete(roomId);
else if (this.inviteSpaces.has(roomId)) this.inviteSpaces.delete(roomId);
else this.inviteRooms.delete(roomId);
this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId);
}
// When user get invited
if (membership === 'invite') { if (membership === 'invite') {
if (this._isDMInvite(room)) this.inviteDirects.add(roomId); if (this._isDMInvite(room)) this.inviteDirects.add(roomId);
else if (room.isSpaceRoom()) this.inviteSpaces.add(roomId); else if (room.isSpaceRoom()) this.inviteSpaces.add(roomId);
@@ -324,88 +323,53 @@ class RoomList extends EventEmitter {
return; return;
} }
// When user join room (first time) or start DM. if (prevMembership === 'invite') {
if ((prevMembership === null || prevMembership === 'invite') && membership === 'join') { if (this.inviteDirects.has(roomId)) this.inviteDirects.delete(roomId);
// when user create room/DM OR accept room/dm invite from this client. else if (this.inviteSpaces.has(roomId)) this.inviteSpaces.delete(roomId);
// we will update this.rooms/this.directs with user action else this.inviteRooms.delete(roomId);
if (this.directs.has(roomId) || this.spaces.has(roomId) || this.rooms.has(roomId)) return;
if (this.processingRooms.has(roomId)) { this.emit(cons.events.roomList.INVITELIST_UPDATED, roomId);
const procRoomInfo = this.processingRooms.get(roomId);
if (procRoomInfo.isDM) this.directs.add(roomId);
else if (room.isSpaceRoom()) this.addToSpaces(roomId);
else this.rooms.add(roomId);
if (procRoomInfo.task === 'CREATE') this.emit(cons.events.roomList.ROOM_CREATED, roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
this.processingRooms.delete(roomId);
return;
}
if (room.isSpaceRoom()) {
this.addToSpaces(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
return;
}
// below code intented to work when user create room/DM
// OR accept room/dm invite from other client.
// and we have to update our client. (it's ok to have 10sec delay)
// create a buffer of 10sec and HOPE client.accoundData get updated
// then accoundData event listener will update this.mDirects.
// and we will be able to know if it's a DM.
// ----------
// less likely situation:
// if we don't get accountData with 10sec then:
// we will temporary add it to this.rooms.
// and in future when accountData get updated
// accountData listener will automatically goona REMOVE it from this.rooms
// and will ADD it to this.directs
// and emit the cons.events.roomList.ROOMLIST_UPDATED to update the UI.
setTimeout(() => {
if (this.directs.has(roomId) || this.spaces.has(roomId) || this.rooms.has(roomId)) return;
if (this.mDirects.has(roomId)) this.directs.add(roomId);
else this.rooms.add(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
}, 10000);
return;
} }
// when room is a DM add/remove it from DM's and return. if (['leave', 'kick', 'ban'].includes(membership)) {
if (this.directs.has(roomId)) { if (this.directs.has(roomId)) this.directs.delete(roomId);
if (membership === 'leave' || membership === 'kick' || membership === 'ban') { else if (this.spaces.has(roomId)) this.deleteFromSpaces(roomId);
this.directs.delete(roomId); else this.rooms.delete(roomId);
this.emit(cons.events.roomList.ROOM_LEAVED, roomId); this.emit(cons.events.roomList.ROOM_LEAVED, roomId);
}
}
if (this.mDirects.has(roomId)) {
if (membership === 'join') {
this.directs.add(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
}
this.emit(cons.events.roomList.ROOMLIST_UPDATED); this.emit(cons.events.roomList.ROOMLIST_UPDATED);
return; return;
} }
// when room is not a DM add/remove it from rooms.
if (membership === 'leave' || membership === 'kick' || membership === 'ban') { // when user create room/DM OR accept room/dm invite from this client.
if (room.isSpaceRoom()) this.deleteFromSpaces(roomId); // we will update this.rooms/this.directs with user action
else this.rooms.delete(roomId); if (membership === 'join' && this.processingRooms.has(roomId)) {
this.emit(cons.events.roomList.ROOM_LEAVED, roomId); const procRoomInfo = this.processingRooms.get(roomId);
if (procRoomInfo.isDM) this.directs.add(roomId);
else if (room.isSpaceRoom()) this.addToSpaces(roomId);
else this.rooms.add(roomId);
if (procRoomInfo.task === 'CREATE') this.emit(cons.events.roomList.ROOM_CREATED, roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
this.processingRooms.delete(roomId);
return;
} }
if (this.mDirects.has(roomId) && membership === 'join') {
this.directs.add(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
return;
}
if (membership === 'join') { if (membership === 'join') {
if (room.isSpaceRoom()) this.addToSpaces(roomId); if (room.isSpaceRoom()) this.addToSpaces(roomId);
else this.rooms.add(roomId); else this.rooms.add(roomId);
this.emit(cons.events.roomList.ROOM_JOINED, roomId); this.emit(cons.events.roomList.ROOM_JOINED, roomId);
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
} }
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
}); });
} }
} }

View File

@@ -1,5 +1,5 @@
const cons = { const cons = {
version: '1.8.2', version: '2.0.4',
secretKey: { secretKey: {
ACCESS_TOKEN: 'cinny_access_token', ACCESS_TOKEN: 'cinny_access_token',
DEVICE_ID: 'cinny_device_id', DEVICE_ID: 'cinny_device_id',
@@ -38,6 +38,7 @@ const cons = {
OPEN_INVITE_LIST: 'OPEN_INVITE_LIST', OPEN_INVITE_LIST: 'OPEN_INVITE_LIST',
OPEN_PUBLIC_ROOMS: 'OPEN_PUBLIC_ROOMS', OPEN_PUBLIC_ROOMS: 'OPEN_PUBLIC_ROOMS',
OPEN_CREATE_ROOM: 'OPEN_CREATE_ROOM', OPEN_CREATE_ROOM: 'OPEN_CREATE_ROOM',
OPEN_JOIN_ALIAS: 'OPEN_JOIN_ALIAS',
OPEN_INVITE_USER: 'OPEN_INVITE_USER', OPEN_INVITE_USER: 'OPEN_INVITE_USER',
OPEN_PROFILE_VIEWER: 'OPEN_PROFILE_VIEWER', OPEN_PROFILE_VIEWER: 'OPEN_PROFILE_VIEWER',
OPEN_SETTINGS: 'OPEN_SETTINGS', OPEN_SETTINGS: 'OPEN_SETTINGS',
@@ -49,6 +50,7 @@ const cons = {
OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU', OPEN_REUSABLE_CONTEXT_MENU: 'OPEN_REUSABLE_CONTEXT_MENU',
OPEN_NAVIGATION: 'OPEN_NAVIGATION', OPEN_NAVIGATION: 'OPEN_NAVIGATION',
OPEN_REUSABLE_DIALOG: 'OPEN_REUSABLE_DIALOG', OPEN_REUSABLE_DIALOG: 'OPEN_REUSABLE_DIALOG',
OPEN_EMOJI_VERIFICATION: 'OPEN_EMOJI_VERIFICATION',
}, },
room: { room: {
JOIN: 'JOIN', JOIN: 'JOIN',
@@ -85,6 +87,7 @@ const cons = {
INVITE_LIST_OPENED: 'INVITE_LIST_OPENED', INVITE_LIST_OPENED: 'INVITE_LIST_OPENED',
PUBLIC_ROOMS_OPENED: 'PUBLIC_ROOMS_OPENED', PUBLIC_ROOMS_OPENED: 'PUBLIC_ROOMS_OPENED',
CREATE_ROOM_OPENED: 'CREATE_ROOM_OPENED', CREATE_ROOM_OPENED: 'CREATE_ROOM_OPENED',
JOIN_ALIAS_OPENED: 'JOIN_ALIAS_OPENED',
INVITE_USER_OPENED: 'INVITE_USER_OPENED', INVITE_USER_OPENED: 'INVITE_USER_OPENED',
SETTINGS_OPENED: 'SETTINGS_OPENED', SETTINGS_OPENED: 'SETTINGS_OPENED',
PROFILE_VIEWER_OPENED: 'PROFILE_VIEWER_OPENED', PROFILE_VIEWER_OPENED: 'PROFILE_VIEWER_OPENED',
@@ -96,6 +99,7 @@ const cons = {
REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED', REUSABLE_CONTEXT_MENU_OPENED: 'REUSABLE_CONTEXT_MENU_OPENED',
NAVIGATION_OPENED: 'NAVIGATION_OPENED', NAVIGATION_OPENED: 'NAVIGATION_OPENED',
REUSABLE_DIALOG_OPENED: 'REUSABLE_DIALOG_OPENED', REUSABLE_DIALOG_OPENED: 'REUSABLE_DIALOG_OPENED',
EMOJI_VERIFICATION_OPENED: 'EMOJI_VERIFICATION_OPENED',
}, },
roomList: { roomList: {
ROOMLIST_UPDATED: 'ROOMLIST_UPDATED', ROOMLIST_UPDATED: 'ROOMLIST_UPDATED',

View File

@@ -14,7 +14,7 @@ class Navigation extends EventEmitter {
this.isRoomSettings = false; this.isRoomSettings = false;
this.recentRooms = []; this.recentRooms = [];
this.isRawModalVisible = false; this.rawModelStack = [];
} }
_setSpacePath(roomId) { _setSpacePath(roomId) {
@@ -47,8 +47,13 @@ class Navigation extends EventEmitter {
} }
} }
get isRawModalVisible() {
return this.rawModelStack.length > 0;
}
setIsRawModalVisible(visible) { setIsRawModalVisible(visible) {
this.isRawModalVisible = visible; if (visible) this.rawModelStack.push(true);
else this.rawModelStack.pop();
} }
navigate(action) { navigate(action) {
@@ -122,6 +127,12 @@ class Navigation extends EventEmitter {
action.parentId, action.parentId,
); );
}, },
[cons.actions.navigation.OPEN_JOIN_ALIAS]: () => {
this.emit(
cons.events.navigation.JOIN_ALIAS_OPENED,
action.term,
);
},
[cons.actions.navigation.OPEN_INVITE_USER]: () => { [cons.actions.navigation.OPEN_INVITE_USER]: () => {
this.emit(cons.events.navigation.INVITE_USER_OPENED, action.roomId, action.searchTerm); this.emit(cons.events.navigation.INVITE_USER_OPENED, action.roomId, action.searchTerm);
}, },
@@ -185,6 +196,13 @@ class Navigation extends EventEmitter {
action.afterClose, action.afterClose,
); );
}, },
[cons.actions.navigation.OPEN_EMOJI_VERIFICATION]: () => {
this.emit(
cons.events.navigation.EMOJI_VERIFICATION_OPENED,
action.request,
action.targetDevice,
);
},
}; };
actions[action.type]?.(); actions[action.type]?.();
} }

View File

@@ -475,6 +475,10 @@ textarea {
supported by Chrome, Edge, Opera and Firefox */ supported by Chrome, Edge, Opera and Firefox */
} }
audio:not([controls]) {
display: none !important;
}
.flex--center { .flex--center {
display: flex; display: flex;
justify-content: center; justify-content: center;