48 Commits

Author SHA1 Message Date
Declan Chidlow
87dfca26d7 Add AutoComplete for role completion 2025-06-02 10:47:08 +08: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
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
36 changed files with 523 additions and 272 deletions

4
.env
View File

@@ -1,5 +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=https://app.revolt.chat/api
# VITE_API_URL=http://local.revolt.chat:8000 # VITE_API_URL=http://local.revolt.chat:8000
# VITE_API_URL=https://revolt.chat/api VITE_API_URL=https://app.revolt.chat/api
VITE_THEMES_URL=https://themes.revolt.chat 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 ## 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 * [ ] 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 * [ ] 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) * [ ] (optional) I have opened a pull request on [the translation repository](https://github.com/revoltchat/translations)

View File

@@ -30,55 +30,18 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: 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: publish:
needs: [test]
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v4
with: with:
submodules: "recursive" submodules: "recursive"
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v3
- name: Cache amd64 Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache/linux/amd64
key: ${{ runner.os }}-buildx-linux/amd64-${{ github.sha }}
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v3 uses: docker/metadata-action@v3
@@ -86,26 +49,22 @@ jobs:
images: revoltchat/client, ghcr.io/revoltchat/client images: revoltchat/client, ghcr.io/revoltchat/client
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1 uses: docker/login-action@v1
if: github.event_name != 'pull_request'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Container Registry - name: Login to Github Container Registry
uses: docker/login-action@v1 uses: docker/login-action@v3
if: github.event_name != 'pull_request'
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and publish - name: Build and publish
uses: docker/build-push-action@v2 uses: docker/build-push-action@v6
with: with:
context: . context: .
push: true push: ${{ github.event_name != 'pull_request' }}
platforms: linux/amd64 platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} 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=' gh api graphql -f query='
query { query {
organization(login: "revoltchat"){ organization(login: "revoltchat"){
projectV2(number: 3) { projectV2(number: 5) {
id id
fields(first:20) { fields(first:20) {
nodes { nodes {
@@ -35,7 +35,7 @@ jobs:
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV 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 '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 - name: Add PR to project
env: env:

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 # Revite
## Description ## Description

2
external/lang vendored

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Channel, User } from "revolt.js"; import { Channel, User, Role } from "revolt.js";
import { Emoji as CustomEmoji } from "revolt.js/esm/maps/Emojis"; import { Emoji as CustomEmoji } from "revolt.js/esm/maps/Emojis";
import styled, { css } from "styled-components/macro"; import styled, { css } from "styled-components/macro";
@@ -29,11 +29,16 @@ export type AutoCompleteState =
type: "channel"; type: "channel";
matches: Channel[]; matches: Channel[];
} }
| {
type: "role";
matches: Role[];
}
)); ));
export type SearchClues = { export type SearchClues = {
users?: { type: "channel"; id: string } | { type: "all" }; users?: { type: "channel"; id: string } | { type: "all" };
channels?: { server: string }; channels?: { server: string };
roles?: { server: string };
}; };
export type AutoCompleteProps = { export type AutoCompleteProps = {
@@ -59,7 +64,7 @@ export function useAutoComplete(
function findSearchString( function findSearchString(
el: HTMLTextAreaElement, el: HTMLTextAreaElement,
): ["emoji" | "user" | "channel", string, number] | undefined { ): ["emoji" | "user" | "channel" | "role", string, number] | undefined {
if (el.selectionStart === el.selectionEnd) { if (el.selectionStart === el.selectionEnd) {
const cursor = el.selectionStart; const cursor = el.selectionStart;
const content = el.value.slice(0, cursor); const content = el.value.slice(0, cursor);
@@ -71,6 +76,8 @@ export function useAutoComplete(
return ["user", "", j]; return ["user", "", j];
} else if (content[j] === "#") { } else if (content[j] === "#") {
return ["channel", "", j]; return ["channel", "", j];
} else if (content[j] === "%") {
return ["role", "", j];
} }
while (j >= 0 && valid.test(content[j])) { while (j >= 0 && valid.test(content[j])) {
@@ -80,7 +87,12 @@ export function useAutoComplete(
if (j === -1) return; if (j === -1) return;
const current = content[j]; const current = content[j];
if (current === ":" || current === "@" || current === "#") { if (
current === ":" ||
current === "@" ||
current === "#" ||
current === "%"
) {
const search = content.slice(j + 1, content.length); const search = content.slice(j + 1, content.length);
const minLen = current === ":" ? 2 : 1; const minLen = current === ":" ? 2 : 1;
@@ -90,6 +102,8 @@ export function useAutoComplete(
? "channel" ? "channel"
: current === ":" : current === ":"
? "emoji" ? "emoji"
: current === "%"
? "role"
: "user", : "user",
search.toLowerCase(), search.toLowerCase(),
current === ":" ? j + 1 : j, current === ":" ? j + 1 : j,
@@ -230,6 +244,42 @@ export function useAutoComplete(
return; return;
} }
} }
if (type === "role" && searchClues?.roles) {
const server = client.servers.get(searchClues.roles.server);
let roles: (Role & { id: string })[] = [];
if (server?.roles) {
roles = Object.entries(server.roles).map(([id, role]) => ({
...role,
id,
}));
}
const matches = (
search.length > 0
? roles.filter((role) =>
role.name.toLowerCase().match(regex),
)
: roles
)
.splice(0, 5)
.filter((x) => typeof x !== "undefined");
if (matches.length > 0) {
const currentPosition =
state.type !== "none" ? state.selected : 0;
setState({
type: "role",
matches,
selected: Math.min(currentPosition, matches.length - 1),
within: false,
});
return;
}
}
} }
if (state.type !== "none") { if (state.type !== "none") {
@@ -262,6 +312,14 @@ export function useAutoComplete(
state.matches[state.selected]._id, state.matches[state.selected]._id,
"> ", "> ",
); );
} else if (state.type === "role") {
content.splice(
index,
search.length + 1,
"<%",
state.matches[state.selected].id,
"> ",
);
} else { } else {
content.splice( content.splice(
index, index,
@@ -492,7 +550,7 @@ export default function AutoComplete({
{state.type === "user" && {state.type === "user" &&
state.matches.map((match, i) => ( state.matches.map((match, i) => (
<button <button
key={match} key={match._id}
className={i === state.selected ? "active" : ""} className={i === state.selected ? "active" : ""}
onMouseEnter={() => onMouseEnter={() =>
(i !== state.selected || !state.within) && (i !== state.selected || !state.within) &&
@@ -517,7 +575,7 @@ export default function AutoComplete({
{state.type === "channel" && {state.type === "channel" &&
state.matches.map((match, i) => ( state.matches.map((match, i) => (
<button <button
key={match} key={match._id}
className={i === state.selected ? "active" : ""} className={i === state.selected ? "active" : ""}
onMouseEnter={() => onMouseEnter={() =>
(i !== state.selected || !state.within) && (i !== state.selected || !state.within) &&
@@ -539,6 +597,40 @@ export default function AutoComplete({
{match.name} {match.name}
</button> </button>
))} ))}
{state.type === "role" &&
state.matches.map((match, i) => (
<button
key={match._id}
className={i === state.selected ? "active" : ""}
onMouseEnter={() =>
(i !== state.selected || !state.within) &&
setState({
...state,
selected: i,
within: true,
})
}
onMouseLeave={() =>
state.within &&
setState({
...state,
within: false,
})
}
onClick={onClick}>
<div
style={{
width: "16px",
height: "16px",
borderRadius: "50%",
backgroundColor: match.colour || "#7c7c7c",
marginRight: "8px",
flexShrink: 0,
}}
/>
{match.name}
</button>
))}
</div> </div>
</Base> </Base>
); );

View File

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

View File

@@ -305,7 +305,7 @@ export default observer(({ channel }: Props) => {
if (!state.draft.has(channel._id)) { if (!state.draft.has(channel._id)) {
setMessage(text); setMessage(text);
} else { } 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; return;
const content = state.draft.get(channel._id)?.content?.trim() ?? ""; 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; if (content.length === 0) return;
internalEmit("NewMessages", "hide"); internalEmit("NewMessages", "hide");
@@ -406,7 +406,9 @@ export default observer(({ channel }: Props) => {
* @returns * @returns
*/ */
async function sendFile(content: string) { async function sendFile(content: string) {
if (uploadState.type !== "attached") return; if (uploadState.type !== "attached" && uploadState.type !== "failed")
return;
const attachments: string[] = []; const attachments: string[] = [];
setMessage; setMessage;
@@ -565,6 +567,7 @@ export default observer(({ channel }: Props) => {
channel.channel_type === "TextChannel" channel.channel_type === "TextChannel"
? { server: channel.server_id! } ? { server: channel.server_id! }
: undefined, : undefined,
roles: { server: channel.server_id! },
}); });
return ( return (

View File

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

View File

@@ -119,7 +119,7 @@ export default function UserBadges({ badges, uid }: Props) {
}} }}
onClick={() => { onClick={() => {
window.open( window.open(
"https://insrt.uk/donate", "https://wiki.revolt.chat/notes/project/financial-support/",
"_blank", "_blank",
); );
}} }}

View File

@@ -185,7 +185,8 @@ const Container = styled.div<{ largeEmoji: boolean }>`
/** /**
* Regex for matching execessive recursion of blockquotes and lists * 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 * Regex for matching multi-line blockquotes
@@ -247,9 +248,13 @@ export default memo(({ content, disallowBigEmoji }: MarkdownProps) => {
const [Content, setContent] = useState<React.ReactElement>(null!); const [Content, setContent] = useState<React.ReactElement>(null!);
useLayoutEffect(() => { useLayoutEffect(() => {
render try {
.process(sanitisedContent) render
.then((file) => setContent(file.result)); .process(sanitisedContent)
.then((file) => setContent(file.result));
} catch (err) {
setContent("Message failed to render." as never);
}
}, [sanitisedContent]); }, [sanitisedContent]);
const largeEmoji = useMemo( const largeEmoji = useMemo(

View File

@@ -39,7 +39,7 @@ export default function AccountManagement() {
<CategoryButton <CategoryButton
icon={<Block size={24} color="var(--error)" />} icon={<Block size={24} color="var(--error)" />}
description={ 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" action="chevron"
onClick={callback("disable")}> onClick={callback("disable")}>
@@ -49,7 +49,7 @@ export default function AccountManagement() {
<CategoryButton <CategoryButton
icon={<Trash size={24} color="var(--error)" />} icon={<Trash size={24} color="var(--error)" />}
description={ 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" action="chevron"
onClick={callback("delete")}> onClick={callback("delete")}>

View File

@@ -159,7 +159,7 @@ class ClientController {
}) })
.catch((err) => { .catch((err) => {
const error = takeError(err); const error = takeError(err);
if (error === "Forbidden" || error === "Unauthorized") { if (error === "Unauthorized") {
this.sessions.delete(user_id); this.sessions.delete(user_id);
this.current = null; this.current = null;
this.pickNextSession(); this.pickNextSession();

View File

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

View File

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

View File

@@ -297,4 +297,5 @@ export const modalController = new ModalControllerExtended({
report_success: ReportSuccess, report_success: ReportSuccess,
modify_displayname: ModifyDisplayname, modify_displayname: ModifyDisplayname,
changelog_usernames: ChangelogUsernames, changelog_usernames: ChangelogUsernames,
reset_bot_token: Confirmation
}); });

View File

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

View File

@@ -5,6 +5,7 @@ import { Text } from "preact-i18n";
import { Column, H1, IconButton, Modal, Row } from "@revoltchat/ui"; import { Column, H1, IconButton, Modal, Row } from "@revoltchat/ui";
import Markdown from "../../../components/markdown/Markdown"; import Markdown from "../../../components/markdown/Markdown";
import { useClient } from "../../client/ClientController";
import { report } from "../../safety"; import { report } from "../../safety";
import { modalController } from "../ModalController"; import { modalController } from "../ModalController";
import { ModalProps } from "../types"; import { ModalProps } from "../types";
@@ -13,6 +14,49 @@ export default function ServerInfo({
server, server,
...props ...props
}: ModalProps<"server_info">) { }: 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 ( return (
<Modal <Modal
{...props} {...props}
@@ -26,30 +70,7 @@ export default function ServerInfo({
</IconButton> </IconButton>
</Row> </Row>
} }
actions={[ actions={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",
},
]}>
<Markdown content={server.description!} /> <Markdown content={server.description!} />
</Modal> </Modal>
); );

View File

@@ -6,6 +6,7 @@ import {
UserX, UserX,
Group, Group,
InfoCircle, InfoCircle,
Flag,
} from "@styled-icons/boxicons-solid"; } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Link, useHistory } from "react-router-dom"; import { Link, useHistory } from "react-router-dom";
@@ -246,6 +247,24 @@ export const UserProfile = observer(
<UserX size={28} /> <UserX size={28} />
</IconButton> </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> </div>
{badges > 0 && ( {badges > 0 && (
<div <div

View File

@@ -190,6 +190,11 @@ export type Modal = {
type: "report_success"; type: "report_success";
user?: User; user?: User;
} }
| {
type: "reset_bot_token";
target: { name: string, id: string },
callback: () => Promise<void>;
}
); );
export type ModalProps<T extends Modal["type"]> = Modal & { type: T } & { 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 { Cog, UserVoice } from "@styled-icons/boxicons-solid";
import { isFirefox } from "react-device-detect"; import { isFirefox } from "react-device-detect";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { import { Channel, Message, Server, User, API, Permission, UserPermission, Member } from "revolt.js";
Channel,
Message,
Server,
User,
API,
Permission,
UserPermission,
Member,
} from "revolt.js";
import {
ContextMenuWithData,
MenuItem, import { ContextMenuWithData, MenuItem, openContextMenu } from "preact-context-menu";
openContextMenu,
} from "preact-context-menu";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { Column, IconButton, LineDivider } from "@revoltchat/ui"; import { Column, IconButton, LineDivider } from "@revoltchat/ui";
import { useApplicationState } from "../mobx/State"; import { useApplicationState } from "../mobx/State";
import { QueuedMessage } from "../mobx/stores/MessageQueue"; import { QueuedMessage } from "../mobx/stores/MessageQueue";
import { NotificationState } from "../mobx/stores/NotificationOptions"; import { NotificationState } from "../mobx/stores/NotificationOptions";
import CMNotifications from "./contextmenu/CMNotifications"; import CMNotifications from "./contextmenu/CMNotifications";
import Tooltip from "../components/common/Tooltip"; import Tooltip from "../components/common/Tooltip";
import UserStatus from "../components/common/user/UserStatus"; import UserStatus from "../components/common/user/UserStatus";
import { useSession } from "../controllers/client/ClientController"; import { useSession } from "../controllers/client/ClientController";
@@ -36,6 +33,7 @@ import { modalController } from "../controllers/modals/ModalController";
import { internalEmit } from "./eventEmitter"; import { internalEmit } from "./eventEmitter";
import { getRenderer } from "./renderer/Singleton"; import { getRenderer } from "./renderer/Singleton";
interface ContextMenuData { interface ContextMenuData {
user?: string; user?: string;
server?: string; server?: string;
@@ -53,6 +51,7 @@ interface ContextMenuData {
type Action = type Action =
| { action: "copy_id"; id: string } | { action: "copy_id"; id: string }
| { action: "admin"; id: string; type: "channel" | "message" | "user" } | { action: "admin"; id: string; type: "channel" | "message" | "user" }
| { action: "admin_system"; id: string }
| { action: "copy_message_link"; message: Message } | { action: "copy_message_link"; message: Message }
| { action: "copy_selection" } | { action: "copy_selection" }
| { action: "copy_text"; content: string } | { action: "copy_text"; content: string }
@@ -140,6 +139,12 @@ export default function ContextMenus() {
"_blank", "_blank",
); );
break; break;
case "admin_system":
window.open(
`https://admin.revolt.chat/panel/inspect/user/${data.id}`,
"_blank",
);
break;
case "copy_message_link": case "copy_message_link":
{ {
let pathname = `/channel/${data.message.channel_id}/${data.message._id}`; let pathname = `/channel/${data.message.channel_id}/${data.message._id}`;
@@ -283,11 +288,7 @@ export default function ContextMenus() {
window.open( window.open(
// ! FIXME: do this from revolt.js // ! FIXME: do this from revolt.js
client client
.generateFileURL(data.attachment) .generateFileURL(data.attachment),
?.replace(
"attachments",
"attachments/download",
),
isFirefox || window.native ? "_blank" : "_self", isFirefox || window.native ? "_blank" : "_self",
); );
} }
@@ -505,6 +506,8 @@ export default function ContextMenus() {
<span style={{ color }}> <span style={{ color }}>
{locale === "admin" ? ( {locale === "admin" ? (
"Open in Admin Panel" "Open in Admin Panel"
) : locale === "admin_system" ? (
"Open User in Admin Panel"
) : ( ) : (
<Text <Text
id={`app.context_menu.${ id={`app.context_menu.${
@@ -1131,6 +1134,22 @@ export default function ContextMenus() {
}, },
"admin", "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( generateAction(

View File

@@ -1,5 +1,6 @@
import { Hash } from "@styled-icons/boxicons-regular"; import { Hash } from "@styled-icons/boxicons-regular";
import { Ghost } from "@styled-icons/boxicons-solid"; import { Ghost } from "@styled-icons/boxicons-solid";
import dayjs from "dayjs";
import { reaction } from "mobx"; import { reaction } from "mobx";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Redirect, useParams } from "react-router-dom"; import { Redirect, useParams } from "react-router-dom";
@@ -163,6 +164,7 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
const checkUnread = () => const checkUnread = () =>
channel.unread && channel.unread &&
document.hasFocus() &&
channel.client.unreads!.markRead( channel.client.unreads!.markRead(
channel._id, channel._id,
channel.last_message_id!, channel.last_message_id!,
@@ -176,6 +178,46 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
); );
}, [channel]); }, [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 ( return (
<AgeGate <AgeGate
type="channel" type="channel"

View File

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

View File

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

View File

@@ -202,11 +202,9 @@ export const Form = observer(({ page, callback }: Props) => {
/> />
)} )}
{error && ( {error && (
<Category> <p style={{ fontSize: "0.8em", color: "var(--error)" }}>
<I18nError error={error}> <Text id={`error.${error}`} children={error} />
<Text id={`login.error.${page}`} /> </p>
</I18nError>
</Category>
)} )}
<Button> <Button>
<Text <Text
@@ -259,7 +257,7 @@ export const Form = observer(({ page, callback }: Props) => {
<span> <span>
<Text id="login.unofficial_instance" />{" "} <Text id="login.unofficial_instance" />{" "}
<a <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)" }} style={{ color: "var(--accent)" }}
target="_blank" target="_blank"
rel="noreferrer"> rel="noreferrer">

View File

@@ -281,7 +281,7 @@ export default observer(() => {
</ButtonItem> </ButtonItem>
</a> </a>
<a <a
href="https://insrt.uk/donate" href="https://wiki.revolt.chat/notes/project/financial-support/"
target="_blank" target="_blank"
rel="noreferrer"> rel="noreferrer">
<ButtonItem className={styles.donate} compact> <ButtonItem className={styles.donate} compact>

View File

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

View File

@@ -144,7 +144,7 @@ export function Native() {
</Button> </Button>
</p> </p>
<h3 style={{ marginTop: "4em" }}>Local Development Mode</h3> <h3 style={{ marginTop: "4em" }}>Local Development Mode</h3>
{config.build === "dev" ? ( {/*config.build === "dev" ? (
<> <>
<h5>Development mode is currently on.</h5> <h5>Development mode is currently on.</h5>
<Button <Button
@@ -197,7 +197,7 @@ export function Native() {
</Button> </Button>
</p> </p>
</> </>
)} )*/}
<hr /> <hr />
<CategoryButton <CategoryButton
icon={<img src={RLogo} draggable={false} />} icon={<img src={RLogo} draggable={false} />}

View File

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

View File

@@ -103,10 +103,43 @@ export const Members = ({ server }: Props) => {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
useEffect(() => { useEffect(() => {
server function fetch() {
.fetchMembers() server
.then((data) => data.members) .fetchMembers()
.then(setData); .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]); }, [server, setData]);
const members = useMemo( const members = useMemo(

View File

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