Compare commits

..
1 Commits
Author SHA1 Message Date
Krishan cc423fcc96 Don't enable e2ee from profileViewer for bridge users 2022-07-08 09:50:23 +05:30
184 changed files with 22313 additions and 14594 deletions

No files matched your search

+3
View File
@@ -1,2 +1,5 @@
webpack.common.js
webpack.dev.js
webpack.prod.js
experiment
node_modules
+2 -33
View File
@@ -4,56 +4,25 @@ module.exports = {
es2021: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
'plugin:react/recommended',
'airbnb',
'prettier',
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
ecmaVersion: 12,
sourceType: 'module',
},
plugins: [
'react',
'@typescript-eslint'
],
rules: {
'linebreak-style': 0,
'no-underscore-dangle': 0,
"import/prefer-default-export": "off",
"import/extensions": "off",
"import/no-unresolved": "off",
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: true,
},
],
'react/no-unstable-nested-components': [
'error',
{ allowAsProps: true },
],
"react/jsx-filename-extension": [
"error",
{
extensions: [".tsx", ".jsx"],
},
],
"react/require-default-props": "off",
"react/jsx-props-no-spreading": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error",
"@typescript-eslint/no-unused-vars": "error",
},
};
+3 -1
View File
@@ -1 +1,3 @@
github: true-og
github: ajbura
liberapay: ajbura
open_collective: cinny
+8 -8
View File
@@ -6,9 +6,9 @@ body:
attributes:
value: |
## First of all
1. Please search for [existing issues](https://github.com/NotAlexNoyle/Versi/issues?q=is%3Aissue) about the problem first.
2. Make sure Versi is up to date.
3. Make sure it's an issue with Versi and not something else you are using.
1. Please search for [existing issues](https://github.com/ajbura/cinny/issues?q=is%3Aissue) about this problem first.
2. Make sure Cinny is up to date.
3. Make sure it's an issue with Cinny and not something else you are using.
4. Remember to be friendly.
- type: textarea
@@ -40,12 +40,12 @@ body:
id: info
attributes:
label: Platform and versions
description: "Provide OS, browser and Versi version with your Homeserver."
description: "Provide OS, browser and Cinny version with your Homeserver."
placeholder: |
1. OS: [e.g. Binbows 10, MacOS]
2. Browser: [e.g. pale moon 33, waterfox 105]
3. Versi version: [e.g. 1.8.1]
4. Matrix homeserver: [e.g. chat.true-og.net]
1. OS: [e.g. Windows 10, MacOS]
2. Browser: [e.g. chrome 99.5, firefox 97.2]
3. Cinny version: [e.g. 1.8.1 (app.cinny.in)]
4. Matrix homeserver: [e.g. matrix.org]
render: shell
validations:
required: true
+2 -2
View File
@@ -1,4 +1,4 @@
contact_links:
- name: 💬 Matrix Chat
url: https://chat.true-og.net/
about: Ask questions and talk to other Versi users and the maintainers
url: https://matrix.to/#/#cinny:matrix.org
about: Ask questions and talk to other Cinny users and the maintainers
+2
View File
@@ -1,3 +1,5 @@
<!-- Please read https://github.com/ajbura/cinny/CONTRIBUTING.md before submitting your pull request -->
### Description
<!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. -->
+1 -1
View File
@@ -1,3 +1,3 @@
# Reporting a Vulnerability
**If you've found a security vulnerability, please report it to admin@true-og.net**
**If you've found a security vulnerability, please report it to cinnyapp@gmail.com**
-15
View File
@@ -1,15 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":dependencyDashboardApproval"
],
"labels": [ "Dependencies" ],
"packageRules": [
{
"matchUpdateTypes": [ "lockFileMaintenance" ]
}
],
"lockFileMaintenance": { "enabled": true },
"dependencyDashboard": true
}
+17 -17
View File
@@ -1,4 +1,4 @@
name: Build pull request
name: 'Build pull request'
on:
pull_request:
@@ -6,33 +6,33 @@ on:
jobs:
build-pull-request:
name: Build pull request
runs-on: ubuntu-latest
env:
PR_NUMBER: ${{github.event.number}}
steps:
- name: Checkout repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.5.1
uses: actions/setup-node@v3.3.0
with:
node-version: 18.12.1
cache: "npm"
- name: Install dependencies
run: npm ci
node-version: 17.9.0
- name: Build app
run: npm run build
run: npm ci && npm run build
- name: Upload artifact
uses: actions/upload-artifact@v3.1.1
uses: actions/upload-artifact@v3.1.0
with:
name: preview
name: previewbuild
path: dist
retention-days: 1
- name: Save pr number
run: echo ${PR_NUMBER} > ./pr.txt
- name: Upload pr number
uses: actions/upload-artifact@v3.1.1
- name: Get PR info
uses: actions/github-script@v6.1.0
with:
name: pr
path: ./pr.txt
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.1.0
with:
name: pr.json
path: pr.json
retention-days: 1
+57 -32
View File
@@ -1,56 +1,81 @@
name: Deploy PR to Netlify
name: Upload Preview Build to Netlify
on:
workflow_run:
workflows: ["Build pull request"]
types: [completed]
types:
- completed
jobs:
deploy-pull-request:
name: Deploy pull request
runs-on: ubuntu-latest
get-build-and-deploy:
permissions:
contents: read
pull-requests: write
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
if: >
${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Download pr number
uses: dawidd6/action-download-artifact@e6e25ac3a2b93187502a8be1ef9e9603afc34925
with:
workflow: ${{ github.event.workflow.id }}
run_id: ${{ github.event.workflow_run.id }}
name: pr
- name: Output pr number
id: pr
run: echo "id=$(<pr.txt)" >> $GITHUB_OUTPUT
# 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: dawidd6/action-download-artifact@e6e25ac3a2b93187502a8be1ef9e9603afc34925
uses: actions/github-script@v6.1.0
with:
workflow: ${{ github.event.workflow.id }}
run_id: ${{ github.event.workflow_run.id }}
name: preview
path: dist
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.1.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@5da65c9f74c7961c5501a3ba329b8d0912f39c03
uses: nwtgck/actions-netlify@b7c1504e00c6b8a249d1848cc1b522a4865eed99
with:
publish-dir: dist
deploy-message: "Deploy PR ${{ steps.pr.outputs.id }}"
alias: ${{ steps.pr.outputs.id }}
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_SITE_ID_PR_CINNY }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }}
timeout-minutes: 1
- name: Comment preview on PR
uses: thollander/actions-comment-pull-request@c22fb302208b7b170d252a61a505d2ea27245eff
- name: Edit PR Description
uses: Beakyn/gha-comment-pull-request@2167a7aee24f9e61ce76a23039f322e49a990409
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
pr_number: ${{ steps.pr.outputs.id }}
comment_tag: ${{ steps.pr.outputs.id }}
message: |
pull-request-number: ${{ steps.readctx.outputs.prnumber }}
description-message: |
Preview: ${{ steps.netlify.outputs.deploy-url }}
⚠️ Exercise caution. Use test accounts. ⚠️
⚠️ Exercise caution. Use test accounts. ⚠️
+4 -2
View File
@@ -9,11 +9,13 @@ on:
jobs:
docker-build:
runs-on: ubuntu-latest
env:
PR_NUMBER: ${{github.event.number}}
steps:
- name: Checkout repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v3.0.2
- name: Build Docker image
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.0.0
with:
context: .
push: false
-26
View File
@@ -1,26 +0,0 @@
name: NPM Lockfile Changes
on:
pull_request:
paths:
- 'package-lock.json'
jobs:
lockfile_changes:
runs-on: ubuntu-latest
# Permission overwrite is required for Dependabot PRs, see "Common issues" below.
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v3.2.0
- name: NPM Lockfile Changes
uses: codepunkt/npm-lockfile-changes@b40543471c36394409466fdb277a73a0856d7891
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Optional inputs, can be deleted safely if you are happy with default values.
collapsibleThreshold: 25
failOnDowngrade: false
path: package-lock.json
updateComment: true
+14 -22
View File
@@ -1,4 +1,4 @@
name: Deploy to Netlify (dev)
name: 'Deploy to Netlify (dev)'
on:
push:
@@ -7,31 +7,23 @@ on:
jobs:
deploy-to-netlify:
name: Deploy to Netlify
name: 'Deploy'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v3.2.0
uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.5.1
uses: actions/setup-node@v3.3.0
with:
node-version: 18.12.1
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build app
run: npm run build
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@5da65c9f74c7961c5501a3ba329b8d0912f39c03
node-version: 17.9.0
- name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@53de32e559b0b3833615b9788c7a090cd2fddb03
with:
publish-dir: dist
deploy-message: "Dev deploy ${{ github.sha }}"
enable-commit-comment: false
github-token: ${{ secrets.GITHUB_TOKEN }}
production-deploy: true
github-deployment-environment: nightly
github-deployment-description: 'Nightly deployment on each commit to dev branch'
env:
install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_DEV }}
timeout-minutes: 1
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE2_ID }}
BUILD_DIRECTORY: "dist"
NETLIFY_DEPLOY_MESSAGE: "Dev deploy v${{ github.ref }}"
NETLIFY_DEPLOY_TO_PROD: true
+95
View File
@@ -0,0 +1,95 @@
name: 'Production deploy'
on:
release:
types: [published]
jobs:
create-release-tar:
name: 'Create release tar'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.3.0
with:
node-version: 17.9.0
- name: Build
run: |
npm ci
npm run build
- 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: Sign tar.gz
run: |
echo '${{ secrets.GNUPG_KEY }}' | gpg --batch --import
# Sadly a few lines in the private key match a few lines in the public key,
# As a result just --export --armor gives us a few lines replaced with ***
# making it useless for importing the signing key. Instead, we dump it as
# non-armored and hex-encode it so that its printable.
echo "PGP Signing key, in raw PGP format in hex. Import with cat ... | xxd -r -p - | gpg --import"
gpg --export | xxd -p
echo '${{ secrets.GNUPG_PASSPHRASE }}' | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --armor --detach-sign cinny-${{ steps.vars.outputs.tag }}.tar.gz
- name: Upload tagged release
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
with:
files: |
cinny-${{ steps.vars.outputs.tag }}.tar.gz
cinny-${{ steps.vars.outputs.tag }}.tar.gz.asc
deploy-to-netlify:
name: 'Deploy to Netlify'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.3.0
with:
node-version: 17.9.0
- name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@53de32e559b0b3833615b9788c7a090cd2fddb03
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
push-to-dockerhub:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v3.0.2
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
uses: docker/login-action@v2.0.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4.0.1
with:
images: ajbura/cinny
- name: Build and push Docker image
uses: docker/build-push-action@v3.0.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-1
View File
@@ -4,4 +4,3 @@ node_modules
devAssets
.DS_Store
android
-3
View File
@@ -1,3 +0,0 @@
legacy-peer-deps=true
save-exact=true
@matrix-org:registry=https://gitlab.matrix.org/api/v4/projects/27/packages/npm/
-6
View File
@@ -1,6 +0,0 @@
dist
node_modules
package.json
package-lock.json
LICENSE
README.md
-4
View File
@@ -1,4 +0,0 @@
{
"printWidth": 100,
"singleQuote": true
}
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.tsdk": "node_modules/typescript/lib"
}
+39
View File
@@ -0,0 +1,39 @@
# Contributing to Cinny
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
> - Star the project
> - Tweet about it (tag @cinnyapp)
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
> - [Donate to us](https://cinny.in/#sponsor)
## Bug reports
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/ajbura/cinny/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.
## Pull requests
> ### Legal Notice
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
**NOTE: If you want to add new features, please discuss with maintainers before coding or opening a pull request.** This is to ensure that we are on same track and following our roadmap.
**Please use clean, concise titles for your pull requests.** We use commit squashing, so the final commit in the dev branch will carry the title of the pull request. For easier sorting in changelog, start your pull request titles using one of the verbs "Add", "Change", "Remove", or "Fix" (present tense).
Example:
|Not ideal|Better|
|---|----|
|Fixed markAllAsRead in RoomTimeline|Fix read marker when paginating room timeline|
It is not always possible to phrase every change in such a manner, but it is desired.
**The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged.** Splitting tasks into multiple smaller pull requests is often preferable.
Also, we use [ESLint](https://eslint.org/) for clean and stylistically consistent code syntax, so make sure your pull request follow it.
**For any query or design discussion, join our [Matrix room](https://matrix.to/#/#cinny:matrix.org).**
+18
View File
@@ -0,0 +1,18 @@
## Builder
FROM node:17.9.0-alpine3.15 as builder
WORKDIR /src
COPY package.json package-lock.json /src/
RUN npm ci
COPY . /src/
RUN npm run build
## App
FROM nginx:1.23.0-alpine
COPY --from=builder /src/dist /app
RUN rm -rf /usr/share/nginx/html \
&& ln -s /app /usr/share/nginx/html
+2 -2
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-present Ajay Bura (ajbura)
Copyright (c) 2021 Ajay Bura (ajbura)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
+101 -41
View File
@@ -1,65 +1,125 @@
# Versi
<p>
<a href="https://github.com/NotAlexNoyle/Versi/releases">Download</a> |
<a href="https://mastodon.gamedev.place/@trueog">Mastodon</a> |
<a href="https://store.trueog.net">Donate</a> |
<a href="https://true-og.net">Join TrueOG Network</a>
<br>
<br>
<img alt="Versi Logo" src="https://raw.githubusercontent.com/NotAlexNoyle/Versi/dev/assets/versi-logo.png"></a>
<p align="center">
<img src="https://raw.githubusercontent.com/ajbura/cinny/dev/public/res/svg/cinny.svg?sanitize=true"
height="16">
<span><b>Cinny</b></span>
</p>
<p align="center">
<a href="https://github.com/ajbura/cinny/releases">
<img alt="GitHub release downloads" src="https://img.shields.io/github/downloads/ajbura/cinny/total?logo=github&style=social"></a>
<a href="https://hub.docker.com/r/ajbura/cinny">
<img alt="DockerHub downloads" src="https://img.shields.io/docker/pulls/ajbura/cinny?logo=docker&style=social"></a>
<a href="https://fosstodon.org/@cinnyapp">
<img alt="Follow on Mastodon" src="https://img.shields.io/mastodon/follow/106845779685925461?domain=https%3A%2F%2Ffosstodon.org&logo=mastodon&style=social"></a>
<a href="https://twitter.com/intent/follow?screen_name=cinnyapp">
<img alt="Follow on Twitter" src="https://img.shields.io/twitter/follow/cinnyapp?logo=twitter&style=social"></a>
<a href="https://cinny.in/#sponsor">
<img alt="Sponsor Cinny" src="https://img.shields.io/opencollective/all/cinny?logo=opencollective&style=social"></a>
</p>
Versi is a liberated MIT-licensed fork of [Cinny](https://github.com/cinnyapp/cinny) 2.2.4 maintained by and for [TrueOG Network](https://true-og.net/).
**Cinny** is a Matrix client focusing primarily on simple, elegant and secure interface. The main goal is to have a client that is easy on end user
and feels a modern chat application.
Versi is named for Calotes versicolor, a predator of the Cinnyris asiaticus. It also means "an account or description from a particular point of view, especially as contrasted with another account.", which calls attention to the permissive vs copyleft licensing conflict between Versi and Cinny.
- [Contributing](./CONTRIBUTING.md)
- [Roadmap](https://github.com/ajbura/cinny/projects/11)
- [Roadmap](https://true-og.net/todo-list)
- [Contributing](https://true-og.net/TrueOG-Contributor-Guide.pdf)
## Building and Running
## Getting started
Our web app is available at https://chat.true-og.net and gets updated upon each new release.
### Running pre-compiled
You can also download our desktop app, [Versi-Desktop](https://github.com/NotAlexNoyle/Versi-Desktop).
To host Versi on your own, download tarball of the app from the latest [GitHub release](https://github.com/NotAlexNoyle/Versi/releases/latest).
You can serve the application with a webserver of your choice by simply copying `dist/` directory to the webroot.
To set default Homeserver on login and register page, place a customized [`config.json`](config.json) in webroot of your choice.
A tarball of pre-compiled version of the app is provided with each [release](https://github.com/ajbura/cinny/releases).
You can serve the application with a webserver of your choosing by simply copying `dist/` directory to the webroot.
<details>
<summary>PGP Public Key to verify tarball</summary>
<summary>PGP Public Key to verify pre-compiled tarball</summary>
```
TBA on first release.
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBGJw/g0BDAC8qQeLqDMzYzfPyOmRlHVEoguVTo+eo1aVdQH2X7OELdjjBlyj
6d6c1adv/uF2g83NNMoQY7GEeHjRnXE4m8kYSaarb840pxrYUagDc0dAbJOGaCBY
FKTo7U1Kvg0vdiaRuus0pvc1NVdXSxRNQbFXBSwduD+zn66TI3HfcEHNN62FG1cE
K1jWDwLAU0P3kKmj8+CAc3h9ZklPu0k/+t5bf/LJkvdBJAUzGZpehbPL5f3u3BZ0
leZLIrR8uV7PiV5jKFahxlKR5KQHld8qQm+qVhYbUzpuMBGmh419I6UvTzxuRcvU
Frn9ttCEzV55Y+so4X2e4ZnB+5gOnNw+ecifGVdj/+UyWnqvqqDvLrEjjK890nLb
Pil4siecNMEpiwAN6WSmKpWaCwQAHEGDVeZCc/kT0iYfj5FBcsTVqWiO6eaxkUlm
jnulqWqRrlB8CJQQvih/g//uSEBdzIibo+ro+3Jpe120U/XVUH62i9HoRQEm6ADG
4zS5hIq4xyA8fL8AEQEAAbQdQ2lubnlBcHAgPGNpbm55YXBwQGdtYWlsLmNvbT6J
AdQEEwEIAD4WIQSRri2MHidaaZv+vvuUMwx6UK/M8wUCYnD+DQIbAwUJA8JnAAUL
CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCUMwx6UK/M88ApC/9HAdbum1lYBC0s
1k7GwP2A7B4sQtBWjy771BzybWlHeaeG+BGJwg4YiuowXZMm5dubFJFoI/CfeY07
B5aK40/bmT6Xcfkp0VA74c1wUpubBUEJN7tH5HG/OGd9BKeq9E/HHtVaJLVT1k3w
Rhv9VuHO6nR30EEp7IDthftotl5S4lio3+W0pKk4TAKV8vjaCNp3y/lAHzoP1BU9
bUSao+7GXVeArKBjuqxN+t1uuiaxPH4L0oe2pMVjTig04zGJM5fTVoly859MEcC/
R7Taq9RWGfXFmgCXy8Dviz3eOD90vqpCzhX4+ypK0cp2X0UwhMH4dpKUzExmdbhl
eBO5GcHB4VxvloRBNf9/Lr7YOTgWejMUw+MlhZE2RE8unfW1LnM/cjL4dhXzO/XB
FUHHNq8d6d4e02rfWqw7mZo2/NVJgFRcvzw2rgx7w7CKtCNwF4lNjUetB2waZzDb
fAE0kwhK4Iuwvy12JOBzL0Yy9MxANtwUryr/LQz9AmdT4Rwnp0S5AY0EYnD+DQEM
ANOu/d6ZMF8bW+Df9RDCUQKytbaZfa+ZbIHBus7whCD/SQMOhPKntv3HX7SmMCs+
5i27kJMu4YN623JCS7hdCoXVO1R5kXCEcneW/rPBMDutaM472YvIWMIqK9Wwl5+0
Piu2N+uTkKhe9uS2u7eN+Khef3d7xfjGRxoppM+xI9dZO+jhYiy8LuC0oBohTjJq
QPqfGDpowBwRkkOsGz/XVcesJ1Pzg4bKivTS9kZjZSyT9RRSY8As0sVUN57AwYul
s1+eh00n/tVpi2Jj9pCm7S0csSXvXj8v2OTdK1jt4YjpzR0/rwh4+/xlOjDjZEqH
vMPhpzpbgnwkxZ3X8BFne9dJ3maC5zQ3LAeCP5m1W0hXzagYhfyjo74slJgD1O8c
LDf2Oxc5MyM8Y/UK497zfqSPfgT3NhQmhHzk83DjXw3I6Z3A3U+Jp61w0eBRI1nx
H1UIG+gldcAKUTcfwL0lghoT3nmi9JAbvek0Smhz00Bbo8/dx8vwQRxDUxlt7Exx
NwARAQABiQG8BBgBCAAmFiEEka4tjB4nWmmb/r77lDMMelCvzPMFAmJw/g0CGwwF
CQPCZwAACgkQlDMMelCvzPPT7Qv8CjXUEhphZFLwpBfaNOzRNfIXJST9aDit8zHW
IMmfSpORVfpU71IyIB3o/DtTUPwCeb8nvNJs7aj1QT1ZUSsqFa3yY2S16V/g8+WN
sHca6oDSc1J+A0eEpEL1HbG1b5OPBC0AeGvvMOoqrbqThBZVKg1Jc/0SD3cvKElv
aHeCZCNNmfcZ2Ib4HYhhc8//ZtC9TeI+5J/YesctY1M12EoWMxMrc27Y3P5Pa0BI
Uc3qxWggPq1vOFYsEshL0w99HyJvREJmQA7Fa0crV+rICxyrBxJeNnEvjH/0KCBU
LCkEonLY1QwrxyeeV3VpxGE3zHHE3azOdAjTIoAdzX5f/qhbgYlM68GL2f8xdDkp
O0igSGHWhO4F8BfmE7IOTx1Bi7daczp8nCFxh73cKpKB0RUsd9xxrqYpovjmEAlo
w7aHpdzt64NQcsrbK10OSVDF3gFa9Vz20/NQvdUrp8jGmAb/8+nYqI94Jsc28H36
UeGsouhyuITLwEhScounZDqop+Dx
=Zg+6
-----END PGP PUBLIC KEY BLOCK-----
```
</details>
## Local development
> We recommend using [nvm](https://github.com/nvm-sh/nvm) to create a dedicated node environment for each project you work on.
### Building from source
> We recommend using a version manager as versions change very quickly. You will likely need to switch
between multiple Node.js versions based on the needs of different projects you're working on. [NVM on windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) on Windows and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are pretty good choices. Also recommended nodejs version is 16.15.0 LTS.
Execute the following commands to compile the app from its source code:
Execute the following commands to start a development server:
```sh
npm ci # Installs all dependencies
npm start # Serve a development version
```
To build Versi:
```sh
npm run build # Compiles the app into the dist/ directory
```
## Changes
- Changed branding from Cinny to Versi.
- Updated dependencies, patched security vulnerabilities.
- Close settings and search with escape key (credit to MyBeansAreBaked).
You can then copy the files to a webserver's webroot of your choice.
To serve a development version of the app locally for testing, you need to use the command `npm start`.
### Running with Docker
This repository includes a Dockerfile, which builds the application from source and serves it with Nginx on port 80. To
use this locally, you can build the container like so:
```
docker build -t cinny:latest .
```
You can then run the container you've built with a command similar to this:
```
docker run -p 8080:80 cinny:latest
```
This will forward your `localhost` port 8080 to the container's port 80. You can visit the app in your browser by
navigating to `http://localhost:8080`.
Alternatively you can just pull the [DockerHub image](https://hub.docker.com/r/ajbura/cinny) by `docker pull ajbura/cinny`.
### Configuring default Homeserver
To set default Homeserver on login and register page, place a customized [`config.json`](config.json) in webroot of your choice.
## License
Copyright (c) 2021-present Ajay Bura (ajbura)
Copyright (c) 2021 Ajay Bura (ajbura)
Code licensed under the [MIT License](https://raw.githubusercontent.com/NotAlexNoyle/Versi/dev/LICENSE.md).
Code licensed under the MIT License: <http://opensource.org/licenses/MIT>
Original graphics from [Cinny](https://github.com/cinnyapp/cinny) licensed under [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/).
Original graphics from [TrueOG Network](https://github.com/true-og/true-og) are released into the public domain via [The Unlicense](https://github.com/true-og/true-og/blob/main/LICENSE).
Graphics licensed under CC-BY 4.0: <https://creativecommons.org/licenses/by/4.0/>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.
-10
View File
@@ -1,10 +0,0 @@
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'net.trueog.versi',
appName: 'Versi',
webDir: 'dist',
bundledWebRuntime: false,
};
export default config;
+5 -2
View File
@@ -1,8 +1,11 @@
{
"defaultHomeserver": 3,
"defaultHomeserver": 4,
"homeserverList": [
"converser.eu",
"envs.net",
"halogen.city",
"kde.org",
"matrix.org",
"chat.true-og.net",
"mozilla.org"
],
"allowCustomHomeservers": true
@@ -1,7 +1,7 @@
server {
listen 80;
listen [::]:80;
server_name versi.domain.tld;
server_name cinny.domain.tld;
location / {
return 301 https://$host$request_uri;
@@ -15,10 +15,10 @@ server {
server {
listen 443 ssl http2;
listen [::]:443 ssl;
server_name versi.domain.tld;
server_name cinny.domain.tld;
location / {
root /opt/versi/dist/;
root /opt/cinny/dist/;
index index.html;
}
location ~* ^\/(login|register) {
-101
View File
@@ -1,101 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Versi</title>
<meta name="name" content="Versi" />
<meta name="author" content="TrueOG Network" />
<meta
name="description"
content="A liberated MIT-licensed fork of Cinny 2.2.4 maintained by and for TrueOG Network. "
/>
<meta
name="keywords"
content="versi, versiapp, versichat, matrix, matrix client, matrix.org"
/>
<meta property="og:title" content="Versi" />
<meta property="og:url" content="https://chat.true-og.net" />
<meta property="og:image" content="https://raw.githubusercontent.com/NotAlexNoyle/Versi/dev/assets/versi-logo.png" />
<meta
property="og:description"
content="A liberated MIT-licensed fork of Cinny 2.2.4 maintained by and for TrueOG Network. "
/>
<meta name="theme-color" content="#000000" />
<link id="favicon" rel="shortcut icon" href="./public/favicon.ico" />
<link rel="manifest" href="./manifest.json" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="application-name" content="Versi" />
<meta name="apple-mobile-web-app-title" content="Versi" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link
rel="apple-touch-icon"
sizes="57x57"
href="./public/res/apple/apple-touch-icon-57x57.png"
/>
<link
rel="apple-touch-icon"
sizes="60x60"
href="./public/res/apple/apple-touch-icon-60x60.png"
/>
<link
rel="apple-touch-icon"
sizes="72x72"
href="./public/res/apple/apple-touch-icon-72x72.png"
/>
<link
rel="apple-touch-icon"
sizes="76x76"
href="./public/res/apple/apple-touch-icon-76x76.png"
/>
<link
rel="apple-touch-icon"
sizes="114x114"
href="./public/res/apple/apple-touch-icon-114x114.png"
/>
<link
rel="apple-touch-icon"
sizes="120x120"
href="./public/res/apple/apple-touch-icon-120x120.png"
/>
<link
rel="apple-touch-icon"
sizes="144x144"
href="./public/res/apple/apple-touch-icon-144x144.png"
/>
<link
rel="apple-touch-icon"
sizes="152x152"
href="./public/res/apple/apple-touch-icon-152x152.png"
/>
<link
rel="apple-touch-icon"
sizes="167x167"
href="./public/res/apple/apple-touch-icon-167x167.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="./public/res/apple/apple-touch-icon-180x180.png"
/>
</head>
<body id="appBody">
<script>
window.global ||= window;
</script>
<div id="root"></div>
<audio id="notificationSound">
<source src="./public/sound/notification.ogg" type="audio/ogg" />
</audio>
<audio id="inviteSound">
<source src="./public/sound/invite.ogg" type="audio/ogg" />
</audio>
<script type="module" src="./src/index.jsx"></script>
</body>
</html>
BIN
View File
Binary file not shown.
+20331 -5920
View File
File diff suppressed because it is too large. Load diff
+74 -74
View File
@@ -1,87 +1,87 @@
{
"name": "versi",
"version": 0.1,
"description": "A liberated MIT-licensed fork of Cinny 2.2.4 maintained by and for TrueOG Network.",
"name": "cinny",
"version": "2.0.4",
"description": "Yet another matrix client",
"main": "index.js",
"engines": {
"node": ">=16.0.0"
"npm": ">=6.14.8 <=8.5.5",
"node": ">=14.15.0 <=17.9.0"
},
"scripts": {
"start": "vite",
"build": "vite build",
"lint": "yarn check:eslint && yarn check:prettier",
"check:eslint": "eslint src/*",
"check:prettier": "prettier --check .",
"fix:prettier": "prettier --write .",
"typecheck": "tsc --noEmit"
"start": "webpack serve --config ./webpack.dev.js --open",
"build": "webpack --config ./webpack.prod.js"
},
"keywords": [],
"author": "TrueOG Network",
"author": "Ajay Bura",
"license": "MIT",
"dependencies": {
"@capacitor/android": "4.7.1",
"@capacitor/core": "4.7.1",
"@fontsource/inter": "4.5.14",
"@fontsource/roboto": "4.5.8",
"@khanacademy/simple-markdown": "0.8.6",
"@lzy1960/google-translate": "0.2.6",
"@matrix-org/olm": "3.2.14",
"@tippyjs/react": "4.2.6",
"blurhash": "2.0.4",
"browser-encrypt-attachment": "0.3.0",
"dateformat": "5.0.3",
"emojibase-data": "7.0.1",
"file-saver": "2.0.5",
"flux": "4.0.3",
"formik": "2.2.9",
"google-translate-api-browser": "5.0.0",
"html-react-parser": "3.0.4",
"i18next": "22.4.9",
"i18next-browser-languagedetector": "7.0.1",
"i18next-http-backend": "2.1.1",
"katex": "0.16.10",
"linkify-html": "4.0.2",
"linkifyjs": "4.0.2",
"matrix-js-sdk": "24.1.0",
"prop-types": "15.8.1",
"react": "17.0.2",
"react-autosize-textarea": "7.1.0",
"react-blurhash": "0.2.0",
"react-dnd": "15.1.2",
"react-dnd-html5-backend": "15.1.3",
"react-dom": "17.0.2",
"react-google-recaptcha": "2.1.0",
"react-i18next": "12.1.5",
"react-modal": "3.16.1",
"sanitize-html": "2.13.0",
"tippy.js": "6.3.7",
"twemoji": "14.0.2"
"@fontsource/inter": "^4.5.11",
"@fontsource/roboto": "^4.5.7",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.8.tgz",
"@tippyjs/react": "^4.2.6",
"babel-polyfill": "^6.26.0",
"browser-encrypt-attachment": "^0.3.0",
"dateformat": "^5.0.3",
"emojibase-data": "^7.0.1",
"file-saver": "^2.0.5",
"flux": "^4.0.3",
"formik": "^2.2.9",
"html-react-parser": "^2.0.0",
"katex": "^0.15.6",
"linkifyjs": "^2.1.9",
"matrix-js-sdk": "^18.1.0",
"micromark": "^3.0.10",
"micromark-extension-gfm": "^2.0.1",
"micromark-extension-math": "^2.0.2",
"micromark-util-chunked": "^1.0.0",
"micromark-util-resolve-all": "^1.0.0",
"micromark-util-symbol": "^1.0.1",
"prop-types": "^15.8.1",
"react": "^17.0.2",
"react-autosize-textarea": "^7.1.0",
"react-dnd": "^15.1.2",
"react-dnd-html5-backend": "^15.1.3",
"react-dom": "^17.0.2",
"react-google-recaptcha": "^2.1.0",
"react-modal": "^3.15.1",
"sanitize-html": "^2.7.0",
"tippy.js": "^6.3.7",
"twemoji": "^14.0.2"
},
"devDependencies": {
"@capacitor/assets": "3.0.5",
"@capacitor/cli": "4.8.2",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rollup/plugin-inject": "5.0.3",
"@rollup/plugin-wasm": "6.1.1",
"@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"@typescript-eslint/eslint-plugin": "5.46.1",
"@typescript-eslint/parser": "5.46.1",
"@vitejs/plugin-react": "3.0.0",
"buffer": "6.0.3",
"eslint": "8.29.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jsx-a11y": "6.6.1",
"eslint-plugin-react": "7.31.11",
"eslint-plugin-react-hooks": "4.6.0",
"mini-svg-data-uri": "1.4.4",
"prettier": "2.8.1",
"sass": "1.56.2",
"typescript": "4.9.4",
"vite": "4.5.3",
"vite-plugin-static-copy": "0.13.0"
"@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"assert": "^2.0.0",
"babel-loader": "^8.2.5",
"browserify-fs": "^1.0.0",
"buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"eslint": "^8.19.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2",
"html-loader": "^3.1.2",
"html-webpack-plugin": "^5.3.1",
"mini-css-extract-plugin": "^2.6.1",
"path-browserify": "^1.0.1",
"sass": "^1.53.0",
"sass-loader": "^13.0.2",
"stream-browserify": "^3.0.0",
"style-loader": "^3.3.1",
"url": "^0.11.0",
"util": "^0.12.4",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3",
"webpack-merge": "^5.7.3"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

+28
View File
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cinny</title>
<meta name="name" content="Cinny">
<meta name="author" content="Ajay Bura">
<meta name="description" content="A Matrix client where you can enjoy the conversation using simple, elegant and secure interface protected by e2ee with the power of open source.">
<meta name="keywords" content="cinny, cinnyapp, cinnychat, matrix, matrix client, matrix.org, element">
<meta property="og:title" content="Cinny">
<meta property="og:url" content="https://cinny.in">
<meta property="og:image" content="https://cinny.in/assets/favicon-48x48.png">
<meta property="og:description" content="A Matrix client where you can enjoy the conversation using simple, elegant and secure interface protected by e2ee with the power of open source.">
<meta name="theme-color" content="#000000">
</head>
<body id="appBody">
<div id="root"></div>
<audio id="notificationSound">
<source src="./sound/notification.ogg" type="audio/ogg" />
</audio>
<audio id="inviteSound">
<source src="./sound/invite.ogg" type="audio/ogg" />
</audio>
</body>
</html>
-889
View File
@@ -1,889 +0,0 @@
{
"translation": {
"common": {
"close": "Close",
"open": "Open",
"leave": "Leave",
"options": "Options",
versi: Versi,
"slogan": "Yet another matrix client",
"source_code": "Source code",
"sponsor": "Support",
"retry": "Retry",
"delete": "Delete",
"continue": "Continue",
"cancel": "Cancel",
"save": "Save",
"view_more": "View more",
"view_less": "View less",
"copy": "Copy",
"upload": "Upload",
"download": "Download",
"or": "Or",
"reset": "Reset",
"setup": "Setup",
"search": "Search",
"loading": "Loading...",
"joining": "Joining...",
"join": "Join",
"remove": "Remove",
"send": "Send",
"homeserver": "Homeserver",
"invite": "Invite",
"uninvite": "Uninvite",
"invited": "Invited",
"inviting": "Inviting...",
"uninviting": "Uninviting...",
"change": "Change",
"edit": "Edit",
"message_prompt": "Message"
},
"errors": {
"browser_not_supported": "Not supported in this browser",
"generic": "Something went wrong!"
},
"Organisms": {
"Welcome": {
"heading": "Welcome to Versi!",
"subheading": A libre matrix client by TrueOG Network
},
"ViewSource": {
"title": "View Source",
"original_source": "Original source",
"decrypted_source": "Decrypted source"
},
"SpaceManage": {
"subtitle": "manage rooms",
"load_more": "Load more",
"rooms_and_spaces": "Rooms and spaces",
"private_rooms_message": "Either the space contains private rooms or you need to join space to view it's rooms.",
"items_selected_zero": "No selected items",
"items_selected_one": "{{count}} selected item",
"items_selected_other": "{{count}} selected items",
"room_members_zero": "No room members",
"room_members_one": "{{count}} room member",
"room_members_other": "{{count}} room members",
"mark_suggested_zero": "Marking no rooms as suggested",
"mark_suggested_one": "Marking {{count}} room as suggested",
"mark_suggested_other": "Marking {{count}} rooms as suggested",
"mark_not_suggested_zero": "Marking no rooms as suggested",
"mark_not_suggested_one": "Marking {{count}} room as suggested",
"mark_not_suggested_other": "Marking {{count}} rooms as suggested",
"remove_zero": "Removing no items",
"remove_one": "Removing {{count}} item",
"remove_other": "Removing {{count}} items.",
"suggested": "Suggested",
"mark_as_suggested": "Mark as suggested",
"mark_as_not_suggested": "Mark as not suggested"
},
"SpaceSettings": {
"subtitle": "space settings",
"leave": {
"leave_space": "Leave Space",
"leave_dialog_title": "Leave Space",
"leave_dialog_message": "Are you sure you want to leave {{space}}?"
},
"visibility": {
"header": "Space visibility (who can join)"
},
"addresses": {
"header": "Space addresses"
},
"categorize_subspaces": "Categorize subspaces",
"uncategorize_subspaces": "Uncategorize subspaces",
"pin_sidebar": "Pin to sidebar",
"unpin_sidebar": "Unpin from sidebar"
},
"Settings": {
"title": "Settings",
"theme": {
"follow_system": {
"title": "Follow system theme",
"description": "Use light or dark mode based on the system settings."
},
"title": "Theme",
"theme_light": "Light",
"theme_silver": "Silver",
"theme_dark": "Dark",
"theme_butter": "Butter"
},
"markdown": {
"title": "Markdown formatting",
"description": "Format messages with markdown before sending"
},
"hide_membership_events": {
"title": "Hide membership events",
"description": "Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and Ban)"
},
"hide_nickname_avatar_events": {
"title": "Hide nick/avatar events",
"description": "Hide nickname and avatar change messages from the room timeline."
},
"notifications_and_sound": {
"title": "Notifications & Sound",
"desktop": {
"title": "Desktop notifications",
"description": "Show desktop notifications when new messages arrive."
},
"sound": {
"title": "Notification sound",
"description": "Play a sound when new messages arrive."
}
},
"security": {
"cross_signing": {
"title": "Cross signing and backup"
},
"export_import_encryption_keys": {
"title": "Export / Import encryption keys"
},
"export_encryption_keys": {
"title": "Export E2E room keys",
"description": "Export end-to-end encryption room keys to decrypt old messages in other session. In order to encrypt keys you need to set a password, which will be used while importing."
},
"import_encryption_keys": {
"title": "Import E2E room keys",
"description": "To decrypt older messages, Export E2EE room keys from Element (Settings > Security & Privacy > Encryption > Cryptography) and import them here. Imported keys are encrypted so you\\'ll have to enter the password you set in order to decrypt it."
}
},
"logout": {
"title": "Logout",
"dialog": {
"title": "Logout",
"description": "Are you sure that you want to logout your session?",
"confirm": "Logout"
}
},
"about": {
"application": "Application",
"credits": "Credits"
}
},
"ShortcutSpaces": {
"header": "Pin Spaces",
"pinned_spaces": "Pinned spaces",
"no_pinned_spaces": "No pinned spaces",
"unpinned_spaces": "Unpinned spaces",
"no_unpinned_spaces": "No unpinned spaces",
"spaces_selected_zero": "No selected spaces",
"spaces_selected_one": "{{count}} selected space",
"spaces_selected_other": "{{count}} selected spaces",
"pin_button": "Pin"
},
"SecretStorageAccess": {
"incorrect_security_key": "Incorrect security key",
"incorrect_security_phrase": "Incorrect security phrase",
"security_phrase": "Security Phrase",
"security_key": "Security Key",
"use_security_key": "Use Security Key",
"use_security_phrase": "Use Security Phrase"
},
"KeyBackup": {
"create_backup_title": "Create key backup",
"create_backup_tooltip": "Create backup",
"creating_backup": "Creating Backup...",
"backup_created": "Successfully created backup",
"backup_failed": "Failed to create backup",
"restoring": "Restoring backup keys...",
"restoring_progress": "Restoring backup keys... ({{progress}}/{{total}}",
"restore_backup_title": "Restore Key Backup",
"restore_backup_tooltip": "Restore Key Backup",
"restore_complete": "Successfully restored backup keys ({{progress}}/{{total}})",
"restore_failed_bad_key": "Failed to restore backup. Key is invalid!",
"restore_failed_unknown": "Failed to restore backup.",
"delete_key_backup_title": "Delete key backup",
"delete_key_backup_tooltip": "Delete backup",
"delete_key_backup_subtitle": "Deleting key backup is permanent.",
"delete_key_backup_message": "All encrypted message keys stored on the server will be permanently deleted.",
"encrypted_messages_backup_description": "Online backup your encrypted messages keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.",
"encrypted_messages_backup_title": "Encrypted messages backup",
"encrypted_messages_backup_cross_signing_disabled": "Setup cross signing to backup your encrypted messages."
},
"DeviceManage": {
"edit_session_name_title": "Edit session name",
"edit_session_name_subtitle": "Session name",
"edit_session_name_tooltip": "Edit session name",
"current_device_label": "Current",
"verify_session_button": "Verify",
"unverified_sessions_title": "Unverified sessions",
"unverified_sessions_none": "No unverified sessions",
"unencrypted_sessions_title": "Sessions without encryption support",
"verified_sessions_title": "Verified sessions",
"verified_sessions_none": "No verified sessions",
"setup_cross_signing_message": "Setup cross signing in case you lose all your sessions",
"loading_devices": "Loading devices...",
"logout_device_title": "Logout {{device}}",
"logout_device_message": "You are about to log out the session for {{device}}",
"logout_device_confirm": "Logout",
"logout_device_tooltip": "Remove session",
"session_verification_title": "Session Verification",
"session_name_privacy_message": "Session names are visible to everyone, so do not put any private info here."
},
"CrossSigning": {
"title": "Cross Signing",
"setup_failed": "Failed to setup cross signing. Please try again",
"setup": "Setup cross signing",
"save_security_key_message": "Please save this security key somewhere safe",
"security_key_dialog_title": "Security Key",
"security_key_generation_message": "We will generate a Security Key, which you can use to manage message backups and session verification.",
"security_key_generation_button": "Generate Key",
"security_phrase_message": "Alternatively you can set a 'Security Phrase' so you don't have to remember the long Security Key, and optionally save the key as a backup",
"security_phrase_label": "Security Phrase",
"security_phrase_confirm_label": "Confirm Security Phrase",
"security_phrase_set_button": "Set Phrase & Generate Key",
"setup_dialog_title": "Setup cross signing",
"setup_message": "Setup to verify and keep track of all your sessions. Also required to backup encrypted message.",
"reset_keys_subtitle": "Resetting cross-signing keys is permanent.",
"reset_keys_message": "Anyone you have verified with will see security alerts and your message backup will lost. You almost certainly do not want to do this, unless you have lost Security Key or Phrase and every session you can cross-sign from."
},
"AuthRequest": {
"wrong_password": "Wrong password. Please enter the correct password",
"request_failed": "Request failed!",
"password_label": "Account password"
},
"Search": {
"description": "Type # for rooms, @ for DMs and * for spaces. Hotkey: Ctrl + k"
},
"RoomViewInput": {
"upload_progress": "Uploading: {{progress}}/{{total}} ({{percent}}%)",
"tombstone_replaced": "This room has been replaced, and is no longer active.",
"tombstone_permission_denied": "You do not have permission to post to this room",
"send_message_placeholder": "Send a message...",
"emoji_tooltip": "Emoji",
"file_size": "Size: {{size}}",
"cancel_reply_tooltip": "Cancel reply"
},
"RoomViewHeader": {
"search_tooltip": "Search",
"people_tooltip": "People",
"members_tooltip": "Members"
},
"RoomViewFloating": {
"jump_unread": "Jump to unread messages",
"mark_read": "Mark as read",
"jump_latest": "Jump to latest",
"user_typing_1": "<bold><user_one/></bold> is typing...",
"user_typing_2": "<bold><user_one/></bold> and <bold><user_two/></bold> are typing...",
"user_typing_3": "<bold><user_one/></bold>, <bold><user_two/></bold> and <bold><user_three/></bold> are typing...",
"user_typing_4": "<bold><user_one/></bold>, <bold><user_two/></bold>, <bold><user_three/></bold> and <bold><user_four/></bold> are typing...",
"user_typing_other": "<bold>Several people</bold> are typing..."
},
"RoomViewContent": {
"welcome_to_room": "Welcome to {{room_name}}!",
"beginning_room": "This is the beginning of the <bold>{{room_name}}</bold> room.",
"beginning_dm": "This is the beginning of your direct message history with <bold>@{{user_name}}</bold>.",
"created_on": "Created on {{date, datetime}}",
"new_messages": "New messages"
},
"RoomSettings": {
"leave_room": "Leave room",
"leave_room_confirm_message": "Are you sure you want to leave {{room_name}}?",
"leave_room_confirm_button": "Leave",
"notification_header": "Notifications (Changing this will only affect you)",
"visibility_header": "Room visibility (Who can join)",
"address_header": "Room addresses",
"encryption_header": "Encryption",
"message_history_header": "Message history visibility",
"room_settings_subtitle": "room settings"
},
"RoomCommon": {
"user_joined": "<bold><user/></bold> joined the room",
"user_left": "<bold><user/></bold> left the room",
"user_left_reason": "<bold><user/></bold> left the room: <reason/>",
"user_invited": "<bold><inviter/></bold> invited <bold><user/></bold>",
"invite_cancelled": "<bold><inviter/></bold> cancelled <bold><user/>'s</bold> invite",
"invite_rejected": "<bold><user/></bold> rejected the invitation",
"user_kicked": "<bold><actor/></bold> kicked <bold><user/></bold>",
"user_kicked_reason": "<bold><actor/></bold> kicked <bold><user/></bold>: <reason/>",
"user_banned": "<bold><actor/></bold> banned <bold><user/></bold>",
"user_banned_reason": "<bold><actor/></bold> banned <bold><user/></bold>: <reason/>",
"user_unbanned": "<bold><actor/> unbanned <bold><user/></bold>",
"avatar_set": "<bold><user/></bold> set an avatar",
"avatar_changed": "<bold><user/></bold> changed their avatar",
"avatar_removed": "<bold><user/></bold> removed their avatar",
"name_set": "<bold><user/></bold> set their display name to <bold><new_name/></bold>",
"name_changed": "<bold><user/></bold> changed their display name to <bold><new_name/></bold>",
"name_removed": "<bold><user/></bold> removed their display name <bold><last_name/></bold>"
},
"PublicRooms": {
"could_not_join_alias": "Unable to join {{alias}}. Either the room is private or doesn't exist",
"try_joining_alias": "Try joining {{alias}}",
"joining_alias": "Joining {{alias}}...",
"no_public_rooms": "No public rooms on {{homeserver}}",
"no_result_found": "No result found for '{{input}}' on {{homeserver}}",
"title": "Public Rooms",
"search_room_name_alias": "Room name or alias",
"search_button": "Search",
"loading": "Loading public rooms from {{homeserver}}...",
"searching": "Searching for '{{query}}' on {{homeserver}}...",
"result_title": "Public rooms on {{homeserver}}",
"search_result_title": "Search result for '{{query}}' on {{homeserver}}"
},
"ProfileViewer": {
"kick_button": "Kick",
"kick_reason_label": "Kick Reason",
"ban_button": "Ban",
"ban_reason_label": "Ban reason",
"loading_sessions": "Loading sessions...",
"no_sessions_found": "No sessions found.",
"view_sessions_one": "View session",
"view_sessions_other": "View {{count}} sessions",
"send_direct_message_button": "Message",
"creating_dm_room": "Creating room...",
"ignore": "Ignore",
"ignoring": "Ignoring...",
"unignore": "Unignore",
"unignoring": "Unignoring...",
"change_power_level": "Change power level",
"shared_power_message": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?",
"demoting_self_message": "You will not be able to undo this change as you are demoting yourself. Are you sure?"
},
"PeopleDrawer": {
"title": "People",
"members_one": "{{count}} member",
"members_other": "{{count}} members",
"joined": "Joined",
"invited": "Invited",
"banned": "Banned",
"search_no_results": "No results found",
"view_more": "View more",
"placeholder": "Search",
"invite_tooltip": "Invite"
},
"ProfileEditor": {
"remove_avatar": "Remove avatar",
"remove_avatar_confirmation": "Are you sure that you want to remove your avatar?",
"display_name_message": "Display name of {{user_name}}"
},
"DrawerBreadcrumb": {
"home": "Home"
},
"DrawerHeader": {
"add_rooms_or_spaces": "Add rooms or spaces",
"create_new_space": "Create new space",
"create_new_room": "Create new room",
"join_public_room": "Join public room",
"join_with_address": "Join with address",
"add_existing": "Add existing",
"manage_rooms": "Manage rooms",
"home": "Home",
"direct_messages": "Direct messages",
"start_dm_tooltip": "Start DM",
"add_rooms_spaces_tooltip": "Add rooms/spaces"
},
"SideBar": {
"settings_tooltip": "Settings",
"unverified_sessions_one": "{{count}} unverified session",
"unverified_sessions_other": "{{count}} unverified sessions",
"home_tooltip": "Home",
"direct_messages_tooltip": "People",
"pin_spaces_tooltip": "Pin spaces",
"search_tooltip": "Search",
"invites_tooltip": "Invites"
},
"JoinAlias": {
"invalid_address": "Invalid address.",
"looking_for_address": "Looking for address...",
"joining_alias": "Joining {{alias_name}}...",
"couldnt_find_room_or_space_alias": "Unable to find room/space with {{alias_name}}. Either the room/space is private or doesn't exist.",
"couldnt_find_room_or_space": "Unable to join {{alias_name}}. Either the room/space is private or doesn't exist.",
"address_label": "Address",
"title": "Join with address"
},
"InviteUser": {
"user_not_found": "{{user_name}} not found!",
"no_matches_found": "No matches found for {{user_name}}",
"invite_result": {
"invited": "Invited",
"already_joined": "Already joined",
"already_invited": "Already invited",
"banned": "Banned"
},
"search_label": "Name or User ID",
"search_result_title": "Search result for user {{user_name}}",
"searching_for_user": "Searching for user {{user_name}}...",
"invite_to_room": "Invite to {{room}}",
"invite_to_dm": "Direct Message"
},
"InviteList": {
"accept_invite": "Accept",
"reject_invite": "Reject",
"direct_messages_title": "Direct Messages",
"rooms_title": "Rooms",
"spaces_title": "Spaces",
"title": "Invites"
},
"EmojiVerification": {
"waiting_for_response": "Waiting for response from other device...",
"confirmation_prompt": "Confirm the emoji below are displayed on both devices, in the same order:",
"emojis_match_button": "They match",
"emojis_dont_match_button": "They don't match",
"accept_request_from_other_device_message": "Please accept the request from other device.",
"begin_verification_process_message": "Click accept to start the verification process.",
"begin_verification_button_text": "Accept",
"title": "Emoji Verification"
},
"DragDrop": {
"drop_file_to_upload_prompt": "Drop file to upload"
},
"CreateRoom": {
"private_room_short": "Private",
"restricted_room_short": "Restricted",
"public_room_short": "Public",
"private_room_long": "Private (invite only)",
"restricted_room_long": "Restricted (space member can join)",
"public_room_long": "Public (anyone can join)",
"visibility_title": "Visibility",
"visibility_message": "Visibility (who can join)",
"select_who_can_join_space": "Select who can join this space",
"select_who_can_join_room": "Select who can join this room",
"space_address": "Space address",
"room_address": "Room address",
"room_address_already_in_use": "{{room_address}} is already in use",
"e2e_title": "Enable end-to-end encryption",
"e2e_message": "You can’t disable this later. Bridges & most bots won’t work yet.",
"role_title": "Select your role",
"role_message": "Selecting 'Admin' sets your power level to 100 whereas 'Founder' sets it to 101.",
"creating_room": "Creating room...",
"creating_space": "Creating space...",
"topic_label": "Topic (optional)",
"space_name": "Space name",
"room_name": "Room name",
"role_admin": "Admin",
"role_founder": "Founder",
"create_room": "Create room",
"create_space": "Create space",
"home": "Home"
}
},
"Molecules": {
"ConfirmDialog": {
"cancel": "Cancel"
},
"ReusableDialog": {
"close_tooltip": "Close"
},
"FollowingMembers": {
"users_following_1": "<bold><user_one/></bold> is following the conversation",
"users_following_2": "<bold><user_one/></bold> and <bold><user_two/></bold> are following the conversation",
"users_following_3": "<bold><user_one/></bold>, <bold><user_two/></bold>, and <bold><user_three/></bold> are following the conversation",
"users_following_other": "<bold><user_one/></bold>, <bold><user_two/></bold>, <bold><user_three/></bold> and {{other_count}} others are following the conversation"
},
"ImageUpload": {
"prompt": "Upload",
"cancel": "Cancel",
"remove": "Remove"
},
"ExportE2ERoomKeys": {
"getting_keys": "Getting keys...",
"encrypting_keys": "Encrypting Keys...",
"password_does_not_match": "Password does not match.",
"export_success": "Successfully exported all keys.",
"export_failed": "Failed to export keys. Please try again.",
"button_text": "Export"
},
"ImportE2ERoomKeys": {
"decrypting_file": "Decrypting file...",
"decrypting_messages": "Decrypting messages...",
"import_success": "Successfully imported all keys",
"import_failed": "Failed tp decrypt keys. Please try again",
"import_keys_button": "Import Keys",
"decrypt_button": "Decrypt"
},
"Media": {
"open_new_tab": "Open in new tab",
"download": "Download",
"play_audio": "Play audio",
"play_video": "Play video"
},
"Message": {
"message_deleted": "*** This message has been deleted ***",
"unable_to_load_reply": "*** Unable to load reply ***",
"unknown_user": "** Unknown user **",
"edited": "(edited)",
"edit_placeholder": "Edit message",
"user_reacted_1": "<bold><user_one/></bold> reacted with <emoji/>",
"user_reacted_2": "<bold><user_one/></bold> and <bold><user_two/></bold> reacted with <emoji/>",
"user_reacted_3": "<bold><user_one/></bold>, <bold><user_two/></bold> and <bold><user_three/></bold> reacted with <emoji/>",
"user_reacted_other": "<bold><user_one/></bold>, <bold><user_two/></bold>, <bold><user_three/></bold> and {{other_count}} others reacted with <emoji/>",
"add_reaction_tooltip": "Add reaction",
"reply_tooltip": "Reply",
"edit_tooltip": "Edit",
"options_header": "Options",
"options_tooltip": "Options",
"read_receipts": "Read receipts",
"view_source": "View source",
"delete_message_prompt": "Delete message",
"delete_message_confirmation": "Are you sure that you want to delete this message?",
"delete_message_button": "Delete"
},
"PopupWindow": {
"close_tooltip": "Back"
},
"PowerLevelSelector": {
"placeholder": "Power level"
},
"RoomAliases": {
"invalid_characters": "Invalid character: only letter, numbers and _- are allowed.",
"validating_alias": "validating {{alias}}...",
"alias_available": "{{alias}} is available.",
"alias_unavailable": "{{alias}} is unavailable.",
"deleting_alias": "Deleting...",
"set_main_alias": "Set as Main",
"publish_alias": "Publish",
"unpublish_alias": "Unpublish",
"delete_alias": "Delete",
"main_alias": "Main",
"publish_to_room_directory": {
"title": "Publish to room directory",
"publish_room_message": "Publish this room to the {{homeserver}} publish directory?",
"publish_space_message": "Publish this space to the {{homeserver}} publish directory?"
},
"published_addresses": {
"title": "Published addresses",
"none": "No published addresses",
"no_main_address": "No main address (select one from below)",
"message_room": "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.",
"message_space": "Published addresses can be used by anyone on any server to join your space. To publish an address, it needs to be set as a local address first."
},
"local_addresses": {
"title": "Local addresses",
"none": "No local addresses",
"message_room": "Set local addresses for this room so users can find this room through your homeserver.",
"message_space": "Set local addresses for this space so users can find this space through your homeserver.",
"add": "Add local address",
"add_button": "Add",
"placeholder_room": "my_room_address",
"placeholder_space": "my_space_address",
"hide": "Hide local addresses",
"view": "View local addresses"
}
},
"RoomEncryption": {
"encryption_public_room_message": "It is not recommended to add encryption in public room. Anyone can find and join public rooms, so anyone should be able to read messages in them.",
"encryption_message": "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly",
"encryption_cannot_be_disabled": "Once enabled, encryption cannot be disabled.",
"enable_room_encryption": "Enable room encryption",
"enable_encryption_prompt": "Enable Encryption",
"enable_encryption_button": "Enable",
"continue_button": "Continue"
},
"RoomHistoryVisibility": {
"world_readable": "Anyone (including guests)",
"shared": "Members (all messages)",
"invited": "Members (messages after invite)",
"joined": "Members (messages after join)",
"changes_only_affect_future": "Changes to history visibility will only apply to future messages. The visibility of existing history will have no effect."
},
"RoomMembers": {
"search_title": "Search member",
"found_members_one": "Found — one member",
"found_members_other": "Found — {{count}} members",
"no_results": "No results found for {{term}}",
"no_members": "No members to display",
"view_more": "View more",
"joined": "Joined",
"invited": "Invited",
"banned": "Banned",
"search_placeholder": "Search for name"
},
"RoomNotification": {
"default": "Global",
"all_messages": "All messages",
"mentions_and_keywords": "Mentions & Keywords",
"mute": "Mute"
},
"RoomOptions": {
"title": "Options for {{room_name}}",
"leave": {
"title": "Leave room",
"subtitle": "Are you sure you want to leave the {{room_name}} room?",
"button_text": "Leave"
},
"mark_as_read": "Mark as read",
"notifications_heading": "Notifications",
"invite": "Invite"
},
"RoomPermissions": {
"default_role": {
"name": "Default role",
"description": "Set default role for all members"
},
"send_messages": {
"name": "Send messages",
"description": "Set minimum power level to send messages in a room"
},
"reactions": {
"name": "Send reactions",
"description": "Set minimum power level to send reactions in a room"
},
"delete": {
"name": "Delete messages sent by others",
"description": "Set minumum power level to delete messages in a room"
},
"notifications": {
"name": "Ping room",
"description": "Set minimum power level to ping room"
},
"manage_rooms": {
"name": "Manage rooms in space",
"description": "Set minimum power level to manage rooms in space"
},
"invite": {
"name": "Invite",
"description": "Set minimum power level to invite members"
},
"kick": {
"name": "Kick",
"description": "Set minimum power level to kick members"
},
"ban": {
"name": "Ban",
"description": "Set minimum power level to ban members"
},
"change_avatar": {
"name": "Change avatar",
"description": "Set minimum power level to change room/space avatar"
},
"change_name": {
"name": "Change name",
"description": "Set minimum power evel to change room/space name"
},
"change_topic": {
"name": "Change topic",
"description": "Set minimum power level to change room/space topic"
},
"change_settings": {
"name": "Change settings",
"description": "Set minimum power level to change settings"
},
"change_published_address": {
"name": "Change published address",
"description": "Set minimum power level to publish and set main address"
},
"change_permissions": {
"name": "Change permissions",
"description": "Set minimum power level to change permissions"
},
"enable_room_encryption": {
"name": "Enable room encryption",
"description": "Set minimum power level to enable room encryption"
},
"change_history_visibility": {
"name": "Change history visibility",
"description": "Set minimum power level to change room message history visibility"
},
"upgrade_room": {
"name": "Upgrade room",
"description": "Set minimum power level to upgrade room"
},
"pin_messages": {
"name": "Pin messages",
"description": "Set minimum power level to pin messages in a room"
},
"change_acls": {
"name": "Change server ACLs",
"description": "Set minimum power level to change server ACLs"
},
"modify_widgets": {
"name": "Modify widgets",
"description": "Set minimum power level to modify room widgets"
},
"groups": {
"general": "General permissions",
"manage_members": "Manage members permissions",
"room": "Room profile permissions",
"space": "Space profile permissions",
"other": "Other permissions",
"settings": "Settings permissions"
}
},
"RoomProfile": {
"saving_room_name": "Saving room name...",
"saving_room_topic": "Saving room topic...",
"save_success": "Saved successfully",
"save_failed": "Unable to save",
"remove_avatar_title": "Remove avatar",
"remove_avatar_subtitle": "Are you sure that you want to remove room avatar?",
"remove_avatar_button": "Remove",
"permission_change_room_name": "You have permission to change room name only",
"permission_change_room_topic": "You have permission to change room topic only",
"permission_change_space_name": "You have permission to change space name only",
"permission_change_space_topic": "You have permission to change space topic only",
"name_label": "Name",
"topic_label": "Topic"
},
"RoomSearch": {
"title": "Room search",
"placeholder": "Search for keywords",
"search_button": "Search",
"searching": "Searching room messages...",
"subtitle": "Search room messages",
"failed": "Failed to search messages",
"no_results": "No results found",
"encrypted_room": "Search does not work in encrypted room",
"load_more": "Load more",
"results_one": "{{count}} result for {{term}}",
"results_other": "{{count}} results for {{term}}"
},
"RoomTile": {
"invited_by_user_zero": "Invited by {{inviter}} to {{id}}",
"invited_by_user_one": "Invited by {{inviter}} to {{id}} • {{member_count}} member",
"invited_by_user_other": "Invited by {{inviter}} to {{id}} • {{member_count}} members",
"invited_zero": "{{id}}",
"invited_one": "{{id}} • {{member_count}} member",
"invited_other": "{{id}} • {{member_count}} members"
},
"RoomVisibility": {
"private": "Private (invite only)",
"restricted": "Restricted (space members can join)",
"restricted_unsupported": "Restricted (Unsupported: room required upgrade)",
"public": "Public (anyone can join)"
},
"RoomEmojis": {
"header": "Create Pack",
"submit_button": "Create pack",
"name_placeholder": "Pack Name",
"no_packs": "No emoji or sticker packs."
},
"ImagePack": {
"rename_title": "Rename",
"rename_button": "Rename",
"shortcode_label": "Shortcode",
"delete_item_title": "Delete",
"delete_item_message": "Are you sure that you want to delete {{key}}?",
"delete_item_button": "Delete",
"delete_pack_title": "Delete Pack",
"delete_pack_message": "Are you sure that you want to delete {{pack_name}}?",
"delete_pack_button": "Delete",
"image_title": "Image",
"image_shortcode": "Shortcode",
"image_usage": "Usage",
"view_more": "View {{count}} more",
"view_less": "View less",
"use_globally": "Use globally",
"use_globally_description": "Add this pack to your account to use in all rooms.",
"no_global_packs": "No global packs",
"default_personal_pack_name": "Personal"
},
"ImagePackItem": {
"rename_tooltip": "Rename",
"delete_tooltip": "Delete"
},
"ImagePackUsageSelector": {
"header": "Usage",
"type_emoji": "Emoji",
"type_sticker": "Sticker",
"type_both": "Both"
},
"ImagePackUpload": {
"remove_file_tooltip": "Remove file",
"import_image_button": "Import image",
"shortcode_placeholder": "shortcode",
"upload_button": "Upload",
"upload_button_progress": "Uploading..."
},
"ImagePackProfile": {
"attribution_prompt": "Attribution",
"name_prompt": "Name",
"edit_tooltip": "Edit",
"usage_header": "Usage",
"pack_usage": "Pack usage"
},
"SpaceAddExisting": {
"adding_items_one": "Adding one item...",
"adding_items_other": "Adding {{count}} items...",
"items_selected_one": "{{count}} item selected",
"items_selected_other": "{{count}} items selected",
"search_rooms_placeholder": "Search rooms",
"no_results": "No results found",
"add_button": "Add",
"subtitle": "add existing rooms"
},
"SpaceOptions": {
"leave_space": "Leave Space",
"leave_space_confirmation": "Are you sure that you want to leave the {{space}} space?",
"leave_space_confirm": "Leave",
"invite": "Invite",
"manage_rooms": "Manage rooms",
"settings": "Settings",
"leave": "Leave"
},
"SSOButtons": {
"login_with": "Login with {{idp_name}}"
}
},
"Templates": {
"Auth": {
"loading_homeserver_list": "Loading homeserver list...",
"looking_for_homeserver": "Looking for homeserver...",
"connecting_to_homeserver": "Connecting to {{homeserver}}...",
"unable_to_connect": "Unable to connect. Please check your input",
"username_taken": "Username is already taken",
"registration_in_progress": "Registration in progress...",
"link_about": "About",
"link_twitter": "Twitter",
"link_matrix": "Powered by Matrix",
"dont_have_an_account": "Dont have an account?",
"already_have_an_account": "Already have an account?",
"register_link": "Register",
"login_link": "Login",
"redirecting": "Redirecting...",
"login_in_progress": "Login in progress...",
"bad_localpart_error": "Username can only contain characters a-z, 0-9, or '=_-./'",
"user_id_too_long_error": "Your user ID, including the hostname, can't be more than 255 characters long.",
"bad_password_error": "Password must contain at least 1 lowercase, 1 uppercase, 1 number, 1 non-alphanumeric character, 8-127 characters with no space.",
"confirm_password_error": "Passwords don't match.",
"bad_email_error": "Invalid email address",
"invalid_password": "Invalid password",
"check_credentials": "Please check your credentials",
"login_types": {
"username": "Username",
"email": "Email"
},
"homeserver_form": {
"title": "Homeserver",
"header": "Homeserver list"
},
"login_form": {
"title": "Login",
"prompt_email": "Email",
"prompt_username": "Username",
"prompt_password": "Password",
"login_button": "Login"
},
"register_form": {
"title": "Register",
"prompt_email_required": "Email (Required)",
"prompt_email_optional": "Email",
"prompt_username": "Username",
"prompt_password": "Password",
"prompt_confirm_password": "Confirm password",
"register_button": "Register"
},
"terms_and_conditions": {
"title": "Agree with terms",
"description": "In order to complete registration, you need to agree to the terms and conditions.",
"accept": "I accept <a>Terms and Conditions</a>",
"submit_button": "Submit"
},
"validate_email": {
"title": "Verify email",
"continue_button": "Continue",
"message": "Please check your email <bold>{{email_address}}</bold> and validate before continuing further"
},
"captcha": {
"message": "Pleace check the box below to proceed"
}
},
"Client": {
"loading_messages": {
"default": "Heating up",
"message_one": "Almost there...",
"message_two": "Looks like you have a lot of stuff to heat up!"
},
"logout_prompt": "Logout"
}
}
}
}
-889
View File
@@ -1,889 +0,0 @@
{
"translation": {
"common": {
"close": "Close",
"open": "Open",
"leave": "Leave",
"options": "Options",
"versi": Versi,
"slogan": "A Libre matrix client by TrueOG Network",
"source_code": "Source code",
"sponsor": "Support",
"retry": "Retry",
"delete": "Delete",
"continue": "Continue",
"cancel": "Cancel",
"save": "Save",
"view_more": "View more",
"view_less": "View less",
"copy": "Copy",
"upload": "Upload",
"download": "Download",
"or": "Or",
"reset": "Reset",
"setup": "Setup",
"search": "Search",
"loading": "Loading...",
"joining": "Joining...",
"join": "Join",
"remove": "Remove",
"send": "Send",
"homeserver": "Homeserver",
"invite": "Invite",
"uninvite": "Uninvite",
"invited": "Invited",
"inviting": "Inviting...",
"uninviting": "Uninviting...",
"change": "Change",
"edit": "Edit",
"message_prompt": "Message"
},
"errors": {
"browser_not_supported": "Not supported in this browser",
"generic": "Something went wrong!"
},
"Organisms": {
"Welcome": {
"heading": "Welcome to Versi!",
"subheading": A Libre matrix client by TrueOG Network
},
"ViewSource": {
"title": "View Source",
"original_source": "Original source",
"decrypted_source": "Decrypted source"
},
"SpaceManage": {
"subtitle": "manage rooms",
"load_more": "Load more",
"rooms_and_spaces": "Rooms and spaces",
"private_rooms_message": "Either the space contains private rooms or you need to join space to view it's rooms.",
"items_selected_zero": "No selected items",
"items_selected_one": "{{count}} selected item",
"items_selected_other": "{{count}} selected items",
"room_members_zero": "No room members",
"room_members_one": "{{count}} room member",
"room_members_other": "{{count}} room members",
"mark_suggested_zero": "Marking no rooms as suggested",
"mark_suggested_one": "Marking {{count}} room as suggested",
"mark_suggested_other": "Marking {{count}} rooms as suggested",
"mark_not_suggested_zero": "Marking no rooms as suggested",
"mark_not_suggested_one": "Marking {{count}} room as suggested",
"mark_not_suggested_other": "Marking {{count}} rooms as suggested",
"remove_zero": "Removing no items",
"remove_one": "Removing {{count}} item",
"remove_other": "Removing {{count}} items.",
"suggested": "Suggested",
"mark_as_suggested": "Mark as suggested",
"mark_as_not_suggested": "Mark as not suggested"
},
"SpaceSettings": {
"subtitle": "space settings",
"leave": {
"leave_space": "Leave Space",
"leave_dialog_title": "Leave Space",
"leave_dialog_message": "Are you sure you want to leave {{space}}?"
},
"visibility": {
"header": "Space visibility (who can join)"
},
"addresses": {
"header": "Space addresses"
},
"categorize_subspaces": "Categorize subspaces",
"uncategorize_subspaces": "Uncategorize subspaces",
"pin_sidebar": "Pin to sidebar",
"unpin_sidebar": "Unpin from sidebar"
},
"Settings": {
"title": "设置",
"theme": {
"follow_system": {
"title": "Follow system theme",
"description": "Use light or dark mode based on the system settings."
},
"title": "主题",
"theme_light": "Light",
"theme_silver": "Silver",
"theme_dark": "Dark",
"theme_butter": "Butter"
},
"markdown": {
"title": "Markdown formatting",
"description": "Format messages with markdown before sending"
},
"hide_membership_events": {
"title": "Hide membership events",
"description": "Hide membership change messages from room timeline. (Join, Leave, Invite, Kick and Ban)"
},
"hide_nickname_avatar_events": {
"title": "Hide nick/avatar events",
"description": "Hide nickname and avatar change messages from the room timeline."
},
"notifications_and_sound": {
"title": "Notifications & Sound",
"desktop": {
"title": "Desktop notifications",
"description": "Show desktop notifications when new messages arrive."
},
"sound": {
"title": "Notification sound",
"description": "Play a sound when new messages arrive."
}
},
"security": {
"cross_signing": {
"title": "Cross signing and backup"
},
"export_import_encryption_keys": {
"title": "Export / Import encryption keys"
},
"export_encryption_keys": {
"title": "Export E2E room keys",
"description": "Export end-to-end encryption room keys to decrypt old messages in other session. In order to encrypt keys you need to set a password, which will be used while importing."
},
"import_encryption_keys": {
"title": "Import E2E room keys",
"description": "To decrypt older messages, Export E2EE room keys from Element (Settings > Security & Privacy > Encryption > Cryptography) and import them here. Imported keys are encrypted so you\\'ll have to enter the password you set in order to decrypt it."
}
},
"logout": {
"title": "Logout",
"dialog": {
"title": "Logout",
"description": "Are you sure that you want to logout your session?",
"confirm": "Logout"
}
},
"about": {
"application": "Application",
"credits": "Credits"
}
},
"ShortcutSpaces": {
"header": "Pin Spaces",
"pinned_spaces": "Pinned spaces",
"no_pinned_spaces": "No pinned spaces",
"unpinned_spaces": "Unpinned spaces",
"no_unpinned_spaces": "No unpinned spaces",
"spaces_selected_zero": "No selected spaces",
"spaces_selected_one": "{{count}} selected space",
"spaces_selected_other": "{{count}} selected spaces",
"pin_button": "Pin"
},
"SecretStorageAccess": {
"incorrect_security_key": "Incorrect security key",
"incorrect_security_phrase": "Incorrect security phrase",
"security_phrase": "Security Phrase",
"security_key": "Security Key",
"use_security_key": "Use Security Key",
"use_security_phrase": "Use Security Phrase"
},
"KeyBackup": {
"create_backup_title": "Create key backup",
"create_backup_tooltip": "Create backup",
"creating_backup": "Creating Backup...",
"backup_created": "Successfully created backup",
"backup_failed": "Failed to create backup",
"restoring": "Restoring backup keys...",
"restoring_progress": "Restoring backup keys... ({{progress}}/{{total}}",
"restore_backup_title": "Restore Key Backup",
"restore_backup_tooltip": "Restore Key Backup",
"restore_complete": "Successfully restored backup keys ({{progress}}/{{total}})",
"restore_failed_bad_key": "Failed to restore backup. Key is invalid!",
"restore_failed_unknown": "Failed to restore backup.",
"delete_key_backup_title": "Delete key backup",
"delete_key_backup_tooltip": "Delete backup",
"delete_key_backup_subtitle": "Deleting key backup is permanent.",
"delete_key_backup_message": "All encrypted message keys stored on the server will be permanently deleted.",
"encrypted_messages_backup_description": "Online backup your encrypted messages keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.",
"encrypted_messages_backup_title": "Encrypted messages backup",
"encrypted_messages_backup_cross_signing_disabled": "Setup cross signing to backup your encrypted messages."
},
"DeviceManage": {
"edit_session_name_title": "Edit session name",
"edit_session_name_subtitle": "Session name",
"edit_session_name_tooltip": "Edit session name",
"current_device_label": "Current",
"verify_session_button": "Verify",
"unverified_sessions_title": "Unverified sessions",
"unverified_sessions_none": "No unverified sessions",
"unencrypted_sessions_title": "Sessions without encryption support",
"verified_sessions_title": "Verified sessions",
"verified_sessions_none": "No verified sessions",
"setup_cross_signing_message": "Setup cross signing in case you lose all your sessions",
"loading_devices": "Loading devices...",
"logout_device_title": "Logout {{device}}",
"logout_device_message": "You are about to log out the session for {{device}}",
"logout_device_confirm": "Logout",
"logout_device_tooltip": "Remove session",
"session_verification_title": "Session Verification",
"session_name_privacy_message": "Session names are visible to everyone, so do not put any private info here."
},
"CrossSigning": {
"title": "Cross Signing",
"setup_failed": "Failed to setup cross signing. Please try again",
"setup": "Setup cross signing",
"save_security_key_message": "Please save this security key somewhere safe",
"security_key_dialog_title": "Security Key",
"security_key_generation_message": "We will generate a Security Key, which you can use to manage message backups and session verification.",
"security_key_generation_button": "Generate Key",
"security_phrase_message": "Alternatively you can set a 'Security Phrase' so you don't have to remember the long Security Key, and optionally save the key as a backup",
"security_phrase_label": "Security Phrase",
"security_phrase_confirm_label": "Confirm Security Phrase",
"security_phrase_set_button": "Set Phrase & Generate Key",
"setup_dialog_title": "Setup cross signing",
"setup_message": "Setup to verify and keep track of all your sessions. Also required to backup encrypted message.",
"reset_keys_subtitle": "Resetting cross-signing keys is permanent.",
"reset_keys_message": "Anyone you have verified with will see security alerts and your message backup will lost. You almost certainly do not want to do this, unless you have lost Security Key or Phrase and every session you can cross-sign from."
},
"AuthRequest": {
"wrong_password": "Wrong password. Please enter the correct password",
"request_failed": "Request failed!",
"password_label": "Account password"
},
"Search": {
"description": "Type # for rooms, @ for DMs and * for spaces. Hotkey: Ctrl + k"
},
"RoomViewInput": {
"upload_progress": "Uploading: {{progress}}/{{total}} ({{percent}}%)",
"tombstone_replaced": "This room has been replaced, and is no longer active.",
"tombstone_permission_denied": "You do not have permission to post to this room",
"send_message_placeholder": "Send a message...",
"emoji_tooltip": "Emoji",
"file_size": "Size: {{size}}",
"cancel_reply_tooltip": "Cancel reply"
},
"RoomViewHeader": {
"search_tooltip": "Search",
"people_tooltip": "People",
"members_tooltip": "Members"
},
"RoomViewFloating": {
"jump_unread": "Jump to unread messages",
"mark_read": "Mark as read",
"jump_latest": "Jump to latest",
"user_typing_1": "<bold><user_one/></bold> is typing...",
"user_typing_2": "<bold><user_one/></bold> and <bold><user_two/></bold> are typing...",
"user_typing_3": "<bold><user_one/></bold>, <bold><user_two/></bold> and <bold><user_three/></bold> are typing...",
"user_typing_4": "<bold><user_one/></bold>, <bold><user_two/></bold>, <bold><user_three/></bold> and <bold><user_four/></bold> are typing...",
"user_typing_other": "<bold>Several people</bold> are typing..."
},
"RoomViewContent": {
"welcome_to_room": "Welcome to {{room_name}}!",
"beginning_room": "This is the beginning of the <bold>{{room_name}}</bold> room.",
"beginning_dm": "This is the beginning of your direct message history with <bold>@{{user_name}}</bold>.",
"created_on": "Created on {{date, datetime}}",
"new_messages": "New messages"
},
"RoomSettings": {
"leave_room": "Leave room",
"leave_room_confirm_message": "Are you sure you want to leave {{room_name}}?",
"leave_room_confirm_button": "Leave",
"notification_header": "Notifications (Changing this will only affect you)",
"visibility_header": "Room visibility (Who can join)",
"address_header": "Room addresses",
"encryption_header": "Encryption",
"message_history_header": "Message history visibility",
"room_settings_subtitle": "room settings"
},
"RoomCommon": {
"user_joined": "<bold><user/></bold> joined the room",
"user_left": "<bold><user/></bold> left the room",
"user_left_reason": "<bold><user/></bold> left the room: <reason/>",
"user_invited": "<bold><inviter/></bold> invited <bold><user/></bold>",
"invite_cancelled": "<bold><inviter/></bold> cancelled <bold><user/>'s</bold> invite",
"invite_rejected": "<bold><user/></bold> rejected the invitation",
"user_kicked": "<bold><actor/></bold> kicked <bold><user/></bold>",
"user_kicked_reason": "<bold><actor/></bold> kicked <bold><user/></bold>: <reason/>",
"user_banned": "<bold><actor/></bold> banned <bold><user/></bold>",
"user_banned_reason": "<bold><actor/></bold> banned <bold><user/></bold>: <reason/>",
"user_unbanned": "<bold><actor/> unbanned <bold><user/></bold>",
"avatar_set": "<bold><user/></bold> set an avatar",
"avatar_changed": "<bold><user/></bold> changed their avatar",
"avatar_removed": "<bold><user/></bold> removed their avatar",
"name_set": "<bold><user/></bold> set their display name to <bold><new_name/></bold>",
"name_changed": "<bold><user/></bold> changed their display name to <bold><new_name/></bold>",
"name_removed": "<bold><user/></bold> removed their display name <bold><last_name/></bold>"
},
"PublicRooms": {
"could_not_join_alias": "Unable to join {{alias}}. Either the room is private or doesn't exist",
"try_joining_alias": "Try joining {{alias}}",
"joining_alias": "Joining {{alias}}...",
"no_public_rooms": "No public rooms on {{homeserver}}",
"no_result_found": "No result found for '{{input}}' on {{homeserver}}",
"title": "Public Rooms",
"search_room_name_alias": "Room name or alias",
"search_button": "Search",
"loading": "Loading public rooms from {{homeserver}}...",
"searching": "Searching for '{{query}}' on {{homeserver}}...",
"result_title": "Public rooms on {{homeserver}}",
"search_result_title": "Search result for '{{query}}' on {{homeserver}}"
},
"ProfileViewer": {
"kick_button": "Kick",
"kick_reason_label": "Kick Reason",
"ban_button": "Ban",
"ban_reason_label": "Ban reason",
"loading_sessions": "Loading sessions...",
"no_sessions_found": "No sessions found.",
"view_sessions_one": "View session",
"view_sessions_other": "View {{count}} sessions",
"send_direct_message_button": "Message",
"creating_dm_room": "Creating room...",
"ignore": "Ignore",
"ignoring": "Ignoring...",
"unignore": "Unignore",
"unignoring": "Unignoring...",
"change_power_level": "Change power level",
"shared_power_message": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself. Are you sure?",
"demoting_self_message": "You will not be able to undo this change as you are demoting yourself. Are you sure?"
},
"PeopleDrawer": {
"title": "People",
"members_one": "{{count}} member",
"members_other": "{{count}} members",
"joined": "Joined",
"invited": "Invited",
"banned": "Banned",
"search_no_results": "No results found",
"view_more": "View more",
"placeholder": "Search",
"invite_tooltip": "Invite"
},
"ProfileEditor": {
"remove_avatar": "Remove avatar",
"remove_avatar_confirmation": "Are you sure that you want to remove your avatar?",
"display_name_message": "Display name of {{user_name}}"
},
"DrawerBreadcrumb": {
"home": "Home"
},
"DrawerHeader": {
"add_rooms_or_spaces": "Add rooms or spaces",
"create_new_space": "Create new space",
"create_new_room": "Create new room",
"join_public_room": "Join public room",
"join_with_address": "Join with address",
"add_existing": "Add existing",
"manage_rooms": "Manage rooms",
"home": "Home",
"direct_messages": "Direct messages",
"start_dm_tooltip": "Start DM",
"add_rooms_spaces_tooltip": "Add rooms/spaces"
},
"SideBar": {
"settings_tooltip": "Settings",
"unverified_sessions_one": "{{count}} unverified session",
"unverified_sessions_other": "{{count}} unverified sessions",
"home_tooltip": "Home",
"direct_messages_tooltip": "People",
"pin_spaces_tooltip": "Pin spaces",
"search_tooltip": "Search",
"invites_tooltip": "Invites"
},
"JoinAlias": {
"invalid_address": "Invalid address.",
"looking_for_address": "Looking for address...",
"joining_alias": "Joining {{alias_name}}...",
"couldnt_find_room_or_space_alias": "Unable to find room/space with {{alias_name}}. Either the room/space is private or doesn't exist.",
"couldnt_find_room_or_space": "Unable to join {{alias_name}}. Either the room/space is private or doesn't exist.",
"address_label": "Address",
"title": "Join with address"
},
"InviteUser": {
"user_not_found": "{{user_name}} not found!",
"no_matches_found": "No matches found for {{user_name}}",
"invite_result": {
"invited": "Invited",
"already_joined": "Already joined",
"already_invited": "Already invited",
"banned": "Banned"
},
"search_label": "Name or User ID",
"search_result_title": "Search result for user {{user_name}}",
"searching_for_user": "Searching for user {{user_name}}...",
"invite_to_room": "Invite to {{room}}",
"invite_to_dm": "Direct Message"
},
"InviteList": {
"accept_invite": "Accept",
"reject_invite": "Reject",
"direct_messages_title": "Direct Messages",
"rooms_title": "Rooms",
"spaces_title": "Spaces",
"title": "Invites"
},
"EmojiVerification": {
"waiting_for_response": "Waiting for response from other device...",
"confirmation_prompt": "Confirm the emoji below are displayed on both devices, in the same order:",
"emojis_match_button": "They match",
"emojis_dont_match_button": "They don't match",
"accept_request_from_other_device_message": "Please accept the request from other device.",
"begin_verification_process_message": "Click accept to start the verification process.",
"begin_verification_button_text": "Accept",
"title": "Emoji Verification"
},
"DragDrop": {
"drop_file_to_upload_prompt": "Drop file to upload"
},
"CreateRoom": {
"private_room_short": "Private",
"restricted_room_short": "Restricted",
"public_room_short": "Public",
"private_room_long": "Private (invite only)",
"restricted_room_long": "Restricted (space member can join)",
"public_room_long": "Public (anyone can join)",
"visibility_title": "Visibility",
"visibility_message": "Visibility (who can join)",
"select_who_can_join_space": "Select who can join this space",
"select_who_can_join_room": "Select who can join this room",
"space_address": "Space address",
"room_address": "Room address",
"room_address_already_in_use": "{{room_address}} is already in use",
"e2e_title": "Enable end-to-end encryption",
"e2e_message": "You can’t disable this later. Bridges & most bots won’t work yet.",
"role_title": "Select your role",
"role_message": "Selecting 'Admin' sets your power level to 100 whereas 'Founder' sets it to 101.",
"creating_room": "Creating room...",
"creating_space": "Creating space...",
"topic_label": "Topic (optional)",
"space_name": "Space name",
"room_name": "Room name",
"role_admin": "Admin",
"role_founder": "Founder",
"create_room": "Create room",
"create_space": "Create space",
"home": "Home"
}
},
"Molecules": {
"ConfirmDialog": {
"cancel": "Cancel"
},
"ReusableDialog": {
"close_tooltip": "Close"
},
"FollowingMembers": {
"users_following_1": "<bold><user_one/></bold> is following the conversation",
"users_following_2": "<bold><user_one/></bold> and <bold><user_two/></bold> are following the conversation",
"users_following_3": "<bold><user_one/></bold>, <bold><user_two/></bold>, and <bold><user_three/></bold> are following the conversation",
"users_following_other": "<bold><user_one/></bold>, <bold><user_two/></bold>, <bold><user_three/></bold> and {{other_count}} others are following the conversation"
},
"ImageUpload": {
"prompt": "Upload",
"cancel": "Cancel",
"remove": "Remove"
},
"ExportE2ERoomKeys": {
"getting_keys": "Getting keys...",
"encrypting_keys": "Encrypting Keys...",
"password_does_not_match": "Password does not match.",
"export_success": "Successfully exported all keys.",
"export_failed": "Failed to export keys. Please try again.",
"button_text": "Export"
},
"ImportE2ERoomKeys": {
"decrypting_file": "Decrypting file...",
"decrypting_messages": "Decrypting messages...",
"import_success": "Successfully imported all keys",
"import_failed": "Failed tp decrypt keys. Please try again",
"import_keys_button": "Import Keys",
"decrypt_button": "Decrypt"
},
"Media": {
"open_new_tab": "Open in new tab",
"download": "Download",
"play_audio": "Play audio",
"play_video": "Play video"
},
"Message": {
"message_deleted": "*** This message has been deleted ***",
"unable_to_load_reply": "*** Unable to load reply ***",
"unknown_user": "** Unknown user **",
"edited": "(edited)",
"edit_placeholder": "Edit message",
"user_reacted_1": "<bold><user_one/></bold> reacted with <emoji/>",
"user_reacted_2": "<bold><user_one/></bold> and <bold><user_two/></bold> reacted with <emoji/>",
"user_reacted_3": "<bold><user_one/></bold>, <bold><user_two/></bold> and <bold><user_three/></bold> reacted with <emoji/>",
"user_reacted_other": "<bold><user_one/></bold>, <bold><user_two/></bold>, <bold><user_three/></bold> and {{other_count}} others reacted with <emoji/>",
"add_reaction_tooltip": "Add reaction",
"reply_tooltip": "Reply",
"edit_tooltip": "Edit",
"options_header": "Options",
"options_tooltip": "Options",
"read_receipts": "Read receipts",
"view_source": "View source",
"delete_message_prompt": "Delete message",
"delete_message_confirmation": "Are you sure that you want to delete this message?",
"delete_message_button": "Delete"
},
"PopupWindow": {
"close_tooltip": "Back"
},
"PowerLevelSelector": {
"placeholder": "Power level"
},
"RoomAliases": {
"invalid_characters": "Invalid character: only letter, numbers and _- are allowed.",
"validating_alias": "validating {{alias}}...",
"alias_available": "{{alias}} is available.",
"alias_unavailable": "{{alias}} is unavailable.",
"deleting_alias": "Deleting...",
"set_main_alias": "Set as Main",
"publish_alias": "Publish",
"unpublish_alias": "Unpublish",
"delete_alias": "Delete",
"main_alias": "Main",
"publish_to_room_directory": {
"title": "Publish to room directory",
"publish_room_message": "Publish this room to the {{homeserver}} publish directory?",
"publish_space_message": "Publish this space to the {{homeserver}} publish directory?"
},
"published_addresses": {
"title": "Published addresses",
"none": "No published addresses",
"no_main_address": "No main address (select one from below)",
"message_room": "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.",
"message_space": "Published addresses can be used by anyone on any server to join your space. To publish an address, it needs to be set as a local address first."
},
"local_addresses": {
"title": "Local addresses",
"none": "No local addresses",
"message_room": "Set local addresses for this room so users can find this room through your homeserver.",
"message_space": "Set local addresses for this space so users can find this space through your homeserver.",
"add": "Add local address",
"add_button": "Add",
"placeholder_room": "my_room_address",
"placeholder_space": "my_space_address",
"hide": "Hide local addresses",
"view": "View local addresses"
}
},
"RoomEncryption": {
"encryption_public_room_message": "It is not recommended to add encryption in public room. Anyone can find and join public rooms, so anyone should be able to read messages in them.",
"encryption_message": "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly",
"encryption_cannot_be_disabled": "Once enabled, encryption cannot be disabled.",
"enable_room_encryption": "Enable room encryption",
"enable_encryption_prompt": "Enable Encryption",
"enable_encryption_button": "Enable",
"continue_button": "Continue"
},
"RoomHistoryVisibility": {
"world_readable": "Anyone (including guests)",
"shared": "Members (all messages)",
"invited": "Members (messages after invite)",
"joined": "Members (messages after join)",
"changes_only_affect_future": "Changes to history visibility will only apply to future messages. The visibility of existing history will have no effect."
},
"RoomMembers": {
"search_title": "Search member",
"found_members_one": "Found — one member",
"found_members_other": "Found — {{count}} members",
"no_results": "No results found for {{term}}",
"no_members": "No members to display",
"view_more": "View more",
"joined": "Joined",
"invited": "Invited",
"banned": "Banned",
"search_placeholder": "Search for name"
},
"RoomNotification": {
"default": "Global",
"all_messages": "All messages",
"mentions_and_keywords": "Mentions & Keywords",
"mute": "Mute"
},
"RoomOptions": {
"title": "Options for {{room_name}}",
"leave": {
"title": "Leave room",
"subtitle": "Are you sure you want to leave the {{room_name}} room?",
"button_text": "Leave"
},
"mark_as_read": "Mark as read",
"notifications_heading": "Notifications",
"invite": "Invite"
},
"RoomPermissions": {
"default_role": {
"name": "Default role",
"description": "Set default role for all members"
},
"send_messages": {
"name": "Send messages",
"description": "Set minimum power level to send messages in a room"
},
"reactions": {
"name": "Send reactions",
"description": "Set minimum power level to send reactions in a room"
},
"delete": {
"name": "Delete messages sent by others",
"description": "Set minumum power level to delete messages in a room"
},
"notifications": {
"name": "Ping room",
"description": "Set minimum power level to ping room"
},
"manage_rooms": {
"name": "Manage rooms in space",
"description": "Set minimum power level to manage rooms in space"
},
"invite": {
"name": "Invite",
"description": "Set minimum power level to invite members"
},
"kick": {
"name": "Kick",
"description": "Set minimum power level to kick members"
},
"ban": {
"name": "Ban",
"description": "Set minimum power level to ban members"
},
"change_avatar": {
"name": "Change avatar",
"description": "Set minimum power level to change room/space avatar"
},
"change_name": {
"name": "Change name",
"description": "Set minimum power evel to change room/space name"
},
"change_topic": {
"name": "Change topic",
"description": "Set minimum power level to change room/space topic"
},
"change_settings": {
"name": "Change settings",
"description": "Set minimum power level to change settings"
},
"change_published_address": {
"name": "Change published address",
"description": "Set minimum power level to publish and set main address"
},
"change_permissions": {
"name": "Change permissions",
"description": "Set minimum power level to change permissions"
},
"enable_room_encryption": {
"name": "Enable room encryption",
"description": "Set minimum power level to enable room encryption"
},
"change_history_visibility": {
"name": "Change history visibility",
"description": "Set minimum power level to change room message history visibility"
},
"upgrade_room": {
"name": "Upgrade room",
"description": "Set minimum power level to upgrade room"
},
"pin_messages": {
"name": "Pin messages",
"description": "Set minimum power level to pin messages in a room"
},
"change_acls": {
"name": "Change server ACLs",
"description": "Set minimum power level to change server ACLs"
},
"modify_widgets": {
"name": "Modify widgets",
"description": "Set minimum power level to modify room widgets"
},
"groups": {
"general": "General permissions",
"manage_members": "Manage members permissions",
"room": "Room profile permissions",
"space": "Space profile permissions",
"other": "Other permissions",
"settings": "Settings permissions"
}
},
"RoomProfile": {
"saving_room_name": "Saving room name...",
"saving_room_topic": "Saving room topic...",
"save_success": "Saved successfully",
"save_failed": "Unable to save",
"remove_avatar_title": "Remove avatar",
"remove_avatar_subtitle": "Are you sure that you want to remove room avatar?",
"remove_avatar_button": "Remove",
"permission_change_room_name": "You have permission to change room name only",
"permission_change_room_topic": "You have permission to change room topic only",
"permission_change_space_name": "You have permission to change space name only",
"permission_change_space_topic": "You have permission to change space topic only",
"name_label": "Name",
"topic_label": "Topic"
},
"RoomSearch": {
"title": "Room search",
"placeholder": "Search for keywords",
"search_button": "Search",
"searching": "Searching room messages...",
"subtitle": "Search room messages",
"failed": "Failed to search messages",
"no_results": "No results found",
"encrypted_room": "Search does not work in encrypted room",
"load_more": "Load more",
"results_one": "{{count}} result for {{term}}",
"results_other": "{{count}} results for {{term}}"
},
"RoomTile": {
"invited_by_user_zero": "Invited by {{inviter}} to {{id}}",
"invited_by_user_one": "Invited by {{inviter}} to {{id}} • {{member_count}} member",
"invited_by_user_other": "Invited by {{inviter}} to {{id}} • {{member_count}} members",
"invited_zero": "{{id}}",
"invited_one": "{{id}} • {{member_count}} member",
"invited_other": "{{id}} • {{member_count}} members"
},
"RoomVisibility": {
"private": "Private (invite only)",
"restricted": "Restricted (space members can join)",
"restricted_unsupported": "Restricted (Unsupported: room required upgrade)",
"public": "Public (anyone can join)"
},
"RoomEmojis": {
"header": "Create Pack",
"submit_button": "Create pack",
"name_placeholder": "Pack Name",
"no_packs": "No emoji or sticker packs."
},
"ImagePack": {
"rename_title": "Rename",
"rename_button": "Rename",
"shortcode_label": "Shortcode",
"delete_item_title": "Delete",
"delete_item_message": "Are you sure that you want to delete {{key}}?",
"delete_item_button": "Delete",
"delete_pack_title": "Delete Pack",
"delete_pack_message": "Are you sure that you want to delete {{pack_name}}?",
"delete_pack_button": "Delete",
"image_title": "Image",
"image_shortcode": "Shortcode",
"image_usage": "Usage",
"view_more": "View {{count}} more",
"view_less": "View less",
"use_globally": "Use globally",
"use_globally_description": "Add this pack to your account to use in all rooms.",
"no_global_packs": "No global packs",
"default_personal_pack_name": "Personal"
},
"ImagePackItem": {
"rename_tooltip": "Rename",
"delete_tooltip": "Delete"
},
"ImagePackUsageSelector": {
"header": "Usage",
"type_emoji": "Emoji",
"type_sticker": "Sticker",
"type_both": "Both"
},
"ImagePackUpload": {
"remove_file_tooltip": "Remove file",
"import_image_button": "Import image",
"shortcode_placeholder": "shortcode",
"upload_button": "Upload",
"upload_button_progress": "Uploading..."
},
"ImagePackProfile": {
"attribution_prompt": "Attribution",
"name_prompt": "Name",
"edit_tooltip": "Edit",
"usage_header": "Usage",
"pack_usage": "Pack usage"
},
"SpaceAddExisting": {
"adding_items_one": "Adding one item...",
"adding_items_other": "Adding {{count}} items...",
"items_selected_one": "{{count}} item selected",
"items_selected_other": "{{count}} items selected",
"search_rooms_placeholder": "Search rooms",
"no_results": "No results found",
"add_button": "Add",
"subtitle": "add existing rooms"
},
"SpaceOptions": {
"leave_space": "Leave Space",
"leave_space_confirmation": "Are you sure that you want to leave the {{space}} space?",
"leave_space_confirm": "Leave",
"invite": "Invite",
"manage_rooms": "Manage rooms",
"settings": "Settings",
"leave": "Leave"
},
"SSOButtons": {
"login_with": "Login with {{idp_name}}"
}
},
"Templates": {
"Auth": {
"loading_homeserver_list": "Loading homeserver list...",
"looking_for_homeserver": "Looking for homeserver...",
"connecting_to_homeserver": "Connecting to {{homeserver}}...",
"unable_to_connect": "Unable to connect. Please check your input",
"username_taken": "Username is already taken",
"registration_in_progress": "Registration in progress...",
"link_about": "About",
"link_twitter": "Twitter",
"link_matrix": "Powered by Matrix",
"dont_have_an_account": "Dont have an account?",
"already_have_an_account": "Already have an account?",
"register_link": "Register",
"login_link": "Login",
"redirecting": "Redirecting...",
"login_in_progress": "Login in progress...",
"bad_localpart_error": "Username can only contain characters a-z, 0-9, or '=_-./'",
"user_id_too_long_error": "Your user ID, including the hostname, can't be more than 255 characters long.",
"bad_password_error": "Password must contain at least 1 lowercase, 1 uppercase, 1 number, 1 non-alphanumeric character, 8-127 characters with no space.",
"confirm_password_error": "Passwords don't match.",
"bad_email_error": "Invalid email address",
"invalid_password": "Invalid password",
"check_credentials": "Please check your credentials",
"login_types": {
"username": "Username",
"email": "Email"
},
"homeserver_form": {
"title": "Homeserver",
"header": "Homeserver list"
},
"login_form": {
"title": "Login",
"prompt_email": "Email",
"prompt_username": "Username",
"prompt_password": "Password",
"login_button": "Login"
},
"register_form": {
"title": "Register",
"prompt_email_required": "Email (Required)",
"prompt_email_optional": "Email",
"prompt_username": "Username",
"prompt_password": "Password",
"prompt_confirm_password": "Confirm password",
"register_button": "Register"
},
"terms_and_conditions": {
"title": "Agree with terms",
"description": "In order to complete registration, you need to agree to the terms and conditions.",
"accept": "I accept <a>Terms and Conditions</a>",
"submit_button": "Submit"
},
"validate_email": {
"title": "Verify email",
"continue_button": "Continue",
"message": "Please check your email <bold>{{email_address}}</bold> and validate before continuing further"
},
"captcha": {
"message": "Pleace check the box below to proceed"
}
},
"Client": {
"loading_messages": {
"default": "Heating up",
"message_one": "Almost there...",
"message_two": "Looks like you have a lot of stuff to heat up!"
},
"logout_prompt": "Logout"
}
}
}
}
-59
View File
@@ -1,59 +0,0 @@
{
"name": "Versi",
"short_name": "Versi",
"description": "A Libre matrix client by TrueOG Network",
"dir": "auto",
"lang": "en-US",
"display": "standalone",
"orientation": "portrait",
"start_url": "/",
"background_color": "#fff",
"theme_color": "#fff",
"icons": [
{
"src": "/public/android/android-chrome-36x36.png",
"sizes": "36x36",
"type": "image/png"
},
{
"src": "/public/android/android-chrome-48x48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "/public/android/android-chrome-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/public/android/android-chrome-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/public/android/android-chrome-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/public/android/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/public/android/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "/public/android/android-chrome-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/public/android/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2021-present Ajay Bura (ajbura)
Graphics (c) by Ajay Bura (ajbura)
Graphic content is licensed under a
Creative Commons Attribution 4.0 International License.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

+11 -3
View File
@@ -1,4 +1,12 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 3V5H17.8L12.9 9.9L14.3 11.3L19 6.6V10.2H21V3H14Z" fill="black"/>
<path d="M5 5H10V3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V14.2H19V19H5V5Z" fill="black"/>
<?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 points="14,3 14,5 17.8,5 12.9,9.9 14.3,11.3 19,6.6 19,10.2 21,10.2 21,3 "/>
<path d="M3,10.2h2V5h5V3H5C3.9,3,3,3.9,3,5V10.2z"/>
<path d="M5,14.2H3V19c0,1.1,0.9,2,2,2h5v-2H5V14.2z"/>
<path d="M19,19h-5v2h5c1.1,0,2-0.9,2-2v-4.8h-2V19z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 703 B

-4
View File
@@ -1,4 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.92896 3.51471L3.51474 4.92892L5.97515 7.38933C4.46742 8.5776 3.32116 9.93994 2.7 10.8C2.1 11.5 2.1 12.5 2.7 13.2C4 15 7.6 19 12 19C13.5709 19 15.0398 18.4902 16.3384 17.7526L19.0711 20.4853L20.4853 19.0711L4.92896 3.51471ZM4.2 12C4.68291 11.3561 5.85678 9.9637 7.39721 8.81139L9.29238 10.7066C9.10496 11.0982 9 11.5368 9 12C9 13.6569 10.3431 15 12 15C12.4632 15 12.9018 14.895 13.2934 14.7076L14.8573 16.2715C13.9566 16.7128 12.9896 17 12 17C8.4 17 5.1 13.2 4.2 12Z" fill="black"/>
<path d="M9.6226 5.37995L11.2906 7.04797C11.5254 7.01661 11.762 7 12 7C15.6 7 18.9 10.8 19.8 12C19.493 12.4094 18.9066 13.1213 18.1244 13.8817L19.5194 15.2768C20.2973 14.4974 20.9049 13.7471 21.3 13.2C21.9 12.5 21.9 11.5 21.3 10.8C20 9 16.4 5 12 5C11.1762 5 10.3805 5.14021 9.6226 5.37995Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 943 B

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

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 718 B

-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 800 800" style="enable-background:new 0 0 800 800;" xml:space="preserve">
<style type="text/css">
.st0{stroke:#000000;stroke-width:9;stroke-miterlimit:10;}
</style>
<g>
<path class="st0" d="M405,442.5H244.8v160.2h-67.1V442.5H17.5v-67.1h160.2V215.2h67.1v160.2H405V442.5z"/>
<path class="st0" d="M757,697.8h-75.1V237.2c-30.4,20.2-77.4,30.4-141,30.4v-59.1c91.9,0,144.6-35.4,158.2-106.3H757V697.8z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 688 B

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

Before

Width:  |  Height:  |  Size: 501 B

-4
View File
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 8L10 13M4 14L10 8L12 5M2 5H14M7 2H8M12.913 17H20.087M12.913 17L11 21M12.913 17L15.7783 11.009C16.0092 10.5263 16.1246 10.2849 16.2826 10.2086C16.4199 10.1423 16.5801 10.1423 16.7174 10.2086C16.8754 10.2849 16.9908 10.5263 17.2217 11.009L20.087 17M20.087 17L22 21" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 586 B

+19
View File
@@ -0,0 +1,19 @@
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In -->
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="18px" height="18px" viewBox="0 0 18 18" enable-background="new 0 0 18 18" xml:space="preserve">
<defs>
</defs>
<g>
<g>
<circle fill="#FFFFFF" cx="9" cy="9" r="8.5"/>
</g>
<g>
<path d="M9,0C4,0,0,4,0,9c0,5,4,9,9,9c5,0,9-4,9-9C18,4,14,0,9,0z M1.2,10.8l3.5-2.3c0-0.1,0-0.2,0-0.3c0-1.8,1.3-3.2,3.1-3.4
c0.1,0,0.2,0,0.4,0c1.2,0,2.3,0.6,2.9,1.6c0.3-0.1,0.6-0.1,0.9-0.1c0.4,0,0.8,0,1.2,0.1c0.7,0.2,1.4,0.5,2,0.9
C14.6,7.1,14,7,13.3,7c-1.2,0-2.2,0.4-2.9,1.4c-0.7,0.9-1.1,2-1.1,3.2c0,1.5-0.4,2.9-1.3,4.2c-0.3,0.4-0.5,0.7-0.8,1
C4.2,16.1,1.9,13.8,1.2,10.8z"/>
<circle cx="9.5" cy="6.4" r="0.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 871 B

-66
View File
@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="560"
height="646.66669"
viewBox="0 0 560 646.66669"
sodipodi:docname="versi-logo-online.png"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="1.370103"
inkscape:cx="279.90595"
inkscape:cy="323.33334"
inkscape:window-width="1920"
inkscape:window-height="1090"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g1" />
<g
inkscape:groupmode="layer"
inkscape:label="Image"
id="g1">
<image
width="560"
height="646.66669"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAaQAAAHlCAYAAAC6SIj8AAAA0GVYSWZJSSoACAAAAAoAAAEEAAEA&#10;AACkAQAAAQEEAAEAAADlAQAAAgEDAAMAAACGAAAAEgEDAAEAAAABAAAAGgEFAAEAAACMAAAAGwEF&#10;AAEAAACUAAAAKAEDAAEAAAACAAAAMQECAA0AAACcAAAAMgECABQAAACqAAAAaYcEAAEAAAC+AAAA&#10;AAAAAAgACAAIAEgAAAABAAAASAAAAAEAAABHSU1QIDIuMTAuMzYAADIwMjQ6MDU6MDQgMTQ6MzE6&#10;NDIAAQABoAMAAQAAAAEAAAAAAAAAZfBtEQAAAYRpQ0NQSUNDIHByb2ZpbGUAAHicfZE9SMNAHMVf&#10;05aKVB3sINIhQ3Wyi4o41ioUoUKoFVp1MLn0C5q0JCkujoJrwcGPxaqDi7OuDq6CIPgB4uzgpOgi&#10;Jf4vKbSI8eC4H+/uPe7eAUKryjQzkAA03TIyqaSYy6+KoVcEEcAgogjKzKzPSVIanuPrHj6+3sV5&#10;lve5P8eAWjAZ4BOJE6xuWMQbxDObVp3zPnGElWWV+Jx4wqALEj9yXXH5jXPJYYFnRoxsZp44QiyW&#10;eljpYVY2NOJp4piq6ZQv5FxWOW9x1qoN1rknf2G4oK8sc51mFCksYgkSRChooIIqLMRp1UkxkaH9&#10;pId/1PFL5FLIVQEjxwJq0CA7fvA/+N2tWZyadJPCSSD4YtsfY0BoF2g3bfv72LbbJ4D/GbjSu/5a&#10;C5j9JL3Z1WJHwNA2cHHd1ZQ94HIHGHmqy4bsSH6aQrEIvJ/RN+WB4Vugf83trbOP0wcgS12lb4CD&#10;Q2C8RNnrHu/u6+3t3zOd/n4AKbNyiX17rBAAAA14aVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8&#10;P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4&#10;bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1F&#10;eGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjIt&#10;cmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxu&#10;czp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0&#10;PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4&#10;bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczpkYz0iaHR0cDov&#10;L3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRv&#10;YmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAv&#10;MS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjE3YmUwMzI2LWIyOWYt&#10;NDI5Yi04NTcwLWI1MTMzYjExZDQ0YSIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpjMjRj&#10;NWQ4Yy0zNTcwLTQ5OTctYjE0Zi0zYzAzYjVjZDdjMzkiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1l&#10;bnRJRD0ieG1wLmRpZDozZjcyZDNjNi0wZjMwLTQwYjgtODcwOC1hYzY3MGRlMmUxMjgiCiAgIEdJ&#10;TVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBHSU1QOlRpbWVTdGFtcD0i&#10;MTcxNDgzMzEwMjQ4MDk0NiIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjM2IgogICBkYzpGb3JtYXQ9&#10;ImltYWdlL3BuZyIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJH&#10;SU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQ6MDU6MDRUMTQ6MzE6NDIrMDA6MDAi&#10;CiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI0OjA1OjA0VDE0OjMxOjQyKzAwOjAwIj4KICAgPHhtcE1N&#10;Okhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0i&#10;c2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9Inht&#10;cC5paWQ6Y2E0MDUwNGQtYzljMy00YjM2LWI4NTYtMGJiNDBiNTllYjBiIgogICAgICBzdEV2dDpz&#10;b2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyNC0w&#10;NS0wNFQxNDozMTo0MiswMDowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+&#10;CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgILine truncated
id="image1" />
<ellipse
style="fill:#000000"
id="path1"
cx="442.30249"
cy="465.65845"
rx="112.40031"
ry="104.37171" />
<ellipse
style="fill:#000000"
id="path2"
cx="454.71033"
cy="534.63135"
rx="2.9194884"
ry="28.100077" />
<path
style="fill:#00a300;stroke-width:0.729872;fill-opacity:1"
d="m 434.2739,569.24658 c -54.95257,-3.48219 -100.03982,-45.22417 -103.50508,-95.82547 -2.89219,-42.23312 19.69182,-80.06487 59.53143,-99.72443 32.34786,-15.96262 72.30954,-15.82157 104.78517,0.36986 33.88524,16.89422 56.36753,49.02828 58.76166,83.98838 2.88058,42.06367 -19.79258,79.97509 -59.54233,99.55973 -12.72429,6.26923 -28.88524,10.67911 -41.76453,11.39637 -3.42289,0.19063 -7.20875,0.43088 -8.41304,0.53388 -1.20429,0.10302 -5.63826,-0.0312 -9.85328,-0.29832 z"
id="path3" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 202 KiB

-66
View File
@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="560"
height="646.66669"
viewBox="0 0 560 646.66669"
sodipodi:docname="versi-logo-online.png"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="1.370103"
inkscape:cx="279.90595"
inkscape:cy="323.33334"
inkscape:window-width="1920"
inkscape:window-height="1090"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g1" />
<g
inkscape:groupmode="layer"
inkscape:label="Image"
id="g1">
<image
width="560"
height="646.66669"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAaQAAAHlCAYAAAC6SIj8AAAA0GVYSWZJSSoACAAAAAoAAAEEAAEA&#10;AACkAQAAAQEEAAEAAADlAQAAAgEDAAMAAACGAAAAEgEDAAEAAAABAAAAGgEFAAEAAACMAAAAGwEF&#10;AAEAAACUAAAAKAEDAAEAAAACAAAAMQECAA0AAACcAAAAMgECABQAAACqAAAAaYcEAAEAAAC+AAAA&#10;AAAAAAgACAAIAEgAAAABAAAASAAAAAEAAABHSU1QIDIuMTAuMzYAADIwMjQ6MDU6MDQgMTQ6MzE6&#10;NDIAAQABoAMAAQAAAAEAAAAAAAAAZfBtEQAAAYRpQ0NQSUNDIHByb2ZpbGUAAHicfZE9SMNAHMVf&#10;05aKVB3sINIhQ3Wyi4o41ioUoUKoFVp1MLn0C5q0JCkujoJrwcGPxaqDi7OuDq6CIPgB4uzgpOgi&#10;Jf4vKbSI8eC4H+/uPe7eAUKryjQzkAA03TIyqaSYy6+KoVcEEcAgogjKzKzPSVIanuPrHj6+3sV5&#10;lve5P8eAWjAZ4BOJE6xuWMQbxDObVp3zPnGElWWV+Jx4wqALEj9yXXH5jXPJYYFnRoxsZp44QiyW&#10;eljpYVY2NOJp4piq6ZQv5FxWOW9x1qoN1rknf2G4oK8sc51mFCksYgkSRChooIIqLMRp1UkxkaH9&#10;pId/1PFL5FLIVQEjxwJq0CA7fvA/+N2tWZyadJPCSSD4YtsfY0BoF2g3bfv72LbbJ4D/GbjSu/5a&#10;C5j9JL3Z1WJHwNA2cHHd1ZQ94HIHGHmqy4bsSH6aQrEIvJ/RN+WB4Vugf83trbOP0wcgS12lb4CD&#10;Q2C8RNnrHu/u6+3t3zOd/n4AKbNyiX17rBAAAA14aVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8&#10;P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4&#10;bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1F&#10;eGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjIt&#10;cmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxu&#10;czp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0&#10;PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4&#10;bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczpkYz0iaHR0cDov&#10;L3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRv&#10;YmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAv&#10;MS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjE3YmUwMzI2LWIyOWYt&#10;NDI5Yi04NTcwLWI1MTMzYjExZDQ0YSIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpjMjRj&#10;NWQ4Yy0zNTcwLTQ5OTctYjE0Zi0zYzAzYjVjZDdjMzkiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1l&#10;bnRJRD0ieG1wLmRpZDozZjcyZDNjNi0wZjMwLTQwYjgtODcwOC1hYzY3MGRlMmUxMjgiCiAgIEdJ&#10;TVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBHSU1QOlRpbWVTdGFtcD0i&#10;MTcxNDgzMzEwMjQ4MDk0NiIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjM2IgogICBkYzpGb3JtYXQ9&#10;ImltYWdlL3BuZyIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJH&#10;SU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQ6MDU6MDRUMTQ6MzE6NDIrMDA6MDAi&#10;CiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI0OjA1OjA0VDE0OjMxOjQyKzAwOjAwIj4KICAgPHhtcE1N&#10;Okhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0i&#10;c2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9Inht&#10;cC5paWQ6Y2E0MDUwNGQtYzljMy00YjM2LWI4NTYtMGJiNDBiNTllYjBiIgogICAgICBzdEV2dDpz&#10;b2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyNC0w&#10;NS0wNFQxNDozMTo0MiswMDowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+&#10;CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgILine truncated
id="image1" />
<ellipse
style="fill:#000000"
id="path1"
cx="442.30249"
cy="465.65845"
rx="112.40031"
ry="104.37171" />
<ellipse
style="fill:#000000"
id="path2"
cx="454.71033"
cy="534.63135"
rx="2.9194884"
ry="28.100077" />
<path
style="fill:#00a300;stroke-width:0.729872;fill-opacity:1"
d="m 434.2739,569.24658 c -54.95257,-3.48219 -100.03982,-45.22417 -103.50508,-95.82547 -2.89219,-42.23312 19.69182,-80.06487 59.53143,-99.72443 32.34786,-15.96262 72.30954,-15.82157 104.78517,0.36986 33.88524,16.89422 56.36753,49.02828 58.76166,83.98838 2.88058,42.06367 -19.79258,79.97509 -59.54233,99.55973 -12.72429,6.26923 -28.88524,10.67911 -41.76453,11.39637 -3.42289,0.19063 -7.20875,0.43088 -8.41304,0.53388 -1.20429,0.10302 -5.63826,-0.0312 -9.85328,-0.29832 z"
id="path3" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 202 KiB

-52
View File
@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="560"
height="646.66669"
viewBox="0 0 560 646.66669"
sodipodi:docname="versi-logo.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="1.370103"
inkscape:cx="279.90595"
inkscape:cy="323.33334"
inkscape:window-width="1920"
inkscape:window-height="1090"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g1" />
<g
inkscape:groupmode="layer"
inkscape:label="Image"
id="g1">
<image
width="560"
height="646.66669"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAaQAAAHlCAYAAAC6SIj8AAAA0GVYSWZJSSoACAAAAAoAAAEEAAEA&#10;AACkAQAAAQEEAAEAAADlAQAAAgEDAAMAAACGAAAAEgEDAAEAAAABAAAAGgEFAAEAAACMAAAAGwEF&#10;AAEAAACUAAAAKAEDAAEAAAACAAAAMQECAA0AAACcAAAAMgECABQAAACqAAAAaYcEAAEAAAC+AAAA&#10;AAAAAAgACAAIAEgAAAABAAAASAAAAAEAAABHSU1QIDIuMTAuMzYAADIwMjQ6MDU6MDQgMTQ6MzE6&#10;NDIAAQABoAMAAQAAAAEAAAAAAAAAZfBtEQAAAYRpQ0NQSUNDIHByb2ZpbGUAAHicfZE9SMNAHMVf&#10;05aKVB3sINIhQ3Wyi4o41ioUoUKoFVp1MLn0C5q0JCkujoJrwcGPxaqDi7OuDq6CIPgB4uzgpOgi&#10;Jf4vKbSI8eC4H+/uPe7eAUKryjQzkAA03TIyqaSYy6+KoVcEEcAgogjKzKzPSVIanuPrHj6+3sV5&#10;lve5P8eAWjAZ4BOJE6xuWMQbxDObVp3zPnGElWWV+Jx4wqALEj9yXXH5jXPJYYFnRoxsZp44QiyW&#10;eljpYVY2NOJp4piq6ZQv5FxWOW9x1qoN1rknf2G4oK8sc51mFCksYgkSRChooIIqLMRp1UkxkaH9&#10;pId/1PFL5FLIVQEjxwJq0CA7fvA/+N2tWZyadJPCSSD4YtsfY0BoF2g3bfv72LbbJ4D/GbjSu/5a&#10;C5j9JL3Z1WJHwNA2cHHd1ZQ94HIHGHmqy4bsSH6aQrEIvJ/RN+WB4Vugf83trbOP0wcgS12lb4CD&#10;Q2C8RNnrHu/u6+3t3zOd/n4AKbNyiX17rBAAAA14aVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8&#10;P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4&#10;bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1F&#10;eGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjIt&#10;cmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxu&#10;czp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0&#10;PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4&#10;bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczpkYz0iaHR0cDov&#10;L3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRv&#10;YmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAv&#10;MS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjE3YmUwMzI2LWIyOWYt&#10;NDI5Yi04NTcwLWI1MTMzYjExZDQ0YSIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpjMjRj&#10;NWQ4Yy0zNTcwLTQ5OTctYjE0Zi0zYzAzYjVjZDdjMzkiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1l&#10;bnRJRD0ieG1wLmRpZDozZjcyZDNjNi0wZjMwLTQwYjgtODcwOC1hYzY3MGRlMmUxMjgiCiAgIEdJ&#10;TVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IkxpbnV4IgogICBHSU1QOlRpbWVTdGFtcD0i&#10;MTcxNDgzMzEwMjQ4MDk0NiIKICAgR0lNUDpWZXJzaW9uPSIyLjEwLjM2IgogICBkYzpGb3JtYXQ9&#10;ImltYWdlL3BuZyIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJH&#10;SU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQ6MDU6MDRUMTQ6MzE6NDIrMDA6MDAi&#10;CiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI0OjA1OjA0VDE0OjMxOjQyKzAwOjAwIj4KICAgPHhtcE1N&#10;Okhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0i&#10;c2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9Inht&#10;cC5paWQ6Y2E0MDUwNGQtYzljMy00YjM2LWI4NTYtMGJiNDBiNTllYjBiIgogICAgICBzdEV2dDpz&#10;b2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyNC0w&#10;NS0wNFQxNDozMTo0MiswMDowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+&#10;CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg&#10;ICAgICAgICAgICAgILine truncated
id="image1" />
<path
style="stroke:#7d394a;fill:none"
d="M 150.66667,49.333335 C 149.84511,33.530641 134.10995,28.10905 125.33333,26.489706 c -6.8526,-1.264323 -8.73425,-4.663249 -12,-7.823039 8.07987,-8.39091 25.21668,-5.155517 38.66667,2.299398 9.23164,5.116822 10.99872,7.847819 18.66667,12.49585 5.7139,3.463542 25.20355,6.144247 33.33333,10.318929 4.71983,2.423666 9.01042,7.375815 13.33333,10.082358 4.87104,3.049723 7.34558,2.074056 9.33334,2.135783 4.43394,0.137695 9.52384,-1.782878 21.33333,7.692383 9.77309,7.84139 18.93573,13.945394 28,21.10905 19.50376,15.414142 43.61882,30.288292 61.33333,44.966022 9.00533,7.46155 20.7944,21.10299 30.71916,29.61422 5.36153,4.59794 10.0518,7.30705 14.61418,11.39095 8.8752,7.94438 15.89241,15.73889 23.11112,26.56173 7.68966,11.52893 14.94629,23.46757 21.2181,36 3.61397,7.22148 7.61808,12.51115 11.23971,20 8.44006,17.45239 12.4628,34.24911 15.42696,52 3.76977,22.57536 4.30265,44.97136 2.45166,68.00001 -1.30738,16.26538 -4.13957,32.64013 -12.11422,46.66666 M 61.333333,25.333334 c 28.123149,0 35.975108,-5.967936 51.999997,-6.666667 M 116,21.333334 c -4.29793,5.31014 -4.75938,6.922282 -7.65432,12 -2.20742,3.871827 -5.08146,9.133179 -4.6214,14.666668 0.80439,9.674927 6.39278,16.617228 16.27572,21.333334 M 37.333333,42.666668 32,46.666668 c -3.898649,-8.299601 -0.479693,-13.567627 8,-12 l -4,5.333333 5.333333,-5.333333 m 79.999997,14.666667 c 2.84037,-5.710978 3.786,-16.055461 13.02984,-11.91976 3.31931,1.485067 5.80316,10.544475 4.82099,14.585409 -1.25339,5.15682 -5.01705,8.486125 -8.46605,8.486614 -5.81374,8.13e-4 -6.78468,-5.569092 -8.05144,-9.81893 3.11394,0.872071 4.83898,1.065267 12,8.000001 M 41.333333,37.333335 c 4.034277,7.723836 1.504049,12.595784 -8,9.333333 m 5.333334,-2.666667 v 4.000001 m 56,-1.333334 c -0.66506,3.749471 -1.273946,13.887615 2.13271,18.382732 3.202203,4.225342 7.873843,5.902913 11.200623,7.255127 5.00155,2.032959 11.30037,4.646688 18.66667,5.028809 M 30.666667,57.333335 c 4.623647,4.219971 17.565404,11.818563 24,14.886841 7.154856,3.411744 8.093551,3.157553 10.666666,-0.220174 l -6.666666,1.333334 M 146.66667,60.000002 C 140.09175,67.879559 134.20327,70.581098 124,70.666669 m -60,-2.666667 -1.333333,1.333334 m 90.666663,0 c 3.48328,-0.264893 6.92936,-1.798422 12,0.233561 6.6837,2.678385 15.80742,11.552328 21.30042,16.299357 2.76416,2.388754 4.66431,4.185873 6.69958,6.133749 M 65.333333,70.666669 68,69.333336 m -6.666667,4 H 64 m 22.666667,10.666667 c 5.401896,3.341919 12.842214,6.696777 20.000003,9.94751 2.4771,1.125 6.85221,2.685832 10.66666,3.158447 6.27525,0.777547 10.7251,1.253581 16,2.93213 13.36854,4.25411 33.5697,24.09493 53.33334,26.55656 6.09137,0.75871 9.41758,1.39355 13.33333,1.19238 10.84216,-0.55705 21.98891,-2.76872 29.33333,-4.45369 2.79802,-0.64193 6.2284,-3.72445 13.33334,-2.16976 4.82261,1.05526 17.14194,10.28072 20.56583,13.74898 7.93083,8.03357 18.95887,20.30876 24.22223,27.54012 1.32685,1.82296 2.96605,3.89518 3.21194,4.21399 m -176,-69.333334 c 2.04287,9.772174 0.75965,12.666184 8.85083,22.386844 3.3049,3.97046 12.02571,8.85298 17.81583,11.9681 14.38467,7.73901 27.19511,15.39628 41.33334,22.21089 14.88901,7.17652 21.96423,11.33724 38.66666,22.00928 9.42823,6.02417 12.84349,6.36316 15.99384,7.82919 1.20054,0.55867 4.1756,1.62614 6.88169,5.66874 6.67397,9.97013 3.02718,14.18986 7.63784,29.26029 2.66406,8.70781 4.25197,12.48686 5.97839,17.33334 2.5484,7.15393 5.74128,14.71826 7.48765,22.66666 0.85067,3.87175 4.06055,12.75334 -0.0165,17.86113 -5.43841,6.8134 -17.75467,-1.57621 -21.29628,-2.94238 -13.15729,-5.07532 -25.05119,-5.21367 -45.33333,-7.1451 -4.25586,-0.40528 -11.71513,-1.97522 -15.76852,-0.30245 -4.55735,1.88073 -5.35315,4.59135 -6.27881,5.88785 -2.65203,3.71456 -3.13348,6.47201 -9.29012,14.64096 -5.90728,7.83805 -10.41679,18.40478 -15.3498,26.66666 -3.67265,6.15102 -6.5688,9.10759 -9.31275,12 m 86.66666,-141.33333 c 1.54356,2.19124 2.94916,4.06677 5.41152,6.69958 0.41112,0.43958 1.73594,1.77067 3.91667,3.9609 4.15181,4.16992 7.09169,7.09273 8.00515,8.00618 M 304,205.33334 c 9.52553,0.0799 20.92043,5.97677 34.66667,9.8066 3.34326,0.93148 7.09261,0.8953 10.66666,0.87345 5.02071,-0.0307 11.79204,0.24512 17.33334,4.94031 11.01216,9.33069 14.26688,-0.26558 25.69649,15.29321 3.49756,4.76111 3.01489,9.26612 5.15637,13.03394 3.3079,5.82015 7.30921,6.93205 10.11731,13.39095 2.16036,4.96904 4.3999,9.18966 2.20577,14.52161 -2.10201,5.10811 -9.13651,7.0448 -13.84261,7.73869 -4.25517,0.6274 -8.16992,-0.0317 -12,0.56278 -4.99072,0.77466 -5.81938,2.45805 -12,3.43209 -5.31401,0.83748 -11.12219,0.40637 -14.66667,0.40637 L 352.93518,272.22637 345.41154,261.32821 344,248.00001 c 9.84212,-0.0282 12.04993,-0.45642 15.57613,-4.30453 2.42794,-2.64957 2.90747,-7.34358 3.33024,-10.3601 0.63806,-4.5527 1.60791,-6.75155 2.42696,-9.33537 M 166.66667,210.66667 c -3.85874,14.67058 -1.48799,17.93262 0.56584,33.33334 0.7747,5.80912 0.77436,8.54683 1.57099,10.66565 1.64758,4.38212 5.27715,7.67159 11.1965,9.33435 m 161.33333,-46.66667 c -4.8313,5.71358 -10.16711,15.54057 2.66667,30.66667 m -50.Line truncated
id="path1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 212 KiB

+5 -1
View File
@@ -1,4 +1,8 @@
import { avatarInitials, cssVar } from '../../../util/common';
import { avatarInitials } from '../../../util/common';
function cssVar(name) {
return getComputedStyle(document.body).getPropertyValue(name);
}
// renders the avatar and returns it as an URL
export default async function renderAvatar({
+4 -4
View File
@@ -26,10 +26,10 @@
&--icon {
@include dir.side(padding, var(--sp-tight), var(--sp-loose));
}
.ic-raw {
@include dir.side(margin, 0, var(--sp-extra-tight));
flex-shrink: 0;
.ic-raw {
@include dir.side(margin, 0, var(--sp-extra-tight));
flex-shrink: 0;
}
}
}
-2
View File
@@ -16,7 +16,6 @@ function Input({
{ resizable
? (
<TextareaAutosize
dir="auto"
style={{ minHeight: `${minHeight}px` }}
name={name}
id={id}
@@ -35,7 +34,6 @@ function Input({
/>
) : (
<input
dir="auto"
ref={forwardRef}
id={id}
name={name}
+1 -1
View File
@@ -1,11 +1,11 @@
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import './Math.scss';
import katex from 'katex';
import 'katex/dist/katex.min.css';
import 'katex/dist/contrib/copy-tex';
import 'katex/dist/contrib/copy-tex.css';
const Math = React.memo(({
content, throwOnError, errorColor, displayMode,
-3
View File
@@ -1,3 +0,0 @@
.katex-display {
margin: 0 !important;
}
+2 -1
View File
@@ -27,7 +27,7 @@ function RawModal({
}
useEffect(() => {
navigation.isRawModalVisible = isOpen;
navigation.setIsRawModalVisible(isOpen);
}, [isOpen]);
const modalOverlayClass = (overlayClassName !== null) ? `${overlayClassName} ` : '';
@@ -42,6 +42,7 @@ function RawModal({
shouldCloseOnEsc={closeFromOutside}
shouldCloseOnOverlayClick={closeFromOutside}
shouldReturnFocusAfterClose={false}
closeTimeoutMS={300}
>
{children}
</Modal>
+26 -29
View File
@@ -1,9 +1,33 @@
.ReactModal__Overlay {
opacity: 0;
transition: opacity 200ms var(--fluid-slide-up);
}
.ReactModal__Overlay--after-open{
opacity: 1;
}
.ReactModal__Overlay--before-close{
opacity: 0;
}
.ReactModal__Content {
transform: translateY(100%);
transition: transform 200ms var(--fluid-slide-up);
}
.ReactModal__Content--after-open{
transform: translateY(0);
}
.ReactModal__Content--before-close{
transform: translateY(100%);
}
.raw-modal {
--small-modal-width: 525px;
--medium-modal-width: 712px;
--large-modal-width: 1024px;
position: relative;
width: 100%;
max-height: 100%;
border-radius: var(--bo-radius);
@@ -36,31 +60,4 @@
height: 100%;
background-color: var(--bg-overlay);
}
}
.ReactModal__Overlay {
animation: raw-modal--overlay 150ms;
}
.ReactModal__Content {
animation: raw-modal--content 150ms;
}
@keyframes raw-modal--content {
0% {
transform: translateY(100px);
opacity: .5;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes raw-modal--overlay {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
}
+7 -5
View File
@@ -2,18 +2,20 @@ import React from 'react';
import PropTypes from 'prop-types';
import './RawIcon.scss';
function RawIcon({ color, size, src, isImage }) {
function RawIcon({
color, size, src, isImage,
}) {
const style = {};
if (color !== null) style.backgroundColor = color;
if (isImage) {
style.backgroundColor = 'transparent';
style.backgroundImage = `url("${src}")`;
style.backgroundImage = `url(${src})`;
} else {
style.WebkitMaskImage = `url("${src}")`;
style.maskImage = `url("${src}")`;
style.WebkitMaskImage = `url(${src})`;
style.maskImage = `url(${src})`;
}
return <span className={`ic-raw ic-raw-${size}`} style={style} />;
return <span className={`ic-raw ic-raw-${size}`} style={style}> </span>;
}
RawIcon.defaultProps = {
-44
View File
@@ -1,44 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import dateFormat from 'dateformat';
import { isInSameDay } from '../../../util/common';
function Time({ timestamp, fullTime }) {
const date = new Date(timestamp);
const formattedFullTime = dateFormat(date, 'dd mmmm yyyy, hh:MM TT');
let formattedDate = formattedFullTime;
if (!fullTime) {
const compareDate = new Date();
const isToday = isInSameDay(date, compareDate);
compareDate.setDate(compareDate.getDate() - 1);
const isYesterday = isInSameDay(date, compareDate);
formattedDate = dateFormat(date, isToday || isYesterday ? 'hh:MM TT' : 'dd/mm/yyyy');
if (isYesterday) {
formattedDate = `Yesterday, ${formattedDate}`;
}
}
return (
<time
dateTime={date.toISOString()}
title={formattedFullTime}
>
{formattedDate}
</time>
);
}
Time.defaultProps = {
fullTime: false,
};
Time.propTypes = {
timestamp: PropTypes.number.isRequired,
fullTime: PropTypes.bool,
};
export default Time;
-22
View File
@@ -1,22 +0,0 @@
/* eslint-disable import/prefer-default-export */
import { useState, useEffect } from 'react';
import initMatrix from '../../client/initMatrix';
export function useAccountData(eventType) {
const mx = initMatrix.matrixClient;
const [event, setEvent] = useState(mx.getAccountData(eventType));
useEffect(() => {
const handleChange = (mEvent) => {
if (mEvent.getType() !== eventType) return;
setEvent(mEvent);
};
mx.on('accountData', handleChange);
return () => {
mx.removeListener('accountData', handleChange);
};
}, [eventType]);
return event;
}
-26
View File
@@ -1,26 +0,0 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend'
i18n
.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({
debug: true,
fallbackLng: 'en',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
backend: {
loadPath: '/public/locales/{{lng}}/translation.json',
}
});
export default i18n;
@@ -1,174 +0,0 @@
import React from 'react';
import initMatrix from '../../../client/initMatrix';
import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import SettingTile from '../setting-tile/SettingTile';
import NotificationSelector from './NotificationSelector';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import { useAccountData } from '../../hooks/useAccountData';
export const notifType = {
ON: 'on',
OFF: 'off',
NOISY: 'noisy',
};
export const typeToLabel = {
[notifType.ON]: 'On',
[notifType.OFF]: 'Off',
[notifType.NOISY]: 'Noisy',
};
Object.freeze(notifType);
const DM = '.m.rule.room_one_to_one';
const ENC_DM = '.m.rule.encrypted_room_one_to_one';
const ROOM = '.m.rule.message';
const ENC_ROOM = '.m.rule.encrypted';
export function getActionType(rule) {
const { actions } = rule;
if (actions.find((action) => action?.set_tweak === 'sound')) return notifType.NOISY;
if (actions.find((action) => action?.set_tweak === 'highlight')) return notifType.ON;
if (actions.find((action) => action === 'dont_notify')) return notifType.OFF;
return notifType.OFF;
}
export function getTypeActions(type, highlightValue = false) {
if (type === notifType.OFF) return ['dont_notify'];
const highlight = { set_tweak: 'highlight' };
if (typeof highlightValue === 'boolean') highlight.value = highlightValue;
if (type === notifType.ON) return ['notify', highlight];
const sound = { set_tweak: 'sound', value: 'default' };
return ['notify', sound, highlight];
}
function useGlobalNotif() {
const mx = initMatrix.matrixClient;
const pushRules = useAccountData('m.push_rules')?.getContent();
const underride = pushRules?.global?.underride ?? [];
const rulesToType = {
[DM]: notifType.ON,
[ENC_DM]: notifType.ON,
[ROOM]: notifType.NOISY,
[ENC_ROOM]: notifType.NOISY,
};
const getRuleCondition = (rule) => {
const condition = [];
if (rule === DM || rule === ENC_DM) {
condition.push({ kind: 'room_member_count', is: '2' });
}
condition.push({
kind: 'event_match',
key: 'type',
pattern: [ENC_DM, ENC_ROOM].includes(rule) ? 'm.room.encrypted' : 'm.room.message',
});
return condition;
};
const setRule = (rule, type) => {
const content = pushRules ?? {};
if (!content.global) content.global = {};
if (!content.global.underride) content.global.underride = [];
const ur = content.global.underride;
let ruleContent = ur.find((action) => action?.rule_id === rule);
if (!ruleContent) {
ruleContent = {
conditions: getRuleCondition(type),
actions: [],
rule_id: rule,
default: true,
enabled: true,
};
ur.push(ruleContent);
}
ruleContent.actions = getTypeActions(type);
mx.setAccountData('m.push_rules', content);
};
const dmRule = underride.find((rule) => rule.rule_id === DM);
const encDmRule = underride.find((rule) => rule.rule_id === ENC_DM);
const roomRule = underride.find((rule) => rule.rule_id === ROOM);
const encRoomRule = underride.find((rule) => rule.rule_id === ENC_ROOM);
if (dmRule) rulesToType[DM] = getActionType(dmRule);
if (encDmRule) rulesToType[ENC_DM] = getActionType(encDmRule);
if (roomRule) rulesToType[ROOM] = getActionType(roomRule);
if (encRoomRule) rulesToType[ENC_ROOM] = getActionType(encRoomRule);
return [rulesToType, setRule];
}
function GlobalNotification() {
const [rulesToType, setRule] = useGlobalNotif();
const onSelect = (evt, rule) => {
openReusableContextMenu(
'bottom',
getEventCords(evt, '.btn-surface'),
(requestClose) => (
<NotificationSelector
value={rulesToType[rule]}
onSelect={(value) => {
if (rulesToType[rule] !== value) setRule(rule, value);
requestClose();
}}
/>
),
);
};
return (
<div className="global-notification">
<MenuHeader>Global Notifications</MenuHeader>
<SettingTile
title="Direct messages"
options={(
<Button onClick={(evt) => onSelect(evt, DM)} iconSrc={ChevronBottomIC}>
{ typeToLabel[rulesToType[DM]] }
</Button>
)}
content={<Text variant="b3">Default notification settings for all direct message.</Text>}
/>
<SettingTile
title="Encrypted direct messages"
options={(
<Button onClick={(evt) => onSelect(evt, ENC_DM)} iconSrc={ChevronBottomIC}>
{typeToLabel[rulesToType[ENC_DM]]}
</Button>
)}
content={<Text variant="b3">Default notification settings for all encrypted direct message.</Text>}
/>
<SettingTile
title="Rooms messages"
options={(
<Button onClick={(evt) => onSelect(evt, ROOM)} iconSrc={ChevronBottomIC}>
{typeToLabel[rulesToType[ROOM]]}
</Button>
)}
content={<Text variant="b3">Default notification settings for all room message.</Text>}
/>
<SettingTile
title="Encrypted rooms messages"
options={(
<Button onClick={(evt) => onSelect(evt, ENC_ROOM)} iconSrc={ChevronBottomIC}>
{typeToLabel[rulesToType[ENC_ROOM]]}
</Button>
)}
content={<Text variant="b3">Default notification settings for all encrypted room message.</Text>}
/>
</div>
);
}
export default GlobalNotification;
@@ -1,64 +0,0 @@
import React from 'react';
import './IgnoreUserList.scss';
import initMatrix from '../../../client/initMatrix';
import * as roomActions from '../../../client/action/room';
import Text from '../../atoms/text/Text';
import Chip from '../../atoms/chip/Chip';
import Input from '../../atoms/input/Input';
import Button from '../../atoms/button/Button';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import SettingTile from '../setting-tile/SettingTile';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useAccountData } from '../../hooks/useAccountData';
function IgnoreUserList() {
useAccountData('m.ignored_user_list');
const ignoredUsers = initMatrix.matrixClient.getIgnoredUsers();
const handleSubmit = (evt) => {
evt.preventDefault();
const { ignoreInput } = evt.target.elements;
const value = ignoreInput.value.trim();
const userIds = value.split(' ').filter((v) => v.match(/^@\S+:\S+$/));
if (userIds.length === 0) return;
ignoreInput.value = '';
roomActions.ignore(userIds);
};
return (
<div className="ignore-user-list">
<MenuHeader>Ignored users</MenuHeader>
<SettingTile
title="Ignore user"
content={(
<div className="ignore-user-list__users">
<Text variant="b3">Ignore userId if you do not want to receive their messages or invites.</Text>
<form onSubmit={handleSubmit}>
<Input name="ignoreInput" required />
<Button variant="primary" type="submit">Ignore</Button>
</form>
{ignoredUsers.length > 0 && (
<div>
{ignoredUsers.map((uId) => (
<Chip
iconSrc={CrossIC}
key={uId}
text={uId}
iconColor={CrossIC}
onClick={() => roomActions.unignore([uId])}
/>
))}
</div>
)}
</div>
)}
/>
</div>
);
}
export default IgnoreUserList;
@@ -1,17 +0,0 @@
.ignore-user-list {
&__users {
& form,
& > div:last-child {
display: flex;
flex-wrap: wrap;
gap: var(--sp-tight);
}
& form {
margin: var(--sp-extra-tight) 0 var(--sp-normal);
.input-container {
flex-grow: 1;
}
}
}
}
@@ -1,239 +0,0 @@
import React from 'react';
import './KeywordNotification.scss';
import initMatrix from '../../../client/initMatrix';
import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
import Text from '../../atoms/text/Text';
import Chip from '../../atoms/chip/Chip';
import Input from '../../atoms/input/Input';
import Button from '../../atoms/button/Button';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import SettingTile from '../setting-tile/SettingTile';
import NotificationSelector from './NotificationSelector';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
import { useAccountData } from '../../hooks/useAccountData';
import {
notifType, typeToLabel, getActionType, getTypeActions,
} from './GlobalNotification';
const DISPLAY_NAME = '.m.rule.contains_display_name';
const ROOM_PING = '.m.rule.roomnotif';
const USERNAME = '.m.rule.contains_user_name';
const KEYWORD = 'keyword';
function useKeywordNotif() {
const mx = initMatrix.matrixClient;
const pushRules = useAccountData('m.push_rules')?.getContent();
const override = pushRules?.global?.override ?? [];
const content = pushRules?.global?.content ?? [];
const rulesToType = {
[DISPLAY_NAME]: notifType.NOISY,
[ROOM_PING]: notifType.NOISY,
[USERNAME]: notifType.NOISY,
};
const setRule = (rule, type) => {
const evtContent = pushRules ?? {};
if (!evtContent.global) evtContent.global = {};
if (!evtContent.global.override) evtContent.global.override = [];
if (!evtContent.global.content) evtContent.global.content = [];
const or = evtContent.global.override;
const ct = evtContent.global.content;
if (rule === DISPLAY_NAME || rule === ROOM_PING) {
let orRule = or.find((r) => r?.rule_id === rule);
if (!orRule) {
orRule = {
conditions: [],
actions: [],
rule_id: rule,
default: true,
enabled: true,
};
or.push(orRule);
}
if (rule === DISPLAY_NAME) {
orRule.conditions = [{ kind: 'contains_display_name' }];
orRule.actions = getTypeActions(type, true);
} else {
orRule.conditions = [
{ kind: 'event_match', key: 'content.body', pattern: '@room' },
{ kind: 'sender_notification_permission', key: 'room' },
];
orRule.actions = getTypeActions(type, true);
}
} else if (rule === USERNAME) {
let usernameRule = ct.find((r) => r?.rule_id === rule);
if (!usernameRule) {
const userId = mx.getUserId();
const username = userId.match(/^@?(\S+):(\S+)$/)?.[1] ?? userId;
usernameRule = {
actions: [],
default: true,
enabled: true,
pattern: username,
rule_id: rule,
};
ct.push(usernameRule);
}
usernameRule.actions = getTypeActions(type, true);
} else {
const keyRules = ct.filter((r) => r.rule_id !== USERNAME);
keyRules.forEach((r) => {
// eslint-disable-next-line no-param-reassign
r.actions = getTypeActions(type, true);
});
}
mx.setAccountData('m.push_rules', evtContent);
};
const addKeyword = (keyword) => {
if (content.find((r) => r.rule_id === keyword)) return;
content.push({
rule_id: keyword,
pattern: keyword,
enabled: true,
default: false,
actions: getTypeActions(rulesToType[KEYWORD] ?? notifType.NOISY, true),
});
mx.setAccountData('m.push_rules', pushRules);
};
const removeKeyword = (rule) => {
pushRules.global.content = content.filter((r) => r.rule_id !== rule.rule_id);
mx.setAccountData('m.push_rules', pushRules);
};
const dsRule = override.find((rule) => rule.rule_id === DISPLAY_NAME);
const roomRule = override.find((rule) => rule.rule_id === ROOM_PING);
const usernameRule = content.find((rule) => rule.rule_id === USERNAME);
const keywordRule = content.find((rule) => rule.rule_id !== USERNAME);
if (dsRule) rulesToType[DISPLAY_NAME] = getActionType(dsRule);
if (roomRule) rulesToType[ROOM_PING] = getActionType(roomRule);
if (usernameRule) rulesToType[USERNAME] = getActionType(usernameRule);
if (keywordRule) rulesToType[KEYWORD] = getActionType(keywordRule);
return {
rulesToType,
pushRules,
setRule,
addKeyword,
removeKeyword,
};
}
function GlobalNotification() {
const {
rulesToType,
pushRules,
setRule,
addKeyword,
removeKeyword,
} = useKeywordNotif();
const keywordRules = pushRules?.global?.content.filter((r) => r.rule_id !== USERNAME) ?? [];
const onSelect = (evt, rule) => {
openReusableContextMenu(
'bottom',
getEventCords(evt, '.btn-surface'),
(requestClose) => (
<NotificationSelector
value={rulesToType[rule]}
onSelect={(value) => {
if (rulesToType[rule] !== value) setRule(rule, value);
requestClose();
}}
/>
),
);
};
const handleSubmit = (evt) => {
evt.preventDefault();
const { keywordInput } = evt.target.elements;
const value = keywordInput.value.trim();
if (value === '') return;
addKeyword(value);
keywordInput.value = '';
};
return (
<div className="keyword-notification">
<MenuHeader>Mentions & keywords</MenuHeader>
<SettingTile
title="Message containing my display name"
options={(
<Button onClick={(evt) => onSelect(evt, DISPLAY_NAME)} iconSrc={ChevronBottomIC}>
{ typeToLabel[rulesToType[DISPLAY_NAME]] }
</Button>
)}
content={<Text variant="b3">Default notification settings for all message containing your display name.</Text>}
/>
<SettingTile
title="Message containing my username"
options={(
<Button onClick={(evt) => onSelect(evt, USERNAME)} iconSrc={ChevronBottomIC}>
{ typeToLabel[rulesToType[USERNAME]] }
</Button>
)}
content={<Text variant="b3">Default notification settings for all message containing your username.</Text>}
/>
<SettingTile
title="Message containing @room"
options={(
<Button onClick={(evt) => onSelect(evt, ROOM_PING)} iconSrc={ChevronBottomIC}>
{typeToLabel[rulesToType[ROOM_PING]]}
</Button>
)}
content={<Text variant="b3">Default notification settings for all messages containing @room.</Text>}
/>
{ rulesToType[KEYWORD] && (
<SettingTile
title="Message containing keywords"
options={(
<Button onClick={(evt) => onSelect(evt, KEYWORD)} iconSrc={ChevronBottomIC}>
{typeToLabel[rulesToType[KEYWORD]]}
</Button>
)}
content={<Text variant="b3">Default notification settings for all message containing keywords.</Text>}
/>
)}
<SettingTile
title="Keywords"
content={(
<div className="keyword-notification__keyword">
<Text variant="b3">Get notification when a message contains keyword.</Text>
<form onSubmit={handleSubmit}>
<Input name="keywordInput" required />
<Button variant="primary" type="submit">Add</Button>
</form>
{keywordRules.length > 0 && (
<div>
{keywordRules.map((rule) => (
<Chip
iconSrc={CrossIC}
key={rule.rule_id}
text={rule.pattern}
iconColor={CrossIC}
onClick={() => removeKeyword(rule)}
/>
))}
</div>
)}
</div>
)}
/>
</div>
);
}
export default GlobalNotification;
@@ -1,17 +0,0 @@
.keyword-notification {
&__keyword {
& form,
& > div:last-child {
display: flex;
flex-wrap: wrap;
gap: var(--sp-tight);
}
& form {
margin: var(--sp-extra-tight) 0 var(--sp-normal);
.input-container {
flex-grow: 1;
}
}
}
}
@@ -1,26 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
import CheckIC from '../../../../public/res/ic/outlined/check.svg';
function NotificationSelector({
value, onSelect,
}) {
return (
<div>
<MenuHeader>Notification</MenuHeader>
<MenuItem iconSrc={value === 'off' ? CheckIC : null} variant={value === 'off' ? 'positive' : 'surface'} onClick={() => onSelect('off')}>Off</MenuItem>
<MenuItem iconSrc={value === 'on' ? CheckIC : null} variant={value === 'on' ? 'positive' : 'surface'} onClick={() => onSelect('on')}>On</MenuItem>
<MenuItem iconSrc={value === 'noisy' ? CheckIC : null} variant={value === 'noisy' ? 'positive' : 'surface'} onClick={() => onSelect('noisy')}>Noisy</MenuItem>
</div>
);
}
NotificationSelector.propTypes = {
value: PropTypes.oneOf(['off', 'on', 'noisy']).isRequired,
onSelect: PropTypes.func.isRequired,
};
export default NotificationSelector;
@@ -1,47 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import './ImageLightbox.scss';
import FileSaver from 'file-saver';
import Text from '../../atoms/text/Text';
import RawModal from '../../atoms/modal/RawModal';
import IconButton from '../../atoms/button/IconButton';
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
function ImageLightbox({
url, alt, isOpen, onRequestClose,
}) {
const handleDownload = () => {
FileSaver.saveAs(url, alt);
};
return (
<RawModal
className="image-lightbox__modal"
overlayClassName="image-lightbox__overlay"
isOpen={isOpen}
onRequestClose={onRequestClose}
size="large"
>
<div className="image-lightbox__header">
<Text variant="b2" weight="medium">{alt}</Text>
<IconButton onClick={() => window.open(url)} size="small" src={ExternalSVG} />
<IconButton onClick={handleDownload} size="small" src={DownloadSVG} />
</div>
<div className="image-lightbox__content">
<img src={url} alt={alt} />
</div>
</RawModal>
);
}
ImageLightbox.propTypes = {
url: PropTypes.string.isRequired,
alt: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
onRequestClose: PropTypes.func.isRequired,
};
export default ImageLightbox;
@@ -1,50 +0,0 @@
@use '../../partials/flex';
@use '../../partials/text';
.image-lightbox__modal {
box-shadow: none;
width: unset;
gap: var(--sp-normal);
border-radius: 0;
pointer-events: none;
& .text {
color: white;
}
& .ic-raw {
background-color: white;
}
}
.image-lightbox__overlay {
background-color: var(--bg-overlay-low);
}
.image-lightbox__header > *,
.image-lightbox__content > * {
pointer-events: all;
}
.image-lightbox__header {
display: flex;
align-items: center;
& > .text {
@extend .cp-fx__item-one;
@extend .cp-txt__ellipsis;
}
}
.image-lightbox__content {
display: flex;
justify-content: center;
max-height: 80vh;
& img {
background-color: var(--bg-surface-low);
object-fit: contain;
max-width: 100%;
max-height: 100%;
border-radius: var(--bo-radius);
}
}
-469
View File
@@ -1,469 +0,0 @@
import React, {
useState, useMemo, useReducer, useEffect,
} from 'react';
import PropTypes from 'prop-types';
import './ImagePack.scss';
import initMatrix from '../../../client/initMatrix';
import { openReusableDialog } from '../../../client/action/navigation';
import { suffixRename } from '../../../util/common';
import Button from '../../atoms/button/Button';
import Text from '../../atoms/text/Text';
import Input from '../../atoms/input/Input';
import Checkbox from '../../atoms/button/Checkbox';
import { MenuHeader } from '../../atoms/context-menu/ContextMenu';
import { ImagePack as ImagePackBuilder } from '../../organisms/emoji-board/custom-emoji';
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
import ImagePackProfile from './ImagePackProfile';
import ImagePackItem from './ImagePackItem';
import ImagePackUpload from './ImagePackUpload';
const renameImagePackItem = (shortcode) => new Promise((resolve) => {
let isCompleted = false;
openReusableDialog(
<Text variant="s1" weight="medium">Rename</Text>,
(requestClose) => (
<div style={{ padding: 'var(--sp-normal)' }}>
<form
onSubmit={(e) => {
e.preventDefault();
const sc = e.target.shortcode.value;
if (sc.trim() === '') return;
isCompleted = true;
resolve(sc.trim());
requestClose();
}}
>
<Input
value={shortcode}
name="shortcode"
label="Shortcode"
autoFocus
required
/>
<div style={{ height: 'var(--sp-normal)' }} />
<Button variant="primary" type="submit">Rename</Button>
</form>
</div>
),
() => {
if (!isCompleted) resolve(null);
},
);
});
function getUsage(usage) {
if (usage.includes('emoticon') && usage.includes('sticker')) return 'both';
if (usage.includes('emoticon')) return 'emoticon';
if (usage.includes('sticker')) return 'sticker';
return 'both';
}
function isGlobalPack(roomId, stateKey) {
const mx = initMatrix.matrixClient;
const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent();
if (typeof globalContent !== 'object') return false;
const { rooms } = globalContent;
if (typeof rooms !== 'object') return false;
return rooms[roomId]?.[stateKey] !== undefined;
}
function useRoomImagePack(roomId, stateKey) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
const pack = useMemo(() => (
ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent())
), [room, stateKey]);
const sendPackContent = (content) => {
mx.sendStateEvent(roomId, 'im.ponies.room_emotes', content, stateKey);
};
return {
pack,
sendPackContent,
};
}
function useUserImagePack() {
const mx = initMatrix.matrixClient;
const packEvent = mx.getAccountData('im.ponies.user_emotes');
const pack = useMemo(() => (
ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? {
pack: { display_name: 'Personal' },
images: {},
})
), []);
const sendPackContent = (content) => {
mx.setAccountData('im.ponies.user_emotes', content);
};
return {
pack,
sendPackContent,
};
}
function useImagePackHandles(pack, sendPackContent) {
const [, forceUpdate] = useReducer((count) => count + 1, 0);
const getNewKey = (key) => {
if (typeof key !== 'string') return undefined;
let newKey = key?.replace(/\s/g, '_');
if (pack.getImages().get(newKey)) {
newKey = suffixRename(
newKey,
(suffixedKey) => pack.getImages().get(suffixedKey),
);
}
return newKey;
};
const handleAvatarChange = (url) => {
pack.setAvatarUrl(url);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleEditProfile = (name, attribution) => {
pack.setDisplayName(name);
pack.setAttribution(attribution);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleUsageChange = (newUsage) => {
const usage = [];
if (newUsage === 'emoticon' || newUsage === 'both') usage.push('emoticon');
if (newUsage === 'sticker' || newUsage === 'both') usage.push('sticker');
pack.setUsage(usage);
pack.getImages().forEach((img) => pack.setImageUsage(img.shortcode, undefined));
sendPackContent(pack.getContent());
forceUpdate();
};
const handleRenameItem = async (key) => {
const newKey = getNewKey(await renameImagePackItem(key));
if (!newKey || newKey === key) return;
pack.updateImageKey(key, newKey);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleDeleteItem = async (key) => {
const isConfirmed = await confirmDialog(
'Delete',
`Are you sure that you want to delete "${key}"?`,
'Delete',
'danger',
);
if (!isConfirmed) return;
pack.removeImage(key);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleUsageItem = (key, newUsage) => {
const usage = [];
if (newUsage === 'emoticon' || newUsage === 'both') usage.push('emoticon');
if (newUsage === 'sticker' || newUsage === 'both') usage.push('sticker');
pack.setImageUsage(key, usage);
sendPackContent(pack.getContent());
forceUpdate();
};
const handleAddItem = (key, url) => {
const newKey = getNewKey(key);
if (!newKey || !url) return;
pack.addImage(newKey, {
url,
});
sendPackContent(pack.getContent());
forceUpdate();
};
return {
handleAvatarChange,
handleEditProfile,
handleUsageChange,
handleRenameItem,
handleDeleteItem,
handleUsageItem,
handleAddItem,
};
}
function addGlobalImagePack(mx, roomId, stateKey) {
const content = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? {};
if (!content.rooms) content.rooms = {};
if (!content.rooms[roomId]) content.rooms[roomId] = {};
content.rooms[roomId][stateKey] = {};
return mx.setAccountData('im.ponies.emote_rooms', content);
}
function removeGlobalImagePack(mx, roomId, stateKey) {
const content = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? {};
if (!content.rooms) return Promise.resolve();
if (!content.rooms[roomId]) return Promise.resolve();
delete content.rooms[roomId][stateKey];
if (Object.keys(content.rooms[roomId]).length === 0) {
delete content.rooms[roomId];
}
return mx.setAccountData('im.ponies.emote_rooms', content);
}
function ImagePack({ roomId, stateKey, handlePackDelete }) {
const mx = initMatrix.matrixClient;
const room = mx.getRoom(roomId);
const [viewMore, setViewMore] = useState(false);
const [isGlobal, setIsGlobal] = useState(isGlobalPack(roomId, stateKey));
const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey);
const {
handleAvatarChange,
handleEditProfile,
handleUsageChange,
handleRenameItem,
handleDeleteItem,
handleUsageItem,
handleAddItem,
} = useImagePackHandles(pack, sendPackContent);
const handleGlobalChange = (isG) => {
setIsGlobal(isG);
if (isG) addGlobalImagePack(mx, roomId, stateKey);
else removeGlobalImagePack(mx, roomId, stateKey);
};
const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel || 0;
const canChange = room.currentState.hasSufficientPowerLevelFor('state_default', myPowerlevel);
const handleDeletePack = async () => {
const isConfirmed = await confirmDialog(
'Delete Pack',
`Are you sure that you want to delete "${pack.displayName}"?`,
'Delete',
'danger',
);
if (!isConfirmed) return;
handlePackDelete(stateKey);
};
const images = [...pack.images].slice(0, viewMore ? pack.images.size : 2);
return (
<div className="image-pack">
<ImagePackProfile
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
displayName={pack.displayName ?? 'Unknown'}
attribution={pack.attribution}
usage={getUsage(pack.usage)}
onUsageChange={canChange ? handleUsageChange : null}
onAvatarChange={canChange ? handleAvatarChange : null}
onEditProfile={canChange ? handleEditProfile : null}
/>
{ canChange && (
<ImagePackUpload onUpload={handleAddItem} />
)}
{ images.length === 0 ? null : (
<div>
<div className="image-pack__header">
<Text variant="b3">Image</Text>
<Text variant="b3">Shortcode</Text>
<Text variant="b3">Usage</Text>
</div>
{images.map(([shortcode, image]) => (
<ImagePackItem
key={shortcode}
url={mx.mxcUrlToHttp(image.mxc)}
shortcode={shortcode}
usage={getUsage(image.usage)}
onUsageChange={canChange ? handleUsageItem : undefined}
onDelete={canChange ? handleDeleteItem : undefined}
onRename={canChange ? handleRenameItem : undefined}
/>
))}
</div>
)}
{(pack.images.size > 2 || handlePackDelete) && (
<div className="image-pack__footer">
{pack.images.size > 2 && (
<Button onClick={() => setViewMore(!viewMore)}>
{
viewMore
? 'View less'
: `View ${pack.images.size - 2} more`
}
</Button>
)}
{ handlePackDelete && <Button variant="danger" onClick={handleDeletePack}>Delete Pack</Button>}
</div>
)}
<div className="image-pack__global">
<Checkbox variant="positive" onToggle={handleGlobalChange} isActive={isGlobal} />
<div>
<Text variant="b2">Use globally</Text>
<Text variant="b3">Add this pack to your account to use in all rooms.</Text>
</div>
</div>
</div>
);
}
ImagePack.defaultProps = {
handlePackDelete: null,
};
ImagePack.propTypes = {
roomId: PropTypes.string.isRequired,
stateKey: PropTypes.string.isRequired,
handlePackDelete: PropTypes.func,
};
function ImagePackUser() {
const mx = initMatrix.matrixClient;
const [viewMore, setViewMore] = useState(false);
const { pack, sendPackContent } = useUserImagePack();
const {
handleAvatarChange,
handleEditProfile,
handleUsageChange,
handleRenameItem,
handleDeleteItem,
handleUsageItem,
handleAddItem,
} = useImagePackHandles(pack, sendPackContent);
const images = [...pack.images].slice(0, viewMore ? pack.images.size : 2);
return (
<div className="image-pack">
<ImagePackProfile
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
displayName={pack.displayName ?? 'Personal'}
attribution={pack.attribution}
usage={getUsage(pack.usage)}
onUsageChange={handleUsageChange}
onAvatarChange={handleAvatarChange}
onEditProfile={handleEditProfile}
/>
<ImagePackUpload onUpload={handleAddItem} />
{ images.length === 0 ? null : (
<div>
<div className="image-pack__header">
<Text variant="b3">Image</Text>
<Text variant="b3">Shortcode</Text>
<Text variant="b3">Usage</Text>
</div>
{images.map(([shortcode, image]) => (
<ImagePackItem
key={shortcode}
url={mx.mxcUrlToHttp(image.mxc)}
shortcode={shortcode}
usage={getUsage(image.usage)}
onUsageChange={handleUsageItem}
onDelete={handleDeleteItem}
onRename={handleRenameItem}
/>
))}
</div>
)}
{(pack.images.size > 2) && (
<div className="image-pack__footer">
<Button onClick={() => setViewMore(!viewMore)}>
{
viewMore
? 'View less'
: `View ${pack.images.size - 2} more`
}
</Button>
</div>
)}
</div>
);
}
function useGlobalImagePack() {
const [, forceUpdate] = useReducer((count) => count + 1, 0);
const mx = initMatrix.matrixClient;
const roomIdToStateKeys = new Map();
const globalContent = mx.getAccountData('im.ponies.emote_rooms')?.getContent() ?? { rooms: {} };
const { rooms } = globalContent;
Object.keys(rooms).forEach((roomId) => {
if (typeof rooms[roomId] !== 'object') return;
const room = mx.getRoom(roomId);
const stateKeys = Object.keys(rooms[roomId]);
if (!room || stateKeys.length === 0) return;
roomIdToStateKeys.set(roomId, stateKeys);
});
useEffect(() => {
const handleEvent = (event) => {
if (event.getType() === 'im.ponies.emote_rooms') forceUpdate();
};
mx.addListener('accountData', handleEvent);
return () => {
mx.removeListener('accountData', handleEvent);
};
}, []);
return roomIdToStateKeys;
}
function ImagePackGlobal() {
const mx = initMatrix.matrixClient;
const roomIdToStateKeys = useGlobalImagePack();
const handleChange = (roomId, stateKey) => {
removeGlobalImagePack(mx, roomId, stateKey);
};
return (
<div className="image-pack-global">
<MenuHeader>Global packs</MenuHeader>
<div>
{
roomIdToStateKeys.size > 0
? [...roomIdToStateKeys].map(([roomId, stateKeys]) => {
const room = mx.getRoom(roomId);
return (
stateKeys.map((stateKey) => {
const data = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
const pack = ImagePackBuilder.parsePack(data?.getId(), data?.getContent());
if (!pack) return null;
return (
<div className="image-pack__global" key={pack.id}>
<Checkbox variant="positive" onToggle={() => handleChange(roomId, stateKey)} isActive />
<div>
<Text variant="b2">{pack.displayName ?? 'Unknown'}</Text>
<Text variant="b3">{room.name}</Text>
</div>
</div>
);
})
);
})
: <div className="image-pack-global__empty"><Text>No global packs</Text></div>
}
</div>
</div>
);
}
export default ImagePack;
export { ImagePackUser, ImagePackGlobal };
@@ -1,47 +0,0 @@
@use '../../partials/flex';
.image-pack {
&-item {
border-top: 1px solid var(--bg-surface-border);
}
&__header {
padding: var(--sp-extra-tight) var(--sp-normal);
display: flex;
align-items: center;
gap: var(--sp-normal);
& > *:nth-child(2) {
@extend .cp-fx__item-one;
}
}
&__footer {
padding: var(--sp-normal);
display: flex;
justify-content: space-between;
gap: var(--sp-tight);
}
&__global {
padding: var(--sp-normal);
padding-top: var(--sp-tight);
display: flex;
align-items: center;
gap: var(--sp-normal);
}
}
.image-pack-global {
&__empty {
text-align: center;
padding: var(--sp-extra-loose) var(--sp-normal);
}
& .image-pack__global {
padding: 0 var(--sp-normal);
padding-bottom: var(--sp-normal);
&:first-child {
padding-top: var(--sp-normal);
}
}
}
@@ -1,76 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import './ImagePackItem.scss';
import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
import Avatar from '../../atoms/avatar/Avatar';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
import RawIcon from '../../atoms/system-icons/RawIcon';
import IconButton from '../../atoms/button/IconButton';
import ImagePackUsageSelector from './ImagePackUsageSelector';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
function ImagePackItem({
url, shortcode, usage, onUsageChange, onDelete, onRename,
}) {
const handleUsageSelect = (event) => {
openReusableContextMenu(
'bottom',
getEventCords(event, '.btn-surface'),
(closeMenu) => (
<ImagePackUsageSelector
usage={usage}
onSelect={(newUsage) => {
onUsageChange(shortcode, newUsage);
closeMenu();
}}
/>
),
);
};
return (
<div className="image-pack-item">
<Avatar imageSrc={url} size="extra-small" text={shortcode} bgColor="black" />
<div className="image-pack-item__content">
<Text>{shortcode}</Text>
</div>
<div className="image-pack-item__usage">
<div className="image-pack-item__btn">
{onRename && <IconButton tooltip="Rename" size="extra-small" src={PencilIC} onClick={() => onRename(shortcode)} />}
{onDelete && <IconButton tooltip="Delete" size="extra-small" src={BinIC} onClick={() => onDelete(shortcode)} />}
</div>
<Button onClick={onUsageChange ? handleUsageSelect : undefined}>
{onUsageChange && <RawIcon src={ChevronBottomIC} size="extra-small" />}
<Text variant="b2">
{usage === 'emoticon' && 'Emoji'}
{usage === 'sticker' && 'Sticker'}
{usage === 'both' && 'Both'}
</Text>
</Button>
</div>
</div>
);
}
ImagePackItem.defaultProps = {
onUsageChange: null,
onDelete: null,
onRename: null,
};
ImagePackItem.propTypes = {
url: PropTypes.string.isRequired,
shortcode: PropTypes.string.isRequired,
usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired,
onUsageChange: PropTypes.func,
onDelete: PropTypes.func,
onRename: PropTypes.func,
};
export default ImagePackItem;
@@ -1,43 +0,0 @@
@use '../../partials/flex';
@use '../../partials/dir';
.image-pack-item {
margin: 0 var(--sp-normal);
padding: var(--sp-tight) 0;
display: flex;
align-items: center;
gap: var(--sp-normal);
& .avatar-container img {
object-fit: contain;
border-radius: 0;
}
&__content {
@extend .cp-fx__item-one;
}
&__usage {
display: flex;
gap: var(--sp-ultra-tight);
& button {
padding: 6px;
}
& > button.btn-surface {
padding: 6px var(--sp-tight);
min-width: 0;
@include dir.side(margin, var(--sp-ultra-tight), 0);
}
}
&__btn {
display: none;
}
&:hover,
&:focus-within {
.image-pack-item__btn {
display: flex;
gap: var(--sp-ultra-tight);
}
}
}
@@ -1,125 +0,0 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './ImagePackProfile.scss';
import { openReusableContextMenu } from '../../../client/action/navigation';
import { getEventCords } from '../../../util/common';
import Text from '../../atoms/text/Text';
import Avatar from '../../atoms/avatar/Avatar';
import Button from '../../atoms/button/Button';
import IconButton from '../../atoms/button/IconButton';
import Input from '../../atoms/input/Input';
import ImageUpload from '../image-upload/ImageUpload';
import ImagePackUsageSelector from './ImagePackUsageSelector';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import PencilIC from '../../../../public/res/ic/outlined/pencil.svg';
function ImagePackProfile({
avatarUrl, displayName, attribution, usage,
onUsageChange, onAvatarChange, onEditProfile,
}) {
const [isEdit, setIsEdit] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
const { nameInput, attributionInput } = e.target;
const name = nameInput.value.trim() || undefined;
const att = attributionInput.value.trim() || undefined;
onEditProfile(name, att);
setIsEdit(false);
};
const handleUsageSelect = (event) => {
openReusableContextMenu(
'bottom',
getEventCords(event, '.btn-surface'),
(closeMenu) => (
<ImagePackUsageSelector
usage={usage}
onSelect={(newUsage) => {
onUsageChange(newUsage);
closeMenu();
}}
/>
),
);
};
return (
<div className="image-pack-profile">
{
onAvatarChange
? (
<ImageUpload
bgColor="#555"
text={displayName}
imageSrc={avatarUrl}
size="normal"
onUpload={onAvatarChange}
onRequestRemove={() => onAvatarChange(undefined)}
/>
)
: <Avatar bgColor="#555" text={displayName} imageSrc={avatarUrl} size="normal" />
}
<div className="image-pack-profile__content">
{
isEdit
? (
<form onSubmit={handleSubmit}>
<Input name="nameInput" label="Name" value={displayName} required />
<Input name="attributionInput" label="Attribution" value={attribution} resizable />
<div>
<Button variant="primary" type="submit">Save</Button>
<Button onClick={() => setIsEdit(false)}>Cancel</Button>
</div>
</form>
) : (
<>
<div>
<Text>{displayName}</Text>
{onEditProfile && <IconButton size="extra-small" onClick={() => setIsEdit(true)} src={PencilIC} tooltip="Edit" />}
</div>
{attribution && <Text variant="b3">{attribution}</Text>}
</>
)
}
</div>
<div className="image-pack-profile__usage">
<Text variant="b3">Pack usage</Text>
<Button
onClick={onUsageChange ? handleUsageSelect : undefined}
iconSrc={onUsageChange ? ChevronBottomIC : null}
>
<Text>
{usage === 'emoticon' && 'Emoji'}
{usage === 'sticker' && 'Sticker'}
{usage === 'both' && 'Both'}
</Text>
</Button>
</div>
</div>
);
}
ImagePackProfile.defaultProps = {
avatarUrl: null,
attribution: null,
onUsageChange: null,
onAvatarChange: null,
onEditProfile: null,
};
ImagePackProfile.propTypes = {
avatarUrl: PropTypes.string,
displayName: PropTypes.string.isRequired,
attribution: PropTypes.string,
usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired,
onUsageChange: PropTypes.func,
onAvatarChange: PropTypes.func,
onEditProfile: PropTypes.func,
};
export default ImagePackProfile;
@@ -1,37 +0,0 @@
@use '../../partials/flex';
.image-pack-profile {
padding: var(--sp-normal);
display: flex;
align-items: flex-start;
gap: var(--sp-tight);
&__content {
@extend .cp-fx__item-one;
& > div:first-child {
display: flex;
align-items: center;
gap: var(--sp-extra-tight);
& .ic-btn {
padding: var(--sp-ultra-tight);
}
}
& > form {
display: flex;
flex-direction: column;
gap: var(--sp-extra-tight);
& > div:last-child {
margin: var(--sp-extra-tight) 0;
display: flex;
gap: var(--sp-tight);
}
}
}
&__usage {
& > *:first-child {
margin-bottom: var(--sp-ultra-tight);
}
}
}
@@ -1,73 +0,0 @@
import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types';
import './ImagePackUpload.scss';
import initMatrix from '../../../client/initMatrix';
import { scaleDownImage } from '../../../util/common';
import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button';
import Input from '../../atoms/input/Input';
import IconButton from '../../atoms/button/IconButton';
import CirclePlusIC from '../../../../public/res/ic/outlined/circle-plus.svg';
function ImagePackUpload({ onUpload }) {
const mx = initMatrix.matrixClient;
const inputRef = useRef(null);
const shortcodeRef = useRef(null);
const [imgFile, setImgFile] = useState(null);
const [progress, setProgress] = useState(false);
const handleSubmit = async (evt) => {
evt.preventDefault();
if (!imgFile) return;
const { shortcodeInput } = evt.target;
const shortcode = shortcodeInput.value.trim();
if (shortcode === '') return;
setProgress(true);
const image = await scaleDownImage(imgFile, 512, 512);
const { content_uri: url } = await mx.uploadContent(image);
onUpload(shortcode, url);
setProgress(false);
setImgFile(null);
shortcodeRef.current.value = '';
};
const handleFileChange = (evt) => {
const img = evt.target.files[0];
if (!img) return;
setImgFile(img);
shortcodeRef.current.value = img.name.slice(0, img.name.indexOf('.'));
shortcodeRef.current.focus();
};
const handleRemove = () => {
setImgFile(null);
inputRef.current.value = null;
shortcodeRef.current.value = '';
};
return (
<form onSubmit={handleSubmit} className="image-pack-upload">
<input ref={inputRef} onChange={handleFileChange} style={{ display: 'none' }} type="file" accept=".png, .gif, .webp" required />
{
imgFile
? (
<div className="image-pack-upload__file">
<IconButton onClick={handleRemove} src={CirclePlusIC} tooltip="Remove file" />
<Text>{imgFile.name}</Text>
</div>
)
: <Button onClick={() => inputRef.current.click()}>Import image</Button>
}
<Input forwardRef={shortcodeRef} name="shortcodeInput" placeholder="shortcode" required />
<Button disabled={progress} variant="primary" type="submit">{progress ? 'Uploading...' : 'Upload'}</Button>
</form>
);
}
ImagePackUpload.propTypes = {
onUpload: PropTypes.func.isRequired,
};
export default ImagePackUpload;
@@ -1,43 +0,0 @@
@use '../../partials/dir';
@use '../../partials/text';
.image-pack-upload {
padding: var(--sp-normal);
padding-top: 0;
display: flex;
gap: var(--sp-tight);
& > .input-container {
flex-grow: 1;
input {
padding: 9px var(--sp-normal);
}
}
&__file {
display: inline-flex;
align-items: center;
background: var(--bg-surface-low);
border-radius: var(--bo-radius);
box-shadow: var(--bs-surface-border);
& button {
--parent-height: 40px;
width: var(--parent-height);
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
& .ic-raw {
background-color: var(--bg-caution);
transform: rotate(45deg);
}
& .text {
@extend .cp-txt__ellipsis;
@include dir.side(margin, var(--sp-ultra-tight), var(--sp-normal));
max-width: 86px;
}
}
}
@@ -1,41 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu';
import CheckIC from '../../../../public/res/ic/outlined/check.svg';
function ImagePackUsageSelector({ usage, onSelect }) {
return (
<div>
<MenuHeader>Usage</MenuHeader>
<MenuItem
iconSrc={usage === 'emoticon' ? CheckIC : undefined}
variant={usage === 'emoticon' ? 'positive' : 'surface'}
onClick={() => onSelect('emoticon')}
>
Emoji
</MenuItem>
<MenuItem
iconSrc={usage === 'sticker' ? CheckIC : undefined}
variant={usage === 'sticker' ? 'positive' : 'surface'}
onClick={() => onSelect('sticker')}
>
Sticker
</MenuItem>
<MenuItem
iconSrc={usage === 'both' ? CheckIC : undefined}
variant={usage === 'both' ? 'positive' : 'surface'}
onClick={() => onSelect('both')}
>
Both
</MenuItem>
</div>
);
}
ImagePackUsageSelector.propTypes = {
usage: PropTypes.oneOf(['emoticon', 'sticker', 'both']).isRequired,
onSelect: PropTypes.func.isRequired,
};
export default ImagePackUsageSelector;
+4 -14
View File
@@ -7,13 +7,9 @@ import initMatrix from '../../../client/initMatrix';
import Text from '../../atoms/text/Text';
import Avatar from '../../atoms/avatar/Avatar';
import Spinner from '../../atoms/spinner/Spinner';
import RawIcon from '../../atoms/system-icons/RawIcon';
import PlusIC from '../../../../public/res/ic/outlined/plus.svg';
function ImageUpload({
text, bgColor, imageSrc, onUpload, onRequestRemove,
size,
}) {
const [uploadPromise, setUploadPromise] = useState(null);
const uploadImageRef = useRef(null);
@@ -22,7 +18,7 @@ function ImageUpload({
const file = e.target.files.item(0);
if (file === null) return;
try {
const uPromise = initMatrix.matrixClient.uploadContent(file);
const uPromise = initMatrix.matrixClient.uploadContent(file, { onlyContentUri: false });
setUploadPromise(uPromise);
const res = await uPromise;
@@ -54,14 +50,10 @@ function ImageUpload({
imageSrc={imageSrc}
text={text}
bgColor={bgColor}
size={size}
size="large"
/>
<div className={`img-upload__process ${uploadPromise === null ? ' img-upload__process--stopped' : ''}`}>
{uploadPromise === null && (
size === 'large'
? <Text variant="b3" weight="bold">Upload</Text>
: <RawIcon src={PlusIC} color="white" />
)}
{uploadPromise === null && <Text variant="b3" weight="bold">Upload</Text>}
{uploadPromise !== null && <Spinner size="small" />}
</div>
</button>
@@ -74,7 +66,7 @@ function ImageUpload({
<Text variant="b3">{uploadPromise ? 'Cancel' : 'Remove'}</Text>
</button>
)}
<input onChange={uploadImage} style={{ display: 'none' }} ref={uploadImageRef} type="file" accept="image/*" />
<input onChange={uploadImage} style={{ display: 'none' }} ref={uploadImageRef} type="file" />
</div>
);
}
@@ -83,7 +75,6 @@ ImageUpload.defaultProps = {
text: null,
bgColor: 'transparent',
imageSrc: null,
size: 'large',
};
ImageUpload.propTypes = {
@@ -92,7 +83,6 @@ ImageUpload.propTypes = {
imageSrc: PropTypes.string,
onUpload: PropTypes.func.isRequired,
onRequestRemove: PropTypes.func.isRequired,
size: PropTypes.oneOf(['large', 'normal']),
};
export default ImageUpload;
@@ -52,7 +52,7 @@ function ExportE2ERoomKeys() {
const blob = new Blob([encKeys], {
type: 'text/plain;charset=us-ascii',
});
FileSaver.saveAs(blob, 'versi-keys.txt');
FileSaver.saveAs(blob, 'cinny-keys.txt');
if (isMountStore.getItem()) {
setStatus({
isOngoing: false,
+81 -138
View File
@@ -4,17 +4,42 @@ import './Media.scss';
import encrypt from 'browser-encrypt-attachment';
import { BlurhashCanvas } from 'react-blurhash';
import Text from '../../atoms/text/Text';
import IconButton from '../../atoms/button/IconButton';
import Spinner from '../../atoms/spinner/Spinner';
import ImageLightbox from '../image-lightbox/ImageLightbox';
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
import PlaySVG from '../../../../public/res/ic/outlined/play.svg';
import { getBlobSafeMimeType } from '../../../util/mimetypes';
// https://github.com/matrix-org/matrix-react-sdk/blob/a9e28db33058d1893d964ec96cd247ecc3d92fc3/src/utils/blobs.ts#L73
const ALLOWED_BLOB_MIMETYPES = [
'image/jpeg',
'image/gif',
'image/png',
'video/mp4',
'video/webm',
'video/ogg',
'audio/mp4',
'audio/webm',
'audio/aac',
'audio/mpeg',
'audio/ogg',
'audio/wave',
'audio/wav',
'audio/x-wav',
'audio/x-pn-wav',
'audio/flac',
'audio/x-flac',
];
function getBlobSafeMimeType(mimetype) {
if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) {
return 'application/octet-stream';
}
return mimetype;
}
async function getDecryptedBlob(response, type, decryptData) {
const arrayBuffer = await response.arrayBuffer();
@@ -36,12 +61,16 @@ async function getUrl(link, type, decryptData) {
}
}
function getNativeHeight(width, height, maxWidth = 296) {
const scale = maxWidth / width;
function getNativeHeight(width, height) {
const MEDIA_MAX_WIDTH = 296;
const scale = MEDIA_MAX_WIDTH / width;
return scale * height;
}
function FileHeader({ name, link, external, file, type }) {
function FileHeader({
name, link, external,
file, type,
}) {
const [url, setUrl] = useState(null);
async function getFile() {
@@ -58,19 +87,19 @@ function FileHeader({ name, link, external, file, type }) {
}
return (
<div className="file-header">
<Text className="file-name" variant="b3">
{name}
</Text>
{link !== null && (
<Text className="file-name" variant="b3">{name}</Text>
{ link !== null && (
<>
{external && (
<IconButton
size="extra-small"
tooltip="Open in new tab"
src={ExternalSVG}
onClick={() => window.open(url || link)}
/>
)}
{
external && (
<IconButton
size="extra-small"
tooltip="Open in new tab"
src={ExternalSVG}
onClick={() => window.open(url || link)}
/>
)
}
<a href={url || link} download={name} target="_blank" rel="noreferrer">
<IconButton
size="extra-small"
@@ -97,7 +126,9 @@ FileHeader.propTypes = {
type: PropTypes.string.isRequired,
};
function File({ name, link, file, type }) {
function File({
name, link, file, type,
}) {
return (
<div className="file-container">
<FileHeader name={name} link={link} file={file} type={type} />
@@ -115,10 +146,10 @@ File.propTypes = {
file: PropTypes.shape({}),
};
function Image({ name, width, height, link, file, type, blurhash }) {
function Image({
name, width, height, link, file, type,
}) {
const [url, setUrl] = useState(null);
const [blur, setBlur] = useState(true);
const [lightbox, setLightbox] = useState(false);
useEffect(() => {
let unmounted = false;
@@ -133,37 +164,13 @@ function Image({ name, width, height, link, file, type, blurhash }) {
};
}, []);
const toggleLightbox = () => {
if (!url) return;
setLightbox(!lightbox);
};
return (
<>
<div className="file-container">
<div
style={{ height: width !== null ? getNativeHeight(width, height) : 'unset' }}
className="image-container"
role="button"
tabIndex="0"
onClick={toggleLightbox}
onKeyDown={toggleLightbox}
>
{blurhash && blur && <BlurhashCanvas hash={blurhash} punch={1} />}
{url !== null && (
<img
style={{ display: blur ? 'none' : 'unset' }}
onLoad={() => setBlur(false)}
src={url || link}
alt={name}
/>
)}
</div>
<div className="file-container">
<FileHeader name={name} link={url || link} type={type} external />
<div style={{ height: width !== null ? getNativeHeight(width, height) : 'unset' }} className="image-container">
{ url !== null && <img src={url || link} alt={name} />}
</div>
{url && (
<ImageLightbox url={url} alt={name} isOpen={lightbox} onRequestClose={toggleLightbox} />
)}
</>
</div>
);
}
Image.defaultProps = {
@@ -171,7 +178,6 @@ Image.defaultProps = {
width: null,
height: null,
type: '',
blurhash: '',
};
Image.propTypes = {
name: PropTypes.string.isRequired,
@@ -180,50 +186,11 @@ Image.propTypes = {
link: PropTypes.string.isRequired,
file: PropTypes.shape({}),
type: PropTypes.string,
blurhash: PropTypes.string,
};
function Sticker({ name, height, width, link, file, type }) {
const [url, setUrl] = useState(null);
useEffect(() => {
let unmounted = false;
async function fetchUrl() {
const myUrl = await getUrl(link, type, file);
if (unmounted) return;
setUrl(myUrl);
}
fetchUrl();
return () => {
unmounted = true;
};
}, []);
return (
<div
className="sticker-container"
style={{ height: width !== null ? getNativeHeight(width, height, 256) : 'unset' }}
>
{url !== null && <img src={url || link} title={name} alt={name} />}
</div>
);
}
Sticker.defaultProps = {
file: null,
type: '',
width: null,
height: null,
};
Sticker.propTypes = {
name: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number,
link: PropTypes.string.isRequired,
file: PropTypes.shape({}),
type: PropTypes.string,
};
function Audio({ name, link, type, file }) {
function Audio({
name, link, type, file,
}) {
const [isLoading, setIsLoading] = useState(false);
const [url, setUrl] = useState(null);
@@ -241,11 +208,9 @@ function Audio({ name, link, type, file }) {
<div className="file-container">
<FileHeader name={name} link={file !== null ? url : url || link} type={type} external />
<div className="audio-container">
{url === null && isLoading && <Spinner size="small" />}
{url === null && !isLoading && (
<IconButton onClick={handlePlayAudio} tooltip="Play audio" src={PlaySVG} />
)}
{url !== null && (
{ url === null && isLoading && <Spinner size="small" /> }
{ url === null && !isLoading && <IconButton onClick={handlePlayAudio} tooltip="Play audio" src={PlaySVG} />}
{ url !== null && (
/* eslint-disable-next-line jsx-a11y/media-has-caption */
<audio autoPlay controls>
<source src={url} type={getBlobSafeMimeType(type)} />
@@ -267,21 +232,12 @@ Audio.propTypes = {
};
function Video({
name,
link,
thumbnail,
thumbnailFile,
thumbnailType,
width,
height,
file,
type,
blurhash,
name, link, thumbnail,
width, height, file, type, thumbnailFile, thumbnailType,
}) {
const [isLoading, setIsLoading] = useState(false);
const [url, setUrl] = useState(null);
const [thumbUrl, setThumbUrl] = useState(null);
const [blur, setBlur] = useState(true);
useEffect(() => {
let unmounted = false;
@@ -296,16 +252,16 @@ function Video({
};
}, []);
const loadVideo = async () => {
async function loadVideo() {
const myUrl = await getUrl(link, type, file);
setUrl(myUrl);
setIsLoading(false);
};
}
const handlePlayVideo = () => {
function handlePlayVideo() {
setIsLoading(true);
loadVideo();
};
}
return (
<div className="file-container">
@@ -313,27 +269,14 @@ function Video({
<div
style={{
height: width !== null ? getNativeHeight(width, height) : 'unset',
backgroundImage: thumbUrl === null ? 'none' : `url(${thumbUrl}`,
}}
className="video-container"
>
{url === null ? (
<>
{blurhash && blur && <BlurhashCanvas hash={blurhash} punch={1} />}
{thumbUrl !== null && (
<img
style={{ display: blur ? 'none' : 'unset' }}
src={thumbUrl}
onLoad={() => setBlur(false)}
alt={name}
/>
)}
{isLoading && <Spinner size="small" />}
{!isLoading && (
<IconButton onClick={handlePlayVideo} tooltip="Play video" src={PlaySVG} />
)}
</>
) : (
/* eslint-disable-next-line jsx-a11y/media-has-caption */
{ url === null && isLoading && <Spinner size="small" /> }
{ url === null && !isLoading && <IconButton onClick={handlePlayVideo} tooltip="Play video" src={PlaySVG} />}
{ url !== null && (
/* eslint-disable-next-line jsx-a11y/media-has-caption */
<video autoPlay controls poster={thumbUrl}>
<source src={url} type={getBlobSafeMimeType(type)} />
</video>
@@ -347,22 +290,22 @@ Video.defaultProps = {
height: null,
file: null,
thumbnail: null,
type: '',
thumbnailType: null,
thumbnailFile: null,
type: '',
blurhash: null,
};
Video.propTypes = {
name: PropTypes.string.isRequired,
link: PropTypes.string.isRequired,
thumbnail: PropTypes.string,
thumbnailFile: PropTypes.shape({}),
thumbnailType: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
file: PropTypes.shape({}),
type: PropTypes.string,
blurhash: PropTypes.string,
thumbnailFile: PropTypes.shape({}),
thumbnailType: PropTypes.string,
};
export { File, Image, Sticker, Audio, Video };
export {
File, Image, Audio, Video,
};
Loaded 100 of 184 files, more files were not shown because too many files have changed in this diff. Show more