Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12369ba2ec | ||
|
|
f5720bde14 | ||
|
|
0d59a4de48 | ||
|
|
73c74d0477 | ||
|
|
70ffd7ded8 | ||
|
|
92a3a8d6fa | ||
|
|
6e9cd02b2b | ||
|
|
d0f90af251 | ||
|
|
c53eb27c6f | ||
|
|
38773e89ff | ||
|
|
19cb30d360 | ||
|
|
50a734b977 | ||
|
|
b988758ac2 | ||
|
|
2567328e8b | ||
|
|
a7568fcbbf | ||
|
|
27a06ae90c | ||
|
|
211fd19031 | ||
|
|
fe18611b4b | ||
|
|
5e9b45ad5f | ||
|
|
af833daee4 | ||
|
|
1ad5317d6e | ||
|
|
7cf5df80ce | ||
|
|
d6b880d110 | ||
|
|
cf58a4376e | ||
|
|
a76dcb289a | ||
|
|
48ec6224e7 | ||
|
|
127dd8baf4 | ||
|
|
d25c3ff4fc | ||
|
|
f0e9de4cf9 | ||
|
|
92607a788e | ||
|
|
82948c1f55 |
26
.github/dependabot.yml
vendored
26
.github/dependabot.yml
vendored
@@ -1,22 +1,28 @@
|
||||
# Docs: <https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/customizing-dependency-updates>
|
||||
# Docs: <https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/customizing-dependency-updates>
|
||||
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule: {interval: weekly}
|
||||
reviewers: [ajbura]
|
||||
assignees: [ajbura]
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "tuesday"
|
||||
time: "01:00"
|
||||
timezone: "Asia/Kolkata"
|
||||
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
schedule: {interval: weekly}
|
||||
reviewers: [ajbura]
|
||||
assignees: [ajbura]
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "tuesday"
|
||||
time: "01:00"
|
||||
timezone: "Asia/Kolkata"
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule: {interval: weekly}
|
||||
reviewers: [ajbura]
|
||||
assignees: [ajbura]
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: "tuesday"
|
||||
time: "01:00"
|
||||
timezone: "Asia/Kolkata"
|
||||
|
||||
59
.github/workflows/build-pull-request.yml
vendored
59
.github/workflows/build-pull-request.yml
vendored
@@ -1,32 +1,39 @@
|
||||
name: 'Build PR'
|
||||
name: 'Build pull request'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: ['opened', 'synchronize']
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PR_NUMBER: ${{github.event.number}}
|
||||
steps:
|
||||
- uses: actions/checkout@v3.0.0
|
||||
- name: Build
|
||||
run: npm ci && npm run build
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3.0.0
|
||||
with:
|
||||
name: previewbuild
|
||||
path: dist
|
||||
retention-days: 1
|
||||
- uses: actions/github-script@v6.0.0
|
||||
with:
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/pr.json', JSON.stringify(context.payload.pull_request));
|
||||
- name: Upload PR Info
|
||||
uses: actions/upload-artifact@v3.0.0
|
||||
with:
|
||||
name: pr.json
|
||||
path: pr.json
|
||||
retention-days: 1
|
||||
build-pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PR_NUMBER: ${{github.event.number}}
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3.0.0
|
||||
- name: Build app
|
||||
run: npm ci && npm run build
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3.0.0
|
||||
with:
|
||||
name: previewbuild
|
||||
path: dist
|
||||
retention-days: 1
|
||||
- name: Get PR info
|
||||
uses: actions/github-script@v6.0.0
|
||||
with:
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/pr.json', JSON.stringify(context.payload.pull_request));
|
||||
- name: Upload PR Info
|
||||
uses: actions/upload-artifact@v3.0.0
|
||||
with:
|
||||
name: pr.json
|
||||
path: pr.json
|
||||
retention-days: 1
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@v2.10.0
|
||||
with:
|
||||
context: .
|
||||
push: false
|
||||
|
||||
150
.github/workflows/deploy-pull-request.yml
vendored
150
.github/workflows/deploy-pull-request.yml
vendored
@@ -1,78 +1,78 @@
|
||||
name: Upload Preview Build to Netlify
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Build PR"]
|
||||
types:
|
||||
- completed
|
||||
workflow_run:
|
||||
workflows: ["Build pull request"]
|
||||
types:
|
||||
- completed
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
# There's a 'download artifact' action but it hasn't been updated for the
|
||||
# workflow_run action (https://github.com/actions/download-artifact/issues/60)
|
||||
# so instead we get this mess:
|
||||
- name: 'Download artifact'
|
||||
uses: actions/github-script@v6.0.0
|
||||
with:
|
||||
script: |
|
||||
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "previewbuild"
|
||||
})[0];
|
||||
var download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data));
|
||||
var prInfoArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "pr.json"
|
||||
})[0];
|
||||
var download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: prInfoArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/pr.json.zip', Buffer.from(download.data));
|
||||
- name: Extract Artifacts
|
||||
run: unzip -d dist previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip
|
||||
- name: 'Read PR Info'
|
||||
id: readctx
|
||||
uses: actions/github-script@v6.0.0
|
||||
with:
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
var pr = JSON.parse(fs.readFileSync('${{github.workspace}}/pr.json'));
|
||||
console.log(`::set-output name=prnumber::${pr.number}`);
|
||||
- name: Deploy to Netlify
|
||||
id: netlify
|
||||
uses: nwtgck/actions-netlify@v1.2.3
|
||||
with:
|
||||
publish-dir: dist
|
||||
deploy-message: "Deploy from GitHub Actions"
|
||||
# These don't work because we're in workflow_run
|
||||
enable-pull-request-comment: false
|
||||
enable-commit-comment: false
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }}
|
||||
timeout-minutes: 1
|
||||
- name: Edit PR Description
|
||||
uses: velas/pr-description@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
pull-request-number: ${{ steps.readctx.outputs.prnumber }}
|
||||
description-message: |
|
||||
Preview: ${{ steps.netlify.outputs.deploy-url }}
|
||||
⚠️ Exercise caution. Use test accounts. ⚠️
|
||||
get-build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
steps:
|
||||
# There's a 'download artifact' action but it hasn't been updated for the
|
||||
# workflow_run action (https://github.com/actions/download-artifact/issues/60)
|
||||
# so instead we get this mess:
|
||||
- name: 'Download artifact'
|
||||
uses: actions/github-script@v6.0.0
|
||||
with:
|
||||
script: |
|
||||
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "previewbuild"
|
||||
})[0];
|
||||
var download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data));
|
||||
var prInfoArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "pr.json"
|
||||
})[0];
|
||||
var download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: prInfoArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/pr.json.zip', Buffer.from(download.data));
|
||||
- name: Extract Artifacts
|
||||
run: unzip -d dist previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip
|
||||
- name: 'Read PR Info'
|
||||
id: readctx
|
||||
uses: actions/github-script@v6.0.0
|
||||
with:
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
var pr = JSON.parse(fs.readFileSync('${{github.workspace}}/pr.json'));
|
||||
console.log(`::set-output name=prnumber::${pr.number}`);
|
||||
- name: Deploy to Netlify
|
||||
id: netlify
|
||||
uses: nwtgck/actions-netlify@v1.2.3
|
||||
with:
|
||||
publish-dir: dist
|
||||
deploy-message: "Deploy from GitHub Actions"
|
||||
# These don't work because we're in workflow_run
|
||||
enable-pull-request-comment: false
|
||||
enable-commit-comment: false
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }}
|
||||
timeout-minutes: 1
|
||||
- name: Edit PR Description
|
||||
uses: velas/pr-description@v1.0.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
pull-request-number: ${{ steps.readctx.outputs.prnumber }}
|
||||
description-message: |
|
||||
Preview: ${{ steps.netlify.outputs.deploy-url }}
|
||||
⚠️ Exercise caution. Use test accounts. ⚠️
|
||||
|
||||
34
.github/workflows/docker.yaml
vendored
34
.github/workflows/docker.yaml
vendored
@@ -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 }}
|
||||
21
.github/workflows/netlify-prod.yaml
vendored
21
.github/workflows/netlify-prod.yaml
vendored
@@ -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
56
.github/workflows/prod-deploy.yaml
vendored
Normal 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 }}
|
||||
@@ -1,11 +1,11 @@
|
||||
## Builder
|
||||
FROM node:17.6.0-alpine3.15 as builder
|
||||
FROM node:17.7.1-alpine3.15 as builder
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY package.json package-lock.json /src
|
||||
COPY package.json package-lock.json /src/
|
||||
RUN npm ci
|
||||
COPY . /src
|
||||
COPY . /src/
|
||||
RUN npm run build
|
||||
|
||||
|
||||
|
||||
267
package-lock.json
generated
267
package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "cinny",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cinny",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^4.5.4",
|
||||
"@fontsource/inter": "^4.5.5",
|
||||
"@fontsource/roboto": "^4.5.3",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
@@ -38,10 +38,10 @@
|
||||
"react-modal": "^3.14.4",
|
||||
"sanitize-html": "^2.7.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"twemoji": "^13.1.0"
|
||||
"twemoji": "^14.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.5",
|
||||
"@babel/core": "^7.17.7",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"assert": "^2.0.0",
|
||||
@@ -51,13 +51,13 @@
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.4",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.29.3",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"favicons": "^6.2.2",
|
||||
"favicons-webpack-plugin": "^5.0.2",
|
||||
@@ -106,27 +106,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz",
|
||||
"integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz",
|
||||
"integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.17.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz",
|
||||
"integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz",
|
||||
"integrity": "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.16.7",
|
||||
"@babel/generator": "^7.17.3",
|
||||
"@babel/helper-compilation-targets": "^7.16.7",
|
||||
"@babel/helper-module-transforms": "^7.16.7",
|
||||
"@babel/helpers": "^7.17.2",
|
||||
"@babel/parser": "^7.17.3",
|
||||
"@babel/generator": "^7.17.7",
|
||||
"@babel/helper-compilation-targets": "^7.17.7",
|
||||
"@babel/helper-module-transforms": "^7.17.7",
|
||||
"@babel/helpers": "^7.17.7",
|
||||
"@babel/parser": "^7.17.7",
|
||||
"@babel/template": "^7.16.7",
|
||||
"@babel/traverse": "^7.17.3",
|
||||
"@babel/types": "^7.17.0",
|
||||
@@ -145,9 +145,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz",
|
||||
"integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
|
||||
"integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.17.0",
|
||||
@@ -184,12 +184,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz",
|
||||
"integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz",
|
||||
"integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.16.4",
|
||||
"@babel/compat-data": "^7.17.7",
|
||||
"@babel/helper-validator-option": "^7.16.7",
|
||||
"browserslist": "^4.17.5",
|
||||
"semver": "^6.3.0"
|
||||
@@ -344,19 +344,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz",
|
||||
"integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz",
|
||||
"integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.16.7",
|
||||
"@babel/helper-module-imports": "^7.16.7",
|
||||
"@babel/helper-simple-access": "^7.16.7",
|
||||
"@babel/helper-simple-access": "^7.17.7",
|
||||
"@babel/helper-split-export-declaration": "^7.16.7",
|
||||
"@babel/helper-validator-identifier": "^7.16.7",
|
||||
"@babel/template": "^7.16.7",
|
||||
"@babel/traverse": "^7.16.7",
|
||||
"@babel/types": "^7.16.7"
|
||||
"@babel/traverse": "^7.17.3",
|
||||
"@babel/types": "^7.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -414,12 +414,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz",
|
||||
"integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz",
|
||||
"integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.16.7"
|
||||
"@babel/types": "^7.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -483,13 +483,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz",
|
||||
"integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz",
|
||||
"integrity": "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.16.7",
|
||||
"@babel/traverse": "^7.17.0",
|
||||
"@babel/traverse": "^7.17.3",
|
||||
"@babel/types": "^7.17.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -511,9 +511,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz",
|
||||
"integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz",
|
||||
"integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -1782,16 +1782,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.0.tgz",
|
||||
"integrity": "sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
|
||||
"integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
"debug": "^4.3.2",
|
||||
"espree": "^9.3.1",
|
||||
"globals": "^13.9.0",
|
||||
"ignore": "^4.0.6",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimatch": "^3.0.4",
|
||||
@@ -1816,19 +1816,10 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/ignore": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/inter": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.4.tgz",
|
||||
"integrity": "sha512-D0icTFpt9bWvB/OEXMztYf0bhUQZoDIYpsco5C7GVfxgKDRl8Jdn3N2aHHQqwjgRUUvRuyMv8HrRM8Hrt4U52w=="
|
||||
"version": "4.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.5.tgz",
|
||||
"integrity": "sha512-mWnePEroLfaJQWmynipzOVcH6JwT8Jta3+yLsC5Pm/snHBXnOiAOnjBqYjKnvXwJ4eUPt2AaAhyrtwCgWQRGOg=="
|
||||
},
|
||||
"node_modules/@fontsource/roboto": {
|
||||
"version": "4.5.3",
|
||||
@@ -4669,9 +4660,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/css-loader": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.0.tgz",
|
||||
"integrity": "sha512-S7HCfCiDHLA+VXKqdZwyRZgoO0R9BnKDnVIoHMq5grl3N86zAu7MB+FBWHr5xOJC8SmvpTLha/2NpfFkFEN/ig==",
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
|
||||
"integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"icss-utils": "^5.1.0",
|
||||
@@ -5643,12 +5634,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.10.0.tgz",
|
||||
"integrity": "sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==",
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz",
|
||||
"integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/eslintrc": "^1.2.0",
|
||||
"@eslint/eslintrc": "^1.2.1",
|
||||
"@humanwhocodes/config-array": "^0.9.2",
|
||||
"ajv": "^6.10.0",
|
||||
"chalk": "^4.0.0",
|
||||
@@ -5856,9 +5847,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react": {
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz",
|
||||
"integrity": "sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg==",
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz",
|
||||
"integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-includes": "^3.1.4",
|
||||
@@ -13146,20 +13137,20 @@
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||
},
|
||||
"node_modules/twemoji": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-13.1.0.tgz",
|
||||
"integrity": "sha512-e3fZRl2S9UQQdBFLYXtTBT6o4vidJMnpWUAhJA+yLGR+kaUTZAt3PixC0cGvvxWSuq2MSz/o0rJraOXrWw/4Ew==",
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-14.0.1.tgz",
|
||||
"integrity": "sha512-eoqhea0sUhmC10iTacksyp1v9O4BP1jKmVqtK+Nztw40/dzawSHkXL3/xCpyh+mukmEvJ0Gw9VLvwZfQ9HKXDw==",
|
||||
"dependencies": {
|
||||
"fs-extra": "^8.0.1",
|
||||
"jsonfile": "^5.0.0",
|
||||
"twemoji-parser": "13.1.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"universalify": "^0.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/twemoji-parser": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-13.1.0.tgz",
|
||||
"integrity": "sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg=="
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
|
||||
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
@@ -14123,24 +14114,24 @@
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz",
|
||||
"integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz",
|
||||
"integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.17.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz",
|
||||
"integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.7.tgz",
|
||||
"integrity": "sha512-djHlEfFHnSnTAcPb7dATbiM5HxGOP98+3JLBZtjRb5I7RXrw7kFRoG2dXM8cm3H+o11A8IFH/uprmJpwFynRNQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.16.7",
|
||||
"@babel/generator": "^7.17.3",
|
||||
"@babel/helper-compilation-targets": "^7.16.7",
|
||||
"@babel/helper-module-transforms": "^7.16.7",
|
||||
"@babel/helpers": "^7.17.2",
|
||||
"@babel/parser": "^7.17.3",
|
||||
"@babel/generator": "^7.17.7",
|
||||
"@babel/helper-compilation-targets": "^7.17.7",
|
||||
"@babel/helper-module-transforms": "^7.17.7",
|
||||
"@babel/helpers": "^7.17.7",
|
||||
"@babel/parser": "^7.17.7",
|
||||
"@babel/template": "^7.16.7",
|
||||
"@babel/traverse": "^7.17.3",
|
||||
"@babel/types": "^7.17.0",
|
||||
@@ -14152,9 +14143,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz",
|
||||
"integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
|
||||
"integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.17.0",
|
||||
@@ -14182,12 +14173,12 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-compilation-targets": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz",
|
||||
"integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz",
|
||||
"integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.16.4",
|
||||
"@babel/compat-data": "^7.17.7",
|
||||
"@babel/helper-validator-option": "^7.16.7",
|
||||
"browserslist": "^4.17.5",
|
||||
"semver": "^6.3.0"
|
||||
@@ -14300,19 +14291,19 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz",
|
||||
"integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz",
|
||||
"integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-environment-visitor": "^7.16.7",
|
||||
"@babel/helper-module-imports": "^7.16.7",
|
||||
"@babel/helper-simple-access": "^7.16.7",
|
||||
"@babel/helper-simple-access": "^7.17.7",
|
||||
"@babel/helper-split-export-declaration": "^7.16.7",
|
||||
"@babel/helper-validator-identifier": "^7.16.7",
|
||||
"@babel/template": "^7.16.7",
|
||||
"@babel/traverse": "^7.16.7",
|
||||
"@babel/types": "^7.16.7"
|
||||
"@babel/traverse": "^7.17.3",
|
||||
"@babel/types": "^7.17.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-optimise-call-expression": {
|
||||
@@ -14355,12 +14346,12 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-simple-access": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz",
|
||||
"integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz",
|
||||
"integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.16.7"
|
||||
"@babel/types": "^7.17.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-skip-transparent-expression-wrappers": {
|
||||
@@ -14406,13 +14397,13 @@
|
||||
}
|
||||
},
|
||||
"@babel/helpers": {
|
||||
"version": "7.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz",
|
||||
"integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.7.tgz",
|
||||
"integrity": "sha512-TKsj9NkjJfTBxM7Phfy7kv6yYc4ZcOo+AaWGqQOKTPDOmcGkIFb5xNA746eKisQkm4yavUYh4InYM9S+VnO01w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.16.7",
|
||||
"@babel/traverse": "^7.17.0",
|
||||
"@babel/traverse": "^7.17.3",
|
||||
"@babel/types": "^7.17.0"
|
||||
}
|
||||
},
|
||||
@@ -14428,9 +14419,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz",
|
||||
"integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==",
|
||||
"version": "7.17.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.7.tgz",
|
||||
"integrity": "sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
@@ -15289,16 +15280,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.0.tgz",
|
||||
"integrity": "sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz",
|
||||
"integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.12.4",
|
||||
"debug": "^4.3.2",
|
||||
"espree": "^9.3.1",
|
||||
"globals": "^13.9.0",
|
||||
"ignore": "^4.0.6",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimatch": "^3.0.4",
|
||||
@@ -15313,19 +15304,13 @@
|
||||
"requires": {
|
||||
"type-fest": "^0.20.2"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@fontsource/inter": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.4.tgz",
|
||||
"integrity": "sha512-D0icTFpt9bWvB/OEXMztYf0bhUQZoDIYpsco5C7GVfxgKDRl8Jdn3N2aHHQqwjgRUUvRuyMv8HrRM8Hrt4U52w=="
|
||||
"version": "4.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.5.tgz",
|
||||
"integrity": "sha512-mWnePEroLfaJQWmynipzOVcH6JwT8Jta3+yLsC5Pm/snHBXnOiAOnjBqYjKnvXwJ4eUPt2AaAhyrtwCgWQRGOg=="
|
||||
},
|
||||
"@fontsource/roboto": {
|
||||
"version": "4.5.3",
|
||||
@@ -17681,9 +17666,9 @@
|
||||
}
|
||||
},
|
||||
"css-loader": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.0.tgz",
|
||||
"integrity": "sha512-S7HCfCiDHLA+VXKqdZwyRZgoO0R9BnKDnVIoHMq5grl3N86zAu7MB+FBWHr5xOJC8SmvpTLha/2NpfFkFEN/ig==",
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
|
||||
"integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"icss-utils": "^5.1.0",
|
||||
@@ -18419,12 +18404,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"eslint": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.10.0.tgz",
|
||||
"integrity": "sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==",
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.11.0.tgz",
|
||||
"integrity": "sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint/eslintrc": "^1.2.0",
|
||||
"@eslint/eslintrc": "^1.2.1",
|
||||
"@humanwhocodes/config-array": "^0.9.2",
|
||||
"ajv": "^6.10.0",
|
||||
"chalk": "^4.0.0",
|
||||
@@ -18672,9 +18657,9 @@
|
||||
}
|
||||
},
|
||||
"eslint-plugin-react": {
|
||||
"version": "7.29.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz",
|
||||
"integrity": "sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg==",
|
||||
"version": "7.29.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz",
|
||||
"integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-includes": "^3.1.4",
|
||||
@@ -24068,20 +24053,20 @@
|
||||
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
|
||||
},
|
||||
"twemoji": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-13.1.0.tgz",
|
||||
"integrity": "sha512-e3fZRl2S9UQQdBFLYXtTBT6o4vidJMnpWUAhJA+yLGR+kaUTZAt3PixC0cGvvxWSuq2MSz/o0rJraOXrWw/4Ew==",
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/twemoji/-/twemoji-14.0.1.tgz",
|
||||
"integrity": "sha512-eoqhea0sUhmC10iTacksyp1v9O4BP1jKmVqtK+Nztw40/dzawSHkXL3/xCpyh+mukmEvJ0Gw9VLvwZfQ9HKXDw==",
|
||||
"requires": {
|
||||
"fs-extra": "^8.0.1",
|
||||
"jsonfile": "^5.0.0",
|
||||
"twemoji-parser": "13.1.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"universalify": "^0.1.2"
|
||||
}
|
||||
},
|
||||
"twemoji-parser": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-13.1.0.tgz",
|
||||
"integrity": "sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg=="
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/twemoji-parser/-/twemoji-parser-14.0.0.tgz",
|
||||
"integrity": "sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA=="
|
||||
},
|
||||
"type-check": {
|
||||
"version": "0.4.0",
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cinny",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.2",
|
||||
"description": "Yet another matrix client",
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
@@ -15,7 +15,7 @@
|
||||
"author": "Ajay Bura",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^4.5.4",
|
||||
"@fontsource/inter": "^4.5.5",
|
||||
"@fontsource/roboto": "^4.5.3",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
@@ -44,10 +44,10 @@
|
||||
"react-modal": "^3.14.4",
|
||||
"sanitize-html": "^2.7.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"twemoji": "^13.1.0"
|
||||
"twemoji": "^14.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.5",
|
||||
"@babel/core": "^7.17.7",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"assert": "^2.0.0",
|
||||
@@ -57,13 +57,13 @@
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^10.2.4",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.29.3",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"favicons": "^6.2.2",
|
||||
"favicons-webpack-plugin": "^5.0.2",
|
||||
|
||||
16
public/res/ic/outlined/recent-clock.svg
Normal file
16
public/res/ic/outlined/recent-clock.svg
Normal 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 |
@@ -150,10 +150,13 @@ const MessageReplyWrapper = React.memo(({ roomTimeline, eventId }) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const focusReply = () => {
|
||||
if (reply?.event === null) return;
|
||||
if (reply?.event.isRedacted()) return;
|
||||
roomTimeline.loadEventTimeline(eventId);
|
||||
const focusReply = (ev) => {
|
||||
if (!ev.keyCode || ev.keyCode === 32 || ev.keyCode === 13) {
|
||||
if (ev.keyCode) ev.preventDefault();
|
||||
if (reply?.event === null) return;
|
||||
if (reply?.event.isRedacted()) return;
|
||||
roomTimeline.loadEventTimeline(eventId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -201,10 +204,10 @@ const MessageBody = React.memo(({
|
||||
// Count the number of emojis
|
||||
const nEmojis = content.filter((e) => e.type === 'img').length;
|
||||
|
||||
// Make sure there's no text besides whitespace
|
||||
// Make sure there's no text besides whitespace and variation selector U+FE0F
|
||||
if (nEmojis <= 10 && content.every((element) => (
|
||||
(typeof element === 'object' && element.type === 'img')
|
||||
|| (typeof element === 'string' && /^\s*$/g.test(element))
|
||||
|| (typeof element === 'string' && /^[\s\ufe0f]*$/g.test(element))
|
||||
))) {
|
||||
emojiOnly = true;
|
||||
}
|
||||
@@ -466,6 +469,18 @@ function isMedia(mE) {
|
||||
);
|
||||
}
|
||||
|
||||
// if editedTimeline has mEventId then pass editedMEvent else pass mEvent to openViewSource
|
||||
function handleOpenViewSource(mEvent, roomTimeline) {
|
||||
const eventId = mEvent.getId();
|
||||
const { editedTimeline } = roomTimeline ?? {};
|
||||
let editedMEvent;
|
||||
if (editedTimeline?.has(eventId)) {
|
||||
const editedList = editedTimeline.get(eventId);
|
||||
editedMEvent = editedList[editedList.length - 1];
|
||||
}
|
||||
openViewSource(editedMEvent !== undefined ? editedMEvent : mEvent);
|
||||
}
|
||||
|
||||
const MessageOptions = React.memo(({
|
||||
roomTimeline, mEvent, edit, reply,
|
||||
}) => {
|
||||
@@ -513,7 +528,7 @@ const MessageOptions = React.memo(({
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
iconSrc={CmdIC}
|
||||
onClick={() => openViewSource(mEvent)}
|
||||
onClick={() => handleOpenViewSource(mEvent, roomTimeline)}
|
||||
>
|
||||
View source
|
||||
</MenuItem>
|
||||
|
||||
@@ -32,28 +32,9 @@ const items = [{
|
||||
type: cons.notifs.MUTE,
|
||||
}];
|
||||
|
||||
function getNotifType(roomId) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const pushRule = mx.getRoomPushRule('global', roomId);
|
||||
|
||||
if (typeof pushRule === 'undefined') {
|
||||
const overridePushRules = mx.getAccountData('m.push_rules')?.getContent()?.global?.override;
|
||||
if (typeof overridePushRules === 'undefined') return 0;
|
||||
|
||||
const isMuteOverride = overridePushRules.find((rule) => (
|
||||
rule.rule_id === roomId
|
||||
&& rule.actions[0] === 'dont_notify'
|
||||
&& rule.conditions[0].kind === 'event_match'
|
||||
));
|
||||
|
||||
return isMuteOverride ? cons.notifs.MUTE : cons.notifs.DEFAULT;
|
||||
}
|
||||
if (pushRule.actions[0] === 'notify') return cons.notifs.ALL_MESSAGES;
|
||||
return cons.notifs.MENTIONS_AND_KEYWORDS;
|
||||
}
|
||||
|
||||
function setRoomNotifType(roomId, newType) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const { notifications } = initMatrix;
|
||||
const roomPushRule = mx.getRoomPushRule('global', roomId);
|
||||
const promises = [];
|
||||
|
||||
@@ -76,7 +57,7 @@ function setRoomNotifType(roomId, newType) {
|
||||
return promises;
|
||||
}
|
||||
|
||||
const oldState = getNotifType(roomId);
|
||||
const oldState = notifications.getNotiType(roomId);
|
||||
if (oldState === cons.notifs.MUTE) {
|
||||
promises.push(mx.deletePushRule('global', 'override', roomId));
|
||||
}
|
||||
@@ -115,8 +96,9 @@ function setRoomNotifType(roomId, newType) {
|
||||
}
|
||||
|
||||
function useNotifications(roomId) {
|
||||
const [activeType, setActiveType] = useState(getNotifType(roomId));
|
||||
useEffect(() => setActiveType(getNotifType(roomId)), [roomId]);
|
||||
const { notifications } = initMatrix;
|
||||
const [activeType, setActiveType] = useState(notifications.getNotiType(roomId));
|
||||
useEffect(() => setActiveType(notifications.getNotiType(roomId)), [roomId]);
|
||||
|
||||
const setNotification = useCallback((item) => {
|
||||
if (item.type === activeType.type) return;
|
||||
|
||||
@@ -11,13 +11,16 @@ import NotificationBadge from '../../atoms/badge/NotificationBadge';
|
||||
import { blurOnBubbling } from '../../atoms/button/script';
|
||||
|
||||
function RoomSelectorWrapper({
|
||||
isSelected, isUnread, onClick,
|
||||
isSelected, isMuted, isUnread, onClick,
|
||||
content, options, onContextMenu,
|
||||
}) {
|
||||
let myClass = isUnread ? ' room-selector--unread' : '';
|
||||
myClass += isSelected ? ' room-selector--selected' : '';
|
||||
const classes = ['room-selector'];
|
||||
if (isMuted) classes.push('room-selector--muted');
|
||||
if (isUnread) classes.push('room-selector--unread');
|
||||
if (isSelected) classes.push('room-selector--selected');
|
||||
|
||||
return (
|
||||
<div className={`room-selector${myClass}`}>
|
||||
<div className={classes.join(' ')}>
|
||||
<button
|
||||
className="room-selector__content"
|
||||
type="button"
|
||||
@@ -32,11 +35,13 @@ function RoomSelectorWrapper({
|
||||
);
|
||||
}
|
||||
RoomSelectorWrapper.defaultProps = {
|
||||
isMuted: false,
|
||||
options: null,
|
||||
onContextMenu: null,
|
||||
};
|
||||
RoomSelectorWrapper.propTypes = {
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
isMuted: PropTypes.bool,
|
||||
isUnread: PropTypes.bool.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
content: PropTypes.node.isRequired,
|
||||
@@ -46,12 +51,13 @@ RoomSelectorWrapper.propTypes = {
|
||||
|
||||
function RoomSelector({
|
||||
name, parentName, roomId, imageSrc, iconSrc,
|
||||
isSelected, isUnread, notificationCount, isAlert,
|
||||
isSelected, isMuted, isUnread, notificationCount, isAlert,
|
||||
options, onClick, onContextMenu,
|
||||
}) {
|
||||
return (
|
||||
<RoomSelectorWrapper
|
||||
isSelected={isSelected}
|
||||
isMuted={isMuted}
|
||||
isUnread={isUnread}
|
||||
content={(
|
||||
<>
|
||||
@@ -91,6 +97,7 @@ RoomSelector.defaultProps = {
|
||||
isSelected: false,
|
||||
imageSrc: null,
|
||||
iconSrc: null,
|
||||
isMuted: false,
|
||||
options: null,
|
||||
onContextMenu: null,
|
||||
};
|
||||
@@ -101,6 +108,7 @@ RoomSelector.propTypes = {
|
||||
imageSrc: PropTypes.string,
|
||||
iconSrc: PropTypes.string,
|
||||
isSelected: PropTypes.bool,
|
||||
isMuted: PropTypes.bool,
|
||||
isUnread: PropTypes.bool.isRequired,
|
||||
notificationCount: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
border-radius: var(--bo-radius);
|
||||
cursor: pointer;
|
||||
|
||||
&--muted {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&--unread {
|
||||
.room-selector__content > .text {
|
||||
color: var(--tc-surface-high);
|
||||
|
||||
@@ -33,9 +33,11 @@ function Directs() {
|
||||
|
||||
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||
notifications.on(cons.events.notifications.MUTE_TOGGLED, notiChanged);
|
||||
return () => {
|
||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||
notifications.removeListener(cons.events.notifications.MUTE_TOGGLED, notiChanged);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -72,14 +72,22 @@ function DrawerBreadcrumb({ spaceId }) {
|
||||
const noti = notifications.getNoti(roomId);
|
||||
if (!notifications.hasNoti(childId)) return noti;
|
||||
if (noti.from === null) return noti;
|
||||
if (noti.from.has(childId) && noti.from.size === 1) return null;
|
||||
|
||||
const childNoti = notifications.getNoti(childId);
|
||||
|
||||
return {
|
||||
total: noti.total - childNoti.total,
|
||||
highlight: noti.highlight - childNoti.highlight,
|
||||
};
|
||||
let noOther = true;
|
||||
let total = 0;
|
||||
let highlight = 0;
|
||||
noti.from.forEach((fromId) => {
|
||||
if (childNoti.from.has(fromId)) return;
|
||||
noOther = false;
|
||||
const fromNoti = notifications.getNoti(fromId);
|
||||
total += fromNoti.total;
|
||||
highlight += fromNoti.highlight;
|
||||
});
|
||||
|
||||
if (noOther) return null;
|
||||
return { total, highlight };
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,10 +15,8 @@ const drawerPostie = new Postie();
|
||||
function Home({ spaceId }) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const { roomList, notifications, accountData } = initMatrix;
|
||||
const {
|
||||
spaces, rooms, directs, roomIdToParents,
|
||||
} = roomList;
|
||||
const categorizedSpaces = useCategorizedSpaces();
|
||||
const { spaces, rooms, directs } = roomList;
|
||||
useCategorizedSpaces();
|
||||
const isCategorized = accountData.categorizedSpaces.has(spaceId);
|
||||
|
||||
let categories = null;
|
||||
@@ -26,14 +24,14 @@ function Home({ spaceId }) {
|
||||
let roomIds = [];
|
||||
let directIds = [];
|
||||
|
||||
const spaceChildIds = roomList.getSpaceChildren(spaceId);
|
||||
if (spaceChildIds) {
|
||||
if (spaceId) {
|
||||
const spaceChildIds = roomList.getSpaceChildren(spaceId);
|
||||
spaceIds = spaceChildIds.filter((roomId) => spaces.has(roomId));
|
||||
roomIds = spaceChildIds.filter((roomId) => rooms.has(roomId));
|
||||
directIds = spaceChildIds.filter((roomId) => directs.has(roomId));
|
||||
} else {
|
||||
spaceIds = [...spaces].filter((roomId) => !roomIdToParents.has(roomId));
|
||||
roomIds = [...rooms].filter((roomId) => !roomIdToParents.has(roomId));
|
||||
spaceIds = roomList.getOrphanSpaces();
|
||||
roomIds = roomList.getOrphanRooms();
|
||||
}
|
||||
|
||||
spaceIds.sort(AtoZ);
|
||||
@@ -42,6 +40,7 @@ function Home({ spaceId }) {
|
||||
|
||||
if (isCategorized) {
|
||||
categories = roomList.getCategorizedSpaces(spaceIds);
|
||||
categories.delete(spaceId);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -63,9 +62,11 @@ function Home({ spaceId }) {
|
||||
|
||||
navigation.on(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||
notifications.on(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||
notifications.on(cons.events.notifications.MUTE_TOGGLED, notiChanged);
|
||||
return () => {
|
||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, selectorChanged);
|
||||
notifications.removeListener(cons.events.notifications.NOTI_CHANGED, notiChanged);
|
||||
notifications.removeListener(cons.events.notifications.MUTE_TOGGLED, notiChanged);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import './RoomsCategory.scss';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import { selectSpace, selectRoom,openReusableContextMenu } from '../../../client/action/navigation';
|
||||
import { selectSpace, selectRoom, openReusableContextMenu } from '../../../client/action/navigation';
|
||||
import { getEventCords } from '../../../util/common';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import { openReusableContextMenu } from '../../../client/action/navigation';
|
||||
import { getEventCords, abbreviateNumber } from '../../../util/common';
|
||||
@@ -23,9 +24,12 @@ function Selector({
|
||||
const mx = initMatrix.matrixClient;
|
||||
const noti = initMatrix.notifications;
|
||||
const room = mx.getRoom(roomId);
|
||||
|
||||
let imageSrc = room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
if (imageSrc === null) imageSrc = room.getAvatarUrl(mx.baseUrl, 24, 24, 'crop') || null;
|
||||
|
||||
const isMuted = noti.getNotiType(roomId) === cons.notifs.MUTE;
|
||||
|
||||
const [, forceUpdate] = useForceUpdate();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -56,7 +60,8 @@ function Selector({
|
||||
imageSrc={isDM ? imageSrc : null}
|
||||
iconSrc={isDM ? null : joinRuleToIconSrc(room.getJoinRule(), room.isSpaceRoom())}
|
||||
isSelected={navigation.selectedRoomId === roomId}
|
||||
isUnread={noti.hasNoti(roomId)}
|
||||
isMuted={isMuted}
|
||||
isUnread={!isMuted && noti.hasNoti(roomId)}
|
||||
notificationCount={abbreviateNumber(noti.getTotalNoti(roomId))}
|
||||
isAlert={noti.getHighlightNoti(roomId) !== 0}
|
||||
onClick={onClick}
|
||||
|
||||
35
src/app/organisms/room/EventLimit.js
Normal file
35
src/app/organisms/room/EventLimit.js
Normal 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;
|
||||
@@ -13,33 +13,46 @@ import RoomSettings from './RoomSettings';
|
||||
import PeopleDrawer from './PeopleDrawer';
|
||||
|
||||
function Room() {
|
||||
const [roomTimeline, setRoomTimeline] = useState(null);
|
||||
const [eventId, setEventId] = useState(null);
|
||||
const [roomInfo, setRoomInfo] = useState({
|
||||
roomTimeline: null,
|
||||
eventId: null,
|
||||
});
|
||||
const [isDrawer, setIsDrawer] = useState(settings.isPeopleDrawer);
|
||||
|
||||
const mx = initMatrix.matrixClient;
|
||||
const handleRoomSelected = (rId, pRoomId, eId) => {
|
||||
if (mx.getRoom(rId)) {
|
||||
setRoomTimeline(new RoomTimeline(rId, initMatrix.notifications));
|
||||
setEventId(eId);
|
||||
} else {
|
||||
// TODO: add ability to join room if roomId is invalid
|
||||
setRoomTimeline(null);
|
||||
setEventId(null);
|
||||
}
|
||||
};
|
||||
const handleDrawerToggling = (visiblity) => setIsDrawer(visiblity);
|
||||
|
||||
useEffect(() => {
|
||||
const handleRoomSelected = (rId, pRoomId, eId) => {
|
||||
roomInfo.roomTimeline?.removeInternalListeners();
|
||||
if (mx.getRoom(rId)) {
|
||||
setRoomInfo({
|
||||
roomTimeline: new RoomTimeline(rId, initMatrix.notifications),
|
||||
eventId: eId ?? null,
|
||||
});
|
||||
} else {
|
||||
// TODO: add ability to join room if roomId is invalid
|
||||
setRoomInfo({
|
||||
roomTimeline: null,
|
||||
eventId: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
navigation.on(cons.events.navigation.ROOM_SELECTED, handleRoomSelected);
|
||||
settings.on(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
|
||||
return () => {
|
||||
navigation.removeListener(cons.events.navigation.ROOM_SELECTED, handleRoomSelected);
|
||||
};
|
||||
}, [roomInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleDrawerToggling = (visiblity) => setIsDrawer(visiblity);
|
||||
settings.on(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
|
||||
return () => {
|
||||
settings.removeListener(cons.events.settings.PEOPLE_DRAWER_TOGGLED, handleDrawerToggling);
|
||||
roomTimeline?.removeInternalListeners();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { roomTimeline, eventId } = roomInfo;
|
||||
if (roomTimeline === null) return <Welcome />;
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,16 +7,13 @@ import React, {
|
||||
import PropTypes from 'prop-types';
|
||||
import './RoomViewContent.scss';
|
||||
|
||||
import EventEmitter from 'events';
|
||||
import dateFormat from 'dateformat';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import { openProfileViewer } from '../../../client/action/navigation';
|
||||
import {
|
||||
diffMinutes, isInSameDay, Throttle, getScrollInfo,
|
||||
} from '../../../util/common';
|
||||
import { diffMinutes, isInSameDay, Throttle } from '../../../util/common';
|
||||
|
||||
import Divider from '../../atoms/divider/Divider';
|
||||
import ScrollView from '../../atoms/scroll/ScrollView';
|
||||
@@ -27,17 +24,15 @@ import TimelineChange from '../../molecules/message/TimelineChange';
|
||||
import { useStore } from '../../hooks/useStore';
|
||||
import { useForceUpdate } from '../../hooks/useForceUpdate';
|
||||
import { parseTimelineChange } from './common';
|
||||
import TimelineScroll from './TimelineScroll';
|
||||
import EventLimit from './EventLimit';
|
||||
|
||||
const DEFAULT_MAX_EVENTS = 50;
|
||||
const PAG_LIMIT = 30;
|
||||
const MAX_MSG_DIFF_MINUTES = 5;
|
||||
const PLACEHOLDER_COUNT = 2;
|
||||
const PLACEHOLDERS_HEIGHT = 96 * PLACEHOLDER_COUNT;
|
||||
const SCROLL_TRIGGER_POS = PLACEHOLDERS_HEIGHT * 4;
|
||||
|
||||
const SMALLEST_MSG_HEIGHT = 32;
|
||||
const PAGES_COUNT = 4;
|
||||
|
||||
function loadingMsgPlaceholders(key, count = 2) {
|
||||
const pl = [];
|
||||
const genPlaceholders = () => {
|
||||
@@ -124,178 +119,7 @@ function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
|
||||
);
|
||||
}
|
||||
|
||||
class TimelineScroll extends EventEmitter {
|
||||
constructor(target) {
|
||||
super();
|
||||
if (target === null) {
|
||||
throw new Error('Can not initialize TimelineScroll, target HTMLElement in null');
|
||||
}
|
||||
this.scroll = target;
|
||||
|
||||
this.backwards = false;
|
||||
this.inTopHalf = false;
|
||||
this.maxEvents = DEFAULT_MAX_EVENTS;
|
||||
|
||||
this.isScrollable = false;
|
||||
this.top = 0;
|
||||
this.bottom = 0;
|
||||
this.height = 0;
|
||||
this.viewHeight = 0;
|
||||
|
||||
this.topMsg = null;
|
||||
this.bottomMsg = null;
|
||||
this.diff = 0;
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
const scrollInfo = getScrollInfo(this.scroll);
|
||||
const maxScrollTop = scrollInfo.height - scrollInfo.viewHeight;
|
||||
|
||||
this._scrollTo(scrollInfo, maxScrollTop);
|
||||
}
|
||||
|
||||
// restore scroll using previous calc by this._updateTopBottomMsg() and this._calcDiff.
|
||||
tryRestoringScroll() {
|
||||
const scrollInfo = getScrollInfo(this.scroll);
|
||||
|
||||
let scrollTop = 0;
|
||||
const ot = this.inTopHalf ? this.topMsg?.offsetTop : this.bottomMsg?.offsetTop;
|
||||
if (!ot) scrollTop = Math.round(this.height - this.viewHeight);
|
||||
else scrollTop = ot - this.diff;
|
||||
|
||||
this._scrollTo(scrollInfo, scrollTop);
|
||||
}
|
||||
|
||||
scrollToIndex(index, offset = 0) {
|
||||
const scrollInfo = getScrollInfo(this.scroll);
|
||||
const msgs = this.scroll.lastElementChild.lastElementChild.children;
|
||||
const offsetTop = msgs[index]?.offsetTop;
|
||||
|
||||
if (offsetTop === undefined) return;
|
||||
// if msg is already in visible are we don't need to scroll to that
|
||||
if (offsetTop > scrollInfo.top && offsetTop < (scrollInfo.top + scrollInfo.viewHeight)) return;
|
||||
const to = offsetTop - offset;
|
||||
|
||||
this._scrollTo(scrollInfo, to);
|
||||
}
|
||||
|
||||
_scrollTo(scrollInfo, scrollTop) {
|
||||
this.scroll.scrollTop = scrollTop;
|
||||
|
||||
// browser emit 'onscroll' event only if the 'element.scrollTop' value changes.
|
||||
// so here we flag that the upcoming 'onscroll' event is
|
||||
// emitted as side effect of assigning 'this.scroll.scrollTop' above
|
||||
// only if it's changes.
|
||||
// by doing so we prevent this._updateCalc() from calc again.
|
||||
if (scrollTop !== this.top) {
|
||||
this.scrolledByCode = true;
|
||||
}
|
||||
const sInfo = { ...scrollInfo };
|
||||
|
||||
const maxScrollTop = scrollInfo.height - scrollInfo.viewHeight;
|
||||
|
||||
sInfo.top = (scrollTop > maxScrollTop) ? maxScrollTop : scrollTop;
|
||||
this._updateCalc(sInfo);
|
||||
}
|
||||
|
||||
// we maintain reference of top and bottom messages
|
||||
// to restore the scroll position when
|
||||
// messages gets removed from either end and added to other.
|
||||
_updateTopBottomMsg() {
|
||||
const msgs = this.scroll.lastElementChild.lastElementChild.children;
|
||||
const lMsgIndex = msgs.length - 1;
|
||||
|
||||
this.topMsg = msgs[0]?.className === 'ph-msg'
|
||||
? msgs[PLACEHOLDER_COUNT]
|
||||
: msgs[0];
|
||||
this.bottomMsg = msgs[lMsgIndex]?.className === 'ph-msg'
|
||||
? msgs[lMsgIndex - PLACEHOLDER_COUNT]
|
||||
: msgs[lMsgIndex];
|
||||
}
|
||||
|
||||
// we calculate the difference between first/last message and current scrollTop.
|
||||
// if we are going above we calc diff between first and scrollTop
|
||||
// else otherwise.
|
||||
// NOTE: This will help to restore the scroll when msgs get's removed
|
||||
// from one end and added to other end
|
||||
_calcDiff(scrollInfo) {
|
||||
if (!this.topMsg || !this.bottomMsg) return 0;
|
||||
if (this.inTopHalf) {
|
||||
return this.topMsg.offsetTop - scrollInfo.top;
|
||||
}
|
||||
return this.bottomMsg.offsetTop - scrollInfo.top;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
_calcMaxEvents(scrollInfo) {
|
||||
return Math.round(scrollInfo.viewHeight / SMALLEST_MSG_HEIGHT) * PAGES_COUNT;
|
||||
}
|
||||
|
||||
_updateCalc(scrollInfo) {
|
||||
const halfViewHeight = Math.round(scrollInfo.viewHeight / 2);
|
||||
const scrollMiddle = scrollInfo.top + halfViewHeight;
|
||||
const lastMiddle = this.top + halfViewHeight;
|
||||
|
||||
this.backwards = scrollMiddle < lastMiddle;
|
||||
this.inTopHalf = scrollMiddle < scrollInfo.height / 2;
|
||||
|
||||
this.isScrollable = scrollInfo.isScrollable;
|
||||
this.top = scrollInfo.top;
|
||||
this.bottom = scrollInfo.height - (scrollInfo.top + scrollInfo.viewHeight);
|
||||
this.height = scrollInfo.height;
|
||||
|
||||
// only calculate maxEvents if viewHeight change
|
||||
if (this.viewHeight !== scrollInfo.viewHeight) {
|
||||
this.maxEvents = this._calcMaxEvents(scrollInfo);
|
||||
this.viewHeight = scrollInfo.viewHeight;
|
||||
}
|
||||
|
||||
this._updateTopBottomMsg();
|
||||
this.diff = this._calcDiff(scrollInfo);
|
||||
}
|
||||
|
||||
calcScroll() {
|
||||
if (this.scrolledByCode) {
|
||||
this.scrolledByCode = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollInfo = getScrollInfo(this.scroll);
|
||||
this._updateCalc(scrollInfo);
|
||||
|
||||
this.emit('scroll', this.backwards);
|
||||
}
|
||||
}
|
||||
|
||||
let timelineScroll = null;
|
||||
let jumpToItemIndex = -1;
|
||||
const throttle = new Throttle();
|
||||
const limit = {
|
||||
from: 0,
|
||||
getMaxEvents() {
|
||||
return timelineScroll?.maxEvents ?? DEFAULT_MAX_EVENTS;
|
||||
},
|
||||
getEndIndex() {
|
||||
return this.from + this.getMaxEvents();
|
||||
},
|
||||
calcNextFrom(backwards, tLength) {
|
||||
let newFrom = backwards ? this.from - PAG_LIMIT : this.from + PAG_LIMIT;
|
||||
if (!backwards && newFrom + this.getMaxEvents() > tLength) {
|
||||
newFrom = tLength - this.getMaxEvents();
|
||||
}
|
||||
if (newFrom < 0) newFrom = 0;
|
||||
return newFrom;
|
||||
},
|
||||
setFrom(from) {
|
||||
if (from < 0) {
|
||||
this.from = 0;
|
||||
return;
|
||||
}
|
||||
this.from = from;
|
||||
},
|
||||
};
|
||||
|
||||
function useTimeline(roomTimeline, eventId, readEventStore) {
|
||||
function useTimeline(roomTimeline, eventId, readUptoEvtStore, eventLimitRef) {
|
||||
const [timelineInfo, setTimelineInfo] = useState(null);
|
||||
|
||||
const setEventTimeline = async (eId) => {
|
||||
@@ -309,6 +133,7 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const limit = eventLimitRef.current;
|
||||
const initTimeline = (eId) => {
|
||||
// NOTICE: eId can be id of readUpto, reply or specific event.
|
||||
// readUpTo: when user click jump to unread message button.
|
||||
@@ -320,20 +145,19 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
|
||||
|
||||
if (isSpecificEvent) {
|
||||
focusEventIndex = roomTimeline.getEventIndex(eId);
|
||||
} else if (!readEventStore.getItem()) {
|
||||
}
|
||||
if (!readUptoEvtStore.getItem() && roomTimeline.hasEventInTimeline(readUpToId)) {
|
||||
// either opening live timeline or jump to unread.
|
||||
focusEventIndex = roomTimeline.getUnreadEventIndex(readUpToId);
|
||||
if (roomTimeline.hasEventInTimeline(readUpToId)) {
|
||||
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
|
||||
}
|
||||
} else {
|
||||
focusEventIndex = roomTimeline.getUnreadEventIndex(readEventStore.getItem().getId());
|
||||
readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
|
||||
}
|
||||
if (readUptoEvtStore.getItem() && !isSpecificEvent) {
|
||||
focusEventIndex = roomTimeline.getUnreadEventIndex(readUptoEvtStore.getItem().getId());
|
||||
}
|
||||
|
||||
if (focusEventIndex > -1) {
|
||||
limit.setFrom(focusEventIndex - Math.round(limit.getMaxEvents() / 2));
|
||||
limit.setFrom(focusEventIndex - Math.round(limit.maxEvents / 2));
|
||||
} else {
|
||||
limit.setFrom(roomTimeline.timeline.length - limit.getMaxEvents());
|
||||
limit.setFrom(roomTimeline.timeline.length - limit.maxEvents);
|
||||
}
|
||||
setTimelineInfo({ focusEventId: isSpecificEvent ? eId : null });
|
||||
};
|
||||
@@ -342,7 +166,6 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
|
||||
setEventTimeline(eventId);
|
||||
return () => {
|
||||
roomTimeline.removeListener(cons.events.roomTimeline.READY, initTimeline);
|
||||
roomTimeline.removeInternalListeners();
|
||||
limit.setFrom(0);
|
||||
};
|
||||
}, [roomTimeline, eventId]);
|
||||
@@ -350,36 +173,45 @@ function useTimeline(roomTimeline, eventId, readEventStore) {
|
||||
return timelineInfo;
|
||||
}
|
||||
|
||||
function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) {
|
||||
function usePaginate(
|
||||
roomTimeline,
|
||||
readUptoEvtStore,
|
||||
forceUpdateLimit,
|
||||
timelineScrollRef,
|
||||
eventLimitRef,
|
||||
) {
|
||||
const [info, setInfo] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOnPagination = (backwards, loaded) => {
|
||||
const handlePaginatedFromServer = (backwards, loaded) => {
|
||||
const limit = eventLimitRef.current;
|
||||
if (loaded === 0) return;
|
||||
if (!readEventStore.getItem()) {
|
||||
if (!readUptoEvtStore.getItem()) {
|
||||
const readUpToId = roomTimeline.getReadUpToEventId();
|
||||
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
|
||||
readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
|
||||
}
|
||||
limit.setFrom(limit.calcNextFrom(backwards, roomTimeline.timeline.length));
|
||||
limit.paginate(backwards, PAG_LIMIT, roomTimeline.timeline.length);
|
||||
setTimeout(() => setInfo({
|
||||
backwards,
|
||||
loaded,
|
||||
}));
|
||||
};
|
||||
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handleOnPagination);
|
||||
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer);
|
||||
return () => {
|
||||
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handleOnPagination);
|
||||
roomTimeline.on(cons.events.roomTimeline.PAGINATED, handlePaginatedFromServer);
|
||||
};
|
||||
}, [roomTimeline]);
|
||||
|
||||
const autoPaginate = useCallback(async () => {
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
const limit = eventLimitRef.current;
|
||||
if (roomTimeline.isOngoingPagination) return;
|
||||
const tLength = roomTimeline.timeline.length;
|
||||
|
||||
if (timelineScroll.bottom < SCROLL_TRIGGER_POS) {
|
||||
if (limit.getEndIndex() < tLength) {
|
||||
if (limit.length < tLength) {
|
||||
// paginate from memory
|
||||
limit.setFrom(limit.calcNextFrom(false, tLength));
|
||||
limit.paginate(false, PAG_LIMIT, tLength);
|
||||
forceUpdateLimit();
|
||||
} else if (roomTimeline.canPaginateForward()) {
|
||||
// paginate from server.
|
||||
@@ -390,7 +222,7 @@ function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) {
|
||||
if (timelineScroll.top < SCROLL_TRIGGER_POS) {
|
||||
if (limit.from > 0) {
|
||||
// paginate from memory
|
||||
limit.setFrom(limit.calcNextFrom(true, tLength));
|
||||
limit.paginate(true, PAG_LIMIT, tLength);
|
||||
forceUpdateLimit();
|
||||
} else if (roomTimeline.canPaginateBackward()) {
|
||||
// paginate from server.
|
||||
@@ -402,16 +234,25 @@ function usePaginate(roomTimeline, readEventStore, forceUpdateLimit) {
|
||||
return [info, autoPaginate];
|
||||
}
|
||||
|
||||
function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdateLimit) {
|
||||
function useHandleScroll(
|
||||
roomTimeline,
|
||||
autoPaginate,
|
||||
readUptoEvtStore,
|
||||
forceUpdateLimit,
|
||||
timelineScrollRef,
|
||||
eventLimitRef,
|
||||
) {
|
||||
const handleScroll = useCallback(() => {
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
const limit = eventLimitRef.current;
|
||||
requestAnimationFrame(() => {
|
||||
// emit event to toggle scrollToBottom button visibility
|
||||
const isAtBottom = (
|
||||
timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()
|
||||
&& limit.getEndIndex() >= roomTimeline.timeline.length
|
||||
&& limit.length >= roomTimeline.timeline.length
|
||||
);
|
||||
roomTimeline.emit(cons.events.roomTimeline.AT_BOTTOM, isAtBottom);
|
||||
if (isAtBottom && readEventStore.getItem()) {
|
||||
if (isAtBottom && readUptoEvtStore.getItem()) {
|
||||
requestAnimationFrame(() => roomTimeline.markAllAsRead());
|
||||
}
|
||||
});
|
||||
@@ -419,11 +260,13 @@ function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdate
|
||||
}, [roomTimeline]);
|
||||
|
||||
const handleScrollToLive = useCallback(() => {
|
||||
if (readEventStore.getItem()) {
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
const limit = eventLimitRef.current;
|
||||
if (readUptoEvtStore.getItem()) {
|
||||
requestAnimationFrame(() => roomTimeline.markAllAsRead());
|
||||
}
|
||||
if (roomTimeline.isServingLiveTimeline()) {
|
||||
limit.setFrom(roomTimeline.timeline.length - limit.getMaxEvents());
|
||||
limit.setFrom(roomTimeline.timeline.length - limit.maxEvents);
|
||||
timelineScroll.scrollToBottom();
|
||||
forceUpdateLimit();
|
||||
return;
|
||||
@@ -434,48 +277,46 @@ function useHandleScroll(roomTimeline, autoPaginate, readEventStore, forceUpdate
|
||||
return [handleScroll, handleScrollToLive];
|
||||
}
|
||||
|
||||
function useEventArrive(roomTimeline, readEventStore) {
|
||||
function useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, eventLimitRef) {
|
||||
const myUserId = initMatrix.matrixClient.getUserId();
|
||||
const [newEvent, setEvent] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const sendReadReceipt = (event) => {
|
||||
if (event.isSending()) return;
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
const limit = eventLimitRef.current;
|
||||
const trySendReadReceipt = (event) => {
|
||||
if (myUserId === event.getSender()) {
|
||||
roomTimeline.markAllAsRead();
|
||||
requestAnimationFrame(() => roomTimeline.markAllAsRead());
|
||||
return;
|
||||
}
|
||||
const readUpToEvent = readEventStore.getItem();
|
||||
const readUpToEvent = readUptoEvtStore.getItem();
|
||||
const readUpToId = roomTimeline.getReadUpToEventId();
|
||||
const isUnread = readUpToEvent ? readUpToEvent?.getId() === readUpToId : true;
|
||||
|
||||
// if user doesn't have focus on app don't mark messages as read.
|
||||
if (document.visibilityState === 'hidden' || timelineScroll.bottom >= 16) {
|
||||
if (readUpToEvent === readUpToId) return;
|
||||
readEventStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
|
||||
if (isUnread === false) {
|
||||
if (document.visibilityState === 'visible' && timelineScroll.bottom < 16) {
|
||||
requestAnimationFrame(() => roomTimeline.markAllAsRead());
|
||||
} else {
|
||||
readUptoEvtStore.setItem(roomTimeline.findEventByIdInTimelineSet(readUpToId));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// user has not mark room as read
|
||||
const isUnreadMsg = readUpToEvent?.getId() === readUpToId;
|
||||
if (!isUnreadMsg) {
|
||||
roomTimeline.markAllAsRead();
|
||||
}
|
||||
const { timeline } = roomTimeline;
|
||||
const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToEvent?.getId();
|
||||
const unreadMsgIsLast = timeline[timeline.length - 2].getId() === readUpToId;
|
||||
if (unreadMsgIsLast) {
|
||||
roomTimeline.markAllAsRead();
|
||||
requestAnimationFrame(() => roomTimeline.markAllAsRead());
|
||||
}
|
||||
};
|
||||
|
||||
const handleEvent = (event) => {
|
||||
const tLength = roomTimeline.timeline.length;
|
||||
const isUserViewingLive = (
|
||||
roomTimeline.isServingLiveTimeline()
|
||||
&& limit.getEndIndex() >= tLength - 1
|
||||
&& timelineScroll.bottom < SCROLL_TRIGGER_POS
|
||||
);
|
||||
if (isUserViewingLive) {
|
||||
limit.setFrom(tLength - limit.getMaxEvents());
|
||||
sendReadReceipt(event);
|
||||
const isViewingLive = roomTimeline.isServingLiveTimeline() && limit.length >= tLength - 1;
|
||||
const isAttached = timelineScroll.bottom < SCROLL_TRIGGER_POS;
|
||||
|
||||
if (isViewingLive && isAttached) {
|
||||
limit.setFrom(tLength - limit.maxEvents);
|
||||
trySendReadReceipt(event);
|
||||
setEvent(event);
|
||||
return;
|
||||
}
|
||||
@@ -484,11 +325,8 @@ function useEventArrive(roomTimeline, readEventStore) {
|
||||
setEvent(event);
|
||||
return;
|
||||
}
|
||||
const isUserDitchedLive = (
|
||||
roomTimeline.isServingLiveTimeline()
|
||||
&& limit.getEndIndex() >= tLength - 1
|
||||
);
|
||||
if (isUserDitchedLive) {
|
||||
|
||||
if (isViewingLive) {
|
||||
// This stateUpdate will help to put the
|
||||
// loading msg placeholder at bottom
|
||||
setEvent(event);
|
||||
@@ -505,39 +343,52 @@ function useEventArrive(roomTimeline, readEventStore) {
|
||||
};
|
||||
}, [roomTimeline]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!roomTimeline.initialized) return;
|
||||
if (timelineScroll.bottom < 16
|
||||
&& !roomTimeline.canPaginateForward()
|
||||
&& document.visibilityState === 'visible') {
|
||||
timelineScroll.scrollToBottom();
|
||||
} else {
|
||||
timelineScroll.tryRestoringScroll();
|
||||
}
|
||||
}, [newEvent, roomTimeline]);
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
let jumpToItemIndex = -1;
|
||||
|
||||
function RoomViewContent({ eventId, roomTimeline }) {
|
||||
const [throttle] = useState(new Throttle());
|
||||
|
||||
const timelineSVRef = useRef(null);
|
||||
const readEventStore = useStore(roomTimeline);
|
||||
const timelineInfo = useTimeline(roomTimeline, eventId, readEventStore);
|
||||
const timelineScrollRef = useRef(null);
|
||||
const eventLimitRef = useRef(null);
|
||||
|
||||
const readUptoEvtStore = useStore(roomTimeline);
|
||||
const [onLimitUpdate, forceUpdateLimit] = useForceUpdate();
|
||||
const [paginateInfo, autoPaginate] = usePaginate(roomTimeline, readEventStore, forceUpdateLimit);
|
||||
const [handleScroll, handleScrollToLive] = useHandleScroll(
|
||||
roomTimeline, autoPaginate, readEventStore, forceUpdateLimit,
|
||||
|
||||
const timelineInfo = useTimeline(roomTimeline, eventId, readUptoEvtStore, eventLimitRef);
|
||||
const [paginateInfo, autoPaginate] = usePaginate(
|
||||
roomTimeline,
|
||||
readUptoEvtStore,
|
||||
forceUpdateLimit,
|
||||
timelineScrollRef,
|
||||
eventLimitRef,
|
||||
);
|
||||
useEventArrive(roomTimeline, readEventStore);
|
||||
const [handleScroll, handleScrollToLive] = useHandleScroll(
|
||||
roomTimeline,
|
||||
autoPaginate,
|
||||
readUptoEvtStore,
|
||||
forceUpdateLimit,
|
||||
timelineScrollRef,
|
||||
eventLimitRef,
|
||||
);
|
||||
const newEvent = useEventArrive(roomTimeline, readUptoEvtStore, timelineScrollRef, eventLimitRef);
|
||||
|
||||
const { timeline } = roomTimeline;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!roomTimeline.initialized) {
|
||||
timelineScroll = new TimelineScroll(timelineSVRef.current);
|
||||
timelineScrollRef.current = new TimelineScroll(timelineSVRef.current);
|
||||
eventLimitRef.current = new EventLimit();
|
||||
}
|
||||
});
|
||||
|
||||
// when active timeline changes
|
||||
useEffect(() => {
|
||||
if (!roomTimeline.initialized) return undefined;
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
|
||||
if (timeline.length > 0) {
|
||||
if (jumpToItemIndex === -1) {
|
||||
@@ -547,7 +398,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
}
|
||||
if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward()) {
|
||||
const readUpToId = roomTimeline.getReadUpToEventId();
|
||||
if (readEventStore.getItem()?.getId() === readUpToId || readUpToId === null) {
|
||||
if (readUptoEvtStore.getItem()?.getId() === readUpToId || readUpToId === null) {
|
||||
requestAnimationFrame(() => roomTimeline.markAllAsRead());
|
||||
}
|
||||
}
|
||||
@@ -555,11 +406,9 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
}
|
||||
autoPaginate();
|
||||
|
||||
timelineScroll.on('scroll', handleScroll);
|
||||
roomTimeline.on(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive);
|
||||
return () => {
|
||||
if (timelineSVRef.current === null) return;
|
||||
timelineScroll.removeListener('scroll', handleScroll);
|
||||
roomTimeline.removeListener(cons.events.roomTimeline.SCROLL_TO_LIVE, handleScrollToLive);
|
||||
};
|
||||
}, [timelineInfo]);
|
||||
@@ -567,6 +416,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
// when paginating from server
|
||||
useEffect(() => {
|
||||
if (!roomTimeline.initialized) return;
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
timelineScroll.tryRestoringScroll();
|
||||
autoPaginate();
|
||||
}, [paginateInfo]);
|
||||
@@ -574,28 +424,45 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
// when paginating locally
|
||||
useEffect(() => {
|
||||
if (!roomTimeline.initialized) return;
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
timelineScroll.tryRestoringScroll();
|
||||
}, [onLimitUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
if (!roomTimeline.initialized) return;
|
||||
if (timelineScroll.bottom < 16 && !roomTimeline.canPaginateForward() && document.visibilityState === 'visible') {
|
||||
timelineScroll.scrollToBottom();
|
||||
} else {
|
||||
timelineScroll.tryRestoringScroll();
|
||||
}
|
||||
}, [newEvent]);
|
||||
|
||||
const handleTimelineScroll = (event) => {
|
||||
const { target } = event;
|
||||
if (!target) return;
|
||||
throttle._(() => timelineScroll?.calcScroll(), 400)(target);
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
if (!event.target) return;
|
||||
|
||||
throttle._(() => {
|
||||
const backwards = timelineScroll?.calcScroll();
|
||||
if (typeof backwards !== 'boolean') return;
|
||||
handleScroll(backwards);
|
||||
}, 200)();
|
||||
};
|
||||
|
||||
const renderTimeline = () => {
|
||||
const tl = [];
|
||||
const limit = eventLimitRef.current;
|
||||
|
||||
let itemCountIndex = 0;
|
||||
jumpToItemIndex = -1;
|
||||
const readEvent = readEventStore.getItem();
|
||||
const readUptoEvent = readUptoEvtStore.getItem();
|
||||
let unreadDivider = false;
|
||||
|
||||
if (roomTimeline.canPaginateBackward() || limit.from > 0) {
|
||||
tl.push(loadingMsgPlaceholders(1, PLACEHOLDER_COUNT));
|
||||
itemCountIndex += PLACEHOLDER_COUNT;
|
||||
}
|
||||
for (let i = limit.from; i < limit.getEndIndex(); i += 1) {
|
||||
for (let i = limit.from; i < limit.length; i += 1) {
|
||||
if (i >= timeline.length) break;
|
||||
const mEvent = timeline[i];
|
||||
const prevMEvent = timeline[i - 1] ?? null;
|
||||
@@ -614,9 +481,9 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
|
||||
let isNewEvent = false;
|
||||
if (!unreadDivider) {
|
||||
unreadDivider = (readEvent
|
||||
&& prevMEvent?.getTs() <= readEvent.getTs()
|
||||
&& readEvent.getTs() < mEvent.getTs());
|
||||
unreadDivider = (readUptoEvent
|
||||
&& prevMEvent?.getTs() <= readUptoEvent.getTs()
|
||||
&& readUptoEvent.getTs() < mEvent.getTs());
|
||||
if (unreadDivider) {
|
||||
isNewEvent = true;
|
||||
tl.push(<Divider key={`new-${mEvent.getId()}`} variant="positive" text="New messages" />);
|
||||
@@ -637,7 +504,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
tl.push(renderEvent(roomTimeline, mEvent, isNewEvent ? null : prevMEvent, isFocus));
|
||||
itemCountIndex += 1;
|
||||
}
|
||||
if (roomTimeline.canPaginateForward() || limit.getEndIndex() < timeline.length) {
|
||||
if (roomTimeline.canPaginateForward() || limit.length < timeline.length) {
|
||||
tl.push(loadingMsgPlaceholders(2, PLACEHOLDER_COUNT));
|
||||
}
|
||||
|
||||
|
||||
136
src/app/organisms/room/TimelineScroll.js
Normal file
136
src/app/organisms/room/TimelineScroll.js
Normal 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;
|
||||
@@ -2,8 +2,8 @@ import { openSearch, toggleRoomSettings } from '../action/navigation';
|
||||
import navigation from '../state/navigation';
|
||||
|
||||
function listenKeyboard(event) {
|
||||
// Ctrl +
|
||||
if (event.ctrlKey) {
|
||||
// Ctrl/Cmd +
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
// k - for search Modal
|
||||
if (event.keyCode === 75) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -17,6 +17,17 @@ function isNotifEvent(mEvent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isMutedRule(rule) {
|
||||
return rule.actions[0] === 'dont_notify' && rule.conditions[0].kind === 'event_match';
|
||||
}
|
||||
|
||||
function findMutedRule(overrideRules, roomId) {
|
||||
return overrideRules.find((rule) => (
|
||||
rule.rule_id === roomId
|
||||
&& isMutedRule(rule)
|
||||
));
|
||||
}
|
||||
|
||||
class Notifications extends EventEmitter {
|
||||
constructor(roomList) {
|
||||
super();
|
||||
@@ -39,11 +50,12 @@ class Notifications extends EventEmitter {
|
||||
_initNoti() {
|
||||
const addNoti = (roomId) => {
|
||||
const room = this.matrixClient.getRoom(roomId);
|
||||
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) return;
|
||||
if (this.doesRoomHaveUnread(room) === false) return;
|
||||
|
||||
const total = room.getUnreadNotificationCount('total');
|
||||
const highlight = room.getUnreadNotificationCount('highlight');
|
||||
const noti = this.getNoti(room.roomId);
|
||||
this._setNoti(room.roomId, total - noti.total, highlight - noti.highlight);
|
||||
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
||||
};
|
||||
[...this.roomList.rooms].forEach(addNoti);
|
||||
[...this.roomList.directs].forEach(addNoti);
|
||||
@@ -66,6 +78,22 @@ class Notifications extends EventEmitter {
|
||||
return true;
|
||||
}
|
||||
|
||||
getNotiType(roomId) {
|
||||
const mx = this.matrixClient;
|
||||
const pushRule = mx.getRoomPushRule('global', roomId);
|
||||
|
||||
if (pushRule === undefined) {
|
||||
const overrideRules = mx.getAccountData('m.push_rules')?.getContent()?.global?.override;
|
||||
if (overrideRules === undefined) return cons.notifs.DEFAULT;
|
||||
|
||||
const isMuted = findMutedRule(overrideRules, roomId);
|
||||
|
||||
return isMuted ? cons.notifs.MUTE : cons.notifs.DEFAULT;
|
||||
}
|
||||
if (pushRule.actions[0] === 'notify') return cons.notifs.ALL_MESSAGES;
|
||||
return cons.notifs.MENTIONS_AND_KEYWORDS;
|
||||
}
|
||||
|
||||
getNoti(roomId) {
|
||||
return this.roomIdToNoti.get(roomId) || { total: 0, highlight: 0, from: null };
|
||||
}
|
||||
@@ -96,74 +124,64 @@ class Notifications extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
_getAllParentIds(roomId) {
|
||||
let allParentIds = this.roomList.roomIdToParents.get(roomId);
|
||||
if (allParentIds === undefined) return new Set();
|
||||
const parentIds = [...allParentIds];
|
||||
_setNoti(roomId, total, highlight) {
|
||||
const addNoti = (id, t, h, fromId) => {
|
||||
const prevTotal = this.roomIdToNoti.get(id)?.total ?? null;
|
||||
const noti = this.getNoti(id);
|
||||
|
||||
parentIds.forEach((pId) => {
|
||||
allParentIds = new Set(
|
||||
[...allParentIds, ...this._getAllParentIds(pId)],
|
||||
);
|
||||
noti.total += t;
|
||||
noti.highlight += h;
|
||||
|
||||
if (fromId) {
|
||||
if (noti.from === null) noti.from = new Set();
|
||||
noti.from.add(fromId);
|
||||
}
|
||||
this.roomIdToNoti.set(id, noti);
|
||||
this.emit(cons.events.notifications.NOTI_CHANGED, id, noti.total, prevTotal);
|
||||
};
|
||||
|
||||
const noti = this.getNoti(roomId);
|
||||
const addT = total - noti.total;
|
||||
const addH = highlight - noti.highlight;
|
||||
if (addT < 0 || addH < 0) return;
|
||||
|
||||
addNoti(roomId, addT, addH);
|
||||
const allParentSpaces = this.roomList.getAllParentSpaces(roomId);
|
||||
allParentSpaces.forEach((spaceId) => {
|
||||
addNoti(spaceId, addT, addH, roomId);
|
||||
});
|
||||
|
||||
return allParentIds;
|
||||
}
|
||||
|
||||
_setNoti(roomId, total, highlight, childId) {
|
||||
const prevTotal = this.roomIdToNoti.get(roomId)?.total ?? null;
|
||||
const noti = this.getNoti(roomId);
|
||||
_deleteNoti(roomId, total, highlight) {
|
||||
const removeNoti = (id, t, h, fromId) => {
|
||||
if (this.roomIdToNoti.has(id) === false) return;
|
||||
|
||||
if (!childId || this._remainingParentIds?.has(roomId)) {
|
||||
noti.total += total;
|
||||
noti.highlight += highlight;
|
||||
}
|
||||
if (childId) {
|
||||
if (noti.from === null) noti.from = new Set();
|
||||
noti.from.add(childId);
|
||||
}
|
||||
const noti = this.getNoti(id);
|
||||
const prevTotal = noti.total;
|
||||
noti.total -= t;
|
||||
noti.highlight -= h;
|
||||
if (noti.total < 0) {
|
||||
noti.total = 0;
|
||||
noti.highlight = 0;
|
||||
}
|
||||
if (fromId && noti.from !== null) {
|
||||
if (!this.hasNoti(fromId)) noti.from.delete(fromId);
|
||||
}
|
||||
if (noti.from === null || noti.from.size === 0) {
|
||||
this.roomIdToNoti.delete(id);
|
||||
this.emit(cons.events.notifications.FULL_READ, id);
|
||||
this.emit(cons.events.notifications.NOTI_CHANGED, id, null, prevTotal);
|
||||
} else {
|
||||
this.roomIdToNoti.set(id, noti);
|
||||
this.emit(cons.events.notifications.NOTI_CHANGED, id, noti.total, prevTotal);
|
||||
}
|
||||
};
|
||||
|
||||
this.roomIdToNoti.set(roomId, noti);
|
||||
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, noti.total, prevTotal);
|
||||
|
||||
if (!childId) this._remainingParentIds = this._getAllParentIds(roomId);
|
||||
else this._remainingParentIds.delete(roomId);
|
||||
|
||||
const parentIds = this.roomList.roomIdToParents.get(roomId);
|
||||
if (typeof parentIds === 'undefined') {
|
||||
if (!childId) this._remainingParentIds = undefined;
|
||||
return;
|
||||
}
|
||||
[...parentIds].forEach((parentId) => this._setNoti(parentId, total, highlight, roomId));
|
||||
if (!childId) this._remainingParentIds = undefined;
|
||||
}
|
||||
|
||||
_deleteNoti(roomId, total, highlight, childId) {
|
||||
if (this.roomIdToNoti.has(roomId) === false) return;
|
||||
|
||||
const noti = this.getNoti(roomId);
|
||||
const prevTotal = noti.total;
|
||||
noti.total -= total;
|
||||
noti.highlight -= highlight;
|
||||
if (noti.total < 0) {
|
||||
noti.total = 0;
|
||||
noti.highlight = 0;
|
||||
}
|
||||
if (childId && noti.from !== null) {
|
||||
if (!this.hasNoti(childId)) noti.from.delete(childId);
|
||||
}
|
||||
if (noti.from === null || noti.from.size === 0) {
|
||||
this.roomIdToNoti.delete(roomId);
|
||||
this.emit(cons.events.notifications.FULL_READ, roomId);
|
||||
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, null, prevTotal);
|
||||
} else {
|
||||
this.roomIdToNoti.set(roomId, noti);
|
||||
this.emit(cons.events.notifications.NOTI_CHANGED, roomId, noti.total, prevTotal);
|
||||
}
|
||||
|
||||
const parentIds = this.roomList.roomIdToParents.get(roomId);
|
||||
if (typeof parentIds === 'undefined') return;
|
||||
[...parentIds].forEach((parentId) => this._deleteNoti(parentId, total, highlight, roomId));
|
||||
removeNoti(roomId, total, highlight);
|
||||
const allParentSpaces = this.roomList.getAllParentSpaces(roomId);
|
||||
allParentSpaces.forEach((spaceId) => {
|
||||
removeNoti(spaceId, total, highlight, roomId);
|
||||
});
|
||||
}
|
||||
|
||||
async _displayPopupNoti(mEvent, room) {
|
||||
@@ -204,7 +222,9 @@ class Notifications extends EventEmitter {
|
||||
|
||||
_listenEvents() {
|
||||
this.matrixClient.on('Room.timeline', (mEvent, room) => {
|
||||
if (room.isSpaceRoom()) return;
|
||||
if (!isNotifEvent(mEvent)) return;
|
||||
|
||||
const liveEvents = room.getLiveTimeline().getEvents();
|
||||
|
||||
const lastTimelineEvent = liveEvents[liveEvents.length - 1];
|
||||
@@ -214,16 +234,58 @@ class Notifications extends EventEmitter {
|
||||
const total = room.getUnreadNotificationCount('total');
|
||||
const highlight = room.getUnreadNotificationCount('highlight');
|
||||
|
||||
const noti = this.getNoti(room.roomId);
|
||||
this._setNoti(room.roomId, total - noti.total, highlight - noti.highlight);
|
||||
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) {
|
||||
this.deleteNoti(room.roomId, total ?? 0, highlight ?? 0);
|
||||
return;
|
||||
}
|
||||
|
||||
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
||||
|
||||
if (this.matrixClient.getSyncState() === 'SYNCING') {
|
||||
this._displayPopupNoti(mEvent, room);
|
||||
}
|
||||
});
|
||||
|
||||
this.matrixClient.on('accountData', (mEvent, oldMEvent) => {
|
||||
if (mEvent.getType() === 'm.push_rules') {
|
||||
const override = mEvent?.getContent()?.global?.override;
|
||||
const oldOverride = oldMEvent?.getContent()?.global?.override;
|
||||
if (!override || !oldOverride) return;
|
||||
|
||||
const isMuteToggled = (rule, otherOverride) => {
|
||||
const roomId = rule.rule_id;
|
||||
const room = this.matrixClient.getRoom(roomId);
|
||||
if (room === null) return false;
|
||||
if (room.isSpaceRoom()) return false;
|
||||
|
||||
const isMuted = isMutedRule(rule);
|
||||
if (!isMuted) return false;
|
||||
const isOtherMuted = findMutedRule(otherOverride, roomId);
|
||||
if (isOtherMuted) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const mutedRules = override.filter((rule) => isMuteToggled(rule, oldOverride));
|
||||
const unMutedRules = oldOverride.filter((rule) => isMuteToggled(rule, override));
|
||||
|
||||
mutedRules.forEach((rule) => {
|
||||
this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, true);
|
||||
this.deleteNoti(rule.rule_id);
|
||||
});
|
||||
unMutedRules.forEach((rule) => {
|
||||
this.emit(cons.events.notifications.MUTE_TOGGLED, rule.rule_id, false);
|
||||
const room = this.matrixClient.getRoom(rule.rule_id);
|
||||
if (!this.doesRoomHaveUnread(room)) return;
|
||||
const total = room.getUnreadNotificationCount('total');
|
||||
const highlight = room.getUnreadNotificationCount('highlight');
|
||||
this._setNoti(room.roomId, total ?? 0, highlight ?? 0);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.matrixClient.on('Room.receipt', (mEvent, room) => {
|
||||
if (mEvent.getType() === 'm.receipt') {
|
||||
if (room.isSpaceRoom()) return;
|
||||
const content = mEvent.getContent();
|
||||
const readedEventId = Object.keys(content)[0];
|
||||
const readerUserId = Object.keys(content[readedEventId]['m.read'])[0];
|
||||
|
||||
@@ -36,6 +36,14 @@ class RoomList extends EventEmitter {
|
||||
return !this.roomIdToParents.has(roomId);
|
||||
}
|
||||
|
||||
getOrphanSpaces() {
|
||||
return [...this.spaces].filter((roomId) => !this.roomIdToParents.has(roomId));
|
||||
}
|
||||
|
||||
getOrphanRooms() {
|
||||
return [...this.rooms].filter((roomId) => !this.roomIdToParents.has(roomId));
|
||||
}
|
||||
|
||||
getOrphans() {
|
||||
const rooms = [...this.spaces].concat([...this.rooms]);
|
||||
return rooms.filter((roomId) => !this.roomIdToParents.has(roomId));
|
||||
@@ -43,13 +51,15 @@ class RoomList extends EventEmitter {
|
||||
|
||||
getSpaceChildren(roomId) {
|
||||
const space = this.matrixClient.getRoom(roomId);
|
||||
if (space === null) return null;
|
||||
const mSpaceChild = space?.currentState.getStateEvents('m.space.child');
|
||||
const children = mSpaceChild?.map((mEvent) => {
|
||||
|
||||
const children = [];
|
||||
mSpaceChild.forEach((mEvent) => {
|
||||
const childId = mEvent.event.state_key;
|
||||
if (isMEventSpaceChild(mEvent)) return childId;
|
||||
return null;
|
||||
if (isMEventSpaceChild(mEvent)) children.push(childId);
|
||||
});
|
||||
return children?.filter((childId) => childId !== null);
|
||||
return children;
|
||||
}
|
||||
|
||||
getCategorizedSpaces(spaceIds) {
|
||||
@@ -69,7 +79,7 @@ class RoomList extends EventEmitter {
|
||||
else mappedChild.add(childId);
|
||||
});
|
||||
};
|
||||
spaceIds.map(categorizeSpace);
|
||||
spaceIds.forEach(categorizeSpace);
|
||||
|
||||
return categorized;
|
||||
}
|
||||
@@ -89,32 +99,40 @@ class RoomList extends EventEmitter {
|
||||
if (parents.size === 0) this.roomIdToParents.delete(roomId);
|
||||
}
|
||||
|
||||
getParentSpaces(roomId) {
|
||||
let parentIds = this.roomIdToParents.get(roomId);
|
||||
if (parentIds) {
|
||||
[...parentIds].forEach((parentId) => {
|
||||
parentIds = new Set([...parentIds, ...this.getParentSpaces(parentId)]);
|
||||
});
|
||||
}
|
||||
return parentIds || new Set();
|
||||
getAllParentSpaces(roomId) {
|
||||
const allParents = new Set();
|
||||
|
||||
const addAllParentIds = (rId) => {
|
||||
if (allParents.has(rId)) return;
|
||||
allParents.add(rId);
|
||||
|
||||
const parents = this.roomIdToParents.get(rId);
|
||||
if (parents === undefined) return;
|
||||
|
||||
parents.forEach((id) => addAllParentIds(id));
|
||||
};
|
||||
addAllParentIds(roomId);
|
||||
allParents.delete(roomId);
|
||||
return allParents;
|
||||
}
|
||||
|
||||
addToSpaces(roomId) {
|
||||
this.spaces.add(roomId);
|
||||
const allParentSpaces = this.getParentSpaces(roomId);
|
||||
|
||||
const allParentSpaces = this.getAllParentSpaces(roomId);
|
||||
const spaceChildren = this.getSpaceChildren(roomId);
|
||||
spaceChildren?.forEach((childRoomId) => {
|
||||
if (allParentSpaces.has(childRoomId)) return;
|
||||
this.addToRoomIdToParents(childRoomId, roomId);
|
||||
spaceChildren?.forEach((childId) => {
|
||||
if (allParentSpaces.has(childId)) return;
|
||||
this.addToRoomIdToParents(childId, roomId);
|
||||
});
|
||||
}
|
||||
|
||||
deleteFromSpaces(roomId) {
|
||||
this.spaces.delete(roomId);
|
||||
|
||||
const spaceChildren = this.getSpaceChildren(roomId);
|
||||
spaceChildren?.forEach((childRoomId) => {
|
||||
this.removeFromRoomIdToParents(childRoomId, roomId);
|
||||
spaceChildren?.forEach((childId) => {
|
||||
this.removeFromRoomIdToParents(childId, roomId);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -254,12 +272,17 @@ class RoomList extends EventEmitter {
|
||||
|
||||
this.matrixClient.on('RoomState.events', (mEvent, state) => {
|
||||
if (mEvent.getType() === 'm.space.child') {
|
||||
const { event } = mEvent;
|
||||
const roomId = mEvent.event.room_id;
|
||||
const childId = mEvent.event.state_key;
|
||||
if (isMEventSpaceChild(mEvent)) {
|
||||
const allParentSpaces = this.getParentSpaces(event.room_id);
|
||||
if (allParentSpaces.has(event.state_key)) return;
|
||||
this.addToRoomIdToParents(event.state_key, event.room_id);
|
||||
} else this.removeFromRoomIdToParents(event.state_key, event.room_id);
|
||||
const allParentSpaces = this.getAllParentSpaces(roomId);
|
||||
// only add if it doesn't make a cycle
|
||||
if (!allParentSpaces.has(childId)) {
|
||||
this.addToRoomIdToParents(childId, roomId);
|
||||
}
|
||||
} else {
|
||||
this.removeFromRoomIdToParents(childId, roomId);
|
||||
}
|
||||
this.emit(cons.events.roomList.ROOMLIST_UPDATED);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,19 +154,19 @@ class RoomTimeline extends EventEmitter {
|
||||
this._populateAllLinkedEvents(this.activeTimeline);
|
||||
}
|
||||
|
||||
async _reset(eventId) {
|
||||
async _reset() {
|
||||
if (this.isEncrypted()) await this.decryptAllEventsOfTimeline(this.activeTimeline);
|
||||
this._populateTimelines();
|
||||
if (!this.initialized) {
|
||||
this.initialized = true;
|
||||
this._listenEvents();
|
||||
}
|
||||
this.emit(cons.events.roomTimeline.READY, eventId ?? null);
|
||||
}
|
||||
|
||||
async loadLiveTimeline() {
|
||||
this.activeTimeline = this.liveTimeline;
|
||||
await this._reset();
|
||||
this.emit(cons.events.roomTimeline.READY, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,7 +176,8 @@ class RoomTimeline extends EventEmitter {
|
||||
try {
|
||||
const eventTimeline = await this.matrixClient.getEventTimeline(timelineSet, eventId);
|
||||
this.activeTimeline = eventTimeline;
|
||||
await this._reset(eventId);
|
||||
await this._reset();
|
||||
this.emit(cons.events.roomTimeline.READY, eventId);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@@ -229,9 +230,18 @@ class RoomTimeline extends EventEmitter {
|
||||
|
||||
markAllAsRead() {
|
||||
const readEventId = this.getReadUpToEventId();
|
||||
const getLatestValidEvent = () => {
|
||||
for (let i = this.timeline.length - 1; i >= 0; i -= 1) {
|
||||
const latestEvent = this.timeline[i];
|
||||
if (latestEvent.getId() === readEventId) return null;
|
||||
if (!latestEvent.isSending()) return latestEvent;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
this.notifications.deleteNoti(this.roomId);
|
||||
if (this.timeline.length === 0) return;
|
||||
const latestEvent = this.timeline[this.timeline.length - 1];
|
||||
const latestEvent = getLatestValidEvent();
|
||||
if (latestEvent === null) return;
|
||||
if (readEventId === latestEvent.getId()) return;
|
||||
this.matrixClient.sendReadReceipt(latestEvent);
|
||||
this.emit(cons.events.roomTimeline.MARKED_AS_READ, latestEvent);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const cons = {
|
||||
version: '1.8.0',
|
||||
version: '1.8.2',
|
||||
secretKey: {
|
||||
ACCESS_TOKEN: 'cinny_access_token',
|
||||
DEVICE_ID: 'cinny_device_id',
|
||||
@@ -107,6 +107,7 @@ const cons = {
|
||||
notifications: {
|
||||
NOTI_CHANGED: 'NOTI_CHANGED',
|
||||
FULL_READ: 'FULL_READ',
|
||||
MUTE_TOGGLED: 'MUTE_TOGGLED',
|
||||
},
|
||||
roomTimeline: {
|
||||
READY: 'READY',
|
||||
|
||||
@@ -97,8 +97,8 @@ class Settings extends EventEmitter {
|
||||
if (typeof this.hideNickAvatarEvents === 'boolean') return this.hideNickAvatarEvents;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return false;
|
||||
if (typeof settings.hideNickAvatarEvents === 'undefined') return false;
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.hideNickAvatarEvents === 'undefined') return true;
|
||||
return settings.hideNickAvatarEvents;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user