mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-03-13 14:40:01 +01:00
Merge branch 'dev' into js-sdk-v34
This commit is contained in:
commit
0ca778caaf
24 changed files with 471 additions and 111 deletions
4
.github/workflows/build-pull-request.yml
vendored
4
.github/workflows/build-pull-request.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
||||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||||
run: npm run build
|
run: npm run build
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v4.3.4
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: preview
|
name: preview
|
||||||
path: dist
|
path: dist
|
||||||
|
@ -33,7 +33,7 @@ jobs:
|
||||||
- name: Save pr number
|
- name: Save pr number
|
||||||
run: echo ${PR_NUMBER} > ./pr.txt
|
run: echo ${PR_NUMBER} > ./pr.txt
|
||||||
- name: Upload pr number
|
- name: Upload pr number
|
||||||
uses: actions/upload-artifact@v4.3.4
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: pr
|
name: pr
|
||||||
path: ./pr.txt
|
path: ./pr.txt
|
||||||
|
|
2
.github/workflows/cla.yml
vendored
2
.github/workflows/cla.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
||||||
- name: 'CLA Assistant'
|
- name: 'CLA Assistant'
|
||||||
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||||
# Beta Release
|
# Beta Release
|
||||||
uses: cla-assistant/github-action@v2.4.0
|
uses: cla-assistant/github-action@v2.5.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# the below token should have repo scope and must be manually added by you in the repository's secret
|
# the below token should have repo scope and must be manually added by you in the repository's secret
|
||||||
|
|
2
.github/workflows/docker-pr.yml
vendored
2
.github/workflows/docker-pr.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
uses: docker/build-push-action@v6.5.0
|
uses: docker/build-push-action@v6.7.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
|
|
2
.github/workflows/prod-deploy.yml
vendored
2
.github/workflows/prod-deploy.yml
vendored
|
@ -90,7 +90,7 @@ jobs:
|
||||||
${{ secrets.DOCKER_USERNAME }}/cinny
|
${{ secrets.DOCKER_USERNAME }}/cinny
|
||||||
ghcr.io/${{ github.repository }}
|
ghcr.io/${{ github.repository }}
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v6.5.0
|
uses: docker/build-push-action@v6.7.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
cinnyapp@gmail.com.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
|
@ -1,8 +1,11 @@
|
||||||
server {
|
server {
|
||||||
location / {
|
listen 80;
|
||||||
root /usr/share/nginx/html;
|
listen [::]:80;
|
||||||
|
|
||||||
rewrite ^/config.json$ /config.json break;
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
rewrite ^/config.json$ /config.json break;
|
||||||
rewrite ^/manifest.json$ /manifest.json break;
|
rewrite ^/manifest.json$ /manifest.json break;
|
||||||
|
|
||||||
rewrite ^.*/olm.wasm$ /olm.wasm break;
|
rewrite ^.*/olm.wasm$ /olm.wasm break;
|
||||||
|
@ -12,5 +15,5 @@ server {
|
||||||
rewrite ^/assets/(.*)$ /assets/$1 break;
|
rewrite ^/assets/(.*)$ /assets/$1 break;
|
||||||
|
|
||||||
rewrite ^(.+)$ /index.html break;
|
rewrite ^(.+)$ /index.html break;
|
||||||
}
|
}
|
||||||
}
|
}
|
138
package-lock.json
generated
138
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.0.3",
|
"version": "4.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.0.3",
|
"version": "4.1.0",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
|
||||||
|
@ -37,6 +37,9 @@
|
||||||
"formik": "2.4.6",
|
"formik": "2.4.6",
|
||||||
"html-dom-parser": "4.0.0",
|
"html-dom-parser": "4.0.0",
|
||||||
"html-react-parser": "4.2.0",
|
"html-react-parser": "4.2.0",
|
||||||
|
"i18next": "23.12.2",
|
||||||
|
"i18next-browser-languagedetector": "8.0.0",
|
||||||
|
"i18next-http-backend": "2.5.2",
|
||||||
"immer": "9.0.16",
|
"immer": "9.0.16",
|
||||||
"is-hotkey": "0.2.0",
|
"is-hotkey": "0.2.0",
|
||||||
"jotai": "2.6.0",
|
"jotai": "2.6.0",
|
||||||
|
@ -54,6 +57,7 @@
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-error-boundary": "4.0.13",
|
"react-error-boundary": "4.0.13",
|
||||||
"react-google-recaptcha": "2.1.0",
|
"react-google-recaptcha": "2.1.0",
|
||||||
|
"react-i18next": "15.0.0",
|
||||||
"react-modal": "3.16.1",
|
"react-modal": "3.16.1",
|
||||||
"react-range": "1.8.14",
|
"react-range": "1.8.14",
|
||||||
"react-router-dom": "6.20.0",
|
"react-router-dom": "6.20.0",
|
||||||
|
@ -438,11 +442,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.20.6",
|
"version": "7.25.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
|
||||||
"integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
|
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.13.11"
|
"regenerator-runtime": "^0.14.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
|
@ -467,11 +472,6 @@
|
||||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
|
|
||||||
"version": "0.13.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
|
||||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
|
||||||
},
|
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.22.15",
|
"version": "7.22.15",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||||
|
@ -6141,6 +6141,15 @@
|
||||||
"entities": "^4.5.0"
|
"entities": "^4.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-parse-stringify": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"void-elements": "3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-react-parser": {
|
"node_modules/html-react-parser": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-4.2.0.tgz",
|
||||||
|
@ -6186,6 +6195,76 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "23.12.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.12.2.tgz",
|
||||||
|
"integrity": "sha512-XIeh5V+bi8SJSWGL3jqbTEBW5oD6rbP5L+E7dVQh1MNTxxYef0x15rhJVcRb7oiuq4jLtgy2SD8eFlf6P2cmqg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com/i18next.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-browser-languagedetector": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-http-backend": {
|
||||||
|
"version": "2.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.2.tgz",
|
||||||
|
"integrity": "sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-fetch": "4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-http-backend/node_modules/cross-fetch": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-http-backend/node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
@ -7732,6 +7811,28 @@
|
||||||
"react": ">=16.4.1"
|
"react": ">=16.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-i18next": {
|
||||||
|
"version": "15.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.0.tgz",
|
||||||
|
"integrity": "sha512-2O3IgF4zivg57Q6p6i+ChDgJ371IDcEWbuWC6gvoh5NbkDMs0Q+O7RPr4v61+Se32E0V+LmtwePAeqWZW0bi6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.24.8",
|
||||||
|
"html-parse-stringify": "^3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"i18next": ">= 23.2.3",
|
||||||
|
"react": ">= 16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
@ -7839,6 +7940,12 @@
|
||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.2",
|
"version": "1.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
|
||||||
|
@ -9310,6 +9417,15 @@
|
||||||
"@esbuild/win32-x64": "0.19.12"
|
"@esbuild/win32-x64": "0.19.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/void-elements": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/warning": {
|
"node_modules/warning": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.0.3",
|
"version": "4.1.0",
|
||||||
"description": "Yet another matrix client",
|
"description": "Yet another matrix client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -48,6 +48,9 @@
|
||||||
"formik": "2.4.6",
|
"formik": "2.4.6",
|
||||||
"html-dom-parser": "4.0.0",
|
"html-dom-parser": "4.0.0",
|
||||||
"html-react-parser": "4.2.0",
|
"html-react-parser": "4.2.0",
|
||||||
|
"i18next": "23.12.2",
|
||||||
|
"i18next-browser-languagedetector": "8.0.0",
|
||||||
|
"i18next-http-backend": "2.5.2",
|
||||||
"immer": "9.0.16",
|
"immer": "9.0.16",
|
||||||
"is-hotkey": "0.2.0",
|
"is-hotkey": "0.2.0",
|
||||||
"jotai": "2.6.0",
|
"jotai": "2.6.0",
|
||||||
|
@ -65,6 +68,7 @@
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-error-boundary": "4.0.13",
|
"react-error-boundary": "4.0.13",
|
||||||
"react-google-recaptcha": "2.1.0",
|
"react-google-recaptcha": "2.1.0",
|
||||||
|
"react-i18next": "15.0.0",
|
||||||
"react-modal": "3.16.1",
|
"react-modal": "3.16.1",
|
||||||
"react-range": "1.8.14",
|
"react-range": "1.8.14",
|
||||||
"react-router-dom": "6.20.0",
|
"react-router-dom": "6.20.0",
|
||||||
|
|
7
public/locales/de.json
Normal file
7
public/locales/de.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"Organisms": {
|
||||||
|
"RoomCommon": {
|
||||||
|
"changed_room_name": " hat den Raum Name geändert"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
public/locales/en.json
Normal file
7
public/locales/en.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"Organisms": {
|
||||||
|
"RoomCommon": {
|
||||||
|
"changed_room_name": " changed room name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,25 @@ export const ReplyBend = style({
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ThreadIndicator = style({
|
||||||
|
opacity: config.opacity.P300,
|
||||||
|
gap: toRem(2),
|
||||||
|
|
||||||
|
selectors: {
|
||||||
|
'button&': {
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
':hover&': {
|
||||||
|
opacity: config.opacity.P500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ThreadIndicatorIcon = style({
|
||||||
|
width: toRem(14),
|
||||||
|
height: toRem(14),
|
||||||
|
});
|
||||||
|
|
||||||
export const Reply = style({
|
export const Reply = style({
|
||||||
marginBottom: toRem(1),
|
marginBottom: toRem(1),
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Box, Icon, Icons, Text, as, color, toRem } from 'folds';
|
import { Box, Icon, Icons, Text, as, color, toRem } from 'folds';
|
||||||
import { EventTimelineSet, MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk';
|
import { EventTimelineSet, MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk';
|
||||||
import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend';
|
import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend';
|
||||||
import React, { ReactNode, useEffect, useMemo, useState } from 'react';
|
import React, { MouseEventHandler, ReactNode, useEffect, useMemo, useState } from 'react';
|
||||||
import to from 'await-to-js';
|
import to from 'await-to-js';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import colorMXID from '../../../util/colorMXID';
|
import colorMXID from '../../../util/colorMXID';
|
||||||
|
@ -22,6 +22,7 @@ export const ReplyLayout = as<'div', ReplyLayoutProps>(
|
||||||
<Box
|
<Box
|
||||||
className={classNames(css.Reply, className)}
|
className={classNames(css.Reply, className)}
|
||||||
alignItems="Center"
|
alignItems="Center"
|
||||||
|
alignSelf="Start"
|
||||||
gap="100"
|
gap="100"
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
@ -37,16 +38,26 @@ export const ReplyLayout = as<'div', ReplyLayoutProps>(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ThreadIndicator = as<'div'>(({ ...props }, ref) => (
|
||||||
|
<Box className={css.ThreadIndicator} alignItems="Center" alignSelf="Start" {...props} ref={ref}>
|
||||||
|
<Icon className={css.ThreadIndicatorIcon} src={Icons.Message} />
|
||||||
|
<Text size="T200">Threaded reply</Text>
|
||||||
|
</Box>
|
||||||
|
));
|
||||||
|
|
||||||
type ReplyProps = {
|
type ReplyProps = {
|
||||||
mx: MatrixClient;
|
mx: MatrixClient;
|
||||||
room: Room;
|
room: Room;
|
||||||
timelineSet?: EventTimelineSet;
|
timelineSet?: EventTimelineSet | undefined;
|
||||||
eventId: string;
|
replyEventId: string;
|
||||||
|
threadRootId?: string | undefined;
|
||||||
|
onClick?: MouseEventHandler | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Reply = as<'div', ReplyProps>(({ mx, room, timelineSet, eventId, ...props }, ref) => {
|
export const Reply = as<'div', ReplyProps>((_, ref) => {
|
||||||
|
const { mx, room, timelineSet, replyEventId, threadRootId, onClick, ...props } = _;
|
||||||
const [replyEvent, setReplyEvent] = useState<MatrixEvent | null | undefined>(
|
const [replyEvent, setReplyEvent] = useState<MatrixEvent | null | undefined>(
|
||||||
timelineSet?.findEventById(eventId)
|
timelineSet?.findEventById(replyEventId)
|
||||||
);
|
);
|
||||||
const placeholderWidth = useMemo(() => randomNumberBetween(40, 400), []);
|
const placeholderWidth = useMemo(() => randomNumberBetween(40, 400), []);
|
||||||
|
|
||||||
|
@ -62,7 +73,7 @@ export const Reply = as<'div', ReplyProps>(({ mx, room, timelineSet, eventId, ..
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let disposed = false;
|
let disposed = false;
|
||||||
const loadEvent = async () => {
|
const loadEvent = async () => {
|
||||||
const [err, evt] = await to(mx.fetchRoomEvent(room.roomId, eventId));
|
const [err, evt] = await to(mx.fetchRoomEvent(room.roomId, replyEventId));
|
||||||
const mEvent = new MatrixEvent(evt);
|
const mEvent = new MatrixEvent(evt);
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -78,37 +89,43 @@ export const Reply = as<'div', ReplyProps>(({ mx, room, timelineSet, eventId, ..
|
||||||
return () => {
|
return () => {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
};
|
};
|
||||||
}, [replyEvent, mx, room, eventId]);
|
}, [replyEvent, mx, room, replyEventId]);
|
||||||
|
|
||||||
const badEncryption = replyEvent?.getContent().msgtype === 'm.bad.encrypted';
|
const badEncryption = replyEvent?.getContent().msgtype === 'm.bad.encrypted';
|
||||||
const bodyJSX = body ? scaleSystemEmoji(trimReplyFromBody(body)) : fallbackBody;
|
const bodyJSX = body ? scaleSystemEmoji(trimReplyFromBody(body)) : fallbackBody;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReplyLayout
|
<Box direction="Column" {...props} ref={ref}>
|
||||||
userColor={sender ? colorMXID(sender) : undefined}
|
{threadRootId && (
|
||||||
username={
|
<ThreadIndicator as="button" data-event-id={threadRootId} onClick={onClick} />
|
||||||
sender && (
|
|
||||||
<Text size="T300" truncate>
|
|
||||||
<b>{getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender)}</b>
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{...props}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
{replyEvent !== undefined ? (
|
|
||||||
<Text size="T300" truncate>
|
|
||||||
{badEncryption ? <MessageBadEncryptedContent /> : bodyJSX}
|
|
||||||
</Text>
|
|
||||||
) : (
|
|
||||||
<LinePlaceholder
|
|
||||||
style={{
|
|
||||||
backgroundColor: color.SurfaceVariant.ContainerActive,
|
|
||||||
maxWidth: toRem(placeholderWidth),
|
|
||||||
width: '100%',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</ReplyLayout>
|
<ReplyLayout
|
||||||
|
as="button"
|
||||||
|
userColor={sender ? colorMXID(sender) : undefined}
|
||||||
|
username={
|
||||||
|
sender && (
|
||||||
|
<Text size="T300" truncate>
|
||||||
|
<b>{getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender)}</b>
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
data-event-id={replyEventId}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{replyEvent !== undefined ? (
|
||||||
|
<Text size="T300" truncate>
|
||||||
|
{badEncryption ? <MessageBadEncryptedContent /> : bodyJSX}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<LinePlaceholder
|
||||||
|
style={{
|
||||||
|
backgroundColor: color.SurfaceVariant.ContainerActive,
|
||||||
|
maxWidth: toRem(placeholderWidth),
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ReplyLayout>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -148,7 +148,7 @@ export function SearchResultGroup({
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOpenClick: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
const handleOpenClick: MouseEventHandler = (evt) => {
|
||||||
const eventId = evt.currentTarget.getAttribute('data-event-id');
|
const eventId = evt.currentTarget.getAttribute('data-event-id');
|
||||||
if (!eventId) return;
|
if (!eventId) return;
|
||||||
onOpen(room.roomId, eventId);
|
onOpen(room.roomId, eventId);
|
||||||
|
@ -183,15 +183,16 @@ export function SearchResultGroup({
|
||||||
event.sender;
|
event.sender;
|
||||||
const senderAvatarMxc = getMemberAvatarMxc(room, event.sender);
|
const senderAvatarMxc = getMemberAvatarMxc(room, event.sender);
|
||||||
|
|
||||||
|
const relation = event.content['m.relates_to'];
|
||||||
const mainEventId =
|
const mainEventId =
|
||||||
event.content['m.relates_to']?.rel_type === RelationType.Replace
|
relation?.rel_type === RelationType.Replace ? relation.event_id : event.event_id;
|
||||||
? event.content['m.relates_to'].event_id
|
|
||||||
: event.event_id;
|
|
||||||
|
|
||||||
const getContent = (() =>
|
const getContent = (() =>
|
||||||
event.content['m.new_content'] ?? event.content) as GetContentCallback;
|
event.content['m.new_content'] ?? event.content) as GetContentCallback;
|
||||||
|
|
||||||
const replyEventId = event.content['m.relates_to']?.['m.in_reply_to']?.event_id;
|
const replyEventId = relation?.['m.in_reply_to']?.event_id;
|
||||||
|
const threadRootId =
|
||||||
|
relation?.rel_type === RelationType.Thread ? relation.event_id : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SequenceCard
|
<SequenceCard
|
||||||
|
@ -240,11 +241,10 @@ export function SearchResultGroup({
|
||||||
</Box>
|
</Box>
|
||||||
{replyEventId && (
|
{replyEventId && (
|
||||||
<Reply
|
<Reply
|
||||||
as="button"
|
|
||||||
mx={mx}
|
mx={mx}
|
||||||
room={room}
|
room={room}
|
||||||
eventId={replyEventId}
|
replyEventId={replyEventId}
|
||||||
data-event-id={replyEventId}
|
threadRootId={threadRootId}
|
||||||
onClick={handleOpenClick}
|
onClick={handleOpenClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import React, {
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { useAtom, useAtomValue } from 'jotai';
|
||||||
import { isKeyHotkey } from 'is-hotkey';
|
import { isKeyHotkey } from 'is-hotkey';
|
||||||
import { EventType, IContent, MsgType, Room } from 'matrix-js-sdk';
|
import { EventType, IContent, MsgType, RelationType, Room } from 'matrix-js-sdk';
|
||||||
import { ReactEditor } from 'slate-react';
|
import { ReactEditor } from 'slate-react';
|
||||||
import { Transforms, Editor } from 'slate';
|
import { Transforms, Editor } from 'slate';
|
||||||
import {
|
import {
|
||||||
|
@ -106,7 +106,7 @@ import { CommandAutocomplete } from './CommandAutocomplete';
|
||||||
import { Command, SHRUG, useCommands } from '../../hooks/useCommands';
|
import { Command, SHRUG, useCommands } from '../../hooks/useCommands';
|
||||||
import { mobileOrTablet } from '../../utils/user-agent';
|
import { mobileOrTablet } from '../../utils/user-agent';
|
||||||
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
|
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
|
||||||
import { ReplyLayout } from '../../components/message';
|
import { ReplyLayout, ThreadIndicator } from '../../components/message';
|
||||||
import { roomToParentsAtom } from '../../state/room/roomToParents';
|
import { roomToParentsAtom } from '../../state/room/roomToParents';
|
||||||
|
|
||||||
interface RoomInputProps {
|
interface RoomInputProps {
|
||||||
|
@ -310,6 +310,11 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||||
event_id: replyDraft.eventId,
|
event_id: replyDraft.eventId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
if (replyDraft.relation?.rel_type === RelationType.Thread) {
|
||||||
|
content['m.relates_to'].event_id = replyDraft.relation.event_id;
|
||||||
|
content['m.relates_to'].rel_type = RelationType.Thread;
|
||||||
|
content['m.relates_to'].is_falling_back = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mx.sendMessage(roomId, content);
|
mx.sendMessage(roomId, content);
|
||||||
resetEditor(editor);
|
resetEditor(editor);
|
||||||
|
@ -489,22 +494,25 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||||
>
|
>
|
||||||
<Icon src={Icons.Cross} size="50" />
|
<Icon src={Icons.Cross} size="50" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<ReplyLayout
|
<Box direction="Column">
|
||||||
userColor={colorMXID(replyDraft.userId)}
|
{replyDraft.relation?.rel_type === RelationType.Thread && <ThreadIndicator />}
|
||||||
username={
|
<ReplyLayout
|
||||||
|
userColor={colorMXID(replyDraft.userId)}
|
||||||
|
username={
|
||||||
|
<Text size="T300" truncate>
|
||||||
|
<b>
|
||||||
|
{getMemberDisplayName(room, replyDraft.userId) ??
|
||||||
|
getMxIdLocalPart(replyDraft.userId) ??
|
||||||
|
replyDraft.userId}
|
||||||
|
</b>
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Text size="T300" truncate>
|
<Text size="T300" truncate>
|
||||||
<b>
|
{trimReplyFromBody(replyDraft.body)}
|
||||||
{getMemberDisplayName(room, replyDraft.userId) ??
|
|
||||||
getMxIdLocalPart(replyDraft.userId) ??
|
|
||||||
replyDraft.userId}
|
|
||||||
</b>
|
|
||||||
</Text>
|
</Text>
|
||||||
}
|
</ReplyLayout>
|
||||||
>
|
</Box>
|
||||||
<Text size="T300" truncate>
|
|
||||||
{trimReplyFromBody(replyDraft.body)}
|
|
||||||
</Text>
|
|
||||||
</ReplyLayout>
|
|
||||||
</Box>
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
EventTimeline,
|
EventTimeline,
|
||||||
EventTimelineSet,
|
EventTimelineSet,
|
||||||
EventTimelineSetHandlerMap,
|
EventTimelineSetHandlerMap,
|
||||||
|
IContent,
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
Room,
|
Room,
|
||||||
|
@ -45,6 +46,7 @@ import {
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import { isKeyHotkey } from 'is-hotkey';
|
import { isKeyHotkey } from 'is-hotkey';
|
||||||
import { Opts as LinkifyOpts } from 'linkifyjs';
|
import { Opts as LinkifyOpts } from 'linkifyjs';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { eventWithShortcode, factoryEventSentBy, getMxIdLocalPart } from '../../utils/matrix';
|
import { eventWithShortcode, factoryEventSentBy, getMxIdLocalPart } from '../../utils/matrix';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { useVirtualPaginator, ItemRange } from '../../hooks/useVirtualPaginator';
|
import { useVirtualPaginator, ItemRange } from '../../hooks/useVirtualPaginator';
|
||||||
|
@ -818,13 +820,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
markAsRead(mx, room.roomId);
|
markAsRead(mx, room.roomId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenReply: MouseEventHandler<HTMLButtonElement> = useCallback(
|
const handleOpenReply: MouseEventHandler = useCallback(
|
||||||
async (evt) => {
|
async (evt) => {
|
||||||
const replyId = evt.currentTarget.getAttribute('data-reply-id');
|
const targetId = evt.currentTarget.getAttribute('data-event-id');
|
||||||
if (typeof replyId !== 'string') return;
|
if (!targetId) return;
|
||||||
const replyTimeline = getEventTimeline(room, replyId);
|
const replyTimeline = getEventTimeline(room, targetId);
|
||||||
const absoluteIndex =
|
const absoluteIndex =
|
||||||
replyTimeline && getEventIdAbsoluteIndex(timeline.linkedTimelines, replyTimeline, replyId);
|
replyTimeline && getEventIdAbsoluteIndex(timeline.linkedTimelines, replyTimeline, targetId);
|
||||||
|
|
||||||
if (typeof absoluteIndex === 'number') {
|
if (typeof absoluteIndex === 'number') {
|
||||||
scrollToItem(absoluteIndex, {
|
scrollToItem(absoluteIndex, {
|
||||||
|
@ -839,7 +841,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setTimeline(getEmptyTimeline());
|
setTimeline(getEmptyTimeline());
|
||||||
loadEventTimeline(replyId);
|
loadEventTimeline(targetId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[room, timeline, scrollToItem, loadEventTimeline]
|
[room, timeline, scrollToItem, loadEventTimeline]
|
||||||
|
@ -890,8 +892,9 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
const replyEvt = room.findEventById(replyId);
|
const replyEvt = room.findEventById(replyId);
|
||||||
if (!replyEvt) return;
|
if (!replyEvt) return;
|
||||||
const editedReply = getEditedEvent(replyId, replyEvt, room.getUnfilteredTimelineSet());
|
const editedReply = getEditedEvent(replyId, replyEvt, room.getUnfilteredTimelineSet());
|
||||||
const { body, formatted_body: formattedBody }: Record<string, string> =
|
const content: IContent = editedReply?.getContent()['m.new_content'] ?? replyEvt.getContent();
|
||||||
editedReply?.getContent()['m.new_content'] ?? replyEvt.getContent();
|
const { body, formatted_body: formattedBody } = content;
|
||||||
|
const { 'm.relates_to': relation } = replyEvt.getOriginalContent();
|
||||||
const senderId = replyEvt.getSender();
|
const senderId = replyEvt.getSender();
|
||||||
if (senderId && typeof body === 'string') {
|
if (senderId && typeof body === 'string') {
|
||||||
setReplyDraft({
|
setReplyDraft({
|
||||||
|
@ -899,6 +902,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
eventId: replyId,
|
eventId: replyId,
|
||||||
body,
|
body,
|
||||||
formattedBody,
|
formattedBody,
|
||||||
|
relation,
|
||||||
});
|
});
|
||||||
setTimeout(() => ReactEditor.focus(editor), 100);
|
setTimeout(() => ReactEditor.focus(editor), 100);
|
||||||
}
|
}
|
||||||
|
@ -940,6 +944,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
},
|
},
|
||||||
[editor]
|
[editor]
|
||||||
);
|
);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const renderMatrixEvent = useMatrixEventRenderer<
|
const renderMatrixEvent = useMatrixEventRenderer<
|
||||||
[string, MatrixEvent, number, EventTimelineSet, boolean]
|
[string, MatrixEvent, number, EventTimelineSet, boolean]
|
||||||
|
@ -949,7 +954,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
const reactionRelations = getEventReactions(timelineSet, mEventId);
|
const reactionRelations = getEventReactions(timelineSet, mEventId);
|
||||||
const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey();
|
const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey();
|
||||||
const hasReactions = reactions && reactions.length > 0;
|
const hasReactions = reactions && reactions.length > 0;
|
||||||
const { replyEventId } = mEvent;
|
const { replyEventId, threadRootId } = mEvent;
|
||||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
|
|
||||||
const editedEvent = getEditedEvent(mEventId, mEvent, timelineSet);
|
const editedEvent = getEditedEvent(mEventId, mEvent, timelineSet);
|
||||||
|
@ -984,12 +989,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
reply={
|
reply={
|
||||||
replyEventId && (
|
replyEventId && (
|
||||||
<Reply
|
<Reply
|
||||||
as="button"
|
|
||||||
mx={mx}
|
mx={mx}
|
||||||
room={room}
|
room={room}
|
||||||
timelineSet={timelineSet}
|
timelineSet={timelineSet}
|
||||||
eventId={replyEventId}
|
replyEventId={replyEventId}
|
||||||
data-reply-id={replyEventId}
|
threadRootId={threadRootId}
|
||||||
onClick={handleOpenReply}
|
onClick={handleOpenReply}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -1030,7 +1034,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
const reactionRelations = getEventReactions(timelineSet, mEventId);
|
const reactionRelations = getEventReactions(timelineSet, mEventId);
|
||||||
const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey();
|
const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey();
|
||||||
const hasReactions = reactions && reactions.length > 0;
|
const hasReactions = reactions && reactions.length > 0;
|
||||||
const { replyEventId } = mEvent;
|
const { replyEventId, threadRootId } = mEvent;
|
||||||
const highlighted = focusItem?.index === item && focusItem.highlight;
|
const highlighted = focusItem?.index === item && focusItem.highlight;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1057,12 +1061,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
reply={
|
reply={
|
||||||
replyEventId && (
|
replyEventId && (
|
||||||
<Reply
|
<Reply
|
||||||
as="button"
|
|
||||||
mx={mx}
|
mx={mx}
|
||||||
room={room}
|
room={room}
|
||||||
timelineSet={timelineSet}
|
timelineSet={timelineSet}
|
||||||
eventId={replyEventId}
|
replyEventId={replyEventId}
|
||||||
data-reply-id={replyEventId}
|
threadRootId={threadRootId}
|
||||||
onClick={handleOpenReply}
|
onClick={handleOpenReply}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -1255,7 +1258,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
<Box grow="Yes" direction="Column">
|
<Box grow="Yes" direction="Column">
|
||||||
<Text size="T300" priority="300">
|
<Text size="T300" priority="300">
|
||||||
<b>{senderName}</b>
|
<b>{senderName}</b>
|
||||||
{' changed room name'}
|
{t('Organisms.RoomCommon.changed_room_name')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
|
|
31
src/app/i18n.ts
Normal file
31
src/app/i18n.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import i18n from 'i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import Backend, { HttpBackendOptions } from 'i18next-http-backend';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import { trimTrailingSlash } from './utils/common';
|
||||||
|
|
||||||
|
i18n
|
||||||
|
// i18next-http-backend
|
||||||
|
// loads translations from your server
|
||||||
|
// https://github.com/i18next/i18next-http-backend
|
||||||
|
.use(Backend)
|
||||||
|
// detect user language
|
||||||
|
// learn more: https://github.com/i18next/i18next-browser-languageDetector
|
||||||
|
.use(LanguageDetector)
|
||||||
|
// pass the i18n instance to react-i18next.
|
||||||
|
.use(initReactI18next)
|
||||||
|
// init i18next
|
||||||
|
// for all options read: https://www.i18next.com/overview/configuration-options
|
||||||
|
.init<HttpBackendOptions>({
|
||||||
|
debug: false,
|
||||||
|
fallbackLng: 'en',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false, // not needed for react as it escapes by default
|
||||||
|
},
|
||||||
|
load: 'languageOnly',
|
||||||
|
backend: {
|
||||||
|
loadPath: `${trimTrailingSlash(import.meta.env.BASE_URL)}/public/locales/{{lng}}.json`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
|
@ -15,7 +15,7 @@ export function AuthFooter() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
v4.0.3
|
v4.1.0
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
|
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
|
||||||
Twitter
|
Twitter
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function WelcomePage() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
v4.0.3
|
v4.1.0
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
IRoomEvent,
|
IRoomEvent,
|
||||||
JoinRule,
|
JoinRule,
|
||||||
Method,
|
Method,
|
||||||
|
RelationType,
|
||||||
Room,
|
Room,
|
||||||
} from 'matrix-js-sdk';
|
} from 'matrix-js-sdk';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
|
@ -352,7 +353,7 @@ function RoomNotificationsGroupComp({
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOpenClick: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
const handleOpenClick: MouseEventHandler = (evt) => {
|
||||||
const eventId = evt.currentTarget.getAttribute('data-event-id');
|
const eventId = evt.currentTarget.getAttribute('data-event-id');
|
||||||
if (!eventId) return;
|
if (!eventId) return;
|
||||||
onOpen(room.roomId, eventId);
|
onOpen(room.roomId, eventId);
|
||||||
|
@ -403,7 +404,10 @@ function RoomNotificationsGroupComp({
|
||||||
const senderAvatarMxc = getMemberAvatarMxc(room, event.sender);
|
const senderAvatarMxc = getMemberAvatarMxc(room, event.sender);
|
||||||
const getContent = (() => event.content) as GetContentCallback;
|
const getContent = (() => event.content) as GetContentCallback;
|
||||||
|
|
||||||
const replyEventId = event.content['m.relates_to']?.['m.in_reply_to']?.event_id;
|
const relation = event.content['m.relates_to'];
|
||||||
|
const replyEventId = relation?.['m.in_reply_to']?.event_id;
|
||||||
|
const threadRootId =
|
||||||
|
relation?.rel_type === RelationType.Thread ? relation.event_id : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SequenceCard
|
<SequenceCard
|
||||||
|
@ -452,11 +456,10 @@ function RoomNotificationsGroupComp({
|
||||||
</Box>
|
</Box>
|
||||||
{replyEventId && (
|
{replyEventId && (
|
||||||
<Reply
|
<Reply
|
||||||
as="button"
|
|
||||||
mx={mx}
|
mx={mx}
|
||||||
room={room}
|
room={room}
|
||||||
eventId={replyEventId}
|
replyEventId={replyEventId}
|
||||||
data-event-id={replyEventId}
|
threadRootId={threadRootId}
|
||||||
onClick={handleOpenClick}
|
onClick={handleOpenClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { atom } from 'jotai';
|
||||||
import { atomFamily } from 'jotai/utils';
|
import { atomFamily } from 'jotai/utils';
|
||||||
import { Descendant } from 'slate';
|
import { Descendant } from 'slate';
|
||||||
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
|
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
|
||||||
|
import { IEventRelation } from 'matrix-js-sdk';
|
||||||
import { TListAtom, createListAtom } from '../list';
|
import { TListAtom, createListAtom } from '../list';
|
||||||
import { createUploadAtomFamily } from '../upload';
|
import { createUploadAtomFamily } from '../upload';
|
||||||
import { TUploadContent } from '../../utils/matrix';
|
import { TUploadContent } from '../../utils/matrix';
|
||||||
|
@ -39,7 +40,8 @@ export type IReplyDraft = {
|
||||||
userId: string;
|
userId: string;
|
||||||
eventId: string;
|
eventId: string;
|
||||||
body: string;
|
body: string;
|
||||||
formattedBody?: string;
|
formattedBody?: string | undefined;
|
||||||
|
relation?: IEventRelation | undefined;
|
||||||
};
|
};
|
||||||
const createReplyDraftAtom = () => atom<IReplyDraft | undefined>(undefined);
|
const createReplyDraftAtom = () => atom<IReplyDraft | undefined>(undefined);
|
||||||
export type TReplyDraftAtom = ReturnType<typeof createReplyDraftAtom>;
|
export type TReplyDraftAtom = ReturnType<typeof createReplyDraftAtom>;
|
||||||
|
|
|
@ -389,13 +389,18 @@ export const getEditedEvent = (
|
||||||
return edits && getLatestEdit(mEvent, edits.getRelations());
|
return edits && getLatestEdit(mEvent, edits.getRelations());
|
||||||
};
|
};
|
||||||
|
|
||||||
export const canEditEvent = (mx: MatrixClient, mEvent: MatrixEvent) =>
|
export const canEditEvent = (mx: MatrixClient, mEvent: MatrixEvent) => {
|
||||||
mEvent.getSender() === mx.getUserId() &&
|
const content = mEvent.getContent();
|
||||||
!mEvent.isRelation() &&
|
const relationType = content['m.relates_to']?.rel_type;
|
||||||
mEvent.getType() === MessageEvent.RoomMessage &&
|
return (
|
||||||
(mEvent.getContent().msgtype === MsgType.Text ||
|
mEvent.getSender() === mx.getUserId() &&
|
||||||
mEvent.getContent().msgtype === MsgType.Emote ||
|
(!relationType || relationType === RelationType.Thread) &&
|
||||||
mEvent.getContent().msgtype === MsgType.Notice);
|
mEvent.getType() === MessageEvent.RoomMessage &&
|
||||||
|
(content.msgtype === MsgType.Text ||
|
||||||
|
content.msgtype === MsgType.Emote ||
|
||||||
|
content.msgtype === MsgType.Notice)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getLatestEditableEvt = (
|
export const getLatestEditableEvt = (
|
||||||
timeline: EventTimeline,
|
timeline: EventTimeline,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const cons = {
|
const cons = {
|
||||||
version: '4.0.3',
|
version: '4.1.0',
|
||||||
secretKey: {
|
secretKey: {
|
||||||
ACCESS_TOKEN: 'cinny_access_token',
|
ACCESS_TOKEN: 'cinny_access_token',
|
||||||
DEVICE_ID: 'cinny_device_id',
|
DEVICE_ID: 'cinny_device_id',
|
||||||
|
|
|
@ -14,6 +14,9 @@ import settings from './client/state/settings';
|
||||||
|
|
||||||
import App from './app/pages/App';
|
import App from './app/pages/App';
|
||||||
|
|
||||||
|
// import i18n (needs to be bundled ;))
|
||||||
|
import './app/i18n';
|
||||||
|
|
||||||
document.body.classList.add(configClass, varsClass);
|
document.body.classList.add(configClass, varsClass);
|
||||||
settings.applyTheme();
|
settings.applyTheme();
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,10 @@ const copyFiles = {
|
||||||
src: 'public/res/android',
|
src: 'public/res/android',
|
||||||
dest: 'public/',
|
dest: 'public/',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
src: 'public/locales',
|
||||||
|
dest: 'public/',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue