Compare commits
29 commits
6c5b8a1d14
...
3c4b7e5c33
Author | SHA1 | Date | |
---|---|---|---|
3c4b7e5c33 | |||
|
a142630ff9 | ||
|
492a149c7f | ||
|
c110e64341 | ||
|
0e51e19cab | ||
|
35b0b1ea42 | ||
|
cca8b5f2b2 | ||
|
48265c4227 | ||
|
c38efdfbce | ||
|
d8833a310d | ||
|
5824d7c716 | ||
|
6e191d3c79 | ||
|
21164a9b61 | ||
|
4923b17ad6 | ||
|
c75e903619 | ||
|
042cbc4453 | ||
|
03cc25eec0 | ||
|
f2c31d29a2 | ||
|
5482f8e72e | ||
|
96df140153 | ||
|
4dfce32730 | ||
|
388f606ad2 | ||
|
09444f9e08 | ||
|
c6a8fb1117 | ||
|
043012e809 | ||
|
5c9ee1a988 | ||
|
22b7f6dd7d | ||
|
bdba0332e1 | ||
|
16be69c104 |
72 changed files with 4034 additions and 735 deletions
4
.github/workflows/build-pull-request.yml
vendored
4
.github/workflows/build-pull-request.yml
vendored
|
@ -12,9 +12,9 @@ jobs:
|
||||||
PR_NUMBER: ${{github.event.number}}
|
PR_NUMBER: ${{github.event.number}}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.2.0
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20.12.2
|
node-version: 20.12.2
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
2
.github/workflows/cla.yml
vendored
2
.github/workflows/cla.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
||||||
- name: 'CLA Assistant'
|
- name: 'CLA Assistant'
|
||||||
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||||
# Beta Release
|
# Beta Release
|
||||||
uses: cla-assistant/github-action@v2.4.0
|
uses: cla-assistant/github-action@v2.6.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# the below token should have repo scope and must be manually added by you in the repository's secret
|
# the below token should have repo scope and must be manually added by you in the repository's secret
|
||||||
|
|
4
.github/workflows/docker-pr.yml
vendored
4
.github/workflows/docker-pr.yml
vendored
|
@ -11,9 +11,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.2.0
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
uses: docker/build-push-action@v6.6.1
|
uses: docker/build-push-action@v6.9.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
|
|
2
.github/workflows/lockfile.yml
vendored
2
.github/workflows/lockfile.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.2.0
|
||||||
- name: NPM Lockfile Changes
|
- name: NPM Lockfile Changes
|
||||||
uses: codepunkt/npm-lockfile-changes@b40543471c36394409466fdb277a73a0856d7891
|
uses: codepunkt/npm-lockfile-changes@b40543471c36394409466fdb277a73a0856d7891
|
||||||
with:
|
with:
|
||||||
|
|
4
.github/workflows/netlify-dev.yml
vendored
4
.github/workflows/netlify-dev.yml
vendored
|
@ -11,9 +11,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.2.0
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20.12.2
|
node-version: 20.12.2
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
8
.github/workflows/prod-deploy.yml
vendored
8
.github/workflows/prod-deploy.yml
vendored
|
@ -10,9 +10,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.2.0
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.4
|
||||||
with:
|
with:
|
||||||
node-version: 20.12.2
|
node-version: 20.12.2
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
@ -66,7 +66,7 @@ jobs:
|
||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.2.0
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.2.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
@ -90,7 +90,7 @@ jobs:
|
||||||
${{ secrets.DOCKER_USERNAME }}/cinny
|
${{ secrets.DOCKER_USERNAME }}/cinny
|
||||||
ghcr.io/${{ github.repository }}
|
ghcr.io/${{ github.repository }}
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v6.6.1
|
uses: docker/build-push-action@v6.9.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
3
.npmrc
3
.npmrc
|
@ -1,3 +1,2 @@
|
||||||
legacy-peer-deps=true
|
legacy-peer-deps=true
|
||||||
save-exact=true
|
save-exact=true
|
||||||
@matrix-org:registry=https://gitlab.matrix.org/api/v4/projects/27/packages/npm/
|
|
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
cinnyapp@gmail.com.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
|
@ -24,6 +24,7 @@ server {
|
||||||
rewrite ^/manifest.json$ /manifest.json break;
|
rewrite ^/manifest.json$ /manifest.json break;
|
||||||
|
|
||||||
rewrite ^.*/olm.wasm$ /olm.wasm break;
|
rewrite ^.*/olm.wasm$ /olm.wasm break;
|
||||||
|
rewrite ^/sw.js$ /sw.js break;
|
||||||
rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break;
|
rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break;
|
||||||
|
|
||||||
rewrite ^/public/(.*)$ /public/$1 break;
|
rewrite ^/public/(.*)$ /public/$1 break;
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
server {
|
server {
|
||||||
location / {
|
listen 80;
|
||||||
root /usr/share/nginx/html;
|
listen [::]:80;
|
||||||
|
|
||||||
rewrite ^/config.json$ /config.json break;
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
rewrite ^/config.json$ /config.json break;
|
||||||
rewrite ^/manifest.json$ /manifest.json break;
|
rewrite ^/manifest.json$ /manifest.json break;
|
||||||
|
|
||||||
rewrite ^.*/olm.wasm$ /olm.wasm break;
|
rewrite ^.*/olm.wasm$ /olm.wasm break;
|
||||||
|
rewrite ^/sw.js$ /sw.js break;
|
||||||
rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break;
|
rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break;
|
||||||
|
|
||||||
rewrite ^/public/(.*)$ /public/$1 break;
|
rewrite ^/public/(.*)$ /public/$1 break;
|
||||||
rewrite ^/assets/(.*)$ /assets/$1 break;
|
rewrite ^/assets/(.*)$ /assets/$1 break;
|
||||||
|
|
||||||
rewrite ^(.+)$ /index.html break;
|
rewrite ^(.+)$ /index.html break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,12 @@
|
||||||
from = "/manifest.json"
|
from = "/manifest.json"
|
||||||
to = "/manifest.json"
|
to = "/manifest.json"
|
||||||
status = 200
|
status = 200
|
||||||
|
|
||||||
|
[[redirects]]
|
||||||
|
from = "/sw.js"
|
||||||
|
to = "/sw.js"
|
||||||
|
status = 200
|
||||||
|
|
||||||
[[redirects]]
|
[[redirects]]
|
||||||
from = "*/olm.wasm"
|
from = "*/olm.wasm"
|
||||||
to = "/olm.wasm"
|
to = "/olm.wasm"
|
||||||
|
|
3358
package-lock.json
generated
3358
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cinny",
|
"name": "cinny",
|
||||||
"version": "4.1.0",
|
"version": "4.2.3",
|
||||||
"description": "Yet another matrix client",
|
"description": "Yet another matrix client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
|
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
|
||||||
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
|
||||||
"@fontsource/inter": "4.5.14",
|
"@fontsource/inter": "4.5.14",
|
||||||
"@matrix-org/olm": "3.2.14",
|
"@matrix-org/olm": "3.2.15",
|
||||||
"@tanstack/react-query": "5.24.1",
|
"@tanstack/react-query": "5.24.1",
|
||||||
"@tanstack/react-query-devtools": "5.24.1",
|
"@tanstack/react-query-devtools": "5.24.1",
|
||||||
"@tanstack/react-virtual": "3.2.0",
|
"@tanstack/react-virtual": "3.2.0",
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
"jotai": "2.6.0",
|
"jotai": "2.6.0",
|
||||||
"linkify-react": "4.1.3",
|
"linkify-react": "4.1.3",
|
||||||
"linkifyjs": "4.1.3",
|
"linkifyjs": "4.1.3",
|
||||||
"matrix-js-sdk": "29.1.0",
|
"matrix-js-sdk": "34.11.1",
|
||||||
"millify": "6.1.0",
|
"millify": "6.1.0",
|
||||||
"pdfjs-dist": "4.2.67",
|
"pdfjs-dist": "4.2.67",
|
||||||
"prismjs": "1.29.0",
|
"prismjs": "1.29.0",
|
||||||
|
@ -106,6 +106,7 @@
|
||||||
"sass": "1.56.2",
|
"sass": "1.56.2",
|
||||||
"typescript": "4.9.4",
|
"typescript": "4.9.4",
|
||||||
"vite": "5.0.13",
|
"vite": "5.0.13",
|
||||||
|
"vite-plugin-pwa": "0.20.5",
|
||||||
"vite-plugin-static-copy": "1.0.4",
|
"vite-plugin-static-copy": "1.0.4",
|
||||||
"vite-plugin-top-level-await": "1.4.1"
|
"vite-plugin-top-level-await": "1.4.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { getEmojiUrl, isUsingTwemoji } from '../../plugins/emoji';
|
import { getEmojiUrl, isUsingTwemoji } from '../../plugins/emoji';
|
||||||
import { getBeginCommand } from './utils';
|
import { getBeginCommand } from './utils';
|
||||||
import { BlockType } from './types';
|
import { BlockType } from './types';
|
||||||
|
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
// Put this at the start and end of an inline component to work around this Chromium bug:
|
// Put this at the start and end of an inline component to work around this Chromium bug:
|
||||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1249405
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1249405
|
||||||
|
@ -77,6 +79,7 @@ function RenderEmoticonElement({
|
||||||
children,
|
children,
|
||||||
}: { element: EmoticonElement } & RenderElementProps) {
|
}: { element: EmoticonElement } & RenderElementProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const selected = useSelected();
|
const selected = useSelected();
|
||||||
const focused = useFocused();
|
const focused = useFocused();
|
||||||
|
|
||||||
|
@ -91,7 +94,7 @@ function RenderEmoticonElement({
|
||||||
{element.key.startsWith('mxc://') ? (
|
{element.key.startsWith('mxc://') ? (
|
||||||
<img
|
<img
|
||||||
className={css.EmoticonImg}
|
className={css.EmoticonImg}
|
||||||
src={mx.mxcUrlToHttp(element.key) ?? element.key}
|
src={mxcUrlToHttp(mx, element.key, useAuthentication) ?? element.key}
|
||||||
alt={element.shortcode}
|
alt={element.shortcode}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -18,6 +18,8 @@ import { useRelevantImagePacks } from '../../../hooks/useImagePacks';
|
||||||
import { IEmoji, emojis } from '../../../plugins/emoji';
|
import { IEmoji, emojis } from '../../../plugins/emoji';
|
||||||
import { ExtendedPackImage, PackUsage } from '../../../plugins/custom-emoji';
|
import { ExtendedPackImage, PackUsage } from '../../../plugins/custom-emoji';
|
||||||
import { useKeyDown } from '../../../hooks/useKeyDown';
|
import { useKeyDown } from '../../../hooks/useKeyDown';
|
||||||
|
import { mxcUrlToHttp } from '../../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type EmoticonCompleteHandler = (key: string, shortcode: string) => void;
|
type EmoticonCompleteHandler = (key: string, shortcode: string) => void;
|
||||||
|
|
||||||
|
@ -48,6 +50,7 @@ export function EmoticonAutocomplete({
|
||||||
requestClose,
|
requestClose,
|
||||||
}: EmoticonAutocompleteProps) {
|
}: EmoticonAutocompleteProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const imagePacks = useRelevantImagePacks(mx, PackUsage.Emoticon, imagePackRooms);
|
const imagePacks = useRelevantImagePacks(mx, PackUsage.Emoticon, imagePackRooms);
|
||||||
const recentEmoji = useRecentEmoji(mx, 20);
|
const recentEmoji = useRecentEmoji(mx, 20);
|
||||||
|
@ -103,7 +106,7 @@ export function EmoticonAutocomplete({
|
||||||
<Box
|
<Box
|
||||||
shrink="No"
|
shrink="No"
|
||||||
as="img"
|
as="img"
|
||||||
src={mx.mxcUrlToHttp(key) || key}
|
src={mxcUrlToHttp(mx, key, useAuthentication) || key}
|
||||||
alt={emoticon.shortcode}
|
alt={emoticon.shortcode}
|
||||||
style={{ width: toRem(24), height: toRem(24), objectFit: 'contain' }}
|
style={{ width: toRem(24), height: toRem(24), objectFit: 'contain' }}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { useKeyDown } from '../../../hooks/useKeyDown';
|
||||||
import { getMxIdLocalPart, getMxIdServer, validMxId } from '../../../utils/matrix';
|
import { getMxIdLocalPart, getMxIdServer, validMxId } from '../../../utils/matrix';
|
||||||
import { getMemberDisplayName, getMemberSearchStr } from '../../../utils/room';
|
import { getMemberDisplayName, getMemberSearchStr } from '../../../utils/room';
|
||||||
import { UserAvatar } from '../../user-avatar';
|
import { UserAvatar } from '../../user-avatar';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type MentionAutoCompleteHandler = (userId: string, name: string) => void;
|
type MentionAutoCompleteHandler = (userId: string, name: string) => void;
|
||||||
|
|
||||||
|
@ -84,6 +85,7 @@ export function UserMentionAutocomplete({
|
||||||
requestClose,
|
requestClose,
|
||||||
}: UserMentionAutocompleteProps) {
|
}: UserMentionAutocompleteProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const roomId: string = room.roomId!;
|
const roomId: string = room.roomId!;
|
||||||
const roomAliasOrId = room.getCanonicalAlias() || roomId;
|
const roomAliasOrId = room.getCanonicalAlias() || roomId;
|
||||||
const members = useRoomMembers(mx, roomId);
|
const members = useRoomMembers(mx, roomId);
|
||||||
|
@ -143,7 +145,10 @@ export function UserMentionAutocomplete({
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
autoCompleteMembers.map((roomMember) => {
|
autoCompleteMembers.map((roomMember) => {
|
||||||
const avatarUrl = roomMember.getAvatarUrl(mx.baseUrl, 32, 32, 'crop', undefined, false);
|
const avatarMxcUrl = roomMember.getMxcAvatarUrl();
|
||||||
|
const avatarUrl = avatarMxcUrl
|
||||||
|
? mx.mxcUrlToHttp(avatarMxcUrl, 32, 32, 'crop', undefined, false, useAuthentication)
|
||||||
|
: undefined;
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={roomMember.userId}
|
key={roomMember.userId}
|
||||||
|
|
|
@ -62,7 +62,7 @@ const elementToCustomHtml = (node: CustomElement, children: string): string => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const matrixTo = `https://matrix.to/#/${fragment}`;
|
const matrixTo = `https://matrix.to/#/${fragment}`;
|
||||||
return `<a href="${encodeURIComponent(matrixTo)}">${sanitizeText(node.name)}</a>`;
|
return `<a href="${encodeURI(matrixTo)}">${sanitizeText(node.name)}</a>`;
|
||||||
}
|
}
|
||||||
case BlockType.Emoticon:
|
case BlockType.Emoticon:
|
||||||
return node.key.startsWith('mxc://')
|
return node.key.startsWith('mxc://')
|
||||||
|
@ -71,7 +71,7 @@ const elementToCustomHtml = (node: CustomElement, children: string): string => {
|
||||||
)}" title="${sanitizeText(node.shortcode)}" height="32" />`
|
)}" title="${sanitizeText(node.shortcode)}" height="32" />`
|
||||||
: sanitizeText(node.key);
|
: sanitizeText(node.key);
|
||||||
case BlockType.Link:
|
case BlockType.Link:
|
||||||
return `<a href="${encodeURIComponent(node.href)}">${node.children}</a>`;
|
return `<a href="${encodeURI(node.href)}">${node.children}</a>`;
|
||||||
case BlockType.Command:
|
case BlockType.Command:
|
||||||
return `/${sanitizeText(node.command)}`;
|
return `/${sanitizeText(node.command)}`;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -50,13 +50,14 @@ import { useRelevantImagePacks } from '../../hooks/useImagePacks';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { useRecentEmoji } from '../../hooks/useRecentEmoji';
|
import { useRecentEmoji } from '../../hooks/useRecentEmoji';
|
||||||
import { ExtendedPackImage, ImagePack, PackUsage } from '../../plugins/custom-emoji';
|
import { ExtendedPackImage, ImagePack, PackUsage } from '../../plugins/custom-emoji';
|
||||||
import { isUserId } from '../../utils/matrix';
|
import { isUserId, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
import { editableActiveElement, isIntersectingScrollView, targetFromEvent } from '../../utils/dom';
|
import { editableActiveElement, isIntersectingScrollView, targetFromEvent } from '../../utils/dom';
|
||||||
import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
|
import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch';
|
||||||
import { useDebounce } from '../../hooks/useDebounce';
|
import { useDebounce } from '../../hooks/useDebounce';
|
||||||
import { useThrottle } from '../../hooks/useThrottle';
|
import { useThrottle } from '../../hooks/useThrottle';
|
||||||
import { addRecentEmoji } from '../../plugins/recent-emoji';
|
import { addRecentEmoji } from '../../plugins/recent-emoji';
|
||||||
import { mobileOrTablet } from '../../utils/user-agent';
|
import { mobileOrTablet } from '../../utils/user-agent';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const RECENT_GROUP_ID = 'recent_group';
|
const RECENT_GROUP_ID = 'recent_group';
|
||||||
const SEARCH_GROUP_ID = 'search_group';
|
const SEARCH_GROUP_ID = 'search_group';
|
||||||
|
@ -371,11 +372,13 @@ function ImagePackSidebarStack({
|
||||||
packs,
|
packs,
|
||||||
usage,
|
usage,
|
||||||
onItemClick,
|
onItemClick,
|
||||||
|
useAuthentication,
|
||||||
}: {
|
}: {
|
||||||
mx: MatrixClient;
|
mx: MatrixClient;
|
||||||
packs: ImagePack[];
|
packs: ImagePack[];
|
||||||
usage: PackUsage;
|
usage: PackUsage;
|
||||||
onItemClick: (id: string) => void;
|
onItemClick: (id: string) => void;
|
||||||
|
useAuthentication?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const activeGroupId = useAtomValue(activeGroupIdAtom);
|
const activeGroupId = useAtomValue(activeGroupIdAtom);
|
||||||
return (
|
return (
|
||||||
|
@ -398,7 +401,7 @@ function ImagePackSidebarStack({
|
||||||
height: toRem(24),
|
height: toRem(24),
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
}}
|
}}
|
||||||
src={mx.mxcUrlToHttp(pack.getPackAvatarUrl(usage) ?? '') || pack.avatarUrl}
|
src={mxcUrlToHttp(mx, pack.getPackAvatarUrl(usage) ?? '', useAuthentication) || pack.avatarUrl}
|
||||||
alt={label || 'Unknown Pack'}
|
alt={label || 'Unknown Pack'}
|
||||||
/>
|
/>
|
||||||
</SidebarBtn>
|
</SidebarBtn>
|
||||||
|
@ -470,68 +473,70 @@ export function SearchEmojiGroup({
|
||||||
label,
|
label,
|
||||||
id,
|
id,
|
||||||
emojis: searchResult,
|
emojis: searchResult,
|
||||||
|
useAuthentication,
|
||||||
}: {
|
}: {
|
||||||
mx: MatrixClient;
|
mx: MatrixClient;
|
||||||
tab: EmojiBoardTab;
|
tab: EmojiBoardTab;
|
||||||
label: string;
|
label: string;
|
||||||
id: string;
|
id: string;
|
||||||
emojis: Array<ExtendedPackImage | IEmoji>;
|
emojis: Array<ExtendedPackImage | IEmoji>;
|
||||||
|
useAuthentication?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<EmojiGroup key={id} id={id} label={label}>
|
<EmojiGroup key={id} id={id} label={label}>
|
||||||
{tab === EmojiBoardTab.Emoji
|
{tab === EmojiBoardTab.Emoji
|
||||||
? searchResult.map((emoji) =>
|
? searchResult.map((emoji) =>
|
||||||
'unicode' in emoji ? (
|
'unicode' in emoji ? (
|
||||||
<EmojiItem
|
<EmojiItem
|
||||||
key={emoji.unicode}
|
key={emoji.unicode}
|
||||||
label={emoji.label}
|
label={emoji.label}
|
||||||
type={EmojiType.Emoji}
|
type={EmojiType.Emoji}
|
||||||
data={emoji.unicode}
|
data={emoji.unicode}
|
||||||
shortcode={emoji.shortcode}
|
shortcode={emoji.shortcode}
|
||||||
>
|
>
|
||||||
{NativeEmoji(emoji)}
|
{NativeEmoji(emoji)}
|
||||||
</EmojiItem>
|
</EmojiItem>
|
||||||
) : (
|
) : (
|
||||||
<EmojiItem
|
<EmojiItem
|
||||||
key={emoji.shortcode}
|
key={emoji.shortcode}
|
||||||
label={emoji.body || emoji.shortcode}
|
label={emoji.body || emoji.shortcode}
|
||||||
type={EmojiType.CustomEmoji}
|
type={EmojiType.CustomEmoji}
|
||||||
data={emoji.url}
|
data={emoji.url}
|
||||||
shortcode={emoji.shortcode}
|
shortcode={emoji.shortcode}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={css.CustomEmojiImg}
|
className={css.CustomEmojiImg}
|
||||||
alt={emoji.body || emoji.shortcode}
|
alt={emoji.body || emoji.shortcode}
|
||||||
src={mx.mxcUrlToHttp(emoji.url) ?? emoji.url}
|
src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
|
||||||
/>
|
/>
|
||||||
</EmojiItem>
|
</EmojiItem>
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
: searchResult.map((emoji) =>
|
: searchResult.map((emoji) =>
|
||||||
'unicode' in emoji ? null : (
|
'unicode' in emoji ? null : (
|
||||||
<StickerItem
|
<StickerItem
|
||||||
key={emoji.shortcode}
|
key={emoji.shortcode}
|
||||||
label={emoji.body || emoji.shortcode}
|
label={emoji.body || emoji.shortcode}
|
||||||
type={EmojiType.Sticker}
|
type={EmojiType.Sticker}
|
||||||
data={emoji.url}
|
data={emoji.url}
|
||||||
shortcode={emoji.shortcode}
|
shortcode={emoji.shortcode}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={css.StickerImg}
|
className={css.StickerImg}
|
||||||
alt={emoji.body || emoji.shortcode}
|
alt={emoji.body || emoji.shortcode}
|
||||||
src={mx.mxcUrlToHttp(emoji.url) ?? emoji.url}
|
src={mxcUrlToHttp(mx, emoji.url, useAuthentication) ?? emoji.url}
|
||||||
/>
|
/>
|
||||||
</StickerItem>
|
</StickerItem>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</EmojiGroup>
|
</EmojiGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CustomEmojiGroups = memo(
|
export const CustomEmojiGroups = memo(
|
||||||
({ mx, groups }: { mx: MatrixClient; groups: ImagePack[] }) => (
|
({ mx, groups, useAuthentication }: { mx: MatrixClient; groups: ImagePack[]; useAuthentication?: boolean }) => (
|
||||||
<>
|
<>
|
||||||
{groups.map((pack) => (
|
{groups.map((pack) => (
|
||||||
<EmojiGroup key={pack.id} id={pack.id} label={pack.displayName || 'Unknown'}>
|
<EmojiGroup key={pack.id} id={pack.id} label={pack.displayName || 'Unknown'}>
|
||||||
|
@ -547,7 +552,7 @@ export const CustomEmojiGroups = memo(
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={css.CustomEmojiImg}
|
className={css.CustomEmojiImg}
|
||||||
alt={image.body || image.shortcode}
|
alt={image.body || image.shortcode}
|
||||||
src={mx.mxcUrlToHttp(image.url) ?? image.url}
|
src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
|
||||||
/>
|
/>
|
||||||
</EmojiItem>
|
</EmojiItem>
|
||||||
))}
|
))}
|
||||||
|
@ -557,7 +562,7 @@ export const CustomEmojiGroups = memo(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export const StickerGroups = memo(({ mx, groups }: { mx: MatrixClient; groups: ImagePack[] }) => (
|
export const StickerGroups = memo(({ mx, groups, useAuthentication }: { mx: MatrixClient; groups: ImagePack[]; useAuthentication?: boolean }) => (
|
||||||
<>
|
<>
|
||||||
{groups.length === 0 && (
|
{groups.length === 0 && (
|
||||||
<Box
|
<Box
|
||||||
|
@ -590,7 +595,7 @@ export const StickerGroups = memo(({ mx, groups }: { mx: MatrixClient; groups: I
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={css.StickerImg}
|
className={css.StickerImg}
|
||||||
alt={image.body || image.shortcode}
|
alt={image.body || image.shortcode}
|
||||||
src={mx.mxcUrlToHttp(image.url) ?? image.url}
|
src={mxcUrlToHttp(mx, image.url, useAuthentication) ?? image.url}
|
||||||
/>
|
/>
|
||||||
</StickerItem>
|
</StickerItem>
|
||||||
))}
|
))}
|
||||||
|
@ -662,6 +667,7 @@ export function EmojiBoard({
|
||||||
|
|
||||||
const setActiveGroupId = useSetAtom(activeGroupIdAtom);
|
const setActiveGroupId = useSetAtom(activeGroupIdAtom);
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const emojiGroupLabels = useEmojiGroupLabels();
|
const emojiGroupLabels = useEmojiGroupLabels();
|
||||||
const emojiGroupIcons = useEmojiGroupIcons();
|
const emojiGroupIcons = useEmojiGroupIcons();
|
||||||
const imagePacks = useRelevantImagePacks(mx, usage, imagePackRooms);
|
const imagePacks = useRelevantImagePacks(mx, usage, imagePackRooms);
|
||||||
|
@ -755,14 +761,14 @@ export function EmojiBoard({
|
||||||
} else if (emojiInfo.type === EmojiType.CustomEmoji && emojiPreviewRef.current) {
|
} else if (emojiInfo.type === EmojiType.CustomEmoji && emojiPreviewRef.current) {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.className = css.CustomEmojiImg;
|
img.className = css.CustomEmojiImg;
|
||||||
img.setAttribute('src', mx.mxcUrlToHttp(emojiInfo.data) || emojiInfo.data);
|
img.setAttribute('src', mxcUrlToHttp(mx, emojiInfo.data, useAuthentication) || emojiInfo.data);
|
||||||
img.setAttribute('alt', emojiInfo.shortcode);
|
img.setAttribute('alt', emojiInfo.shortcode);
|
||||||
emojiPreviewRef.current.textContent = '';
|
emojiPreviewRef.current.textContent = '';
|
||||||
emojiPreviewRef.current.appendChild(img);
|
emojiPreviewRef.current.appendChild(img);
|
||||||
}
|
}
|
||||||
emojiPreviewTextRef.current.textContent = `:${emojiInfo.shortcode}:`;
|
emojiPreviewTextRef.current.textContent = `:${emojiInfo.shortcode}:`;
|
||||||
},
|
},
|
||||||
[mx]
|
[mx, useAuthentication]
|
||||||
);
|
);
|
||||||
|
|
||||||
const throttleEmojiHover = useThrottle(handleEmojiPreview, {
|
const throttleEmojiHover = useThrottle(handleEmojiPreview, {
|
||||||
|
@ -855,6 +861,7 @@ export function EmojiBoard({
|
||||||
usage={usage}
|
usage={usage}
|
||||||
packs={imagePacks}
|
packs={imagePacks}
|
||||||
onItemClick={handleScrollToGroup}
|
onItemClick={handleScrollToGroup}
|
||||||
|
useAuthentication={useAuthentication}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{emojiTab && (
|
{emojiTab && (
|
||||||
|
@ -916,13 +923,14 @@ export function EmojiBoard({
|
||||||
id={SEARCH_GROUP_ID}
|
id={SEARCH_GROUP_ID}
|
||||||
label={result.items.length ? 'Search Results' : 'No Results found'}
|
label={result.items.length ? 'Search Results' : 'No Results found'}
|
||||||
emojis={result.items}
|
emojis={result.items}
|
||||||
|
useAuthentication={useAuthentication}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{emojiTab && recentEmojis.length > 0 && (
|
{emojiTab && recentEmojis.length > 0 && (
|
||||||
<RecentEmojiGroup id={RECENT_GROUP_ID} label="Recent" emojis={recentEmojis} />
|
<RecentEmojiGroup id={RECENT_GROUP_ID} label="Recent" emojis={recentEmojis} />
|
||||||
)}
|
)}
|
||||||
{emojiTab && <CustomEmojiGroups mx={mx} groups={imagePacks} />}
|
{emojiTab && <CustomEmojiGroups mx={mx} groups={imagePacks} useAuthentication={useAuthentication} />}
|
||||||
{stickerTab && <StickerGroups mx={mx} groups={imagePacks} />}
|
{stickerTab && <StickerGroups mx={mx} groups={imagePacks} useAuthentication={useAuthentication} />}
|
||||||
{emojiTab && <NativeEmojiGroups groups={emojiGroups} labels={emojiGroupLabels} />}
|
{emojiTab && <NativeEmojiGroups groups={emojiGroups} labels={emojiGroupLabels} />}
|
||||||
</Box>
|
</Box>
|
||||||
</Scroll>
|
</Scroll>
|
||||||
|
|
|
@ -21,6 +21,7 @@ import * as css from './EventReaders.css';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { openProfileViewer } from '../../../client/action/navigation';
|
import { openProfileViewer } from '../../../client/action/navigation';
|
||||||
import { UserAvatar } from '../user-avatar';
|
import { UserAvatar } from '../user-avatar';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
export type EventReadersProps = {
|
export type EventReadersProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -30,6 +31,7 @@ export type EventReadersProps = {
|
||||||
export const EventReaders = as<'div', EventReadersProps>(
|
export const EventReaders = as<'div', EventReadersProps>(
|
||||||
({ className, room, eventId, requestClose, ...props }, ref) => {
|
({ className, room, eventId, requestClose, ...props }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const latestEventReaders = useRoomEventReaders(room, eventId);
|
const latestEventReaders = useRoomEventReaders(room, eventId);
|
||||||
|
|
||||||
const getName = (userId: string) =>
|
const getName = (userId: string) =>
|
||||||
|
@ -55,9 +57,10 @@ export const EventReaders = as<'div', EventReadersProps>(
|
||||||
<Box className={css.Content} direction="Column">
|
<Box className={css.Content} direction="Column">
|
||||||
{latestEventReaders.map((readerId) => {
|
{latestEventReaders.map((readerId) => {
|
||||||
const name = getName(readerId);
|
const name = getName(readerId);
|
||||||
const avatarUrl = room
|
const avatarMxcUrl = room
|
||||||
.getMember(readerId)
|
.getMember(readerId)
|
||||||
?.getAvatarUrl(mx.baseUrl, 100, 100, 'crop', undefined, false);
|
?.getMxcAvatarUrl();
|
||||||
|
const avatarUrl = avatarMxcUrl ? mx.mxcUrlToHttp(avatarMxcUrl, 100, 100, 'crop', undefined, false, useAuthentication) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds';
|
||||||
import * as css from './ImageViewer.css';
|
import * as css from './ImageViewer.css';
|
||||||
import { useZoom } from '../../hooks/useZoom';
|
import { useZoom } from '../../hooks/useZoom';
|
||||||
import { usePan } from '../../hooks/usePan';
|
import { usePan } from '../../hooks/usePan';
|
||||||
|
import { downloadMedia } from '../../utils/matrix';
|
||||||
|
|
||||||
export type ImageViewerProps = {
|
export type ImageViewerProps = {
|
||||||
alt: string;
|
alt: string;
|
||||||
|
@ -18,8 +19,9 @@ export const ImageViewer = as<'div', ImageViewerProps>(
|
||||||
const { zoom, zoomIn, zoomOut, setZoom } = useZoom(0.2);
|
const { zoom, zoomIn, zoomOut, setZoom } = useZoom(0.2);
|
||||||
const { pan, cursor, onMouseDown } = usePan(zoom !== 1);
|
const { pan, cursor, onMouseDown } = usePan(zoom !== 1);
|
||||||
|
|
||||||
const handleDownload = () => {
|
const handleDownload = async () => {
|
||||||
FileSaver.saveAs(src, alt);
|
const fileContent = await downloadMedia(src);
|
||||||
|
FileSaver.saveAs(fileContent, alt);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk';
|
||||||
import * as css from './Reaction.css';
|
import * as css from './Reaction.css';
|
||||||
import { getHexcodeForEmoji, getShortcodeFor, getEmojiUrl, isUsingTwemoji } from '../../plugins/emoji';
|
import { getHexcodeForEmoji, getShortcodeFor, getEmojiUrl, isUsingTwemoji } from '../../plugins/emoji';
|
||||||
import { getMemberDisplayName } from '../../utils/room';
|
import { getMemberDisplayName } from '../../utils/room';
|
||||||
import { eventWithShortcode, getMxIdLocalPart } from '../../utils/matrix';
|
import { eventWithShortcode, getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
|
|
||||||
export const Reaction = as<
|
export const Reaction = as<
|
||||||
'button',
|
'button',
|
||||||
|
@ -13,8 +13,9 @@ export const Reaction = as<
|
||||||
mx: MatrixClient;
|
mx: MatrixClient;
|
||||||
count: number;
|
count: number;
|
||||||
reaction: string;
|
reaction: string;
|
||||||
|
useAuthentication?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, mx, count, reaction, ...props }, ref) => (
|
>(({ className, mx, count, reaction, useAuthentication, ...props }, ref) => (
|
||||||
<Box
|
<Box
|
||||||
as="button"
|
as="button"
|
||||||
className={classNames(css.Reaction, className)}
|
className={classNames(css.Reaction, className)}
|
||||||
|
@ -28,7 +29,8 @@ export const Reaction = as<
|
||||||
{reaction.startsWith('mxc://') ? (
|
{reaction.startsWith('mxc://') ? (
|
||||||
<img
|
<img
|
||||||
className={css.ReactionImg}
|
className={css.ReactionImg}
|
||||||
src={mx.mxcUrlToHttp(reaction) ?? reaction}
|
src={mxcUrlToHttp(mx, reaction, useAuthentication) ?? reaction
|
||||||
|
}
|
||||||
alt={reaction}
|
alt={reaction}
|
||||||
/>
|
/>
|
||||||
) : isUsingTwemoji() ? (
|
) : isUsingTwemoji() ? (
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
|
||||||
import { Range } from 'react-range';
|
import { Range } from 'react-range';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { getFileSrcUrl } from './util';
|
|
||||||
import { IAudioInfo } from '../../../../types/matrix/common';
|
import { IAudioInfo } from '../../../../types/matrix/common';
|
||||||
import {
|
import {
|
||||||
PlayTimeCallback,
|
PlayTimeCallback,
|
||||||
|
@ -17,6 +16,13 @@ import {
|
||||||
} from '../../../hooks/media';
|
} from '../../../hooks/media';
|
||||||
import { useThrottle } from '../../../hooks/useThrottle';
|
import { useThrottle } from '../../../hooks/useThrottle';
|
||||||
import { secondsToMinutesAndSeconds } from '../../../utils/common';
|
import { secondsToMinutesAndSeconds } from '../../../utils/common';
|
||||||
|
import {
|
||||||
|
decryptFile,
|
||||||
|
downloadEncryptedMedia,
|
||||||
|
downloadMedia,
|
||||||
|
mxcUrlToHttp,
|
||||||
|
} from '../../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const PLAY_TIME_THROTTLE_OPS = {
|
const PLAY_TIME_THROTTLE_OPS = {
|
||||||
wait: 500,
|
wait: 500,
|
||||||
|
@ -44,12 +50,16 @@ export function AudioContent({
|
||||||
renderMediaControl,
|
renderMediaControl,
|
||||||
}: AudioContentProps) {
|
}: AudioContentProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const [srcState, loadSrc] = useAsyncCallback(
|
const [srcState, loadSrc] = useAsyncCallback(
|
||||||
useCallback(
|
useCallback(async () => {
|
||||||
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo),
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
[mx, url, mimeType, encInfo]
|
const fileContent = encInfo
|
||||||
)
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
|
||||||
|
: await downloadMedia(mediaUrl);
|
||||||
|
return URL.createObjectURL(fileContent);
|
||||||
|
}, [mx, url, useAuthentication, mimeType, encInfo])
|
||||||
);
|
);
|
||||||
|
|
||||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||||
|
|
|
@ -20,7 +20,6 @@ import FocusTrap from 'focus-trap-react';
|
||||||
import { IFileInfo } from '../../../../types/matrix/common';
|
import { IFileInfo } from '../../../../types/matrix/common';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { getFileSrcUrl, getSrcFile } from './util';
|
|
||||||
import { bytesToSize } from '../../../utils/common';
|
import { bytesToSize } from '../../../utils/common';
|
||||||
import {
|
import {
|
||||||
READABLE_EXT_TO_MIME_TYPE,
|
READABLE_EXT_TO_MIME_TYPE,
|
||||||
|
@ -30,6 +29,13 @@ import {
|
||||||
} from '../../../utils/mimeTypes';
|
} from '../../../utils/mimeTypes';
|
||||||
import * as css from './style.css';
|
import * as css from './style.css';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
|
import {
|
||||||
|
decryptFile,
|
||||||
|
downloadEncryptedMedia,
|
||||||
|
downloadMedia,
|
||||||
|
mxcUrlToHttp,
|
||||||
|
} from '../../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const renderErrorButton = (retry: () => void, text: string) => (
|
const renderErrorButton = (retry: () => void, text: string) => (
|
||||||
<TooltipProvider
|
<TooltipProvider
|
||||||
|
@ -75,21 +81,20 @@ type ReadTextFileProps = {
|
||||||
};
|
};
|
||||||
export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: ReadTextFileProps) {
|
export function ReadTextFile({ body, mimeType, url, encInfo, renderViewer }: ReadTextFileProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [textViewer, setTextViewer] = useState(false);
|
const [textViewer, setTextViewer] = useState(false);
|
||||||
|
|
||||||
const loadSrc = useCallback(
|
|
||||||
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo),
|
|
||||||
[mx, url, mimeType, encInfo]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [textState, loadText] = useAsyncCallback(
|
const [textState, loadText] = useAsyncCallback(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const src = await loadSrc();
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
const blob = await getSrcFile(src);
|
const fileContent = encInfo
|
||||||
const text = blob.text();
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
|
||||||
|
: await downloadMedia(mediaUrl);
|
||||||
|
|
||||||
|
const text = fileContent.text();
|
||||||
setTextViewer(true);
|
setTextViewer(true);
|
||||||
return text;
|
return text;
|
||||||
}, [loadSrc])
|
}, [mx, useAuthentication, mimeType, encInfo, url])
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -166,14 +171,18 @@ export type ReadPdfFileProps = {
|
||||||
};
|
};
|
||||||
export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: ReadPdfFileProps) {
|
export function ReadPdfFile({ body, mimeType, url, encInfo, renderViewer }: ReadPdfFileProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [pdfViewer, setPdfViewer] = useState(false);
|
const [pdfViewer, setPdfViewer] = useState(false);
|
||||||
|
|
||||||
const [pdfState, loadPdf] = useAsyncCallback(
|
const [pdfState, loadPdf] = useAsyncCallback(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const httpUrl = await getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo);
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
|
const fileContent = encInfo
|
||||||
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
|
||||||
|
: await downloadMedia(mediaUrl);
|
||||||
setPdfViewer(true);
|
setPdfViewer(true);
|
||||||
return httpUrl;
|
return URL.createObjectURL(fileContent);
|
||||||
}, [mx, url, mimeType, encInfo])
|
}, [mx, url, useAuthentication, mimeType, encInfo])
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -240,13 +249,19 @@ export type DownloadFileProps = {
|
||||||
};
|
};
|
||||||
export function DownloadFile({ body, mimeType, url, info, encInfo }: DownloadFileProps) {
|
export function DownloadFile({ body, mimeType, url, info, encInfo }: DownloadFileProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const [downloadState, download] = useAsyncCallback(
|
const [downloadState, download] = useAsyncCallback(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const httpUrl = await getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo);
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
FileSaver.saveAs(httpUrl, body);
|
const fileContent = encInfo
|
||||||
return httpUrl;
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) => decryptFile(encBuf, mimeType, encInfo))
|
||||||
}, [mx, url, mimeType, encInfo, body])
|
: await downloadMedia(mediaUrl);
|
||||||
|
|
||||||
|
const fileURL = URL.createObjectURL(fileContent);
|
||||||
|
FileSaver.saveAs(fileURL, body);
|
||||||
|
return fileURL;
|
||||||
|
}, [mx, url, useAuthentication, mimeType, encInfo, body])
|
||||||
);
|
);
|
||||||
|
|
||||||
return downloadState.status === AsyncStatus.Error ? (
|
return downloadState.status === AsyncStatus.Error ? (
|
||||||
|
|
|
@ -22,11 +22,12 @@ import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
|
||||||
import { IImageInfo, MATRIX_BLUR_HASH_PROPERTY_NAME } from '../../../../types/matrix/common';
|
import { IImageInfo, MATRIX_BLUR_HASH_PROPERTY_NAME } from '../../../../types/matrix/common';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { getFileSrcUrl } from './util';
|
|
||||||
import * as css from './style.css';
|
import * as css from './style.css';
|
||||||
import { bytesToSize } from '../../../utils/common';
|
import { bytesToSize } from '../../../utils/common';
|
||||||
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
|
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
|
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type RenderViewerProps = {
|
type RenderViewerProps = {
|
||||||
src: string;
|
src: string;
|
||||||
|
@ -69,6 +70,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const blurHash = info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
|
const blurHash = info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
|
||||||
|
|
||||||
const [load, setLoad] = useState(false);
|
const [load, setLoad] = useState(false);
|
||||||
|
@ -76,10 +78,16 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||||
const [viewer, setViewer] = useState(false);
|
const [viewer, setViewer] = useState(false);
|
||||||
|
|
||||||
const [srcState, loadSrc] = useAsyncCallback(
|
const [srcState, loadSrc] = useAsyncCallback(
|
||||||
useCallback(
|
useCallback(async () => {
|
||||||
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType || FALLBACK_MIMETYPE, encInfo),
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
[mx, url, mimeType, encInfo]
|
if (encInfo) {
|
||||||
)
|
const fileContent = await downloadEncryptedMedia(mediaUrl, (encBuf) =>
|
||||||
|
decryptFile(encBuf, mimeType ?? FALLBACK_MIMETYPE, encInfo)
|
||||||
|
);
|
||||||
|
return URL.createObjectURL(fileContent);
|
||||||
|
}
|
||||||
|
return mediaUrl;
|
||||||
|
}, [mx, url, useAuthentication, mimeType, encInfo])
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLoad = () => {
|
const handleLoad = () => {
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { ReactNode, useCallback, useEffect } from 'react';
|
||||||
import { IThumbnailContent } from '../../../../types/matrix/common';
|
import { IThumbnailContent } from '../../../../types/matrix/common';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { getFileSrcUrl } from './util';
|
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
|
||||||
|
|
||||||
export type ThumbnailContentProps = {
|
export type ThumbnailContentProps = {
|
||||||
info: IThumbnailContent;
|
info: IThumbnailContent;
|
||||||
|
@ -10,20 +12,27 @@ export type ThumbnailContentProps = {
|
||||||
};
|
};
|
||||||
export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
|
export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const [thumbSrcState, loadThumbSrc] = useAsyncCallback(
|
const [thumbSrcState, loadThumbSrc] = useAsyncCallback(
|
||||||
useCallback(() => {
|
useCallback(async () => {
|
||||||
const thumbInfo = info.thumbnail_info;
|
const thumbInfo = info.thumbnail_info;
|
||||||
const thumbMxcUrl = info.thumbnail_file?.url ?? info.thumbnail_url;
|
const thumbMxcUrl = info.thumbnail_file?.url ?? info.thumbnail_url;
|
||||||
|
const encInfo = info.thumbnail_file;
|
||||||
if (typeof thumbMxcUrl !== 'string' || typeof thumbInfo?.mimetype !== 'string') {
|
if (typeof thumbMxcUrl !== 'string' || typeof thumbInfo?.mimetype !== 'string') {
|
||||||
throw new Error('Failed to load thumbnail');
|
throw new Error('Failed to load thumbnail');
|
||||||
}
|
}
|
||||||
return getFileSrcUrl(
|
|
||||||
mx.mxcUrlToHttp(thumbMxcUrl) ?? '',
|
const mediaUrl = mxcUrlToHttp(mx, thumbMxcUrl, useAuthentication) ?? thumbMxcUrl;
|
||||||
thumbInfo.mimetype,
|
if (encInfo) {
|
||||||
info.thumbnail_file
|
const fileContent = await downloadEncryptedMedia(mediaUrl, (encBuf) =>
|
||||||
);
|
decryptFile(encBuf, thumbInfo.mimetype ?? FALLBACK_MIMETYPE, encInfo)
|
||||||
}, [mx, info])
|
);
|
||||||
|
return URL.createObjectURL(fileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mediaUrl;
|
||||||
|
}, [mx, info, useAuthentication])
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -22,9 +22,15 @@ import {
|
||||||
import * as css from './style.css';
|
import * as css from './style.css';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { getFileSrcUrl } from './util';
|
|
||||||
import { bytesToSize } from '../../../../util/common';
|
import { bytesToSize } from '../../../../util/common';
|
||||||
import { millisecondsToMinutesAndSeconds } from '../../../utils/common';
|
import { millisecondsToMinutesAndSeconds } from '../../../utils/common';
|
||||||
|
import {
|
||||||
|
decryptFile,
|
||||||
|
downloadEncryptedMedia,
|
||||||
|
downloadMedia,
|
||||||
|
mxcUrlToHttp,
|
||||||
|
} from '../../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type RenderVideoProps = {
|
type RenderVideoProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -61,16 +67,22 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const blurHash = info.thumbnail_info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
|
const blurHash = info.thumbnail_info?.[MATRIX_BLUR_HASH_PROPERTY_NAME];
|
||||||
|
|
||||||
const [load, setLoad] = useState(false);
|
const [load, setLoad] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
const [srcState, loadSrc] = useAsyncCallback(
|
const [srcState, loadSrc] = useAsyncCallback(
|
||||||
useCallback(
|
useCallback(async () => {
|
||||||
() => getFileSrcUrl(mx.mxcUrlToHttp(url) ?? '', mimeType, encInfo),
|
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
|
||||||
[mx, url, mimeType, encInfo]
|
const fileContent = encInfo
|
||||||
)
|
? await downloadEncryptedMedia(mediaUrl, (encBuf) =>
|
||||||
|
decryptFile(encBuf, mimeType, encInfo)
|
||||||
|
)
|
||||||
|
: await downloadMedia(mediaUrl);
|
||||||
|
return URL.createObjectURL(fileContent);
|
||||||
|
}, [mx, url, useAuthentication, mimeType, encInfo])
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLoad = () => {
|
const handleLoad = () => {
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
|
|
||||||
import { decryptFile } from '../../../utils/matrix';
|
|
||||||
|
|
||||||
export const getFileSrcUrl = async (
|
|
||||||
httpUrl: string,
|
|
||||||
mimeType: string,
|
|
||||||
encInfo?: EncryptedAttachmentInfo
|
|
||||||
): Promise<string> => {
|
|
||||||
if (encInfo) {
|
|
||||||
if (typeof httpUrl !== 'string') throw new Error('Malformed event');
|
|
||||||
const encRes = await fetch(httpUrl, { method: 'GET' });
|
|
||||||
const encData = await encRes.arrayBuffer();
|
|
||||||
const decryptedBlob = await decryptFile(encData, mimeType, encInfo);
|
|
||||||
return URL.createObjectURL(decryptedBlob);
|
|
||||||
}
|
|
||||||
return httpUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSrcFile = async (src: string): Promise<Blob> => {
|
|
||||||
const res = await fetch(src, { method: 'GET' });
|
|
||||||
const blob = await res.blob();
|
|
||||||
return blob;
|
|
||||||
};
|
|
|
@ -21,7 +21,7 @@ import classNames from 'classnames';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import * as css from './style.css';
|
import * as css from './style.css';
|
||||||
import { RoomAvatar } from '../room-avatar';
|
import { RoomAvatar } from '../room-avatar';
|
||||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
import { nameInitials } from '../../utils/common';
|
import { nameInitials } from '../../utils/common';
|
||||||
import { millify } from '../../plugins/millify';
|
import { millify } from '../../plugins/millify';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
@ -32,6 +32,7 @@ import { useJoinedRoomId } from '../../hooks/useJoinedRoomId';
|
||||||
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
|
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
|
||||||
import { getRoomAvatarUrl, getStateEvent } from '../../utils/room';
|
import { getRoomAvatarUrl, getStateEvent } from '../../utils/room';
|
||||||
import { useStateEventCallback } from '../../hooks/useStateEventCallback';
|
import { useStateEventCallback } from '../../hooks/useStateEventCallback';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type GridColumnCount = '1' | '2' | '3';
|
type GridColumnCount = '1' | '2' | '3';
|
||||||
const getGridColumnCount = (gridWidth: number): GridColumnCount => {
|
const getGridColumnCount = (gridWidth: number): GridColumnCount => {
|
||||||
|
@ -161,6 +162,7 @@ export const RoomCard = as<'div', RoomCardProps>(
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const joinedRoomId = useJoinedRoomId(allRooms, roomIdOrAlias);
|
const joinedRoomId = useJoinedRoomId(allRooms, roomIdOrAlias);
|
||||||
const joinedRoom = mx.getRoom(joinedRoomId);
|
const joinedRoom = mx.getRoom(joinedRoomId);
|
||||||
const [topicEvent, setTopicEvent] = useState(() =>
|
const [topicEvent, setTopicEvent] = useState(() =>
|
||||||
|
@ -171,8 +173,8 @@ export const RoomCard = as<'div', RoomCardProps>(
|
||||||
const fallbackTopic = roomIdOrAlias;
|
const fallbackTopic = roomIdOrAlias;
|
||||||
|
|
||||||
const avatar = joinedRoom
|
const avatar = joinedRoom
|
||||||
? getRoomAvatarUrl(mx, joinedRoom, 96)
|
? getRoomAvatarUrl(mx, joinedRoom, 96, useAuthentication)
|
||||||
: avatarUrl && mx.mxcUrlToHttp(avatarUrl, 96, 96, 'crop');
|
: avatarUrl && mxcUrlToHttp(mx, avatarUrl, useAuthentication, 96, 96, 'crop');
|
||||||
|
|
||||||
const roomName = joinedRoom?.name || name || fallbackName;
|
const roomName = joinedRoom?.name || name || fallbackName;
|
||||||
const roomTopic =
|
const roomTopic =
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { openInviteUser } from '../../../client/action/navigation';
|
||||||
import { IRoomCreateContent, Membership, StateEvent } from '../../../types/matrix/room';
|
import { IRoomCreateContent, Membership, StateEvent } from '../../../types/matrix/room';
|
||||||
import { getMemberDisplayName, getStateEvent } from '../../utils/room';
|
import { getMemberDisplayName, getStateEvent } from '../../utils/room';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||||
import { timeDayMonthYear, timeHourMinute } from '../../utils/time';
|
import { timeDayMonthYear, timeHourMinute } from '../../utils/time';
|
||||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||||
|
@ -14,6 +14,7 @@ import { RoomAvatar } from '../room-avatar';
|
||||||
import { nameInitials } from '../../utils/common';
|
import { nameInitials } from '../../utils/common';
|
||||||
import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
import { useRoomAvatar, useRoomName, useRoomTopic } from '../../hooks/useRoomMeta';
|
||||||
import { mDirectAtom } from '../../state/mDirectList';
|
import { mDirectAtom } from '../../state/mDirectList';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
export type RoomIntroProps = {
|
export type RoomIntroProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -21,6 +22,7 @@ export type RoomIntroProps = {
|
||||||
|
|
||||||
export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) => {
|
export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const { navigateRoom } = useRoomNavigate();
|
const { navigateRoom } = useRoomNavigate();
|
||||||
const mDirects = useAtomValue(mDirectAtom);
|
const mDirects = useAtomValue(mDirectAtom);
|
||||||
|
|
||||||
|
@ -28,7 +30,7 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
|
||||||
const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId));
|
const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId));
|
||||||
const name = useRoomName(room);
|
const name = useRoomName(room);
|
||||||
const topic = useRoomTopic(room);
|
const topic = useRoomTopic(room);
|
||||||
const avatarHttpUrl = avatarMxc ? mx.mxcUrlToHttp(avatarMxc) : undefined;
|
const avatarHttpUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication) : undefined;
|
||||||
|
|
||||||
const createContent = createEvent?.getContent<IRoomCreateContent>();
|
const createContent = createEvent?.getContent<IRoomCreateContent>();
|
||||||
const ts = createEvent?.getTs();
|
const ts = createEvent?.getTs();
|
||||||
|
|
|
@ -10,12 +10,15 @@ import {
|
||||||
} from '../../hooks/useIntersectionObserver';
|
} from '../../hooks/useIntersectionObserver';
|
||||||
import * as css from './UrlPreviewCard.css';
|
import * as css from './UrlPreviewCard.css';
|
||||||
import { tryDecodeURIComponent } from '../../utils/dom';
|
import { tryDecodeURIComponent } from '../../utils/dom';
|
||||||
|
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const linkStyles = { color: color.Success.Main };
|
const linkStyles = { color: color.Success.Main };
|
||||||
|
|
||||||
export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
|
export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
|
||||||
({ url, ts, ...props }, ref) => {
|
({ url, ts, ...props }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [previewStatus, loadPreview] = useAsyncCallback(
|
const [previewStatus, loadPreview] = useAsyncCallback(
|
||||||
useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx])
|
useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx])
|
||||||
);
|
);
|
||||||
|
@ -27,7 +30,7 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
|
||||||
if (previewStatus.status === AsyncStatus.Error) return null;
|
if (previewStatus.status === AsyncStatus.Error) return null;
|
||||||
|
|
||||||
const renderContent = (prev: IPreviewUrlResponse) => {
|
const renderContent = (prev: IPreviewUrlResponse) => {
|
||||||
const imgUrl = mx.mxcUrlToHttp(prev['og:image'] || '', 256, 256, 'scale', false);
|
const imgUrl = mxcUrlToHttp(mx, prev['og:image'] || '', useAuthentication, 256, 256, 'scale', false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -33,6 +33,8 @@ import { LeaveSpacePrompt } from '../../components/leave-space-prompt';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||||
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
||||||
|
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type LobbyMenuProps = {
|
type LobbyMenuProps = {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
|
@ -122,6 +124,7 @@ type LobbyHeaderProps = {
|
||||||
};
|
};
|
||||||
export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
|
export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const space = useSpace();
|
const space = useSpace();
|
||||||
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
|
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
|
||||||
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
||||||
|
@ -129,7 +132,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
|
||||||
|
|
||||||
const name = useRoomName(space);
|
const name = useRoomName(space);
|
||||||
const avatarMxc = useRoomAvatar(space);
|
const avatarMxc = useRoomAvatar(space);
|
||||||
const avatarUrl = avatarMxc ? mx.mxcUrlToHttp(avatarMxc, 96, 96, 'crop') ?? undefined : undefined;
|
const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined;
|
||||||
|
|
||||||
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||||
setMenuAnchor(evt.currentTarget.getBoundingClientRect());
|
setMenuAnchor(evt.currentTarget.getBoundingClientRect());
|
||||||
|
|
|
@ -11,15 +11,18 @@ import { RoomTopicViewer } from '../../components/room-topic-viewer';
|
||||||
import * as css from './LobbyHero.css';
|
import * as css from './LobbyHero.css';
|
||||||
import { PageHero } from '../../components/page';
|
import { PageHero } from '../../components/page';
|
||||||
import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard';
|
import { onEnterOrSpace, stopPropagation } from '../../utils/keyboard';
|
||||||
|
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
export function LobbyHero() {
|
export function LobbyHero() {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const space = useSpace();
|
const space = useSpace();
|
||||||
|
|
||||||
const name = useRoomName(space);
|
const name = useRoomName(space);
|
||||||
const topic = useRoomTopic(space);
|
const topic = useRoomTopic(space);
|
||||||
const avatarMxc = useRoomAvatar(space);
|
const avatarMxc = useRoomAvatar(space);
|
||||||
const avatarUrl = avatarMxc ? mx.mxcUrlToHttp(avatarMxc, 96, 96, 'crop') ?? undefined : undefined;
|
const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHero
|
<PageHero
|
||||||
|
|
|
@ -39,6 +39,8 @@ import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||||
import { ErrorCode } from '../../cs-errorcode';
|
import { ErrorCode } from '../../cs-errorcode';
|
||||||
import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../../utils/room';
|
import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../../utils/room';
|
||||||
import { ItemDraggableTarget, useDraggableItem } from './DnD';
|
import { ItemDraggableTarget, useDraggableItem } from './DnD';
|
||||||
|
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type RoomJoinButtonProps = {
|
type RoomJoinButtonProps = {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
|
@ -334,6 +336,7 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const { roomId, content } = item;
|
const { roomId, content } = item;
|
||||||
const room = getRoom(roomId);
|
const room = getRoom(roomId);
|
||||||
const targetRef = useRef<HTMLDivElement>(null);
|
const targetRef = useRef<HTMLDivElement>(null);
|
||||||
|
@ -364,7 +367,7 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
|
||||||
name={localSummary.name}
|
name={localSummary.name}
|
||||||
topic={localSummary.topic}
|
topic={localSummary.topic}
|
||||||
avatarUrl={
|
avatarUrl={
|
||||||
dm ? getDirectRoomAvatarUrl(mx, room, 96) : getRoomAvatarUrl(mx, room, 96)
|
dm ? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication) : getRoomAvatarUrl(mx, room, 96, useAuthentication)
|
||||||
}
|
}
|
||||||
memberCount={localSummary.memberCount}
|
memberCount={localSummary.memberCount}
|
||||||
suggested={content.suggested}
|
suggested={content.suggested}
|
||||||
|
@ -418,8 +421,8 @@ export const RoomItemCard = as<'div', RoomItemCardProps>(
|
||||||
topic={summaryState.data.topic}
|
topic={summaryState.data.topic}
|
||||||
avatarUrl={
|
avatarUrl={
|
||||||
summaryState.data?.avatar_url
|
summaryState.data?.avatar_url
|
||||||
? mx.mxcUrlToHttp(summaryState.data.avatar_url, 96, 96, 'crop') ??
|
? mxcUrlToHttp(mx, summaryState.data.avatar_url, useAuthentication, 96, 96, 'crop') ??
|
||||||
undefined
|
undefined
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
memberCount={summaryState.data.num_joined_members}
|
memberCount={summaryState.data.num_joined_members}
|
||||||
|
|
|
@ -35,6 +35,8 @@ import { ErrorCode } from '../../cs-errorcode';
|
||||||
import { useDraggableItem } from './DnD';
|
import { useDraggableItem } from './DnD';
|
||||||
import { openCreateRoom, openSpaceAddExisting } from '../../../client/action/navigation';
|
import { openCreateRoom, openSpaceAddExisting } from '../../../client/action/navigation';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
|
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
function SpaceProfileLoading() {
|
function SpaceProfileLoading() {
|
||||||
return (
|
return (
|
||||||
|
@ -408,6 +410,7 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>(
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const { roomId, content } = item;
|
const { roomId, content } = item;
|
||||||
const space = getRoom(roomId);
|
const space = getRoom(roomId);
|
||||||
const targetRef = useRef<HTMLDivElement>(null);
|
const targetRef = useRef<HTMLDivElement>(null);
|
||||||
|
@ -432,7 +435,7 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>(
|
||||||
<SpaceProfile
|
<SpaceProfile
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
name={localSummary.name}
|
name={localSummary.name}
|
||||||
avatarUrl={getRoomAvatarUrl(mx, space, 96)}
|
avatarUrl={getRoomAvatarUrl(mx, space, 96, useAuthentication)}
|
||||||
suggested={content.suggested}
|
suggested={content.suggested}
|
||||||
closed={closed}
|
closed={closed}
|
||||||
categoryId={categoryId}
|
categoryId={categoryId}
|
||||||
|
@ -469,8 +472,8 @@ export const SpaceItemCard = as<'div', SpaceItemCardProps>(
|
||||||
name={summaryState.data.name || summaryState.data.canonical_alias || roomId}
|
name={summaryState.data.name || summaryState.data.canonical_alias || roomId}
|
||||||
avatarUrl={
|
avatarUrl={
|
||||||
summaryState.data?.avatar_url
|
summaryState.data?.avatar_url
|
||||||
? mx.mxcUrlToHttp(summaryState.data.avatar_url, 96, 96, 'crop') ??
|
? mxcUrlToHttp(mx, summaryState.data.avatar_url, useAuthentication, 96, 96, 'crop') ??
|
||||||
undefined
|
undefined
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
suggested={content.suggested}
|
suggested={content.suggested}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
makeMentionCustomProps,
|
makeMentionCustomProps,
|
||||||
renderMatrixMention,
|
renderMatrixMention,
|
||||||
} from '../../plugins/react-custom-html-parser';
|
} from '../../plugins/react-custom-html-parser';
|
||||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
import { useMatrixEventRenderer } from '../../hooks/useMatrixEventRenderer';
|
import { useMatrixEventRenderer } from '../../hooks/useMatrixEventRenderer';
|
||||||
import { GetContentCallback, MessageEvent, StateEvent } from '../../../types/matrix/room';
|
import { GetContentCallback, MessageEvent, StateEvent } from '../../../types/matrix/room';
|
||||||
import {
|
import {
|
||||||
|
@ -38,6 +38,7 @@ import { SequenceCard } from '../../components/sequence-card';
|
||||||
import { UserAvatar } from '../../components/user-avatar';
|
import { UserAvatar } from '../../components/user-avatar';
|
||||||
import { useMentionClickHandler } from '../../hooks/useMentionClickHandler';
|
import { useMentionClickHandler } from '../../hooks/useMentionClickHandler';
|
||||||
import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler';
|
import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type SearchResultGroupProps = {
|
type SearchResultGroupProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -56,6 +57,7 @@ export function SearchResultGroup({
|
||||||
onOpen,
|
onOpen,
|
||||||
}: SearchResultGroupProps) {
|
}: SearchResultGroupProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const highlightRegex = useMemo(() => makeHighlightRegex(highlights), [highlights]);
|
const highlightRegex = useMemo(() => makeHighlightRegex(highlights), [highlights]);
|
||||||
|
|
||||||
const mentionClickHandler = useMentionClickHandler(room.roomId);
|
const mentionClickHandler = useMentionClickHandler(room.roomId);
|
||||||
|
@ -75,10 +77,11 @@ export function SearchResultGroup({
|
||||||
getReactCustomHtmlParser(mx, room.roomId, {
|
getReactCustomHtmlParser(mx, room.roomId, {
|
||||||
linkifyOpts,
|
linkifyOpts,
|
||||||
highlightRegex,
|
highlightRegex,
|
||||||
|
useAuthentication,
|
||||||
handleSpoilerClick: spoilerClickHandler,
|
handleSpoilerClick: spoilerClickHandler,
|
||||||
handleMentionClick: mentionClickHandler,
|
handleMentionClick: mentionClickHandler,
|
||||||
}),
|
}),
|
||||||
[mx, room, linkifyOpts, highlightRegex, mentionClickHandler, spoilerClickHandler]
|
[mx, room, linkifyOpts, highlightRegex, mentionClickHandler, spoilerClickHandler, useAuthentication]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderMatrixEvent = useMatrixEventRenderer<[IEventWithRoomId, string, GetContentCallback]>(
|
const renderMatrixEvent = useMatrixEventRenderer<[IEventWithRoomId, string, GetContentCallback]>(
|
||||||
|
@ -161,7 +164,7 @@ export function SearchResultGroup({
|
||||||
<Avatar size="200" radii="300">
|
<Avatar size="200" radii="300">
|
||||||
<RoomAvatar
|
<RoomAvatar
|
||||||
roomId={room.roomId}
|
roomId={room.roomId}
|
||||||
src={getRoomAvatarUrl(mx, room, 96)}
|
src={getRoomAvatarUrl(mx, room, 96, useAuthentication)}
|
||||||
alt={room.name}
|
alt={room.name}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<RoomIcon size="50" joinRule={room.getJoinRule() ?? JoinRule.Restricted} filled />
|
<RoomIcon size="50" joinRule={room.getJoinRule() ?? JoinRule.Restricted} filled />
|
||||||
|
@ -209,7 +212,7 @@ export function SearchResultGroup({
|
||||||
userId={event.sender}
|
userId={event.sender}
|
||||||
src={
|
src={
|
||||||
senderAvatarMxc
|
senderAvatarMxc
|
||||||
? mx.mxcUrlToHttp(senderAvatarMxc, 48, 48, 'crop') ?? undefined
|
? mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
alt={displayName}
|
alt={displayName}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
||||||
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
|
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
|
||||||
import { getViaServers } from '../../plugins/via-servers';
|
import { getViaServers } from '../../plugins/via-servers';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type RoomNavItemMenuProps = {
|
type RoomNavItemMenuProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -175,6 +176,7 @@ export function RoomNavItem({
|
||||||
linkPath,
|
linkPath,
|
||||||
}: RoomNavItemProps) {
|
}: RoomNavItemProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const { hoverProps } = useHover({ onHoverChange: setHover });
|
const { hoverProps } = useHover({ onHoverChange: setHover });
|
||||||
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
|
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
|
||||||
|
@ -217,7 +219,7 @@ export function RoomNavItem({
|
||||||
<RoomAvatar
|
<RoomAvatar
|
||||||
roomId={room.roomId}
|
roomId={room.roomId}
|
||||||
src={
|
src={
|
||||||
direct ? getDirectRoomAvatarUrl(mx, room, 96) : getRoomAvatarUrl(mx, room, 96)
|
direct ? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication) : getRoomAvatarUrl(mx, room, 96, useAuthentication)
|
||||||
}
|
}
|
||||||
alt={room.name}
|
alt={room.name}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
|
|
|
@ -55,6 +55,7 @@ import { ScrollTopContainer } from '../../components/scroll-top-container';
|
||||||
import { UserAvatar } from '../../components/user-avatar';
|
import { UserAvatar } from '../../components/user-avatar';
|
||||||
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
|
import { useRoomTypingMember } from '../../hooks/useRoomTypingMembers';
|
||||||
import { stopPropagation } from '../../utils/keyboard';
|
import { stopPropagation } from '../../utils/keyboard';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
export const MembershipFilters = {
|
export const MembershipFilters = {
|
||||||
filterJoined: (m: RoomMember) => m.membership === Membership.Join,
|
filterJoined: (m: RoomMember) => m.membership === Membership.Join,
|
||||||
|
@ -171,6 +172,7 @@ type MembersDrawerProps = {
|
||||||
};
|
};
|
||||||
export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
const scrollTopAnchorRef = useRef<HTMLDivElement>(null);
|
const scrollTopAnchorRef = useRef<HTMLDivElement>(null);
|
||||||
|
@ -426,9 +428,8 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
||||||
}}
|
}}
|
||||||
after={<Icon size="50" src={Icons.Cross} />}
|
after={<Icon size="50" src={Icons.Cross} />}
|
||||||
>
|
>
|
||||||
<Text size="B300">{`${result.items.length || 'No'} ${
|
<Text size="B300">{`${result.items.length || 'No'} ${result.items.length === 1 ? 'Result' : 'Results'
|
||||||
result.items.length === 1 ? 'Result' : 'Results'
|
}`}</Text>
|
||||||
}`}</Text>
|
|
||||||
</Chip>
|
</Chip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -483,14 +484,16 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
|
||||||
|
|
||||||
const member = tagOrMember;
|
const member = tagOrMember;
|
||||||
const name = getName(member);
|
const name = getName(member);
|
||||||
const avatarUrl = member.getAvatarUrl(
|
const avatarMxcUrl = member.getMxcAvatarUrl();
|
||||||
mx.baseUrl,
|
const avatarUrl = avatarMxcUrl ? mx.mxcUrlToHttp(
|
||||||
|
avatarMxcUrl,
|
||||||
100,
|
100,
|
||||||
100,
|
100,
|
||||||
'crop',
|
'crop',
|
||||||
undefined,
|
undefined,
|
||||||
false
|
false,
|
||||||
);
|
useAuthentication
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { useKeyDown } from '../../hooks/useKeyDown';
|
||||||
import { markAsRead } from '../../../client/action/notifications';
|
import { markAsRead } from '../../../client/action/notifications';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { useRoomMembers } from '../../hooks/useRoomMembers';
|
import { useRoomMembers } from '../../hooks/useRoomMembers';
|
||||||
import { editableActiveElement } from '../../utils/dom';
|
|
||||||
|
|
||||||
export function Room() {
|
export function Room() {
|
||||||
const { eventId } = useParams();
|
const { eventId } = useParams();
|
||||||
|
@ -29,7 +28,7 @@ export function Room() {
|
||||||
window,
|
window,
|
||||||
useCallback(
|
useCallback(
|
||||||
(evt) => {
|
(evt) => {
|
||||||
if (isKeyHotkey('escape', evt) && !editableActiveElement()) {
|
if (isKeyHotkey('escape', evt)) {
|
||||||
markAsRead(mx, room.roomId);
|
markAsRead(mx, room.roomId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -56,7 +56,7 @@ import {
|
||||||
} from '../../components/editor';
|
} from '../../components/editor';
|
||||||
import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board';
|
import { EmojiBoard, EmojiBoardTab } from '../../components/emoji-board';
|
||||||
import { UseStateProvider } from '../../components/UseStateProvider';
|
import { UseStateProvider } from '../../components/UseStateProvider';
|
||||||
import { TUploadContent, encryptFile, getImageInfo, getMxIdLocalPart } from '../../utils/matrix';
|
import { TUploadContent, encryptFile, getImageInfo, getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
import { useTypingStatusUpdater } from '../../hooks/useTypingStatusUpdater';
|
import { useTypingStatusUpdater } from '../../hooks/useTypingStatusUpdater';
|
||||||
import { useFilePicker } from '../../hooks/useFilePicker';
|
import { useFilePicker } from '../../hooks/useFilePicker';
|
||||||
import { useFilePasteHandler } from '../../hooks/useFilePasteHandler';
|
import { useFilePasteHandler } from '../../hooks/useFilePasteHandler';
|
||||||
|
@ -108,6 +108,7 @@ import { mobileOrTablet } from '../../utils/user-agent';
|
||||||
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
|
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
|
||||||
import { ReplyLayout, ThreadIndicator } from '../../components/message';
|
import { ReplyLayout, ThreadIndicator } from '../../components/message';
|
||||||
import { roomToParentsAtom } from '../../state/room/roomToParents';
|
import { roomToParentsAtom } from '../../state/room/roomToParents';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
interface RoomInputProps {
|
interface RoomInputProps {
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
|
@ -118,6 +119,7 @@ interface RoomInputProps {
|
||||||
export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||||
({ editor, fileDropContainerRef, roomId, room }, ref) => {
|
({ editor, fileDropContainerRef, roomId, room }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
const [enterForNewline] = useSetting(settingsAtom, 'enterForNewline');
|
||||||
const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
|
||||||
const commands = useCommands(mx, room);
|
const commands = useCommands(mx, room);
|
||||||
|
@ -368,7 +370,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStickerSelect = async (mxc: string, shortcode: string, label: string) => {
|
const handleStickerSelect = async (mxc: string, shortcode: string, label: string) => {
|
||||||
const stickerUrl = mx.mxcUrlToHttp(mxc);
|
const stickerUrl = mxcUrlToHttp(mx, mxc, useAuthentication);
|
||||||
if (!stickerUrl) return;
|
if (!stickerUrl) return;
|
||||||
|
|
||||||
const info = await getImageInfo(
|
const info = await getImageInfo(
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
EventTimelineSet,
|
EventTimelineSet,
|
||||||
EventTimelineSetHandlerMap,
|
EventTimelineSetHandlerMap,
|
||||||
IContent,
|
IContent,
|
||||||
IEncryptedFile,
|
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
Room,
|
Room,
|
||||||
|
@ -48,12 +47,7 @@ import {
|
||||||
import { isKeyHotkey } from 'is-hotkey';
|
import { isKeyHotkey } from 'is-hotkey';
|
||||||
import { Opts as LinkifyOpts } from 'linkifyjs';
|
import { Opts as LinkifyOpts } from 'linkifyjs';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { eventWithShortcode, factoryEventSentBy, getMxIdLocalPart } from '../../utils/matrix';
|
||||||
decryptFile,
|
|
||||||
eventWithShortcode,
|
|
||||||
factoryEventSentBy,
|
|
||||||
getMxIdLocalPart,
|
|
||||||
} from '../../utils/matrix';
|
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
import { useVirtualPaginator, ItemRange } from '../../hooks/useVirtualPaginator';
|
import { useVirtualPaginator, ItemRange } from '../../hooks/useVirtualPaginator';
|
||||||
import { useAlive } from '../../hooks/useAlive';
|
import { useAlive } from '../../hooks/useAlive';
|
||||||
|
@ -122,6 +116,7 @@ import { roomToUnreadAtom } from '../../state/room/roomToUnread';
|
||||||
import { useMentionClickHandler } from '../../hooks/useMentionClickHandler';
|
import { useMentionClickHandler } from '../../hooks/useMentionClickHandler';
|
||||||
import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler';
|
import { useSpoilerClickHandler } from '../../hooks/useSpoilerClickHandler';
|
||||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
|
const TimelineFloat = as<'div', css.TimelineFloatVariants>(
|
||||||
({ position, className, ...props }, ref) => (
|
({ position, className, ...props }, ref) => (
|
||||||
|
@ -219,18 +214,6 @@ export const getEventIdAbsoluteIndex = (
|
||||||
return baseIndex + eventIndex;
|
return baseIndex + eventIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const factoryGetFileSrcUrl =
|
|
||||||
(httpUrl: string, mimeType: string, encFile?: IEncryptedFile) => async (): Promise<string> => {
|
|
||||||
if (encFile) {
|
|
||||||
if (typeof httpUrl !== 'string') throw new Error('Malformed event');
|
|
||||||
const encRes = await fetch(httpUrl, { method: 'GET' });
|
|
||||||
const encData = await encRes.arrayBuffer();
|
|
||||||
const decryptedBlob = await decryptFile(encData, mimeType, encFile);
|
|
||||||
return URL.createObjectURL(decryptedBlob);
|
|
||||||
}
|
|
||||||
return httpUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RoomTimelineProps = {
|
type RoomTimelineProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
eventId?: string;
|
eventId?: string;
|
||||||
|
@ -437,6 +420,7 @@ const getRoomUnreadInfo = (room: Room, scrollTo = false) => {
|
||||||
|
|
||||||
export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) {
|
export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimelineProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const encryptedRoom = mx.isRoomEncrypted(room.roomId);
|
const encryptedRoom = mx.isRoomEncrypted(room.roomId);
|
||||||
const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
|
const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
|
||||||
const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
|
const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
|
||||||
|
@ -511,10 +495,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
() =>
|
() =>
|
||||||
getReactCustomHtmlParser(mx, room.roomId, {
|
getReactCustomHtmlParser(mx, room.roomId, {
|
||||||
linkifyOpts,
|
linkifyOpts,
|
||||||
|
useAuthentication,
|
||||||
handleSpoilerClick: spoilerClickHandler,
|
handleSpoilerClick: spoilerClickHandler,
|
||||||
handleMentionClick: mentionClickHandler,
|
handleMentionClick: mentionClickHandler,
|
||||||
}),
|
}),
|
||||||
[mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler]
|
[mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler, useAuthentication]
|
||||||
);
|
);
|
||||||
const parseMemberEvent = useMemberEventParser();
|
const parseMemberEvent = useMemberEventParser();
|
||||||
|
|
||||||
|
@ -726,6 +711,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||||
const editableEvtId = editableEvt?.getId();
|
const editableEvtId = editableEvt?.getId();
|
||||||
if (!editableEvtId) return;
|
if (!editableEvtId) return;
|
||||||
setEditId(editableEvtId);
|
setEditId(editableEvtId);
|
||||||
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[mx, room, editor]
|
[mx, room, editor]
|
||||||
|
|
|
@ -36,7 +36,7 @@ import { useSetSetting } from '../../state/hooks/settings';
|
||||||
import { settingsAtom } from '../../state/settings';
|
import { settingsAtom } from '../../state/settings';
|
||||||
import { useSpaceOptionally } from '../../hooks/useSpace';
|
import { useSpaceOptionally } from '../../hooks/useSpace';
|
||||||
import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils';
|
import { getHomeSearchPath, getSpaceSearchPath, withSearchParam } from '../../pages/pathUtils';
|
||||||
import { getCanonicalAliasOrRoomId, isRoomAlias } from '../../utils/matrix';
|
import { getCanonicalAliasOrRoomId, isRoomAlias, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
import { _SearchPathSearchParams } from '../../pages/paths';
|
import { _SearchPathSearchParams } from '../../pages/paths';
|
||||||
import * as css from './RoomViewHeader.css';
|
import * as css from './RoomViewHeader.css';
|
||||||
import { useRoomUnread } from '../../state/hooks/unread';
|
import { useRoomUnread } from '../../state/hooks/unread';
|
||||||
|
@ -53,6 +53,7 @@ import { stopPropagation } from '../../utils/keyboard';
|
||||||
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
import { getMatrixToRoom } from '../../plugins/matrix-to';
|
||||||
import { getViaServers } from '../../plugins/via-servers';
|
import { getViaServers } from '../../plugins/via-servers';
|
||||||
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type RoomMenuProps = {
|
type RoomMenuProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -174,6 +175,7 @@ const RoomMenu = forwardRef<HTMLDivElement, RoomMenuProps>(({ room, requestClose
|
||||||
export function RoomViewHeader() {
|
export function RoomViewHeader() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const screenSize = useScreenSizeContext();
|
const screenSize = useScreenSizeContext();
|
||||||
const room = useRoom();
|
const room = useRoom();
|
||||||
const space = useSpaceOptionally();
|
const space = useSpaceOptionally();
|
||||||
|
@ -185,7 +187,7 @@ export function RoomViewHeader() {
|
||||||
const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId));
|
const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId));
|
||||||
const name = useRoomName(room);
|
const name = useRoomName(room);
|
||||||
const topic = useRoomTopic(room);
|
const topic = useRoomTopic(room);
|
||||||
const avatarUrl = avatarMxc ? mx.mxcUrlToHttp(avatarMxc, 96, 96, 'crop') ?? undefined : undefined;
|
const avatarUrl = avatarMxc ? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined : undefined;
|
||||||
|
|
||||||
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
|
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ import {
|
||||||
getMemberAvatarMxc,
|
getMemberAvatarMxc,
|
||||||
getMemberDisplayName,
|
getMemberDisplayName,
|
||||||
} from '../../../utils/room';
|
} from '../../../utils/room';
|
||||||
import { getCanonicalAliasOrRoomId, getMxIdLocalPart, isRoomAlias } from '../../../utils/matrix';
|
import { getCanonicalAliasOrRoomId, getMxIdLocalPart, isRoomAlias, mxcUrlToHttp } from '../../../utils/matrix';
|
||||||
import { MessageLayout, MessageSpacing } from '../../../state/settings';
|
import { MessageLayout, MessageSpacing } from '../../../state/settings';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
|
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
|
||||||
|
@ -67,6 +67,7 @@ import { copyToClipboard } from '../../../utils/dom';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
|
import { getMatrixToRoomEvent } from '../../../plugins/matrix-to';
|
||||||
import { getViaServers } from '../../../plugins/via-servers';
|
import { getViaServers } from '../../../plugins/via-servers';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
|
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
|
||||||
|
|
||||||
|
@ -234,9 +235,9 @@ export const MessageSourceCodeItem = as<
|
||||||
const getContent = (evt: MatrixEvent) =>
|
const getContent = (evt: MatrixEvent) =>
|
||||||
evt.isEncrypted()
|
evt.isEncrypted()
|
||||||
? {
|
? {
|
||||||
[`<== DECRYPTED_EVENT ==>`]: evt.getEffectiveEvent(),
|
[`<== DECRYPTED_EVENT ==>`]: evt.getEffectiveEvent(),
|
||||||
[`<== ORIGINAL_EVENT ==>`]: evt.event,
|
[`<== ORIGINAL_EVENT ==>`]: evt.event,
|
||||||
}
|
}
|
||||||
: evt.event;
|
: evt.event;
|
||||||
|
|
||||||
const getText = (): string => {
|
const getText = (): string => {
|
||||||
|
@ -650,6 +651,7 @@ export const Message = as<'div', MessageProps>(
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const senderId = mEvent.getSender() ?? '';
|
const senderId = mEvent.getSender() ?? '';
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const { hoverProps } = useHover({ onHoverChange: setHover });
|
const { hoverProps } = useHover({ onHoverChange: setHover });
|
||||||
|
@ -709,7 +711,7 @@ export const Message = as<'div', MessageProps>(
|
||||||
userId={senderId}
|
userId={senderId}
|
||||||
src={
|
src={
|
||||||
senderAvatarMxc
|
senderAvatarMxc
|
||||||
? mx.mxcUrlToHttp(senderAvatarMxc, 48, 48, 'crop') ?? undefined
|
? mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
alt={senderDisplayName}
|
alt={senderDisplayName}
|
||||||
|
@ -950,26 +952,26 @@ export const Message = as<'div', MessageProps>(
|
||||||
</Box>
|
</Box>
|
||||||
{((!mEvent.isRedacted() && canDelete) ||
|
{((!mEvent.isRedacted() && canDelete) ||
|
||||||
mEvent.getSender() !== mx.getUserId()) && (
|
mEvent.getSender() !== mx.getUserId()) && (
|
||||||
<>
|
<>
|
||||||
<Line size="300" />
|
<Line size="300" />
|
||||||
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
|
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
|
||||||
{!mEvent.isRedacted() && canDelete && (
|
{!mEvent.isRedacted() && canDelete && (
|
||||||
<MessageDeleteItem
|
<MessageDeleteItem
|
||||||
room={room}
|
room={room}
|
||||||
mEvent={mEvent}
|
mEvent={mEvent}
|
||||||
onClose={closeMenu}
|
onClose={closeMenu}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{mEvent.getSender() !== mx.getUserId() && (
|
{mEvent.getSender() !== mx.getUserId() && (
|
||||||
<MessageReportItem
|
<MessageReportItem
|
||||||
room={room}
|
room={room}
|
||||||
mEvent={mEvent}
|
mEvent={mEvent}
|
||||||
onClose={closeMenu}
|
onClose={closeMenu}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
}
|
}
|
||||||
|
@ -1093,26 +1095,26 @@ export const Event = as<'div', EventProps>(
|
||||||
</Box>
|
</Box>
|
||||||
{((!mEvent.isRedacted() && canDelete && !stateEvent) ||
|
{((!mEvent.isRedacted() && canDelete && !stateEvent) ||
|
||||||
(mEvent.getSender() !== mx.getUserId() && !stateEvent)) && (
|
(mEvent.getSender() !== mx.getUserId() && !stateEvent)) && (
|
||||||
<>
|
<>
|
||||||
<Line size="300" />
|
<Line size="300" />
|
||||||
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
|
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
|
||||||
{!mEvent.isRedacted() && canDelete && (
|
{!mEvent.isRedacted() && canDelete && (
|
||||||
<MessageDeleteItem
|
<MessageDeleteItem
|
||||||
room={room}
|
room={room}
|
||||||
mEvent={mEvent}
|
mEvent={mEvent}
|
||||||
onClose={closeMenu}
|
onClose={closeMenu}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{mEvent.getSender() !== mx.getUserId() && (
|
{mEvent.getSender() !== mx.getUserId() && (
|
||||||
<MessageReportItem
|
<MessageReportItem
|
||||||
room={room}
|
room={room}
|
||||||
mEvent={mEvent}
|
mEvent={mEvent}
|
||||||
onClose={closeMenu}
|
onClose={closeMenu}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</FocusTrap>
|
</FocusTrap>
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { useRelations } from '../../../hooks/useRelations';
|
||||||
import * as css from './styles.css';
|
import * as css from './styles.css';
|
||||||
import { ReactionViewer } from '../reaction-viewer';
|
import { ReactionViewer } from '../reaction-viewer';
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
export type ReactionsProps = {
|
export type ReactionsProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -33,6 +34,7 @@ export type ReactionsProps = {
|
||||||
export const Reactions = as<'div', ReactionsProps>(
|
export const Reactions = as<'div', ReactionsProps>(
|
||||||
({ className, room, relations, mEventId, canSendReaction, onReactionToggle, ...props }, ref) => {
|
({ className, room, relations, mEventId, canSendReaction, onReactionToggle, ...props }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [viewer, setViewer] = useState<boolean | string>(false);
|
const [viewer, setViewer] = useState<boolean | string>(false);
|
||||||
const myUserId = mx.getUserId();
|
const myUserId = mx.getUserId();
|
||||||
const reactions = useRelations(
|
const reactions = useRelations(
|
||||||
|
@ -86,6 +88,7 @@ export const Reactions = as<'div', ReactionsProps>(
|
||||||
onClick={canSendReaction ? () => onReactionToggle(mEventId, key) : undefined}
|
onClick={canSendReaction ? () => onReactionToggle(mEventId, key) : undefined}
|
||||||
onContextMenu={handleViewReaction}
|
onContextMenu={handleViewReaction}
|
||||||
aria-disabled={!canSendReaction}
|
aria-disabled={!canSendReaction}
|
||||||
|
useAuthentication={useAuthentication}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { useRelations } from '../../../hooks/useRelations';
|
||||||
import { Reaction } from '../../../components/message';
|
import { Reaction } from '../../../components/message';
|
||||||
import { getHexcodeForEmoji, getShortcodeFor } from '../../../plugins/emoji';
|
import { getHexcodeForEmoji, getShortcodeFor } from '../../../plugins/emoji';
|
||||||
import { UserAvatar } from '../../../components/user-avatar';
|
import { UserAvatar } from '../../../components/user-avatar';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
export type ReactionViewerProps = {
|
export type ReactionViewerProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -35,6 +36,7 @@ export type ReactionViewerProps = {
|
||||||
export const ReactionViewer = as<'div', ReactionViewerProps>(
|
export const ReactionViewer = as<'div', ReactionViewerProps>(
|
||||||
({ className, room, initialKey, relations, requestClose, ...props }, ref) => {
|
({ className, room, initialKey, relations, requestClose, ...props }, ref) => {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const reactions = useRelations(
|
const reactions = useRelations(
|
||||||
relations,
|
relations,
|
||||||
useCallback((rel) => [...(rel.getSortedAnnotationsByKey() ?? [])], [])
|
useCallback((rel) => [...(rel.getSortedAnnotationsByKey() ?? [])], [])
|
||||||
|
@ -81,6 +83,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
|
||||||
count={evts.size}
|
count={evts.size}
|
||||||
aria-selected={key === selectedKey}
|
aria-selected={key === selectedKey}
|
||||||
onClick={() => setSelectedKey(key)}
|
onClick={() => setSelectedKey(key)}
|
||||||
|
useAuthentication={useAuthentication}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -107,14 +110,16 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
|
||||||
const member = room.getMember(senderId);
|
const member = room.getMember(senderId);
|
||||||
const name = (member ? getName(member) : getMxIdLocalPart(senderId)) ?? senderId;
|
const name = (member ? getName(member) : getMxIdLocalPart(senderId)) ?? senderId;
|
||||||
|
|
||||||
const avatarUrl = member?.getAvatarUrl(
|
const avatarMxcUrl = member?.getMxcAvatarUrl();
|
||||||
mx.baseUrl,
|
const avatarUrl = avatarMxcUrl ? mx.mxcUrlToHttp(
|
||||||
|
avatarMxcUrl,
|
||||||
100,
|
100,
|
||||||
100,
|
100,
|
||||||
'crop',
|
'crop',
|
||||||
undefined,
|
undefined,
|
||||||
false
|
false,
|
||||||
);
|
useAuthentication
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|
11
src/app/hooks/useMediaAuthentication.ts
Normal file
11
src/app/hooks/useMediaAuthentication.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { useSpecVersions } from './useSpecVersions';
|
||||||
|
|
||||||
|
export const useMediaAuthentication = (): boolean => {
|
||||||
|
const { versions, unstable_features: unstableFeatures } = useSpecVersions();
|
||||||
|
|
||||||
|
// Media authentication is introduced in spec version 1.11
|
||||||
|
const authenticatedMedia =
|
||||||
|
unstableFeatures?.['org.matrix.msc3916.stable'] || versions.includes('v1.11');
|
||||||
|
|
||||||
|
return authenticatedMedia;
|
||||||
|
};
|
|
@ -14,6 +14,7 @@ export const useMentionClickHandler = (roomId: string): ReactEventHandler<HTMLEl
|
||||||
|
|
||||||
const handleClick: ReactEventHandler<HTMLElement> = useCallback(
|
const handleClick: ReactEventHandler<HTMLElement> = useCallback(
|
||||||
(evt) => {
|
(evt) => {
|
||||||
|
evt.stopPropagation();
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const target = evt.currentTarget;
|
const target = evt.currentTarget;
|
||||||
const mentionId = target.getAttribute('data-mention-id');
|
const mentionId = target.getAttribute('data-mention-id');
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { ILoginFlow, IPasswordFlow, ISSOFlow, LoginFlow } from 'matrix-js-sdk/lib/@types/auth';
|
import { ILoginFlow, IPasswordFlow, ISSOFlow, LoginFlow } from 'matrix-js-sdk/lib/@types/auth';
|
||||||
import { WithRequiredProp } from '../../types/utils';
|
|
||||||
|
|
||||||
export type Required_SSOFlow = WithRequiredProp<ISSOFlow, 'identity_providers'>;
|
export const getSSOFlow = (loginFlows: LoginFlow[]): ISSOFlow | undefined =>
|
||||||
export const getSSOFlow = (loginFlows: LoginFlow[]): Required_SSOFlow | undefined =>
|
loginFlows.find((flow) => flow.type === 'm.login.sso' || flow.type === 'm.login.cas') as
|
||||||
loginFlows.find(
|
| ISSOFlow
|
||||||
(flow) =>
|
| undefined;
|
||||||
(flow.type === 'm.login.sso' || flow.type === 'm.login.cas') &&
|
|
||||||
'identity_providers' in flow &&
|
|
||||||
Array.isArray(flow.identity_providers) &&
|
|
||||||
flow.identity_providers.length > 0
|
|
||||||
) as Required_SSOFlow | undefined;
|
|
||||||
|
|
||||||
export const getPasswordFlow = (loginFlows: LoginFlow[]): IPasswordFlow | undefined =>
|
export const getPasswordFlow = (loginFlows: LoginFlow[]): IPasswordFlow | undefined =>
|
||||||
loginFlows.find((flow) => flow.type === 'm.login.password') as IPasswordFlow;
|
loginFlows.find((flow) => flow.type === 'm.login.password') as IPasswordFlow;
|
||||||
|
@ -22,7 +16,7 @@ export const getTokenFlow = (loginFlows: LoginFlow[]): LoginFlow | undefined =>
|
||||||
export type ParsedLoginFlows = {
|
export type ParsedLoginFlows = {
|
||||||
password?: LoginFlow;
|
password?: LoginFlow;
|
||||||
token?: LoginFlow;
|
token?: LoginFlow;
|
||||||
sso?: Required_SSOFlow;
|
sso?: ISSOFlow;
|
||||||
};
|
};
|
||||||
export const useParsedLoginFlows = (loginFlows: LoginFlow[]) => {
|
export const useParsedLoginFlows = (loginFlows: LoginFlow[]) => {
|
||||||
const parsedFlow: ParsedLoginFlows = useMemo<ParsedLoginFlows>(
|
const parsedFlow: ParsedLoginFlows = useMemo<ParsedLoginFlows>(
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import React, {
|
import React, { useState, useMemo, useReducer, useEffect } from 'react';
|
||||||
useState, useMemo, useReducer, useEffect,
|
|
||||||
} from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import './ImagePack.scss';
|
import './ImagePack.scss';
|
||||||
|
|
||||||
|
@ -19,41 +17,41 @@ import ImagePackProfile from './ImagePackProfile';
|
||||||
import ImagePackItem from './ImagePackItem';
|
import ImagePackItem from './ImagePackItem';
|
||||||
import ImagePackUpload from './ImagePackUpload';
|
import ImagePackUpload from './ImagePackUpload';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const renameImagePackItem = (shortcode) => new Promise((resolve) => {
|
const renameImagePackItem = (shortcode) =>
|
||||||
let isCompleted = false;
|
new Promise((resolve) => {
|
||||||
|
let isCompleted = false;
|
||||||
|
|
||||||
openReusableDialog(
|
openReusableDialog(
|
||||||
<Text variant="s1" weight="medium">Rename</Text>,
|
<Text variant="s1" weight="medium">
|
||||||
(requestClose) => (
|
Rename
|
||||||
<div style={{ padding: 'var(--sp-normal)' }}>
|
</Text>,
|
||||||
<form
|
(requestClose) => (
|
||||||
onSubmit={(e) => {
|
<div style={{ padding: 'var(--sp-normal)' }}>
|
||||||
e.preventDefault();
|
<form
|
||||||
const sc = e.target.shortcode.value;
|
onSubmit={(e) => {
|
||||||
if (sc.trim() === '') return;
|
e.preventDefault();
|
||||||
isCompleted = true;
|
const sc = e.target.shortcode.value;
|
||||||
resolve(sc.trim());
|
if (sc.trim() === '') return;
|
||||||
requestClose();
|
isCompleted = true;
|
||||||
}}
|
resolve(sc.trim());
|
||||||
>
|
requestClose();
|
||||||
<Input
|
}}
|
||||||
value={shortcode}
|
>
|
||||||
name="shortcode"
|
<Input value={shortcode} name="shortcode" label="Shortcode" autoFocus required />
|
||||||
label="Shortcode"
|
<div style={{ height: 'var(--sp-normal)' }} />
|
||||||
autoFocus
|
<Button variant="primary" type="submit">
|
||||||
required
|
Rename
|
||||||
/>
|
</Button>
|
||||||
<div style={{ height: 'var(--sp-normal)' }} />
|
</form>
|
||||||
<Button variant="primary" type="submit">Rename</Button>
|
</div>
|
||||||
</form>
|
),
|
||||||
</div>
|
() => {
|
||||||
),
|
if (!isCompleted) resolve(null);
|
||||||
() => {
|
}
|
||||||
if (!isCompleted) resolve(null);
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function getUsage(usage) {
|
function getUsage(usage) {
|
||||||
if (usage.includes('emoticon') && usage.includes('sticker')) return 'both';
|
if (usage.includes('emoticon') && usage.includes('sticker')) return 'both';
|
||||||
|
@ -79,7 +77,7 @@ function useRoomImagePack(roomId, stateKey) {
|
||||||
|
|
||||||
const pack = useMemo(() => {
|
const pack = useMemo(() => {
|
||||||
const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
|
const packEvent = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
|
||||||
return ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent())
|
return ImagePackBuilder.parsePack(packEvent.getId(), packEvent.getContent());
|
||||||
}, [room, stateKey]);
|
}, [room, stateKey]);
|
||||||
|
|
||||||
const sendPackContent = (content) => {
|
const sendPackContent = (content) => {
|
||||||
|
@ -96,10 +94,13 @@ function useUserImagePack() {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const pack = useMemo(() => {
|
const pack = useMemo(() => {
|
||||||
const packEvent = mx.getAccountData('im.ponies.user_emotes');
|
const packEvent = mx.getAccountData('im.ponies.user_emotes');
|
||||||
return ImagePackBuilder.parsePack(mx.getUserId(), packEvent?.getContent() ?? {
|
return ImagePackBuilder.parsePack(
|
||||||
pack: { display_name: 'Personal' },
|
mx.getUserId(),
|
||||||
images: {},
|
packEvent?.getContent() ?? {
|
||||||
})
|
pack: { display_name: 'Personal' },
|
||||||
|
images: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
}, [mx]);
|
}, [mx]);
|
||||||
|
|
||||||
const sendPackContent = (content) => {
|
const sendPackContent = (content) => {
|
||||||
|
@ -119,10 +120,7 @@ function useImagePackHandles(pack, sendPackContent) {
|
||||||
if (typeof key !== 'string') return undefined;
|
if (typeof key !== 'string') return undefined;
|
||||||
let newKey = key?.replace(/\s/g, '_');
|
let newKey = key?.replace(/\s/g, '_');
|
||||||
if (pack.getImages().get(newKey)) {
|
if (pack.getImages().get(newKey)) {
|
||||||
newKey = suffixRename(
|
newKey = suffixRename(newKey, (suffixedKey) => pack.getImages().get(suffixedKey));
|
||||||
newKey,
|
|
||||||
(suffixedKey) => pack.getImages().get(suffixedKey),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return newKey;
|
return newKey;
|
||||||
};
|
};
|
||||||
|
@ -163,7 +161,7 @@ function useImagePackHandles(pack, sendPackContent) {
|
||||||
'Delete',
|
'Delete',
|
||||||
`Are you sure that you want to delete "${key}"?`,
|
`Are you sure that you want to delete "${key}"?`,
|
||||||
'Delete',
|
'Delete',
|
||||||
'danger',
|
'danger'
|
||||||
);
|
);
|
||||||
if (!isConfirmed) return;
|
if (!isConfirmed) return;
|
||||||
pack.removeImage(key);
|
pack.removeImage(key);
|
||||||
|
@ -226,6 +224,7 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
const room = mx.getRoom(roomId);
|
const room = mx.getRoom(roomId);
|
||||||
const [viewMore, setViewMore] = useState(false);
|
const [viewMore, setViewMore] = useState(false);
|
||||||
const [isGlobal, setIsGlobal] = useState(isGlobalPack(mx, roomId, stateKey));
|
const [isGlobal, setIsGlobal] = useState(isGlobalPack(mx, roomId, stateKey));
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey);
|
const { pack, sendPackContent } = useRoomImagePack(roomId, stateKey);
|
||||||
|
|
||||||
|
@ -253,7 +252,7 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
'Delete Pack',
|
'Delete Pack',
|
||||||
`Are you sure that you want to delete "${pack.displayName}"?`,
|
`Are you sure that you want to delete "${pack.displayName}"?`,
|
||||||
'Delete',
|
'Delete',
|
||||||
'danger',
|
'danger'
|
||||||
);
|
);
|
||||||
if (!isConfirmed) return;
|
if (!isConfirmed) return;
|
||||||
handlePackDelete(stateKey);
|
handlePackDelete(stateKey);
|
||||||
|
@ -264,7 +263,19 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
return (
|
return (
|
||||||
<div className="image-pack">
|
<div className="image-pack">
|
||||||
<ImagePackProfile
|
<ImagePackProfile
|
||||||
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
|
avatarUrl={
|
||||||
|
pack.avatarUrl
|
||||||
|
? mx.mxcUrlToHttp(
|
||||||
|
pack.avatarUrl,
|
||||||
|
42,
|
||||||
|
42,
|
||||||
|
'crop',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
displayName={pack.displayName ?? 'Unknown'}
|
displayName={pack.displayName ?? 'Unknown'}
|
||||||
attribution={pack.attribution}
|
attribution={pack.attribution}
|
||||||
usage={getUsage(pack.usage)}
|
usage={getUsage(pack.usage)}
|
||||||
|
@ -272,10 +283,8 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
onAvatarChange={canChange ? handleAvatarChange : null}
|
onAvatarChange={canChange ? handleAvatarChange : null}
|
||||||
onEditProfile={canChange ? handleEditProfile : null}
|
onEditProfile={canChange ? handleEditProfile : null}
|
||||||
/>
|
/>
|
||||||
{ canChange && (
|
{canChange && <ImagePackUpload onUpload={handleAddItem} />}
|
||||||
<ImagePackUpload onUpload={handleAddItem} />
|
{images.length === 0 ? null : (
|
||||||
)}
|
|
||||||
{ images.length === 0 ? null : (
|
|
||||||
<div>
|
<div>
|
||||||
<div className="image-pack__header">
|
<div className="image-pack__header">
|
||||||
<Text variant="b3">Image</Text>
|
<Text variant="b3">Image</Text>
|
||||||
|
@ -285,7 +294,15 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
{images.map(([shortcode, image]) => (
|
{images.map(([shortcode, image]) => (
|
||||||
<ImagePackItem
|
<ImagePackItem
|
||||||
key={shortcode}
|
key={shortcode}
|
||||||
url={mx.mxcUrlToHttp(image.mxc)}
|
url={mx.mxcUrlToHttp(
|
||||||
|
image.mxc,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)}
|
||||||
shortcode={shortcode}
|
shortcode={shortcode}
|
||||||
usage={getUsage(image.usage)}
|
usage={getUsage(image.usage)}
|
||||||
onUsageChange={canChange ? handleUsageItem : undefined}
|
onUsageChange={canChange ? handleUsageItem : undefined}
|
||||||
|
@ -299,14 +316,14 @@ function ImagePack({ roomId, stateKey, handlePackDelete }) {
|
||||||
<div className="image-pack__footer">
|
<div className="image-pack__footer">
|
||||||
{pack.images.size > 2 && (
|
{pack.images.size > 2 && (
|
||||||
<Button onClick={() => setViewMore(!viewMore)}>
|
<Button onClick={() => setViewMore(!viewMore)}>
|
||||||
{
|
{viewMore ? 'View less' : `View ${pack.images.size - 2} more`}
|
||||||
viewMore
|
</Button>
|
||||||
? 'View less'
|
)}
|
||||||
: `View ${pack.images.size - 2} more`
|
{handlePackDelete && (
|
||||||
}
|
<Button variant="danger" onClick={handleDeletePack}>
|
||||||
|
Delete Pack
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{ handlePackDelete && <Button variant="danger" onClick={handleDeletePack}>Delete Pack</Button>}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="image-pack__global">
|
<div className="image-pack__global">
|
||||||
|
@ -332,6 +349,7 @@ ImagePack.propTypes = {
|
||||||
function ImagePackUser() {
|
function ImagePackUser() {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const [viewMore, setViewMore] = useState(false);
|
const [viewMore, setViewMore] = useState(false);
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const { pack, sendPackContent } = useUserImagePack();
|
const { pack, sendPackContent } = useUserImagePack();
|
||||||
|
|
||||||
|
@ -350,7 +368,19 @@ function ImagePackUser() {
|
||||||
return (
|
return (
|
||||||
<div className="image-pack">
|
<div className="image-pack">
|
||||||
<ImagePackProfile
|
<ImagePackProfile
|
||||||
avatarUrl={pack.avatarUrl ? mx.mxcUrlToHttp(pack.avatarUrl, 42, 42, 'crop') : null}
|
avatarUrl={
|
||||||
|
pack.avatarUrl
|
||||||
|
? mx.mxcUrlToHttp(
|
||||||
|
pack.avatarUrl,
|
||||||
|
42,
|
||||||
|
42,
|
||||||
|
'crop',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
displayName={pack.displayName ?? 'Personal'}
|
displayName={pack.displayName ?? 'Personal'}
|
||||||
attribution={pack.attribution}
|
attribution={pack.attribution}
|
||||||
usage={getUsage(pack.usage)}
|
usage={getUsage(pack.usage)}
|
||||||
|
@ -359,7 +389,7 @@ function ImagePackUser() {
|
||||||
onEditProfile={handleEditProfile}
|
onEditProfile={handleEditProfile}
|
||||||
/>
|
/>
|
||||||
<ImagePackUpload onUpload={handleAddItem} />
|
<ImagePackUpload onUpload={handleAddItem} />
|
||||||
{ images.length === 0 ? null : (
|
{images.length === 0 ? null : (
|
||||||
<div>
|
<div>
|
||||||
<div className="image-pack__header">
|
<div className="image-pack__header">
|
||||||
<Text variant="b3">Image</Text>
|
<Text variant="b3">Image</Text>
|
||||||
|
@ -369,7 +399,15 @@ function ImagePackUser() {
|
||||||
{images.map(([shortcode, image]) => (
|
{images.map(([shortcode, image]) => (
|
||||||
<ImagePackItem
|
<ImagePackItem
|
||||||
key={shortcode}
|
key={shortcode}
|
||||||
url={mx.mxcUrlToHttp(image.mxc)}
|
url={mx.mxcUrlToHttp(
|
||||||
|
image.mxc,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)}
|
||||||
shortcode={shortcode}
|
shortcode={shortcode}
|
||||||
usage={getUsage(image.usage)}
|
usage={getUsage(image.usage)}
|
||||||
onUsageChange={handleUsageItem}
|
onUsageChange={handleUsageItem}
|
||||||
|
@ -379,14 +417,10 @@ function ImagePackUser() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(pack.images.size > 2) && (
|
{pack.images.size > 2 && (
|
||||||
<div className="image-pack__footer">
|
<div className="image-pack__footer">
|
||||||
<Button onClick={() => setViewMore(!viewMore)}>
|
<Button onClick={() => setViewMore(!viewMore)}>
|
||||||
{
|
{viewMore ? 'View less' : `View ${pack.images.size - 2} more`}
|
||||||
viewMore
|
|
||||||
? 'View less'
|
|
||||||
: `View ${pack.images.size - 2} more`
|
|
||||||
}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -435,29 +469,33 @@ function ImagePackGlobal() {
|
||||||
<div className="image-pack-global">
|
<div className="image-pack-global">
|
||||||
<MenuHeader>Global packs</MenuHeader>
|
<MenuHeader>Global packs</MenuHeader>
|
||||||
<div>
|
<div>
|
||||||
{
|
{roomIdToStateKeys.size > 0 ? (
|
||||||
roomIdToStateKeys.size > 0
|
[...roomIdToStateKeys].map(([roomId, stateKeys]) => {
|
||||||
? [...roomIdToStateKeys].map(([roomId, stateKeys]) => {
|
const room = mx.getRoom(roomId);
|
||||||
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 (
|
return (
|
||||||
stateKeys.map((stateKey) => {
|
<div className="image-pack__global" key={pack.id}>
|
||||||
const data = room.currentState.getStateEvents('im.ponies.room_emotes', stateKey);
|
<Checkbox
|
||||||
const pack = ImagePackBuilder.parsePack(data?.getId(), data?.getContent());
|
variant="positive"
|
||||||
if (!pack) return null;
|
onToggle={() => handleChange(roomId, stateKey)}
|
||||||
return (
|
isActive
|
||||||
<div className="image-pack__global" key={pack.id}>
|
/>
|
||||||
<Checkbox variant="positive" onToggle={() => handleChange(roomId, stateKey)} isActive />
|
<div>
|
||||||
<div>
|
<Text variant="b2">{pack.displayName ?? 'Unknown'}</Text>
|
||||||
<Text variant="b2">{pack.displayName ?? 'Unknown'}</Text>
|
<Text variant="b3">{room.name}</Text>
|
||||||
<Text variant="b3">{room.name}</Text>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
: <div className="image-pack-global__empty"><Text>No global packs</Text></div>
|
})
|
||||||
}
|
) : (
|
||||||
|
<div className="image-pack-global__empty">
|
||||||
|
<Text>No global packs</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,11 +18,13 @@ import UserIC from '../../../../public/res/ic/outlined/user.svg';
|
||||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||||
import { getDMRoomFor } from '../../utils/matrix';
|
import { getDMRoomFor } from '../../utils/matrix';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
|
function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
|
||||||
const [isSearching, updateIsSearching] = useState(false);
|
const [isSearching, updateIsSearching] = useState(false);
|
||||||
const [searchQuery, updateSearchQuery] = useState({});
|
const [searchQuery, updateSearchQuery] = useState({});
|
||||||
const [users, updateUsers] = useState([]);
|
const [users, updateUsers] = useState([]);
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const [procUsers, updateProcUsers] = useState(new Set()); // proc stands for processing.
|
const [procUsers, updateProcUsers] = useState(new Set()); // proc stands for processing.
|
||||||
const [procUserError, updateUserProcError] = useState(new Map());
|
const [procUserError, updateUserProcError] = useState(new Map());
|
||||||
|
@ -222,7 +224,15 @@ function InviteUser({ isOpen, roomId, searchTerm, onRequestClose }) {
|
||||||
key={userId}
|
key={userId}
|
||||||
avatarSrc={
|
avatarSrc={
|
||||||
typeof user.avatar_url === 'string'
|
typeof user.avatar_url === 'string'
|
||||||
? mx.mxcUrlToHttp(user.avatar_url, 42, 42, 'crop')
|
? mx.mxcUrlToHttp(
|
||||||
|
user.avatar_url,
|
||||||
|
42,
|
||||||
|
42,
|
||||||
|
'crop',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
name={name}
|
name={name}
|
||||||
|
|
|
@ -14,15 +14,19 @@ import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||||
|
|
||||||
import './ProfileEditor.scss';
|
import './ProfileEditor.scss';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
function ProfileEditor({ userId }) {
|
function ProfileEditor({ userId }) {
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const user = mx.getUser(mx.getUserId());
|
const user = mx.getUser(mx.getUserId());
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const displayNameRef = useRef(null);
|
const displayNameRef = useRef(null);
|
||||||
const [avatarSrc, setAvatarSrc] = useState(
|
const [avatarSrc, setAvatarSrc] = useState(
|
||||||
user.avatarUrl ? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop') : null
|
user.avatarUrl
|
||||||
|
? mx.mxcUrlToHttp(user.avatarUrl, 80, 80, 'crop', undefined, undefined, useAuthentication)
|
||||||
|
: null
|
||||||
);
|
);
|
||||||
const [username, setUsername] = useState(user.displayName);
|
const [username, setUsername] = useState(user.displayName);
|
||||||
const [disabled, setDisabled] = useState(true);
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
@ -31,13 +35,25 @@ function ProfileEditor({ userId }) {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
mx.getProfileInfo(mx.getUserId()).then((info) => {
|
mx.getProfileInfo(mx.getUserId()).then((info) => {
|
||||||
if (!isMounted) return;
|
if (!isMounted) return;
|
||||||
setAvatarSrc(info.avatar_url ? mx.mxcUrlToHttp(info.avatar_url, 80, 80, 'crop') : null);
|
setAvatarSrc(
|
||||||
|
info.avatar_url
|
||||||
|
? mx.mxcUrlToHttp(
|
||||||
|
info.avatar_url,
|
||||||
|
80,
|
||||||
|
80,
|
||||||
|
'crop',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
useAuthentication
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
);
|
||||||
setUsername(info.displayname);
|
setUsername(info.displayname);
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
};
|
};
|
||||||
}, [mx, userId]);
|
}, [mx, userId, useAuthentication]);
|
||||||
|
|
||||||
const handleAvatarUpload = async (url) => {
|
const handleAvatarUpload = async (url) => {
|
||||||
if (url === null) {
|
if (url === null) {
|
||||||
|
@ -54,7 +70,7 @@ function ProfileEditor({ userId }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mx.setAvatarUrl(url);
|
mx.setAvatarUrl(url);
|
||||||
setAvatarSrc(mx.mxcUrlToHttp(url, 80, 80, 'crop'));
|
setAvatarSrc(mx.mxcUrlToHttp(url, 80, 80, 'crop', undefined, undefined, useAuthentication));
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveDisplayName = () => {
|
const saveDisplayName = () => {
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { confirmDialog } from '../../molecules/confirm-dialog/ConfirmDialog';
|
||||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||||
import { getDMRoomFor } from '../../utils/matrix';
|
import { getDMRoomFor } from '../../utils/matrix';
|
||||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
function ModerationTools({ roomId, userId }) {
|
function ModerationTools({ roomId, userId }) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
@ -329,6 +330,7 @@ function useRerenderOnProfileChange(roomId, userId) {
|
||||||
function ProfileViewer() {
|
function ProfileViewer() {
|
||||||
const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog();
|
const [isOpen, roomId, userId, closeDialog, handleAfterClose] = useToggleDialog();
|
||||||
useRerenderOnProfileChange(roomId, userId);
|
useRerenderOnProfileChange(roomId, userId);
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
|
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const room = mx.getRoom(roomId);
|
const room = mx.getRoom(roomId);
|
||||||
|
@ -338,7 +340,9 @@ function ProfileViewer() {
|
||||||
const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(mx, userId);
|
const username = roomMember ? getUsernameOfRoomMember(roomMember) : getUsername(mx, userId);
|
||||||
const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl;
|
const avatarMxc = roomMember?.getMxcAvatarUrl?.() || mx.getUser(userId)?.avatarUrl;
|
||||||
const avatarUrl =
|
const avatarUrl =
|
||||||
avatarMxc && avatarMxc !== 'null' ? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop') : null;
|
avatarMxc && avatarMxc !== 'null'
|
||||||
|
? mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop', undefined, undefined, useAuthentication)
|
||||||
|
: null;
|
||||||
|
|
||||||
const powerLevel = roomMember?.powerLevel || 0;
|
const powerLevel = roomMember?.powerLevel || 0;
|
||||||
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
|
const myPowerLevel = room.getMember(mx.getUserId())?.powerLevel || 0;
|
||||||
|
|
|
@ -15,7 +15,7 @@ export function AuthFooter() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
v4.1.0
|
v4.2.3
|
||||||
</Text>
|
</Text>
|
||||||
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
|
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
|
||||||
Twitter
|
Twitter
|
||||||
|
|
|
@ -4,69 +4,89 @@ import React, { useMemo } from 'react';
|
||||||
import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
|
import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
|
||||||
|
|
||||||
type SSOLoginProps = {
|
type SSOLoginProps = {
|
||||||
providers: IIdentityProvider[];
|
providers?: IIdentityProvider[];
|
||||||
asIcons?: boolean;
|
|
||||||
redirectUrl: string;
|
redirectUrl: string;
|
||||||
|
saveScreenSpace?: boolean;
|
||||||
};
|
};
|
||||||
export function SSOLogin({ providers, redirectUrl, asIcons }: SSOLoginProps) {
|
export function SSOLogin({ providers, redirectUrl, saveScreenSpace }: SSOLoginProps) {
|
||||||
const discovery = useAutoDiscoveryInfo();
|
const discovery = useAutoDiscoveryInfo();
|
||||||
const baseUrl = discovery['m.homeserver'].base_url;
|
const baseUrl = discovery['m.homeserver'].base_url;
|
||||||
const mx = useMemo(() => createClient({ baseUrl }), [baseUrl]);
|
const mx = useMemo(() => createClient({ baseUrl }), [baseUrl]);
|
||||||
|
|
||||||
const getSSOIdUrl = (ssoId: string): string => mx.getSsoLoginUrl(redirectUrl, 'sso', ssoId);
|
const getSSOIdUrl = (ssoId?: string): string => mx.getSsoLoginUrl(redirectUrl, 'sso', ssoId);
|
||||||
|
|
||||||
const anyAsBtn = providers.find(
|
const withoutIcon = providers
|
||||||
(provider) => !provider.icon || !mx.mxcUrlToHttp(provider.icon, 96, 96, 'crop', false)
|
? providers.find(
|
||||||
);
|
(provider) => !provider.icon || !mx.mxcUrlToHttp(provider.icon, 96, 96, 'crop', false)
|
||||||
|
)
|
||||||
|
: true;
|
||||||
|
|
||||||
|
const renderAsIcons = withoutIcon ? false : saveScreenSpace && providers && providers.length > 2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box justifyContent="Center" gap="600" wrap="Wrap">
|
<Box justifyContent="Center" gap="600" wrap="Wrap">
|
||||||
{providers.map((provider) => {
|
{providers ? (
|
||||||
const { id, name, icon } = provider;
|
providers.map((provider) => {
|
||||||
const iconUrl = icon && mx.mxcUrlToHttp(icon, 96, 96, 'crop', false);
|
const { id, name, icon } = provider;
|
||||||
|
const iconUrl = icon && mx.mxcUrlToHttp(icon, 96, 96, 'crop', false);
|
||||||
|
|
||||||
const buttonTitle = `Continue with ${name}`;
|
const buttonTitle = `Continue with ${name}`;
|
||||||
|
|
||||||
|
if (renderAsIcons) {
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
key={id}
|
||||||
|
as="a"
|
||||||
|
href={getSSOIdUrl(id)}
|
||||||
|
aria-label={buttonTitle}
|
||||||
|
size="300"
|
||||||
|
radii="300"
|
||||||
|
>
|
||||||
|
<AvatarImage src={iconUrl!} alt={name} title={buttonTitle} />
|
||||||
|
</Avatar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!anyAsBtn && iconUrl && asIcons) {
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Button
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ width: '100%' }}
|
||||||
key={id}
|
key={id}
|
||||||
as="a"
|
as="a"
|
||||||
href={getSSOIdUrl(id)}
|
href={getSSOIdUrl(id)}
|
||||||
aria-label={buttonTitle}
|
size="500"
|
||||||
size="300"
|
variant="Secondary"
|
||||||
radii="300"
|
fill="Soft"
|
||||||
|
outlined
|
||||||
|
before={
|
||||||
|
iconUrl && (
|
||||||
|
<Avatar size="200" radii="300">
|
||||||
|
<AvatarImage src={iconUrl} alt={name} />
|
||||||
|
</Avatar>
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<AvatarImage src={iconUrl} alt={name} title={buttonTitle} />
|
<Text align="Center" size="B500" truncate>
|
||||||
</Avatar>
|
{buttonTitle}
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
})
|
||||||
|
) : (
|
||||||
return (
|
<Button
|
||||||
<Button
|
style={{ width: '100%' }}
|
||||||
style={{ width: '100%' }}
|
as="a"
|
||||||
key={id}
|
href={getSSOIdUrl()}
|
||||||
as="a"
|
size="500"
|
||||||
href={getSSOIdUrl(id)}
|
variant="Secondary"
|
||||||
size="500"
|
fill="Soft"
|
||||||
variant="Secondary"
|
outlined
|
||||||
fill="Soft"
|
>
|
||||||
outlined
|
<Text align="Center" size="B500" truncate>
|
||||||
before={
|
Continue with SSO
|
||||||
iconUrl && (
|
</Text>
|
||||||
<Avatar size="200" radii="300">
|
</Button>
|
||||||
<AvatarImage src={iconUrl} alt={name} />
|
)}
|
||||||
</Avatar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Text align="Center" size="B500" truncate>
|
|
||||||
{buttonTitle}
|
|
||||||
</Text>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,9 +76,7 @@ export function Login() {
|
||||||
<SSOLogin
|
<SSOLogin
|
||||||
providers={parsedFlows.sso.identity_providers}
|
providers={parsedFlows.sso.identity_providers}
|
||||||
redirectUrl={ssoRedirectUrl}
|
redirectUrl={ssoRedirectUrl}
|
||||||
asIcons={
|
saveScreenSpace={parsedFlows.password !== undefined}
|
||||||
parsedFlows.password !== undefined && parsedFlows.sso.identity_providers.length > 2
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<span data-spacing-node />
|
<span data-spacing-node />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -83,10 +83,7 @@ export function Register() {
|
||||||
<SSOLogin
|
<SSOLogin
|
||||||
providers={sso.identity_providers}
|
providers={sso.identity_providers}
|
||||||
redirectUrl={ssoRedirectUrl}
|
redirectUrl={ssoRedirectUrl}
|
||||||
asIcons={
|
saveScreenSpace={registerFlows.status === RegisterFlowStatus.FlowRequired}
|
||||||
registerFlows.status === RegisterFlowStatus.FlowRequired &&
|
|
||||||
sso.identity_providers.length > 2
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<span data-spacing-node />
|
<span data-spacing-node />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -22,9 +22,10 @@ import {
|
||||||
isNotificationEvent,
|
isNotificationEvent,
|
||||||
} from '../../utils/room';
|
} from '../../utils/room';
|
||||||
import { NotificationType, UnreadInfo } from '../../../types/matrix/room';
|
import { NotificationType, UnreadInfo } from '../../../types/matrix/room';
|
||||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
|
import { useSelectedRoom } from '../../hooks/router/useSelectedRoom';
|
||||||
import { useInboxNotificationsSelected } from '../../hooks/router/useInbox';
|
import { useInboxNotificationsSelected } from '../../hooks/router/useInbox';
|
||||||
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
function SystemEmojiFeature() {
|
function SystemEmojiFeature() {
|
||||||
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
|
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
|
||||||
|
@ -132,6 +133,7 @@ function MessageNotifications() {
|
||||||
const notifRef = useRef<Notification>();
|
const notifRef = useRef<Notification>();
|
||||||
const unreadCacheRef = useRef<Map<string, UnreadInfo>>(new Map());
|
const unreadCacheRef = useRef<Map<string, UnreadInfo>>(new Map());
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const [showNotifications] = useSetting(settingsAtom, 'showNotifications');
|
const [showNotifications] = useSetting(settingsAtom, 'showNotifications');
|
||||||
const [notificationSound] = useSetting(settingsAtom, 'isNotificationSounds');
|
const [notificationSound] = useSetting(settingsAtom, 'isNotificationSounds');
|
||||||
|
|
||||||
|
@ -216,7 +218,7 @@ function MessageNotifications() {
|
||||||
notify({
|
notify({
|
||||||
roomName: room.name ?? 'Unknown',
|
roomName: room.name ?? 'Unknown',
|
||||||
roomAvatar: avatarMxc
|
roomAvatar: avatarMxc
|
||||||
? mx.mxcUrlToHttp(avatarMxc, 96, 96, 'crop') ?? undefined
|
? mxcUrlToHttp(mx, avatarMxc, useAuthentication, 96, 96, 'crop') ?? undefined
|
||||||
: undefined,
|
: undefined,
|
||||||
username: getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender) ?? sender,
|
username: getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender) ?? sender,
|
||||||
roomId: room.roomId,
|
roomId: room.roomId,
|
||||||
|
@ -240,6 +242,7 @@ function MessageNotifications() {
|
||||||
playSound,
|
playSound,
|
||||||
notify,
|
notify,
|
||||||
selectedRoomId,
|
selectedRoomId,
|
||||||
|
useAuthentication,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function WelcomePage() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
v4.1.0
|
v4.2.3
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
|
||||||
import { useRoomTopic } from '../../../hooks/useRoomMeta';
|
import { useRoomTopic } from '../../../hooks/useRoomMeta';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
||||||
import { BackRouteHandler } from '../../../components/BackRouteHandler';
|
import { BackRouteHandler } from '../../../components/BackRouteHandler';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
const COMPACT_CARD_WIDTH = 548;
|
const COMPACT_CARD_WIDTH = 548;
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@ type InviteCardProps = {
|
||||||
};
|
};
|
||||||
function InviteCard({ room, userId, direct, compact, onNavigate }: InviteCardProps) {
|
function InviteCard({ room, userId, direct, compact, onNavigate }: InviteCardProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const roomName = room.name || room.getCanonicalAlias() || room.roomId;
|
const roomName = room.name || room.getCanonicalAlias() || room.roomId;
|
||||||
const member = room.getMember(userId);
|
const member = room.getMember(userId);
|
||||||
const memberEvent = member?.events.member;
|
const memberEvent = member?.events.member;
|
||||||
|
@ -110,7 +112,7 @@ function InviteCard({ room, userId, direct, compact, onNavigate }: InviteCardPro
|
||||||
<Avatar size="300">
|
<Avatar size="300">
|
||||||
<RoomAvatar
|
<RoomAvatar
|
||||||
roomId={room.roomId}
|
roomId={room.roomId}
|
||||||
src={direct ? getDirectRoomAvatarUrl(mx, room, 96) : getRoomAvatarUrl(mx, room, 96)}
|
src={direct ? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication) : getRoomAvatarUrl(mx, room, 96, useAuthentication)}
|
||||||
alt={roomName}
|
alt={roomName}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<Text as="span" size="H6">
|
<Text as="span" size="H6">
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { HTMLReactParserOptions } from 'html-react-parser';
|
||||||
import { Opts as LinkifyOpts } from 'linkifyjs';
|
import { Opts as LinkifyOpts } from 'linkifyjs';
|
||||||
import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
|
import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { getMxIdLocalPart } from '../../../utils/matrix';
|
import { getMxIdLocalPart, mxcUrlToHttp } from '../../../utils/matrix';
|
||||||
import { InboxNotificationsPathSearchParams } from '../../paths';
|
import { InboxNotificationsPathSearchParams } from '../../paths';
|
||||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||||
import { SequenceCard } from '../../../components/sequence-card';
|
import { SequenceCard } from '../../../components/sequence-card';
|
||||||
|
@ -81,6 +81,7 @@ import { useMentionClickHandler } from '../../../hooks/useMentionClickHandler';
|
||||||
import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler';
|
import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler';
|
||||||
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
||||||
import { BackRouteHandler } from '../../../components/BackRouteHandler';
|
import { BackRouteHandler } from '../../../components/BackRouteHandler';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type RoomNotificationsGroup = {
|
type RoomNotificationsGroup = {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
|
@ -191,6 +192,7 @@ function RoomNotificationsGroupComp({
|
||||||
onOpen,
|
onOpen,
|
||||||
}: RoomNotificationsGroupProps) {
|
}: RoomNotificationsGroupProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
const unread = useRoomUnread(room.roomId, roomToUnreadAtom);
|
||||||
const mentionClickHandler = useMentionClickHandler(room.roomId);
|
const mentionClickHandler = useMentionClickHandler(room.roomId);
|
||||||
const spoilerClickHandler = useSpoilerClickHandler();
|
const spoilerClickHandler = useSpoilerClickHandler();
|
||||||
|
@ -208,10 +210,11 @@ function RoomNotificationsGroupComp({
|
||||||
() =>
|
() =>
|
||||||
getReactCustomHtmlParser(mx, room.roomId, {
|
getReactCustomHtmlParser(mx, room.roomId, {
|
||||||
linkifyOpts,
|
linkifyOpts,
|
||||||
|
useAuthentication,
|
||||||
handleSpoilerClick: spoilerClickHandler,
|
handleSpoilerClick: spoilerClickHandler,
|
||||||
handleMentionClick: mentionClickHandler,
|
handleMentionClick: mentionClickHandler,
|
||||||
}),
|
}),
|
||||||
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler]
|
[mx, room, linkifyOpts, mentionClickHandler, spoilerClickHandler, useAuthentication]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderMatrixEvent = useMatrixEventRenderer<[IRoomEvent, string, GetContentCallback]>(
|
const renderMatrixEvent = useMatrixEventRenderer<[IRoomEvent, string, GetContentCallback]>(
|
||||||
|
@ -369,7 +372,7 @@ function RoomNotificationsGroupComp({
|
||||||
<Avatar size="200" radii="300">
|
<Avatar size="200" radii="300">
|
||||||
<RoomAvatar
|
<RoomAvatar
|
||||||
roomId={room.roomId}
|
roomId={room.roomId}
|
||||||
src={getRoomAvatarUrl(mx, room, 96)}
|
src={getRoomAvatarUrl(mx, room, 96, useAuthentication)}
|
||||||
alt={room.name}
|
alt={room.name}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<RoomIcon size="50" joinRule={room.getJoinRule() ?? JoinRule.Restricted} filled />
|
<RoomIcon size="50" joinRule={room.getJoinRule() ?? JoinRule.Restricted} filled />
|
||||||
|
@ -424,7 +427,7 @@ function RoomNotificationsGroupComp({
|
||||||
userId={event.sender}
|
userId={event.sender}
|
||||||
src={
|
src={
|
||||||
senderAvatarMxc
|
senderAvatarMxc
|
||||||
? mx.mxcUrlToHttp(senderAvatarMxc, 48, 48, 'crop') ?? undefined
|
? mxcUrlToHttp(mx, senderAvatarMxc, useAuthentication, 48, 48, 'crop') ?? undefined
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
alt={displayName}
|
alt={displayName}
|
||||||
|
|
|
@ -86,6 +86,8 @@ import { openInviteUser, openSpaceSettings } from '../../../../client/action/nav
|
||||||
import { stopPropagation } from '../../../utils/keyboard';
|
import { stopPropagation } from '../../../utils/keyboard';
|
||||||
import { getMatrixToRoom } from '../../../plugins/matrix-to';
|
import { getMatrixToRoom } from '../../../plugins/matrix-to';
|
||||||
import { getViaServers } from '../../../plugins/via-servers';
|
import { getViaServers } from '../../../plugins/via-servers';
|
||||||
|
import { getRoomAvatarUrl } from '../../../utils/room';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type SpaceMenuProps = {
|
type SpaceMenuProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -225,18 +227,18 @@ const useDraggableItem = (
|
||||||
return !target
|
return !target
|
||||||
? undefined
|
? undefined
|
||||||
: draggable({
|
: draggable({
|
||||||
element: target,
|
element: target,
|
||||||
dragHandle,
|
dragHandle,
|
||||||
getInitialData: () => ({ item }),
|
getInitialData: () => ({ item }),
|
||||||
onDragStart: () => {
|
onDragStart: () => {
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
onDragging?.(item);
|
onDragging?.(item);
|
||||||
},
|
},
|
||||||
onDrop: () => {
|
onDrop: () => {
|
||||||
setDragging(false);
|
setDragging(false);
|
||||||
onDragging?.(undefined);
|
onDragging?.(undefined);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [targetRef, dragHandleRef, item, onDragging]);
|
}, [targetRef, dragHandleRef, item, onDragging]);
|
||||||
|
|
||||||
return dragging;
|
return dragging;
|
||||||
|
@ -379,15 +381,16 @@ function SpaceTab({
|
||||||
onUnpin,
|
onUnpin,
|
||||||
}: SpaceTabProps) {
|
}: SpaceTabProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const targetRef = useRef<HTMLDivElement>(null);
|
const targetRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const spaceDraggable: SidebarDraggable = useMemo(
|
const spaceDraggable: SidebarDraggable = useMemo(
|
||||||
() =>
|
() =>
|
||||||
folder
|
folder
|
||||||
? {
|
? {
|
||||||
folder,
|
folder,
|
||||||
spaceId: space.roomId,
|
spaceId: space.roomId,
|
||||||
}
|
}
|
||||||
: space.roomId,
|
: space.roomId,
|
||||||
[folder, space]
|
[folder, space]
|
||||||
);
|
);
|
||||||
|
@ -431,7 +434,7 @@ function SpaceTab({
|
||||||
>
|
>
|
||||||
<RoomAvatar
|
<RoomAvatar
|
||||||
roomId={space.roomId}
|
roomId={space.roomId}
|
||||||
src={space.getAvatarUrl(mx.baseUrl, 96, 96, 'crop') ?? undefined}
|
src={getRoomAvatarUrl(mx, space, 96, useAuthentication) ?? undefined}
|
||||||
alt={space.name}
|
alt={space.name}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<Text size={folder ? 'H6' : 'H4'}>{nameInitials(space.name, 2)}</Text>
|
<Text size={folder ? 'H6' : 'H4'}>{nameInitials(space.name, 2)}</Text>
|
||||||
|
@ -524,6 +527,7 @@ function ClosedSpaceFolder({
|
||||||
disabled,
|
disabled,
|
||||||
}: ClosedSpaceFolderProps) {
|
}: ClosedSpaceFolderProps) {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const handlerRef = useRef<HTMLDivElement>(null);
|
const handlerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const spaceDraggable: FolderDraggable = useMemo(() => ({ folder }), [folder]);
|
const spaceDraggable: FolderDraggable = useMemo(() => ({ folder }), [folder]);
|
||||||
|
@ -556,7 +560,7 @@ function ClosedSpaceFolder({
|
||||||
<SidebarAvatar key={sId} size="200" radii="300">
|
<SidebarAvatar key={sId} size="200" radii="300">
|
||||||
<RoomAvatar
|
<RoomAvatar
|
||||||
roomId={space.roomId}
|
roomId={space.roomId}
|
||||||
src={space.getAvatarUrl(mx.baseUrl, 96, 96, 'crop') ?? undefined}
|
src={getRoomAvatarUrl(mx, space, 96, useAuthentication) ?? undefined}
|
||||||
alt={space.name}
|
alt={space.name}
|
||||||
renderFallback={() => (
|
renderFallback={() => (
|
||||||
<Text size="Inherit">
|
<Text size="Inherit">
|
||||||
|
|
|
@ -5,8 +5,9 @@ import { SidebarItem, SidebarItemTooltip, SidebarAvatar } from '../../../compone
|
||||||
import { openSettings } from '../../../../client/action/navigation';
|
import { openSettings } from '../../../../client/action/navigation';
|
||||||
import { UserAvatar } from '../../../components/user-avatar';
|
import { UserAvatar } from '../../../components/user-avatar';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
import { getMxIdLocalPart } from '../../../utils/matrix';
|
import { getMxIdLocalPart, mxcUrlToHttp } from '../../../utils/matrix';
|
||||||
import { nameInitials } from '../../../utils/common';
|
import { nameInitials } from '../../../utils/common';
|
||||||
|
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||||
|
|
||||||
type UserProfile = {
|
type UserProfile = {
|
||||||
avatar_url?: string;
|
avatar_url?: string;
|
||||||
|
@ -14,12 +15,13 @@ type UserProfile = {
|
||||||
};
|
};
|
||||||
export function UserTab() {
|
export function UserTab() {
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
|
const useAuthentication = useMediaAuthentication();
|
||||||
const userId = mx.getUserId()!;
|
const userId = mx.getUserId()!;
|
||||||
|
|
||||||
const [profile, setProfile] = useState<UserProfile>({});
|
const [profile, setProfile] = useState<UserProfile>({});
|
||||||
const displayName = profile.displayname ?? getMxIdLocalPart(userId) ?? userId;
|
const displayName = profile.displayname ?? getMxIdLocalPart(userId) ?? userId;
|
||||||
const avatarUrl = profile.avatar_url
|
const avatarUrl = profile.avatar_url
|
||||||
? mx.mxcUrlToHttp(profile.avatar_url, 96, 96, 'crop') ?? undefined
|
? mxcUrlToHttp(mx, profile.avatar_url, useAuthentication, 96, 96, 'crop') ?? undefined
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -14,7 +14,12 @@ import { IntermediateRepresentation, Opts as LinkifyOpts, OptFn } from 'linkifyj
|
||||||
import Linkify from 'linkify-react';
|
import Linkify from 'linkify-react';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import * as css from '../styles/CustomHtml.css';
|
import * as css from '../styles/CustomHtml.css';
|
||||||
import { getMxIdLocalPart, getCanonicalAliasRoomId, isRoomAlias } from '../utils/matrix';
|
import {
|
||||||
|
getMxIdLocalPart,
|
||||||
|
getCanonicalAliasRoomId,
|
||||||
|
isRoomAlias,
|
||||||
|
mxcUrlToHttp,
|
||||||
|
} from '../utils/matrix';
|
||||||
import { getMemberDisplayName } from '../utils/room';
|
import { getMemberDisplayName } from '../utils/room';
|
||||||
import { EMOJI_PATTERN, URL_NEG_LB } from '../utils/regex';
|
import { EMOJI_PATTERN, URL_NEG_LB } from '../utils/regex';
|
||||||
import { getHexcodeForEmoji, getShortcodeFor, getEmojiUrl, isUsingTwemoji } from './emoji';
|
import { getHexcodeForEmoji, getShortcodeFor, getEmojiUrl, isUsingTwemoji } from './emoji';
|
||||||
|
@ -44,7 +49,8 @@ export const LINKIFY_OPTS: LinkifyOpts = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const makeMentionCustomProps = (
|
export const makeMentionCustomProps = (
|
||||||
handleMentionClick?: ReactEventHandler<HTMLElement>
|
handleMentionClick?: ReactEventHandler<HTMLElement>,
|
||||||
|
content?: string
|
||||||
): ComponentPropsWithoutRef<'a'> => ({
|
): ComponentPropsWithoutRef<'a'> => ({
|
||||||
style: { cursor: 'pointer' },
|
style: { cursor: 'pointer' },
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
|
@ -53,6 +59,7 @@ export const makeMentionCustomProps = (
|
||||||
tabIndex: handleMentionClick ? 0 : -1,
|
tabIndex: handleMentionClick ? 0 : -1,
|
||||||
onKeyDown: handleMentionClick ? onEnterOrSpace(handleMentionClick) : undefined,
|
onKeyDown: handleMentionClick ? onEnterOrSpace(handleMentionClick) : undefined,
|
||||||
onClick: handleMentionClick,
|
onClick: handleMentionClick,
|
||||||
|
children: content,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const renderMatrixMention = (
|
export const renderMatrixMention = (
|
||||||
|
@ -86,6 +93,8 @@ export const renderMatrixMention = (
|
||||||
isRoomAlias(roomIdOrAlias) ? getCanonicalAliasRoomId(mx, roomIdOrAlias) : roomIdOrAlias
|
isRoomAlias(roomIdOrAlias) ? getCanonicalAliasRoomId(mx, roomIdOrAlias) : roomIdOrAlias
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fallbackContent = mentionRoom ? `#${mentionRoom.name}` : roomIdOrAlias;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
href={href}
|
href={href}
|
||||||
|
@ -96,7 +105,7 @@ export const renderMatrixMention = (
|
||||||
data-mention-id={mentionRoom?.roomId ?? roomIdOrAlias}
|
data-mention-id={mentionRoom?.roomId ?? roomIdOrAlias}
|
||||||
data-mention-via={viaServers?.join(',')}
|
data-mention-via={viaServers?.join(',')}
|
||||||
>
|
>
|
||||||
{mentionRoom ? `#${mentionRoom.name}` : roomIdOrAlias}
|
{customProps.children ? customProps.children : fallbackContent}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -119,7 +128,9 @@ export const renderMatrixMention = (
|
||||||
data-mention-event-id={eventId}
|
data-mention-event-id={eventId}
|
||||||
data-mention-via={viaServers?.join(',')}
|
data-mention-via={viaServers?.join(',')}
|
||||||
>
|
>
|
||||||
Message: {mentionRoom ? `#${mentionRoom.name}` : roomIdOrAlias}
|
{customProps.children
|
||||||
|
? customProps.children
|
||||||
|
: `Message: ${mentionRoom ? `#${mentionRoom.name}` : roomIdOrAlias}`}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -201,6 +212,7 @@ export const getReactCustomHtmlParser = (
|
||||||
highlightRegex?: RegExp;
|
highlightRegex?: RegExp;
|
||||||
handleSpoilerClick?: ReactEventHandler<HTMLElement>;
|
handleSpoilerClick?: ReactEventHandler<HTMLElement>;
|
||||||
handleMentionClick?: ReactEventHandler<HTMLElement>;
|
handleMentionClick?: ReactEventHandler<HTMLElement>;
|
||||||
|
useAuthentication?: boolean;
|
||||||
}
|
}
|
||||||
): HTMLReactParserOptions => {
|
): HTMLReactParserOptions => {
|
||||||
const opts: HTMLReactParserOptions = {
|
const opts: HTMLReactParserOptions = {
|
||||||
|
@ -336,12 +348,17 @@ export const getReactCustomHtmlParser = (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'a' && testMatrixTo(tryDecodeURIComponent(props.href))) {
|
if (name === 'a' && testMatrixTo(tryDecodeURIComponent(props.href))) {
|
||||||
|
const content = children.find((child) => !(child instanceof DOMText))
|
||||||
|
? undefined
|
||||||
|
: children.map((c) => (c instanceof DOMText ? c.data : '')).join();
|
||||||
|
|
||||||
const mention = renderMatrixMention(
|
const mention = renderMatrixMention(
|
||||||
mx,
|
mx,
|
||||||
roomId,
|
roomId,
|
||||||
tryDecodeURIComponent(props.href),
|
tryDecodeURIComponent(props.href),
|
||||||
makeMentionCustomProps(params.handleMentionClick)
|
makeMentionCustomProps(params.handleMentionClick, content)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mention) return mention;
|
if (mention) return mention;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,7 +380,7 @@ export const getReactCustomHtmlParser = (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'img') {
|
if (name === 'img') {
|
||||||
const htmlSrc = mx.mxcUrlToHttp(props.src);
|
const htmlSrc = mxcUrlToHttp(mx, props.src, params.useAuthentication);
|
||||||
if (htmlSrc && props.src.startsWith('mxc://') === false) {
|
if (htmlSrc && props.src.startsWith('mxc://') === false) {
|
||||||
return (
|
return (
|
||||||
<a href={htmlSrc} target="_blank" rel="noreferrer noopener">
|
<a href={htmlSrc} target="_blank" rel="noreferrer noopener">
|
||||||
|
|
|
@ -253,3 +253,40 @@ export const removeRoomIdFromMDirect = async (mx: MatrixClient, roomId: string):
|
||||||
|
|
||||||
await mx.setAccountData(AccountDataEvent.Direct, userIdToRoomIds);
|
await mx.setAccountData(AccountDataEvent.Direct, userIdToRoomIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mxcUrlToHttp = (
|
||||||
|
mx: MatrixClient,
|
||||||
|
mxcUrl: string,
|
||||||
|
useAuthentication?: boolean,
|
||||||
|
width?: number,
|
||||||
|
height?: number,
|
||||||
|
resizeMethod?: string,
|
||||||
|
allowDirectLinks?: boolean,
|
||||||
|
allowRedirects?: boolean
|
||||||
|
): string | null =>
|
||||||
|
mx.mxcUrlToHttp(
|
||||||
|
mxcUrl,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
resizeMethod,
|
||||||
|
allowDirectLinks,
|
||||||
|
allowRedirects,
|
||||||
|
useAuthentication
|
||||||
|
);
|
||||||
|
|
||||||
|
export const downloadMedia = async (src: string): Promise<Blob> => {
|
||||||
|
// this request is authenticated by service worker
|
||||||
|
const res = await fetch(src, { method: 'GET' });
|
||||||
|
const blob = await res.blob();
|
||||||
|
return blob;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const downloadEncryptedMedia = async (
|
||||||
|
src: string,
|
||||||
|
decryptContent: (buf: ArrayBuffer) => Promise<Blob>
|
||||||
|
): Promise<Blob> => {
|
||||||
|
const encryptedContent = await downloadMedia(src);
|
||||||
|
const decryptedContent = await decryptContent(await encryptedContent.arrayBuffer());
|
||||||
|
|
||||||
|
return decryptedContent;
|
||||||
|
};
|
||||||
|
|
|
@ -273,16 +273,26 @@ export const joinRuleToIconSrc = (
|
||||||
export const getRoomAvatarUrl = (
|
export const getRoomAvatarUrl = (
|
||||||
mx: MatrixClient,
|
mx: MatrixClient,
|
||||||
room: Room,
|
room: Room,
|
||||||
size: 32 | 96 = 32
|
size: 32 | 96 = 32,
|
||||||
): string | undefined => room.getAvatarUrl(mx.baseUrl, size, size, 'crop') ?? undefined;
|
useAuthentication = false
|
||||||
|
): string | undefined => {
|
||||||
|
const mxcUrl = room.getMxcAvatarUrl();
|
||||||
|
return mxcUrl
|
||||||
|
? mx.mxcUrlToHttp(mxcUrl, size, size, 'crop', undefined, false, useAuthentication) ?? undefined
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
export const getDirectRoomAvatarUrl = (
|
export const getDirectRoomAvatarUrl = (
|
||||||
mx: MatrixClient,
|
mx: MatrixClient,
|
||||||
room: Room,
|
room: Room,
|
||||||
size: 32 | 96 = 32
|
size: 32 | 96 = 32,
|
||||||
): string | undefined =>
|
useAuthentication = false
|
||||||
room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, size, size, 'crop', undefined, false) ??
|
): string | undefined => {
|
||||||
undefined;
|
const mxcUrl = room.getAvatarFallbackMember()?.getMxcAvatarUrl();
|
||||||
|
return mxcUrl
|
||||||
|
? mx.mxcUrlToHttp(mxcUrl, size, size, 'crop', undefined, false, useAuthentication) ?? undefined
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
export const trimReplyFromBody = (body: string): string => {
|
export const trimReplyFromBody = (body: string): string => {
|
||||||
const match = body.match(/^> <.+?> .+\n(>.*\n)*?\n/m);
|
const match = body.match(/^> <.+?> .+\n(>.*\n)*?\n/m);
|
||||||
|
|
|
@ -244,11 +244,7 @@ async function unignore(mx, userIds) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setPowerLevel(mx, roomId, userId, powerLevel) {
|
async function setPowerLevel(mx, roomId, userId, powerLevel) {
|
||||||
const room = mx.getRoom(roomId);
|
const result = await mx.setPowerLevel(roomId, userId, powerLevel);
|
||||||
|
|
||||||
const powerlevelEvent = room.currentState.getStateEvents('m.room.power_levels')[0];
|
|
||||||
|
|
||||||
const result = await mx.setPowerLevel(roomId, userId, powerLevel, powerlevelEvent);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,6 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
|
||||||
localStorage: global.localStorage,
|
localStorage: global.localStorage,
|
||||||
dbName: 'web-sync-store',
|
dbName: 'web-sync-store',
|
||||||
});
|
});
|
||||||
await indexedDBStore.startup();
|
|
||||||
|
|
||||||
const mx = createClient({
|
const mx = createClient({
|
||||||
baseUrl: session.baseUrl,
|
baseUrl: session.baseUrl,
|
||||||
|
@ -38,6 +37,7 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await mx.initCrypto();
|
await mx.initCrypto();
|
||||||
|
await indexedDBStore.startup();
|
||||||
|
|
||||||
mx.setGlobalErrorOnUnknownDevices(false);
|
mx.setGlobalErrorOnUnknownDevices(false);
|
||||||
mx.setMaxListeners(50);
|
mx.setMaxListeners(50);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const cons = {
|
const cons = {
|
||||||
version: '4.1.0',
|
version: '4.2.3',
|
||||||
secretKey: {
|
secretKey: {
|
||||||
ACCESS_TOKEN: 'cinny_access_token',
|
ACCESS_TOKEN: 'cinny_access_token',
|
||||||
DEVICE_ID: 'cinny_device_id',
|
DEVICE_ID: 'cinny_device_id',
|
||||||
|
|
|
@ -5,7 +5,7 @@ export const onLightFontWeight = createTheme(config.fontWeight, {
|
||||||
W100: '100',
|
W100: '100',
|
||||||
W200: '200',
|
W200: '200',
|
||||||
W300: '300',
|
W300: '300',
|
||||||
W400: '420',
|
W400: '400',
|
||||||
W500: '500',
|
W500: '500',
|
||||||
W600: '600',
|
W600: '600',
|
||||||
W700: '700',
|
W700: '700',
|
||||||
|
@ -17,10 +17,10 @@ export const onDarkFontWeight = createTheme(config.fontWeight, {
|
||||||
W100: '100',
|
W100: '100',
|
||||||
W200: '200',
|
W200: '200',
|
||||||
W300: '300',
|
W300: '300',
|
||||||
W400: '350',
|
W400: '400',
|
||||||
W500: '450',
|
W500: '500',
|
||||||
W600: '550',
|
W600: '600',
|
||||||
W700: '650',
|
W700: '700',
|
||||||
W800: '750',
|
W800: '800',
|
||||||
W900: '850',
|
W900: '900',
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,7 @@ import './index.scss';
|
||||||
|
|
||||||
import settings from './client/state/settings';
|
import settings from './client/state/settings';
|
||||||
|
|
||||||
|
import { trimTrailingSlash } from './app/utils/common';
|
||||||
import App from './app/pages/App';
|
import App from './app/pages/App';
|
||||||
|
|
||||||
// import i18n (needs to be bundled ;))
|
// import i18n (needs to be bundled ;))
|
||||||
|
@ -20,6 +21,26 @@ import './app/i18n';
|
||||||
document.body.classList.add(configClass, varsClass);
|
document.body.classList.add(configClass, varsClass);
|
||||||
settings.applyTheme();
|
settings.applyTheme();
|
||||||
|
|
||||||
|
// Register Service Worker
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
const swUrl =
|
||||||
|
import.meta.env.MODE === 'production'
|
||||||
|
? `${trimTrailingSlash(import.meta.env.BASE_URL)}/sw.js`
|
||||||
|
: `/dev-sw.js?dev-sw`;
|
||||||
|
|
||||||
|
navigator.serviceWorker.register(swUrl);
|
||||||
|
navigator.serviceWorker.addEventListener('message', (event) => {
|
||||||
|
if (event.data?.type === 'token' && event.data?.responseKey) {
|
||||||
|
// Get the token for SW.
|
||||||
|
const token = localStorage.getItem('cinny_access_token') ?? undefined;
|
||||||
|
event.source!.postMessage({
|
||||||
|
responseKey: event.data.responseKey,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const mountApp = () => {
|
const mountApp = () => {
|
||||||
const rootContainer = document.getElementById('root');
|
const rootContainer = document.getElementById('root');
|
||||||
|
|
||||||
|
|
52
src/sw.ts
Normal file
52
src/sw.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/// <reference lib="WebWorker" />
|
||||||
|
|
||||||
|
export type {};
|
||||||
|
declare const self: ServiceWorkerGlobalScope;
|
||||||
|
|
||||||
|
async function askForAccessToken(client: Client): Promise<string | undefined> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const responseKey = Math.random().toString(36);
|
||||||
|
const listener = (event: ExtendableMessageEvent) => {
|
||||||
|
if (event.data.responseKey !== responseKey) return;
|
||||||
|
resolve(event.data.token);
|
||||||
|
self.removeEventListener('message', listener);
|
||||||
|
};
|
||||||
|
self.addEventListener('message', listener);
|
||||||
|
client.postMessage({ responseKey, type: 'token' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchConfig(token?: string): RequestInit | undefined {
|
||||||
|
if (!token) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
cache: 'default',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener('activate', (event: ExtendableEvent) => {
|
||||||
|
event.waitUntil(clients.claim());
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', (event: FetchEvent) => {
|
||||||
|
const { url, method } = event.request;
|
||||||
|
if (method !== 'GET') return;
|
||||||
|
if (
|
||||||
|
!url.includes('/_matrix/client/v1/media/download') &&
|
||||||
|
!url.includes('/_matrix/client/v1/media/thumbnail')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.respondWith(
|
||||||
|
(async (): Promise<Response> => {
|
||||||
|
const client = await self.clients.get(event.clientId);
|
||||||
|
let token: string | undefined;
|
||||||
|
if (client) token = await askForAccessToken(client);
|
||||||
|
|
||||||
|
return fetch(url, fetchConfig(token));
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
});
|
|
@ -10,7 +10,8 @@
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"lib": ["ES2016", "DOM"]
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "dist"],
|
"exclude": ["node_modules", "dist"],
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
|
||||||
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
|
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill';
|
||||||
import inject from '@rollup/plugin-inject';
|
import inject from '@rollup/plugin-inject';
|
||||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
import buildConfig from './build.config';
|
import buildConfig from './build.config';
|
||||||
|
|
||||||
const copyFiles = {
|
const copyFiles = {
|
||||||
|
@ -54,11 +55,11 @@ export default defineConfig({
|
||||||
port: 8080,
|
port: 8080,
|
||||||
host: true,
|
host: true,
|
||||||
proxy: {
|
proxy: {
|
||||||
"^\\/.*?\\/olm\\.wasm$": {
|
'^\\/.*?\\/olm\\.wasm$': {
|
||||||
target: 'http://localhost:8080',
|
target: 'http://localhost:8080',
|
||||||
rewrite: () => '/olm.wasm'
|
rewrite: () => '/olm.wasm',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
topLevelAwait({
|
topLevelAwait({
|
||||||
|
@ -71,6 +72,20 @@ export default defineConfig({
|
||||||
vanillaExtractPlugin(),
|
vanillaExtractPlugin(),
|
||||||
wasm(),
|
wasm(),
|
||||||
react(),
|
react(),
|
||||||
|
VitePWA({
|
||||||
|
srcDir: 'src',
|
||||||
|
filename: 'sw.ts',
|
||||||
|
strategies: 'injectManifest',
|
||||||
|
injectRegister: false,
|
||||||
|
manifest: false,
|
||||||
|
injectManifest: {
|
||||||
|
injectionPoint: undefined,
|
||||||
|
},
|
||||||
|
devOptions: {
|
||||||
|
enabled: true,
|
||||||
|
type: 'module'
|
||||||
|
}
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
esbuildOptions: {
|
esbuildOptions: {
|
||||||
|
|
Loading…
Reference in a new issue