115 Commits

Author SHA1 Message Date
Paul Makles
41f47a1a3f merge: pull request #1143 from Asraye/fix/member-sidebar 2025-09-07 13:09:46 +01:00
Paul Makles
c6130a19bf merge: pull request #1109 from Docteh/smaller3 2025-09-07 13:06:39 +01:00
Paul Makles
3c7cb2a222 merge: pull request #1141 from Asraye/fixemoji-pack 2025-09-07 13:05:41 +01:00
Paul Makles
2625b37f70 merge: pull request #1142 from Asraye/ignore-default-category 2025-09-07 13:04:47 +01:00
Paul Makles
ca3613725a merge: pull request #1137 from Asraye/master 2025-09-07 13:04:05 +01:00
Paul Makles
707d0a2d7d merge: pull request #1146 from Asraye/fix/attachment-actions 2025-09-07 13:01:20 +01:00
Asraye
33e686ab1b fix: correct open/download link behaviour 2025-09-04 04:23:09 +10:00
Asraye
f22e6fa9fe fix: correct open/download link behaviour 2025-09-04 04:22:20 +10:00
Asraye
2e2886f01b fix: filter out members without ViewChannel
Small fix to add a ViewChannel check when rendering members in the sidebar
2025-09-02 16:14:10 +10:00
Asraye
cbdeb1ba6d FIX: Ignore Default category (parity with beta)
Just a little change so people still using Revite don't need to see an ugly "Default" category.
2025-08-31 03:47:43 +10:00
Asraye
608b0a3ef2 FIX: Emoji pack now persists on reload 2025-08-29 21:55:56 +10:00
Asraye
59226959ba Add scroll on mobile 2025-08-21 16:40:51 +10:00
izzy
9d82873d2a chore: make sure role ordering updates are instantenous 2025-08-07 16:57:20 +02:00
izzy
56af4b9423 chore: add default.nix 2025-08-07 12:04:45 +02:00
izzy
cce94d2e0e chore: bump lang submodule 2025-08-07 12:03:54 +02:00
izzy
8ba7a769b3 merge: remote-tracking branch 'origin/role-reordering' 2025-08-07 12:03:35 +02:00
Paul Makles
92e84309ec merge: pull request #1123 from Asraye/patch 2025-08-04 18:03:53 +01:00
Paul Makles
dd5548c5e6 merge: pull request #1126 from solunareclipse1/master 2025-08-04 17:44:49 +01:00
Declan Chidlow
e7d7420d5b feat: Finish crappy role ranking implementation that works but is full of sin 2025-08-04 22:28:24 +08:00
izzy
ee3b54b373 docs: shorten age gate message 2025-07-25 20:34:45 +01:00
izzy
3dc9f6b045 feat: geoblock age restricted content
https://www.ofcom.org.uk/online-safety/protecting-children/online-age-checks-must-be-in-force-from-tomorrow
https://petition.parliament.uk/petitions/722903
https://wikimediafoundation.org/news/2025/07/17/wikimedia-foundation-challenges-uk-online-safety-act-regulations/
2025-07-25 19:49:00 +01:00
izzy
2a1f7cb8bb chore: bump lang submodule 2025-07-25 19:48:16 +01:00
solunareclipse1
1136e8e31b fix: Make animated WebP emotes properly animate
Signed-off-by: solunareclipse1 <sol@solunareclipse1.net>
2025-07-10 23:41:57 -05:00
Asraye
5337031359 Update EmbedInvite.tsx 2025-06-12 16:26:09 +10:00
Asraye
670c4e5f6e Update EmbedInvite.tsx 2025-06-12 15:46:07 +10:00
Declan Chidlow
20c8dde197 feat: Add role re-ordering
Doesn't currently update order in UI
2025-06-12 12:33:39 +08:00
Declan Chidlow
ddaca7b0c4 feat: remove legacy role ranking 2025-06-12 09:59:57 +08:00
Asraye
94fab0852f Ugly ahh patch 2025-06-11 18:08:33 +10:00
Kyle Kienapfel
2c41c5789c feat: drastically shrink the docker image
Right now the generated docker container contains a copy of the development environment used to compile the revite interface.
This PR loses about 440MB compressed, or around 800MB uncompressed from the final image size. node in the final image has been bumped from 16 to 24, sirv-cli, and the dependencies for inject.js have been updated
2025-05-18 01:59:51 -07:00
Kyle Kienapfel
184754ca0f chore: have GHA generate ARM64 image, add description to images
The box where githubs container website complains about a lack
a description is really big. Okay its not important.
2025-05-18 01:29:54 -07:00
Kyle Kienapfel
6bacbfbb56 fix: move disabled-js.svg to proper location 2025-05-18 01:29:17 -07:00
Paul Makles
9e293e0a30 merge: pull request #1108 from Zomatree/feat/reset-bot-password 2025-05-17 13:27:04 +01:00
Paul Makles
d957fbd5dc merge: pull request #1103 from Docteh/automation 2025-05-17 13:26:33 +01:00
Zomatree
0a8b70c8ed chore: update submodule 2025-05-17 13:10:11 +01:00
Zomatree
07d74a4ef4 feat: reset bot token button 2025-05-17 12:59:39 +01:00
Kyle Kienapfel
38ab8a7801 chore: update link to contribution guide in PR template
[skip ci]
2025-05-12 16:14:26 -07:00
Paul Makles
bccf1eebae merge: pull request #1102 from Docteh/docky 2025-05-12 11:16:25 +01:00
Kyle Kienapfel
135dbe2da4 fix: Remove caching from GitHub actions to allow compilation of docker container
The GitHub action `actions/cache@v2` is no longer available. Last successful docker build was 2024-11-15

I've removed the caching, as merely upgrading the version of the cache script did not fix the problem. Since the revoltchat/revite is deprecated I believe that the lack of caching in builds shouldn't be an undue burden on GitHub. Only 9 commits have been made here since builds stopped working

The logic for publishing Docker containers has been shuffled so that the build is executed only once when publishing.

I have tested building to GitHub's container registry, but not to dockerhub, I do not forsee any issues there.

Main reason for doing this, is that a fix for attachment downloads (https://github.com/revoltchat/revite/pull/1067) is not easily available to anyone running self-hosted.
2025-05-11 13:21:08 -07:00
izzy
f95d3d27d8 chore: remove the extra string 2025-05-09 08:48:41 +01:00
izzy
3d41a016cf fix: login error messages not rendering properly 2025-05-09 08:42:58 +01:00
Paul Makles
f9c2fcec56 chore: bump language submodule 2025-01-29 13:42:20 +00:00
Paul Makles
18243f0207 chore: always target blank for download button 2025-01-29 13:41:54 +00:00
Declan Chidlow
7750276554 fix: remove /download from attachments (#1067) 2025-01-29 13:39:17 +00:00
Declan Chidlow
97a6c7d399 fix: remove ability for owners to leave server from ServerInfo modal (#1070)
Co-authored-by: Declan Chidlow <git.rh92c@8shield.net>
2025-01-29 13:39:03 +00:00
Paul Makles
ac2beaf549 chore: update URL to FAQ 2024-12-17 17:35:55 +00:00
Amy
bf951e59ee fix: appending mention to draft content (#1065) 2024-12-17 17:29:57 +00:00
Paul Makles
03f9f7673d merge: pull request #1050 from JackDotJS/fix-blank-category-name 2024-12-17 17:29:16 +00:00
Paul Makles
478d375125 fix: add try-catch block around md renderer
fix: update recursive match to block numbered lists
2024-11-15 13:09:40 +00:00
Paul Makles
cb6296a96e chore: update languages submodule 2024-11-06 09:51:44 +00:00
Paul Makles
48a9d7d370 chore: update donation links 2024-11-06 09:51:35 +00:00
Paul Makles
5de18192b2 chore: delete .env.production 2024-10-27 22:31:42 +00:00
Paul Makles
ef4218d12b merge branch: 'insert/member-subscriptions' 2024-10-27 22:00:22 +00:00
Paul Makles
df4f6578f7 feat: add session token to file uploads 2024-10-27 21:59:16 +00:00
JackDotJS
28c897ac3d fix(settings): fixed inability to rename categories when they have blank names
Signed-off-by: JackDotJS <jackdotjs@proton.me>
2024-10-27 11:14:04 -07:00
Paul Makles
86e8424e46 merge: pull request #1035 from DeclanChidlow/server-leave-mobile 2024-10-16 12:53:00 +01:00
Declan Chidlow
5e66bc8dc4 fix: Add ability to leave server through server info screen 2024-10-15 20:23:36 +08:00
Paul Makles
00e6ead9bd chore: bump revolt.js submodule 2024-07-27 15:18:23 +02:00
Paul Makles
0f0808aa56 feat: add report button to user profiles 2024-06-23 12:23:42 +01:00
Paul Makles
c972e6813f fix: should always call subscribe 2024-06-19 18:16:30 +01:00
Paul Makles
61304f18c2 feat: implement support for Subscribe event 2024-06-19 17:40:59 +01:00
Paul Makles
2722d0a854 chore: remove this so people don't click it 2024-06-09 13:57:36 +01:00
Paul Makles
dfd96c449a chore: synchronise locales 2024-06-09 13:55:52 +01:00
Paul Makles
9eca58dda1 merge: pull request #967 from revoltchat/rexo/fix/remove-deprecated-emoji 2024-06-09 13:51:11 +01:00
Sophie L
6cf0ef95ad fix: remove (broken/deprecated) built-in custom emoji
these have been broken for a while due to the data loss when insert's server failed, and were also deprecated upon the addition of custom server emoji.

they were meant to be temporary anyway, and other clients (including the new web app) don't implement them, so let's remove them to avoid confusion.
2024-05-16 15:29:53 +01:00
insertish
d3661170a4 ci: synced local '.github/workflows/triage_pr.yml' with remote 'workflows/triage_pr.yml'
[skip ci]
2024-03-28 13:37:37 +00:00
Paul Makles
5b6546b761 fix: allow retrying sending files 2024-03-15 19:50:17 +00:00
Sophie L
c25ecc12b2 chore: rm mirroring script 2024-03-13 23:51:56 +00:00
Paul Makles
60fdb5c091 docs: legacy release procedure 2024-03-13 23:41:54 +00:00
Bob Bobs
472e6e07b5 fix: only delete session if error is Unauthorized 2024-03-13 22:55:12 +00:00
Paul Makles
15e8e10151 merge: pull request #864
fix(home): inconsistent discover button height
2023-11-11 19:16:59 +00:00
Paul Makles
782a9b54ba chore: update dest directory 2023-10-04 19:22:54 +01:00
Paul Makles
f52ede1c21 chore: bump languages submodule 2023-10-04 19:18:57 +01:00
Lea
7fef354fe6 fix: appeal to monkey brain 2023-10-04 19:13:07 +01:00
Lea
d4e0f19f95 feat: admin panel link for system message user 2023-10-04 19:13:07 +01:00
wangdejiang
fe63c6633f chore: resolve ts-to-JSX type hints 2023-08-25 18:44:57 +01:00
Sophie L
be3565871c feat: translate account management options 2023-08-25 18:44:34 +01:00
Paul Makles
bc604c0157 chore: bump language submodule 2023-08-01 23:10:06 +01:00
Paul Makles
8c183b530b feat: add admin shortcuts for convenience 2023-08-01 23:09:50 +01:00
Paul Makles
9bbc22b091 chore: update lang submodule; disable typechecking for docker build 2023-07-06 19:01:29 +01:00
Paul Makles
0d5ffb4df8 chore: update alerts to use new health service 2023-07-06 19:00:30 +01:00
Paul Makles
9d83d7cc1c chore: bump language submodule 2023-07-02 08:46:57 +01:00
FluffyCookie000
534b9227de fix: usernames are evolving blog post link (#895) 2023-07-02 08:46:34 +01:00
Paul Makles
5363ff2572 feat: raise katex limit to 512, upgrade dependencies 2023-06-18 10:22:01 +02:00
Paul Makles
3f1c2be709 chore: update language submodule 2023-06-17 16:11:32 +01:00
Paul Makles
04027658e6 feat: add more validation to KaTeX in markdown 2023-06-17 16:11:22 +01:00
Paul Makles
f1594a7b36 chore: show error on modify account 2023-06-15 18:43:36 +01:00
Paul Makles
fa0fcedce5 chore: we cooking now frfr 2023-06-13 22:04:14 +01:00
Paul Makles
70f5e6fc7e chore: monkey patch ulid parse error 2023-06-12 09:58:13 +01:00
Paul Makles
9b9ec867da chore: update typing indicator to at least use display name 2023-06-12 09:52:00 +01:00
Paul Makles
fead4ca879 feat: always show link warning for masked links 2023-06-12 09:51:17 +01:00
Paul Makles
449eee006d fix: copy full username from user profile 2023-06-11 22:26:56 +01:00
Paul Makles
cf691a8462 fix: use display name where available for friends 2023-06-11 22:22:52 +01:00
Paul Makles
c5ca579c95 chore: add helpful hint for new username format 2023-06-11 19:00:34 +01:00
Paul Makles
636367faea chore: strip emoji picker experiment
chore: use discrim
2023-06-11 18:36:07 +01:00
Paul Makles
f20ada7c49 feat: add changelog entry in preparation for update 2023-06-11 17:31:22 +01:00
Paul Makles
75d07ffe0f chore: move badges to top half of user profile 2023-06-11 13:02:38 +01:00
Paul Makles
5b600bec20 feat: back port discriminators and display names 2023-06-11 12:44:05 +01:00
temptrash
86218a7272 feat: swap pomelo usernames for discriminators 2023-06-10 15:01:12 +02:00
Paul Makles
9bc052ea87 chore: don't update the default .env file 2023-06-03 19:29:50 +01:00
Paul Makles
1862db89a3 feat: add support for webhooks
fix: removing reactions crashes client
2023-06-03 19:29:14 +01:00
Paul Makles
00708bb8f4 feat: include message context (if any) when reporting user 2023-05-31 12:06:31 +01:00
Paul Makles
7e87265ccb chore: update lang submodule 2023-05-31 11:50:35 +01:00
kate
d55d86aa3c fix: comment out link for nightly desktop build (#882) 2023-05-06 19:19:45 +01:00
Paul Makles
c426b6c184 chore: bump components and lang submodules 2023-04-21 21:12:20 +01:00
Paul Makles
4d58ff06f3 fix: weird typing errors that cropped up 2023-04-21 21:05:29 +01:00
Paul Makles
9f9902ffb1 fix: reset member sidebar fetch status when we become ready 2023-04-21 20:52:23 +01:00
Ashley
e8989fcffa add a blank space to re run CI 2023-03-22 18:40:24 +03:00
Ashley
06dea9300c fix inconsonant height in buttons 2023-03-20 18:11:10 +03:00
Paul Makles
c13896244c chore: update components submodule 2023-03-18 22:22:02 +00:00
Paul Makles
e17e556ee6 chore: add notice to emoji menu 2023-03-14 21:57:47 +00:00
Paul Makles
7441569cfa chore: enable emoji picker by default 2023-03-14 21:48:19 +00:00
Paul Makles
eff1de7c78 chore: add notices throughout app about development 2023-03-14 21:48:13 +00:00
Paul Makles
b733e13ec2 merge: branch 'master' of https://github.com/revoltchat/revite 2023-03-14 21:16:16 +00:00
Declan Chidlow
e68ceac4f6 fix: overflow management in voice menus (#849) 2023-03-14 21:16:05 +00:00
Paul Makles
3f536589e5 chore: update languages submodule 2023-03-14 21:15:04 +00:00
86 changed files with 1966 additions and 714 deletions

5
.env
View File

@@ -1,2 +1,5 @@
VITE_API_URL=https://api.revolt.chat
# VITE_API_URL=https://api.revolt.chat
# VITE_API_URL=https://app.revolt.chat/api
# VITE_API_URL=http://local.revolt.chat:8000
VITE_API_URL=https://app.revolt.chat/api
VITE_THEMES_URL=https://themes.revolt.chat

View File

@@ -1,6 +1,6 @@
## Please make sure to check the following tasks before opening and submitting a PR
* [ ] I understand and have followed the [contribution guide](https://github.com/revoltchat/revolt/discussions/282)
* [ ] I understand and have followed the [contribution guide](https://developers.revolt.chat/contrib.html)
* [ ] I have tested my changes locally and they are working as intended
* [ ] These changes do not have any notable side effects on other Revolt projects
* [ ] (optional) I have opened a pull request on [the translation repository](https://github.com/revoltchat/translations)

View File

@@ -30,82 +30,44 @@ on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
architecture: [linux/amd64]
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: "recursive"
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache/${{ matrix.architecture }}
key: ${{ runner.os }}-buildx-${{ matrix.architecture }}-${{ github.sha }}
- name: Build
uses: docker/build-push-action@v2
with:
context: .
platforms: ${{ matrix.architecture }}
cache-from: type=local,src=/tmp/.buildx-cache/${{ matrix.architecture }}
cache-to: type=local,dest=/tmp/.buildx-cache-new/${{ matrix.architecture }},mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache/${{ matrix.architecture }}
mv /tmp/.buildx-cache-new/${{ matrix.architecture }} /tmp/.buildx-cache/${{ matrix.architecture }}
publish:
needs: [test]
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: "recursive"
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Cache amd64 Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache/linux/amd64
key: ${{ runner.os }}-buildx-linux/amd64-${{ github.sha }}
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
uses: docker/metadata-action@v5
with:
images: revoltchat/client, ghcr.io/revoltchat/client
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
- name: Login to DockerHub
uses: docker/login-action@v1
if: github.event_name != 'pull_request'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v3
if: github.event_name != 'pull_request'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and publish
uses: docker/build-push-action@v2
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=local,src=/tmp/.buildx-cache/linux/amd64
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache

View File

@@ -1,16 +0,0 @@
name: Mirroring
on:
push:
branches:
- "master"
jobs:
to_gitlab:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url: git@gitlab.com:insert/revolt-vite.git
ssh_private_key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}

View File

@@ -15,7 +15,7 @@ jobs:
gh api graphql -f query='
query {
organization(login: "revoltchat"){
projectV2(number: 3) {
projectV2(number: 5) {
id
fields(first:20) {
nodes {
@@ -35,7 +35,7 @@ jobs:
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="🆕 Untriaged") |.id' project_data.json) >> $GITHUB_ENV
- name: Add PR to project
env:

View File

@@ -1,4 +1,5 @@
FROM node:16-buster AS builder
# syntax=docker.io/docker/dockerfile:1.7-labs
FROM --platform=$BUILDPLATFORM node:16-buster AS builder
WORKDIR /usr/src/app
COPY . .
@@ -6,13 +7,15 @@ COPY .env.build .env
RUN yarn install --frozen-lockfile
RUN yarn build:deps
RUN yarn typecheck
# RUN yarn typecheck # lol no
RUN yarn build:highmem
RUN yarn workspaces focus --production --all
FROM node:16-alpine
FROM node:24-alpine
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app .
COPY docker/package.json docker/yarn.lock .
RUN yarn install --frozen-lockfile
COPY --from=builder --exclude=package.json --exclude=yarn.lock --exclude=.yarn* --exclude=.git --exclude=external --exclude=node_modules /usr/src/app .
EXPOSE 5000
CMD [ "yarn", "start:inject" ]

View File

@@ -1,3 +1,30 @@
# Deprecation Notice
This project is deprecated, however it still may receive maintenance updates.
PRs for small fixes are more than welcome.
## Deploying a new release
Ensure `.env.local` points to `https://app.revolt.chat/api`.
```bash
cd ~/deployments/revite
git pull
git submodule update
# check:
git status
export REVOLT_SAAS_BRANCH=revite/main
export REMOTE=root@production
scripts/publish.sh
# SSH in and restart revite:
ssh $REMOTE
tmux a -t 4
```
# Revite
## Description

8
default.nix Normal file
View File

@@ -0,0 +1,8 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell rec {
buildInputs = [
pkgs.nodejs
pkgs.nodejs.pkgs.yarn
];
}

16
docker/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "inject-and-sirv",
"version": "0.0.1",
"scripts": {
"start:inject": "node scripts/inject.js && sirv dist_injected --port 5000 --cors --single --host"
},
"private": true,
"dependencies": {
"fs-extra": "^11.3.0",
"klaw": "^4.1.0",
"sirv-cli": "^3.0.1"
},
"engines": {
"node": ">=18"
}
}

116
docker/yarn.lock Normal file
View File

@@ -0,0 +1,116 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@polka/url@^1.0.0-next.24":
version "1.0.0-next.29"
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.29.tgz#5a40109a1ab5f84d6fd8fc928b19f367cbe7e7b1"
integrity sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==
console-clear@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/console-clear/-/console-clear-1.1.1.tgz#995e20cbfbf14dd792b672cde387bd128d674bf7"
integrity sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==
fs-extra@^11.3.0:
version "11.3.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
get-port@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
klaw@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/klaw/-/klaw-4.1.0.tgz#5df608067d8cb62bbfb24374f8e5d956323338f3"
integrity sha512-1zGZ9MF9H22UnkpVeuaGKOjfA2t6WrfdrJmGjy16ykcjnKQDmHVX+KI477rpbGevz/5FD4MC3xf1oxylBgcaQw==
kleur@^4.1.4:
version "4.1.5"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
local-access@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/local-access/-/local-access-1.1.0.tgz#e007c76ba2ca83d5877ba1a125fc8dfe23ba4798"
integrity sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw==
mri@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
mrmime@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.1.tgz#bc3e87f7987853a54c9850eeb1f1078cd44adddc"
integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==
sade@^1.6.0:
version "1.8.1"
resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
dependencies:
mri "^1.1.0"
semiver@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/semiver/-/semiver-1.1.0.tgz#9c97fb02c21c7ce4fcf1b73e2c7a24324bdddd5f"
integrity sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==
sirv-cli@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/sirv-cli/-/sirv-cli-3.0.1.tgz#9a53e4fa85fdc08d54a76fd76a7c866cd4c3988b"
integrity sha512-ICXaF2u6IQhLZ0EXF6nqUF4YODfSQSt+mGykt4qqO5rY+oIiwdg7B8w2PVDBJlQulaS2a3J8666CUoDoAuCGvg==
dependencies:
console-clear "^1.1.0"
get-port "^5.1.1"
kleur "^4.1.4"
local-access "^1.0.1"
sade "^1.6.0"
semiver "^1.0.0"
sirv "^3.0.0"
tinydate "^1.0.0"
sirv@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-3.0.1.tgz#32a844794655b727f9e2867b777e0060fbe07bf3"
integrity sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==
dependencies:
"@polka/url" "^1.0.0-next.24"
mrmime "^2.0.0"
totalist "^3.0.0"
tinydate@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/tinydate/-/tinydate-1.3.0.tgz#e6ca8e5a22b51bb4ea1c3a2a4fd1352dbd4c57fb"
integrity sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==
totalist@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
universalify@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==

2
external/lang vendored

View File

@@ -44,8 +44,10 @@
}
},
"dependencies": {
"@revoltchat/rehype-katex": "6.0.3-patch.1",
"fs-extra": "^10.0.0",
"klaw": "^3.0.0",
"lottie-react": "^2.4.0",
"sirv-cli": "^1.0.14",
"vite": "^3.0.5"
},
@@ -136,7 +138,6 @@
"react-router-dom": "^5.2.0",
"react-scroll": "^1.8.2",
"react-virtuoso": "^2.12.0",
"rehype-katex": "^6.0.2",
"rehype-prism": "^2.1.3",
"rehype-react": "^7.1.1",
"remark-breaks": "^3.0.2",

View File

Before

Width:  |  Height:  |  Size: 828 B

After

Width:  |  Height:  |  Size: 828 B

View File

@@ -8,10 +8,10 @@ if [ -z "$REMOTE" ]; then
fi
# Remote Directory
REMOTE_DIR=/root/revite
REMOTE_DIR=/root/deployments/revite
# Post-install script
POST_INSTALL="pm2 restart revite"
POST_INSTALL=""
# Assets
export REVOLT_SAAS=https://github.com/revoltchat/assets

View File

@@ -1,10 +1,17 @@
import Lottie, { LottieRefCurrentProps } from "lottie-react";
import { JSX } from "preact";
import usernameAnim from "../controllers/modals/components/legacy/usernameUpdateLottie.json";
type Element =
| string
| {
type: "image";
src: string;
shadow?: boolean;
};
}
| { type: "element"; element: JSX.Element };
export interface ChangelogPost {
date: Date;
@@ -43,6 +50,33 @@ export const changelogEntries: Record<number, ChangelogPost> = {
"If you want to learn more about how we're making Revolt safer for you, check out our new blog post :point_right: [https://revolt.chat/posts/improving-user-safety](https://revolt.chat/posts/improving-user-safety)",
],
},
3: {
date: new Date("2023-06-11T15:00:00.000Z"),
title: "Usernames are Changing",
content: [
{
type: "element",
element: (
<Lottie
animationData={usernameAnim}
style={{
background: "var(--secondary-background)",
borderRadius: "6px",
}}
/>
),
},
"Revolt has undergone a significant change to its username system, transitioning from unique username handles to a new system of display names and usernames with four-digit number tags called discriminators. The four-digit number tags serve as identifiers to differentiate users with the same username, allowing individuals to select desired usernames that reflect their identity.",
{
type: "element",
element: (
<a href="https://revolt.chat/posts/evolving-usernames">
Read more on our blog!
</a>
),
},
],
},
};
export const changelogEntryArray = Object.keys(changelogEntries).map(

View File

@@ -1848,109 +1848,109 @@ export const emojiDictionary = {
england: "🏴󠁧󠁢󠁥󠁮󠁧󠁿",
scotland: "🏴󠁧󠁢󠁳󠁣󠁴󠁿",
wales: "🏴󠁧󠁢󠁷󠁬󠁳󠁿",
...{
1984: "custom:1984.gif",
KekW: "custom:KekW.png",
amogus: "custom:amogus.gif",
awaa: "custom:awaa.png",
boohoo: "custom:boohoo.png",
boohoo_goes_hard: "custom:boohoo_goes_hard.png",
boohoo_shaken: "custom:boohoo_shaken.png",
cat_arrival: "custom:cat_arrival.gif",
cat_awson: "custom:cat_awson.png",
cat_blob: "custom:cat_blob.png",
cat_bonk: "custom:cat_bonk.png",
cat_concern: "custom:cat_concern.png",
cat_fast: "custom:cat_fast.gif",
cat_kitty: "custom:cat_kitty.png",
cat_lick: "custom:cat_lick.gif",
cat_not_like: "custom:cat_not_like.png",
cat_put: "custom:cat_put.gif",
cat_pwease: "custom:cat_pwease.png",
cat_rage: "custom:cat_rage.png",
cat_sad: "custom:cat_sad.png",
cat_snuff: "custom:cat_snuff.gif",
cat_spin: "custom:cat_spin.gif",
cat_squish: "custom:cat_squish.gif",
cat_stare: "custom:cat_stare.gif",
cat_steal: "custom:cat_steal.gif",
cat_sussy: "custom:cat_sussy.gif",
clueless: "custom:clueless.png",
death: "custom:death.gif",
developers: "custom:developers.gif",
fastwawa: "custom:fastwawa.gif",
ferris: "custom:ferris.png",
ferris_bongo: "custom:ferris_bongo.gif",
ferris_nom: "custom:ferris_nom.png",
ferris_pensive: "custom:ferris_pensive.png",
ferris_unsafe: "custom:ferris_unsafe.png",
flesh: "custom:flesh.png",
flooshed: "custom:flooshed.png",
flosh: "custom:flosh.png",
flushee: "custom:flushee.png",
forgor: "custom:forgor.png",
hollow: "custom:hollow.png",
john: "custom:john.png",
lightspeed: "custom:lightspeed.png",
little_guy: "custom:little_guy.png",
lmaoooo: "custom:lmaoooo.gif",
lol: "custom:lol.png",
looking: "custom:looking.gif",
marie: "custom:marie.png",
marie_furret: "custom:marie_furret.gif",
marie_smug: "custom:marie_smug.png",
megumin: "custom:megumin.png",
michi_above: "custom:michi_above.png",
michi_awww: "custom:michi_awww.gif",
michi_drag: "custom:michi_drag.gif",
michi_flustered: "custom:michi_flustered.png",
michi_glare: "custom:michi_glare.png",
michi_sus: "custom:michi_sus.png",
monkaS: "custom:monkaS.png",
monkaStare: "custom:monkaStare.png",
monkey_grr: "custom:monkey_grr.png",
monkey_pensive: "custom:monkey_pensive.png",
monkey_zany: "custom:monkey_zany.png",
nazu_sit: "custom:nazu_sit.png",
nazu_sus: "custom:nazu_sus.png",
ok_and: "custom:ok_and.gif",
owo: "custom:owo.png",
pat: "custom:pat.png",
pointThink: "custom:pointThink.png",
rainbowHype: "custom:rainbowHype.gif",
rawr: "custom:rawr.png",
rember: "custom:rember.png",
revolt: "custom:revolt.png",
sickly: "custom:sickly.png",
stare: "custom:stare.png",
tfyoulookingat: "custom:tfyoulookingat.png",
thanks: "custom:thanks.png",
thonk: "custom:thonk.png",
trol: "custom:trol.png",
troll_smile: "custom:troll_smile.gif",
uber: "custom:uber.png",
ubertroll: "custom:ubertroll.png",
verycool: "custom:verycool.png",
verygood: "custom:verygood.png",
wawafast: "custom:wawafast.gif",
wawastance: "custom:wawastance.png",
yeahokayyy: "custom:yeahokayyy.png",
yed: "custom:yed.png",
yems: "custom:yems.png",
michael: "custom:michael.gif",
charle: "custom:charle.gif",
sadge: "custom:sadge.webp",
sus: "custom:sus.webp",
chade: "custom:chade.gif",
gigachad: "custom:gigachad.webp",
sippy: "custom:sippy.webp",
ayame_heart: "custom:ayame_heart.png",
catgirl_peek: "custom:catgirl_peek.png",
girl_happy: "custom:girl_happy.png",
hug_plushie: "custom:hug_plushie.png",
huggies: "custom:huggies.png",
noted: "custom:noted.gif",
waving: "custom:waving.png",
mogusvented: "custom:mogusvented.png",
},
// ...{
// 1984: "custom:1984.gif",
// KekW: "custom:KekW.png",
// amogus: "custom:amogus.gif",
// awaa: "custom:awaa.png",
// boohoo: "custom:boohoo.png",
// boohoo_goes_hard: "custom:boohoo_goes_hard.png",
// boohoo_shaken: "custom:boohoo_shaken.png",
// cat_arrival: "custom:cat_arrival.gif",
// cat_awson: "custom:cat_awson.png",
// cat_blob: "custom:cat_blob.png",
// cat_bonk: "custom:cat_bonk.png",
// cat_concern: "custom:cat_concern.png",
// cat_fast: "custom:cat_fast.gif",
// cat_kitty: "custom:cat_kitty.png",
// cat_lick: "custom:cat_lick.gif",
// cat_not_like: "custom:cat_not_like.png",
// cat_put: "custom:cat_put.gif",
// cat_pwease: "custom:cat_pwease.png",
// cat_rage: "custom:cat_rage.png",
// cat_sad: "custom:cat_sad.png",
// cat_snuff: "custom:cat_snuff.gif",
// cat_spin: "custom:cat_spin.gif",
// cat_squish: "custom:cat_squish.gif",
// cat_stare: "custom:cat_stare.gif",
// cat_steal: "custom:cat_steal.gif",
// cat_sussy: "custom:cat_sussy.gif",
// clueless: "custom:clueless.png",
// death: "custom:death.gif",
// developers: "custom:developers.gif",
// fastwawa: "custom:fastwawa.gif",
// ferris: "custom:ferris.png",
// ferris_bongo: "custom:ferris_bongo.gif",
// ferris_nom: "custom:ferris_nom.png",
// ferris_pensive: "custom:ferris_pensive.png",
// ferris_unsafe: "custom:ferris_unsafe.png",
// flesh: "custom:flesh.png",
// flooshed: "custom:flooshed.png",
// flosh: "custom:flosh.png",
// flushee: "custom:flushee.png",
// forgor: "custom:forgor.png",
// hollow: "custom:hollow.png",
// john: "custom:john.png",
// lightspeed: "custom:lightspeed.png",
// little_guy: "custom:little_guy.png",
// lmaoooo: "custom:lmaoooo.gif",
// lol: "custom:lol.png",
// looking: "custom:looking.gif",
// marie: "custom:marie.png",
// marie_furret: "custom:marie_furret.gif",
// marie_smug: "custom:marie_smug.png",
// megumin: "custom:megumin.png",
// michi_above: "custom:michi_above.png",
// michi_awww: "custom:michi_awww.gif",
// michi_drag: "custom:michi_drag.gif",
// michi_flustered: "custom:michi_flustered.png",
// michi_glare: "custom:michi_glare.png",
// michi_sus: "custom:michi_sus.png",
// monkaS: "custom:monkaS.png",
// monkaStare: "custom:monkaStare.png",
// monkey_grr: "custom:monkey_grr.png",
// monkey_pensive: "custom:monkey_pensive.png",
// monkey_zany: "custom:monkey_zany.png",
// nazu_sit: "custom:nazu_sit.png",
// nazu_sus: "custom:nazu_sus.png",
// ok_and: "custom:ok_and.gif",
// owo: "custom:owo.png",
// pat: "custom:pat.png",
// pointThink: "custom:pointThink.png",
// rainbowHype: "custom:rainbowHype.gif",
// rawr: "custom:rawr.png",
// rember: "custom:rember.png",
// revolt: "custom:revolt.png",
// sickly: "custom:sickly.png",
// stare: "custom:stare.png",
// tfyoulookingat: "custom:tfyoulookingat.png",
// thanks: "custom:thanks.png",
// thonk: "custom:thonk.png",
// trol: "custom:trol.png",
// troll_smile: "custom:troll_smile.gif",
// uber: "custom:uber.png",
// ubertroll: "custom:ubertroll.png",
// verycool: "custom:verycool.png",
// verygood: "custom:verygood.png",
// wawafast: "custom:wawafast.gif",
// wawastance: "custom:wawastance.png",
// yeahokayyy: "custom:yeahokayyy.png",
// yed: "custom:yed.png",
// yems: "custom:yems.png",
// michael: "custom:michael.gif",
// charle: "custom:charle.gif",
// sadge: "custom:sadge.webp",
// sus: "custom:sus.webp",
// chade: "custom:chade.gif",
// gigachad: "custom:gigachad.webp",
// sippy: "custom:sippy.webp",
// ayame_heart: "custom:ayame_heart.png",
// catgirl_peek: "custom:catgirl_peek.png",
// girl_happy: "custom:girl_happy.png",
// hug_plushie: "custom:hug_plushie.png",
// huggies: "custom:huggies.png",
// noted: "custom:noted.gif",
// waving: "custom:waving.png",
// mogusvented: "custom:mogusvented.png",
// },
};

View File

@@ -4,9 +4,9 @@ import { Channel } from "revolt.js";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
import { useState } from "preact/hooks";
import { useEffect, useState } from "preact/hooks";
import { Button, Checkbox } from "@revoltchat/ui";
import { Button, Checkbox, Preloader } from "@revoltchat/ui";
import { useApplicationState } from "../../mobx/State";
import { SECTION_NSFW } from "../../mobx/stores/Layout";
@@ -45,14 +45,36 @@ type Props = {
channel: Channel;
};
let geoBlock:
| undefined
| {
countryCode: string;
isAgeRestrictedGeo: true;
};
export default observer((props: Props) => {
const history = useHistory();
const layout = useApplicationState().layout;
const [geoLoaded, setGeoLoaded] = useState(typeof geoBlock !== "undefined");
const [ageGate, setAgeGate] = useState(false);
if (ageGate || !props.gated) {
useEffect(() => {
if (!geoLoaded) {
fetch("https://geo.revolt.chat")
.then((res) => res.json())
.then((data) => {
geoBlock = data;
setGeoLoaded(true);
});
}
}, []);
if (!geoBlock) return <Preloader type="spinner" />;
if ((ageGate && !geoBlock.isAgeRestrictedGeo) || !props.gated) {
return <>{props.children}</>;
}
if (
!(
props.channel.channel_type === "Group" ||
@@ -76,23 +98,40 @@ export default observer((props: Props) => {
</a>
</span>
<Checkbox
title={<Text id="app.main.channel.nsfw.confirm" />}
value={layout.getSectionState(SECTION_NSFW, false)}
onChange={() => layout.toggleSectionState(SECTION_NSFW, false)}
/>
<div className="actions">
<Button palette="secondary" onClick={() => history.goBack()}>
<Text id="app.special.modals.actions.back" />
</Button>
<Button
palette="secondary"
onClick={() =>
layout.getSectionState(SECTION_NSFW) && setAgeGate(true)
}>
<Text id={`app.main.channel.nsfw.${props.type}.confirm`} />
</Button>
</div>
{geoBlock.isAgeRestrictedGeo ? (
<div style={{ maxWidth: "420px", textAlign: "center" }}>
{geoBlock.countryCode === "GB"
? "This channel is not available in your region while we review options on legal compliance."
: "This content is not available in your region."}
</div>
) : (
<>
<Checkbox
title={<Text id="app.main.channel.nsfw.confirm" />}
value={layout.getSectionState(SECTION_NSFW, false)}
onChange={() =>
layout.toggleSectionState(SECTION_NSFW, false)
}
/>
<div className="actions">
<Button
palette="secondary"
onClick={() => history.goBack()}>
<Text id="app.special.modals.actions.back" />
</Button>
<Button
palette="secondary"
onClick={() =>
layout.getSectionState(SECTION_NSFW) &&
setAgeGate(true)
}>
<Text
id={`app.main.channel.nsfw.${props.type}.confirm`}
/>
</Button>
</div>
</>
)}
</Base>
);
});

View File

@@ -43,11 +43,11 @@ function toCodePoint(rune: string) {
}
export function parseEmoji(emoji: string) {
if (emoji.startsWith("custom:")) {
return `https://dl.insrt.uk/projects/revolt/emotes/${emoji.substring(
7,
)}`;
}
// if (emoji.startsWith("custom:")) {
// return `https://dl.insrt.uk/projects/revolt/emotes/${emoji.substring(
// 7,
// )}`;
// }
const codepoint = toCodePoint(emoji);
return `https://static.revolt.chat/emoji/${EMOJI_PACK}/${codepoint}.svg?rev=${REVISION}`;

View File

@@ -5,7 +5,7 @@ import { useTriggerEvents } from "preact-context-menu";
import { memo } from "preact/compat";
import { useEffect, useState } from "preact/hooks";
import { Category, Button } from "@revoltchat/ui";
import { Category } from "@revoltchat/ui";
import { internalEmit } from "../../../lib/eventEmitter";
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
@@ -33,7 +33,7 @@ import InviteList from "./embed/EmbedInvite";
interface Props {
attachContext?: boolean;
queued?: QueuedMessage;
message: MessageObject;
message: MessageObject & { webhook: { name: string; avatar?: string } };
highlight?: boolean;
contrast?: boolean;
content?: Children;
@@ -63,6 +63,7 @@ const Message = observer(
? useTriggerEvents("Menu", {
user: message.author_id,
contextualChannel: message.channel_id,
contextualMessage: message._id,
// eslint-disable-next-line
})
: undefined;
@@ -137,6 +138,11 @@ const Message = observer(
<UserIcon
className="avatar"
url={message.generateMasqAvatarURL()}
override={
message.webhook?.avatar
? `https://autumn.revolt.chat/avatars/${message.webhook.avatar}`
: undefined
}
target={user}
size={36}
onClick={handleUserClick}
@@ -157,6 +163,7 @@ const Message = observer(
showServerIdentity
onClick={handleUserClick}
masquerade={message.masquerade!}
override={message.webhook?.name}
{...userContext}
/>
<MessageDetail

View File

@@ -305,7 +305,7 @@ export default observer(({ channel }: Props) => {
if (!state.draft.has(channel._id)) {
setMessage(text);
} else {
setMessage(`${state.draft.get(channel._id)}\n${text}`);
setMessage(`${state.draft.get(channel._id)?.content}\n${text}`);
}
}
@@ -324,7 +324,7 @@ export default observer(({ channel }: Props) => {
return;
const content = state.draft.get(channel._id)?.content?.trim() ?? "";
if (uploadState.type === "attached") return sendFile(content);
if (uploadState.type !== "none") return sendFile(content);
if (content.length === 0) return;
internalEmit("NewMessages", "hide");
@@ -406,7 +406,9 @@ export default observer(({ channel }: Props) => {
* @returns
*/
async function sendFile(content: string) {
if (uploadState.type !== "attached") return;
if (uploadState.type !== "attached" && uploadState.type !== "failed")
return;
const attachments: string[] = [];
setMessage;
@@ -744,13 +746,11 @@ export default observer(({ channel }: Props) => {
onFocus={onFocus}
onBlur={onBlur}
/>
{state.experiments.isEnabled("picker") && (
<Action>
<IconButton onClick={() => setPicker(!picker)}>
<HappyBeaming size={24} />
</IconButton>
</Action>
)}
<Action>
<IconButton onClick={() => setPicker(!picker)}>
<HappyBeaming size={24} />
</IconButton>
</Action>
<Action>
<IconButton
className={

View File

@@ -26,8 +26,8 @@ export default function AttachmentActions({ attachment }: Props) {
const { filename, metadata, size } = attachment;
const url = client.generateFileURL(attachment);
const open_url = `${url}/${filename}`;
const download_url = url?.replace("attachments", "attachments/download");
const open_url = url;
const download_url = `${url}/${filename}`;
const filesize = determineFileSize(size);
@@ -49,10 +49,11 @@ export default function AttachmentActions({ attachment }: Props) {
</IconButton>
</a>
<a
target="_blank"
href={download_url}
className={styles.downloadIcon}
download
target={isFirefox || window.native ? "_blank" : "_self"}
// target={isFirefox || window.native ? "_blank" : "_self"}
rel="noreferrer">
<IconButton>
<Download size={24} />

View File

@@ -119,19 +119,18 @@ export const MessageOverlayBar = observer(
</Tooltip>
)}
{message.channel?.havePermission("React") &&
state.experiments.isEnabled("picker") && (
<ReactionWrapper
open={reactionsOpen}
setOpen={setReactionsOpen}
message={message}>
<Tooltip content="React">
<Entry>
<HappyBeaming size={18} />
</Entry>
</Tooltip>
</ReactionWrapper>
)}
{message.channel?.havePermission("React") && (
<ReactionWrapper
open={reactionsOpen}
setOpen={setReactionsOpen}
message={message}>
<Tooltip content="React">
<Entry>
<HappyBeaming size={18} />
</Entry>
</Tooltip>
</ReactionWrapper>
)}
{isAuthor && (
<Tooltip content="Edit">

View File

@@ -17,6 +17,7 @@ import { Bar } from "./JumpToBottom";
export default observer(
({ channel, last_id }: { channel: Channel; last_id?: string }) => {
const [hidden, setHidden] = useState(false);
const [timeAgo, setTimeAgo] = useState("");
const hide = () => setHidden(true);
useEffect(() => setHidden(false), [last_id]);
@@ -29,6 +30,14 @@ export default observer(
return () => document.removeEventListener("keydown", onKeyDown);
}, []);
useEffect(() => {
if (last_id) {
try {
setTimeAgo(dayjs(decodeTime(last_id)).fromNow());
} catch (err) {}
}
}, [last_id]);
const renderer = getRenderer(channel);
const history = useHistory();
if (renderer.state !== "RENDER") return null;
@@ -52,7 +61,7 @@ export default observer(
<Text
id="app.main.channel.misc.new_messages"
fields={{
time_ago: dayjs(decodeTime(last_id)).fromNow(),
time_ago: timeAgo,
}}
/>
</div>

View File

@@ -76,7 +76,9 @@ export default observer(({ channel }: Props) => {
if (users.length >= 5) {
text = <Text id="app.main.channel.typing.several" />;
} else if (users.length > 1) {
const userlist = [...users].map((x) => x!.username);
const userlist = [...users].map(
(x) => x!.display_name ?? x!.username,
);
const user = userlist.pop();
text = (
@@ -92,7 +94,9 @@ export default observer(({ channel }: Props) => {
text = (
<Text
id="app.main.channel.typing.single"
fields={{ user: users[0]!.username }}
fields={{
user: users[0]!.display_name ?? users[0]!.username,
}}
/>
);
}

View File

@@ -142,7 +142,11 @@ export default function Embed({ embed }: Props) {
<a
onMouseDown={(ev) =>
(ev.button === 0 || ev.button === 1) &&
modalController.openLink(embed.url!)
modalController.openLink(
embed.url!,
undefined,
true,
)
}
className={styles.title}>
{embed.title}
@@ -194,7 +198,8 @@ export default function Embed({ embed }: Props) {
modalController.push({ type: "image_viewer", embed })
}
onMouseDown={(ev) =>
ev.button === 1 && modalController.openLink(embed.url)
ev.button === 1 &&
modalController.openLink(embed.url, undefined, true)
}
/>
);

View File

@@ -126,8 +126,16 @@ export function EmbedInvite({ code }: Props) {
<EmbedInviteName>{invite.server_name}</EmbedInviteName>
<EmbedInviteMemberCount>
<Group size={12} />
{invite.member_count.toLocaleString()}{" "}
{invite.member_count === 1 ? "member" : "members"}
{invite.member_count != null ? (
<>
{invite.member_count.toLocaleString()}{" "}
{invite.member_count === 1
? "member"
: "members"}
</>
) : (
"N/A"
)}
</EmbedInviteMemberCount>
</EmbedInviteDetails>
{processing ? (

View File

@@ -22,12 +22,11 @@ enum Badges {
const BadgesBase = styled.div`
gap: 8px;
display: flex;
margin-top: 4px;
flex-direction: row;
img {
width: 32px;
height: 32px;
width: 24px;
height: 24px;
}
`;
@@ -103,7 +102,7 @@ export default function UserBadges({ badges, uid }: Props) {
content={
<Text id="app.special.popovers.user_profile.badges.responsible_disclosure" />
}>
<Shield size={32} color="gray" />
<Shield size={24} color="gray" />
</Tooltip>
) : (
<></>
@@ -120,7 +119,7 @@ export default function UserBadges({ badges, uid }: Props) {
}}
onClick={() => {
window.open(
"https://insrt.uk/donate",
"https://wiki.revolt.chat/notes/project/financial-support/",
"_blank",
);
}}

View File

@@ -29,9 +29,14 @@ const HeaderBase = styled.div`
text-overflow: ellipsis;
}
.new-name {
font-size: 16px;
font-weight: 600;
}
.username {
cursor: pointer;
font-size: 16px;
font-size: 13px;
font-weight: 600;
}
@@ -50,6 +55,9 @@ export default observer(({ user }: Props) => {
return (
<Header topBorder palette="secondary">
<HeaderBase>
<div className="new-name">
{user.display_name ?? user.username}
</div>
<Localizer>
<Tooltip content={<Text id="app.special.copy_username" />}>
<span
@@ -57,7 +65,9 @@ export default observer(({ user }: Props) => {
onClick={() =>
modalController.writeText(user.username)
}>
@{user.username}
{user.username}
{"#"}
{user.discriminator}
</span>
</Tooltip>
</Localizer>

View File

@@ -14,6 +14,7 @@ import IconBase, { IconBaseProps } from "../IconBase";
type VoiceStatus = "muted" | "deaf";
interface Props extends IconBaseProps<User> {
status?: boolean;
override?: string;
voice?: VoiceStatus;
masquerade?: API.Masquerade;
showServerIdentity?: boolean;
@@ -70,12 +71,15 @@ export default observer(
showServerIdentity,
masquerade,
innerRef,
override,
...svgProps
} = props;
let { url } = props;
if (masquerade?.avatar) {
url = client.proxyFile(masquerade.avatar);
} else if (override) {
url = override;
} else if (!url) {
let override;
if (target && showServerIdentity) {

View File

@@ -39,6 +39,7 @@ type UsernameProps = Omit<
masquerade?: API.Masquerade;
showServerIdentity?: boolean | "both";
override?: string;
innerRef?: Ref<any>;
};
@@ -64,13 +65,18 @@ export const Username = observer(
masquerade,
showServerIdentity,
innerRef,
override,
...otherProps
}: UsernameProps) => {
let username = user?.username;
let username =
(user as unknown as { display_name: string })?.display_name ??
user?.username;
let color = masquerade?.colour;
let timed_out: Date | undefined;
if (user && showServerIdentity) {
if (override) {
username = override;
} else if (user && showServerIdentity) {
const { server } = useParams<{ server?: string }>();
if (server) {
const client = useClient();
@@ -146,6 +152,17 @@ export const Username = observer(
);
}
if (override) {
return (
<>
{el}
<BotBadge>
<Text id="app.main.channel.bot" />
</BotBadge>
</>
);
}
return el;
},
);

View File

@@ -1,5 +1,4 @@
import "katex/dist/katex.min.css";
import rehypeKatex from "rehype-katex";
import rehypePrism from "rehype-prism";
import rehypeReact from "rehype-react";
import remarkBreaks from "remark-breaks";
@@ -14,6 +13,9 @@ import { createElement } from "preact";
import { memo } from "preact/compat";
import { useLayoutEffect, useMemo, useState } from "preact/hooks";
// @ts-expect-error no typings
import rehypeKatex from "@revoltchat/rehype-katex";
import { MarkdownProps } from "./Markdown";
import { handlers } from "./hast";
import { RenderCodeblock } from "./plugins/Codeblock";
@@ -146,6 +148,7 @@ const render = unified()
.use(rehypeKatex, {
maxSize: 10,
maxExpand: 0,
maxLength: 512,
trust: false,
strict: false,
output: "html",
@@ -182,7 +185,8 @@ const Container = styled.div<{ largeEmoji: boolean }>`
/**
* Regex for matching execessive recursion of blockquotes and lists
*/
const RE_RECURSIVE = /(^(?:[>*+-][^\S\r\n]*){5})(?:[>*+-][^\S\r\n]*)+(.*$)/gm;
const RE_RECURSIVE =
/(^(?:(?:[>*+-]|\d+\.)[^\S\r\n]*){5})(?:(?:[>*+-]|\d+\.)[^\S\r\n]*)+(.*$)/gm;
/**
* Regex for matching multi-line blockquotes
@@ -244,9 +248,13 @@ export default memo(({ content, disallowBigEmoji }: MarkdownProps) => {
const [Content, setContent] = useState<React.ReactElement>(null!);
useLayoutEffect(() => {
render
.process(sanitisedContent)
.then((file) => setContent(file.result));
try {
render
.process(sanitisedContent)
.then((file) => setContent(file.result));
} catch (err) {
setContent("Message failed to render." as never);
}
}, [sanitisedContent]);
const largeEmoji = useMemo(

View File

@@ -27,7 +27,11 @@ export function RenderAnchor({
target="_blank"
rel="noreferrer"
onClick={(ev) =>
modalController.openLink(href) && ev.preventDefault()
modalController.openLink(
href,
undefined,
ev.currentTarget.innerText !== href,
) && ev.preventDefault()
}
/>
);

View File

@@ -34,7 +34,7 @@ export function RenderEmoji({ match }: CustomComponentProps) {
? `${
clientController.getAvailableClient().configuration?.features
.autumn.url
}/emojis/${match}`
}/emojis/${match}/original`
: parseEmoji(
match in emojiDictionary
? emojiDictionary[match as keyof typeof emojiDictionary]

View File

@@ -50,8 +50,10 @@ const ServerList = styled.div`
export default observer(() => {
const client = useClient();
const state = useApplicationState();
const { server: server_id, channel: channel_id } =
useParams<{ server: string; channel?: string }>();
const { server: server_id, channel: channel_id } = useParams<{
server: string;
channel?: string;
}>();
const server = client.servers.get(server_id);
if (!server) return <Redirect to="/" />;
@@ -119,14 +121,18 @@ export default observer(() => {
channels.push(addChannel(id));
}
elements.push(
<CollapsibleSection
id={`category_${category.id}`}
defaultValue
summary={<Category>{category.title}</Category>}>
{channels}
</CollapsibleSection>,
);
if (category.title === "Default") {
elements.push(...channels);
} else {
elements.push(
<CollapsibleSection
id={`category_${category.id}`}
defaultValue
summary={<Category>{category.title}</Category>}>
{channels}
</CollapsibleSection>,
);
}
}
}

View File

@@ -76,17 +76,25 @@ function useEntries(
keys.forEach((key) => {
let u;
let member;
if (isServer) {
const { server, user } = JSON.parse(key);
if (server !== channel.server_id) return;
u = client.users.get(user);
member = client.members.get(key);
if (!member?.hasPermission(channel, "ViewChannel")) {
return;
}
} else {
u = client.users.get(key);
member = client.members.get(key);
}
if (!u) return;
const member = client.members.get(key);
const sort = member?.nickname ?? u.username;
const entry = [u, sort] as [User, string];

View File

@@ -39,7 +39,7 @@ export default function AccountManagement() {
<CategoryButton
icon={<Block size={24} color="var(--error)" />}
description={
"Disable your account. You won't be able to access it unless you contact support."
<Text id="app.settings.pages.account.manage.disable_description" />
}
action="chevron"
onClick={callback("disable")}>
@@ -49,7 +49,7 @@ export default function AccountManagement() {
<CategoryButton
icon={<Trash size={24} color="var(--error)" />}
description={
"Your account will be queued for deletion, a confirmation email will be sent."
<Text id="app.settings.pages.account.manage.delete_description" />
}
action="chevron"
onClick={callback("delete")}>

View File

@@ -37,7 +37,13 @@ export default observer(() => {
{(
[
["username", client.user!.username, At],
[
"username",
client.user!.username +
"#" +
client.user!.discriminator,
At,
],
["email", email, Envelope],
["password", "•••••••••", Key],
] as const

View File

@@ -159,7 +159,7 @@ class ClientController {
})
.catch((err) => {
const error = takeError(err);
if (error === "Forbidden" || error === "Unauthorized") {
if (error === "Unauthorized") {
this.sessions.delete(user_id);
this.current = null;
this.pickNextSession();
@@ -252,7 +252,7 @@ class ClientController {
// Start client lifecycle
this.addSession(
{
session,
session: session as never,
},
"new",
);

View File

@@ -3,6 +3,7 @@ import { API, Client } from "revolt.js";
import { state } from "../../mobx/State";
import { resetMemberSidebarFetched } from "../../components/navigation/right/MemberSidebar";
import { modalController } from "../modals/ModalController";
/**
@@ -97,6 +98,7 @@ export default class Session {
* Called when the client signals it has received the Ready packet
*/
private onReady() {
resetMemberSidebarFetched();
this.emit({
action: "SUCCESS",
});

View File

@@ -10,8 +10,9 @@ export function takeError(error: any): string {
case 429:
return "TooManyRequests";
case 401:
return "Unauthorized"
case 403:
return "Unauthorized";
return "Forbidden";
default:
return "UnknownError";
}

View File

@@ -12,7 +12,7 @@ import { IconButton, Preloader } from "@revoltchat/ui";
import { determineFileSize } from "../../../../lib/fileSize";
import { modalController } from "../../../modals/ModalController";
import { useClient } from "../../ClientController";
import { clientController, useClient } from "../../ClientController";
import { takeError } from "../error";
type BehaviourType =
@@ -67,9 +67,16 @@ export async function uploadFile(
const formData = new FormData();
formData.append("file", file);
const client = clientController.getActiveSession()?.client;
const sesToken =
typeof client?.session === "string"
? client.session
: client?.session?.token;
const res = await Axios.post(`${autumnURL}/${tag}`, formData, {
headers: {
"Content-Type": "multipart/form-data",
"X-Session-Token": sesToken,
},
...config,
});

View File

@@ -18,6 +18,7 @@ import { history } from "../../context/history";
import AddFriend from "./components/AddFriend";
import BanMember from "./components/BanMember";
import Changelog from "./components/Changelog";
import ChangelogUsernames from "./components/ChangelogUsernames";
import ChannelInfo from "./components/ChannelInfo";
import Clipboard from "./components/Clipboard";
import ConfirmLeave from "./components/ConfirmLeave";
@@ -39,6 +40,7 @@ import MFAEnableTOTP from "./components/MFAEnableTOTP";
import MFAFlow from "./components/MFAFlow";
import MFARecovery from "./components/MFARecovery";
import ModifyAccount from "./components/ModifyAccount";
import ModifyDisplayname from "./components/ModifyDisplayname";
import OutOfDate from "./components/OutOfDate";
import PendingFriendRequests from "./components/PendingFriendRequests";
import ReportContent from "./components/Report";
@@ -206,12 +208,25 @@ class ModalControllerExtended extends ModalController<Modal> {
* Safely open external or internal link
* @param href Raw URL
* @param trusted Whether we trust this link
* @param mismatch Whether to always open link warning
* @returns Whether to cancel default event
*/
openLink(href?: string, trusted?: boolean) {
openLink(href?: string, trusted?: boolean, mismatch?: boolean) {
const link = determineLink(href);
const settings = getApplicationState().settings;
if (mismatch) {
if (href) {
modalController.push({
type: "link_warning",
link: href,
callback: () => this.openLink(href, true) as true,
});
}
return true;
}
switch (link.type) {
case "navigate": {
history.push(link.path);
@@ -280,4 +295,7 @@ export const modalController = new ModalControllerExtended({
user_profile: UserProfile,
report: ReportContent,
report_success: ReportSuccess,
modify_displayname: ModifyDisplayname,
changelog_usernames: ChangelogUsernames,
reset_bot_token: Confirmation
});

View File

@@ -23,6 +23,7 @@ export default function AddFriend({ ...props }: ModalProps<"add_friend">) {
data={{
username: {
field: "Username",
placeholder: "username#1234",
},
}}
callback={({ username }) =>

View File

@@ -33,6 +33,8 @@ function RenderLog({ post }: { post: ChangelogPost }) {
{post.content.map((entry) =>
typeof entry === "string" ? (
<Markdown content={entry} />
) : entry.type === "element" ? (
entry.element
) : (
<Image src={entry.src} shadow={entry.shadow} />
),

View File

@@ -0,0 +1,146 @@
import Lottie, { LottieRefCurrentProps } from "lottie-react";
import { useEffect, useRef } from "preact/hooks";
import { Button, Column, InputBox, Modal, Row } from "@revoltchat/ui";
import { useClient } from "../../client/ClientController";
import { modalController } from "../ModalController";
import { ModalProps } from "../types";
import usernameAnim from "./legacy/usernameUpdateLottie.json";
/**
* Changelog: Username update
*/
export default function ChangelogUsernames({
onClose,
signal,
}: ModalProps<"changelog_usernames">) {
const client = useClient();
const lottieRef = useRef<LottieRefCurrentProps>();
useEffect(() => {
if (lottieRef.current) {
const timer = setTimeout(() => lottieRef.current!.play(), 2500);
return () => clearTimeout(timer);
}
}, [lottieRef]);
return (
<Modal onClose={onClose} signal={signal} transparent>
{
(
<Column gap="0">
<div
style={{
background: "black",
borderStartStartRadius: "12px",
borderStartEndRadius: "12px",
display: "grid",
placeItems: "center",
padding: "1.5em",
}}>
<Lottie
lottieRef={lottieRef as never}
animationData={usernameAnim}
autoplay={false}
loop={false}
style={{ width: "240px" }}
/>
</div>
<div
style={{
padding: "1em",
background: "var(--secondary-header)",
textAlign: "center",
}}>
<Column
gap="6px"
style={{
alignItems: "center",
}}>
<span
style={{
fontSize: "1.5em",
fontWeight: 700,
marginBottom: "12px",
}}>
Usernames are Changing
</span>
<span
style={{
color: "var(--secondary-foreground)",
fontSize: "0.9em",
fontWeight: 500,
}}>
We've changed how usernames work on Revolt.
Now you can set a display name in addition
to a username with a number tag for easier
sharing.
</span>
<span
style={{
color: "var(--secondary-foreground)",
fontSize: "0.9em",
fontWeight: 500,
}}>
Here's how your new username looks:
</span>
<InputBox
value={
client.user!.display_name ??
client.user!.username
}
style={{
maxWidth: "180px",
}}
disabled
/>
<InputBox
value={
client.user.username +
"#" +
client.user.discriminator
}
style={{
maxWidth: "180px",
}}
disabled
/>
<a
href="https://revolt.chat/posts/evolving-usernames"
target="_blank">
Read more about this change
</a>
</Column>
</div>
<Row
style={{
padding: "1em",
borderEndStartRadius: "12px",
borderEndEndRadius: "12px",
background: "var(--secondary-background)",
textAlign: "center",
justifyContent: "end",
}}>
<Button palette="plain" onClick={onClose}>
Skip for now
</Button>
<Button
palette="accent"
onClick={() => {
modalController.openLink(
"/settings/profile",
);
onClose();
}}>
Edit Profile
</Button>
</Row>
</Column>
) as any
}
</Modal>
);
}

View File

@@ -20,6 +20,7 @@ export default function Confirmation(
| "delete_bot"
| "block_user"
| "unfriend_user"
| "reset_bot_token"
>,
) {
const history = useHistory();
@@ -31,6 +32,7 @@ export default function Confirmation(
delete_bot: ["confirm_delete", "delete"],
unfriend_user: ["unfriend_user", "remove"],
block_user: ["block_user", "block"],
reset_bot_token: ["reset_bot_token", "reset"],
};
const event = EVENTS[props.type];
@@ -88,6 +90,12 @@ export default function Confirmation(
.bots.delete(props.target);
props.cb?.();
break;
case "reset_bot_token":
clientController
.getAvailableClient()
.bots
.edit(props.target.id, { remove: ["Token"] })
.then(props.callback)
}
}}
submit={{

View File

@@ -3,7 +3,7 @@ import { SubmitHandler, useForm } from "react-hook-form";
import { Text } from "preact-i18n";
import { useState } from "preact/hooks";
import { Category, Error, Modal } from "@revoltchat/ui";
import { Category, Error, InputBox, Modal, Tip } from "@revoltchat/ui";
import { noopTrue } from "../../../lib/js";
@@ -120,14 +120,34 @@ export default function ModifyAccount({
/>
)}
{field === "username" && (
<FormField
type="username"
name="new_username"
register={register}
showOverline
error={errors.new_username?.message}
disabled={processing}
/>
<div
style={{
display: "flex",
alignItems: "end",
gap: "8px",
}}>
<div style={{ flexGrow: 1 }}>
<FormField
type="username"
name="new_username"
register={register}
showOverline
error={errors.new_username?.message}
disabled={processing}
/>
</div>
<div
style={{
flexShrink: 0,
width: "80px",
textAlign: "center",
}}>
<InputBox
disabled
value={"#" + client.user.discriminator}
/>
</div>
</div>
)}
<FormField
type="current_password"
@@ -141,11 +161,24 @@ export default function ModifyAccount({
<Category compact>
<Error
error={
<Text id="app.special.modals.account.failed" />
<>
<Text id="app.special.modals.account.failed" />{" "}
<Text id={`error.${error}`}>{error}</Text>
</>
}
/>
</Category>
)}
{field === "username" && (
<div style={{ marginTop: "8px" }}>
<Tip palette="warning">
Changing your username may change your number tag.
You can freely change the case of your username.
Your number tag may change at most once a day.
</Tip>
</div>
)}
</form>
</Modal>
);

View File

@@ -0,0 +1,47 @@
import { Text } from "preact-i18n";
import { ModalForm } from "@revoltchat/ui";
import { useClient } from "../../client/ClientController";
import { ModalProps } from "../types";
/**
* Modify display name modal
*/
export default function ModifyDisplayname({
...props
}: ModalProps<"modify_displayname">) {
const client = useClient();
return (
<ModalForm
{...props}
title="Update display name"
schema={{
display_name: "text",
}}
defaults={{
display_name: (
client.user as unknown as { display_name: string }
)?.display_name as string,
}}
data={{
display_name: {
field: "Display Name",
},
}}
callback={({ display_name }) =>
display_name && display_name !== client.user!.username
? client.users.edit({
display_name,
} as never)
: client.users.edit({
remove: ["DisplayName"],
} as never)
}
submit={{
children: <Text id="app.special.modals.actions.save" />,
}}
/>
);
}

View File

@@ -41,6 +41,7 @@ const MessageContainer = styled.div`
*/
export default function ReportContent({
target,
messageId,
...props
}: ModalProps<"report">) {
const client = useClient();
@@ -124,6 +125,7 @@ export default function ReportContent({
? "User"
: "Server",
report_reason: reason as any,
message_id: messageId,
},
additional_context,
});

View File

@@ -5,6 +5,7 @@ import { Text } from "preact-i18n";
import { Column, H1, IconButton, Modal, Row } from "@revoltchat/ui";
import Markdown from "../../../components/markdown/Markdown";
import { useClient } from "../../client/ClientController";
import { report } from "../../safety";
import { modalController } from "../ModalController";
import { ModalProps } from "../types";
@@ -13,6 +14,49 @@ export default function ServerInfo({
server,
...props
}: ModalProps<"server_info">) {
const client = useClient();
const isOwner = server.owner === client.user?._id;
const actions = [
{
onClick: () => {
modalController.push({
type: "server_identity",
member: server.member!,
});
return true;
},
children: "Edit Identity",
palette: "primary",
},
];
if (!isOwner) {
actions.push({
onClick: () => {
modalController.push({
type: "leave_server",
target: server,
});
return true;
},
children: "Leave Server",
palette: "error",
});
}
actions.push({
onClick: () => {
modalController.push({
type: "report",
target: server,
});
return true;
},
children: <Text id="app.special.modals.actions.report" />,
palette: "error",
});
return (
<Modal
{...props}
@@ -26,30 +70,7 @@ export default function ServerInfo({
</IconButton>
</Row>
}
actions={[
{
onClick: () => {
modalController.push({
type: "server_identity",
member: server.member!,
});
return true;
},
children: "Edit Identity",
palette: "primary",
},
{
onClick: () => {
modalController.push({
type: "report",
target: server,
});
return true;
},
children: <Text id="app.special.modals.actions.report" />,
palette: "error",
},
]}>
actions={actions}>
<Markdown content={server.description!} />
</Modal>
);

View File

@@ -71,6 +71,10 @@ export function OnboardingModal({
error={error}
/>
</div>
<p>
You will be automatically assigned a number
tag which you can find from settings.
</p>
<Button palette="accent">
{"Looks good!"}
</Button>

View File

@@ -46,13 +46,28 @@
text-overflow: ellipsis;
}
.username {
.usernameDetail {
display: flex;
flex-direction: column;
margin-bottom: 4px;
}
.displayname {
font-size: 22px;
font-weight: 600;
}
.username {
display: block;
font-size: 13px;
font-weight: 600;
width: fit-content;
color: var(--secondary-foreground);
}
.status {
font-size: 13px;
color: var(--secondary-foreground);
> div {
display: inline !important;

View File

@@ -6,6 +6,7 @@ import {
UserX,
Group,
InfoCircle,
Flag,
} from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Link, useHistory } from "react-router-dom";
@@ -163,16 +164,33 @@ export const UserProfile = observer(
}
/>
<div className={styles.details}>
<Localizer>
<div className={styles.usernameDetail}>
<span
className={styles.username}
className={styles.displayname}
onClick={() =>
modalController.writeText(user.username)
}>
{"@"}
{user.username}
{user.display_name ?? user.username}
</span>
</Localizer>
<span
className={styles.username}
onClick={() =>
modalController.writeText(
user.username +
"#" +
user.discriminator,
)
}>
<Localizer>
<Tooltip
content={
<Text id="app.special.copy_username" />
}>
{user.username}#{user.discriminator}
</Tooltip>
</Localizer>
</span>
</div>
{user.status?.text && (
<span className={styles.status}>
<UserStatus user={user} tooltip />
@@ -229,7 +247,40 @@ export const UserProfile = observer(
<UserX size={28} />
</IconButton>
)}
{!user.bot && flags != 2 && flags != 4 && (
<Localizer>
<Tooltip
content={
<Text id="app.context_menu.report_user" />
}>
<IconButton
onClick={() =>
modalController.push({
type: "report",
target: user,
})
}>
<Flag size={28} />
</IconButton>
</Tooltip>
</Localizer>
)}
</div>
{badges > 0 && (
<div
style={{
marginInline: "1em",
padding: "0.5em",
background: "var(--primary-background)",
borderRadius: "8px",
width: "fit-content",
backgroundColor:
"rgba(var(--primary-header-rgb), max(var(--min-opacity), 0.65))",
backdropFilter: "blur(20px)",
}}>
<UserBadges badges={badges} uid={user._id} />
</div>
)}
<div className={styles.tabs}>
<div
data-active={tab === "profile"}
@@ -261,10 +312,7 @@ export const UserProfile = observer(
</div>
<div className={styles.content}>
{tab === "profile" &&
(profile?.content ||
badges > 0 ||
flags > 0 ||
user.bot ? (
(profile?.content || flags > 0 || user.bot ? (
<div>
{flags & 1 ? (
/** ! FIXME: i18n this area */
@@ -314,17 +362,6 @@ export const UserProfile = observer(
</div>
</>
) : undefined}
{badges > 0 && (
<div className={styles.category}>
<Text id="app.special.popovers.user_profile.sub.badges" />
</div>
)}
{badges > 0 && (
<UserBadges
badges={badges}
uid={user._id}
/>
)}
{profile?.content && (
<>
<div className={styles.category}>

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@ export type Modal = {
| "create_group"
| "create_server"
| "custom_status"
| "modify_displayname"
| "add_friend";
}
| ({
@@ -40,6 +41,7 @@ export type Modal = {
type: "changelog";
initial?: number;
}
| { type: "changelog_usernames" }
| {
type: "sign_out_sessions";
client: Client;
@@ -182,11 +184,17 @@ export type Modal = {
| {
type: "report";
target: Server | User | Message;
messageId?: string;
}
| {
type: "report_success";
user?: User;
}
| {
type: "reset_bot_token";
target: { name: string, id: string },
callback: () => Promise<void>;
}
);
export type ModalProps<T extends Modal["type"]> = Modal & { type: T } & {

View File

@@ -2,32 +2,29 @@ import { ChevronRight, Trash } from "@styled-icons/boxicons-regular";
import { Cog, UserVoice } from "@styled-icons/boxicons-solid";
import { isFirefox } from "react-device-detect";
import { useHistory } from "react-router-dom";
import {
Channel,
Message,
Server,
User,
API,
Permission,
UserPermission,
Member,
} from "revolt.js";
import { Channel, Message, Server, User, API, Permission, UserPermission, Member } from "revolt.js";
import {
ContextMenuWithData,
MenuItem,
openContextMenu,
} from "preact-context-menu";
import { ContextMenuWithData, MenuItem, openContextMenu } from "preact-context-menu";
import { Text } from "preact-i18n";
import { IconButton, LineDivider } from "@revoltchat/ui";
import { Column, IconButton, LineDivider } from "@revoltchat/ui";
import { useApplicationState } from "../mobx/State";
import { QueuedMessage } from "../mobx/stores/MessageQueue";
import { NotificationState } from "../mobx/stores/NotificationOptions";
import CMNotifications from "./contextmenu/CMNotifications";
import Tooltip from "../components/common/Tooltip";
import UserStatus from "../components/common/user/UserStatus";
import { useSession } from "../controllers/client/ClientController";
@@ -36,6 +33,7 @@ import { modalController } from "../controllers/modals/ModalController";
import { internalEmit } from "./eventEmitter";
import { getRenderer } from "./renderer/Singleton";
interface ContextMenuData {
user?: string;
server?: string;
@@ -47,10 +45,13 @@ interface ContextMenuData {
unread?: boolean;
queued?: QueuedMessage;
contextualChannel?: string;
contextualMessage?: string;
}
type Action =
| { action: "copy_id"; id: string }
| { action: "admin"; id: string; type: "channel" | "message" | "user" }
| { action: "admin_system"; id: string }
| { action: "copy_message_link"; message: Message }
| { action: "copy_selection" }
| { action: "copy_text"; content: string }
@@ -112,7 +113,7 @@ type Action =
key: string;
state?: NotificationState;
}
| { action: "report"; target: User | Server | Message };
| { action: "report"; target: User | Server | Message; messageId?: string };
// ! FIXME: I dare someone to re-write this
// Tip: This should just be split into separate context menus per logical area.
@@ -132,6 +133,18 @@ export default function ContextMenus() {
case "copy_id":
modalController.writeText(data.id);
break;
case "admin":
window.open(
`https://admin.revolt.chat/panel/inspect/${data.type}/${data.id}`,
"_blank",
);
break;
case "admin_system":
window.open(
`https://admin.revolt.chat/panel/inspect/user/${data.id}`,
"_blank",
);
break;
case "copy_message_link":
{
let pathname = `/channel/${data.message.channel_id}/${data.message._id}`;
@@ -275,11 +288,7 @@ export default function ContextMenus() {
window.open(
// ! FIXME: do this from revolt.js
client
.generateFileURL(data.attachment)
?.replace(
"attachments",
"attachments/download",
),
.generateFileURL(data.attachment),
isFirefox || window.native ? "_blank" : "_self",
);
}
@@ -454,6 +463,7 @@ export default function ContextMenus() {
modalController.push({
type: "report",
target: data.target,
messageId: data.messageId,
});
break;
}
@@ -478,6 +488,7 @@ export default function ContextMenus() {
queued,
unread,
contextualChannel: cxid,
contextualMessage,
}: ContextMenuData) => {
const elements: Children[] = [];
let lastDivider = false;
@@ -493,11 +504,17 @@ export default function ContextMenus() {
elements.push(
<MenuItem data={action} disabled={disabled}>
<span style={{ color }}>
<Text
id={`app.context_menu.${
locale ?? action.action
}`}
/>
{locale === "admin" ? (
"Open in Admin Panel"
) : locale === "admin_system" ? (
"Open User in Admin Panel"
) : (
<Text
id={`app.context_menu.${
locale ?? action.action
}`}
/>
)}
</span>
{tip && <div className="tip">{tip}</div>}
</MenuItem>,
@@ -682,6 +699,7 @@ export default function ContextMenus() {
{
action: "report",
target: user,
messageId: contextualMessage,
},
"report_user",
undefined,
@@ -1103,6 +1121,37 @@ export default function ContextMenus() {
}
if (!hideIDButton) {
if (state.experiments.isEnabled("admin_beta")) {
generateAction(
{
action: "admin",
id,
type: cid
? "channel"
: message
? "message"
: "user",
},
"admin",
);
switch (message?.system?.type) {
case "user_added":
case "user_remove":
case "user_joined":
case "user_left":
case "user_kicked":
case "user_banned":
generateAction(
{
action: "admin_system",
id: message.system.id,
},
"admin_system",
);
}
}
generateAction(
{ action: "copy_id", id },
cid
@@ -1138,7 +1187,21 @@ export default function ContextMenus() {
content={
<Text id="app.special.copy_username" />
}>
@{user.username}
<Column gap="0">
<span>
{user.display_name ??
user.username}
</span>
<span
style={{
fontSize: "0.8em",
}}>
{user.username}
{"#"}
{user.discriminator ??
"0000"}
</span>
</Column>
</Tooltip>
</div>
<div

View File

@@ -1,6 +1,6 @@
import { action, makeAutoObservable, runInAction } from "mobx";
import { latestChangelog } from "../../assets/changelogs";
import { changelogEntries, latestChangelog } from "../../assets/changelogs";
import { modalController } from "../../controllers/modals/ModalController";
import Persistent from "../interfaces/Persistent";
import Store from "../interfaces/Store";
@@ -58,10 +58,21 @@ export default class Changelog implements Store, Persistent<Data>, Syncable {
*/
checkForUpdates() {
if (this.viewed < latestChangelog) {
modalController.push({
type: "changelog",
initial: latestChangelog,
});
const expires = new Date(+changelogEntries[latestChangelog].date);
expires.setDate(expires.getDate() + 7);
if (+new Date() < +expires) {
if (latestChangelog === 3) {
modalController.push({
type: "changelog_usernames",
});
} else {
modalController.push({
type: "changelog",
initial: latestChangelog,
});
}
}
runInAction(() => {
this.viewed = latestChangelog;

View File

@@ -10,7 +10,7 @@ import Store from "../interfaces/Store";
/**
* Union type of available experiments.
*/
export type Experiment = "dummy" | "offline_users" | "plugins" | "picker";
export type Experiment = "dummy" | "offline_users" | "plugins" | "admin_beta";
/**
* Currently active experiments.
@@ -19,7 +19,7 @@ export const AVAILABLE_EXPERIMENTS: Experiment[] = [
"dummy",
"offline_users",
"plugins",
"picker",
"admin_beta",
];
/**
@@ -42,10 +42,10 @@ export const EXPERIMENTS: {
description:
"This will enable the experimental plugin API. Only touch this if you know what you're doing.",
},
picker: {
title: "Custom Emoji",
admin_beta: {
title: "Admin Panel Shortcuts",
description:
"This will enable a work-in-progress emoji picker, custom emoji settings and a reaction picker.",
"Adds context menu entries to quickly jump to Revolt Admin Panel. This is intended for use by the team.",
},
};

View File

@@ -66,11 +66,16 @@ export default class Settings
}
@action hydrate(data: ISettings) {
Object.keys(data).forEach(
(key) =>
typeof (data as any)[key] !== "undefined" &&
this.data.set(key, (data as any)[key]),
);
Object.keys(data).forEach((key) => {
const val = (data as any)[key];
if (typeof val !== "undefined") {
if (key === "appearance:emoji") {
setGlobalEmojiPack(val);
}
this.data.set(key, val);
}
});
}
/**

View File

@@ -28,7 +28,7 @@ const AppContainer = styled.div`
background-position: center center !important;
`;
const StatusBar = styled.div`
export const StatusBar = styled.div`
height: 40px;
width: 100%;
display: flex;

View File

@@ -1,5 +1,6 @@
import { Hash } from "@styled-icons/boxicons-regular";
import { Ghost } from "@styled-icons/boxicons-solid";
import dayjs from "dayjs";
import { reaction } from "mobx";
import { observer } from "mobx-react-lite";
import { Redirect, useParams } from "react-router-dom";
@@ -163,6 +164,7 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
const checkUnread = () =>
channel.unread &&
document.hasFocus() &&
channel.client.unreads!.markRead(
channel._id,
channel.last_message_id!,
@@ -176,6 +178,46 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
);
}, [channel]);
useEffect(() => {
let lastSubscribed: number | undefined;
function subscribe() {
if (document.hasFocus()) {
if (
!lastSubscribed ||
dayjs().subtract(10, "minutes").isAfter(lastSubscribed)
) {
lastSubscribed = +new Date();
channel.server?.subscribe();
}
}
}
// Trigger logic every minute
const subTimer = setInterval(subscribe, 60e3);
subscribe();
function onFocus() {
// Mark channel as read if it's unread
if (channel.unread) {
channel.client.unreads!.markRead(
channel._id,
channel.last_message_id!,
true,
);
}
// Subscribe to channel if expired
subscribe();
}
addEventListener("focus", onFocus);
return () => {
removeEventListener("focus", onFocus);
clearInterval(subTimer);
};
}, [channel]);
return (
<AgeGate
type="channel"

View File

@@ -29,6 +29,7 @@ const VoiceBase = styled.div`
margin-top: 48px;
padding: 20px;
background: var(--secondary-background);
flex-grow: 1;
.status {
flex: 1 0;
@@ -54,8 +55,6 @@ const VoiceBase = styled.div`
display: flex;
flex-direction: column;
overflow-y: scroll;
.participants {
margin: 40px 20px;
justify-content: center;

View File

@@ -136,7 +136,7 @@ export const Friend = observer(({ user }: Props) => {
})}>
<UserIcon target={user} size={36} status />
<div className={styles.name}>
<span>{user.username}</span>
<span>{user.display_name ?? user.username}</span>
{subtext && <span className={styles.subtext}>{subtext}</span>}
</div>
<div className={styles.actions}>{actions}</div>

View File

@@ -34,7 +34,7 @@
a {
width: 100%;
height: 6em;
div {
margin: 0;
}
@@ -54,3 +54,4 @@
[data-light="true"] .home svg {
filter: invert(100%);
}

View File

@@ -51,7 +51,7 @@ export default observer(() => {
state.settings.set("appearance:seasonal", !seasonalTheme);
const isDecember = !isTouchscreenDevice && new Date().getMonth() === 11;
const isOctober = !isTouchscreenDevice && new Date().getMonth() === 9
const isOctober = !isTouchscreenDevice && new Date().getMonth() === 9;
const snowflakes = useMemo(() => {
const flakes: string[] = [];
@@ -175,7 +175,7 @@ export default observer(() => {
</CategoryButton>
</Link>
<a
href="https://insrt.uk/donate"
href="https://wiki.revolt.chat/notes/project/financial-support/"
target="_blank"
rel="noreferrer">
<CategoryButton

View File

@@ -10,6 +10,22 @@ import UserIcon from "../../components/common/user/UserIcon";
import Markdown from "../../components/markdown/Markdown";
import { useClient } from "../../controllers/client/ClientController";
const Page = styled.div`
padding: 6em;
min-height: 100vh;
overflow-y: auto;
overflow-x: hidden;
box-sizing: border-box;
@media (max-width: 768px) {
padding: 2em;
}
@media (max-width: 480px) {
padding: 1em;
}
`;
const BotInfo = styled.div`
gap: 12px;
display: flex;
@@ -42,7 +58,7 @@ export default function InviteBot() {
const [group, setGroup] = useState("none");
return (
<div style={{ padding: "6em" }}>
<Page>
<Tip palette="warning">This section is under construction.</Tip>
{typeof data === "undefined" && <Preloader type="spinner" />}
{data && (
@@ -106,6 +122,7 @@ export default function InviteBot() {
</Option>
</>
)}
</div>
</Page>
);
}

View File

@@ -1,17 +1,19 @@
import { Twitter, Github, Mastodon } from "@styled-icons/boxicons-logos";
import { observer } from "mobx-react-lite";
import { Helmet } from "react-helmet";
import { Route, Switch } from "react-router-dom";
import { Link, Route, Switch } from "react-router-dom";
import styles from "./Login.module.scss";
import { Text } from "preact-i18n";
import { useApplicationState } from "../../mobx/State";
import LocaleSelector from "../../components/common/LocaleSelector";
import wideSVG from "/assets/wide.svg";
import LocaleSelector from "../../components/common/LocaleSelector";
import { Titlebar } from "../../components/native/Titlebar";
import { useSystemAlert } from "../../updateWorker";
import { StatusBar } from "../RevoltApp";
import { FormCreate } from "./forms/FormCreate";
import { FormLogin } from "./forms/FormLogin";
import { FormReset, FormSendReset } from "./forms/FormReset";
@@ -21,11 +23,31 @@ export default observer(() => {
const state = useApplicationState();
const theme = state.settings.theme;
const alert = useSystemAlert();
return (
<>
{window.isNative && !window.native.getConfig().frame && (
<Titlebar overlay />
)}
{alert && (
<StatusBar>
<div className="title">{alert.text}</div>
<div className="actions">
{alert.actions?.map((action) =>
action.type === "internal" ? null : action.type ===
"external" ? (
<a
href={action.href}
target="_blank"
rel="noreferrer">
<div className="button">{action.text}</div>{" "}
</a>
) : null,
)}
</div>
</StatusBar>
)}
<div className={styles.login}>
<Helmet>
<meta
@@ -77,17 +99,20 @@ export default observer(() => {
<div className={styles.socials}>
<a
href="https://github.com/revoltchat"
target="_blank" rel="noreferrer">
target="_blank"
rel="noreferrer">
<Github size={24} />
</a>
<a
href="https://twitter.com/revoltchat"
target="_blank" rel="noreferrer">
target="_blank"
rel="noreferrer">
<Twitter size={24} />
</a>
<a
href="https://mastodon.social/@revoltchat"
target="_blank" rel="noreferrer">
target="_blank"
rel="noreferrer">
<Mastodon size={24} />
</a>
</div>
@@ -116,7 +141,8 @@ export default observer(() => {
<a
className={styles.attribution}
href="https://unsplash.com/@fakurian"
target="_blank" rel="noreferrer">
target="_blank"
rel="noreferrer">
<Text id="general.image_by" /> &lrm;@fakurian &rlm;·
unsplash.com
</a>

View File

@@ -202,11 +202,9 @@ export const Form = observer(({ page, callback }: Props) => {
/>
)}
{error && (
<Category>
<I18nError error={error}>
<Text id={`login.error.${page}`} />
</I18nError>
</Category>
<p style={{ fontSize: "0.8em", color: "var(--error)" }}>
<Text id={`error.${error}`} children={error} />
</p>
)}
<Button>
<Text
@@ -259,7 +257,7 @@ export const Form = observer(({ page, callback }: Props) => {
<span>
<Text id="login.unofficial_instance" />{" "}
<a
href="https://developers.revolt.chat/faq/instances#what-is-a-third-party-instance"
href="https://developers.revolt.chat/faq.html"
style={{ color: "var(--accent)" }}
target="_blank"
rel="noreferrer">

View File

@@ -79,7 +79,6 @@ export default observer(() => {
id: "emojis",
icon: <HappyBeaming size={20} />,
title: <Text id="app.settings.server_pages.emojis.title" />,
hidden: !state.experiments.isEnabled("picker"),
},
{
category: (

View File

@@ -80,10 +80,15 @@ const AccountHeader = styled.div`
font-size: 12px;
gap: 2px;
> span {
.new {
font-size: 20px;
font-weight: 600;
}
.full {
font-size: 14px;
font-weight: 600;
}
}
}
@@ -276,7 +281,7 @@ export default observer(() => {
</ButtonItem>
</a>
<a
href="https://insrt.uk/donate"
href="https://wiki.revolt.chat/notes/project/financial-support/"
target="_blank"
rel="noreferrer">
<ButtonItem className={styles.donate} compact>
@@ -336,7 +341,15 @@ export default observer(() => {
onClick={() => openContextMenu("Status")}
/>
<div className="details">
<Username user={client.user!} prefixAt />
<span className="new">
{client.user.display_name ??
client.user.username}
</span>
<span className="full">
{client.user.username}
{"#"}
{client.user.discriminator}
</span>
<UserStatus user={client.user!} />
</div>
</div>

View File

@@ -94,6 +94,33 @@ export function Audio() {
return (
<>
<div className={styles.audio}>
<Tip palette="warning">
<span>
We are currently{" "}
<a
style={{ color: "inherit", fontWeight: "600" }}
href="https://github.com/revoltchat/frontend/issues/14"
target="_blank"
rel="noreferrer">
rebuilding the client
</a>{" "}
and{" "}
<a
style={{ color: "inherit", fontWeight: "600" }}
href="https://trello.com/c/Ay6KdiOV/1-voice-overhaul-and-video-calling"
target="_blank"
rel="noreferrer">
the voice server
</a>{" "}
from scratch.
<br />
<br />
The old voice should work in most cases, but it may
inexplicably not connect in some scenarios and / or
exhibit weird behaviour.
</span>
</Tip>
{!permission && (
<Tip palette="error">
<Text id="app.settings.pages.audio.tip_grant_permission" />

View File

@@ -5,60 +5,76 @@ import { Link } from "react-router-dom";
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { CategoryButton } from "@revoltchat/ui";
import { CategoryButton, Column, Tip } from "@revoltchat/ui";
export function Feedback() {
return (
<div className={styles.feedback}>
<a
href="https://github.com/revoltchat/revolt/discussions"
target="_blank"
rel="noreferrer">
<CategoryButton
action="external"
icon={<Github size={24} />}
description={
<Text id="app.settings.pages.feedback.suggest_desc" />
}>
<Text id="app.settings.pages.feedback.suggest" />
</CategoryButton>
</a>
<a
href="https://github.com/revoltchat/revite/issues/new/choose"
target="_blank"
rel="noreferrer">
<CategoryButton
action="external"
icon={<ListOl size={24} />}
description={
<Text id="app.settings.pages.feedback.issue_desc" />
}>
<Text id="app.settings.pages.feedback.issue" />
</CategoryButton>
</a>
<a
href="https://github.com/orgs/revoltchat/projects/3"
target="_blank"
rel="noreferrer">
<CategoryButton
action="external"
icon={<BugAlt size={24} />}
description={
<Text id="app.settings.pages.feedback.bug_desc" />
}>
<Text id="app.settings.pages.feedback.bug" />
</CategoryButton>
</a>
<Link to="/invite/Testers">
<a>
<Column>
<Tip palette="warning">
<span>
We are currently rebuilding the client from scratch. You can{" "}
<a
style={{ color: "inherit", fontWeight: "600" }}
href="https://github.com/revoltchat/frontend/issues/14"
target="_blank"
rel="noreferrer">
view our progress here
</a>
; in the meantime, we are only accepting and fixing critical
bugs with the current client.
</span>
</Tip>
<div className={styles.feedback}>
<a
href="https://github.com/revoltchat/revolt/discussions"
target="_blank"
rel="noreferrer">
<CategoryButton
action="chevron"
icon={<Group size={24} />}
description="You can report issues and discuss improvements with us directly here.">
{"Join the Revolt Lounge"}
action="external"
icon={<Github size={24} />}
description={
<Text id="app.settings.pages.feedback.suggest_desc" />
}>
<Text id="app.settings.pages.feedback.suggest" />
</CategoryButton>
</a>
</Link>
</div>
<a
href="https://github.com/revoltchat/revite/issues/new/choose"
target="_blank"
rel="noreferrer">
<CategoryButton
action="external"
icon={<ListOl size={24} />}
description={
<Text id="app.settings.pages.feedback.issue_desc" />
}>
<Text id="app.settings.pages.feedback.issue" />
</CategoryButton>
</a>
<a
href="https://github.com/orgs/revoltchat/projects/3"
target="_blank"
rel="noreferrer">
<CategoryButton
action="external"
icon={<BugAlt size={24} />}
description={
<Text id="app.settings.pages.feedback.bug_desc" />
}>
<Text id="app.settings.pages.feedback.bug" />
</CategoryButton>
</a>
<Link to="/invite/Testers">
<a>
<CategoryButton
action="chevron"
icon={<Group size={24} />}
description="You can report issues and discuss improvements with us directly here.">
{"Join the Revolt Lounge"}
</CategoryButton>
</a>
</Link>
</div>
</Column>
);
}

View File

@@ -35,6 +35,7 @@ import { modalController } from "../../../controllers/modals/ModalController";
interface Data {
_id: string;
token: string;
username: string;
public: boolean;
interactions_url?: string;
@@ -74,6 +75,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
const [user, setUser] = useState<User>(client.users.get(bot._id)!);
const [data, setData] = useState<Data>({
_id: bot._id,
token: bot.token,
username: user.username,
public: bot.public,
interactions_url: bot.interactions_url as any,
@@ -94,7 +96,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
const refreshProfile = useCallback(() => {
client.api
.get(`/users/${bot._id as ""}/profile`, undefined, {
headers: { "x-bot-token": bot.token },
headers: { "x-bot-token": data.token },
})
.then((profile) => setProfile(profile ?? {}));
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -149,7 +151,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
"/users/@me",
avatar ? { avatar } : { remove: ["Avatar"] },
{
headers: { "x-bot-token": bot.token },
headers: { "x-bot-token": data.token },
},
);
@@ -168,7 +170,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
? { profile: { background } }
: { remove: ["ProfileBackground"] },
{
headers: { "x-bot-token": bot.token },
headers: { "x-bot-token": data.token },
},
);
@@ -184,7 +186,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
"/users/@me",
content ? { profile: { content } } : { remove: ["ProfileContent"] },
{
headers: { "x-bot-token": bot.token },
headers: { "x-bot-token": data.token },
},
);
@@ -312,6 +314,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
if (editMode) {
setData({
_id: bot._id,
token: data.token,
username: user!.username,
public: bot.public,
interactions_url: bot.interactions_url as any,
@@ -334,7 +337,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
<CategoryButton
account
icon={<Key size={24} />}
onClick={() => modalController.writeText(bot.token)}
onClick={() => modalController.writeText(data.token)}
description={
<>
{"••••• "}
@@ -344,7 +347,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
ev,
modalController.push({
type: "show_token",
token: bot.token,
token: data.token,
name: user!.username,
}),
)
@@ -490,6 +493,25 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
}>
<Text id="app.settings.pages.bots.add" />
</Button>
<Button
palette="error"
onClick={ async () => {
modalController.push({
type: "reset_bot_token",
target: { name: user.username, id: bot._id },
callback: async () => {
const updatedBot = await client.bots.fetch(bot._id);
setData({
...data,
token: updatedBot.bot.token
})
}
})
}
}>
<Text id="app.settings.pages.bots.reset_token" />
</Button>
</>
)}
</div>

View File

@@ -66,7 +66,7 @@ export function Native() {
title="Enable Discord status"
description="Rep Revolt on your Discord status."
/>
<Checkbox
{/* <Checkbox
value={config.build === "nightly"}
onChange={(nightly) => {
const build = nightly ? "nightly" : "stable";
@@ -79,7 +79,7 @@ export function Native() {
}}
title="Revolt Nightly"
description="Use the beta branch of Revolt."
/>
/> */}
<h3>Titlebar</h3>
<Checkbox
@@ -144,7 +144,7 @@ export function Native() {
</Button>
</p>
<h3 style={{ marginTop: "4em" }}>Local Development Mode</h3>
{config.build === "dev" ? (
{/*config.build === "dev" ? (
<>
<h5>Development mode is currently on.</h5>
<Button
@@ -197,7 +197,7 @@ export function Native() {
</Button>
</p>
</>
)}
)*/}
<hr />
<CategoryButton
icon={<img src={RLogo} draggable={false} />}

View File

@@ -694,3 +694,23 @@
section {
margin-bottom: 20px;
}
.titleNew {
display: flex;
margin: 15px 0;
gap: 8px;
align-items: center;
font-size: 0.8125rem;
font-weight: 700;
text-transform: uppercase;
color: var(--secondary-foreground);
.new {
font-size: 10px;
background: var(--accent);
padding: 3px 5px;
border-radius: 20px;
color: white;
}
}

View File

@@ -1,4 +1,5 @@
import { Markdown } from "@styled-icons/boxicons-logos";
import { UserCircle } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { API } from "revolt.js";
@@ -7,7 +8,7 @@ import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
import { Button, LineDivider, Tip } from "@revoltchat/ui";
import { Button, LineDivider, Tip, CategoryButton } from "@revoltchat/ui";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { useTranslation } from "../../../lib/i18n";
@@ -17,6 +18,7 @@ import AutoComplete, {
} from "../../../components/common/AutoComplete";
import { useSession } from "../../../controllers/client/ClientController";
import { FileUploader } from "../../../controllers/client/jsx/legacy/FileUploads";
import { modalController } from "../../../controllers/modals/ModalController";
import { UserProfile } from "../../../controllers/modals/components/legacy/UserProfile";
export const Profile = observer(() => {
@@ -77,6 +79,19 @@ export const Profile = observer(() => {
{...({} as any)}
/>
</div>
<div className={styles.titleNew}>
Display Name
<div className={styles.new}>NEW</div>
</div>
<CategoryButton
onClick={() =>
modalController.push({ type: "modify_displayname" })
}
icon={<UserCircle size={24} />}
action="chevron"
description={"Change your display name to whatever you like"}>
Display Name
</CategoryButton>
{/*<h3>Badges</h3>
<div className={styles.badgePicker}>
<div className={styles.overlay} />

View File

@@ -38,7 +38,9 @@ dayjs.extend(relativeTime);
export function Sessions() {
const client = useClient();
const deviceId =
typeof client.session === "object" ? client.session._id : undefined;
typeof client.session === "object"
? (client.session as unknown as { _id: string })._id
: undefined;
const [sessions, setSessions] = useState<API.SessionInfo[] | undefined>(
undefined,

View File

@@ -369,7 +369,9 @@ function ListElement({
<KanbanList last={false} key={category.id}>
<div className="inner">
<Row>
<KanbanListHeader {...provided.dragHandleProps}>
<KanbanListHeader
{...provided.dragHandleProps}
onClick={startEditing}>
{editing !== undefined ? (
<input
value={editing}
@@ -384,7 +386,7 @@ function ListElement({
id={category.id}
/>
) : (
<span onClick={startEditing}>
<span>
{category.title}
</span>
)}

View File

@@ -4,7 +4,7 @@ import styled from "styled-components";
import { Text } from "preact-i18n";
import { Button, Column, Row, Stacked } from "@revoltchat/ui";
import { Button, Column, Row, Stacked, Tip } from "@revoltchat/ui";
import UserShort from "../../../components/common/user/UserShort";
import { EmojiUploader } from "../../../components/settings/customisation/EmojiUploader";
@@ -33,6 +33,22 @@ export const Emojis = observer(({ server }: Props) => {
return (
<Column>
<Tip palette="warning">
<span>
This UI was never finished and will be polished in the{" "}
<a
style={{ color: "inherit", fontWeight: "600" }}
href="https://github.com/revoltchat/frontend/issues/14"
target="_blank"
rel="noreferrer">
new client
</a>
.<br />
<br />
Also please note that emoji names must be lowercase
alphanumeric!
</span>
</Tip>
{server.havePermission("ManageCustomisation") && (
<EmojiUploader server={server} />
)}

View File

@@ -103,10 +103,43 @@ export const Members = ({ server }: Props) => {
const [query, setQuery] = useState("");
useEffect(() => {
server
.fetchMembers()
.then((data) => data.members)
.then(setData);
function fetch() {
server
.fetchMembers()
.then((data) => data.members)
.then(setData);
}
fetch();
// Members may be invalidated if we stop receiving events
// This is not very accurate, this should be tracked within
// revolt.js so we know the true validity.
let valid = true,
invalidationTimer: number;
function waitToInvalidate() {
invalidationTimer = setTimeout(() => {
valid = false;
}, 15 * 60e3) as never; // 15 minutes
}
function cancelInvalidation() {
if (!valid) {
fetch();
valid = true;
}
clearTimeout(invalidationTimer);
}
addEventListener("blur", waitToInvalidate);
addEventListener("focus", cancelInvalidation);
return () => {
removeEventListener("blur", waitToInvalidate);
removeEventListener("focus", cancelInvalidation);
};
}, [server, setData]);
const members = useMemo(

View File

@@ -1,4 +1,8 @@
import { HelpCircle } from "@styled-icons/boxicons-solid";
import {
HelpCircle,
ChevronUp,
ChevronDown,
} from "@styled-icons/boxicons-solid";
import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite";
import { Server } from "revolt.js";
@@ -16,17 +20,208 @@ import {
ColourSwatches,
InputBox,
Category,
Row,
} from "@revoltchat/ui";
import Tooltip from "../../../components/common/Tooltip";
import { PermissionList } from "../../../components/settings/roles/PermissionList";
import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection";
import { useSession } from "../../../controllers/client/ClientController";
import { modalController } from "../../../controllers/modals/ModalController";
interface Props {
server: Server;
}
const RoleReorderContainer = styled.div`
margin: 16px 0;
`;
const RoleItem = styled.div`
display: flex;
align-items: center;
padding: 12px 16px;
margin: 12px 0;
background: var(--secondary-background);
border-radius: var(--border-radius);
`;
const RoleInfo = styled.div`
flex: 1;
display: flex;
flex-direction: column;
`;
const RoleName = styled.div`
font-weight: 600;
color: var(--foreground);
`;
const RoleRank = styled.div`
font-size: 12px;
color: var(--secondary-foreground);
`;
const RoleControls = styled.div`
display: flex;
gap: 4px;
`;
const MoveButton = styled(Button)`
padding: 4px 8px;
min-width: auto;
`;
/**
* Hook to memo-ize role information with proper ordering
* @param server Target server
* @returns Role array with default at bottom
*/
export function useRolesForReorder(server: Server) {
return useMemo(() => {
const roles = [...server.orderedRoles] as RoleOrDefault[];
roles.push({
id: "default",
name: "Default",
permissions: server.default_permissions,
});
return roles;
}, [server.roles, server.default_permissions]);
}
/**
* Role reordering component
*/
const RoleReorderPanel = observer(
({ server, onExit }: Props & { onExit: () => void }) => {
const initialRoles = useRolesForReorder(server);
const [roles, setRoles] = useState(initialRoles);
const [isReordering, setIsReordering] = useState(false);
// Update local state when server roles change
useMemo(() => {
setRoles(useRolesForReorder(server));
}, [server.roles, server.default_permissions]);
const moveRoleUp = (index: number) => {
if (index === 0 || roles[index].id === "default") return;
const newRoles = [...roles];
[newRoles[index - 1], newRoles[index]] = [
newRoles[index],
newRoles[index - 1],
];
setRoles(newRoles);
};
const moveRoleDown = (index: number) => {
// Can't move down if it's the last non-default role or if it's default
if (index >= roles.length - 2 || roles[index].id === "default")
return;
const newRoles = [...roles];
[newRoles[index], newRoles[index + 1]] = [
newRoles[index + 1],
newRoles[index],
];
setRoles(newRoles);
};
const saveReorder = async () => {
setIsReordering(true);
try {
const nonDefaultRoles = roles.filter(
(role) => role.id !== "default",
);
const roleIds = nonDefaultRoles.map((role) => role.id);
const session = useSession()!;
const client = session.client!;
// Make direct API request since it's not in r.js as of writing
await client.api.patch(`/servers/${server._id}/roles/ranks`, {
ranks: roleIds,
});
console.log("Roles reordered successfully");
} catch (error) {
console.error("Failed to reorder roles:", error);
setRoles(initialRoles);
} finally {
setIsReordering(false);
}
};
const hasChanges = !isEqual(
roles.filter((r) => r.id !== "default").map((r) => r.id),
initialRoles.filter((r) => r.id !== "default").map((r) => r.id),
);
return (
<div>
<SpaceBetween>
<H1>
<Text id="app.settings.permissions.role_ranking" />
</H1>
<Row>
<Button
palette="secondary"
onClick={onExit}
style={{ marginBottom: "16px" }}>
<Text id="app.special.modals.actions.back" />
</Button>
<Button
palette="secondary"
disabled={!hasChanges || isReordering}
onClick={saveReorder}>
<Text id="app.special.modals.actions.save" />
</Button>
</Row>
</SpaceBetween>
<RoleReorderContainer>
{roles.map((role, index) => (
<RoleItem key={role.id}>
<RoleInfo>
<RoleName>{role.name}</RoleName>
<RoleRank>
{role.id === "default" ? (
<Text id="app.settings.permissions.default_desc" />
) : (
<>
<Text id="app.settings.permissions.role_ranking" />{" "}
{index}
</>
)}
</RoleRank>
</RoleInfo>
{role.id !== "default" && (
<RoleControls>
<MoveButton
palette="secondary"
disabled={index === 0}
onClick={() => moveRoleUp(index)}>
<ChevronUp size={16} />
</MoveButton>
<MoveButton
palette="secondary"
disabled={index >= roles.length - 2}
onClick={() => moveRoleDown(index)}>
<ChevronDown size={16} />
</MoveButton>
</RoleControls>
)}
</RoleItem>
))}
</RoleReorderContainer>
</div>
);
},
);
/**
* Hook to memo-ize role information.
* @param server Target server
@@ -50,9 +245,11 @@ export function useRoles(server: Server) {
}
/**
* Roles settings menu
* Updated Roles settings menu with reordering panel
*/
export const Roles = observer(({ server }: Props) => {
const [showReorderPanel, setShowReorderPanel] = useState(false);
// Consolidate all permissions that we can change right now.
const currentRoles = useRoles(server);
@@ -74,213 +271,219 @@ export const Roles = observer(({ server }: Props) => {
margin: 16px 0;
`;
const ReorderButton = styled(Button)`
margin-inline: auto 8px;
`;
if (showReorderPanel) {
return (
<div>
<RoleReorderPanel
server={server}
onExit={() => setShowReorderPanel(false)}
/>
</div>
);
}
return (
<PermissionsLayout
server={server}
rank={server.member?.ranking ?? Infinity}
onCreateRole={(callback) =>
modalController.push({
type: "create_role",
server,
callback,
})
}
editor={({ selected }) => {
const currentRole = currentRoles.find(
(x) => x.id === selected,
)!;
<div>
<PermissionsLayout
server={server}
rank={server.member?.ranking ?? Infinity}
onCreateRole={(callback) =>
modalController.push({
type: "create_role",
server,
callback,
})
}
editor={({ selected }) => {
const currentRole = currentRoles.find(
(x) => x.id === selected,
)!;
if (!currentRole) return null;
if (!currentRole) return null;
// Keep track of whatever role we're editing right now.
const [value, setValue] = useState<Partial<RoleOrDefault>>({});
const [value, setValue] = useState<Partial<RoleOrDefault>>(
{},
);
const currentRoleValue = { ...currentRole, ...value };
const currentRoleValue = { ...currentRole, ...value };
// Calculate permissions we have access to on this server.
const current = server.permission;
function save() {
const { permissions: permsCurrent, ...current } =
currentRole;
const { permissions: permsValue, ...value } =
currentRoleValue;
// Upload new role information to server.
function save() {
const { permissions: permsCurrent, ...current } =
currentRole;
const { permissions: permsValue, ...value } =
currentRoleValue;
if (!isEqual(permsCurrent, permsValue)) {
server.setPermissions(
selected,
typeof permsValue === "number"
? permsValue
: {
allow: permsValue.a,
deny: permsValue.d,
},
);
}
if (!isEqual(permsCurrent, permsValue)) {
server.setPermissions(
selected,
typeof permsValue === "number"
? permsValue
: {
allow: permsValue.a,
deny: permsValue.d,
},
);
if (!isEqual(current, value)) {
server.editRole(selected, value);
}
}
if (!isEqual(current, value)) {
server.editRole(selected, value);
function deleteRole() {
server.deleteRole(selected);
}
}
// Delete the role from this server.
function deleteRole() {
server.deleteRole(selected);
}
return (
<div>
<SpaceBetween>
<H1>
<Text
id="app.settings.actions.edit"
fields={{ name: currentRole.name }}
/>
</H1>
<Button
palette="secondary"
disabled={isEqual(
currentRole,
currentRoleValue,
)}
onClick={save}>
<Text id="app.special.modals.actions.save" />
</Button>
</SpaceBetween>
<hr />
{selected !== "default" && (
<>
<section>
<Category>
<Text id="app.settings.permissions.role_name" />
</Category>
<p>
<InputBox
value={currentRoleValue.name}
onChange={(e) =>
setValue({
...value,
name: e.currentTarget.value,
})
}
palette="secondary"
/>
</p>
</section>
<section>
<Category>{"Role ID"}</Category>
<RoleId>
<Tooltip
content={
"This is a unique identifier for this role."
}>
<HelpCircle size={16} />
</Tooltip>
<Tooltip
content={
<Text id="app.special.copy" />
}>
<a
onClick={() =>
modalController.writeText(
currentRole.id,
)
return (
<div>
<SpaceBetween>
<H1>
<Text
id="app.settings.actions.edit"
fields={{ name: currentRole.name }}
/>
</H1>
<ReorderButton
palette="secondary"
onClick={() => setShowReorderPanel(true)}>
<Text id="app.settings.permissions.role_ranking" />
</ReorderButton>
<Button
palette="secondary"
disabled={isEqual(
currentRole,
currentRoleValue,
)}
onClick={save}>
<Text id="app.special.modals.actions.save" />
</Button>
</SpaceBetween>
<hr />
{selected !== "default" && (
<>
<section>
<Category>
<Text id="app.settings.permissions.role_name" />
</Category>
<p>
<InputBox
value={currentRoleValue.name}
onChange={(e) =>
setValue({
...value,
name: e.currentTarget
.value,
})
}
palette="secondary"
/>
</p>
</section>
<section>
<Category>{"Role ID"}</Category>
<RoleId>
<Tooltip
content={
"This is a unique identifier for this role."
}>
{currentRole.id}
</a>
</Tooltip>
</RoleId>
</section>
<section>
<Category>
<Text id="app.settings.permissions.role_colour" />
</Category>
<p>
<ColourSwatches
value={
currentRoleValue.colour ??
"gray"
}
onChange={(colour) =>
setValue({ ...value, colour })
}
/>
</p>
</section>
<section>
<Category>
<Text id="app.settings.permissions.role_options" />
</Category>
<p>
<Checkbox
value={
currentRoleValue.hoist ?? false
}
onChange={(hoist) =>
setValue({ ...value, hoist })
}
title={
<Text id="app.settings.permissions.hoist_role" />
}
description={
<Text id="app.settings.permissions.hoist_desc" />
}
/>
</p>
</section>
<section>
<Category>
<Text id="app.settings.permissions.role_ranking" />
</Category>
<p>
<InputBox
type="number"
value={currentRoleValue.rank ?? 0}
onChange={(e) =>
setValue({
...value,
rank: parseInt(
e.currentTarget.value,
),
})
}
palette="secondary"
/>
</p>
</section>
</>
)}
<h1>
<Text id="app.settings.permissions.edit_title" />
</h1>
<PermissionList
value={currentRoleValue.permissions}
onChange={(permissions) =>
setValue({
...value,
permissions,
} as RoleOrDefault)
}
target={server}
/>
{selected !== "default" && (
<>
<hr />
<h1>
<Text id="app.settings.categories.danger_zone" />
</h1>
<DeleteRoleButton
palette="error"
compact
onClick={deleteRole}>
<Text id="app.settings.permissions.delete_role" />
</DeleteRoleButton>
</>
)}
</div>
);
}}
/>
<HelpCircle size={16} />
</Tooltip>
<Tooltip
content={
<Text id="app.special.copy" />
}>
<a
onClick={() =>
modalController.writeText(
currentRole.id,
)
}>
{currentRole.id}
</a>
</Tooltip>
</RoleId>
</section>
<section>
<Category>
<Text id="app.settings.permissions.role_colour" />
</Category>
<p>
<ColourSwatches
value={
currentRoleValue.colour ??
"gray"
}
onChange={(colour) =>
setValue({
...value,
colour,
})
}
/>
</p>
</section>
<section>
<Category>
<Text id="app.settings.permissions.role_options" />
</Category>
<p>
<Checkbox
value={
currentRoleValue.hoist ??
false
}
onChange={(hoist) =>
setValue({
...value,
hoist,
})
}
title={
<Text id="app.settings.permissions.hoist_role" />
}
description={
<Text id="app.settings.permissions.hoist_desc" />
}
/>
</p>
</section>
</>
)}
<h1>
<Text id="app.settings.permissions.edit_title" />
</h1>
<PermissionList
value={currentRoleValue.permissions}
onChange={(permissions) =>
setValue({
...value,
permissions,
} as RoleOrDefault)
}
target={server}
/>
{selected !== "default" && (
<>
<hr />
<h1>
<Text id="app.settings.categories.danger_zone" />
</h1>
<DeleteRoleButton
palette="error"
compact
onClick={deleteRole}>
<Text id="app.settings.permissions.delete_role" />
</DeleteRoleButton>
</>
)}
</div>
);
}}
/>
</div>
);
});

View File

@@ -78,7 +78,7 @@ export function useSystemAlert() {
*/
async function checkVersion() {
const { version, poll_rate, alert } = (await fetch(
"https://api.revolt.chat/release",
"https://health.revolt.chat/api/health",
).then((res) => res.json())) as {
version: string;
poll_rate?: number;
@@ -111,7 +111,10 @@ async function checkVersion() {
}
}
if (import.meta.env.VITE_API_URL === "https://api.revolt.chat") {
if (
import.meta.env.VITE_API_URL === "https://api.revolt.chat" ||
import.meta.env.VITE_API_URL === "https://app.revolt.chat/api"
) {
// Check for critical updates hourly
schedule();
checkVersion();

View File

@@ -14,6 +14,7 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "preact",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",
"types": ["vite-plugin-pwa/client"],

159
yarn.lock
View File

@@ -2299,6 +2299,20 @@ __metadata:
languageName: node
linkType: hard
"@revoltchat/rehype-katex@npm:6.0.3-patch.1":
version: 6.0.3-patch.1
resolution: "@revoltchat/rehype-katex@npm:6.0.3-patch.1"
dependencies:
"@types/hast": ^2.0.0
"@types/katex": ^0.14.0
hast-util-from-html-isomorphic: ^1.0.0
hast-util-to-text: ^3.1.0
katex: ^0.16.7
unist-util-visit: ^4.0.0
checksum: 1b7e57ddafa300e96396a5f5d64dad947c201b514562b1d581ac1a683252bc074fcb620fb271c136dd8b84c069dad23cd98ef0b01585be426bb2ce3f5dde004f
languageName: node
linkType: hard
"@revoltchat/ui@portal:external/components::locator=client%40workspace%3A.":
version: 0.0.0-use.local
resolution: "@revoltchat/ui@portal:external/components::locator=client%40workspace%3A."
@@ -2627,6 +2641,13 @@ __metadata:
languageName: node
linkType: hard
"@types/katex@npm:^0.14.0":
version: 0.14.0
resolution: "@types/katex@npm:0.14.0"
checksum: 330e0d0337ba48c87f5b793965fbad673653789bf6e50dfe8d726a7b0cbefd37195055e31503aae629814aa79447e4f23a4b87ad1ac565c0d9a9d9978836f39b
languageName: node
linkType: hard
"@types/lodash.defaultsdeep@npm:^4.6.6":
version: 4.6.6
resolution: "@types/lodash.defaultsdeep@npm:4.6.6"
@@ -3688,6 +3709,7 @@ __metadata:
"@hcaptcha/react-hcaptcha": ^1.4.4
"@insertish/vite-plugin-babel-macros": ^1.0.5
"@preact/preset-vite": ^2.0.0
"@revoltchat/rehype-katex": 6.0.3-patch.1
"@revoltchat/ui": ^1.0.77
"@rollup/plugin-replace": ^2.4.2
"@styled-icons/boxicons-logos": ^10.38.0
@@ -3732,6 +3754,7 @@ __metadata:
lodash.defaultsdeep: ^4.6.1
lodash.isequal: ^4.5.0
long: ^5.2.0
lottie-react: ^2.4.0
mdast-util-to-hast: ^12.1.2
mediasoup-client: "npm:@insertish/mediasoup-client@3.6.36-esnext"
mobx: ^6.6.0
@@ -3750,7 +3773,6 @@ __metadata:
react-router-dom: ^5.2.0
react-scroll: ^1.8.2
react-virtuoso: ^2.12.0
rehype-katex: ^6.0.2
rehype-prism: ^2.1.3
rehype-react: ^7.1.1
remark-breaks: ^3.0.2
@@ -3875,7 +3897,7 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:^8.0.0":
"commander@npm:^8.0.0, commander@npm:^8.3.0":
version: 8.3.0
resolution: "commander@npm:8.3.0"
checksum: 0f82321821fc27b83bd409510bb9deeebcfa799ff0bf5d102128b500b7af22872c0c92cb6a0ebc5a4cf19c6b550fba9cedfa7329d18c6442a625f851377bacf0
@@ -4232,6 +4254,13 @@ __metadata:
languageName: node
linkType: hard
"entities@npm:^4.4.0":
version: 4.5.0
resolution: "entities@npm:4.5.0"
checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7
languageName: node
linkType: hard
"env-paths@npm:^2.2.0":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
@@ -5324,6 +5353,41 @@ __metadata:
languageName: node
linkType: hard
"hast-util-from-dom@npm:^4.0.0":
version: 4.2.0
resolution: "hast-util-from-dom@npm:4.2.0"
dependencies:
hastscript: ^7.0.0
web-namespaces: ^2.0.0
checksum: 0eac72cfb2bad20cf70ad978332be8c746dba27576fea3c3b123d7d1a52a8e10a88a25622b60f45255994163845e3f784fcd7c013b1301f2df4b81e3d0b1b973
languageName: node
linkType: hard
"hast-util-from-html-isomorphic@npm:^1.0.0":
version: 1.0.0
resolution: "hast-util-from-html-isomorphic@npm:1.0.0"
dependencies:
"@types/hast": ^2.0.0
hast-util-from-dom: ^4.0.0
hast-util-from-html: ^1.0.0
unist-util-remove-position: ^4.0.0
checksum: a72786d6757a1a38d76cf74ba86fecfaf0690d465dcae477bfa7199ec03d364ba964f658331406ee7e62e912186df6d4ff38fc3ad050e0e3d8bc33a653df060d
languageName: node
linkType: hard
"hast-util-from-html@npm:^1.0.0":
version: 1.0.2
resolution: "hast-util-from-html@npm:1.0.2"
dependencies:
"@types/hast": ^2.0.0
hast-util-from-parse5: ^7.0.0
parse5: ^7.0.0
vfile: ^5.0.0
vfile-message: ^3.0.0
checksum: 81cbda7dfa4c02fcb4e4359a48e25ebee7bef133dc607c2c036c53df27efacdfa22313a86398391ef22b31decda98c7110f1b441c5a48f419514be6b5d4b5603
languageName: node
linkType: hard
"hast-util-from-parse5@npm:^7.0.0":
version: 7.1.0
resolution: "hast-util-from-parse5@npm:7.1.0"
@@ -5341,12 +5405,12 @@ __metadata:
linkType: hard
"hast-util-is-element@npm:^2.0.0":
version: 2.1.2
resolution: "hast-util-is-element@npm:2.1.2"
version: 2.1.3
resolution: "hast-util-is-element@npm:2.1.3"
dependencies:
"@types/hast": ^2.0.0
"@types/unist": ^2.0.0
checksum: c5fe9f7cde3775d4cbe19a9a55631a80b7a4ea0131fc2e3d097ebe228a35f09b9219f64b788b7a9cf819e6dcb6d1fc7830fd2f10ad536649e436e8c83da41e00
checksum: 9d988f6839a50566a895a3dd19222e6ab1591243f6a3c36bba835b7e9339a2845f1ff1c583425afd602de1a57a76c5bae8a6dc0ab1d6e5d1e252b422cdeadbb7
languageName: node
linkType: hard
@@ -5360,13 +5424,14 @@ __metadata:
linkType: hard
"hast-util-to-text@npm:^3.1.0":
version: 3.1.1
resolution: "hast-util-to-text@npm:3.1.1"
version: 3.1.2
resolution: "hast-util-to-text@npm:3.1.2"
dependencies:
"@types/hast": ^2.0.0
"@types/unist": ^2.0.0
hast-util-is-element: ^2.0.0
unist-util-find-after: ^4.0.0
checksum: 2312a818c8ec7b02307b04175357e5a7a9918f48624d05366668ba60918734ca62b0ee21006a2a448e0e5a198654cd1fa4ba8c813702b465cb487e2320db523a
checksum: d17cf3344c1d584ddd811cbb78d25b6c9819e62c8edb9643b53be38083fd978a6fa9a5bf6e6cd7b5ea48d30d9edc2859acae40b8bb89e166bebcda6017d4703d
languageName: node
linkType: hard
@@ -6004,14 +6069,14 @@ __metadata:
languageName: node
linkType: hard
"katex@npm:^0.15.0":
version: 0.15.6
resolution: "katex@npm:0.15.6"
"katex@npm:^0.16.7":
version: 0.16.7
resolution: "katex@npm:0.16.7"
dependencies:
commander: ^8.0.0
commander: ^8.3.0
bin:
katex: cli.js
checksum: 2da808bbd1d3be27715006cd86767dd3fcce3e317fb3bbd64d407328d2d90de17b5d83062b2cfd0e0d0de32e340efbac214862bc96892a5d1492462e553728d4
checksum: 6c3f61e28820ecba074a149d17be6d731d8a950cdd7826851e16f2b0f3627c4119acd8c6e4fb86950063c4e99a6e478720ea6cca3dc82e59bbca81001ee00b70
languageName: node
linkType: hard
@@ -6205,6 +6270,25 @@ __metadata:
languageName: node
linkType: hard
"lottie-react@npm:^2.4.0":
version: 2.4.0
resolution: "lottie-react@npm:2.4.0"
dependencies:
lottie-web: ^5.10.2
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: e9ea4a89be90a29bde4a83956f76a80d1f8031882f18ea38ef5271d2aafd8e68348ae6297f185ed85b149ca4896fe33aee7faf9780b88a1b289b8e146f477448
languageName: node
linkType: hard
"lottie-web@npm:^5.10.2":
version: 5.12.0
resolution: "lottie-web@npm:5.12.0"
checksum: 77c35be880e484d1a766f21f789ede7c7be59e957579580a18c2dfec08c4812c03e1d652e453166c31fd80556446d6b857fb490d901e34154105532519bc5ef5
languageName: node
linkType: hard
"lru-cache@npm:^6.0.0":
version: 6.0.0
resolution: "lru-cache@npm:6.0.0"
@@ -7268,6 +7352,15 @@ __metadata:
languageName: node
linkType: hard
"parse5@npm:^7.0.0":
version: 7.1.2
resolution: "parse5@npm:7.1.2"
dependencies:
entities: ^4.4.0
checksum: 59465dd05eb4c5ec87b76173d1c596e152a10e290b7abcda1aecf0f33be49646ea74840c69af975d7887543ea45564801736356c568d6b5e71792fd0f4055713
languageName: node
linkType: hard
"path-exists@npm:^4.0.0":
version: 4.0.0
resolution: "path-exists@npm:4.0.0"
@@ -7794,23 +7887,7 @@ __metadata:
languageName: node
linkType: hard
"rehype-katex@npm:^6.0.2":
version: 6.0.2
resolution: "rehype-katex@npm:6.0.2"
dependencies:
"@types/hast": ^2.0.0
"@types/katex": ^0.11.0
hast-util-to-text: ^3.1.0
katex: ^0.15.0
rehype-parse: ^8.0.0
unified: ^10.0.0
unist-util-remove-position: ^4.0.0
unist-util-visit: ^4.0.0
checksum: ac8b3486441697b8e22cb7ebf6ec58e06d190240f45b128fe60422b9eb887599f38406581e6e3356af967eb1d45d631b0c09387f060190641f402f56c78fa771
languageName: node
linkType: hard
"rehype-parse@npm:^7 || ^ 8, rehype-parse@npm:^8.0.0":
"rehype-parse@npm:^7 || ^ 8":
version: 8.0.4
resolution: "rehype-parse@npm:8.0.4"
dependencies:
@@ -8016,14 +8093,14 @@ __metadata:
languageName: node
linkType: hard
"revolt-api@npm:0.5.8":
version: 0.5.8
resolution: "revolt-api@npm:0.5.8"
"revolt-api@npm:0.6.0":
version: 0.6.0
resolution: "revolt-api@npm:0.6.0"
dependencies:
"@insertish/oapi": 0.1.18
axios: ^0.26.1
lodash.defaultsdeep: ^4.6.1
checksum: d488fa87774e4e9d0b3136779c555cbed257d2b76a298b17dca346cc3909f81e84aa516c10a415a7c20cd31990d540ade5489f69e06f69e14ba4974a26aa145e
checksum: 94865aa1aee5b05682ffe4e40a0fe431809f879437399943fc429d078160a5fe24686011b43639d6fae88e9a7a43fbe4bb9beb9bbe1bb3070965636f0ec051fd
languageName: node
linkType: hard
@@ -8040,7 +8117,7 @@ __metadata:
lodash.isequal: ^4.5.0
long: ^5.2.0
mobx: ^6.3.2
revolt-api: 0.5.8
revolt-api: 0.6.0
ulid: ^2.3.0
ws: ^8.2.2
languageName: node
@@ -9120,12 +9197,12 @@ __metadata:
linkType: hard
"unist-util-find-after@npm:^4.0.0":
version: 4.0.0
resolution: "unist-util-find-after@npm:4.0.0"
version: 4.0.1
resolution: "unist-util-find-after@npm:4.0.1"
dependencies:
"@types/unist": ^2.0.0
unist-util-is: ^5.0.0
checksum: 8381ef0bad18a0b1fa1c7ee47f94a2578ab6bf572eb126a1f179526b9dca47584fc070976f2d83bbe381161fa33b9164a894d0279a30ec83e65433356d43df57
checksum: bed7e7a1a87539bea0b33ddc9ce8e2f3fdd4a7c87e143a848ed5bbb4cf9c563ade7ecf80b3ee5a38f9ad9e6af29cdb8cdde9001eea92542cbb14784f5add7019
languageName: node
linkType: hard
@@ -9160,12 +9237,12 @@ __metadata:
linkType: hard
"unist-util-remove-position@npm:^4.0.0":
version: 4.0.1
resolution: "unist-util-remove-position@npm:4.0.1"
version: 4.0.2
resolution: "unist-util-remove-position@npm:4.0.2"
dependencies:
"@types/unist": ^2.0.0
unist-util-visit: ^4.0.0
checksum: 7d2808662ac65f2b2f615822b78060419f738fb3b074b10cec77c596ea966b8f5c47553d2d322822a5975c49d2b21cdd64c198ae9fb02a9d54d1afa6342cdd6a
checksum: 989831da913d09a82a99ed9b47b78471b6409bde95942cde47e09da54b7736516f17e3c7e026af468684c1efcec5fb52df363381b2f9dc7fd96ce791c5a2fa4a
languageName: node
linkType: hard