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 version: 2
updates: updates:
- package-ecosystem: github-actions - package-ecosystem: github-actions
directory: / directory: /
schedule: {interval: weekly} schedule:
reviewers: [ajbura] interval: weekly
assignees: [ajbura] day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"
- package-ecosystem: docker - package-ecosystem: docker
directory: / directory: /
schedule: {interval: weekly} schedule:
reviewers: [ajbura] interval: weekly
assignees: [ajbura] day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"
- package-ecosystem: npm - package-ecosystem: npm
directory: / directory: /
schedule: {interval: weekly} schedule:
reviewers: [ajbura] interval: weekly
assignees: [ajbura] day: "tuesday"
time: "01:00"
timezone: "Asia/Kolkata"

View File

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

267
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "cinny", "name": "cinny",
"version": "1.8.0", "version": "1.8.2",
"description": "Yet another matrix client", "description": "Yet another matrix client",
"main": "index.js", "main": "index.js",
"engines": { "engines": {
@@ -15,7 +15,7 @@
"author": "Ajay Bura", "author": "Ajay Bura",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fontsource/inter": "^4.5.4", "@fontsource/inter": "^4.5.5",
"@fontsource/roboto": "^4.5.3", "@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", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
@@ -44,10 +44,10 @@
"react-modal": "^3.14.4", "react-modal": "^3.14.4",
"sanitize-html": "^2.7.0", "sanitize-html": "^2.7.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"twemoji": "^13.1.0" "twemoji": "^14.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.5", "@babel/core": "^7.17.7",
"@babel/preset-env": "^7.16.11", "@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7", "@babel/preset-react": "^7.16.7",
"assert": "^2.0.0", "assert": "^2.0.0",
@@ -57,13 +57,13 @@
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^10.2.4",
"crypto-browserify": "^3.12.0", "crypto-browserify": "^3.12.0",
"css-loader": "^6.7.0", "css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1", "css-minimizer-webpack-plugin": "^3.4.1",
"eslint": "^8.10.0", "eslint": "^8.11.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.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", "eslint-plugin-react-hooks": "^4.2.0",
"favicons": "^6.2.2", "favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.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 = () => { const focusReply = (ev) => {
if (reply?.event === null) return; if (!ev.keyCode || ev.keyCode === 32 || ev.keyCode === 13) {
if (reply?.event.isRedacted()) return; if (ev.keyCode) ev.preventDefault();
roomTimeline.loadEventTimeline(eventId); if (reply?.event === null) return;
if (reply?.event.isRedacted()) return;
roomTimeline.loadEventTimeline(eventId);
}
}; };
return ( return (
@@ -201,10 +204,10 @@ const MessageBody = React.memo(({
// Count the number of emojis // Count the number of emojis
const nEmojis = content.filter((e) => e.type === 'img').length; 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) => ( if (nEmojis <= 10 && content.every((element) => (
(typeof element === 'object' && element.type === 'img') (typeof element === 'object' && element.type === 'img')
|| (typeof element === 'string' && /^\s*$/g.test(element)) || (typeof element === 'string' && /^[\s\ufe0f]*$/g.test(element))
))) { ))) {
emojiOnly = true; 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(({ const MessageOptions = React.memo(({
roomTimeline, mEvent, edit, reply, roomTimeline, mEvent, edit, reply,
}) => { }) => {
@@ -513,7 +528,7 @@ const MessageOptions = React.memo(({
</MenuItem> </MenuItem>
<MenuItem <MenuItem
iconSrc={CmdIC} iconSrc={CmdIC}
onClick={() => openViewSource(mEvent)} onClick={() => handleOpenViewSource(mEvent, roomTimeline)}
> >
View source View source
</MenuItem> </MenuItem>

View File

@@ -32,28 +32,9 @@ const items = [{
type: cons.notifs.MUTE, 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) { function setRoomNotifType(roomId, newType) {
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const { notifications } = initMatrix;
const roomPushRule = mx.getRoomPushRule('global', roomId); const roomPushRule = mx.getRoomPushRule('global', roomId);
const promises = []; const promises = [];
@@ -76,7 +57,7 @@ function setRoomNotifType(roomId, newType) {
return promises; return promises;
} }
const oldState = getNotifType(roomId); const oldState = notifications.getNotiType(roomId);
if (oldState === cons.notifs.MUTE) { if (oldState === cons.notifs.MUTE) {
promises.push(mx.deletePushRule('global', 'override', roomId)); promises.push(mx.deletePushRule('global', 'override', roomId));
} }
@@ -115,8 +96,9 @@ function setRoomNotifType(roomId, newType) {
} }
function useNotifications(roomId) { function useNotifications(roomId) {
const [activeType, setActiveType] = useState(getNotifType(roomId)); const { notifications } = initMatrix;
useEffect(() => setActiveType(getNotifType(roomId)), [roomId]); const [activeType, setActiveType] = useState(notifications.getNotiType(roomId));
useEffect(() => setActiveType(notifications.getNotiType(roomId)), [roomId]);
const setNotification = useCallback((item) => { const setNotification = useCallback((item) => {
if (item.type === activeType.type) return; if (item.type === activeType.type) return;

View File

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

View File

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

View File

@@ -33,9 +33,11 @@ function Directs() {
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged); navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged); notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
notifications.on(cons.events.notifications.MUTE_TOGGLED, notiChanged);
return () => { return () => {
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged); navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged); 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); const noti = notifications.getNoti(roomId);
if (!notifications.hasNoti(childId)) return noti; if (!notifications.hasNoti(childId)) return noti;
if (noti.from === null) return noti; if (noti.from === null) return noti;
if (noti.from.has(childId) && noti.from.size === 1) return null;
const childNoti = notifications.getNoti(childId); const childNoti = notifications.getNoti(childId);
return { let noOther = true;
total: noti.total - childNoti.total, let total = 0;
highlight: noti.highlight - childNoti.highlight, 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 ( return (

View File

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

View File

@@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { openReusableContextMenu } from '../../../client/action/navigation'; import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords, abbreviateNumber } from '../../../util/common'; import { getEventCords, abbreviateNumber } from '../../../util/common';
@@ -23,9 +24,12 @@ function Selector({
const mx = initMatrix.matrixClient; const mx = initMatrix.matrixClient;
const noti = initMatrix.notifications; const noti = initMatrix.notifications;
const room = mx.getRoom(roomId); const room = mx.getRoom(roomId);
let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null; let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
if (imageSrc === null) imageSrc = room.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(); const [, forceUpdate] = useForceUpdate();
useEffect(() => { useEffect(() => {
@@ -56,7 +60,8 @@ function Selector({
imageSrc={isDM ? imageSrc : null} imageSrc={isDM ? imageSrc : null}
iconSrc={isDM ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())} iconSrc={isDM ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())}
isSelected={navigation.selectedRoomId === roomId} isSelected={navigation.selectedRoomId === roomId}
isUnread={noti.hasNoti(roomId)} isMuted={isMuted}
isUnread={!isMuted && noti.hasNoti(roomId)}
notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))} notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))}
isAlert={noti.getHighlightNoti(roomId) !== 0} isAlert={noti.getHighlightNoti(roomId) !== 0}
onClick={onClick} 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'; import PeopleDrawer from './PeopleDrawer';
function Room() { function Room() {
const [roomTimeline, setRoomTimeline] = useState(null); const [roomInfo, setRoomInfo] = useState({
const [eventId, setEventId] = useState(null); roomTimeline: null,
eventId: null,
});
const [isDrawer, setIsDrawer] = useState(settings.isPeopleDrawer); const [isDrawer, setIsDrawer] = useState(settings.isPeopleDrawer);
const mx = initMatrix.matrixClient; 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(() => { 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); navigation.on(cons.events.navigation.ROOM_SELECTED, handleRoomSelected);
settings.on(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
return () => { return () => {
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, handleRoomSelected); 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); settings.removeListener(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
roomTimeline?.removeInternalListeners();
}; };
}, []); }, []);
const { roomTimeline, eventId } = roomInfo;
if (roomTimeline === null) return <Welcome />; if (roomTimeline === null) return <Welcome />;
return ( return (

View File

@@ -7,16 +7,13 @@ import React, {
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './RoomViewContent.scss'; import './RoomViewContent.scss';
import EventEmitter from 'events';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import initMatrix from '../../../client/initMatrix'; import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import navigation from '../../../client/state/navigation'; import navigation from '../../../client/state/navigation';
import { openProfileViewer } from '../../../client/action/navigation'; import { openProfileViewer } from '../../../client/action/navigation';
import { import { diffMinutes, isInSameDay, Throttle } from '../../../util/common';
diffMinutes, isInSameDay, Throttle, getScrollInfo,
} from '../../../util/common';
import Divider from '../../atoms/divider/Divider'; import Divider from '../../atoms/divider/Divider';
import ScrollView from '../../atoms/scroll/ScrollView'; import ScrollView from '../../atoms/scroll/ScrollView';
@@ -27,17 +24,15 @@ import TimelineChange from '../../molecules/message/TimelineChange';
import { useStore } from '../../hooks/useStore'; import { useStore } from '../../hooks/useStore';
import { useForceUpdate } from '../../hooks/useForceUpdate'; import { useForceUpdate } from '../../hooks/useForceUpdate';
import { parseTimelineChange } from './common'; import { parseTimelineChange } from './common';
import TimelineScroll from './TimelineScroll';
import EventLimit from './EventLimit';
const DEFAULT_MAX_EVENTS = 50;
const PAG_LIMIT = 30; const PAG_LIMIT = 30;
const MAX_MSG_DIFF_MINUTES = 5; const MAX_MSG_DIFF_MINUTES = 5;
const PLACEHOLDER_COUNT = 2; const PLACEHOLDER_COUNT = 2;
const PLACEHOLDERS_HEIGHT = 96 * PLACEHOLDER_COUNT; const PLACEHOLDERS_HEIGHT = 96 * PLACEHOLDER_COUNT;
const SCROLL_TRIGGER_POS = PLACEHOLDERS_HEIGHT * 4; const SCROLL_TRIGGER_POS = PLACEHOLDERS_HEIGHT * 4;
const SMALLEST_MSG_HEIGHT = 32;
const PAGES_COUNT = 4;
function loadingMsgPlaceholders(key, count = 2) { function loadingMsgPlaceholders(key, count = 2) {
const pl = []; const pl = [];
const genPlaceholders = () => { const genPlaceholders = () => {
@@ -124,178 +119,7 @@ function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
); );
} }
class TimelineScroll extends EventEmitter { function useTimeline(roomTimeline, eventId, readUptoEvtStore, eventLimitRef) {
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) {
const [timelineInfo, setTimelineInfo] = useState(null); const [timelineInfo, setTimelineInfo] = useState(null);
const setEventTimeline = async (eId) => { const setEventTimeline = async (eId) => {
@@ -309,6 +133,7 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
}; };
useEffect(() => { useEffect(() => {
const limit = eventLimitRef.current;
const initTimeline = (eId) => { const initTimeline = (eId) => {
// NOTICE: eId can be id of readUpto, reply or specific event. // NOTICE: eId can be id of readUpto, reply or specific event.
// readUpTo: when user click jump to unread message button. // readUpTo: when user click jump to unread message button.
@@ -320,20 +145,19 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
if (isSpecificEvent) { if (isSpecificEvent) {
focusEventIndex = roomTimeline.getEventIndex(eId); focusEventIndex = roomTimeline.getEventIndex(eId);
} else if (!readEventStore.getItem()) { }
if (!readUptoEvtStore.getItem() && roomTimeline.hasEventInTimeline(readUpToId)) {
// either opening live timeline or jump to unread. // either opening live timeline or jump to unread.
focusEventIndex = roomTimeline.getUnreadEventIndex(readUpToId); readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
if (roomTimeline.hasEventInTimeline(readUpToId)) { }
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId)); if (readUptoEvtStore.getItem() && !isSpecificEvent) {
} focusEventIndex = roomTimeline.getUnreadEventIndex(readUptoEvtStore.getItem().getId());
} else {
focusEventIndex = roomTimeline.getUnreadEventIndex(readEventStore.getItem().getId());
} }
if (focusEventIndex > -1) { if (focusEventIndex > -1) {
limit.setFrom(focusEventIndex - Math.round(limit.getMaxEvents() / 2)); limit.setFrom(focusEventIndex - Math.round(limit.maxEvents / 2));
} else { } else {
limit.setFrom(roomTimeline.timeline.length - limit.getMaxEvents()); limit.setFrom(roomTimeline.timeline.length - limit.maxEvents);
} }
setTimelineInfo({ focusEventId: isSpecificEvent ? eId : null }); setTimelineInfo({ focusEventId: isSpecificEvent ? eId : null });
}; };
@@ -342,7 +166,6 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
setEventTimeline(eventId); setEventTimeline(eventId);
return () => { return () => {
roomTimeline.removeListener(cons.events.roomTimeline.READY, initTimeline); roomTimeline.removeListener(cons.events.roomTimeline.READY, initTimeline);
roomTimeline.removeInternalListeners();
limit.setFrom(0); limit.setFrom(0);
}; };
}, [roomTimeline, eventId]); }, [roomTimeline, eventId]);
@@ -350,36 +173,45 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
return timelineInfo; return timelineInfo;
} }
function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) { function usePaginate(
roomTimeline,
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
) {
const [info, setInfo] = useState(null); const [info, setInfo] = useState(null);
useEffect(() => { useEffect(() => {
const handleOnPagination = (backwards, loaded) => { const handlePaginatedFromServer = (backwards, loaded) => {
const limit = eventLimitRef.current;
if (loaded === 0) return; if (loaded === 0) return;
if (!readEventStore.getItem()) { if (!readUptoEvtStore.getItem()) {
const readUpToId = roomTimeline.getReadUpToEventId(); 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({ setTimeout(() => setInfo({
backwards, backwards,
loaded, loaded,
})); }));
}; };
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handleOnPagination); roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer);
return () => { return () => {
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handleOnPagination); roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer);
}; };
}, [roomTimeline]); }, [roomTimeline]);
const autoPaginate = useCallback(async () => { const autoPaginate = useCallback(async () => {
const timelineScroll = timelineScrollRef.current;
const limit = eventLimitRef.current;
if (roomTimeline.isOngoingPagination) return; if (roomTimeline.isOngoingPagination) return;
const tLength = roomTimeline.timeline.length; const tLength = roomTimeline.timeline.length;
if (timelineScroll.bottom < SCROLL_TRIGGER_POS) { if (timelineScroll.bottom < SCROLL_TRIGGER_POS) {
if (limit.getEndIndex() < tLength) { if (limit.length < tLength) {
// paginate from memory // paginate from memory
limit.setFrom(limit.calcNextFrom(false, tLength)); limit.paginate(false, PAG_LIMIT, tLength);
forceUpdateLimit(); forceUpdateLimit();
} else if (roomTimeline.canPaginateForward()) { } else if (roomTimeline.canPaginateForward()) {
// paginate from server. // paginate from server.
@@ -390,7 +222,7 @@ function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) {
if (timelineScroll.top < SCROLL_TRIGGER_POS) { if (timelineScroll.top < SCROLL_TRIGGER_POS) {
if (limit.from > 0) { if (limit.from > 0) {
// paginate from memory // paginate from memory
limit.setFrom(limit.calcNextFrom(true, tLength)); limit.paginate(true, PAG_LIMIT, tLength);
forceUpdateLimit(); forceUpdateLimit();
} else if (roomTimeline.canPaginateBackward()) { } else if (roomTimeline.canPaginateBackward()) {
// paginate from server. // paginate from server.
@@ -402,16 +234,25 @@ function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) {
return [info, autoPaginate]; return [info, autoPaginate];
} }
function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdateLimit) { function useHandleScroll(
roomTimeline,
autoPaginate,
readUptoEvtStore,
forceUpdateLimit,
timelineScrollRef,
eventLimitRef,
) {
const handleScroll = useCallback(() => { const handleScroll = useCallback(() => {
const timelineScroll = timelineScrollRef.current;
const limit = eventLimitRef.current;
requestAnimationFrame(() => { requestAnimationFrame(() => {
// emit event to toggle scrollToBottom button visibility // emit event to toggle scrollToBottom button visibility
const isAtBottom = ( const isAtBottom = (
timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward() timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()
&& limit.getEndIndex() >= roomTimeline.timeline.length && limit.length >= roomTimeline.timeline.length
); );
roomTimeline.emit(cons.events.roomTimeline.AT_BOTTOM, isAtBottom); roomTimeline.emit(cons.events.roomTimeline.AT_BOTTOM, isAtBottom);
if (isAtBottom && readEventStore.getItem()) { if (isAtBottom && readUptoEvtStore.getItem()) {
requestAnimationFrame(() => roomTimeline.markAllAsRead()); requestAnimationFrame(() => roomTimeline.markAllAsRead());
} }
}); });
@@ -419,11 +260,13 @@ function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdate
}, [roomTimeline]); }, [roomTimeline]);
const handleScrollToLive = useCallback(() => { const handleScrollToLive = useCallback(() => {
if (readEventStore.getItem()) { const timelineScroll = timelineScrollRef.current;
const limit = eventLimitRef.current;
if (readUptoEvtStore.getItem()) {
requestAnimationFrame(() => roomTimeline.markAllAsRead()); requestAnimationFrame(() => roomTimeline.markAllAsRead());
} }
if (roomTimeline.isServingLiveTimeline()) { if (roomTimeline.isServingLiveTimeline()) {
limit.setFrom(roomTimeline.timeline.length - limit.getMaxEvents()); limit.setFrom(roomTimeline.timeline.length - limit.maxEvents);
timelineScroll.scrollToBottom(); timelineScroll.scrollToBottom();
forceUpdateLimit(); forceUpdateLimit();
return; return;
@@ -434,48 +277,46 @@ function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdate
return [handleScroll, handleScrollToLive]; return [handleScroll, handleScrollToLive];
} }
function useEventArrive(roomTimeline, readEventStore) { function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, eventLimitRef) {
const myUserId = initMatrix.matrixClient.getUserId(); const myUserId = initMatrix.matrixClient.getUserId();
const [newEvent, setEvent] = useState(null); const [newEvent, setEvent] = useState(null);
useEffect(() => { useEffect(() => {
const sendReadReceipt = (event) => { const timelineScroll = timelineScrollRef.current;
if (event.isSending()) return; const limit = eventLimitRef.current;
const trySendReadReceipt = (event) => {
if (myUserId === event.getSender()) { if (myUserId === event.getSender()) {
roomTimeline.markAllAsRead(); requestAnimationFrame(() => roomTimeline.markAllAsRead());
return; return;
} }
const readUpToEvent = readEventStore.getItem(); const readUpToEvent = readUptoEvtStore.getItem();
const readUpToId = roomTimeline.getReadUpToEventId(); 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 (isUnread === false) {
if (document.visibilityState === 'hidden' || timelineScroll.bottom >= 16) { if (document.visibilityState === 'visible' && timelineScroll.bottom < 16) {
if (readUpToEvent === readUpToId) return; requestAnimationFrame(() => roomTimeline.markAllAsRead());
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId)); } else {
readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
}
return; return;
} }
// user has not mark room as read
const isUnreadMsg = readUpToEvent?.getId() === readUpToId;
if (!isUnreadMsg) {
roomTimeline.markAllAsRead();
}
const { timeline } = roomTimeline; const { timeline } = roomTimeline;
const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToEvent?.getId(); const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToId;
if (unreadMsgIsLast) { if (unreadMsgIsLast) {
roomTimeline.markAllAsRead(); requestAnimationFrame(() => roomTimeline.markAllAsRead());
} }
}; };
const handleEvent = (event) => { const handleEvent = (event) => {
const tLength = roomTimeline.timeline.length; const tLength = roomTimeline.timeline.length;
const isUserViewingLive = ( const isViewingLive = roomTimeline.isServingLiveTimeline() && limit.length >= tLength - 1;
roomTimeline.isServingLiveTimeline() const isAttached = timelineScroll.bottom < SCROLL_TRIGGER_POS;
&& limit.getEndIndex() >= tLength - 1
&& timelineScroll.bottom < SCROLL_TRIGGER_POS if (isViewingLive && isAttached) {
); limit.setFrom(tLength - limit.maxEvents);
if (isUserViewingLive) { trySendReadReceipt(event);
limit.setFrom(tLength - limit.getMaxEvents());
sendReadReceipt(event);
setEvent(event); setEvent(event);
return; return;
} }
@@ -484,11 +325,8 @@ function useEventArrive(roomTimeline, readEventStore) {
setEvent(event); setEvent(event);
return; return;
} }
const isUserDitchedLive = (
roomTimeline.isServingLiveTimeline() if (isViewingLive) {
&& limit.getEndIndex() >= tLength - 1
);
if (isUserDitchedLive) {
// This stateUpdate will help to put the // This stateUpdate will help to put the
// loading msg placeholder at bottom // loading msg placeholder at bottom
setEvent(event); setEvent(event);
@@ -505,39 +343,52 @@ function useEventArrive(roomTimeline, readEventStore) {
}; };
}, [roomTimeline]); }, [roomTimeline]);
useEffect(() => { return newEvent;
if (!roomTimeline.initialized) return;
if (timelineScroll.bottom < 16
&& !roomTimeline.canPaginateForward()
&& document.visibilityState === 'visible') {
timelineScroll.scrollToBottom();
} else {
timelineScroll.tryRestoringScroll();
}
}, [newEvent, roomTimeline]);
} }
let jumpToItemIndex = -1;
function RoomViewContent({ eventId, roomTimeline }) { function RoomViewContent({ eventId, roomTimeline }) {
const [throttle] = useState(new Throttle());
const timelineSVRef = useRef(null); const timelineSVRef = useRef(null);
const readEventStore = useStore(roomTimeline); const timelineScrollRef = useRef(null);
const timelineInfo = useTimeline(roomTimeline, eventId, readEventStore); const eventLimitRef = useRef(null);
const readUptoEvtStore = useStore(roomTimeline);
const [onLimitUpdate, forceUpdateLimit] = useForceUpdate(); const [onLimitUpdate, forceUpdateLimit] = useForceUpdate();
const [paginateInfo, autoPaginate] = usePaginate(roomTimeline, readEventStore, forceUpdateLimit);
const [handleScroll, handleScrollToLive] = useHandleScroll( const timelineInfo = useTimeline(roomTimeline, eventId, readUptoEvtStore, eventLimitRef);
roomTimeline, autoPaginate, readEventStore, forceUpdateLimit, 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; const { timeline } = roomTimeline;
useLayoutEffect(() => { useLayoutEffect(() => {
if (!roomTimeline.initialized) { if (!roomTimeline.initialized) {
timelineScroll = new TimelineScroll(timelineSVRef.current); timelineScrollRef.current = new TimelineScroll(timelineSVRef.current);
eventLimitRef.current = new EventLimit();
} }
}); });
// when active timeline changes // when active timeline changes
useEffect(() => { useEffect(() => {
if (!roomTimeline.initialized) return undefined; if (!roomTimeline.initialized) return undefined;
const timelineScroll = timelineScrollRef.current;
if (timeline.length > 0) { if (timeline.length > 0) {
if (jumpToItemIndex === -1) { if (jumpToItemIndex === -1) {
@@ -547,7 +398,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
} }
if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()) { if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()) {
const readUpToId = roomTimeline.getReadUpToEventId(); const readUpToId = roomTimeline.getReadUpToEventId();
if (readEventStore.getItem()?.getId() === readUpToId || readUpToId === null) { if (readUptoEvtStore.getItem()?.getId() === readUpToId || readUpToId === null) {
requestAnimationFrame(() => roomTimeline.markAllAsRead()); requestAnimationFrame(() => roomTimeline.markAllAsRead());
} }
} }
@@ -555,11 +406,9 @@ function RoomViewContent({ eventId, roomTimeline }) {
} }
autoPaginate(); autoPaginate();
timelineScroll.on('scroll', handleScroll);
roomTimeline.on(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive); roomTimeline.on(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive);
return () => { return () => {
if (timelineSVRef.current === null) return; if (timelineSVRef.current === null) return;
timelineScroll.removeListener('scroll', handleScroll);
roomTimeline.removeListener(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive); roomTimeline.removeListener(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive);
}; };
}, [timelineInfo]); }, [timelineInfo]);
@@ -567,6 +416,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
// when paginating from server // when paginating from server
useEffect(() => { useEffect(() => {
if (!roomTimeline.initialized) return; if (!roomTimeline.initialized) return;
const timelineScroll = timelineScrollRef.current;
timelineScroll.tryRestoringScroll(); timelineScroll.tryRestoringScroll();
autoPaginate(); autoPaginate();
}, [paginateInfo]); }, [paginateInfo]);
@@ -574,28 +424,45 @@ function RoomViewContent({ eventId, roomTimeline }) {
// when paginating locally // when paginating locally
useEffect(() => { useEffect(() => {
if (!roomTimeline.initialized) return; if (!roomTimeline.initialized) return;
const timelineScroll = timelineScrollRef.current;
timelineScroll.tryRestoringScroll(); timelineScroll.tryRestoringScroll();
}, [onLimitUpdate]); }, [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 handleTimelineScroll = (event) => {
const { target } = event; const timelineScroll = timelineScrollRef.current;
if (!target) return; if (!event.target) return;
throttle._(() => timelineScroll?.calcScroll(), 400)(target);
throttle._(() => {
const backwards = timelineScroll?.calcScroll();
if (typeof backwards !== 'boolean') return;
handleScroll(backwards);
}, 200)();
}; };
const renderTimeline = () => { const renderTimeline = () => {
const tl = []; const tl = [];
const limit = eventLimitRef.current;
let itemCountIndex = 0; let itemCountIndex = 0;
jumpToItemIndex = -1; jumpToItemIndex = -1;
const readEvent = readEventStore.getItem(); const readUptoEvent = readUptoEvtStore.getItem();
let unreadDivider = false; let unreadDivider = false;
if (roomTimeline.canPaginateBackward() || limit.from > 0) { if (roomTimeline.canPaginateBackward() || limit.from > 0) {
tl.push(loadingMsgPlaceholders(1, PLACEHOLDER_COUNT)); tl.push(loadingMsgPlaceholders(1, PLACEHOLDER_COUNT));
itemCountIndex += 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; if (i >= timeline.length) break;
const mEvent = timeline[i]; const mEvent = timeline[i];
const prevMEvent = timeline[i - 1] ?? null; const prevMEvent = timeline[i - 1] ?? null;
@@ -614,9 +481,9 @@ function RoomViewContent({ eventId, roomTimeline }) {
let isNewEvent = false; let isNewEvent = false;
if (!unreadDivider) { if (!unreadDivider) {
unreadDivider = (readEvent unreadDivider = (readUptoEvent
&& prevMEvent?.getTs() <= readEvent.getTs() && prevMEvent?.getTs() <= readUptoEvent.getTs()
&& readEvent.getTs() < mEvent.getTs()); && readUptoEvent.getTs() < mEvent.getTs());
if (unreadDivider) { if (unreadDivider) {
isNewEvent = true; isNewEvent = true;
tl.push(<Divider key={`new-${mEvent.getId()}`} variant="positive" text="New messages" />); 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)); tl.push(renderEvent(roomTimeline, mEvent, isNewEvent ? null : prevMEvent, isFocus));
itemCountIndex += 1; itemCountIndex += 1;
} }
if (roomTimeline.canPaginateForward() || limit.getEndIndex() < timeline.length) { if (roomTimeline.canPaginateForward() || limit.length < timeline.length) {
tl.push(loadingMsgPlaceholders(2, PLACEHOLDER_COUNT)); 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'; import navigation from '../state/navigation';
function listenKeyboard(event) { function listenKeyboard(event) {
// Ctrl + // Ctrl/Cmd +
if (event.ctrlKey) { if (event.ctrlKey || event.metaKey) {
// k - for search Modal // k - for search Modal
if (event.keyCode === 75) { if (event.keyCode === 75) {
event.preventDefault(); event.preventDefault();

View File

@@ -17,6 +17,17 @@ function isNotifEvent(mEvent) {
return true; 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 { class Notifications extends EventEmitter {
constructor(roomList) { constructor(roomList) {
super(); super();
@@ -39,11 +50,12 @@ class Notifications extends EventEmitter {
_initNoti() { _initNoti() {
const addNoti = (roomId) => { const addNoti = (roomId) => {
const room = this.matrixClient.getRoom(roomId); const room = this.matrixClient.getRoom(roomId);
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) return;
if (this.doesRoomHaveUnread(room) === false) return; if (this.doesRoomHaveUnread(room) === false) return;
const total = room.getUnreadNotificationCount('total'); const total = room.getUnreadNotificationCount('total');
const highlight = room.getUnreadNotificationCount('highlight'); const highlight = room.getUnreadNotificationCount('highlight');
const noti = this.getNoti(room.roomId); this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
this._setNoti(room.roomId, total - noti.total, highlight - noti.highlight);
}; };
[...this.roomList.rooms].forEach(addNoti); [...this.roomList.rooms].forEach(addNoti);
[...this.roomList.directs].forEach(addNoti); [...this.roomList.directs].forEach(addNoti);
@@ -66,6 +78,22 @@ class Notifications extends EventEmitter {
return true; 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) { getNoti(roomId) {
return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null }; return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null };
} }
@@ -96,74 +124,64 @@ class Notifications extends EventEmitter {
} }
} }
_getAllParentIds(roomId) { _setNoti(roomId, total, highlight) {
let allParentIds = this.roomList.roomIdToParents.get(roomId); const addNoti = (id, t, h, fromId) => {
if (allParentIds === undefined) return new Set(); const prevTotal = this.roomIdToNoti.get(id)?.total ?? null;
const parentIds = [...allParentIds]; const noti = this.getNoti(id);
parentIds.forEach((pId) => { noti.total += t;
allParentIds = new Set( noti.highlight += h;
[...allParentIds, ...this._getAllParentIds(pId)],
); 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) { _deleteNoti(roomId, total, highlight) {
const prevTotal = this.roomIdToNoti.get(roomId)?.total ?? null; const removeNoti = (id, t, h, fromId) => {
const noti = this.getNoti(roomId); if (this.roomIdToNoti.has(id) === false) return;
if (!childId || this._remainingParentIds?.has(roomId)) { const noti = this.getNoti(id);
noti.total += total; const prevTotal = noti.total;
noti.highlight += highlight; noti.total -= t;
} noti.highlight -= h;
if (childId) { if (noti.total < 0) {
if (noti.from === null) noti.from = new Set(); noti.total = 0;
noti.from.add(childId); 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); removeNoti(roomId, total, highlight);
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, noti.total, prevTotal); const allParentSpaces = this.roomList.getAllParentSpaces(roomId);
allParentSpaces.forEach((spaceId) => {
if (!childId) this._remainingParentIds = this._getAllParentIds(roomId); removeNoti(spaceId, total, highlight, 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));
} }
async _displayPopupNoti(mEvent, room) { async _displayPopupNoti(mEvent, room) {
@@ -204,7 +222,9 @@ class Notifications extends EventEmitter {
_listenEvents() { _listenEvents() {
this.matrixClient.on('Room.timeline', (mEvent, room) => { this.matrixClient.on('Room.timeline', (mEvent, room) => {
if (room.isSpaceRoom()) return;
if (!isNotifEvent(mEvent)) return; if (!isNotifEvent(mEvent)) return;
const liveEvents = room.getLiveTimeline().getEvents(); const liveEvents = room.getLiveTimeline().getEvents();
const lastTimelineEvent = liveEvents[liveEvents.length - 1]; const lastTimelineEvent = liveEvents[liveEvents.length - 1];
@@ -214,16 +234,58 @@ class Notifications extends EventEmitter {
const total = room.getUnreadNotificationCount('total'); const total = room.getUnreadNotificationCount('total');
const highlight = room.getUnreadNotificationCount('highlight'); const highlight = room.getUnreadNotificationCount('highlight');
const noti = this.getNoti(room.roomId); if (this.getNotiType(room.roomId) === cons.notifs.MUTE) {
this._setNoti(room.roomId, total - noti.total, highlight - noti.highlight); this.deleteNoti(room.roomId, total ?? 0, highlight ?? 0);
return;
}
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
if (this.matrixClient.getSyncState() === 'SYNCING') { if (this.matrixClient.getSyncState() === 'SYNCING') {
this._displayPopupNoti(mEvent, room); 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) => { this.matrixClient.on('Room.receipt', (mEvent, room) => {
if (mEvent.getType() === 'm.receipt') { if (mEvent.getType() === 'm.receipt') {
if (room.isSpaceRoom()) return;
const content = mEvent.getContent(); const content = mEvent.getContent();
const readedEventId = Object.keys(content)[0]; const readedEventId = Object.keys(content)[0];
const readerUserId = Object.keys(content[readedEventId]['m.read'])[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); 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() { getOrphans() {
const rooms = [...this.spaces].concat([...this.rooms]); const rooms = [...this.spaces].concat([...this.rooms]);
return rooms.filter((roomId) => !this.roomIdToParents.has(roomId)); return rooms.filter((roomId) => !this.roomIdToParents.has(roomId));
@@ -43,13 +51,15 @@ class RoomList extends EventEmitter {
getSpaceChildren(roomId) { getSpaceChildren(roomId) {
const space = this.matrixClient.getRoom(roomId); const space = this.matrixClient.getRoom(roomId);
if (space === null) return null;
const mSpaceChild = space?.currentState.getStateEvents('m.space.child'); const mSpaceChild = space?.currentState.getStateEvents('m.space.child');
const children = mSpaceChild?.map((mEvent) => {
const children = [];
mSpaceChild.forEach((mEvent) => {
const childId = mEvent.event.state_key; const childId = mEvent.event.state_key;
if (isMEventSpaceChild(mEvent)) return childId; if (isMEventSpaceChild(mEvent)) children.push(childId);
return null;
}); });
return children?.filter((childId) => childId !== null); return children;
} }
getCategorizedSpaces(spaceIds) { getCategorizedSpaces(spaceIds) {
@@ -69,7 +79,7 @@ class RoomList extends EventEmitter {
else mappedChild.add(childId); else mappedChild.add(childId);
}); });
}; };
spaceIds.map(categorizeSpace); spaceIds.forEach(categorizeSpace);
return categorized; return categorized;
} }
@@ -89,32 +99,40 @@ class RoomList extends EventEmitter {
if (parents.size === 0) this.roomIdToParents.delete(roomId); if (parents.size === 0) this.roomIdToParents.delete(roomId);
} }
getParentSpaces(roomId) { getAllParentSpaces(roomId) {
let parentIds = this.roomIdToParents.get(roomId); const allParents = new Set();
if (parentIds) {
[...parentIds].forEach((parentId) => { const addAllParentIds = (rId) => {
parentIds = new Set([...parentIds, ...this.getParentSpaces(parentId)]); if (allParents.has(rId)) return;
}); allParents.add(rId);
}
return parentIds || new Set(); const parents = this.roomIdToParents.get(rId);
if (parents === undefined) return;
parents.forEach((id) => addAllParentIds(id));
};
addAllParentIds(roomId);
allParents.delete(roomId);
return allParents;
} }
addToSpaces(roomId) { addToSpaces(roomId) {
this.spaces.add(roomId); this.spaces.add(roomId);
const allParentSpaces = this.getParentSpaces(roomId);
const allParentSpaces = this.getAllParentSpaces(roomId);
const spaceChildren = this.getSpaceChildren(roomId); const spaceChildren = this.getSpaceChildren(roomId);
spaceChildren?.forEach((childRoomId) => { spaceChildren?.forEach((childId) => {
if (allParentSpaces.has(childRoomId)) return; if (allParentSpaces.has(childId)) return;
this.addToRoomIdToParents(childRoomId, roomId); this.addToRoomIdToParents(childId, roomId);
}); });
} }
deleteFromSpaces(roomId) { deleteFromSpaces(roomId) {
this.spaces.delete(roomId); this.spaces.delete(roomId);
const spaceChildren = this.getSpaceChildren(roomId); const spaceChildren = this.getSpaceChildren(roomId);
spaceChildren?.forEach((childRoomId) => { spaceChildren?.forEach((childId) => {
this.removeFromRoomIdToParents(childRoomId, roomId); this.removeFromRoomIdToParents(childId, roomId);
}); });
} }
@@ -254,12 +272,17 @@ class RoomList extends EventEmitter {
this.matrixClient.on('RoomState.events', (mEvent, state) => { this.matrixClient.on('RoomState.events', (mEvent, state) => {
if (mEvent.getType() === 'm.space.child') { if (mEvent.getType() === 'm.space.child') {
const { event } = mEvent; const roomId = mEvent.event.room_id;
const childId = mEvent.event.state_key;
if (isMEventSpaceChild(mEvent)) { if (isMEventSpaceChild(mEvent)) {
const allParentSpaces = this.getParentSpaces(event.room_id); const allParentSpaces = this.getAllParentSpaces(roomId);
if (allParentSpaces.has(event.state_key)) return; // only add if it doesn't make a cycle
this.addToRoomIdToParents(event.state_key, event.room_id); if (!allParentSpaces.has(childId)) {
} else this.removeFromRoomIdToParents(event.state_key, event.room_id); this.addToRoomIdToParents(childId, roomId);
}
} else {
this.removeFromRoomIdToParents(childId, roomId);
}
this.emit(cons.events.roomList.ROOMLIST_UPDATED); this.emit(cons.events.roomList.ROOMLIST_UPDATED);
return; return;
} }

View File

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

View File

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

View File

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