Compare commits

...

31 Commits

Author SHA1 Message Date
Ajay Bura
12369ba2ec v1.8.2
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-17 16:44:50 +05:30
Ajay Bura
f5720bde14 Fix new message not appearing (#391)
* Fix new message no appearing

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Fix room not marking as read

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Fix room automatically gets mark as read

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Fix sending wrong read recipt

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Fix sending message not mark as read

Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-17 08:16:49 +05:30
Krishan
0d59a4de48 v1.8.1 (#390) 2022-03-15 18:15:08 +05:30
dependabot[bot]
73c74d0477 Bump twemoji from 13.1.0 to 14.0.1 (#383)
Bumps [twemoji](https://github.com/twitter/twemoji) from 13.1.0 to 14.0.1.
- [Release notes](https://github.com/twitter/twemoji/releases)
- [Commits](https://github.com/twitter/twemoji/commits)

---
updated-dependencies:
- dependency-name: twemoji
  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-03-15 17:50:47 +05:30
Ajay Bura
70ffd7ded8 Fix muted room show unread indicator (#386)
* Move getNotifType function

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Fix bug in getNotiType

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Add isMuted prop in room selector

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Fix muted room show unread indicator

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Fix muted room notification visible in space

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Fix space shows muted room notification on load

Signed-off-by: Ajay Bura <ajbura@gmail.com>

* Toggle room mute when changed from other client

Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-15 17:21:36 +05:30
Clament John
92a3a8d6fa Fix view source shows original event for an edited message (#377)
Signed-off-by: Clament John <cj@hackerlab.in>

fixes #376

When we click view source for an edited message we were showing
the original event (the unedited event) instead of the latest
edited event.
2022-03-15 17:20:43 +05:30
Krishan
6e9cd02b2b Fix workflow name (#389) 2022-03-15 17:19:18 +05:30
dependabot[bot]
d0f90af251 Bump docker/build-push-action from 2.9.0 to 2.10.0 (#388)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2.9.0 to 2.10.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2.9.0...v2.10.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-03-15 17:16:58 +05:30
dependabot[bot]
c53eb27c6f Bump node from 17.6.0-alpine3.15 to 17.7.1-alpine3.15 (#382)
Bumps node from 17.6.0-alpine3.15 to 17.7.1-alpine3.15.

---
updated-dependencies:
- dependency-name: 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-03-15 17:16:18 +05:30
Krishan
38773e89ff Simplify GitHub actions (#387)
* Simplify production build actions 

This merges both the netlify-prod and docker action and also automatically add tarball to releases.

* Delete docker.yaml

* Delete netlify-prod.yaml

* Cosmetic changes and add dockerhub check

* Cosmetic changes

* Fix check runs on Tuesdays only
2022-03-15 17:04:14 +05:30
dependabot[bot]
19cb30d360 Bump eslint from 8.10.0 to 8.11.0 (#381)
Bumps [eslint](https://github.com/eslint/eslint) from 8.10.0 to 8.11.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.10.0...v8.11.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-03-15 10:36:20 +05:30
dependabot[bot]
50a734b977 Bump eslint-plugin-react from 7.29.3 to 7.29.4 (#385)
Bumps [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) from 7.29.3 to 7.29.4.
- [Release notes](https://github.com/yannickcr/eslint-plugin-react/releases)
- [Changelog](https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yannickcr/eslint-plugin-react/compare/v7.29.3...v7.29.4)

---
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-03-15 10:35:45 +05:30
dependabot[bot]
b988758ac2 Bump css-loader from 6.7.0 to 6.7.1 (#384)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 6.7.0 to 6.7.1.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v6.7.0...v6.7.1)

---
updated-dependencies:
- dependency-name: css-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-03-15 10:35:21 +05:30
dependabot[bot]
2567328e8b Bump @fontsource/inter from 4.5.4 to 4.5.5 (#380)
Bumps [@fontsource/inter](https://github.com/fontsource/fontsource/tree/HEAD/packages/inter) from 4.5.4 to 4.5.5.
- [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/packages/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-03-15 09:48:42 +05:30
dependabot[bot]
a7568fcbbf Bump @babel/core from 7.17.5 to 7.17.7 (#379)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.17.5 to 7.17.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.17.7/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-03-15 09:48:31 +05:30
Ajay Bura
27a06ae90c Enable hide nick avatar event by default
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-14 17:48:27 +05:30
Ajay Bura
211fd19031 Prevent unnecessary calc in home roomlist
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-14 17:34:34 +05:30
Ajay Bura
fe18611b4b Fix getAllParent including itself
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-14 17:31:57 +05:30
Ajay Bura
5e9b45ad5f Fix roomlist not updating when adding space cycle
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-14 17:29:40 +05:30
Ajay Bura
af833daee4 Fix root space appear as category in cyclic relation
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-13 18:52:51 +05:30
Ajay Bura
1ad5317d6e Ignore space notification
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-13 16:40:26 +05:30
Ajay Bura
7cf5df80ce Fix getParentSpaces can cause call stack overflow
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-13 15:36:15 +05:30
Ajay Bura
d6b880d110 Fix wrong notification count
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-13 15:12:54 +05:30
Ajay Bura
cf58a4376e Improve roomlist code
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-13 10:37:25 +05:30
Ajay Bura
a76dcb289a Improve code quality
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-12 14:14:56 +05:30
Ajay Bura
48ec6224e7 Add recent clock icon
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-11 16:03:48 +05:30
Ajay Bura
127dd8baf4 Add cmd + k hotkey for mac os
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-11 14:14:57 +05:30
Ajay Bura
d25c3ff4fc Fix pressing crtl/alt key jump to highlighted msg (#344)
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-11 14:04:44 +05:30
ginnyTheCat
f0e9de4cf9 Fix not all emoji-only messages being detected as jumbo emoji (#368) 2022-03-11 10:33:58 +05:30
Ajay Bura
92607a788e Fix docker not building on some OS 2022-03-10 18:23:54 +05:30
Ajay Bura
82948c1f55 Fix new message do not appear sometimes (#185)
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2022-03-10 17:58:40 +05:30
29 changed files with 931 additions and 744 deletions

View File

@@ -1,22 +1,28 @@
# Docs: <https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/customizing-dependency-updates>
# Docs: <https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/customizing-dependency-updates>
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule: {interval: weekly}
reviewers: [ajbura]
assignees: [ajbura]
schedule:
interval: weekly
day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"
- package-ecosystem: docker
directory: /
schedule: {interval: weekly}
reviewers: [ajbura]
assignees: [ajbura]
schedule:
interval: weekly
day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"
- package-ecosystem: npm
directory: /
schedule: {interval: weekly}
reviewers: [ajbura]
assignees: [ajbura]
schedule:
interval: weekly
day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"

View File

@@ -1,32 +1,39 @@
name: 'Build PR'
name: 'Build pull request'
on:
pull_request:
types: ['opened', 'synchronize']
jobs:
build:
runs-on: ubuntu-latest
env:
PR_NUMBER: ${{github.event.number}}
steps:
- uses: actions/checkout@v3.0.0
- name: Build
run: npm ci && npm run build
- name: Upload Artifact
uses: actions/upload-artifact@v3.0.0
with:
name: previewbuild
path: dist
retention-days: 1
- uses: actions/github-script@v6.0.0
with:
script: |
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.json', JSON.stringify(context.payload.pull_request));
- name: Upload PR Info
uses: actions/upload-artifact@v3.0.0
with:
name: pr.json
path: pr.json
retention-days: 1
build-pull-request:
runs-on: ubuntu-latest
env:
PR_NUMBER: ${{github.event.number}}
steps:
- name: Check out the repo
uses: actions/checkout@v3.0.0
- name: Build app
run: npm ci && npm run build
- name: Upload artifact
uses: actions/upload-artifact@v3.0.0
with:
name: previewbuild
path: dist
retention-days: 1
- name: Get PR info
uses: actions/github-script@v6.0.0
with:
script: |
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.json', JSON.stringify(context.payload.pull_request));
- name: Upload PR Info
uses: actions/upload-artifact@v3.0.0
with:
name: pr.json
path: pr.json
retention-days: 1
- name: Build Docker image
uses: docker/build-push-action@v2.10.0
with:
context: .
push: false

View File

@@ -1,78 +1,78 @@
name: Upload Preview Build to Netlify
on:
workflow_run:
workflows: ["Build PR"]
types:
- completed
workflow_run:
workflows: ["Build pull request"]
types:
- completed
jobs:
build:
runs-on: ubuntu-latest
if: >
${{ github.event.workflow_run.conclusion == 'success' }}
steps:
# There's a 'download artifact' action but it hasn't been updated for the
# workflow_run action (https://github.com/actions/download-artifact/issues/60)
# so instead we get this mess:
- name: 'Download artifact'
uses: actions/github-script@v6.0.0
with:
script: |
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "previewbuild"
})[0];
var download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data));
var prInfoArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr.json"
})[0];
var download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: prInfoArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.json.zip', Buffer.from(download.data));
- name: Extract Artifacts
run: unzip -d dist previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip
- name: 'Read PR Info'
id: readctx
uses: actions/github-script@v6.0.0
with:
script: |
var fs = require('fs');
var pr = JSON.parse(fs.readFileSync('${{github.workspace}}/pr.json'));
console.log(`::set-output name=prnumber::${pr.number}`);
- name: Deploy to Netlify
id: netlify
uses: nwtgck/actions-netlify@v1.2.3
with:
publish-dir: dist
deploy-message: "Deploy from GitHub Actions"
# These don't work because we're in workflow_run
enable-pull-request-comment: false
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }}
timeout-minutes: 1
- name: Edit PR Description
uses: velas/pr-description@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
pull-request-number: ${{ steps.readctx.outputs.prnumber }}
description-message: |
Preview: ${{ steps.netlify.outputs.deploy-url }}
⚠️ Exercise caution. Use test accounts. ⚠️
get-build-and-deploy:
runs-on: ubuntu-latest
if: >
${{ github.event.workflow_run.conclusion == 'success' }}
steps:
# There's a 'download artifact' action but it hasn't been updated for the
# workflow_run action (https://github.com/actions/download-artifact/issues/60)
# so instead we get this mess:
- name: 'Download artifact'
uses: actions/github-script@v6.0.0
with:
script: |
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "previewbuild"
})[0];
var download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data));
var prInfoArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr.json"
})[0];
var download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: prInfoArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.json.zip', Buffer.from(download.data));
- name: Extract Artifacts
run: unzip -d dist previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip
- name: 'Read PR Info'
id: readctx
uses: actions/github-script@v6.0.0
with:
script: |
var fs = require('fs');
var pr = JSON.parse(fs.readFileSync('${{github.workspace}}/pr.json'));
console.log(`::set-output name=prnumber::${pr.number}`);
- name: Deploy to Netlify
id: netlify
uses: nwtgck/actions-netlify@v1.2.3
with:
publish-dir: dist
deploy-message: "Deploy from GitHub Actions"
# These don't work because we're in workflow_run
enable-pull-request-comment: false
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }}
timeout-minutes: 1
- name: Edit PR Description
uses: velas/pr-description@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
pull-request-number: ${{ steps.readctx.outputs.prnumber }}
description-message: |
Preview: ${{ steps.netlify.outputs.deploy-url }}
⚠️ Exercise caution. Use test accounts. ⚠️

View File

@@ -1,34 +0,0 @@
name: Publish Docker image
on:
release:
types: [published]
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3.0.0
- name: Log in to Docker Hub
uses: docker/login-action@v1.14.1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v3.6.2
with:
images: ajbura/cinny
- name: Build and push Docker image
uses: docker/build-push-action@v2.9.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,21 +0,0 @@
name: 'Deploy to Netlify (prod)'
on:
release:
types: [published]
jobs:
deploy:
name: 'Deploy'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.0.0
- uses: jsmrcaga/action-netlify-deploy@v1.7.2
with:
install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
BUILD_DIRECTORY: "dist"
NETLIFY_DEPLOY_MESSAGE: "Prod deploy v${{ github.ref }}"
NETLIFY_DEPLOY_TO_PROD: true

56
.github/workflows/prod-deploy.yaml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: 'Production deploy'
on:
release:
types: [published]
jobs:
deploy-to-netlify:
name: 'Deploy to Netlify'
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3.0.0
- name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@v1.7.2
with:
install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
BUILD_DIRECTORY: "dist"
NETLIFY_DEPLOY_MESSAGE: "Prod deploy v${{ github.ref }}"
NETLIFY_DEPLOY_TO_PROD: true
- name: Get version from tag
id: vars
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Create tar.gz
run: tar -czvf cinny-${{ steps.vars.outputs.tag }}.tar.gz dist
- name: Upload tagged release
uses: softprops/action-gh-release@v1
with:
files: |
cinny-${{ steps.vars.outputs.tag }}.tar.gz
push_to_dockerhub:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3.0.0
- name: Login to Docker Hub
uses: docker/login-action@v1.14.1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v3.6.2
with:
images: ajbura/cinny
- name: Build and push Docker image
uses: docker/build-push-action@v2.10.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,11 +1,11 @@
## Builder
FROM node:17.6.0-alpine3.15 as builder
FROM node:17.7.1-alpine3.15 as builder
WORKDIR /src
COPY package.json package-lock.json /src
COPY package.json package-lock.json /src/
RUN npm ci
COPY . /src
COPY . /src/
RUN npm run build

267
package-lock.json generated
View File

@@ -1,15 +1,15 @@
{
"name": "cinny",
"version": "1.8.0",
"version": "1.8.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cinny",
"version": "1.8.0",
"version": "1.8.2",
"license": "MIT",
"dependencies": {
"@fontsource/inter": "^4.5.4",
"@fontsource/inter": "^4.5.5",
"@fontsource/roboto": "^4.5.3",
"@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",
@@ -38,10 +38,10 @@
"react-modal": "^3.14.4",
"sanitize-html": "^2.7.0",
"tippy.js": "^6.3.7",
"twemoji": "^13.1.0"
"twemoji": "^14.0.1"
},
"devDependencies": {
"@babel/core": "^7.17.5",
"@babel/core": "^7.17.7",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"assert": "^2.0.0",
@@ -51,13 +51,13 @@
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.4",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"eslint": "^8.10.0",
"eslint": "^8.11.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.29.3",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.2.0",
"favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2",
@@ -106,27 +106,27 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.16.8",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz",
"integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz",
"integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.17.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz",
"integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz",
"integrity": "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.17.3",
"@babel/helper-compilation-targets": "^7.16.7",
"@babel/helper-module-transforms": "^7.16.7",
"@babel/helpers": "^7.17.2",
"@babel/parser": "^7.17.3",
"@babel/generator": "^7.17.7",
"@babel/helper-compilation-targets": "^7.17.7",
"@babel/helper-module-transforms": "^7.17.7",
"@babel/helpers": "^7.17.7",
"@babel/parser": "^7.17.7",
"@babel/template": "^7.16.7",
"@babel/traverse": "^7.17.3",
"@babel/types": "^7.17.0",
@@ -145,9 +145,9 @@
}
},
"node_modules/@babel/generator": {
"version": "7.17.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz",
"integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
"integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
"dev": true,
"dependencies": {
"@babel/types": "^7.17.0",
@@ -184,12 +184,12 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz",
"integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz",
"integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.16.4",
"@babel/compat-data": "^7.17.7",
"@babel/helper-validator-option": "^7.16.7",
"browserslist": "^4.17.5",
"semver": "^6.3.0"
@@ -344,19 +344,19 @@
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz",
"integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz",
"integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-module-imports": "^7.16.7",
"@babel/helper-simple-access": "^7.16.7",
"@babel/helper-simple-access": "^7.17.7",
"@babel/helper-split-export-declaration": "^7.16.7",
"@babel/helper-validator-identifier": "^7.16.7",
"@babel/template": "^7.16.7",
"@babel/traverse": "^7.16.7",
"@babel/types": "^7.16.7"
"@babel/traverse": "^7.17.3",
"@babel/types": "^7.17.0"
},
"engines": {
"node": ">=6.9.0"
@@ -414,12 +414,12 @@
}
},
"node_modules/@babel/helper-simple-access": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz",
"integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz",
"integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.16.7"
"@babel/types": "^7.17.0"
},
"engines": {
"node": ">=6.9.0"
@@ -483,13 +483,13 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.17.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz",
"integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz",
"integrity": "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==",
"dev": true,
"dependencies": {
"@babel/template": "^7.16.7",
"@babel/traverse": "^7.17.0",
"@babel/traverse": "^7.17.3",
"@babel/types": "^7.17.0"
},
"engines": {
@@ -511,9 +511,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.17.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz",
"integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz",
"integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -1782,16 +1782,16 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.0.tgz",
"integrity": "sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
"integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.3.1",
"globals": "^13.9.0",
"ignore": "^4.0.6",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.0.4",
@@ -1816,19 +1816,10 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/eslintrc/node_modules/ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/@fontsource/inter": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.4.tgz",
"integrity": "sha512-D0icTFpt9bWvB/OEXMztYf0bhUQZoDIYpsco5C7GVfxgKDRl8Jdn3N2aHHQqwjgRUUvRuyMv8HrRM8Hrt4U52w=="
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.5.tgz",
"integrity": "sha512-mWnePEroLfaJQWmynipzOVcH6JwT8Jta3+yLsC5Pm/snHBXnOiAOnjBqYjKnvXwJ4eUPt2AaAhyrtwCgWQRGOg=="
},
"node_modules/@fontsource/roboto": {
"version": "4.5.3",
@@ -4669,9 +4660,9 @@
}
},
"node_modules/css-loader": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.0.tgz",
"integrity": "sha512-S7HCfCiDHLA+VXKqdZwyRZgoO0R9BnKDnVIoHMq5grl3N86zAu7MB+FBWHr5xOJC8SmvpTLha/2NpfFkFEN/ig==",
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
"integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==",
"dev": true,
"dependencies": {
"icss-utils": "^5.1.0",
@@ -5643,12 +5634,12 @@
}
},
"node_modules/eslint": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.10.0.tgz",
"integrity": "sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz",
"integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.2.0",
"@eslint/eslintrc": "^1.2.1",
"@humanwhocodes/config-array": "^0.9.2",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
@@ -5856,9 +5847,9 @@
}
},
"node_modules/eslint-plugin-react": {
"version": "7.29.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz",
"integrity": "sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg==",
"version": "7.29.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz",
"integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==",
"dev": true,
"dependencies": {
"array-includes": "^3.1.4",
@@ -13146,20 +13137,20 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"node_modules/twemoji": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-13.1.0.tgz",
"integrity": "sha512-e3fZRl2S9UQQdBFLYXtTBT6o4vidJMnpWUAhJA+yLGR+kaUTZAt3PixC0cGvvxWSuq2MSz/o0rJraOXrWw/4Ew==",
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-14.0.1.tgz",
"integrity": "sha512-eoqhea0sUhmC10iTacksyp1v9O4BP1jKmVqtK+Nztw40/dzawSHkXL3/xCpyh+mukmEvJ0Gw9VLvwZfQ9HKXDw==",
"dependencies": {
"fs-extra": "^8.0.1",
"jsonfile": "^5.0.0",
"twemoji-parser": "13.1.0",
"twemoji-parser": "14.0.0",
"universalify": "^0.1.2"
}
},
"node_modules/twemoji-parser": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-13.1.0.tgz",
"integrity": "sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg=="
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -14123,24 +14114,24 @@
}
},
"@babel/compat-data": {
"version": "7.16.8",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz",
"integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz",
"integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==",
"dev": true
},
"@babel/core": {
"version": "7.17.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz",
"integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz",
"integrity": "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==",
"dev": true,
"requires": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.17.3",
"@babel/helper-compilation-targets": "^7.16.7",
"@babel/helper-module-transforms": "^7.16.7",
"@babel/helpers": "^7.17.2",
"@babel/parser": "^7.17.3",
"@babel/generator": "^7.17.7",
"@babel/helper-compilation-targets": "^7.17.7",
"@babel/helper-module-transforms": "^7.17.7",
"@babel/helpers": "^7.17.7",
"@babel/parser": "^7.17.7",
"@babel/template": "^7.16.7",
"@babel/traverse": "^7.17.3",
"@babel/types": "^7.17.0",
@@ -14152,9 +14143,9 @@
}
},
"@babel/generator": {
"version": "7.17.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz",
"integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
"integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
"dev": true,
"requires": {
"@babel/types": "^7.17.0",
@@ -14182,12 +14173,12 @@
}
},
"@babel/helper-compilation-targets": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz",
"integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz",
"integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==",
"dev": true,
"requires": {
"@babel/compat-data": "^7.16.4",
"@babel/compat-data": "^7.17.7",
"@babel/helper-validator-option": "^7.16.7",
"browserslist": "^4.17.5",
"semver": "^6.3.0"
@@ -14300,19 +14291,19 @@
}
},
"@babel/helper-module-transforms": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz",
"integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz",
"integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==",
"dev": true,
"requires": {
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-module-imports": "^7.16.7",
"@babel/helper-simple-access": "^7.16.7",
"@babel/helper-simple-access": "^7.17.7",
"@babel/helper-split-export-declaration": "^7.16.7",
"@babel/helper-validator-identifier": "^7.16.7",
"@babel/template": "^7.16.7",
"@babel/traverse": "^7.16.7",
"@babel/types": "^7.16.7"
"@babel/traverse": "^7.17.3",
"@babel/types": "^7.17.0"
}
},
"@babel/helper-optimise-call-expression": {
@@ -14355,12 +14346,12 @@
}
},
"@babel/helper-simple-access": {
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz",
"integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz",
"integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==",
"dev": true,
"requires": {
"@babel/types": "^7.16.7"
"@babel/types": "^7.17.0"
}
},
"@babel/helper-skip-transparent-expression-wrappers": {
@@ -14406,13 +14397,13 @@
}
},
"@babel/helpers": {
"version": "7.17.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz",
"integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz",
"integrity": "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==",
"dev": true,
"requires": {
"@babel/template": "^7.16.7",
"@babel/traverse": "^7.17.0",
"@babel/traverse": "^7.17.3",
"@babel/types": "^7.17.0"
}
},
@@ -14428,9 +14419,9 @@
}
},
"@babel/parser": {
"version": "7.17.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz",
"integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==",
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz",
"integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==",
"dev": true
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
@@ -15289,16 +15280,16 @@
"dev": true
},
"@eslint/eslintrc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.0.tgz",
"integrity": "sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
"integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.3.1",
"globals": "^13.9.0",
"ignore": "^4.0.6",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.0.4",
@@ -15313,19 +15304,13 @@
"requires": {
"type-fest": "^0.20.2"
}
},
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
}
}
},
"@fontsource/inter": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.4.tgz",
"integrity": "sha512-D0icTFpt9bWvB/OEXMztYf0bhUQZoDIYpsco5C7GVfxgKDRl8Jdn3N2aHHQqwjgRUUvRuyMv8HrRM8Hrt4U52w=="
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.5.tgz",
"integrity": "sha512-mWnePEroLfaJQWmynipzOVcH6JwT8Jta3+yLsC5Pm/snHBXnOiAOnjBqYjKnvXwJ4eUPt2AaAhyrtwCgWQRGOg=="
},
"@fontsource/roboto": {
"version": "4.5.3",
@@ -17681,9 +17666,9 @@
}
},
"css-loader": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.0.tgz",
"integrity": "sha512-S7HCfCiDHLA+VXKqdZwyRZgoO0R9BnKDnVIoHMq5grl3N86zAu7MB+FBWHr5xOJC8SmvpTLha/2NpfFkFEN/ig==",
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
"integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==",
"dev": true,
"requires": {
"icss-utils": "^5.1.0",
@@ -18419,12 +18404,12 @@
"dev": true
},
"eslint": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.10.0.tgz",
"integrity": "sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz",
"integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.2.0",
"@eslint/eslintrc": "^1.2.1",
"@humanwhocodes/config-array": "^0.9.2",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
@@ -18672,9 +18657,9 @@
}
},
"eslint-plugin-react": {
"version": "7.29.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz",
"integrity": "sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg==",
"version": "7.29.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz",
"integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==",
"dev": true,
"requires": {
"array-includes": "^3.1.4",
@@ -24068,20 +24053,20 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"twemoji": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-13.1.0.tgz",
"integrity": "sha512-e3fZRl2S9UQQdBFLYXtTBT6o4vidJMnpWUAhJA+yLGR+kaUTZAt3PixC0cGvvxWSuq2MSz/o0rJraOXrWw/4Ew==",
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-14.0.1.tgz",
"integrity": "sha512-eoqhea0sUhmC10iTacksyp1v9O4BP1jKmVqtK+Nztw40/dzawSHkXL3/xCpyh+mukmEvJ0Gw9VLvwZfQ9HKXDw==",
"requires": {
"fs-extra": "^8.0.1",
"jsonfile": "^5.0.0",
"twemoji-parser": "13.1.0",
"twemoji-parser": "14.0.0",
"universalify": "^0.1.2"
}
},
"twemoji-parser": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-13.1.0.tgz",
"integrity": "sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg=="
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="
},
"type-check": {
"version": "0.4.0",

View File

@@ -1,6 +1,6 @@
{
"name": "cinny",
"version": "1.8.0",
"version": "1.8.2",
"description": "Yet another matrix client",
"main": "index.js",
"engines": {
@@ -15,7 +15,7 @@
"author": "Ajay Bura",
"license": "MIT",
"dependencies": {
"@fontsource/inter": "^4.5.4",
"@fontsource/inter": "^4.5.5",
"@fontsource/roboto": "^4.5.3",
"@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",
@@ -44,10 +44,10 @@
"react-modal": "^3.14.4",
"sanitize-html": "^2.7.0",
"tippy.js": "^6.3.7",
"twemoji": "^13.1.0"
"twemoji": "^14.0.1"
},
"devDependencies": {
"@babel/core": "^7.17.5",
"@babel/core": "^7.17.7",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"assert": "^2.0.0",
@@ -57,13 +57,13 @@
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.4",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"eslint": "^8.10.0",
"eslint": "^8.11.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.29.3",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.2.0",
"favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2",

View File

@@ -0,0 +1,16 @@
<?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>
<polygon fill="#010101" points="11,6 11,12 15.2,16.2 16.7,14.8 13,11.2 13,6 "/>
<path fill="#010101" d="M12,2C6.5,2,2,6.5,2,12H0.2L3,14.8L5.8,12H4c0-4.4,3.6-8,8-8s8,3.6,8,8s-3.6,8-8,8c-1.9,0-3.7-0.7-5-1.8
l-1.2,1.6C7.4,21.2,9.6,22,12,22c5.5,0,10-4.5,10-10S17.5,2,12,2z"/>
</g>
<g>
<polygon fill="#010101" points="49,44 49,50 53.2,54.2 54.7,52.8 51,49.2 51,44 "/>
<path fill="#010101" d="M50,40c-5.5,0-10,4.5-10,10h-1.8l2.8,2.8l2.8-2.8H42c0-4.4,3.6-8,8-8s8,3.6,8,8s-3.6,8-8,8
c-1.9,0-3.7-0.7-5-1.8l-1.2,1.6c1.7,1.4,3.9,2.2,6.3,2.2c5.5,0,10-4.5,10-10S55.5,40,50,40z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -150,10 +150,13 @@ const MessageReplyWrapper = React.memo(({ roomTimeline, eventId }) => {
};
}, []);
const focusReply = () => {
if (reply?.event === null) return;
if (reply?.event.isRedacted()) return;
roomTimeline.loadEventTimeline(eventId);
const focusReply = (ev) => {
if (!ev.keyCode || ev.keyCode === 32 || ev.keyCode === 13) {
if (ev.keyCode) ev.preventDefault();
if (reply?.event === null) return;
if (reply?.event.isRedacted()) return;
roomTimeline.loadEventTimeline(eventId);
}
};
return (
@@ -201,10 +204,10 @@ const MessageBody = React.memo(({
// Count the number of emojis
const nEmojis = content.filter((e) => e.type === 'img').length;
// Make sure there's no text besides whitespace
// Make sure there's no text besides whitespace and variation selector U+FE0F
if (nEmojis <= 10 && content.every((element) => (
(typeof element === 'object' && element.type === 'img')
|| (typeof element === 'string' && /^\s*$/g.test(element))
|| (typeof element === 'string' && /^[\s\ufe0f]*$/g.test(element))
))) {
emojiOnly = true;
}
@@ -466,6 +469,18 @@ function isMedia(mE) {
);
}
// if editedTimeline has mEventId then pass editedMEvent else pass mEvent to openViewSource
function handleOpenViewSource(mEvent, roomTimeline) {
const eventId = mEvent.getId();
const { editedTimeline } = roomTimeline ?? {};
let editedMEvent;
if (editedTimeline?.has(eventId)) {
const editedList = editedTimeline.get(eventId);
editedMEvent = editedList[editedList.length - 1];
}
openViewSource(editedMEvent !== undefined ? editedMEvent : mEvent);
}
const MessageOptions = React.memo(({
roomTimeline, mEvent, edit, reply,
}) => {
@@ -513,7 +528,7 @@ const MessageOptions = React.memo(({
</MenuItem>
<MenuItem
iconSrc={CmdIC}
onClick={() => openViewSource(mEvent)}
onClick={() => handleOpenViewSource(mEvent, roomTimeline)}
>
View source
</MenuItem>

View File

@@ -32,28 +32,9 @@ const items = [{
type: cons.notifs.MUTE,
}];
function getNotifType(roomId) {
const mx = initMatrix.matrixClient;
const pushRule = mx.getRoomPushRule('global', roomId);
if (typeof pushRule === 'undefined') {
const overridePushRules = mx.getAccountData('m.push_rules')?.getContent()?.global?.override;
if (typeof overridePushRules === 'undefined') return 0;
const isMuteOverride = overridePushRules.find((rule) => (
rule.rule_id === roomId
&& rule.actions[0] === 'dont_notify'
&& rule.conditions[0].kind === 'event_match'
));
return isMuteOverride ? cons.notifs.MUTE : cons.notifs.DEFAULT;
}
if (pushRule.actions[0] === 'notify') return cons.notifs.ALL_MESSAGES;
return cons.notifs.MENTIONS_AND_KEYWORDS;
}
function setRoomNotifType(roomId, newType) {
const mx = initMatrix.matrixClient;
const { notifications } = initMatrix;
const roomPushRule = mx.getRoomPushRule('global', roomId);
const promises = [];
@@ -76,7 +57,7 @@ function setRoomNotifType(roomId, newType) {
return promises;
}
const oldState = getNotifType(roomId);
const oldState = notifications.getNotiType(roomId);
if (oldState === cons.notifs.MUTE) {
promises.push(mx.deletePushRule('global', 'override', roomId));
}
@@ -115,8 +96,9 @@ function setRoomNotifType(roomId, newType) {
}
function useNotifications(roomId) {
const [activeType, setActiveType] = useState(getNotifType(roomId));
useEffect(() => setActiveType(getNotifType(roomId)), [roomId]);
const { notifications } = initMatrix;
const [activeType, setActiveType] = useState(notifications.getNotiType(roomId));
useEffect(() => setActiveType(notifications.getNotiType(roomId)), [roomId]);
const setNotification = useCallback((item) => {
if (item.type === activeType.type) return;

View File

@@ -11,13 +11,16 @@ import NotificationBadge from '../../atoms/badge/NotificationBadge';
import { blurOnBubbling } from '../../atoms/button/script';
function RoomSelectorWrapper({
isSelected, isUnread, onClick,
isSelected, isMuted, isUnread, onClick,
content, options, onContextMenu,
}) {
let myClass = isUnread ? ' room-selector--unread' : '';
myClass += isSelected ? ' room-selector--selected' : '';
const classes = ['room-selector'];
if (isMuted) classes.push('room-selector--muted');
if (isUnread) classes.push('room-selector--unread');
if (isSelected) classes.push('room-selector--selected');
return (
<div className={`room-selector${myClass}`}>
<div className={classes.join(' ')}>
<button
className="room-selector__content"
type="button"
@@ -32,11 +35,13 @@ function RoomSelectorWrapper({
);
}
RoomSelectorWrapper.defaultProps = {
isMuted: false,
options: null,
onContextMenu: null,
};
RoomSelectorWrapper.propTypes = {
isSelected: PropTypes.bool.isRequired,
isMuted: PropTypes.bool,
isUnread: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
content: PropTypes.node.isRequired,
@@ -46,12 +51,13 @@ RoomSelectorWrapper.propTypes = {
function RoomSelector({
name, parentName, roomId, imageSrc, iconSrc,
isSelected, isUnread, notificationCount, isAlert,
isSelected, isMuted, isUnread, notificationCount, isAlert,
options, onClick, onContextMenu,
}) {
return (
<RoomSelectorWrapper
isSelected={isSelected}
isMuted={isMuted}
isUnread={isUnread}
content={(
<>
@@ -91,6 +97,7 @@ RoomSelector.defaultProps = {
isSelected: false,
imageSrc: null,
iconSrc: null,
isMuted: false,
options: null,
onContextMenu: null,
};
@@ -101,6 +108,7 @@ RoomSelector.propTypes = {
imageSrc: PropTypes.string,
iconSrc: PropTypes.string,
isSelected: PropTypes.bool,
isMuted: PropTypes.bool,
isUnread: PropTypes.bool.isRequired,
notificationCount: PropTypes.oneOfType([
PropTypes.string,

View File

@@ -9,6 +9,10 @@
border-radius: var(--bo-radius);
cursor: pointer;
&--muted {
opacity: 0.6;
}
&--unread {
.room-selector__content > .text {
color: var(--tc-surface-high);

View File

@@ -33,9 +33,11 @@ function Directs() {
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
notifications.on(cons.events.notifications.MUTE_TOGGLED, notiChanged);
return () => {
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
notifications.removeListener(cons.events.notifications.MUTE_TOGGLED, notiChanged);
};
}, []);

View File

@@ -72,14 +72,22 @@ function DrawerBreadcrumb({ spaceId }) {
const noti = notifications.getNoti(roomId);
if (!notifications.hasNoti(childId)) return noti;
if (noti.from === null) return noti;
if (noti.from.has(childId) && noti.from.size === 1) return null;
const childNoti = notifications.getNoti(childId);
return {
total: noti.total - childNoti.total,
highlight: noti.highlight - childNoti.highlight,
};
let noOther = true;
let total = 0;
let highlight = 0;
noti.from.forEach((fromId) => {
if (childNoti.from.has(fromId)) return;
noOther = false;
const fromNoti = notifications.getNoti(fromId);
total += fromNoti.total;
highlight += fromNoti.highlight;
});
if (noOther) return null;
return { total, highlight };
}
return (

View File

@@ -15,10 +15,8 @@ const drawerPostie = new Postie();
function Home({ spaceId }) {
const mx = initMatrix.matrixClient;
const { roomList, notifications, accountData } = initMatrix;
const {
spaces, rooms, directs, roomIdToParents,
} = roomList;
const categorizedSpaces = useCategorizedSpaces();
const { spaces, rooms, directs } = roomList;
useCategorizedSpaces();
const isCategorized = accountData.categorizedSpaces.has(spaceId);
let categories = null;
@@ -26,14 +24,14 @@ function Home({ spaceId }) {
let roomIds = [];
let directIds = [];
const spaceChildIds = roomList.getSpaceChildren(spaceId);
if (spaceChildIds) {
if (spaceId) {
const spaceChildIds = roomList.getSpaceChildren(spaceId);
spaceIds = spaceChildIds.filter((roomId) => spaces.has(roomId));
roomIds = spaceChildIds.filter((roomId) => rooms.has(roomId));
directIds = spaceChildIds.filter((roomId) => directs.has(roomId));
} else {
spaceIds = [...spaces].filter((roomId) => !roomIdToParents.has(roomId));
roomIds = [...rooms].filter((roomId) => !roomIdToParents.has(roomId));
spaceIds = roomList.getOrphanSpaces();
roomIds = roomList.getOrphanRooms();
}
spaceIds.sort(AtoZ);
@@ -42,6 +40,7 @@ function Home({ spaceId }) {
if (isCategorized) {
categories = roomList.getCategorizedSpaces(spaceIds);
categories.delete(spaceId);
}
useEffect(() => {
@@ -63,9 +62,11 @@ function Home({ spaceId }) {
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
notifications.on(cons.events.notifications.MUTE_TOGGLED, notiChanged);
return () => {
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
notifications.removeListener(cons.events.notifications.MUTE_TOGGLED, notiChanged);
};
}, []);

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import './RoomsCategory.scss';
import initMatrix from '../../../client/initMatrix';
import { selectSpace, selectRoom,openReusableContextMenu } from '../../../client/action/navigation';
import { selectSpace, selectRoom, openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
import Text from '../../atoms/text/Text';

View File

@@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords, abbreviateNumber } from '../../../util/common';
@@ -23,9 +24,12 @@ function Selector({
const mx = initMatrix.matrixClient;
const noti = initMatrix.notifications;
const room = mx.getRoom(roomId);
let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
const isMuted = noti.getNotiType(roomId) === cons.notifs.MUTE;
const [, forceUpdate] = useForceUpdate();
useEffect(() => {
@@ -56,7 +60,8 @@ function Selector({
imageSrc={isDM ? imageSrc : null}
iconSrc={isDM ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())}
isSelected={navigation.selectedRoomId === roomId}
isUnread={noti.hasNoti(roomId)}
isMuted={isMuted}
isUnread={!isMuted && noti.hasNoti(roomId)}
notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))}
isAlert={noti.getHighlightNoti(roomId) !== 0}
onClick={onClick}

View File

@@ -0,0 +1,35 @@
class EventLimit {
constructor() {
this._from = 0;
this.SMALLEST_EVT_HEIGHT = 32;
this.PAGES_COUNT = 4;
}
get maxEvents() {
return Math.round(document.body.clientHeight / this.SMALLEST_EVT_HEIGHT) * this.PAGES_COUNT;
}
get from() {
return this._from;
}
get length() {
return this._from + this.maxEvents;
}
setFrom(from) {
this._from = from < 0 ? 0 : from;
}
paginate(backwards, limit, timelineLength) {
this._from = backwards ? this._from - limit : this._from + limit;
if (!backwards && this.length > timelineLength) {
this._from = timelineLength - this.maxEvents;
}
if (this._from < 0) this._from = 0;
}
}
export default EventLimit;

View File

@@ -13,33 +13,46 @@ import RoomSettings from './RoomSettings';
import PeopleDrawer from './PeopleDrawer';
function Room() {
const [roomTimeline, setRoomTimeline] = useState(null);
const [eventId, setEventId] = useState(null);
const [roomInfo, setRoomInfo] = useState({
roomTimeline: null,
eventId: null,
});
const [isDrawer, setIsDrawer] = useState(settings.isPeopleDrawer);
const mx = initMatrix.matrixClient;
const handleRoomSelected = (rId, pRoomId, eId) => {
if (mx.getRoom(rId)) {
setRoomTimeline(new RoomTimeline(rId, initMatrix.notifications));
setEventId(eId);
} else {
// TODO: add ability to join room if roomId is invalid
setRoomTimeline(null);
setEventId(null);
}
};
const handleDrawerToggling = (visiblity) => setIsDrawer(visiblity);
useEffect(() => {
const handleRoomSelected = (rId, pRoomId, eId) => {
roomInfo.roomTimeline?.removeInternalListeners();
if (mx.getRoom(rId)) {
setRoomInfo({
roomTimeline: new RoomTimeline(rId, initMatrix.notifications),
eventId: eId ?? null,
});
} else {
// TODO: add ability to join room if roomId is invalid
setRoomInfo({
roomTimeline: null,
eventId: null,
});
}
};
navigation.on(cons.events.navigation.ROOM_SELECTED, handleRoomSelected);
settings.on(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
return () => {
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, handleRoomSelected);
};
}, [roomInfo]);
useEffect(() => {
const handleDrawerToggling = (visiblity) => setIsDrawer(visiblity);
settings.on(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
return () => {
settings.removeListener(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
roomTimeline?.removeInternalListeners();
};
}, []);
const { roomTimeline, eventId } = roomInfo;
if (roomTimeline === null) return <Welcome />;
return (

View File

@@ -7,16 +7,13 @@ import React, {
import PropTypes from 'prop-types';
import './RoomViewContent.scss';
import EventEmitter from 'events';
import dateFormat from 'dateformat';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation';
import { openProfileViewer } from '../../../client/action/navigation';
import {
diffMinutes, isInSameDay, Throttle, getScrollInfo,
} from '../../../util/common';
import { diffMinutes, isInSameDay, Throttle } from '../../../util/common';
import Divider from '../../atoms/divider/Divider';
import ScrollView from '../../atoms/scroll/ScrollView';
@@ -27,17 +24,15 @@ import TimelineChange from '../../molecules/message/TimelineChange';
import { useStore } from '../../hooks/useStore';
import { useForceUpdate } from '../../hooks/useForceUpdate';
import { parseTimelineChange } from './common';
import TimelineScroll from './TimelineScroll';
import EventLimit from './EventLimit';
const DEFAULT_MAX_EVENTS = 50;
const PAG_LIMIT = 30;
const MAX_MSG_DIFF_MINUTES = 5;
const PLACEHOLDER_COUNT = 2;
const PLACEHOLDERS_HEIGHT = 96 * PLACEHOLDER_COUNT;
const SCROLL_TRIGGER_POS = PLACEHOLDERS_HEIGHT * 4;
const SMALLEST_MSG_HEIGHT = 32;
const PAGES_COUNT = 4;
function loadingMsgPlaceholders(key, count = 2) {
const pl = [];
const genPlaceholders = () => {
@@ -124,178 +119,7 @@ function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
);
}
class TimelineScroll extends EventEmitter {
constructor(target) {
super();
if (target === null) {
throw new Error('Can not initialize TimelineScroll, target HTMLElement in null');
}
this.scroll = target;
this.backwards = false;
this.inTopHalf = false;
this.maxEvents = DEFAULT_MAX_EVENTS;
this.isScrollable = false;
this.top = 0;
this.bottom = 0;
this.height = 0;
this.viewHeight = 0;
this.topMsg = null;
this.bottomMsg = null;
this.diff = 0;
}
scrollToBottom() {
const scrollInfo = getScrollInfo(this.scroll);
const maxScrollTop = scrollInfo.height - scrollInfo.viewHeight;
this._scrollTo(scrollInfo, maxScrollTop);
}
// restore scroll using previous calc by this._updateTopBottomMsg() and this._calcDiff.
tryRestoringScroll() {
const scrollInfo = getScrollInfo(this.scroll);
let scrollTop = 0;
const ot = this.inTopHalf ? this.topMsg?.offsetTop : this.bottomMsg?.offsetTop;
if (!ot) scrollTop = Math.round(this.height - this.viewHeight);
else scrollTop = ot - this.diff;
this._scrollTo(scrollInfo, scrollTop);
}
scrollToIndex(index, offset = 0) {
const scrollInfo = getScrollInfo(this.scroll);
const msgs = this.scroll.lastElementChild.lastElementChild.children;
const offsetTop = msgs[index]?.offsetTop;
if (offsetTop === undefined) return;
// if msg is already in visible are we don't need to scroll to that
if (offsetTop > scrollInfo.top && offsetTop < (scrollInfo.top + scrollInfo.viewHeight)) return;
const to = offsetTop - offset;
this._scrollTo(scrollInfo, to);
}
_scrollTo(scrollInfo, scrollTop) {
this.scroll.scrollTop = scrollTop;
// browser emit 'onscroll' event only if the 'element.scrollTop' value changes.
// so here we flag that the upcoming 'onscroll' event is
// emitted as side effect of assigning 'this.scroll.scrollTop' above
// only if it's changes.
// by doing so we prevent this._updateCalc() from calc again.
if (scrollTop !== this.top) {
this.scrolledByCode = true;
}
const sInfo = { ...scrollInfo };
const maxScrollTop = scrollInfo.height - scrollInfo.viewHeight;
sInfo.top = (scrollTop > maxScrollTop) ? maxScrollTop : scrollTop;
this._updateCalc(sInfo);
}
// we maintain reference of top and bottom messages
// to restore the scroll position when
// messages gets removed from either end and added to other.
_updateTopBottomMsg() {
const msgs = this.scroll.lastElementChild.lastElementChild.children;
const lMsgIndex = msgs.length - 1;
this.topMsg = msgs[0]?.className === 'ph-msg'
? msgs[PLACEHOLDER_COUNT]
: msgs[0];
this.bottomMsg = msgs[lMsgIndex]?.className === 'ph-msg'
? msgs[lMsgIndex - PLACEHOLDER_COUNT]
: msgs[lMsgIndex];
}
// we calculate the difference between first/last message and current scrollTop.
// if we are going above we calc diff between first and scrollTop
// else otherwise.
// NOTE: This will help to restore the scroll when msgs get's removed
// from one end and added to other end
_calcDiff(scrollInfo) {
if (!this.topMsg || !this.bottomMsg) return 0;
if (this.inTopHalf) {
return this.topMsg.offsetTop - scrollInfo.top;
}
return this.bottomMsg.offsetTop - scrollInfo.top;
}
// eslint-disable-next-line class-methods-use-this
_calcMaxEvents(scrollInfo) {
return Math.round(scrollInfo.viewHeight / SMALLEST_MSG_HEIGHT) * PAGES_COUNT;
}
_updateCalc(scrollInfo) {
const halfViewHeight = Math.round(scrollInfo.viewHeight / 2);
const scrollMiddle = scrollInfo.top + halfViewHeight;
const lastMiddle = this.top + halfViewHeight;
this.backwards = scrollMiddle < lastMiddle;
this.inTopHalf = scrollMiddle < scrollInfo.height / 2;
this.isScrollable = scrollInfo.isScrollable;
this.top = scrollInfo.top;
this.bottom = scrollInfo.height - (scrollInfo.top + scrollInfo.viewHeight);
this.height = scrollInfo.height;
// only calculate maxEvents if viewHeight change
if (this.viewHeight !== scrollInfo.viewHeight) {
this.maxEvents = this._calcMaxEvents(scrollInfo);
this.viewHeight = scrollInfo.viewHeight;
}
this._updateTopBottomMsg();
this.diff = this._calcDiff(scrollInfo);
}
calcScroll() {
if (this.scrolledByCode) {
this.scrolledByCode = false;
return;
}
const scrollInfo = getScrollInfo(this.scroll);
this._updateCalc(scrollInfo);
this.emit('scroll', this.backwards);
}
}
let timelineScroll = null;
let jumpToItemIndex = -1;
const throttle = new Throttle();
const limit = {
from: 0,
getMaxEvents() {
return timelineScroll?.maxEvents ?? DEFAULT_MAX_EVENTS;
},
getEndIndex() {
return this.from + this.getMaxEvents();
},
calcNextFrom(backwards, tLength) {
let newFrom = backwards ? this.from - PAG_LIMIT : this.from + PAG_LIMIT;
if (!backwards && newFrom + this.getMaxEvents() > tLength) {
newFrom = tLength - this.getMaxEvents();
}
if (newFrom < 0) newFrom = 0;
return newFrom;
},
setFrom(from) {
if (from < 0) {
this.from = 0;
return;
}
this.from = from;
},
};
function useTimeline(roomTimeline, eventId, readEventStore) {
function useTimeline(roomTimeline, eventId, readUptoEvtStore, eventLimitRef) {
const [timelineInfo, setTimelineInfo] = useState(null);
const setEventTimeline = async (eId) => {
@@ -309,6 +133,7 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
};
useEffect(() => {
const limit = eventLimitRef.current;
const initTimeline = (eId) => {
// NOTICE: eId can be id of readUpto, reply or specific event.
// readUpTo: when user click jump to unread message button.
@@ -320,20 +145,19 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
if (isSpecificEvent) {
focusEventIndex = roomTimeline.getEventIndex(eId);
} else if (!readEventStore.getItem()) {
}
if (!readUptoEvtStore.getItem() && roomTimeline.hasEventInTimeline(readUpToId)) {
// either opening live timeline or jump to unread.
focusEventIndex = roomTimeline.getUnreadEventIndex(readUpToId);
if (roomTimeline.hasEventInTimeline(readUpToId)) {
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
}
} else {
focusEventIndex = roomTimeline.getUnreadEventIndex(readEventStore.getItem().getId());
readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
}
if (readUptoEvtStore.getItem() && !isSpecificEvent) {
focusEventIndex = roomTimeline.getUnreadEventIndex(readUptoEvtStore.getItem().getId());
}
if (focusEventIndex > -1) {
limit.setFrom(focusEventIndex - Math.round(limit.getMaxEvents() / 2));
limit.setFrom(focusEventIndex - Math.round(limit.maxEvents / 2));
} else {
limit.setFrom(roomTimeline.timeline.length - limit.getMaxEvents());
limit.setFrom(roomTimeline.timeline.length - limit.maxEvents);
}
setTimelineInfo({ focusEventId: isSpecificEvent ? eId : null });
};
@@ -342,7 +166,6 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
setEventTimeline(eventId);
return () => {
roomTimeline.removeListener(cons.events.roomTimeline.READY, initTimeline);
roomTimeline.removeInternalListeners();
limit.setFrom(0);
};
}, [roomTimeline, eventId]);
@@ -350,36 +173,45 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
return timelineInfo;
}
function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) {
function usePaginate(
roomTimeline,
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
) {
const [info, setInfo] = useState(null);
useEffect(() => {
const handleOnPagination = (backwards, loaded) => {
const handlePaginatedFromServer = (backwards, loaded) => {
const limit = eventLimitRef.current;
if (loaded === 0) return;
if (!readEventStore.getItem()) {
if (!readUptoEvtStore.getItem()) {
const readUpToId = roomTimeline.getReadUpToEventId();
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
}
limit.setFrom(limit.calcNextFrom(backwards, roomTimeline.timeline.length));
limit.paginate(backwards, PAG_LIMIT, roomTimeline.timeline.length);
setTimeout(() => setInfo({
backwards,
loaded,
}));
};
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handleOnPagination);
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer);
return () => {
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handleOnPagination);
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer);
};
}, [roomTimeline]);
const autoPaginate = useCallback(async () => {
const timelineScroll = timelineScrollRef.current;
const limit = eventLimitRef.current;
if (roomTimeline.isOngoingPagination) return;
const tLength = roomTimeline.timeline.length;
if (timelineScroll.bottom < SCROLL_TRIGGER_POS) {
if (limit.getEndIndex() < tLength) {
if (limit.length < tLength) {
// paginate from memory
limit.setFrom(limit.calcNextFrom(false, tLength));
limit.paginate(false, PAG_LIMIT, tLength);
forceUpdateLimit();
} else if (roomTimeline.canPaginateForward()) {
// paginate from server.
@@ -390,7 +222,7 @@ function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) {
if (timelineScroll.top < SCROLL_TRIGGER_POS) {
if (limit.from > 0) {
// paginate from memory
limit.setFrom(limit.calcNextFrom(true, tLength));
limit.paginate(true, PAG_LIMIT, tLength);
forceUpdateLimit();
} else if (roomTimeline.canPaginateBackward()) {
// paginate from server.
@@ -402,16 +234,25 @@ function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) {
return [info, autoPaginate];
}
function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdateLimit) {
function useHandleScroll(
roomTimeline,
autoPaginate,
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
) {
const handleScroll = useCallback(() => {
const timelineScroll = timelineScrollRef.current;
const limit = eventLimitRef.current;
requestAnimationFrame(() => {
// emit event to toggle scrollToBottom button visibility
const isAtBottom = (
timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()
&& limit.getEndIndex() >= roomTimeline.timeline.length
&& limit.length >= roomTimeline.timeline.length
);
roomTimeline.emit(cons.events.roomTimeline.AT_BOTTOM, isAtBottom);
if (isAtBottom && readEventStore.getItem()) {
if (isAtBottom && readUptoEvtStore.getItem()) {
requestAnimationFrame(() => roomTimeline.markAllAsRead());
}
});
@@ -419,11 +260,13 @@ function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdate
}, [roomTimeline]);
const handleScrollToLive = useCallback(() => {
if (readEventStore.getItem()) {
const timelineScroll = timelineScrollRef.current;
const limit = eventLimitRef.current;
if (readUptoEvtStore.getItem()) {
requestAnimationFrame(() => roomTimeline.markAllAsRead());
}
if (roomTimeline.isServingLiveTimeline()) {
limit.setFrom(roomTimeline.timeline.length - limit.getMaxEvents());
limit.setFrom(roomTimeline.timeline.length - limit.maxEvents);
timelineScroll.scrollToBottom();
forceUpdateLimit();
return;
@@ -434,48 +277,46 @@ function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdate
return [handleScroll, handleScrollToLive];
}
function useEventArrive(roomTimeline, readEventStore) {
function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, eventLimitRef) {
const myUserId = initMatrix.matrixClient.getUserId();
const [newEvent, setEvent] = useState(null);
useEffect(() => {
const sendReadReceipt = (event) => {
if (event.isSending()) return;
const timelineScroll = timelineScrollRef.current;
const limit = eventLimitRef.current;
const trySendReadReceipt = (event) => {
if (myUserId === event.getSender()) {
roomTimeline.markAllAsRead();
requestAnimationFrame(() => roomTimeline.markAllAsRead());
return;
}
const readUpToEvent = readEventStore.getItem();
const readUpToEvent = readUptoEvtStore.getItem();
const readUpToId = roomTimeline.getReadUpToEventId();
const isUnread = readUpToEvent ? readUpToEvent?.getId() === readUpToId : true;
// if user doesn't have focus on app don't mark messages as read.
if (document.visibilityState === 'hidden' || timelineScroll.bottom >= 16) {
if (readUpToEvent === readUpToId) return;
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
if (isUnread === false) {
if (document.visibilityState === 'visible' && timelineScroll.bottom < 16) {
requestAnimationFrame(() => roomTimeline.markAllAsRead());
} else {
readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
}
return;
}
// user has not mark room as read
const isUnreadMsg = readUpToEvent?.getId() === readUpToId;
if (!isUnreadMsg) {
roomTimeline.markAllAsRead();
}
const { timeline } = roomTimeline;
const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToEvent?.getId();
const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToId;
if (unreadMsgIsLast) {
roomTimeline.markAllAsRead();
requestAnimationFrame(() => roomTimeline.markAllAsRead());
}
};
const handleEvent = (event) => {
const tLength = roomTimeline.timeline.length;
const isUserViewingLive = (
roomTimeline.isServingLiveTimeline()
&& limit.getEndIndex() >= tLength - 1
&& timelineScroll.bottom < SCROLL_TRIGGER_POS
);
if (isUserViewingLive) {
limit.setFrom(tLength - limit.getMaxEvents());
sendReadReceipt(event);
const isViewingLive = roomTimeline.isServingLiveTimeline() && limit.length >= tLength - 1;
const isAttached = timelineScroll.bottom < SCROLL_TRIGGER_POS;
if (isViewingLive && isAttached) {
limit.setFrom(tLength - limit.maxEvents);
trySendReadReceipt(event);
setEvent(event);
return;
}
@@ -484,11 +325,8 @@ function useEventArrive(roomTimeline, readEventStore) {
setEvent(event);
return;
}
const isUserDitchedLive = (
roomTimeline.isServingLiveTimeline()
&& limit.getEndIndex() >= tLength - 1
);
if (isUserDitchedLive) {
if (isViewingLive) {
// This stateUpdate will help to put the
// loading msg placeholder at bottom
setEvent(event);
@@ -505,39 +343,52 @@ function useEventArrive(roomTimeline, readEventStore) {
};
}, [roomTimeline]);
useEffect(() => {
if (!roomTimeline.initialized) return;
if (timelineScroll.bottom < 16
&& !roomTimeline.canPaginateForward()
&& document.visibilityState === 'visible') {
timelineScroll.scrollToBottom();
} else {
timelineScroll.tryRestoringScroll();
}
}, [newEvent, roomTimeline]);
return newEvent;
}
let jumpToItemIndex = -1;
function RoomViewContent({ eventId, roomTimeline }) {
const [throttle] = useState(new Throttle());
const timelineSVRef = useRef(null);
const readEventStore = useStore(roomTimeline);
const timelineInfo = useTimeline(roomTimeline, eventId, readEventStore);
const timelineScrollRef = useRef(null);
const eventLimitRef = useRef(null);
const readUptoEvtStore = useStore(roomTimeline);
const [onLimitUpdate, forceUpdateLimit] = useForceUpdate();
const [paginateInfo, autoPaginate] = usePaginate(roomTimeline, readEventStore, forceUpdateLimit);
const [handleScroll, handleScrollToLive] = useHandleScroll(
roomTimeline, autoPaginate, readEventStore, forceUpdateLimit,
const timelineInfo = useTimeline(roomTimeline, eventId, readUptoEvtStore, eventLimitRef);
const [paginateInfo, autoPaginate] = usePaginate(
roomTimeline,
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
);
useEventArrive(roomTimeline, readEventStore);
const [handleScroll, handleScrollToLive] = useHandleScroll(
roomTimeline,
autoPaginate,
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
);
const newEvent = useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, eventLimitRef);
const { timeline } = roomTimeline;
useLayoutEffect(() => {
if (!roomTimeline.initialized) {
timelineScroll = new TimelineScroll(timelineSVRef.current);
timelineScrollRef.current = new TimelineScroll(timelineSVRef.current);
eventLimitRef.current = new EventLimit();
}
});
// when active timeline changes
useEffect(() => {
if (!roomTimeline.initialized) return undefined;
const timelineScroll = timelineScrollRef.current;
if (timeline.length > 0) {
if (jumpToItemIndex === -1) {
@@ -547,7 +398,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
}
if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()) {
const readUpToId = roomTimeline.getReadUpToEventId();
if (readEventStore.getItem()?.getId() === readUpToId || readUpToId === null) {
if (readUptoEvtStore.getItem()?.getId() === readUpToId || readUpToId === null) {
requestAnimationFrame(() => roomTimeline.markAllAsRead());
}
}
@@ -555,11 +406,9 @@ function RoomViewContent({ eventId, roomTimeline }) {
}
autoPaginate();
timelineScroll.on('scroll', handleScroll);
roomTimeline.on(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive);
return () => {
if (timelineSVRef.current === null) return;
timelineScroll.removeListener('scroll', handleScroll);
roomTimeline.removeListener(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive);
};
}, [timelineInfo]);
@@ -567,6 +416,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
// when paginating from server
useEffect(() => {
if (!roomTimeline.initialized) return;
const timelineScroll = timelineScrollRef.current;
timelineScroll.tryRestoringScroll();
autoPaginate();
}, [paginateInfo]);
@@ -574,28 +424,45 @@ function RoomViewContent({ eventId, roomTimeline }) {
// when paginating locally
useEffect(() => {
if (!roomTimeline.initialized) return;
const timelineScroll = timelineScrollRef.current;
timelineScroll.tryRestoringScroll();
}, [onLimitUpdate]);
useEffect(() => {
const timelineScroll = timelineScrollRef.current;
if (!roomTimeline.initialized) return;
if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward() && document.visibilityState === 'visible') {
timelineScroll.scrollToBottom();
} else {
timelineScroll.tryRestoringScroll();
}
}, [newEvent]);
const handleTimelineScroll = (event) => {
const { target } = event;
if (!target) return;
throttle._(() => timelineScroll?.calcScroll(), 400)(target);
const timelineScroll = timelineScrollRef.current;
if (!event.target) return;
throttle._(() => {
const backwards = timelineScroll?.calcScroll();
if (typeof backwards !== 'boolean') return;
handleScroll(backwards);
}, 200)();
};
const renderTimeline = () => {
const tl = [];
const limit = eventLimitRef.current;
let itemCountIndex = 0;
jumpToItemIndex = -1;
const readEvent = readEventStore.getItem();
const readUptoEvent = readUptoEvtStore.getItem();
let unreadDivider = false;
if (roomTimeline.canPaginateBackward() || limit.from > 0) {
tl.push(loadingMsgPlaceholders(1, PLACEHOLDER_COUNT));
itemCountIndex += PLACEHOLDER_COUNT;
}
for (let i = limit.from; i < limit.getEndIndex(); i += 1) {
for (let i = limit.from; i < limit.length; i += 1) {
if (i >= timeline.length) break;
const mEvent = timeline[i];
const prevMEvent = timeline[i - 1] ?? null;
@@ -614,9 +481,9 @@ function RoomViewContent({ eventId, roomTimeline }) {
let isNewEvent = false;
if (!unreadDivider) {
unreadDivider = (readEvent
&& prevMEvent?.getTs() <= readEvent.getTs()
&& readEvent.getTs() < mEvent.getTs());
unreadDivider = (readUptoEvent
&& prevMEvent?.getTs() <= readUptoEvent.getTs()
&& readUptoEvent.getTs() < mEvent.getTs());
if (unreadDivider) {
isNewEvent = true;
tl.push(<Divider key={`new-${mEvent.getId()}`} variant="positive" text="New messages" />);
@@ -637,7 +504,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
tl.push(renderEvent(roomTimeline, mEvent, isNewEvent ? null : prevMEvent, isFocus));
itemCountIndex += 1;
}
if (roomTimeline.canPaginateForward() || limit.getEndIndex() < timeline.length) {
if (roomTimeline.canPaginateForward() || limit.length < timeline.length) {
tl.push(loadingMsgPlaceholders(2, PLACEHOLDER_COUNT));
}

View File

@@ -0,0 +1,136 @@
import { getScrollInfo } from '../../../util/common';
class TimelineScroll {
constructor(target) {
if (target === null) {
throw new Error('Can not initialize TimelineScroll, target HTMLElement in null');
}
this.scroll = target;
this.backwards = false;
this.inTopHalf = false;
this.isScrollable = false;
this.top = 0;
this.bottom = 0;
this.height = 0;
this.viewHeight = 0;
this.topMsg = null;
this.bottomMsg = null;
this.diff = 0;
}
scrollToBottom() {
const scrollInfo = getScrollInfo(this.scroll);
const maxScrollTop = scrollInfo.height - scrollInfo.viewHeight;
this._scrollTo(scrollInfo, maxScrollTop);
}
// use previous calc by this._updateTopBottomMsg() & this._calcDiff.
tryRestoringScroll() {
const scrollInfo = getScrollInfo(this.scroll);
let scrollTop = 0;
const ot = this.inTopHalf ? this.topMsg?.offsetTop : this.bottomMsg?.offsetTop;
if (!ot) scrollTop = Math.round(this.height - this.viewHeight);
else scrollTop = ot - this.diff;
this._scrollTo(scrollInfo, scrollTop);
}
scrollToIndex(index, offset = 0) {
const scrollInfo = getScrollInfo(this.scroll);
const msgs = this.scroll.lastElementChild.lastElementChild.children;
const offsetTop = msgs[index]?.offsetTop;
if (offsetTop === undefined) return;
// if msg is already in visible are we don't need to scroll to that
if (offsetTop > scrollInfo.top && offsetTop < (scrollInfo.top + scrollInfo.viewHeight)) return;
const to = offsetTop - offset;
this._scrollTo(scrollInfo, to);
}
_scrollTo(scrollInfo, scrollTop) {
this.scroll.scrollTop = scrollTop;
// browser emit 'onscroll' event only if the 'element.scrollTop' value changes.
// so here we flag that the upcoming 'onscroll' event is
// emitted as side effect of assigning 'this.scroll.scrollTop' above
// only if it's changes.
// by doing so we prevent this._updateCalc() from calc again.
if (scrollTop !== this.top) {
this.scrolledByCode = true;
}
const sInfo = { ...scrollInfo };
const maxScrollTop = scrollInfo.height - scrollInfo.viewHeight;
sInfo.top = (scrollTop > maxScrollTop) ? maxScrollTop : scrollTop;
this._updateCalc(sInfo);
}
// we maintain reference of top and bottom messages
// to restore the scroll position when
// messages gets removed from either end and added to other.
_updateTopBottomMsg() {
const msgs = this.scroll.lastElementChild.lastElementChild.children;
const lMsgIndex = msgs.length - 1;
// TODO: classname 'ph-msg' prevent this class from being used
const PLACEHOLDER_COUNT = 2;
this.topMsg = msgs[0]?.className === 'ph-msg'
? msgs[PLACEHOLDER_COUNT]
: msgs[0];
this.bottomMsg = msgs[lMsgIndex]?.className === 'ph-msg'
? msgs[lMsgIndex - PLACEHOLDER_COUNT]
: msgs[lMsgIndex];
}
// we calculate the difference between first/last message and current scrollTop.
// if we are going above we calc diff between first and scrollTop
// else otherwise.
// NOTE: This will help to restore the scroll when msgs get's removed
// from one end and added to other end
_calcDiff(scrollInfo) {
if (!this.topMsg || !this.bottomMsg) return 0;
if (this.inTopHalf) {
return this.topMsg.offsetTop - scrollInfo.top;
}
return this.bottomMsg.offsetTop - scrollInfo.top;
}
_updateCalc(scrollInfo) {
const halfViewHeight = Math.round(scrollInfo.viewHeight / 2);
const scrollMiddle = scrollInfo.top + halfViewHeight;
const lastMiddle = this.top + halfViewHeight;
this.backwards = scrollMiddle < lastMiddle;
this.inTopHalf = scrollMiddle < scrollInfo.height / 2;
this.isScrollable = scrollInfo.isScrollable;
this.top = scrollInfo.top;
this.bottom = scrollInfo.height - (scrollInfo.top + scrollInfo.viewHeight);
this.height = scrollInfo.height;
this.viewHeight = scrollInfo.viewHeight;
this._updateTopBottomMsg();
this.diff = this._calcDiff(scrollInfo);
}
calcScroll() {
if (this.scrolledByCode) {
this.scrolledByCode = false;
return undefined;
}
const scrollInfo = getScrollInfo(this.scroll);
this._updateCalc(scrollInfo);
return this.backwards;
}
}
export default TimelineScroll;

View File

@@ -2,8 +2,8 @@ import { openSearch, toggleRoomSettings } from '../action/navigation';
import navigation from '../state/navigation';
function listenKeyboard(event) {
// Ctrl +
if (event.ctrlKey) {
// Ctrl/Cmd +
if (event.ctrlKey || event.metaKey) {
// k - for search Modal
if (event.keyCode === 75) {
event.preventDefault();

View File

@@ -17,6 +17,17 @@ function isNotifEvent(mEvent) {
return true;
}
function isMutedRule(rule) {
return rule.actions[0] === 'dont_notify' && rule.conditions[0].kind === 'event_match';
}
function findMutedRule(overrideRules, roomId) {
return overrideRules.find((rule) => (
rule.rule_id === roomId
&& isMutedRule(rule)
));
}
class Notifications extends EventEmitter {
constructor(roomList) {
super();
@@ -39,11 +50,12 @@ class Notifications extends EventEmitter {
_initNoti() {
const addNoti = (roomId) => {
const room = this.matrixClient.getRoom(roomId);
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) return;
if (this.doesRoomHaveUnread(room) === false) return;
const total = room.getUnreadNotificationCount('total');
const highlight = room.getUnreadNotificationCount('highlight');
const noti = this.getNoti(room.roomId);
this._setNoti(room.roomId, total - noti.total, highlight - noti.highlight);
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
};
[...this.roomList.rooms].forEach(addNoti);
[...this.roomList.directs].forEach(addNoti);
@@ -66,6 +78,22 @@ class Notifications extends EventEmitter {
return true;
}
getNotiType(roomId) {
const mx = this.matrixClient;
const pushRule = mx.getRoomPushRule('global', roomId);
if (pushRule === undefined) {
const overrideRules = mx.getAccountData('m.push_rules')?.getContent()?.global?.override;
if (overrideRules === undefined) return cons.notifs.DEFAULT;
const isMuted = findMutedRule(overrideRules, roomId);
return isMuted ? cons.notifs.MUTE : cons.notifs.DEFAULT;
}
if (pushRule.actions[0] === 'notify') return cons.notifs.ALL_MESSAGES;
return cons.notifs.MENTIONS_AND_KEYWORDS;
}
getNoti(roomId) {
return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null };
}
@@ -96,74 +124,64 @@ class Notifications extends EventEmitter {
}
}
_getAllParentIds(roomId) {
let allParentIds = this.roomList.roomIdToParents.get(roomId);
if (allParentIds === undefined) return new Set();
const parentIds = [...allParentIds];
_setNoti(roomId, total, highlight) {
const addNoti = (id, t, h, fromId) => {
const prevTotal = this.roomIdToNoti.get(id)?.total ?? null;
const noti = this.getNoti(id);
parentIds.forEach((pId) => {
allParentIds = new Set(
[...allParentIds, ...this._getAllParentIds(pId)],
);
noti.total += t;
noti.highlight += h;
if (fromId) {
if (noti.from === null) noti.from = new Set();
noti.from.add(fromId);
}
this.roomIdToNoti.set(id, noti);
this.emit(cons.events.notifications.NOTI_CHANGED, id, noti.total, prevTotal);
};
const noti = this.getNoti(roomId);
const addT = total - noti.total;
const addH = highlight - noti.highlight;
if (addT < 0 || addH < 0) return;
addNoti(roomId, addT, addH);
const allParentSpaces = this.roomList.getAllParentSpaces(roomId);
allParentSpaces.forEach((spaceId) => {
addNoti(spaceId, addT, addH, roomId);
});
return allParentIds;
}
_setNoti(roomId, total, highlight, childId) {
const prevTotal = this.roomIdToNoti.get(roomId)?.total ?? null;
const noti = this.getNoti(roomId);
_deleteNoti(roomId, total, highlight) {
const removeNoti = (id, t, h, fromId) => {
if (this.roomIdToNoti.has(id) === false) return;
if (!childId || this._remainingParentIds?.has(roomId)) {
noti.total += total;
noti.highlight += highlight;
}
if (childId) {
if (noti.from === null) noti.from = new Set();
noti.from.add(childId);
}
const noti = this.getNoti(id);
const prevTotal = noti.total;
noti.total -= t;
noti.highlight -= h;
if (noti.total < 0) {
noti.total = 0;
noti.highlight = 0;
}
if (fromId && noti.from !== null) {
if (!this.hasNoti(fromId)) noti.from.delete(fromId);
}
if (noti.from === null || noti.from.size === 0) {
this.roomIdToNoti.delete(id);
this.emit(cons.events.notifications.FULL_READ, id);
this.emit(cons.events.notifications.NOTI_CHANGED, id, null, prevTotal);
} else {
this.roomIdToNoti.set(id, noti);
this.emit(cons.events.notifications.NOTI_CHANGED, id, noti.total, prevTotal);
}
};
this.roomIdToNoti.set(roomId, noti);
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, noti.total, prevTotal);
if (!childId) this._remainingParentIds = this._getAllParentIds(roomId);
else this._remainingParentIds.delete(roomId);
const parentIds = this.roomList.roomIdToParents.get(roomId);
if (typeof parentIds === 'undefined') {
if (!childId) this._remainingParentIds = undefined;
return;
}
[...parentIds].forEach((parentId) => this._setNoti(parentId, total, highlight, roomId));
if (!childId) this._remainingParentIds = undefined;
}
_deleteNoti(roomId, total, highlight, childId) {
if (this.roomIdToNoti.has(roomId) === false) return;
const noti = this.getNoti(roomId);
const prevTotal = noti.total;
noti.total -= total;
noti.highlight -= highlight;
if (noti.total < 0) {
noti.total = 0;
noti.highlight = 0;
}
if (childId && noti.from !== null) {
if (!this.hasNoti(childId)) noti.from.delete(childId);
}
if (noti.from === null || noti.from.size === 0) {
this.roomIdToNoti.delete(roomId);
this.emit(cons.events.notifications.FULL_READ, roomId);
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, null, prevTotal);
} else {
this.roomIdToNoti.set(roomId, noti);
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, noti.total, prevTotal);
}
const parentIds = this.roomList.roomIdToParents.get(roomId);
if (typeof parentIds === 'undefined') return;
[...parentIds].forEach((parentId) => this._deleteNoti(parentId, total, highlight, roomId));
removeNoti(roomId, total, highlight);
const allParentSpaces = this.roomList.getAllParentSpaces(roomId);
allParentSpaces.forEach((spaceId) => {
removeNoti(spaceId, total, highlight, roomId);
});
}
async _displayPopupNoti(mEvent, room) {
@@ -204,7 +222,9 @@ class Notifications extends EventEmitter {
_listenEvents() {
this.matrixClient.on('Room.timeline', (mEvent, room) => {
if (room.isSpaceRoom()) return;
if (!isNotifEvent(mEvent)) return;
const liveEvents = room.getLiveTimeline().getEvents();
const lastTimelineEvent = liveEvents[liveEvents.length - 1];
@@ -214,16 +234,58 @@ class Notifications extends EventEmitter {
const total = room.getUnreadNotificationCount('total');
const highlight = room.getUnreadNotificationCount('highlight');
const noti = this.getNoti(room.roomId);
this._setNoti(room.roomId, total - noti.total, highlight - noti.highlight);
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) {
this.deleteNoti(room.roomId, total ?? 0, highlight ?? 0);
return;
}
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
if (this.matrixClient.getSyncState() === 'SYNCING') {
this._displayPopupNoti(mEvent, room);
}
});
this.matrixClient.on('accountData', (mEvent, oldMEvent) => {
if (mEvent.getType() === 'm.push_rules') {
const override = mEvent?.getContent()?.global?.override;
const oldOverride = oldMEvent?.getContent()?.global?.override;
if (!override || !oldOverride) return;
const isMuteToggled = (rule, otherOverride) => {
const roomId = rule.rule_id;
const room = this.matrixClient.getRoom(roomId);
if (room === null) return false;
if (room.isSpaceRoom()) return false;
const isMuted = isMutedRule(rule);
if (!isMuted) return false;
const isOtherMuted = findMutedRule(otherOverride, roomId);
if (isOtherMuted) return false;
return true;
};
const mutedRules = override.filter((rule) => isMuteToggled(rule, oldOverride));
const unMutedRules = oldOverride.filter((rule) => isMuteToggled(rule, override));
mutedRules.forEach((rule) => {
this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, true);
this.deleteNoti(rule.rule_id);
});
unMutedRules.forEach((rule) => {
this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, false);
const room = this.matrixClient.getRoom(rule.rule_id);
if (!this.doesRoomHaveUnread(room)) return;
const total = room.getUnreadNotificationCount('total');
const highlight = room.getUnreadNotificationCount('highlight');
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
});
}
});
this.matrixClient.on('Room.receipt', (mEvent, room) => {
if (mEvent.getType() === 'm.receipt') {
if (room.isSpaceRoom()) return;
const content = mEvent.getContent();
const readedEventId = Object.keys(content)[0];
const readerUserId = Object.keys(content[readedEventId]['m.read'])[0];

View File

@@ -36,6 +36,14 @@ class RoomList extends EventEmitter {
return !this.roomIdToParents.has(roomId);
}
getOrphanSpaces() {
return [...this.spaces].filter((roomId) => !this.roomIdToParents.has(roomId));
}
getOrphanRooms() {
return [...this.rooms].filter((roomId) => !this.roomIdToParents.has(roomId));
}
getOrphans() {
const rooms = [...this.spaces].concat([...this.rooms]);
return rooms.filter((roomId) => !this.roomIdToParents.has(roomId));
@@ -43,13 +51,15 @@ class RoomList extends EventEmitter {
getSpaceChildren(roomId) {
const space = this.matrixClient.getRoom(roomId);
if (space === null) return null;
const mSpaceChild = space?.currentState.getStateEvents('m.space.child');
const children = mSpaceChild?.map((mEvent) => {
const children = [];
mSpaceChild.forEach((mEvent) => {
const childId = mEvent.event.state_key;
if (isMEventSpaceChild(mEvent)) return childId;
return null;
if (isMEventSpaceChild(mEvent)) children.push(childId);
});
return children?.filter((childId) => childId !== null);
return children;
}
getCategorizedSpaces(spaceIds) {
@@ -69,7 +79,7 @@ class RoomList extends EventEmitter {
else mappedChild.add(childId);
});
};
spaceIds.map(categorizeSpace);
spaceIds.forEach(categorizeSpace);
return categorized;
}
@@ -89,32 +99,40 @@ class RoomList extends EventEmitter {
if (parents.size === 0) this.roomIdToParents.delete(roomId);
}
getParentSpaces(roomId) {
let parentIds = this.roomIdToParents.get(roomId);
if (parentIds) {
[...parentIds].forEach((parentId) => {
parentIds = new Set([...parentIds, ...this.getParentSpaces(parentId)]);
});
}
return parentIds || new Set();
getAllParentSpaces(roomId) {
const allParents = new Set();
const addAllParentIds = (rId) => {
if (allParents.has(rId)) return;
allParents.add(rId);
const parents = this.roomIdToParents.get(rId);
if (parents === undefined) return;
parents.forEach((id) => addAllParentIds(id));
};
addAllParentIds(roomId);
allParents.delete(roomId);
return allParents;
}
addToSpaces(roomId) {
this.spaces.add(roomId);
const allParentSpaces = this.getParentSpaces(roomId);
const allParentSpaces = this.getAllParentSpaces(roomId);
const spaceChildren = this.getSpaceChildren(roomId);
spaceChildren?.forEach((childRoomId) => {
if (allParentSpaces.has(childRoomId)) return;
this.addToRoomIdToParents(childRoomId, roomId);
spaceChildren?.forEach((childId) => {
if (allParentSpaces.has(childId)) return;
this.addToRoomIdToParents(childId, roomId);
});
}
deleteFromSpaces(roomId) {
this.spaces.delete(roomId);
const spaceChildren = this.getSpaceChildren(roomId);
spaceChildren?.forEach((childRoomId) => {
this.removeFromRoomIdToParents(childRoomId, roomId);
spaceChildren?.forEach((childId) => {
this.removeFromRoomIdToParents(childId, roomId);
});
}
@@ -254,12 +272,17 @@ class RoomList extends EventEmitter {
this.matrixClient.on('RoomState.events', (mEvent, state) => {
if (mEvent.getType() === 'm.space.child') {
const { event } = mEvent;
const roomId = mEvent.event.room_id;
const childId = mEvent.event.state_key;
if (isMEventSpaceChild(mEvent)) {
const allParentSpaces = this.getParentSpaces(event.room_id);
if (allParentSpaces.has(event.state_key)) return;
this.addToRoomIdToParents(event.state_key, event.room_id);
} else this.removeFromRoomIdToParents(event.state_key, event.room_id);
const allParentSpaces = this.getAllParentSpaces(roomId);
// only add if it doesn't make a cycle
if (!allParentSpaces.has(childId)) {
this.addToRoomIdToParents(childId, roomId);
}
} else {
this.removeFromRoomIdToParents(childId, roomId);
}
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
return;
}

View File

@@ -154,19 +154,19 @@ class RoomTimeline extends EventEmitter {
this._populateAllLinkedEvents(this.activeTimeline);
}
async _reset(eventId) {
async _reset() {
if (this.isEncrypted()) await this.decryptAllEventsOfTimeline(this.activeTimeline);
this._populateTimelines();
if (!this.initialized) {
this.initialized = true;
this._listenEvents();
}
this.emit(cons.events.roomTimeline.READY, eventId ?? null);
}
async loadLiveTimeline() {
this.activeTimeline = this.liveTimeline;
await this._reset();
this.emit(cons.events.roomTimeline.READY, null);
return true;
}
@@ -176,7 +176,8 @@ class RoomTimeline extends EventEmitter {
try {
const eventTimeline = await this.matrixClient.getEventTimeline(timelineSet, eventId);
this.activeTimeline = eventTimeline;
await this._reset(eventId);
await this._reset();
this.emit(cons.events.roomTimeline.READY, eventId);
return true;
} catch {
return false;
@@ -229,9 +230,18 @@ class RoomTimeline extends EventEmitter {
markAllAsRead() {
const readEventId = this.getReadUpToEventId();
const getLatestValidEvent = () => {
for (let i = this.timeline.length - 1; i >= 0; i -= 1) {
const latestEvent = this.timeline[i];
if (latestEvent.getId() === readEventId) return null;
if (!latestEvent.isSending()) return latestEvent;
}
return null;
};
this.notifications.deleteNoti(this.roomId);
if (this.timeline.length === 0) return;
const latestEvent = this.timeline[this.timeline.length - 1];
const latestEvent = getLatestValidEvent();
if (latestEvent === null) return;
if (readEventId === latestEvent.getId()) return;
this.matrixClient.sendReadReceipt(latestEvent);
this.emit(cons.events.roomTimeline.MARKED_AS_READ, latestEvent);

View File

@@ -1,5 +1,5 @@
const cons = {
version: '1.8.0',
version: '1.8.2',
secretKey: {
ACCESS_TOKEN: 'cinny_access_token',
DEVICE_ID: 'cinny_device_id',
@@ -107,6 +107,7 @@ const cons = {
notifications: {
NOTI_CHANGED: 'NOTI_CHANGED',
FULL_READ: 'FULL_READ',
MUTE_TOGGLED: 'MUTE_TOGGLED',
},
roomTimeline: {
READY: 'READY',

View File

@@ -97,8 +97,8 @@ class Settings extends EventEmitter {
if (typeof this.hideNickAvatarEvents === 'boolean') return this.hideNickAvatarEvents;
const settings = getSettings();
if (settings === null) return false;
if (typeof settings.hideNickAvatarEvents === 'undefined') return false;
if (settings === null) return true;
if (typeof settings.hideNickAvatarEvents === 'undefined') return true;
return settings.hideNickAvatarEvents;
}