Compare commits

...
Author SHA1 Message Date
Ajay Bura 1ac790647b Merge branch 'dev' into custom-emoji-viewer 2022-08-05 19:24:39 +05:30
ginnyTheCat 5e527e434a Fix shortcuts on non QWERTY keyboards (#715)
* Use key instead of keyCode or code

* Use key for Escape
2022-08-05 19:12:25 +05:30
Ajay Bura aa6fb5940b Add support for sending stickers 2022-08-05 17:10:58 +05:30
Ajay Bura bcd2bffd0c Fix pill generating regex 2022-08-05 11:27:54 +05:30
Ajay Bura c0a10e9b18 Send user pills 2022-08-05 11:19:26 +05:30
Ajay Bura 5ad03dad5f Remove unnessesary comments 2022-08-05 10:34:01 +05:30
Ajay Bura 7abd8aebe0 Fix sending space emoji in message 2022-08-04 20:06:07 +05:30
Ajay Bura 46a72ae098 Fix stretched custom emoji 2022-08-04 19:46:36 +05:30
Ajay Bura 0f5ced76a2 Fix reaction width 2022-08-04 19:40:26 +05:30
Ajay Bura c80310603c Fix sticker jump bug 2022-08-04 19:37:25 +05:30
Ajay Bura a33cefea76 Render stickers as stickers 2022-08-04 19:25:38 +05:30
Ajay Bura 519f620cb7 Send custom emoji reaction as mxc 2022-08-04 17:14:08 +05:30
Ajay Bura b0741faa4d Show space emoji in emojiboard 2022-08-04 16:22:39 +05:30
Ajay Bura b5788a4f58 Merge branch 'custom-emoji-viewer' of https://github.com/ajbura/cinny into custom-emoji-viewer 2022-08-03 20:05:14 +05:30
Ajay Bura 5fb2958b0a Show global pack selector in settings 2022-08-03 20:03:52 +05:30
Ajay Bura d05c03cacd Add personal emoji in settings 2022-08-03 20:03:52 +05:30
Ajay Bura 42a8daa187 Create or delete pack 2022-08-03 20:03:52 +05:30
Ajay Bura f646daa322 Bug fix 2022-08-03 20:03:52 +05:30
Ajay Bura 9174d6d93e Add checkbox to make pack global 2022-08-03 20:03:52 +05:30
Ajay Bura 91b18cc5fd Change pack avatar, name & attribution 2022-08-03 20:03:52 +05:30
Ajay Bura 6ae79e080f Add/delete/rename images to exisitng packs 2022-08-03 20:03:52 +05:30
Ajay Bura 7d8c4ce5f1 WIP 2022-08-03 20:03:52 +05:30
Ajay Bura 5816ec7d2f Fix some pack not visible in emojiboard 2022-08-03 20:03:52 +05:30
Ajay Bura 4fd2d3adc2 Display emoji and sticker in room settings 2022-08-03 20:03:52 +05:30
Ajay Bura d678b05259 Show global image packs in emoji picker 2022-08-03 20:03:52 +05:30
Ajay Bura 5325485ff3 Show custom emoji first in suggestions 2022-08-03 20:03:52 +05:30
Ajay Bura 89eb1334fc Remove comments 2022-08-03 20:03:52 +05:30
ginnyTheCat 1d90f7588b Allow removing the room name (#702) 2022-08-03 19:59:56 +05:30
dependabot[bot] f8b8a35152 Bump html-react-parser from 2.0.0 to 3.0.1 (#675)
Bumps [html-react-parser](https://github.com/remarkablemark/html-react-parser) from 2.0.0 to 3.0.1.
- [Release notes](https://github.com/remarkablemark/html-react-parser/releases)
- [Changelog](https://github.com/remarkablemark/html-react-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/remarkablemark/html-react-parser/compare/v2.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: html-react-parser
  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-08-03 19:52:50 +05:30
dependabot[bot] fa4c95a9b6 Bump html-loader from 3.1.2 to 4.1.0 (#677)
Bumps [html-loader](https://github.com/webpack-contrib/html-loader) from 3.1.2 to 4.1.0.
- [Release notes](https://github.com/webpack-contrib/html-loader/releases)
- [Changelog](https://github.com/webpack-contrib/html-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/html-loader/compare/v3.1.2...v4.1.0)

---
updated-dependencies:
- dependency-name: html-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-08-03 19:36:36 +05:30
dependabot[bot] a478fc4805 Bump sass from 1.53.0 to 1.54.1 (#712)
Bumps [sass](https://github.com/sass/dart-sass) from 1.53.0 to 1.54.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.53.0...1.54.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-08-03 19:33:54 +05:30
dependabot[bot]andKrishan febb28e9c4 Bump katex from 0.15.6 to 0.16.0 (#616)
* Bump katex from 0.15.6 to 0.16.0

Bumps [katex](https://github.com/KaTeX/KaTeX) from 0.15.6 to 0.16.0.
- [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.6...v0.16.0)

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

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

* Remove copy-tex.css as it no longer required

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
2022-08-03 19:28:54 +05:30
dependabot[bot] c78a39af50 Bump webpack from 5.73.0 to 5.74.0 (#696)
Bumps [webpack](https://github.com/webpack/webpack) from 5.73.0 to 5.74.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.73.0...v5.74.0)

---
updated-dependencies:
- dependency-name: webpack
  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-08-03 19:27:07 +05:30
dependabot[bot] 21e6049c16 Bump sanitize-html from 2.7.0 to 2.7.1 (#698)
Bumps [sanitize-html](https://github.com/apostrophecms/sanitize-html) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/apostrophecms/sanitize-html/releases)
- [Changelog](https://github.com/apostrophecms/sanitize-html/blob/main/CHANGELOG.md)
- [Commits](https://github.com/apostrophecms/sanitize-html/compare/2.7.0...2.7.1)

---
updated-dependencies:
- dependency-name: sanitize-html
  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-08-03 19:25:02 +05:30
dependabot[bot] 6e418337cc Bump eslint-plugin-jsx-a11y from 6.6.0 to 6.6.1 (#699)
Bumps [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) from 6.6.0 to 6.6.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.6.0...v6.6.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsx-a11y
  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-08-03 19:23:17 +05:30
dependabot[bot] 48f34053ab Bump eslint from 8.20.0 to 8.21.0 (#704)
Bumps [eslint](https://github.com/eslint/eslint) from 8.20.0 to 8.21.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.20.0...v8.21.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-08-03 19:21:10 +05:30
dependabot[bot] abfe263750 Bump @babel/core from 7.18.9 to 7.18.10 (#705)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.18.9 to 7.18.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.18.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-08-03 19:18:22 +05:30
dependabot[bot] 9ba003b16d Bump @babel/preset-env from 7.18.9 to 7.18.10 (#707)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.18.9 to 7.18.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.18.10/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  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-08-03 19:12:15 +05:30
Ajay Bura 4c661647f1 Show global pack selector in settings 2022-08-03 17:31:52 +05:30
Ajay Bura 6e1cf624d7 Add personal emoji in settings 2022-08-03 15:31:53 +05:30
Ajay Bura 11c3d3bb9f Create or delete pack 2022-07-31 17:51:14 +05:30
Ajay Bura f99e79236e Bug fix 2022-07-31 16:21:10 +05:30
Ajay Bura 3f7ba56106 Add checkbox to make pack global 2022-07-31 16:07:17 +05:30
Ajay Bura 458738f06a Change pack avatar, name & attribution 2022-07-31 10:07:25 +05:30
Ajay Bura 15e8412326 Add/delete/rename images to exisitng packs 2022-07-30 20:34:38 +05:30
dependabot[bot] 48793f3a95 Bump docker/build-push-action from 3.0.0 to 3.1.0 (#695)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: docker/build-push-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-07-27 14:04:59 +05:30
dependabot[bot] d8cf98fd64 Bump nginx from 1.23.0-alpine to 1.23.1-alpine (#694)
Bumps nginx from 1.23.0-alpine to 1.23.1-alpine.

---
updated-dependencies:
- dependency-name: nginx
  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-07-27 14:03:41 +05:30
Ajay Bura dbbb6075f3 WIP 2022-07-27 10:09:46 +05:30
Ajay Bura 4d76b593fe Fix some pack not visible in emojiboard 2022-07-26 16:16:09 +05:30
Ajay Bura b2342ecd16 Display emoji and sticker in room settings 2022-07-26 16:10:53 +05:30
Ava PekandMaple 78e12d5bee Add mark as read button to space options (#667)
This allows users to mark all rooms in a space as read, matching similar
features found in other popular chat applications.

We opted to place the mark as read button at the top of the list instead
of next to the add user button like in room options since we felt this
will be the most-used button in the list.

Fixes #645.

Co-authored-by: Maple <mapletree.dv@gmail.com>

Co-authored-by: Maple <mapletree.dv@gmail.com>
2022-07-25 11:41:56 +05:30
dependabot[bot] bdb8bdf76c Bump cross-fetch from 3.1.4 to 3.1.5 (#512)
Bumps [cross-fetch](https://github.com/lquixada/cross-fetch) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/lquixada/cross-fetch/releases)
- [Commits](https://github.com/lquixada/cross-fetch/compare/v3.1.4...v3.1.5)

---
updated-dependencies:
- dependency-name: cross-fetch
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-20 13:18:12 +05:30
dependabot[bot] 88b79eb3a5 Bump @babel/core from 7.18.6 to 7.18.9 (#690)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.18.6 to 7.18.9.
- [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.9/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-07-19 17:56:48 +05:30
dependabot[bot] b6428197ac Bump eslint from 8.19.0 to 8.20.0 (#691)
Bumps [eslint](https://github.com/eslint/eslint) from 8.19.0 to 8.20.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.19.0...v8.20.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-07-19 17:52:17 +05:30
dependabot[bot] a46138c8b9 Bump @babel/preset-env from 7.18.6 to 7.18.9 (#692)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.18.6 to 7.18.9.
- [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.9/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  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-07-19 17:51:13 +05:30
Dean Bassett 1211ca277b Support mark as read by ESC while in room input (#669)
fixes #cinnyapp/cinny/668
2022-07-18 22:06:51 +05:30
James e6f395c643 Add support to play .mov files (#672)
* update allowed mimetypes

* fix .mov files failing to play in Chromium

* add check for  before passing to FileReader

* add missing semi-colon
2022-07-18 22:03:11 +05:30
dependabot[bot] 1979646b4b Bump actions/setup-node from 3.3.0 to 3.4.1 (#687)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.3.0 to 3.4.1.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3.3.0...v3.4.1)

---
updated-dependencies:
- dependency-name: actions/setup-node
  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-07-16 17:11:48 +05:30
Ajay Bura 11334f946f Show global image packs in emoji picker 2022-07-16 15:11:11 +05:30
Ajay Bura 295a5be26d Show custom emoji first in suggestions 2022-07-16 08:14:12 +05:30
Ajay Bura b0cd26d531 Remove comments 2022-07-13 21:31:29 +05:30
Ajay Bura 5c0eb20cb4 Follow system theme by default 2022-07-09 18:08:35 +05:30
Ajay Bura 009966a5c7 Fix wrong power level in room permission 2022-07-09 16:32:42 +05:30
Ajay Bura 4427b3b291 Accept mxid on login (#187) 2022-07-09 13:58:57 +05:30
Ajay Bura 3dda4d6540 Add toggle to show password in auth page (#73) 2022-07-09 10:35:17 +05:30
Ajay Bura c9df0be874 Fix captcha loop issue in registration form (#664) 2022-07-08 21:07:14 +05:30
Krishan ca2627d3cf Bump linkifyjs 2.1.9 to 4.0.0-beta.5 (#665) 2022-07-08 20:29:07 +05:30
Krishan 47e6527b0e Don't enable e2ee from profileViewer for bridge users (#666) 2022-07-08 20:24:35 +05:30
dependabot[bot] 7decbb6eef Bump webpack-dev-server from 4.9.2 to 4.9.3 (#662)
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.9.2 to 4.9.3.
- [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.9.2...v4.9.3)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  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-07-05 10:08:25 +05:30
dependabot[bot] 68da1d0551 Bump eslint from 8.18.0 to 8.19.0 (#663)
Bumps [eslint](https://github.com/eslint/eslint) from 8.18.0 to 8.19.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.18.0...v8.19.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-07-05 10:06:53 +05:30
Chuang Zhu a6f21b6606 Fix parsing encoded matrix.to URL (#660)
From https://spec.matrix.org/v1.3/appendices/#matrixto-navigation:

	The components of the matrix.to URI (<identifier> and <extra parameter>) are to be percent-encoded as per RFC 3986.

	Historically, clients have not produced URIs which are fully encoded. Clients should try to interpret these cases to the best of their ability. For example, an unencoded room alias should still work within the client if possible
2022-07-04 19:50:11 +05:30
ginnyTheCat 06a4e0c93b Add emoji name fallback (#658) 2022-06-29 18:19:43 +05:30
dependabot[bot] 0ca1df24ed Bump @babel/preset-react from 7.17.12 to 7.18.6 (#656)
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.17.12 to 7.18.6.
- [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.6/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-06-28 20:39:44 +05:30
dependabot[bot] 1d12a906d4 Bump @babel/preset-env from 7.18.2 to 7.18.6 (#654)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.18.2 to 7.18.6.
- [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.6/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  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-06-28 20:31:52 +05:30
dependabot[bot] 7bd7518963 Bump @babel/core from 7.18.5 to 7.18.6 (#653)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.18.5 to 7.18.6.
- [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.6/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-06-28 15:54:07 +05:30
dependabot[bot] a9c5765be5 Bump sass from 1.52.3 to 1.53.0 (#655)
Bumps [sass](https://github.com/sass/dart-sass) from 1.52.3 to 1.53.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.52.3...1.53.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-06-28 15:53:23 +05:30
dependabot[bot] 2292f63fb6 Bump eslint-plugin-jsx-a11y from 6.5.1 to 6.6.0 (#652)
Bumps [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) from 6.5.1 to 6.6.0.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/compare/v6.5.1...v6.6.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsx-a11y
  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-06-28 15:52:31 +05:30
dependabot[bot] db92b9f5ff Bump sass-loader from 13.0.0 to 13.0.2 (#651)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 13.0.0 to 13.0.2.
- [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/v13.0.0...v13.0.2)

---
updated-dependencies:
- dependency-name: sass-loader
  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-06-28 15:28:24 +05:30
dependabot[bot] f538639882 Bump eslint-plugin-react from 7.30.0 to 7.30.1 (#650)
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.30.0 to 7.30.1.
- [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.30.0...v7.30.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  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-06-28 15:27:40 +05:30
dependabot[bot] 56bc8c2890 Bump nginx from 1.21.6-alpine to 1.23.0-alpine (#649)
Bumps nginx from 1.21.6-alpine to 1.23.0-alpine.

---
updated-dependencies:
- dependency-name: nginx
  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-06-28 15:27:20 +05:30
dependabot[bot] 1cba4d3fa7 Bump html-loader from 3.1.0 to 3.1.2 (#643)
Bumps [html-loader](https://github.com/webpack-contrib/html-loader) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/webpack-contrib/html-loader/releases)
- [Changelog](https://github.com/webpack-contrib/html-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/html-loader/compare/v3.1.0...v3.1.2)

---
updated-dependencies:
- dependency-name: html-loader
  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-06-21 17:39:36 +05:30
dependabot[bot] 4c7820ceac Bump html-react-parser from 1.4.14 to 2.0.0 (#641)
Bumps [html-react-parser](https://github.com/remarkablemark/html-react-parser) from 1.4.14 to 2.0.0.
- [Release notes](https://github.com/remarkablemark/html-react-parser/releases)
- [Changelog](https://github.com/remarkablemark/html-react-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/remarkablemark/html-react-parser/compare/v1.4.14...v2.0.0)

---
updated-dependencies:
- dependency-name: html-react-parser
  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-06-21 17:33:58 +05:30
dependabot[bot] 118dcd8fa0 Bump eslint-plugin-react-hooks from 4.5.0 to 4.6.0 (#642)
Bumps [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/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-06-21 17:31:58 +05:30
dependabot[bot] a142ade923 Bump mini-css-extract-plugin from 2.6.0 to 2.6.1 (#640)
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.6.0...v2.6.1)

---
updated-dependencies:
- dependency-name: mini-css-extract-plugin
  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-06-21 17:30:23 +05:30
dependabot[bot] 57ab10a87c Bump eslint from 8.17.0 to 8.18.0 (#638)
Bumps [eslint](https://github.com/eslint/eslint) from 8.17.0 to 8.18.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.17.0...v8.18.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-06-21 17:28:44 +05:30
Krishan 8c1c3cd634 Fix mozilla homerserver domain (#199) (#629) 2022-06-15 12:07:14 +05:30
dependabot[bot] 2d3634d6bf Bump jsmrcaga/action-netlify-deploy from 1.7.2 to 1.8.0 (#618)
Bumps [jsmrcaga/action-netlify-deploy](https://github.com/jsmrcaga/action-netlify-deploy) from 1.7.2 to 1.8.0.
- [Release notes](https://github.com/jsmrcaga/action-netlify-deploy/releases)
- [Commits](https://github.com/jsmrcaga/action-netlify-deploy/compare/fb6a5f936a4b06a8f7793e69fc5a022ffe39807a...53de32e559b0b3833615b9788c7a090cd2fddb03)

---
updated-dependencies:
- dependency-name: jsmrcaga/action-netlify-deploy
  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-06-14 20:19:46 +05:30
dependabot[bot] 217f29f068 Bump webpack-cli from 4.9.2 to 4.10.0 (#622)
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 4.9.2 to 4.10.0.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/webpack-cli@4.9.2...webpack-cli@4.10.0)

---
updated-dependencies:
- dependency-name: webpack-cli
  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-06-14 20:18:38 +05:30
dependabot[bot] 58c3eee153 Bump sass from 1.52.2 to 1.52.3 (#623)
Bumps [sass](https://github.com/sass/dart-sass) from 1.52.2 to 1.52.3.
- [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.52.2...1.52.3)

---
updated-dependencies:
- dependency-name: sass
  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-06-14 20:15:14 +05:30
dependabot[bot]andKrishan d9e1fb620b Bump matrix-js-sdk from 18.0.0 to 18.1.0 (#624)
* Bump matrix-js-sdk from 18.0.0 to 18.1.0

Bumps [matrix-js-sdk](https://github.com/matrix-org/matrix-js-sdk) from 18.0.0 to 18.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/v18.0.0...v18.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>

* Replace with stable function

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
2022-06-14 20:12:27 +05:30
dependabot[bot] a3f5b92484 Bump @babel/core from 7.18.2 to 7.18.5 (#625)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.18.2 to 7.18.5.
- [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.5/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-06-14 19:59:42 +05:30
Ajay BuraandKrishan 8b96e0ab98 Fix nodejs version in actions (#627)
* Update prod-deploy.yml

* Update netlify-dev.yml

* Update build-pull-request.yml

* Update build-pull-request.yml

* Update netlify-dev.yml

* Update prod-deploy.yml

Co-authored-by: Krishan <33421343+kfiven@users.noreply.github.com>
2022-06-14 19:55:48 +05:30
dependabot[bot] eef2d451b7 Bump webpack-dev-server from 4.9.1 to 4.9.2 (#617)
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.9.1 to 4.9.2.
- [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.9.1...v4.9.2)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  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-06-07 15:09:32 +05:30
dependabot[bot] 371e66a6df Bump @fontsource/inter from 4.5.10 to 4.5.11 (#619)
Bumps [@fontsource/inter](https://github.com/fontsource/fontsource/tree/HEAD/fonts/google/inter) from 4.5.10 to 4.5.11.
- [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-06-07 15:08:55 +05:30
dependabot[bot] 0d12144744 Bump matrix-js-sdk from 17.2.0 to 18.0.0 (#591)
Bumps [matrix-js-sdk](https://github.com/matrix-org/matrix-js-sdk) from 17.2.0 to 18.0.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.2.0...v18.0.0)

---
updated-dependencies:
- dependency-name: matrix-js-sdk
  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-06-05 10:32:15 +05:30
dependabot[bot] ba39724813 Bump @babel/preset-env from 7.18.0 to 7.18.2 (#594)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.18.0 to 7.18.2.
- [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.2/packages/babel-preset-env)

---
updated-dependencies:
- dependency-name: "@babel/preset-env"
  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-06-05 10:14:37 +05:30
dependabot[bot] af6e6bfc67 Bump @babel/core from 7.18.0 to 7.18.2 (#592)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.18.0 to 7.18.2.
- [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.2/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-06-05 09:53:30 +05:30
dependabot[bot] 315b5a1048 Bump webpack from 5.72.1 to 5.73.0 (#601)
Bumps [webpack](https://github.com/webpack/webpack) from 5.72.1 to 5.73.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.72.1...v5.73.0)

---
updated-dependencies:
- dependency-name: webpack
  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-06-05 09:52:17 +05:30
dependabot[bot] c410d4e9f5 Bump eslint from 8.16.0 to 8.17.0 (#602)
Bumps [eslint](https://github.com/eslint/eslint) from 8.16.0 to 8.17.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.16.0...v8.17.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-06-05 09:51:10 +05:30
dependabot[bot] 299d976622 Bump webpack-dev-server from 4.9.0 to 4.9.1 (#600)
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.9.0 to 4.9.1.
- [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.9.0...v4.9.1)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  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-06-04 21:33:39 +05:30
dependabot[bot] e8587f99c9 Bump sass from 1.52.1 to 1.52.2 (#599)
Bumps [sass](https://github.com/sass/dart-sass) from 1.52.1 to 1.52.2.
- [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.52.1...1.52.2)

---
updated-dependencies:
- dependency-name: sass
  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-06-04 21:32:06 +05:30
dependabot[bot] 63ab96b71b Bump html-react-parser from 1.4.12 to 1.4.14 (#598)
Bumps [html-react-parser](https://github.com/remarkablemark/html-react-parser) from 1.4.12 to 1.4.14.
- [Release notes](https://github.com/remarkablemark/html-react-parser/releases)
- [Changelog](https://github.com/remarkablemark/html-react-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/remarkablemark/html-react-parser/compare/v1.4.12...v1.4.14)

---
updated-dependencies:
- dependency-name: html-react-parser
  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-06-04 21:30:58 +05:30
dependabot[bot] e998438135 Bump docker/setup-buildx-action from 1.6.0 to 2.0.0 (#595)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1.6.0 to 2.0.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v1.6.0...v2.0.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-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-06-04 21:28:18 +05:30
dependabot[bot] 5fd7d64d21 Bump docker/setup-qemu-action from 1.2.0 to 2.0.0 (#596)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 1.2.0 to 2.0.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v1.2.0...v2.0.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-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-06-04 21:24:43 +05:30
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
64 changed files with 3931 additions and 1811 deletions

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
<!-- Please read https://github.com/ajbura/cinny/CONTRIBUTING.md before submitting your pull request -->
<!-- Please read https://github.com/ajbura/cinny/blob/dev/CONTRIBUTING.md before submitting your pull request -->
### Description
<!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. -->
+4
View File
@@ -12,6 +12,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.4.1
with:
node-version: 17.9.0
- name: Build app
run: npm ci && npm run build
- name: Upload artifact
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Build Docker image
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: .
push: false
+5 -1
View File
@@ -14,8 +14,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.4.1
with:
node-version: 17.9.0
- name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@fb6a5f936a4b06a8f7793e69fc5a022ffe39807a
uses: jsmrcaga/action-netlify-deploy@53de32e559b0b3833615b9788c7a090cd2fddb03
with:
install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
+15 -2
View File
@@ -11,6 +11,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.4.1
with:
node-version: 17.9.0
- name: Build
run: |
npm ci
@@ -45,8 +49,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.4.1
with:
node-version: 17.9.0
- name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@fb6a5f936a4b06a8f7793e69fc5a022ffe39807a
uses: jsmrcaga/action-netlify-deploy@53de32e559b0b3833615b9788c7a090cd2fddb03
with:
install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
@@ -63,6 +71,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
uses: docker/login-action@v2.0.0
with:
@@ -74,9 +86,10 @@ jobs:
with:
images: ajbura/cinny
- name: Build and push Docker image
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+1 -1
View File
@@ -10,7 +10,7 @@ RUN npm run build
## App
FROM nginx:1.21.6-alpine
FROM nginx:1.23.1-alpine
COPY --from=builder /src/dist /app
+3 -4
View File
@@ -1,12 +1,11 @@
{
"defaultHomeserver": 4,
"defaultHomeserver": 3,
"homeserverList": [
"converser.eu",
"envs.net",
"halogen.city",
"kde.org",
"matrix.org",
"chat.mozilla.org"
"mozilla.org"
],
"allowCustomHomeservers": true
}
}
+1906 -1455
View File
File diff suppressed because it is too large. Load diff
+22 -21
View File
@@ -1,6 +1,6 @@
{
"name": "cinny",
"version": "2.0.3",
"version": "2.0.4",
"description": "Yet another matrix client",
"main": "index.js",
"engines": {
@@ -15,7 +15,7 @@
"author": "Ajay Bura",
"license": "MIT",
"dependencies": {
"@fontsource/inter": "^4.5.10",
"@fontsource/inter": "^4.5.11",
"@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",
"@tippyjs/react": "^4.2.6",
@@ -26,10 +26,11 @@
"file-saver": "^2.0.5",
"flux": "^4.0.3",
"formik": "^2.2.9",
"html-react-parser": "^1.4.12",
"katex": "^0.15.6",
"linkifyjs": "^2.1.9",
"matrix-js-sdk": "^17.2.0",
"html-react-parser": "^3.0.1",
"katex": "^0.16.0",
"linkify-html": "^4.0.0-beta.5",
"linkifyjs": "^4.0.0-beta.5",
"matrix-js-sdk": "^18.1.0",
"micromark": "^3.0.10",
"micromark-extension-gfm": "^2.0.1",
"micromark-extension-math": "^2.0.2",
@@ -44,14 +45,14 @@
"react-dom": "^17.0.2",
"react-google-recaptcha": "^2.1.0",
"react-modal": "^3.15.1",
"sanitize-html": "^2.7.0",
"sanitize-html": "^2.7.1",
"tippy.js": "^6.3.7",
"twemoji": "^14.0.2"
},
"devDependencies": {
"@babel/core": "^7.18.0",
"@babel/preset-env": "^7.18.0",
"@babel/preset-react": "^7.17.12",
"@babel/core": "^7.18.10",
"@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"assert": "^2.0.0",
"babel-loader": "^8.2.5",
"browserify-fs": "^1.0.0",
@@ -61,27 +62,27 @@
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"eslint": "^8.16.0",
"eslint": "^8.21.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.5.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2",
"html-loader": "^3.1.0",
"html-loader": "^4.1.0",
"html-webpack-plugin": "^5.3.1",
"mini-css-extract-plugin": "^2.6.0",
"mini-css-extract-plugin": "^2.6.1",
"path-browserify": "^1.0.1",
"sass": "^1.52.1",
"sass-loader": "^13.0.0",
"sass": "^1.54.1",
"sass-loader": "^13.0.2",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"url": "^0.11.0",
"util": "^0.12.4",
"webpack": "^5.72.1",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3",
"webpack-merge": "^5.7.3"
}
}
+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.92896 3.51471L3.51474 4.92892L5.97515 7.38933C4.46742 8.5776 3.32116 9.93994 2.7 10.8C2.1 11.5 2.1 12.5 2.7 13.2C4 15 7.6 19 12 19C13.5709 19 15.0398 18.4902 16.3384 17.7526L19.0711 20.4853L20.4853 19.0711L4.92896 3.51471ZM4.2 12C4.68291 11.3561 5.85678 9.9637 7.39721 8.81139L9.29238 10.7066C9.10496 11.0982 9 11.5368 9 12C9 13.6569 10.3431 15 12 15C12.4632 15 12.9018 14.895 13.2934 14.7076L14.8573 16.2715C13.9566 16.7128 12.9896 17 12 17C8.4 17 5.1 13.2 4.2 12Z" fill="black"/>
<path d="M9.6226 5.37995L11.2906 7.04797C11.5254 7.01661 11.762 7 12 7C15.6 7 18.9 10.8 19.8 12C19.493 12.4094 18.9066 13.1213 18.1244 13.8817L19.5194 15.2768C20.2973 14.4974 20.9049 13.7471 21.3 13.2C21.9 12.5 21.9 11.5 21.3 10.8C20 9 16.4 5 12 5C11.1762 5 10.3805 5.14021 9.6226 5.37995Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 943 B

+3 -12
View File
@@ -1,13 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
<g>
<g>
<path d="M12,19c-4.4,0-8-4-9.3-5.8c-0.6-0.7-0.6-1.7,0-2.4C4,9,7.6,5,12,5s8,4,9.3,5.8c0.6,0.7,0.6,1.7,0,2.4C20,15,16.4,19,12,19
z M12,7c-3.6,0-6.9,3.8-7.8,5c0.9,1.2,4.2,5,7.8,5s6.9-3.8,7.8-5C18.9,10.8,15.6,7,12,7z"/>
</g>
<circle cx="12" cy="12" r="3"/>
</g>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 19C7.6 19 4 15 2.7 13.2C2.1 12.5 2.1 11.5 2.7 10.8C4 9 7.6 5 12 5C16.4 5 20 9 21.3 10.8C21.9 11.5 21.9 12.5 21.3 13.2C20 15 16.4 19 12 19ZM12 7C8.4 7 5.1 10.8 4.2 12C5.1 13.2 8.4 17 12 17C15.6 17 18.9 13.2 19.8 12C18.9 10.8 15.6 7 12 7Z" fill="black"/>
<path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 718 B

After

Width:  |  Height:  |  Size: 508 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 3L21 8V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V5C3 3.89543 3.89543 3 5 3H16ZM19 9H17C15.8954 9 15 8.10457 15 7V5H5V19H19V9Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 12C9 13.6569 10.3431 15 12 15C13.6569 15 15 13.6569 15 12H17C17 14.7614 14.7614 17 12 17C9.23858 17 7 14.7614 7 12H9Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 501 B

+4 -4
View File
@@ -26,10 +26,10 @@
&--icon {
@include dir.side(padding, var(--sp-tight), var(--sp-loose));
.ic-raw {
@include dir.side(margin, 0, var(--sp-extra-tight));
flex-shrink: 0;
}
}
.ic-raw {
@include dir.side(margin, 0, var(--sp-extra-tight));
flex-shrink: 0;
}
}
-1
View File
@@ -5,7 +5,6 @@ import katex from 'katex';
import 'katex/dist/katex.min.css';
import 'katex/dist/contrib/copy-tex';
import 'katex/dist/contrib/copy-tex.css';
const Math = React.memo(({
content, throwOnError, errorColor, displayMode,
+469
View File
@@ -0,0 +1,469 @@
import React, {
useState, useMemo, useReducer, useEffect,
} from 'react';
import PropTypes from 'prop-types';
import './ImagePack.scss';
import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation';
import { suffixRename } from '../../../util/common';
import Button from '../../atoms/button/Button';
import Text from '../../atoms/text/Text';
import Input from '../../atoms/input/Input';
import Checkbox from '../../atoms/button/Checkbox';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import { ImagePack as ImagePackBuilder } from '../../organisms/emoji-board/custom-emoji';
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
import ImagePackProfile from './ImagePackProfile';
import ImagePackItem from './ImagePackItem';
import ImagePackUpload from './ImagePackUpload';
const renameImagePackItem = (shortcode) => new Promise((resolve) => {
let isCompleted = false;
openReusableDialog(
<Text variant="s1" weight="medium">Rename</Text>,
(requestClose) => (
<div style={{ padding: 'var(--sp-normal)' }}>
<form
onSubmit={(e) => {
e.preventDefault();
const sc = e.target.shortcode.value;
if (sc.trim() === '') return;
isCompleted = true;
resolve(sc.trim());
requestClose();
}}
>
<Input
value={shortcode}
name="shortcode"
label="Shortcode"
autoFocus
required
/>
<div style={{ height: 'var(--sp-normal)' }} />
<Button variant="primary" type="submit">Rename</Button>
</form>
</div>
),
() => {
if (!isCompleted) resolve(null);
},
);
});
function getUsage(usage) {
if (usage.includes('emoticon') && usage.includes('sticker')) return 'both';
if (usage.includes('emoticon')) return 'emoticon';
if (usage.includes('sticker')) return 'sticker';
return 'both';
}
function isGlobalPack(roomId, stateKey) {
const mx = initMatrix.matrixClient;
const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent();
if (typeof globalContent !== 'object') return false;
const { rooms } = globalContent;
if (typeof rooms !== 'object') return false;
return rooms[roomId]?.[stateKey] !== undefined;
}
function useRoomImagePack(roomId, stateKey) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
const pack = useMemo(() => (
ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent())
), [room, stateKey]);
const sendPackContent = (content) => {
mx.sendStateEvent(roomId, 'im.ponies.room_emotes', content, stateKey);
};
return {
pack,
sendPackContent,
};
}
function useUserImagePack() {
const mx = initMatrix.matrixClient;
const packEvent = mx.getAccountData('im.ponies.user_emotes');
const pack = useMemo(() => (
ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? {
pack: { display_name: 'Personal' },
images: {},
})
), []);
const sendPackContent = (content) => {
mx.setAccountData('im.ponies.user_emotes', content);
};
return {
pack,
sendPackContent,
};
}
function useImagePackHandles(pack, sendPackContent) {
const [, forceUpdate] = useReducer((count) => count + 1, 0);
const getNewKey = (key) => {
if (typeof key !== 'string') return undefined;
let newKey = key?.replace(/\s/g, '-');
if (pack.getImages().get(newKey)) {
newKey = suffixRename(
newKey,
(suffixedKey) => pack.getImages().get(suffixedKey),
);
}
return newKey;
};
const handleAvatarChange = (url) => {
pack.setAvatarUrl(url);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleEditProfile = (name, attribution) => {
pack.setDisplayName(name);
pack.setAttribution(attribution);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleUsageChange = (newUsage) => {
const usage = [];
if (newUsage === 'emoticon' || newUsage === 'both') usage.push('emoticon');
if (newUsage === 'sticker' || newUsage === 'both') usage.push('sticker');
pack.setUsage(usage);
pack.getImages().forEach((img) => pack.setImageUsage(img.shortcode, undefined));
sendPackContent(pack.getContent());
forceUpdate();
};
const handleRenameItem = async (key) => {
const newKey = getNewKey(await renameImagePackItem(key));
if (!newKey || newKey === key) return;
pack.updateImageKey(key, newKey);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleDeleteItem = async (key) => {
const isConfirmed = await confirmDialog(
'Delete',
`Are you sure that you want to delete "${key}"?`,
'Delete',
'danger',
);
if (!isConfirmed) return;
pack.removeImage(key);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleUsageItem = (key, newUsage) => {
const usage = [];
if (newUsage === 'emoticon' || newUsage === 'both') usage.push('emoticon');
if (newUsage === 'sticker' || newUsage === 'both') usage.push('sticker');
pack.setImageUsage(key, usage);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleAddItem = (key, url) => {
const newKey = getNewKey(key);
if (!newKey || !url) return;
pack.addImage(newKey, {
url,
});
sendPackContent(pack.getContent());
forceUpdate();
};
return {
handleAvatarChange,
handleEditProfile,
handleUsageChange,
handleRenameItem,
handleDeleteItem,
handleUsageItem,
handleAddItem,
};
}
function addGlobalImagePack(mx, roomId, stateKey) {
const content = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? {};
if (!content.rooms) content.rooms = {};
if (!content.rooms[roomId]) content.rooms[roomId] = {};
content.rooms[roomId][stateKey] = {};
return mx.setAccountData('im.ponies.emote_rooms', content);
}
function removeGlobalImagePack(mx, roomId, stateKey) {
const content = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? {};
if (!content.rooms) return Promise.resolve();
if (!content.rooms[roomId]) return Promise.resolve();
delete content.rooms[roomId][stateKey];
if (Object.keys(content.rooms[roomId]).length === 0) {
delete content.rooms[roomId];
}
return mx.setAccountData('im.ponies.emote_rooms', content);
}
function ImagePack({ roomId, stateKey, handlePackDelete }) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const [viewMore, setViewMore] = useState(false);
const [isGlobal, setIsGlobal] = useState(isGlobalPack(roomId, stateKey));
const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey);
const {
handleAvatarChange,
handleEditProfile,
handleUsageChange,
handleRenameItem,
handleDeleteItem,
handleUsageItem,
handleAddItem,
} = useImagePackHandles(pack, sendPackContent);
const handleGlobalChange = (isG) => {
setIsGlobal(isG);
if (isG) addGlobalImagePack(mx, roomId, stateKey);
else removeGlobalImagePack(mx, roomId, stateKey);
};
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0;
const canChange = room.currentState.hasSufficientPowerLevelFor('state_default', myPowerlevel);
const handleDeletePack = async () => {
const isConfirmed = await confirmDialog(
'Delete Pack',
`Are you sure that you want to delete "${pack.displayName}"?`,
'Delete',
'danger',
);
if (!isConfirmed) return;
handlePackDelete(stateKey);
};
const images = [...pack.images].slice(0, viewMore ? pack.images.size : 2);
return (
<div className="image-pack">
<ImagePackProfile
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
displayName={pack.displayName ?? 'Unknown'}
attribution={pack.attribution}
usage={getUsage(pack.usage)}
onUsageChange={canChange ? handleUsageChange : null}
onAvatarChange={canChange ? handleAvatarChange : null}
onEditProfile={canChange ? handleEditProfile : null}
/>
{ canChange && (
<ImagePackUpload onUpload={handleAddItem} />
)}
{ images.length === 0 ? null : (
<div>
<div className="image-pack__header">
<Text variant="b3">Image</Text>
<Text variant="b3">Shortcode</Text>
<Text variant="b3">Usage</Text>
</div>
{images.map(([shortcode, image]) => (
<ImagePackItem
key={shortcode}
url={mx.mxcUrlToHttp(image.mxc)}
shortcode={shortcode}
usage={getUsage(image.usage)}
onUsageChange={canChange ? handleUsageItem : undefined}
onDelete={canChange ? handleDeleteItem : undefined}
onRename={canChange ? handleRenameItem : undefined}
/>
))}
</div>
)}
{(pack.images.size > 2 || handlePackDelete) && (
<div className="image-pack__footer">
{pack.images.size > 2 && (
<Button onClick={() => setViewMore(!viewMore)}>
{
viewMore
? 'View less'
: `View ${pack.images.size - 2} more`
}
</Button>
)}
{ handlePackDelete && <Button variant="danger" onClick={handleDeletePack}>Delete Pack</Button>}
</div>
)}
<div className="image-pack__global">
<Checkbox variant="positive" onToggle={handleGlobalChange} isActive={isGlobal} />
<div>
<Text variant="b2">Use globally</Text>
<Text variant="b3">Add this pack to your account to use in all rooms.</Text>
</div>
</div>
</div>
);
}
ImagePack.defaultProps = {
handlePackDelete: null,
};
ImagePack.propTypes = {
roomId: PropTypes.string.isRequired,
stateKey: PropTypes.string.isRequired,
handlePackDelete: PropTypes.func,
};
function ImagePackUser() {
const mx = initMatrix.matrixClient;
const [viewMore, setViewMore] = useState(false);
const { pack, sendPackContent } = useUserImagePack();
const {
handleAvatarChange,
handleEditProfile,
handleUsageChange,
handleRenameItem,
handleDeleteItem,
handleUsageItem,
handleAddItem,
} = useImagePackHandles(pack, sendPackContent);
const images = [...pack.images].slice(0, viewMore ? pack.images.size : 2);
return (
<div className="image-pack">
<ImagePackProfile
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
displayName={pack.displayName ?? 'Personal'}
attribution={pack.attribution}
usage={getUsage(pack.usage)}
onUsageChange={handleUsageChange}
onAvatarChange={handleAvatarChange}
onEditProfile={handleEditProfile}
/>
<ImagePackUpload onUpload={handleAddItem} />
{ images.length === 0 ? null : (
<div>
<div className="image-pack__header">
<Text variant="b3">Image</Text>
<Text variant="b3">Shortcode</Text>
<Text variant="b3">Usage</Text>
</div>
{images.map(([shortcode, image]) => (
<ImagePackItem
key={shortcode}
url={mx.mxcUrlToHttp(image.mxc)}
shortcode={shortcode}
usage={getUsage(image.usage)}
onUsageChange={handleUsageItem}
onDelete={handleDeleteItem}
onRename={handleRenameItem}
/>
))}
</div>
)}
{(pack.images.size > 2) && (
<div className="image-pack__footer">
<Button onClick={() => setViewMore(!viewMore)}>
{
viewMore
? 'View less'
: `View ${pack.images.size - 2} more`
}
</Button>
</div>
)}
</div>
);
}
function useGlobalImagePack() {
const [, forceUpdate] = useReducer((count) => count + 1, 0);
const mx = initMatrix.matrixClient;
const roomIdToStateKeys = new Map();
const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? { rooms: {} };
const { rooms } = globalContent;
Object.keys(rooms).forEach((roomId) => {
if (typeof rooms[roomId] !== 'object') return;
const room = mx.getRoom(roomId);
const stateKeys = Object.keys(rooms[roomId]);
if (!room || stateKeys.length === 0) return;
roomIdToStateKeys.set(roomId, stateKeys);
});
useEffect(() => {
const handleEvent = (event) => {
if (event.getType() === 'im.ponies.emote_rooms') forceUpdate();
};
mx.addListener('accountData', handleEvent);
return () => {
mx.removeListener('accountData', handleEvent);
};
}, []);
return roomIdToStateKeys;
}
function ImagePackGlobal() {
const mx = initMatrix.matrixClient;
const roomIdToStateKeys = useGlobalImagePack();
const handleChange = (roomId, stateKey) => {
removeGlobalImagePack(mx, roomId, stateKey);
};
return (
<div className="image-pack-global">
<MenuHeader>Global packs</MenuHeader>
<div>
{
roomIdToStateKeys.size > 0
? [...roomIdToStateKeys].map(([roomId, stateKeys]) => {
const room = mx.getRoom(roomId);
return (
stateKeys.map((stateKey) => {
const data = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
const pack = ImagePackBuilder.parsePack(data?.getId(), data?.getContent());
if (!pack) return null;
return (
<div className="image-pack__global" key={pack.id}>
<Checkbox variant="positive" onToggle={() => handleChange(roomId, stateKey)} isActive />
<div>
<Text variant="b2">{pack.displayName ?? 'Unknown'}</Text>
<Text variant="b3">{room.name}</Text>
</div>
</div>
);
})
);
})
: <div className="image-pack-global__empty"><Text>No global packs</Text></div>
}
</div>
</div>
);
}
export default ImagePack;
export { ImagePackUser, ImagePackGlobal };
@@ -0,0 +1,47 @@
@use '../../partials/flex';
.image-pack {
&-item {
border-top: 1px solid var(--bg-surface-border);
}
&__header {
padding: var(--sp-extra-tight) var(--sp-normal);
display: flex;
align-items: center;
gap: var(--sp-normal);
& > *:nth-child(2) {
@extend .cp-fx__item-one;
}
}
&__footer {
padding: var(--sp-normal);
display: flex;
justify-content: space-between;
gap: var(--sp-tight);
}
&__global {
padding: var(--sp-normal);
padding-top: var(--sp-tight);
display: flex;
align-items: center;
gap: var(--sp-normal);
}
}
.image-pack-global {
&__empty {
text-align: center;
padding: var(--sp-extra-loose) var(--sp-normal);
}
& .image-pack__global {
padding: 0 var(--sp-normal);
padding-bottom: var(--sp-normal);
&:first-child {
padding-top: var(--sp-normal);
}
}
}
@@ -0,0 +1,76 @@
import React from 'react';
import PropTypes from 'prop-types';
import './ImagePackItem.scss';
import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
import Avatar from '../../atoms/avatar/Avatar';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
import RawIcon from '../../atoms/system-icons/RawIcon';
import IconButton from '../../atoms/button/IconButton';
import ImagePackUsageSelector from './ImagePackUsageSelector';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
function ImagePackItem({
url, shortcode, usage, onUsageChange, onDelete, onRename,
}) {
const handleUsageSelect = (event) => {
openReusableContextMenu(
'bottom',
getEventCords(event, '.btn-surface'),
(closeMenu) => (
<ImagePackUsageSelector
usage={usage}
onSelect={(newUsage) => {
onUsageChange(shortcode, newUsage);
closeMenu();
}}
/>
),
);
};
return (
<div className="image-pack-item">
<Avatar imageSrc={url} size="extra-small" text={shortcode} bgColor="black" />
<div className="image-pack-item__content">
<Text>{shortcode}</Text>
</div>
<div className="image-pack-item__usage">
<div className="image-pack-item__btn">
{onRename && <IconButton tooltip="Rename" size="extra-small" src={PencilIC} onClick={() => onRename(shortcode)} />}
{onDelete && <IconButton tooltip="Delete" size="extra-small" src={BinIC} onClick={() => onDelete(shortcode)} />}
</div>
<Button onClick={onUsageChange ? handleUsageSelect : undefined}>
{onUsageChange && <RawIcon src={ChevronBottomIC} size="extra-small" />}
<Text variant="b2">
{usage === 'emoticon' && 'Emoji'}
{usage === 'sticker' && 'Sticker'}
{usage === 'both' && 'Both'}
</Text>
</Button>
</div>
</div>
);
}
ImagePackItem.defaultProps = {
onUsageChange: null,
onDelete: null,
onRename: null,
};
ImagePackItem.propTypes = {
url: PropTypes.string.isRequired,
shortcode: PropTypes.string.isRequired,
usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired,
onUsageChange: PropTypes.func,
onDelete: PropTypes.func,
onRename: PropTypes.func,
};
export default ImagePackItem;
@@ -0,0 +1,43 @@
@use '../../partials/flex';
@use '../../partials/dir';
.image-pack-item {
margin: 0 var(--sp-normal);
padding: var(--sp-tight) 0;
display: flex;
align-items: center;
gap: var(--sp-normal);
& .avatar-container img {
object-fit: contain;
border-radius: 0;
}
&__content {
@extend .cp-fx__item-one;
}
&__usage {
display: flex;
gap: var(--sp-ultra-tight);
& button {
padding: 6px;
}
& > button.btn-surface {
padding: 6px var(--sp-tight);
min-width: 0;
@include dir.side(margin, var(--sp-ultra-tight), 0);
}
}
&__btn {
display: none;
}
&:hover,
&:focus-within {
.image-pack-item__btn {
display: flex;
gap: var(--sp-ultra-tight);
}
}
}
@@ -0,0 +1,125 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './ImagePackProfile.scss';
import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
import Text from '../../atoms/text/Text';
import Avatar from '../../atoms/avatar/Avatar';
import Button from '../../atoms/button/Button';
import IconButton from '../../atoms/button/IconButton';
import Input from '../../atoms/input/Input';
import ImageUpload from '../image-upload/ImageUpload';
import ImagePackUsageSelector from './ImagePackUsageSelector';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
function ImagePackProfile({
avatarUrl, displayName, attribution, usage,
onUsageChange, onAvatarChange, onEditProfile,
}) {
const [isEdit, setIsEdit] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
const { nameInput, attributionInput } = e.target;
const name = nameInput.value.trim() || undefined;
const att = attributionInput.value.trim() || undefined;
onEditProfile(name, att);
setIsEdit(false);
};
const handleUsageSelect = (event) => {
openReusableContextMenu(
'bottom',
getEventCords(event, '.btn-surface'),
(closeMenu) => (
<ImagePackUsageSelector
usage={usage}
onSelect={(newUsage) => {
onUsageChange(newUsage);
closeMenu();
}}
/>
),
);
};
return (
<div className="image-pack-profile">
{
onAvatarChange
? (
<ImageUpload
bgColor="#555"
text={displayName}
imageSrc={avatarUrl}
size="normal"
onUpload={onAvatarChange}
onRequestRemove={() => onAvatarChange(undefined)}
/>
)
: <Avatar bgColor="#555" text={displayName} imageSrc={avatarUrl} size="normal" />
}
<div className="image-pack-profile__content">
{
isEdit
? (
<form onSubmit={handleSubmit}>
<Input name="nameInput" label="Name" value={displayName} required />
<Input name="attributionInput" label="Attribution" value={attribution} resizable />
<div>
<Button variant="primary" type="submit">Save</Button>
<Button onClick={() => setIsEdit(false)}>Cancel</Button>
</div>
</form>
) : (
<>
<div>
<Text>{displayName}</Text>
{onEditProfile && <IconButton size="extra-small" onClick={() => setIsEdit(true)} src={PencilIC} tooltip="Edit" />}
</div>
{attribution && <Text variant="b3">{attribution}</Text>}
</>
)
}
</div>
<div className="image-pack-profile__usage">
<Text variant="b3">Pack usage</Text>
<Button
onClick={onUsageChange ? handleUsageSelect : undefined}
iconSrc={onUsageChange ? ChevronBottomIC : null}
>
<Text>
{usage === 'emoticon' && 'Emoji'}
{usage === 'sticker' && 'Sticker'}
{usage === 'both' && 'Both'}
</Text>
</Button>
</div>
</div>
);
}
ImagePackProfile.defaultProps = {
avatarUrl: null,
attribution: null,
onUsageChange: null,
onAvatarChange: null,
onEditProfile: null,
};
ImagePackProfile.propTypes = {
avatarUrl: PropTypes.string,
displayName: PropTypes.string.isRequired,
attribution: PropTypes.string,
usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired,
onUsageChange: PropTypes.func,
onAvatarChange: PropTypes.func,
onEditProfile: PropTypes.func,
};
export default ImagePackProfile;
@@ -0,0 +1,37 @@
@use '../../partials/flex';
.image-pack-profile {
padding: var(--sp-normal);
display: flex;
align-items: flex-start;
gap: var(--sp-tight);
&__content {
@extend .cp-fx__item-one;
& > div:first-child {
display: flex;
align-items: center;
gap: var(--sp-extra-tight);
& .ic-btn {
padding: var(--sp-ultra-tight);
}
}
& > form {
display: flex;
flex-direction: column;
gap: var(--sp-extra-tight);
& > div:last-child {
margin: var(--sp-extra-tight) 0;
display: flex;
gap: var(--sp-tight);
}
}
}
&__usage {
& > *:first-child {
margin-bottom: var(--sp-ultra-tight);
}
}
}
@@ -0,0 +1,73 @@
import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types';
import './ImagePackUpload.scss';
import initMatrix from '../../../client/initMatrix';
import { scaleDownImage } from '../../../util/common';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
import Input from '../../atoms/input/Input';
import IconButton from '../../atoms/button/IconButton';
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
function ImagePackUpload({ onUpload }) {
const mx = initMatrix.matrixClient;
const inputRef = useRef(null);
const shortcodeRef = useRef(null);
const [imgFile, setImgFile] = useState(null);
const [progress, setProgress] = useState(false);
const handleSubmit = async (evt) => {
evt.preventDefault();
if (!imgFile) return;
const { shortcodeInput } = evt.target;
const shortcode = shortcodeInput.value.trim();
if (shortcode === '') return;
setProgress(true);
const image = await scaleDownImage(imgFile, 512, 512);
const url = await mx.uploadContent(image, {
onlyContentUri: true,
});
onUpload(shortcode, url);
setProgress(false);
setImgFile(null);
shortcodeRef.current.value = '';
};
const handleFileChange = (evt) => {
const img = evt.target.files[0];
if (!img) return;
setImgFile(img);
shortcodeRef.current.focus();
};
const handleRemove = () => {
setImgFile(null);
inputRef.current.value = null;
};
return (
<form onSubmit={handleSubmit} className="image-pack-upload">
<input ref={inputRef} onChange={handleFileChange} style={{ display: 'none' }} type="file" accept=".png, .gif, .webp" required />
{
imgFile
? (
<div className="image-pack-upload__file">
<IconButton onClick={handleRemove} src={CirclePlusIC} tooltip="Remove file" />
<Text>{imgFile.name}</Text>
</div>
)
: <Button onClick={() => inputRef.current.click()}>Import image</Button>
}
<Input forwardRef={shortcodeRef} name="shortcodeInput" placeholder="shortcode" required />
<Button disabled={progress} variant="primary" type="submit">{progress ? 'Uploading...' : 'Upload'}</Button>
</form>
);
}
ImagePackUpload.propTypes = {
onUpload: PropTypes.func.isRequired,
};
export default ImagePackUpload;
@@ -0,0 +1,43 @@
@use '../../partials/dir';
@use '../../partials/text';
.image-pack-upload {
padding: var(--sp-normal);
padding-top: 0;
display: flex;
gap: var(--sp-tight);
& > .input-container {
flex-grow: 1;
input {
padding: 9px var(--sp-normal);
}
}
&__file {
display: inline-flex;
align-items: center;
background: var(--bg-surface-low);
border-radius: var(--bo-radius);
box-shadow: var(--bs-surface-border);
& button {
--parent-height: 40px;
width: var(--parent-height);
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
& .ic-raw {
background-color: var(--bg-caution);
transform: rotate(45deg);
}
& .text {
@extend .cp-txt__ellipsis;
@include dir.side(margin, var(--sp-ultra-tight), var(--sp-normal));
max-width: 86px;
}
}
}
@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
import CheckIC from '../../../../public/res/ic/outlined/check.svg';
function ImagePackUsageSelector({ usage, onSelect }) {
return (
<div>
<MenuHeader>Usage</MenuHeader>
<MenuItem
iconSrc={usage === 'emoticon' ? CheckIC : undefined}
variant={usage === 'emoticon' ? 'positive' : 'surface'}
onClick={() => onSelect('emoticon')}
>
Emoji
</MenuItem>
<MenuItem
iconSrc={usage === 'sticker' ? CheckIC : undefined}
variant={usage === 'sticker' ? 'positive' : 'surface'}
onClick={() => onSelect('sticker')}
>
Sticker
</MenuItem>
<MenuItem
iconSrc={usage === 'both' ? CheckIC : undefined}
variant={usage === 'both' ? 'positive' : 'surface'}
onClick={() => onSelect('both')}
>
Both
</MenuItem>
</div>
);
}
ImagePackUsageSelector.propTypes = {
usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired,
onSelect: PropTypes.func.isRequired,
};
export default ImagePackUsageSelector;
+12 -2
View File
@@ -7,9 +7,13 @@ import initMatrix from '../../../client/initMatrix';
import Text from '../../atoms/text/Text';
import Avatar from '../../atoms/avatar/Avatar';
import Spinner from '../../atoms/spinner/Spinner';
import RawIcon from '../../atoms/system-icons/RawIcon';
import PlusIC from '../../../../public/res/ic/outlined/plus.svg';
function ImageUpload({
text, bgColor, imageSrc, onUpload, onRequestRemove,
size,
}) {
const [uploadPromise, setUploadPromise] = useState(null);
const uploadImageRef = useRef(null);
@@ -50,10 +54,14 @@ function ImageUpload({
imageSrc={imageSrc}
text={text}
bgColor={bgColor}
size="large"
size={size}
/>
<div className={`img-upload__process ${uploadPromise === null ? ' img-upload__process--stopped' : ''}`}>
{uploadPromise === null && <Text variant="b3" weight="bold">Upload</Text>}
{uploadPromise === null && (
size === 'large'
? <Text variant="b3" weight="bold">Upload</Text>
: <RawIcon src={PlusIC} color="white" />
)}
{uploadPromise !== null && <Spinner size="small" />}
</div>
</button>
@@ -75,6 +83,7 @@ ImageUpload.defaultProps = {
text: null,
bgColor: 'transparent',
imageSrc: null,
size: 'large',
};
ImageUpload.propTypes = {
@@ -83,6 +92,7 @@ ImageUpload.propTypes = {
imageSrc: PropTypes.string,
onUpload: PropTypes.func.isRequired,
onRequestRemove: PropTypes.func.isRequired,
size: PropTypes.oneOf(['large', 'normal']),
};
export default ImageUpload;
+51 -5
View File
@@ -12,15 +12,19 @@ import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
import PlaySVG from '../../../../public/res/ic/outlined/play.svg';
// https://github.com/matrix-org/matrix-react-sdk/blob/a9e28db33058d1893d964ec96cd247ecc3d92fc3/src/utils/blobs.ts#L73
// https://github.com/matrix-org/matrix-react-sdk/blob/cd15e08fc285da42134817cce50de8011809cd53/src/utils/blobs.ts#L73
const ALLOWED_BLOB_MIMETYPES = [
'image/jpeg',
'image/gif',
'image/png',
'image/apng',
'image/webp',
'image/avif',
'video/mp4',
'video/webm',
'video/ogg',
'video/quicktime',
'audio/mp4',
'audio/webm',
@@ -38,6 +42,10 @@ function getBlobSafeMimeType(mimetype) {
if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) {
return 'application/octet-stream';
}
// Required for Chromium browsers
if (mimetype === 'video/quicktime') {
return 'video/mp4';
}
return mimetype;
}
@@ -61,9 +69,8 @@ async function getUrl(link, type, decryptData) {
}
}
function getNativeHeight(width, height) {
const MEDIA_MAX_WIDTH = 296;
const scale = MEDIA_MAX_WIDTH / width;
function getNativeHeight(width, height, maxWidth = 296) {
const scale = maxWidth / width;
return scale * height;
}
@@ -188,6 +195,45 @@ Image.propTypes = {
type: PropTypes.string,
};
function Sticker({
name, height, width, link, file, type,
}) {
const [url, setUrl] = useState(null);
useEffect(() => {
let unmounted = false;
async function fetchUrl() {
const myUrl = await getUrl(link, type, file);
if (unmounted) return;
setUrl(myUrl);
}
fetchUrl();
return () => {
unmounted = true;
};
}, []);
return (
<div className="sticker-container" style={{ height: width !== null ? getNativeHeight(width, height, 128) : 'unset' }}>
{ url !== null && <img src={url || link} title={name} alt={name} />}
</div>
);
}
Sticker.defaultProps = {
file: null,
type: '',
};
Sticker.propTypes = {
name: PropTypes.string.isRequired,
width: null,
height: null,
width: PropTypes.number,
height: PropTypes.number,
link: PropTypes.string.isRequired,
file: PropTypes.shape({}),
type: PropTypes.string,
};
function Audio({
name, link, type, file,
}) {
@@ -307,5 +353,5 @@ Video.propTypes = {
};
export {
File, Image, Audio, Video,
File, Image, Sticker, Audio, Video,
};
+9
View File
@@ -42,6 +42,15 @@
background-size: cover;
}
.sticker-container {
display: inline-flex;
max-width: 128px;
width: 100%;
& img {
width: 100% !important;
}
}
.image-container {
& img {
max-width: unset !important;
+36 -24
View File
@@ -5,7 +5,6 @@ import React, {
import PropTypes from 'prop-types';
import './Message.scss';
import { getShortcodeToCustomEmoji } from '../../organisms/emoji-board/custom-emoji';
import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
@@ -162,8 +161,8 @@ const MessageReplyWrapper = React.memo(({ roomTimeline, eventId }) => {
}, []);
const focusReply = (ev) => {
if (!ev.keyCode || ev.keyCode === 32 || ev.keyCode === 13) {
if (ev.keyCode) ev.preventDefault();
if (!ev.key || ev.key === ' ' || ev.key === 'Enter') {
if (ev.key) ev.preventDefault();
if (reply?.event === null) return;
if (reply?.event.isRedacted()) return;
roomTimeline.loadEventTimeline(eventId);
@@ -277,7 +276,7 @@ function MessageEdit({ body, onSave, onCancel }) {
}, []);
const handleKeyDown = (e) => {
if (e.keyCode === 13 && e.shiftKey === false) {
if (e.key === 'Enter' && e.shiftKey === false) {
e.preventDefault();
onSave(editInputRef.current.value);
}
@@ -322,7 +321,7 @@ function getMyEmojiEvent(emojiKey, eventId, roomTimeline) {
return rEvent;
}
function toggleEmoji(roomId, eventId, emojiKey, roomTimeline) {
function toggleEmoji(roomId, eventId, emojiKey, shortcode, roomTimeline) {
const myAlreadyReactEvent = getMyEmojiEvent(emojiKey, eventId, roomTimeline);
if (myAlreadyReactEvent) {
const rId = myAlreadyReactEvent.getId();
@@ -330,17 +329,17 @@ function toggleEmoji(roomId, eventId, emojiKey, roomTimeline) {
redactEvent(roomId, rId);
return;
}
sendReaction(roomId, eventId, emojiKey);
sendReaction(roomId, eventId, emojiKey, shortcode);
}
function pickEmoji(e, roomId, eventId, roomTimeline) {
openEmojiBoard(getEventCords(e), (emoji) => {
toggleEmoji(roomId, eventId, emoji.unicode, roomTimeline);
toggleEmoji(roomId, eventId, emoji.mxc ?? emoji.unicode, emoji.shortcodes[0], roomTimeline);
e.target.click();
});
}
function genReactionMsg(userIds, reaction) {
function genReactionMsg(userIds, reaction, shortcode) {
return (
<>
{userIds.map((userId, index) => (
@@ -354,24 +353,22 @@ function genReactionMsg(userIds, reaction) {
</React.Fragment>
))}
<span style={{ opacity: '.6' }}>{' reacted with '}</span>
{twemojify(reaction, { className: 'react-emoji' })}
{twemojify(shortcode ? `:${shortcode}:` : reaction, { className: 'react-emoji' })}
</>
);
}
function MessageReaction({
shortcodeToEmoji, reaction, count, users, isActive, onClick,
reaction, shortcode, count, users, isActive, onClick,
}) {
const customEmojiMatch = reaction.match(/^:(\S+):$/);
let customEmojiUrl = null;
if (customEmojiMatch) {
const customEmoji = shortcodeToEmoji.get(customEmojiMatch[1]);
customEmojiUrl = initMatrix.matrixClient.mxcUrlToHttp(customEmoji?.mxc);
if (reaction.match(/^mxc:\/\/\S+$/)) {
customEmojiUrl = initMatrix.matrixClient.mxcUrlToHttp(reaction);
}
return (
<Tooltip
className="msg__reaction-tooltip"
content={<Text variant="b2">{users.length > 0 ? genReactionMsg(users, reaction) : 'Unable to load who has reacted'}</Text>}
content={<Text variant="b2">{users.length > 0 ? genReactionMsg(users, reaction, shortcode) : 'Unable to load who has reacted'}</Text>}
>
<button
onClick={onClick}
@@ -380,7 +377,7 @@ function MessageReaction({
>
{
customEmojiUrl
? <img className="react-emoji" draggable="false" alt={reaction} src={customEmojiUrl} />
? <img className="react-emoji" draggable="false" alt={shortcode ?? reaction} src={customEmojiUrl} />
: twemojify(reaction, { className: 'react-emoji' })
}
<Text variant="b3" className="msg__reaction-count">{count}</Text>
@@ -388,9 +385,12 @@ function MessageReaction({
</Tooltip>
);
}
MessageReaction.defaultProps = {
shortcode: undefined,
};
MessageReaction.propTypes = {
shortcodeToEmoji: PropTypes.shape({}).isRequired,
reaction: PropTypes.node.isRequired,
shortcode: PropTypes.string,
count: PropTypes.number.isRequired,
users: PropTypes.arrayOf(PropTypes.string).isRequired,
isActive: PropTypes.bool.isRequired,
@@ -401,11 +401,10 @@ function MessageReactionGroup({ roomTimeline, mEvent }) {
const { roomId, room, reactionTimeline } = roomTimeline;
const mx = initMatrix.matrixClient;
const reactions = {};
const shortcodeToEmoji = getShortcodeToCustomEmoji(room);
const canSendReaction = room.currentState.maySendEvent('m.reaction', mx.getUserId());
const eventReactions = reactionTimeline.get(mEvent.getId());
const addReaction = (key, count, senderId, isActive) => {
const addReaction = (key, shortcode, count, senderId, isActive) => {
let reaction = reactions[key];
if (reaction === undefined) {
reaction = {
@@ -414,6 +413,7 @@ function MessageReactionGroup({ roomTimeline, mEvent }) {
isActive: false,
};
}
if (shortcode) reaction.shortcode = shortcode;
if (count) {
reaction.count = count;
} else {
@@ -429,9 +429,10 @@ function MessageReactionGroup({ roomTimeline, mEvent }) {
if (rEvent.getRelation() === null) return;
const reaction = rEvent.getRelation();
const senderId = rEvent.getSender();
const { shortcode } = rEvent.getContent();
const isActive = senderId === mx.getUserId();
addReaction(reaction.key, undefined, senderId, isActive);
addReaction(reaction.key, shortcode, undefined, senderId, isActive);
});
} else {
// Use aggregated reactions
@@ -439,7 +440,7 @@ function MessageReactionGroup({ roomTimeline, mEvent }) {
if (!aggregatedReaction) return null;
aggregatedReaction.forEach((reaction) => {
if (reaction.type !== 'm.reaction') return;
addReaction(reaction.key, reaction.count, undefined, false);
addReaction(reaction.key, undefined, reaction.count, undefined, false);
});
}
@@ -449,13 +450,13 @@ function MessageReactionGroup({ roomTimeline, mEvent }) {
Object.keys(reactions).map((key) => (
<MessageReaction
key={key}
shortcodeToEmoji={shortcodeToEmoji}
reaction={key}
shortcode={reactions[key].shortcode}
count={reactions[key].count}
users={reactions[key].users}
isActive={reactions[key].isActive}
onClick={() => {
toggleEmoji(roomId, mEvent.getId(), key, roomTimeline);
toggleEmoji(roomId, mEvent.getId(), key, reactions[key].shortcode, roomTimeline);
}}
/>
))
@@ -607,7 +608,7 @@ function genMediaContent(mE) {
if (typeof mediaMXC === 'undefined' || mediaMXC === '') return <span style={{ color: 'var(--bg-danger)' }}>Malformed event</span>;
let msgType = mE.getContent()?.msgtype;
if (mE.getType() === 'm.sticker') msgType = 'm.image';
if (mE.getType() === 'm.sticker') msgType = 'm.sticker';
switch (msgType) {
case 'm.file':
@@ -630,6 +631,17 @@ function genMediaContent(mE) {
type={mContent.info?.mimetype}
/>
);
case 'm.sticker':
return (
<Media.Sticker
name={mContent.body}
width={typeof mContent.info?.w === 'number' ? mContent.info?.w : null}
height={typeof mContent.info?.h === 'number' ? mContent.info?.h : null}
link={mx.mxcUrlToHttp(mediaMXC)}
file={isEncryptedFile ? mContent.file : null}
type={mContent.info?.mimetype}
/>
);
case 'm.audio':
return (
<Media.Audio
-1
View File
@@ -250,7 +250,6 @@
cursor: pointer;
& .react-emoji {
width: 16px;
height: 16px;
margin: 2px;
}
@@ -118,7 +118,7 @@ function RoomAliases({ roomId }) {
const loadLocalAliases = async () => {
let local = [];
try {
const result = await mx.unstableGetLocalAliases(roomId);
const result = await mx.getLocalAliases(roomId);
local = result.aliases.filter((alias) => !aliases.published.includes(alias));
} catch {
local = [];
@@ -0,0 +1,130 @@
import React, { useReducer, useEffect } from 'react';
import PropTypes from 'prop-types';
import './RoomEmojis.scss';
import initMatrix from '../../../client/initMatrix';
import { suffixRename } from '../../../util/common';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import Text from '../../atoms/text/Text';
import Input from '../../atoms/input/Input';
import Button from '../../atoms/button/Button';
import ImagePack from '../image-pack/ImagePack';
function useRoomPacks(room) {
const mx = initMatrix.matrixClient;
const [, forceUpdate] = useReducer((count) => count + 1, 0);
const packEvents = room.currentState.getStateEvents('im.ponies.room_emotes');
const unUsablePacks = [];
const usablePacks = packEvents.filter((mEvent) => {
if (typeof mEvent.getContent()?.images !== 'object') {
unUsablePacks.push(mEvent);
return false;
}
return true;
});
useEffect(() => {
const handleEvent = (event, state, prevEvent) => {
if (event.getRoomId() !== room.roomId) return;
if (event.getType() !== 'im.ponies.room_emotes') return;
if (!prevEvent?.getContent()?.images || !event.getContent().images) {
forceUpdate();
}
};
mx.on('RoomState.events', handleEvent);
return () => {
mx.removeListener('RoomState.events', handleEvent);
};
}, [room, mx]);
const isStateKeyAvailable = (key) => !room.currentState.getStateEvents('im.ponies.room_emotes', key);
const createPack = async (name) => {
const packContent = {
pack: { display_name: name },
images: {},
};
let stateKey = '';
if (unUsablePacks.length > 0) {
const mEvent = unUsablePacks[0];
stateKey = mEvent.getStateKey();
} else {
stateKey = packContent.pack.display_name.replace(/\s/g, '-');
if (!isStateKeyAvailable(stateKey)) {
stateKey = suffixRename(
stateKey,
isStateKeyAvailable,
);
}
}
await mx.sendStateEvent(room.roomId, 'im.ponies.room_emotes', packContent, stateKey);
};
const deletePack = async (stateKey) => {
await mx.sendStateEvent(room.roomId, 'im.ponies.room_emotes', {}, stateKey);
};
return {
usablePacks,
createPack,
deletePack,
};
}
function RoomEmojis({ roomId }) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const { usablePacks, createPack, deletePack } = useRoomPacks(room);
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0;
const canChange = room.currentState.hasSufficientPowerLevelFor('state_default', myPowerlevel);
const handlePackCreate = (e) => {
e.preventDefault();
const { nameInput } = e.target;
const name = nameInput.value.trim();
if (name === '') return;
nameInput.value = '';
createPack(name);
};
return (
<div className="room-emojis">
{ canChange && (
<div className="room-emojis__add-pack">
<MenuHeader>Create Pack</MenuHeader>
<form onSubmit={handlePackCreate}>
<Input name="nameInput" placeholder="Pack Name" required />
<Button variant="primary" type="submit">Create pack</Button>
</form>
</div>
)}
{
usablePacks.length > 0
? usablePacks.reverse().map((mEvent) => (
<ImagePack
key={mEvent.getId()}
roomId={roomId}
stateKey={mEvent.getStateKey()}
handlePackDelete={canChange ? deletePack : undefined}
/>
)) : (
<div className="room-emojis__empty">
<Text>No emoji or sticker pack.</Text>
</div>
)
}
</div>
);
}
RoomEmojis.propTypes = {
roomId: PropTypes.string.isRequired,
};
export default RoomEmojis;
@@ -0,0 +1,29 @@
.room-emojis {
.image-pack,
.room-emojis__add-pack,
.room-emojis__empty {
margin: var(--sp-normal) 0;
background-color: var(--bg-surface);
border-radius: var(--bo-radius);
box-shadow: var(--bs-surface-border);
overflow: hidden;
& > .context-menu__header:first-child {
margin-top: 2px;
}
}
&__add-pack {
& form {
margin: var(--sp-normal);
display: flex;
gap: var(--sp-normal);
& .input-container {
flex-grow: 1;
}
}
}
&__empty {
padding: var(--sp-extra-loose) var(--sp-normal);
text-align: center;
}
}
@@ -20,7 +20,7 @@ const items = [{
type: cons.notifs.DEFAULT,
}, {
iconSrc: BellRingIC,
text: 'All message',
text: 'All messages',
type: cons.notifs.ALL_MESSAGES,
}, {
iconSrc: BellPingIC,
@@ -237,12 +237,12 @@ function RoomPermissions({ roomId }) {
? permissions[permInfo.parent]?.[permKey]
: permissions[permKey];
if (!permValue) permValue = permInfo.default;
if (permValue === undefined) permValue = permInfo.default;
if (typeof permValue === 'number') {
powerLevel = permValue;
} else if (permKey === 'notifications') {
powerLevel = permValue.room || 50;
powerLevel = permValue.room ?? 50;
}
return (
<SettingTile
@@ -132,7 +132,7 @@ function RoomProfile({ roomId }) {
const renderEditNameAndTopic = () => (
<form className="room-profile__edit-form" onSubmit={handleOnSubmit}>
{canChangeName && <Input value={roomName} name="room-name" disabled={status.type === cons.status.IN_FLIGHT} label="Name" required />}
{canChangeName && <Input value={roomName} name="room-name" disabled={status.type === cons.status.IN_FLIGHT} label="Name" />}
{canChangeTopic && <Input value={roomTopic} name="room-topic" disabled={status.type === cons.status.IN_FLIGHT} minHeight={100} resizable label="Topic" />}
{(!canChangeName || !canChangeTopic) && <Text variant="b3">{`You have permission to change ${room.isSpaceRoom() ? 'space' : 'room'} ${canChangeName ? 'name' : 'topic'} only.`}</Text>}
{ status.type === cons.status.IN_FLIGHT && <Text variant="b2">{status.msg}</Text>}
@@ -5,6 +5,7 @@ import { twemojify } from '../../../util/twemojify';
import initMatrix from '../../../client/initMatrix';
import { openSpaceSettings, openSpaceManage, openInviteUser } from '../../../client/action/navigation';
import { markAsRead } from '../../../client/action/notifications';
import { leave } from '../../../client/action/room';
import {
createSpaceShortcut,
@@ -17,6 +18,7 @@ import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
import CategoryIC from '../../../../public/res/ic/outlined/category.svg';
import CategoryFilledIC from '../../../../public/res/ic/filled/category.svg';
import TickMarkIC from '../../../../public/res/ic/outlined/tick-mark.svg';
import AddUserIC from '../../../../public/res/ic/outlined/add-user.svg';
import SettingsIC from '../../../../public/res/ic/outlined/settings.svg';
import HashSearchIC from '../../../../public/res/ic/outlined/hash-search.svg';
@@ -28,11 +30,21 @@ import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
function SpaceOptions({ roomId, afterOptionSelect }) {
const mx = initMatrix.matrixClient;
const { roomList } = initMatrix;
const room = mx.getRoom(roomId);
const canInvite = room?.canInvite(mx.getUserId());
const isPinned = initMatrix.accountData.spaceShortcut.has(roomId);
const isCategorized = initMatrix.accountData.categorizedSpaces.has(roomId);
const handleMarkAsRead = () => {
const spaceChildren = roomList.getCategorizedSpaces([roomId]);
spaceChildren?.forEach((childIds, spaceId) => {
childIds?.forEach((childId) => {
markAsRead(childId);
})
});
afterOptionSelect();
};
const handleInviteClick = () => {
openInviteUser(roomId);
afterOptionSelect();
@@ -71,6 +83,7 @@ function SpaceOptions({ roomId, afterOptionSelect }) {
return (
<div style={{ maxWidth: 'calc(var(--navigation-drawer-width) - var(--sp-normal))' }}>
<MenuHeader>{twemojify(`Options for ${initMatrix.matrixClient.getRoom(roomId)?.name}`)}</MenuHeader>
<MenuItem iconSrc={TickMarkIC} onClick={handleMarkAsRead}>Mark as read</MenuItem>
<MenuItem
onClick={handleCategorizeClick}
iconSrc={isCategorized ? CategoryFilledIC : CategoryIC}
+24 -18
View File
@@ -70,7 +70,7 @@ const EmojiGroup = React.memo(({ name, groupEmojis }) => {
unicode={`:${emoji.shortcode}:`}
shortcodes={emoji.shortcode}
src={initMatrix.matrixClient.mxcUrlToHttp(emoji.mxc)}
data-mx-emoticon
data-mx-emoticon={emoji.mxc}
/>
)
}
@@ -141,10 +141,13 @@ function EmojiBoard({ onSelect, searchRef }) {
function getEmojiDataFromTarget(target) {
const unicode = target.getAttribute('unicode');
const hexcode = target.getAttribute('hexcode');
const mxc = target.getAttribute('data-mx-emoticon');
let shortcodes = target.getAttribute('shortcodes');
if (typeof shortcodes === 'undefined') shortcodes = undefined;
else shortcodes = shortcodes.split(',');
return { unicode, hexcode, shortcodes };
return {
unicode, hexcode, shortcodes, mxc,
};
}
function selectEmoji(e) {
@@ -202,21 +205,23 @@ function EmojiBoard({ onSelect, searchRef }) {
setAvailableEmojis([]);
return;
}
// Retrieve the packs for the new room
// Remove packs that aren't marked as emoji packs
// Remove packs without emojis
const packs = getRelevantPacks(
initMatrix.matrixClient.getRoom(selectedRoomId),
)
.filter((pack) => pack.usage.indexOf('emoticon') !== -1)
.filter((pack) => pack.getEmojis().length !== 0);
// Set an index for each pack so that we know where to jump when the user uses the nav
for (let i = 0; i < packs.length; i += 1) {
packs[i].packIndex = i;
const mx = initMatrix.matrixClient;
const room = mx.getRoom(selectedRoomId);
const parentIds = initMatrix.roomList.getAllParentSpaces(room.roomId);
const parentRooms = [...parentIds].map((id) => mx.getRoom(id));
if (room) {
const packs = getRelevantPacks(
room.client,
[room, ...parentRooms],
).filter((pack) => pack.getEmojis().length !== 0);
// Set an index for each pack so that we know where to jump when the user uses the nav
for (let i = 0; i < packs.length; i += 1) {
packs[i].packIndex = i;
}
setAvailableEmojis(packs);
}
setAvailableEmojis(packs);
};
const onOpen = () => {
@@ -260,7 +265,7 @@ function EmojiBoard({ onSelect, searchRef }) {
{
availableEmojis.map((pack) => (
<EmojiGroup
name={pack.displayName}
name={pack.displayName ?? 'Unknown'}
key={pack.packIndex}
groupEmojis={pack.getEmojis()}
className="custom-emoji-group"
@@ -293,13 +298,14 @@ function EmojiBoard({ onSelect, searchRef }) {
<div className="emoji-board__nav-custom">
{
availableEmojis.map((pack) => {
const src = initMatrix.matrixClient.mxcUrlToHttp(pack.avatar ?? pack.images[0].mxc);
const src = initMatrix.matrixClient
.mxcUrlToHttp(pack.avatarUrl ?? pack.getEmojis()[0].mxc);
return (
<IconButton
onClick={() => openGroup(recentOffset + pack.packIndex)}
src={src}
key={pack.packIndex}
tooltip={pack.displayName}
tooltip={pack.displayName ?? 'Unknown'}
tooltipPlacement="right"
isImage
/>
@@ -84,6 +84,7 @@
.emoji {
width: 32px;
height: 32px;
object-fit: contain;
}
}
& > p:last-child {
@@ -123,6 +124,7 @@
& .emoji {
width: 38px;
height: 38px;
object-fit: contain;
padding: var(--emoji-padding);
cursor: pointer;
&:hover {
+184 -102
View File
@@ -1,135 +1,224 @@
import { emojis } from './emoji';
// Custom emoji are stored in one of three places:
// - User emojis, which are stored in account data
// - Room emojis, which are stored in state events in a room
// - Emoji packs, which are rooms of emojis referenced in the account data or in a room's
// cannonical space
//
// Emojis and packs referenced from within a user's account data should be available
// globally, while emojis and packs in rooms and spaces should only be available within
// those spaces and rooms
// https://github.com/Sorunome/matrix-doc/blob/soru/emotes/proposals/2545-emotes.md
class ImagePack {
// Convert a raw image pack into a more maliable format
//
// Takes an image pack as per MSC 2545 (e.g. as in the Matrix spec), and converts it to a
// format used here, while filling in defaults.
//
// The room argument is the room the pack exists in, which is used as a fallback for
// missing properties
//
// Returns `null` if the rawPack is not a properly formatted image pack, although there
// is still a fair amount of tolerance for malformed packs.
static parsePack(rawPack, room) {
if (typeof rawPack.images === 'undefined') {
static parsePack(eventId, packContent) {
if (!eventId || typeof packContent?.images !== 'object') {
return null;
}
const pack = rawPack.pack ?? {};
return new ImagePack(eventId, packContent);
}
const displayName = pack.display_name ?? (room ? room.name : undefined);
const avatar = pack.avatar_url ?? (room ? room.getMxcAvatarUrl() : undefined);
const usage = pack.usage ?? ['emoticon', 'sticker'];
const { attribution } = pack;
const images = Object.entries(rawPack.images).flatMap((e) => {
const data = e[1];
const shortcode = e[0];
constructor(eventId, content) {
this.id = eventId;
this.content = JSON.parse(JSON.stringify(content));
this.applyPack(content);
this.applyImages(content);
}
applyPack(content) {
const pack = content.pack ?? {};
this.displayName = pack.display_name;
this.avatarUrl = pack.avatar_url;
this.usage = pack.usage ?? ['emoticon', 'sticker'];
this.attribution = pack.attribution;
}
applyImages(content) {
this.images = new Map();
this.emoticons = [];
this.stickers = [];
Object.entries(content.images).forEach(([shortcode, data]) => {
const mxc = data.url;
const body = data.body ?? shortcode;
const usage = data.usage ?? this.usage;
const { info } = data;
const usage_ = data.usage ?? usage;
if (mxc) {
return [{
shortcode, mxc, body, info, usage: usage_,
}];
if (!mxc) return;
const image = {
shortcode, mxc, body, usage, info,
};
this.images.set(shortcode, image);
if (usage.includes('emoticon')) {
this.emoticons.push(image);
}
if (usage.includes('sticker')) {
this.stickers.push(image);
}
return [];
});
return new ImagePack(displayName, avatar, usage, attribution, images);
}
constructor(displayName, avatar, usage, attribution, images) {
this.displayName = displayName;
this.avatar = avatar;
this.usage = usage;
this.attribution = attribution;
this.images = images;
getImages() {
return this.images;
}
// Produce a list of emoji in this image pack
getEmojis() {
return this.images.filter((i) => i.usage.indexOf('emoticon') !== -1);
return this.emoticons;
}
// Produce a list of stickers in this image pack
getStickers() {
return this.images.filter((i) => i.usage.indexOf('sticker') !== -1);
return this.stickers;
}
getContent() {
return this.content;
}
_updatePackProperty(property, value) {
if (this.content.pack === undefined) {
this.content.pack = {};
}
this.content.pack[property] = value;
this.applyPack(this.content);
}
setAvatarUrl(avatarUrl) {
this._updatePackProperty('avatar_url', avatarUrl);
}
setDisplayName(displayName) {
this._updatePackProperty('display_name', displayName);
}
setAttribution(attribution) {
this._updatePackProperty('attribution', attribution);
}
setUsage(usage) {
this._updatePackProperty('usage', usage);
}
addImage(key, imgContent) {
this.content.images = {
[key]: imgContent,
...this.content.images,
};
this.applyImages(this.content);
}
removeImage(key) {
if (this.content.images[key] === undefined) return;
delete this.content.images[key];
this.applyImages(this.content);
}
updateImageKey(key, newKey) {
if (this.content.images[key] === undefined) return;
const copyImages = {};
Object.keys(this.content.images).forEach((imgKey) => {
copyImages[imgKey === key ? newKey : imgKey] = this.content.images[imgKey];
});
this.content.images = copyImages;
this.applyImages(this.content);
}
_updateImageProperty(key, property, value) {
if (this.content.images[key] === undefined) return;
this.content.images[key][property] = value;
this.applyImages(this.content);
}
setImageUrl(key, url) {
this._updateImageProperty(key, 'url', url);
}
setImageBody(key, body) {
this._updateImageProperty(key, 'body', body);
}
setImageInfo(key, info) {
this._updateImageProperty(key, 'info', info);
}
setImageUsage(key, usage) {
this._updateImageProperty(key, 'usage', usage);
}
}
// Retrieve a list of user emojis
//
// Result is an ImagePack, or null if the user hasn't set up or has deleted their personal
// image pack.
//
// Accepts a reference to a matrix client as the only argument
function getGlobalImagePacks(mx) {
const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent();
if (typeof globalContent !== 'object') return [];
const { rooms } = globalContent;
if (typeof rooms !== 'object') return [];
const roomIds = Object.keys(rooms);
const packs = roomIds.flatMap((roomId) => {
if (typeof rooms[roomId] !== 'object') return [];
const room = mx.getRoom(roomId);
if (!room) return [];
const stateKeys = Object.keys(rooms[roomId]);
return stateKeys.map((stateKey) => {
const data = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
const pack = ImagePack.parsePack(data?.getId(), data?.getContent());
if (pack) {
pack.displayName ??= room.name;
pack.avatarUrl ??= room.getMxcAvatarUrl();
}
return pack;
}).filter((pack) => pack !== null);
});
return packs;
}
function getUserImagePack(mx) {
const accountDataEmoji = mx.getAccountData('im.ponies.user_emotes');
if (!accountDataEmoji) {
return null;
}
const userImagePack = ImagePack.parsePack(accountDataEmoji.event.content);
if (userImagePack) userImagePack.displayName ??= 'Your Emoji';
const userImagePack = ImagePack.parsePack(mx.getUserId(), accountDataEmoji.event.content);
userImagePack.displayName ??= 'Personal Emoji';
return userImagePack;
}
// Produces a list of all of the emoji packs in a room
//
// Returns a list of `ImagePack`s. This does not include packs in spaces that contain
// this room.
function getPacksInRoom(room) {
const packs = room.currentState.getStateEvents('im.ponies.room_emotes');
function getRoomImagePacks(room) {
const dataEvents = room.currentState.getStateEvents('im.ponies.room_emotes');
return packs
.map((p) => ImagePack.parsePack(p.event.content, room))
.filter((p) => p !== null);
return dataEvents
.map((data) => {
const pack = ImagePack.parsePack(data?.getId(), data?.getContent());
if (pack) {
pack.displayName ??= room.name;
pack.avatarUrl ??= room.getMxcAvatarUrl();
}
return pack;
})
.filter((pack) => pack !== null);
}
// Produce a list of all image packs which should be shown for a given room
//
// This includes packs in that room, the user's personal images, and will eventually
// include the user's enabled global image packs and space-level packs.
//
// This differs from getPacksInRoom, as the former only returns packs that are directly in
// a room, whereas this function returns all packs which should be shown to the user while
// they are in this room.
//
// Packs will be returned in the order that shortcode conflicts should be resolved, with
// higher priority packs coming first.
function getRelevantPacks(room) {
/**
* @param {MatrixClient} mx Provide if you want to include user personal/global pack
* @param {Room[]} rooms Provide rooms if you want to include rooms pack
* @returns {ImagePack[]} packs
*/
function getRelevantPacks(mx, rooms) {
const userPack = mx ? getUserImagePack(mx) : [];
const globalPacks = mx ? getGlobalImagePacks(mx) : [];
const globalPackIds = new Set(globalPacks.map((pack) => pack.id));
const roomsPack = rooms?.flatMap(getRoomImagePacks) ?? [];
return [].concat(
getUserImagePack(room.client) ?? [],
getPacksInRoom(room),
userPack ?? [],
globalPacks,
roomsPack.filter((pack) => !globalPackIds.has(pack.id)),
);
}
// Returns all user+room emojis and all standard unicode emojis
//
// Accepts a reference to a matrix client as the only argument
//
// Result is a map from shortcode to the corresponding emoji. If two emoji share a
// shortcode, only one will be presented, with priority given to custom emoji.
//
// Will eventually be expanded to include all emojis revelant to a room and the user
function getShortcodeToEmoji(room) {
function getShortcodeToEmoji(mx, rooms) {
const allEmoji = new Map();
emojis.forEach((emoji) => {
if (emoji.shortcodes.constructor.name === 'Array') {
if (Array.isArray(emoji.shortcodes)) {
emoji.shortcodes.forEach((shortcode) => {
allEmoji.set(shortcode, emoji);
});
@@ -138,7 +227,7 @@ function getShortcodeToEmoji(room) {
}
});
getRelevantPacks(room).reverse()
getRelevantPacks(mx, rooms)
.flatMap((pack) => pack.getEmojis())
.forEach((emoji) => {
allEmoji.set(emoji.shortcode, emoji);
@@ -150,7 +239,7 @@ function getShortcodeToEmoji(room) {
function getShortcodeToCustomEmoji(room) {
const allEmoji = new Map();
getRelevantPacks(room).reverse()
getRelevantPacks(room.client, [room])
.flatMap((pack) => pack.getEmojis())
.forEach((emoji) => {
allEmoji.set(emoji.shortcode, emoji);
@@ -159,27 +248,20 @@ function getShortcodeToCustomEmoji(room) {
return allEmoji;
}
// Produces a special list of emoji specifically for auto-completion
//
// This list contains each emoji once, with all emoji being deduplicated by shortcode.
// However, the order of the standard emoji will have been preserved, and alternate
// shortcodes for the standard emoji will not be considered.
//
// Standard emoji are guaranteed to be earlier in the list than custom emoji
function getEmojiForCompletion(room) {
function getEmojiForCompletion(mx, rooms) {
const allEmoji = new Map();
getRelevantPacks(room).reverse()
getRelevantPacks(mx, rooms)
.flatMap((pack) => pack.getEmojis())
.forEach((emoji) => {
allEmoji.set(emoji.shortcode, emoji);
});
return emojis.filter((e) => !allEmoji.has(e.shortcode))
.concat(Array.from(allEmoji.values()));
return Array.from(allEmoji.values()).concat(emojis.filter((e) => !allEmoji.has(e.shortcode)));
}
export {
getUserImagePack,
ImagePack,
getUserImagePack, getGlobalImagePacks, getRoomImagePacks,
getShortcodeToEmoji, getShortcodeToCustomEmoji,
getRelevantPacks, getEmojiForCompletion,
};
+3 -2
View File
@@ -1,5 +1,6 @@
import emojisData from 'emojibase-data/en/compact.json';
import shortcodes from 'emojibase-data/en/shortcodes/joypixels.json';
import joypixels from 'emojibase-data/en/shortcodes/joypixels.json';
import emojibase from 'emojibase-data/en/shortcodes/emojibase.json';
const emojiGroups = [{
name: 'Smileys & people',
@@ -52,7 +53,7 @@ function addToGroup(emoji) {
const emojis = [];
emojisData.forEach((emoji) => {
const myShortCodes = shortcodes[emoji.hexcode];
const myShortCodes = joypixels[emoji.hexcode] || emojibase[emoji.hexcode];
if (!myShortCodes) return;
const em = {
...emoji,
+1 -1
View File
@@ -4,7 +4,7 @@ import { emojis } from './emoji';
const eventType = 'io.element.recent_emoji';
function getRecentEmojisRaw() {
return initMatrix.matrixClient.getAccountData(eventType).getContent().recent_emoji ?? [];
return initMatrix.matrixClient.getAccountData(eventType)?.getContent().recent_emoji ?? [];
}
export function getRecentEmojis(limit) {
+1 -13
View File
@@ -6,7 +6,7 @@ import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import * as roomActions from '../../../client/action/room';
import { selectRoom } from '../../../client/action/navigation';
import { hasDMWith } from '../../../util/matrixUtil';
import { hasDMWith, hasDevices } from '../../../util/matrixUtil';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
@@ -103,18 +103,6 @@ function InviteUser({
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) {
if (mx.getUserId() === userId) return;
const dmRoomId = hasDMWith(userId);
@@ -11,7 +11,7 @@ import { selectRoom, openReusableContextMenu } from '../../../client/action/navi
import * as roomActions from '../../../client/action/room';
import {
getUsername, getUsernameOfRoomMember, getPowerLabel, hasDMWith
getUsername, getUsernameOfRoomMember, getPowerLabel, hasDMWith, hasDevices
} from '../../../util/matrixUtil';
import { getEventCords } from '../../../util/common';
import colorMXID from '../../../util/colorMXID';
@@ -201,7 +201,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
// Create new DM
try {
setIsCreatingDM(true);
await roomActions.createDM(userId);
await roomActions.createDM(userId, await hasDevices(userId));
} catch {
if (isMountedRef.current === false) return;
setIsCreatingDM(false);
+10 -4
View File
@@ -25,9 +25,11 @@ import RoomHistoryVisibility from '../../molecules/room-history-visibility/RoomH
import RoomEncryption from '../../molecules/room-encryption/RoomEncryption';
import RoomPermissions from '../../molecules/room-permissions/RoomPermissions';
import RoomMembers from '../../molecules/room-members/RoomMembers';
import RoomEmojis from '../../molecules/room-emojis/RoomEmojis';
import UserIC from '../../../../public/res/ic/outlined/user.svg';
import SettingsIC from '../../../../public/res/ic/outlined/settings.svg';
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
import SearchIC from '../../../../public/res/ic/outlined/search.svg';
import ShieldUserIC from '../../../../public/res/ic/outlined/shield-user.svg';
import LockIC from '../../../../public/res/ic/outlined/lock.svg';
@@ -42,6 +44,7 @@ const tabText = {
GENERAL: 'General',
SEARCH: 'Search',
MEMBERS: 'Members',
EMOJIS: 'Emojis',
PERMISSIONS: 'Permissions',
SECURITY: 'Security',
};
@@ -58,6 +61,10 @@ const tabItems = [{
iconSrc: UserIC,
text: tabText.MEMBERS,
disabled: false,
}, {
iconSrc: EmojiIC,
text: tabText.EMOJIS,
disabled: false,
}, {
iconSrc: ShieldUserIC,
text: tabText.PERMISSIONS,
@@ -197,6 +204,7 @@ function RoomSettings({ roomId }) {
{selectedTab.text === tabText.GENERAL && <GeneralSettings roomId={roomId} />}
{selectedTab.text === tabText.SEARCH && <RoomSearch roomId={roomId} />}
{selectedTab.text === tabText.MEMBERS && <RoomMembers roomId={roomId} />}
{selectedTab.text === tabText.EMOJIS && <RoomEmojis roomId={roomId} />}
{selectedTab.text === tabText.PERMISSIONS && <RoomPermissions roomId={roomId} />}
{selectedTab.text === tabText.SECURITY && <SecuritySettings roomId={roomId} />}
</div>
@@ -210,7 +218,5 @@ RoomSettings.propTypes = {
roomId: PropTypes.string.isRequired,
};
export {
RoomSettings as default,
tabText,
};
export default RoomSettings;
export { tabText };
+12 -6
View File
@@ -21,7 +21,7 @@ import AsyncSearch from '../../../util/AsyncSearch';
import Text from '../../atoms/text/Text';
import ScrollView from '../../atoms/scroll/ScrollView';
import FollowingMembers from '../../molecules/following-members/FollowingMembers';
import { addRecentEmoji } from '../emoji-board/recent';
import { addRecentEmoji, getRecentEmojis } from '../emoji-board/recent';
const commands = [{
name: 'markdown',
@@ -213,9 +213,15 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) {
setCmd({ prefix, suggestions: commands });
},
':': () => {
const emojis = getEmojiForCompletion(mx.getRoom(roomId));
const parentIds = initMatrix.roomList.getAllParentSpaces(roomId);
const parentRooms = [...parentIds].map((id) => mx.getRoom(id));
const emojis = getEmojiForCompletion(mx, [mx.getRoom(roomId), ...parentRooms]);
const recentEmoji = getRecentEmojis(20);
asyncSearch.setup(emojis, { keys: ['shortcode'], isContain: true, limit: 20 });
setCmd({ prefix, suggestions: emojis.slice(26, 46) });
setCmd({
prefix,
suggestions: recentEmoji.length > 0 ? recentEmoji : emojis.slice(26, 46),
});
},
'@': () => {
const members = mx.getRoom(roomId).getJoinedMembers().map((member) => ({
@@ -247,7 +253,7 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) {
}
if (myCmd.prefix === '@') {
viewEvent.emit('cmd_fired', {
replace: myCmd.result.name,
replace: `@${myCmd.result.userId}`,
});
}
deactivateCmd();
@@ -256,11 +262,11 @@ function RoomViewCmdBar({ roomId, roomTimeline, viewEvent }) {
function listenKeyboard(event) {
const { activeElement } = document;
const lastCmdItem = document.activeElement.parentNode.lastElementChild;
if (event.keyCode === 27) {
if (event.key === 'Escape') {
if (activeElement.className !== 'cmd-item') return;
viewEvent.emit('focus_msg_input');
}
if (event.keyCode === 9) {
if (event.key === 'Tab') {
if (lastCmdItem.className !== 'cmd-item') return;
if (lastCmdItem !== activeElement) return;
if (event.shiftKey) return;
+58 -3
View File
@@ -8,7 +8,7 @@ import TextareaAutosize from 'react-autosize-textarea';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import settings from '../../../client/state/settings';
import { openEmojiBoard } from '../../../client/action/navigation';
import { openEmojiBoard, openReusableContextMenu } from '../../../client/action/navigation';
import navigation from '../../../client/state/navigation';
import { bytesToSize, getEventCords } from '../../../util/common';
import { getUsername } from '../../../util/matrixUtil';
@@ -20,9 +20,12 @@ import IconButton from '../../atoms/button/IconButton';
import ScrollView from '../../atoms/scroll/ScrollView';
import { MessageReply } from '../../molecules/message/Message';
import StickerBoard from '../sticker-board/StickerBoard';
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
import SendIC from '../../../../public/res/ic/outlined/send.svg';
import StickerIC from '../../../../public/res/ic/outlined/sticker.svg';
import ShieldIC from '../../../../public/res/ic/outlined/shield.svg';
import VLCIC from '../../../../public/res/ic/outlined/vlc.svg';
import VolumeFullIC from '../../../../public/res/ic/outlined/volume-full.svg';
@@ -129,7 +132,9 @@ function RoomViewInput({
function firedCmd(cmdData) {
const msg = textAreaRef.current.value;
textAreaRef.current.value = replaceCmdWith(
msg, cmdCursorPos, typeof cmdData?.replace !== 'undefined' ? cmdData.replace : '',
msg,
cmdCursorPos,
typeof cmdData?.replace !== 'undefined' ? cmdData.replace : '',
);
deactivateCmd();
}
@@ -201,6 +206,33 @@ function RoomViewInput({
if (replyTo !== null) setReplyTo(null);
};
const handleSendSticker = async (data) => {
const { mxc: url, body, httpUrl } = data;
const info = {};
const img = new Image();
img.src = httpUrl;
try {
const res = await fetch(httpUrl);
const blob = await res.blob();
info.w = img.width;
info.h = img.height;
info.mimetype = blob.type;
info.size = blob.size;
info.thumbnail_info = { ...info };
info.thumbnail_url = url;
} catch {
// send sticker without info
}
mx.sendEvent(roomId, 'm.sticker', {
body,
url,
info,
});
};
function processTyping(msg) {
const isEmptyMsg = msg === '';
@@ -254,7 +286,7 @@ function RoomViewInput({
};
const handleKeyDown = (e) => {
if (e.keyCode === 13 && e.shiftKey === false) {
if (e.key === 'Enter' && e.shiftKey === false) {
e.preventDefault();
sendMessage();
}
@@ -340,6 +372,29 @@ function RoomViewInput({
{isMarkdown && <RawIcon size="extra-small" src={MarkdownIC} />}
</div>
<div ref={rightOptionsRef} className="room-input__option-container">
<IconButton
onClick={(e) => {
openReusableContextMenu(
'top',
(() => {
const cords = getEventCords(e);
cords.y -= 20;
return cords;
})(),
(closeMenu) => (
<StickerBoard
roomId={roomId}
onSelect={(data) => {
handleSendSticker(data);
closeMenu();
}}
/>
),
);
}}
tooltip="Sticker"
src={StickerIC}
/>
<IconButton
onClick={(e) => {
const cords = getEventCords(e);
+2 -1
View File
@@ -155,6 +155,7 @@ function DeviceManage() {
const lastIP = device.last_seen_ip;
const lastTS = device.last_seen_ts;
const isCurrentDevice = mx.deviceId === deviceId;
const canVerify = isVerified === false && (isMeVerified || isCurrentDevice);
return (
<SettingTile
@@ -171,7 +172,7 @@ function DeviceManage() {
? <Spinner size="small" />
: (
<>
{((isMeVerified && isVerified === false) || (isCurrentDevice && isVerified === false)) && <Button onClick={() => verify(deviceId, isCurrentDevice)} variant="positive">Verify</Button>}
{(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={() => handleRemove(device)} src={BinIC} tooltip="Remove session" />
</>
+35 -16
View File
@@ -24,6 +24,7 @@ import PopupWindow from '../../molecules/popup-window/PopupWindow';
import SettingTile from '../../molecules/setting-tile/SettingTile';
import ImportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ImportE2ERoomKeys';
import ExportE2ERoomKeys from '../../molecules/import-export-e2e-room-keys/ExportE2ERoomKeys';
import { ImagePackUser, ImagePackGlobal } from '../../molecules/image-pack/ImagePack';
import ProfileEditor from '../profile-editor/ProfileEditor';
import CrossSigning from './CrossSigning';
@@ -31,6 +32,7 @@ import KeyBackup from './KeyBackup';
import DeviceManage from './DeviceManage';
import SunIC from '../../../../public/res/ic/outlined/sun.svg';
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
import LockIC from '../../../../public/res/ic/outlined/lock.svg';
import BellIC from '../../../../public/res/ic/outlined/bell.svg';
import InfoIC from '../../../../public/res/ic/outlined/info.svg';
@@ -57,23 +59,25 @@ function AppearanceSection() {
)}
content={<Text variant="b3">Use light or dark mode based on the system settings.</Text>}
/>
{!settings.useSystemTheme && (
<SettingTile
title="Theme"
content={(
<SegmentedControls
selected={settings.getThemeIndex()}
segments={[
{ text: 'Light' },
{ text: 'Silver' },
{ text: 'Dark' },
{ text: 'Butter' },
]}
onSelect={(index) => settings.setTheme(index)}
/>
)}
/>
<SettingTile
title="Theme"
content={(
<SegmentedControls
selected={settings.useSystemTheme ? -1 : settings.getThemeIndex()}
segments={[
{ text: 'Light' },
{ text: 'Silver' },
{ text: 'Dark' },
{ text: 'Butter' },
]}
onSelect={(index) => {
if (settings.useSystemTheme) toggleSystemTheme();
settings.setTheme(index);
updateState({});
}}
/>
)}
/>
</div>
<div className="settings-appearance__card">
<MenuHeader>Room messages</MenuHeader>
@@ -167,6 +171,15 @@ function NotificationsSection() {
);
}
function EmojiSection() {
return (
<>
<div className="settings-emoji__card"><ImagePackUser /></div>
<div className="settings-emoji__card"><ImagePackGlobal /></div>
</>
);
}
function SecuritySection() {
return (
<div className="settings-security">
@@ -248,6 +261,7 @@ function AboutSection() {
export const tabText = {
APPEARANCE: 'Appearance',
NOTIFICATIONS: 'Notifications',
EMOJI: 'Emoji',
SECURITY: 'Security',
ABOUT: 'About',
};
@@ -261,6 +275,11 @@ const tabItems = [{
iconSrc: BellIC,
disabled: false,
render: () => <NotificationsSection />,
}, {
text: tabText.EMOJI,
iconSrc: EmojiIC,
disabled: false,
render: () => <EmojiSection />,
}, {
text: tabText.SECURITY,
iconSrc: LockIC,
+2 -1
View File
@@ -40,7 +40,8 @@
.settings-notifications,
.settings-security__card,
.settings-security .device-manage,
.settings-about__card {
.settings-about__card,
.settings-emoji__card {
@extend .settings-window__card;
}
@@ -25,6 +25,7 @@ import RoomVisibility from '../../molecules/room-visibility/RoomVisibility';
import RoomAliases from '../../molecules/room-aliases/RoomAliases';
import RoomPermissions from '../../molecules/room-permissions/RoomPermissions';
import RoomMembers from '../../molecules/room-members/RoomMembers';
import RoomEmojis from '../../molecules/room-emojis/RoomEmojis';
import UserIC from '../../../../public/res/ic/outlined/user.svg';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
@@ -35,6 +36,7 @@ import PinIC from '../../../../public/res/ic/outlined/pin.svg';
import PinFilledIC from '../../../../public/res/ic/filled/pin.svg';
import CategoryIC from '../../../../public/res/ic/outlined/category.svg';
import CategoryFilledIC from '../../../../public/res/ic/filled/category.svg';
import EmojiIC from '../../../../public/res/ic/outlined/emoji.svg';
import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
import { useForceUpdate } from '../../hooks/useForceUpdate';
@@ -42,6 +44,7 @@ import { useForceUpdate } from '../../hooks/useForceUpdate';
const tabText = {
GENERAL: 'General',
MEMBERS: 'Members',
EMOJIS: 'Emojis',
PERMISSIONS: 'Permissions',
};
@@ -53,6 +56,10 @@ const tabItems = [{
iconSrc: UserIC,
text: tabText.MEMBERS,
disabled: false,
}, {
iconSrc: EmojiIC,
text: tabText.EMOJIS,
disabled: false,
}, {
iconSrc: ShieldUserIC,
text: tabText.PERMISSIONS,
@@ -178,6 +185,7 @@ function SpaceSettings() {
<div className="space-settings__cards-wrapper">
{selectedTab.text === tabText.GENERAL && <GeneralSettings roomId={roomId} />}
{selectedTab.text === tabText.MEMBERS && <RoomMembers roomId={roomId} />}
{selectedTab.text === tabText.EMOJIS && <RoomEmojis roomId={roomId} />}
{selectedTab.text === tabText.PERMISSIONS && <RoomPermissions roomId={roomId} />}
</div>
</div>
@@ -0,0 +1,88 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React from 'react';
import PropTypes from 'prop-types';
import './StickerBoard.scss';
import initMatrix from '../../../client/initMatrix';
import { getRelevantPacks } from '../emoji-board/custom-emoji';
import Text from '../../atoms/text/Text';
import ScrollView from '../../atoms/scroll/ScrollView';
function StickerBoard({ roomId, onSelect }) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const parentIds = initMatrix.roomList.getAllParentSpaces(room.roomId);
const parentRooms = [...parentIds].map((id) => mx.getRoom(id));
const packs = getRelevantPacks(
mx,
[room, ...parentRooms],
).filter((pack) => pack.getStickers().length !== 0);
function isTargetNotSticker(target) {
return target.classList.contains('sticker-board__sticker') === false;
}
function getStickerData(target) {
const mxc = target.getAttribute('data-mx-sticker');
const body = target.getAttribute('title');
const httpUrl = target.getAttribute('src');
return { mxc, body, httpUrl };
}
const handleOnSelect = (e) => {
if (isTargetNotSticker(e.target)) return;
const stickerData = getStickerData(e.target);
onSelect(stickerData);
};
const renderPack = (pack) => (
<div className="sticker-board__pack" key={pack.id}>
<Text className="sticker-board__pack-header" variant="b2" weight="bold">{pack.displayName ?? 'Unknown'}</Text>
<div className="sticker-board__pack-items">
{pack.getStickers().map((sticker) => (
<img
key={sticker.shortcode}
className="sticker-board__sticker"
src={mx.mxcUrlToHttp(sticker.mxc)}
alt={sticker.shortcode}
title={sticker.body ?? sticker.shortcode}
data-mx-sticker={sticker.mxc}
/>
))}
</div>
</div>
);
return (
<div className="sticker-board">
<div className="sticker-board__container">
<ScrollView autoHide>
<div
onClick={handleOnSelect}
className="sticker-board__content"
>
{
packs.length > 0
? packs.map(renderPack)
: (
<div className="sticker-board__empty">
<Text>There is no sticker pack.</Text>
</div>
)
}
</div>
</ScrollView>
</div>
<div />
</div>
);
}
StickerBoard.propTypes = {
roomId: PropTypes.string.isRequired,
onSelect: PropTypes.func.isRequired,
};
export default StickerBoard;
@@ -0,0 +1,60 @@
@use '../../partials/dir';
.sticker-board {
--sticker-board-height: 390px;
--sticker-board-width: 286px;
display: flex;
height: var(--sticker-board-height);
&__container {
flex-grow: 1;
min-width: 0;
width: var(--sticker-board-width);
display: flex;
}
&__content {
min-height: 100%;
}
&__pack {
margin-bottom: var(--sp-normal);
position: relative;
&-header {
position: sticky;
top: 0;
z-index: 99;
background-color: var(--bg-surface);
@include dir.side(margin, var(--sp-extra-tight), 0);
padding: var(--sp-extra-tight) var(--sp-ultra-tight);
text-transform: uppercase;
box-shadow: 0 -4px 0 0 var(--bg-surface);
border-bottom: 1px solid var(--bg-surface-border);
}
&-items {
margin: var(--sp-tight);
@include dir.side(margin, var(--sp-normal), var(--sp-extra-tight));
display: flex;
flex-wrap: wrap;
gap: var(--sp-normal) var(--sp-tight);
img {
width: 76px;
height: 76px;
object-fit: contain;
cursor: pointer;
}
}
}
&__empty {
width: 100%;
height: var(--sticker-board-height);
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
}
+55 -30
View File
@@ -21,6 +21,8 @@ import Avatar from '../../atoms/avatar/Avatar';
import ContextMenu, { MenuItem, MenuHeader } from '../../atoms/context-menu/ContextMenu';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import EyeIC from '../../../../public/res/ic/outlined/eye.svg';
import EyeBlindIC from '../../../../public/res/ic/outlined/eye-blind.svg';
import CinnySvg from '../../../../public/res/svg/cinny.svg';
import SSOButtons from '../../molecules/sso-buttons/SSOButtons';
@@ -54,11 +56,8 @@ function Homeserver({ onChange }) {
const setupHsConfig = async (servername) => {
setProcess({ isLoading: true, message: 'Looking for homeserver...' });
let baseUrl = null;
try {
baseUrl = await getBaseUrl(servername);
} catch (e) {
baseUrl = e.message;
}
baseUrl = await getBaseUrl(servername);
if (searchingHs !== servername) return;
setProcess({ isLoading: true, message: `Connecting to ${baseUrl}...` });
const tempClient = auth.createTemporaryClient(baseUrl);
@@ -97,7 +96,7 @@ function Homeserver({ onChange }) {
if (!hsList?.length > 0 || selectedHs < 0 || selectedHs >= hsList?.length) {
throw new Error();
}
setHs({ selected: hsList[selectedHs], list: hsList, allowCustom: allowCustom });
setHs({ selected: hsList[selectedHs], list: hsList, allowCustom });
} catch {
setHs({ selected: 'matrix.org', list: ['matrix.org'], allowCustom: true });
}
@@ -114,8 +113,14 @@ function Homeserver({ onChange }) {
return (
<>
<div className="homeserver-form">
<Input name="homeserver" onChange={handleHsInput} value={hs?.selected} forwardRef={hsRef} label="Homeserver"
disabled={hs === null || !hs.allowCustom} />
<Input
name="homeserver"
onChange={handleHsInput}
value={hs?.selected}
forwardRef={hsRef}
label="Homeserver"
disabled={hs === null || !hs.allowCustom}
/>
<ContextMenu
placement="right"
content={(hideMenu) => (
@@ -156,6 +161,7 @@ Homeserver.propTypes = {
function Login({ loginFlow, baseUrl }) {
const [typeIndex, setTypeIndex] = useState(0);
const [passVisible, setPassVisible] = useState(false);
const loginTypes = ['Username', 'Email'];
const isPassword = loginFlow?.filter((flow) => flow.type === 'm.login.password')[0];
const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0];
@@ -166,31 +172,38 @@ function Login({ loginFlow, baseUrl }) {
const validator = (values) => {
const errors = {};
if (typeIndex === 0 && values.username.length > 0 && values.username.indexOf(':') > -1) {
errors.username = 'Username must contain local-part only';
}
if (typeIndex === 1 && values.email.length > 0 && !isValidInput(values.email, EMAIL_REGEX)) {
errors.email = BAD_EMAIL_ERROR;
}
return errors;
};
const submitter = (values, actions) => auth.login(
baseUrl,
typeIndex === 0 ? normalizeUsername(values.username) : undefined,
typeIndex === 1 ? values.email : undefined,
values.password,
).then(() => {
actions.setSubmitting(true);
window.location.reload();
}).catch((error) => {
let msg = error.message;
if (msg === 'Unknown message') msg = 'Please check your credentials';
actions.setErrors({
password: msg === 'Invalid password' ? msg : undefined,
other: msg !== 'Invalid password' ? msg : undefined,
const submitter = async (values, actions) => {
let userBaseUrl = baseUrl;
let { username } = values;
const mxIdMatch = username.match(/^@(.+):(.+\..+)$/);
if (typeIndex === 0 && mxIdMatch) {
[, username, userBaseUrl] = mxIdMatch;
userBaseUrl = await getBaseUrl(userBaseUrl);
}
return auth.login(
userBaseUrl,
typeIndex === 0 ? normalizeUsername(username) : undefined,
typeIndex === 1 ? values.email : undefined,
values.password,
).then(() => {
actions.setSubmitting(true);
window.location.reload();
}).catch((error) => {
let msg = error.message;
if (msg === 'Unknown message') msg = 'Please check your credentials';
actions.setErrors({
password: msg === 'Invalid password' ? msg : undefined,
other: msg !== 'Invalid password' ? msg : undefined,
});
actions.setSubmitting(false);
});
actions.setSubmitting(false);
});
};
return (
<>
@@ -236,7 +249,10 @@ function Login({ loginFlow, baseUrl }) {
{errors.username && <Text className="auth-form__error" variant="b3">{errors.username}</Text>}
{typeIndex === 1 && <Input values={values.email} name="email" onChange={handleChange} label="Email" type="email" required />}
{errors.email && <Text className="auth-form__error" variant="b3">{errors.email}</Text>}
<Input values={values.password} name="password" onChange={handleChange} label="Password" type="password" required />
<div className="auth-form__pass-eye-wrapper">
<Input values={values.password} name="password" onChange={handleChange} label="Password" type={passVisible ? 'text' : 'password'} required />
<IconButton onClick={() => setPassVisible(!passVisible)} src={passVisible ? EyeIC : EyeBlindIC} size="extra-small" />
</div>
{errors.password && <Text className="auth-form__error" variant="b3">{errors.password}</Text>}
{errors.other && <Text className="auth-form__error" variant="b3">{errors.other}</Text>}
<div className="auth-form__btns">
@@ -269,6 +285,8 @@ let sid;
let clientSecret;
function Register({ registerInfo, loginFlow, baseUrl }) {
const [process, setProcess] = useState({});
const [passVisible, setPassVisible] = useState(false);
const [cPassVisible, setCPassVisible] = useState(false);
const formRef = useRef();
const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0];
@@ -319,6 +337,7 @@ function Register({ registerInfo, loginFlow, baseUrl }) {
if (!isAvail) {
actions.setErrors({ username: 'Username is already taken' });
actions.setSubmitting(false);
return;
}
if (isEmail && values.email.length > 0) {
const result = await auth.verifyEmail(baseUrl, values.email, clientSecret, 1);
@@ -437,9 +456,15 @@ function Register({ registerInfo, loginFlow, baseUrl }) {
<form className="auth-form" ref={formRef} onSubmit={handleSubmit}>
<Input values={values.username} name="username" onChange={handleChange} label="Username" type="username" required />
{errors.username && <Text className="auth-form__error" variant="b3">{errors.username}</Text>}
<Input values={values.password} name="password" onChange={handleChange} label="Password" type="password" required />
<div className="auth-form__pass-eye-wrapper">
<Input values={values.password} name="password" onChange={handleChange} label="Password" type={passVisible ? 'text' : 'password'} required />
<IconButton onClick={() => setPassVisible(!passVisible)} src={passVisible ? EyeIC : EyeBlindIC} size="extra-small" />
</div>
{errors.password && <Text className="auth-form__error" variant="b3">{errors.password}</Text>}
<Input values={values.confirmPassword} name="confirmPassword" onChange={handleChange} label="Confirm password" type="password" required />
<div className="auth-form__pass-eye-wrapper">
<Input values={values.confirmPassword} name="confirmPassword" onChange={handleChange} label="Confirm password" type={cPassVisible ? 'text' : 'password'} required />
<IconButton onClick={() => setCPassVisible(!cPassVisible)} src={cPassVisible ? EyeIC : EyeBlindIC} size="extra-small" />
</div>
{errors.confirmPassword && <Text className="auth-form__error" variant="b3">{errors.confirmPassword}</Text>}
{isEmail && <Input values={values.email} name="email" onChange={handleChange} label={`Email${isEmailRequired ? '' : ' (optional)'}`} type="email" required={isEmailRequired} />}
{errors.email && <Text className="auth-form__error" variant="b3">{errors.email}</Text>}
+16 -1
View File
@@ -97,7 +97,8 @@
}
.auth-form {
& > .input-container {
& > .input-container,
&__pass-eye-wrapper {
margin: var(--sp-tight) 0 var(--sp-ultra-tight);
}
@@ -107,6 +108,20 @@
margin-top: calc(var(--sp-extra-loose) + var(--sp-tight));
}
&__pass-eye-wrapper {
position: relative;
& .ic-btn {
position: absolute;
@include dir.prop(right, 6px, unset);
@include dir.prop(left, unset, 6px );
bottom: 6px;
border-radius: 4px;
}
& input {
@include dir.side(padding, var(--sp-normal), 46px);
}
}
&__btns {
padding-top: var(--sp-loose);
margin-bottom: var(--sp-extra-loose);
+10 -9
View File
@@ -11,17 +11,18 @@ async function redactEvent(roomId, eventId, reason) {
}
}
async function sendReaction(roomId, toEventId, reaction) {
async function sendReaction(roomId, toEventId, reaction, shortcode) {
const mx = initMatrix.matrixClient;
const content = {
'm.relates_to': {
event_id: toEventId,
key: reaction,
rel_type: 'm.annotation',
},
};
if (typeof shortcode === 'string') content.shortcode = shortcode;
try {
await mx.sendEvent(roomId, 'm.reaction', {
'm.relates_to': {
event_id: toEventId,
key: reaction,
rel_type: 'm.annotation',
},
});
await mx.sendEvent(roomId, 'm.reaction', content);
} catch (e) {
throw new Error(e);
}
+7 -6
View File
@@ -31,14 +31,14 @@ function listenKeyboard(event) {
// Ctrl/Cmd +
if (event.ctrlKey || event.metaKey) {
// open search modal
if (event.code === 'KeyK') {
if (event.key === 'k') {
event.preventDefault();
if (navigation.isRawModalVisible) return;
openSearch();
}
// focus message field on paste
if (event.code === 'KeyV') {
if (event.key === 'v') {
if (navigation.isRawModalVisible) return;
const msgTextarea = document.getElementById('message-textarea');
const { activeElement } = document;
@@ -51,11 +51,8 @@ function listenKeyboard(event) {
if (!event.ctrlKey && !event.altKey && !event.metaKey) {
if (navigation.isRawModalVisible) return;
if (['input', 'textarea'].includes(document.activeElement.tagName.toLowerCase())) {
return;
}
if (event.code === 'Escape') {
if (event.key === 'Escape') {
if (navigation.isRoomSettings) {
toggleRoomSettings();
return;
@@ -66,6 +63,10 @@ function listenKeyboard(event) {
}
}
if (['input', 'textarea'].includes(document.activeElement.tagName.toLowerCase())) {
return;
}
// focus the text field on most keypresses
if (shouldFocusMessageField(event.code)) {
// press any key to focus and type in message field
+1 -1
View File
@@ -67,7 +67,7 @@ class InitMatrix extends EventEmitter {
if (prevState === null) {
this.roomList = new RoomList(this.matrixClient);
this.accountData = new AccountData(this.roomList);
this.roomsInput = new RoomsInput(this.matrixClient);
this.roomsInput = new RoomsInput(this.matrixClient, this.roomList);
this.notifications = new Notifications(this.roomList);
this.emit('init_loading_finished');
}
+36 -31
View File
@@ -5,21 +5,10 @@ import encrypt from 'browser-encrypt-attachment';
import { math } from 'micromark-extension-math';
import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji';
import { mathExtensionHtml, spoilerExtension, spoilerExtensionHtml } from '../../util/markdown';
import { getImageDimension } from '../../util/common';
import cons from './cons';
import settings from './settings';
function getImageDimension(file) {
return new Promise((resolve) => {
const img = new Image();
img.onload = async () => {
resolve({
w: img.width,
h: img.height,
});
};
img.src = URL.createObjectURL(file);
});
}
function loadVideo(videoFile) {
return new Promise((resolve, reject) => {
const video = document.createElement('video');
@@ -46,7 +35,12 @@ function loadVideo(videoFile) {
reader.onerror = (e) => {
reject(e);
};
reader.readAsDataURL(videoFile);
if (videoFile.type === 'video/quicktime') {
const quicktimeVideoFile = new File([videoFile], videoFile.name, { type: 'video/mp4' });
reader.readAsDataURL(quicktimeVideoFile);
} else {
reader.readAsDataURL(videoFile);
}
});
}
function getVideoThumbnail(video, width, height, mimeType) {
@@ -115,14 +109,13 @@ function bindReplyToContent(roomId, reply, content) {
return newContent;
}
// Apply formatting to a plain text message
//
// This includes inserting any custom emoji that might be relevant, and (only if the
// user has enabled it in their settings) formatting the message using markdown.
function formatAndEmojifyText(room, text) {
const allEmoji = getShortcodeToEmoji(room);
function formatAndEmojifyText(mx, roomList, roomId, text) {
const room = mx.getRoom(roomId);
const { userIdsToDisplayNames } = room.currentState;
const parentIds = roomList.getAllParentSpaces(roomId);
const parentRooms = [...parentIds].map((id) => mx.getRoom(id));
const allEmoji = getShortcodeToEmoji(mx, [room, ...parentRooms]);
// Start by applying markdown formatting (if relevant)
let formattedText;
if (settings.isMarkdown) {
formattedText = getFormattedBody(text);
@@ -130,17 +123,25 @@ function formatAndEmojifyText(room, text) {
formattedText = text;
}
// Check to see if there are any :shortcode-style-tags: in the message
Array.from(formattedText.matchAll(/\B:([\w-]+):\B/g))
// Then filter to only the ones corresponding to a valid emoji
.filter((match) => allEmoji.has(match[1]))
// Reversing the array ensures that indices are preserved as we start replacing
const MXID_REGEX = /\B@\S+:\S+\.\S+[^.,:;?!\s]/g;
Array.from(formattedText.matchAll(MXID_REGEX))
.filter((mxidMatch) => userIdsToDisplayNames[mxidMatch[0]])
.reverse()
// Replace each :shortcode: with an <img/> tag
.forEach((mxidMatch) => {
const tag = `<a href="https://matrix.to/#/${mxidMatch[0]}">${userIdsToDisplayNames[mxidMatch[0]]}</a>`;
formattedText = formattedText.substr(0, mxidMatch.index)
+ tag
+ formattedText.substr(mxidMatch.index + mxidMatch[0].length);
});
const SHORTCODE_REGEX = /\B:([\w-]+):\B/g;
Array.from(formattedText.matchAll(SHORTCODE_REGEX))
.filter((shortcodeMatch) => allEmoji.has(shortcodeMatch[1]))
.reverse() /* Reversing the array ensures that indices are preserved as we start replacing */
.forEach((shortcodeMatch) => {
const emoji = allEmoji.get(shortcodeMatch[1]);
// Render the tag that will replace the shortcode
let tag;
if (emoji.mxc) {
tag = `<img data-mx-emoticon="" src="${
@@ -154,7 +155,6 @@ function formatAndEmojifyText(room, text) {
tag = emoji.unicode;
}
// Splice the tag into the text
formattedText = formattedText.substr(0, shortcodeMatch.index)
+ tag
+ formattedText.substr(shortcodeMatch.index + shortcodeMatch[0].length);
@@ -164,10 +164,11 @@ function formatAndEmojifyText(room, text) {
}
class RoomsInput extends EventEmitter {
constructor(mx) {
constructor(mx, roomList) {
super();
this.matrixClient = mx;
this.roomList = roomList;
this.roomIdToInput = new Map();
}
@@ -268,7 +269,9 @@ class RoomsInput extends EventEmitter {
// Apply formatting if relevant
const formattedBody = formatAndEmojifyText(
this.matrixClient.getRoom(roomId),
this.matrixClient,
this.roomList,
roomId,
input.message,
);
if (formattedBody !== input.message) {
@@ -407,7 +410,9 @@ class RoomsInput extends EventEmitter {
// Apply formatting if relevant
const formattedBody = formatAndEmojifyText(
this.matrixClient.getRoom(roomId),
this.matrixClient,
this.roomList,
roomId,
editedBody,
);
if (formattedBody !== editedBody) {
+1 -1
View File
@@ -1,5 +1,5 @@
const cons = {
version: '2.0.3',
version: '2.0.4',
secretKey: {
ACCESS_TOKEN: 'cinny_access_token',
DEVICE_ID: 'cinny_device_id',
+26 -19
View File
@@ -48,31 +48,43 @@ class Settings extends EventEmitter {
return this.themes[this.themeIndex];
}
setTheme(themeIndex) {
const appBody = document.getElementById('appBody');
appBody.classList.remove('system-theme');
_clearTheme() {
document.body.classList.remove('system-theme');
this.themes.forEach((themeName) => {
if (themeName === '') return;
appBody.classList.remove(themeName);
document.body.classList.remove(themeName);
});
// If use system theme is enabled
// we will override current theme choice with system theme
}
applyTheme() {
this._clearTheme();
if (this.useSystemTheme) {
appBody.classList.add('system-theme');
} else if (this.themes[themeIndex] !== '') {
appBody.classList.add(this.themes[themeIndex]);
document.body.classList.add('system-theme');
} else if (this.themes[this.themeIndex]) {
document.body.classList.add(this.themes[this.themeIndex]);
}
setSettings('themeIndex', themeIndex);
}
setTheme(themeIndex) {
this.themeIndex = themeIndex;
setSettings('themeIndex', this.themeIndex);
this.applyTheme();
}
toggleUseSystemTheme() {
this.useSystemTheme = !this.useSystemTheme;
setSettings('useSystemTheme', this.useSystemTheme);
this.applyTheme();
this.emit(cons.events.settings.SYSTEM_THEME_TOGGLED, this.useSystemTheme);
}
getUseSystemTheme() {
if (typeof this.useSystemTheme === 'boolean') return this.useSystemTheme;
const settings = getSettings();
if (settings === null) return false;
if (typeof settings.useSystemTheme === 'undefined') return false;
if (settings === null) return true;
if (typeof settings.useSystemTheme === 'undefined') return true;
return settings.useSystemTheme;
}
@@ -138,12 +150,7 @@ class Settings extends EventEmitter {
setter(action) {
const actions = {
[cons.actions.settings.TOGGLE_SYSTEM_THEME]: () => {
this.useSystemTheme = !this.useSystemTheme;
setSettings('useSystemTheme', this.useSystemTheme);
this.setTheme(this.themeIndex);
this.emit(cons.events.settings.SYSTEM_THEME_TOGGLED, this.useSystemTheme);
this.toggleUseSystemTheme();
},
[cons.actions.settings.TOGGLE_MARKDOWN]: () => {
this.isMarkdown = !this.isMarkdown;
+1 -1
View File
@@ -7,7 +7,7 @@ import settings from './client/state/settings';
import App from './app/pages/App';
settings.setTheme(settings.getThemeIndex());
settings.applyTheme();
ReactDom.render(
<App />,
+59
View File
@@ -132,3 +132,62 @@ export function copyToClipboard(text) {
copyInput.remove();
}
}
export function suffixRename(name, validator) {
let suffix = 2;
let newName = name;
do {
newName = name + suffix;
suffix += 1;
} while (validator(newName));
return newName;
}
export function getImageDimension(file) {
return new Promise((resolve) => {
const img = new Image();
img.onload = async () => {
resolve({
w: img.width,
h: img.height,
});
URL.revokeObjectURL(img.src);
};
img.src = URL.createObjectURL(file);
});
}
export function scaleDownImage(imageFile, width, height) {
return new Promise((resolve) => {
const imgURL = URL.createObjectURL(imageFile);
const img = new Image();
img.onload = () => {
let newWidth = img.width;
let newHeight = img.height;
if (newHeight > height) {
newWidth = Math.floor(newWidth * (height / newHeight));
newHeight = height;
}
if (newWidth > width) {
newHeight = Math.floor(newHeight * (width / newWidth));
newWidth = width;
}
const canvas = document.createElement('canvas');
canvas.width = newWidth;
canvas.height = newHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, newWidth, newHeight);
canvas.toBlob((thumbnail) => {
URL.revokeObjectURL(imgURL);
resolve(thumbnail);
}, imageFile.type);
};
img.src = imgURL;
});
}
+13 -1
View File
@@ -20,7 +20,7 @@ export async function getBaseUrl(servername) {
if (baseUrl === undefined) throw new Error();
return baseUrl;
} catch (e) {
throw new Error(`${protocol}${servername}`);
return `${protocol}${servername}`;
}
}
@@ -199,3 +199,15 @@ export function getSSKeyInfo(key) {
return undefined;
}
}
export async function hasDevices(userId) {
const mx = initMatrix.matrixClient;
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;
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ function transformSpanTag(tagName, attribs) {
}
function transformATag(tagName, attribs) {
const userLink = attribs.href.match(/^https?:\/\/matrix.to\/#\/(@.+:.+)/);
const userLink = decodeURIComponent(attribs.href).match(/^https?:\/\/matrix.to\/#\/(@.+:.+)/);
if (userLink !== null) {
// convert user link to pill
const userId = userLink[1];
+1 -1
View File
@@ -1,7 +1,7 @@
/* eslint-disable import/prefer-default-export */
import React, { lazy, Suspense } from 'react';
import linkifyHtml from 'linkifyjs/html';
import linkifyHtml from 'linkify-html';
import parse from 'html-react-parser';
import twemoji from 'twemoji';
import { sanitizeText } from './sanitize';