diff --git a/.github/workflows/deploy-pull-request.yml b/.github/workflows/deploy-pull-request.yml index 9c0bea7897..b330c3c1c9 100644 --- a/.github/workflows/deploy-pull-request.yml +++ b/.github/workflows/deploy-pull-request.yml @@ -15,7 +15,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: Download pr number - uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 + uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 with: workflow: ${{ github.event.workflow.id }} run_id: ${{ github.event.workflow_run.id }} @@ -24,7 +24,7 @@ jobs: id: pr run: echo "id=$(> $GITHUB_OUTPUT - name: Download artifact - uses: dawidd6/action-download-artifact@07ab29fd4a977ae4d2b275087cf67563dfdf0295 + uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 with: workflow: ${{ github.event.workflow.id }} run_id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml index 0a758c5193..24edda96fd 100644 --- a/.github/workflows/prod-deploy.yml +++ b/.github/workflows/prod-deploy.yml @@ -52,7 +52,7 @@ jobs: gpg --export | xxd -p echo '${{ secrets.GNUPG_PASSPHRASE }}' | gpg --batch --yes --pinentry-mode loopback --passphrase-fd 0 --armor --detach-sign cinny-${{ steps.vars.outputs.tag }}.tar.gz - name: Upload tagged release - uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 with: files: | cinny-${{ steps.vars.outputs.tag }}.tar.gz @@ -72,19 +72,19 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.10.0 - name: Login to Docker Hub - uses: docker/login-action@v3.4.0 + uses: docker/login-action@v3.5.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to the Container registry - uses: docker/login-action@v3.4.0 + uses: docker/login-action@v3.5.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v5.7.0 + uses: docker/metadata-action@v5.8.0 with: images: | ${{ secrets.DOCKER_USERNAME }}/cinny diff --git a/.gitignore b/.gitignore index 1af58a970d..d088373743 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ node_modules devAssets .DS_Store -.idea \ No newline at end of file +.idea + +.env \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index abb65ee515..718fed729b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN npm run build ## App -FROM nginx:1.27.4-alpine +FROM nginx:1.29.0-alpine COPY --from=builder /src/dist /app COPY --from=builder /src/docker-nginx.conf /etc/nginx/conf.d/default.conf diff --git a/config.json b/config.json index de6015a104..dc03d4879f 100644 --- a/config.json +++ b/config.json @@ -10,6 +10,12 @@ ], "allowCustomHomeservers": true, + "pushNotificationDetails": { + "pushNotifyUrl": "https://cinny.cc/_matrix/push/v1/notify", + "vapidPublicKey": "BHLwykXs79AbKNiblEtZZRAgnt7o5_ieImhVJD8QZ01MVwAHnXwZzNgQEJJEU3E5CVsihoKtb7yaNe5x3vmkWkI", + "webPushAppID": "cc.cinny.web" + }, + "featuredCommunities": { "openAsDefault": false, "spaces": [ diff --git a/docs/Caddyfile b/docs/Caddyfile new file mode 100644 index 0000000000..3d03c2f49c --- /dev/null +++ b/docs/Caddyfile @@ -0,0 +1,10 @@ +(tls_cloudflare) { + tls { + dns cloudflare {env.CLOUDFLARE_API_TOKEN} + } +} + + { + import tls_cloudflare + reverse_proxy sygnal:5000 +} diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 0000000000..6900bc6779 --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,9 @@ +FROM caddy:builder AS builder + +RUN xcaddy build \ + --with github.com/caddy-dns/cloudflare +FROM caddy:latest + +COPY --from=builder /usr/bin/caddy /usr/bin/caddy +COPY Caddyfile /etc/caddy/Caddyfile +COPY .env /etc/caddy/.env \ No newline at end of file diff --git a/docs/sample.env b/docs/sample.env new file mode 100644 index 0000000000..dc3b650cd6 --- /dev/null +++ b/docs/sample.env @@ -0,0 +1 @@ +CLOUDFLARE_API_TOKEN= \ No newline at end of file diff --git a/docs/sygnal-setup.md b/docs/sygnal-setup.md new file mode 100644 index 0000000000..0619433fd7 --- /dev/null +++ b/docs/sygnal-setup.md @@ -0,0 +1,308 @@ +## Sygnal with Caddy & Cloudflare on Vultr + +This document walks you through setting up a [Sygnal](https://github.com/matrix-org/sygnal) push gateway for Matrix, running in a Docker container. We will use [Caddy](https://caddyserver.com/) as a reverse proxy, also in Docker, to handle HTTPS automatically using DNS challenges with [Cloudflare](https://www.cloudflare.com/). + +Now Cloudflare and Vultr have a deal in place where traffic from Cloudflare to Vultr and vice versa does not incur bandwidth usage. So you can pass endless amounts through without any extra billing. This is why the docs utilize Vultr, but you're free to use whatever cloud provider you want and not use Cloudflare if you so choose. + +### Prerequisites + +1. **Vultr Server**: A running server instance. This guide assumes a fresh server running a common Linux distribution like Debian, Ubuntu, or Alpine. +2. **Domain Name**: A domain name managed through Cloudflare. +3. **Cloudflare Account**: Your domain must be using Cloudflare's DNS. +4. **Docker & Docker Compose**: Docker and `docker-compose` must be installed on your Vultr server. +5. **A Matrix Client**: A client like [Cinny](https://github.com/cinnyapp/cinny) that you want to point to your new push gateway. + +--- + +### Step 1: Cloudflare Configuration + +Before touching the server, we need to configure Cloudflare. + +#### 1.1. DNS Record + +In your Cloudflare dashboard, create an **A** (for IPv4) or **AAAA** (for IPv6) record for the subdomain you'll use for Sygnal. Point it to your Vultr server's IP address. + +- **Type**: `A` or `AAAA` +- **Name**: `sygnal.your-domain.com` (or your chosen subdomain) +- **Content**: Your Vultr server's IP address +- **Proxy status**: **Proxied** (Orange Cloud). This is important for Caddy's setup. + +#### 1.2. API Token + +Caddy needs an API token to prove to Cloudflare that you own the domain so it can create the necessary DNS records for issuing an SSL certificate. + +1. Go to **My Profile** \> **API Tokens** in Cloudflare. +2. Click **Create Token**. +3. Use the **Edit zone DNS** template. +4. Under **Permissions**, ensure `Zone:DNS:Edit` is selected. +5. Under **Zone Resources**, select the specific zone for `your-domain.com`. +6. Continue to summary and create the token. +7. **Copy the generated token immediately.** You will not be able to see it again. We will use this as your `CLOUDFLARE_API_TOKEN`. + +--- + +### Step 2: Server Preparation + +#### 2.1. Connect to your Server (SSH) + +If your Vultr instance uses an IPv6 address, connecting via SSH can sometimes be tricky. You can create an alias in your local `~/.ssh/config` file to make it easier. + +Open or create `~/.ssh/config` on your local machine and add: + +``` +Host vultr-sygnal + # Replace with your server's IPv6 or IPv4 address + Hostname 2001:19f0:5400:1532:5400:05ff:fe78:fb25 + User root + # For IPv6, uncomment the line below + # AddressFamily inet6 +``` + +Now you can connect simply by typing `ssh vultr-sygnal`. + +#### 2.2. Install Docker and Docker Compose + +Follow the official Docker documentation to install the Docker Engine and Docker Compose for your server's operating system. + +#### 2.3. Configure Firewall + +We need to allow HTTP and HTTPS traffic so Caddy can obtain certificates and serve requests. If you are using `ufw`: + +```sh +sudo ufw allow 80/tcp +sudo ufw allow 443/tcp +sudo ufw enable +sudo ufw status +``` + +--- + +### Step 3: Project Structure and Configuration + +On your Vultr server, let's create a directory to hold all our configuration files. + +```sh +mkdir -p /opt/matrix-sygnal +cd /opt/matrix-sygnal +``` + +We will create all subsequent files inside this `/opt/matrix-sygnal` directory. + +#### 3.1. Sygnal VAPID Keys + +WebPush requires a VAPID key pair. The private key stays on your server, and the public key is given to clients. + +1. **Generate the Private Key**: + Use `openssl` to generate an EC private key. + + ```sh + # This command needs to be run in the /opt/matrix-sygnal directory + openssl ecparam -name prime256v1 -genkey -noout -out sygnal_private_key.pem + ``` + +2. **Extract the Public Key**: + Extract the corresponding public key from the private key. You will need this for your client configuration later. + + ```sh + # This command extracts the public key in the correct format + openssl ec -in sygnal_private_key.pem -pubout -outform DER | tail -c 65 | base64 | tr '/+' '_-' | tr -d '=' + ``` + + **Save the output of this command.** This is your `vapidPublicKey`. It should look similar to the one from the `cinny.cc` example. + +#### 3.2. Sygnal Configuration (`sygnal.yaml`) + +Create a file named `sygnal.yaml`. This file tells Sygnal how to run. + +```yaml +# /opt/matrix-sygnal/sygnal.yaml +http: + bind_addresses: ['0.0.0.0'] + port: 5000 + +# This is where we configure our push gateway app +apps: + # This app_id must match the one used in your client's configuration + cc.cinny.web: + type: webpush + # This path is *inside the container*. We will map our generated key to it. + vapid_private_key: /data/private_key.pem + # An email for VAPID contact details + vapid_contact_email: your-email@your-domain.com +``` + +#### 3.3. Caddy Configuration (`Caddyfile`) + +Create a file named `Caddyfile`. This tells Caddy how to proxy requests. + +**Replace `sygnal.your-domain.com`** with the domain you configured in Step 1. + +```caddyfile +# /opt/matrix-sygnal/Caddyfile + +# Reusable snippet for Cloudflare TLS +(tls_cloudflare) { + tls { + dns cloudflare {env.CLOUDFLARE_API_TOKEN} + } +} + +# Your public-facing URL +sygnal.your-domain.com { + # Get an SSL certificate from Let's Encrypt using the Cloudflare DNS challenge + import tls_cloudflare + + # Log requests to standard output + log + + # Reverse proxy requests to the sygnal container on port 5000 + # 'sygnal' is the service name we will define in docker-compose.yml + reverse_proxy sygnal:5000 +} +``` + +#### 3.4. Caddy Dockerfile + +While you can use the standard `caddy:latest` image, you need one with the Cloudflare DNS provider plugin. Create a file named `Dockerfile` for Caddy. + +```dockerfile +# /opt/matrix-sygnal/Dockerfile +FROM caddy:builder AS builder + +RUN xcaddy build \ + --with github.com/caddy-dns/cloudflare + +FROM caddy:latest + +COPY --from=builder /usr/bin/caddy /usr/bin/caddy +``` + +#### 3.5. Environment File (`.env`) + +Create a file named `.env` to securely store your Cloudflare API Token. + +```.env +# /opt/matrix-sygnal/.env +CLOUDFLARE_API_TOKEN=your-cloudflare-api-token-from-step-1 +``` + +--- + +### Step 4: Docker Compose + +Using `docker-compose` simplifies managing our multi-container application. Create a `docker-compose.yml` file. + +```yaml +# /opt/matrix-sygnal/docker-compose.yml +version: '3.7' + +services: + caddy: + # Build the Caddy image from our Dockerfile in the current directory + build: . + container_name: caddy + hostname: caddy + restart: unless-stopped + networks: + - matrix + ports: + # Expose standard web ports to the host + - '80:80' + - '443:443' + volumes: + # Mount the Caddyfile into the container + - ./Caddyfile:/etc/caddy/Caddyfile + # Create a volume for Caddy's data (certs, etc.) + - caddy_data:/data + # Load the Cloudflare token from the .env file + env_file: + - ./.env + + sygnal: + # Use the official Sygnal image + image: matrixdotorg/sygnal:latest + container_name: sygnal + hostname: sygnal + restart: unless-stopped + networks: + - matrix + volumes: + # Mount the Sygnal config file + - ./sygnal.yaml:/sygnal.yaml + # Mount the generated private key to the path specified in sygnal.yaml + - ./sygnal_private_key.pem:/data/private_key.pem + # Create a volume for any other data Sygnal might store + - sygnal_data:/data + command: ['--config-path=/sygnal.yaml'] + +volumes: + caddy_data: + sygnal_data: + +networks: + matrix: + driver: bridge +``` + +--- + +### Step 5: Launch the Services + +Your directory `/opt/matrix-sygnal` should now look like this: + +``` +/opt/matrix-sygnal/ +├── Caddyfile +├── docker-compose.yml +├── Dockerfile +├── .env +├── sygnal.yaml +└── sygnal_private_key.pem +``` + +Now, you can build and run everything with a single command: + +```sh +cd /opt/matrix-sygnal +sudo docker-compose up --build -d +``` + +- `--build` tells Docker Compose to build the Caddy image from your `Dockerfile`. +- `-d` runs the containers in detached mode (in the background). + +To check the status and logs: + +```sh +# See if containers are running +sudo docker-compose ps + +# View the live logs for both services +sudo docker-compose logs -f + +# View logs for a specific service (e.g., caddy) +sudo docker-compose logs -f caddy +``` + +Caddy will automatically start, obtain an SSL certificate for `sygnal.your-domain.com`, and begin proxying requests to the Sygnal container. + +--- + +### Step 6: Client Configuration + +The final step is to configure your Matrix client to use your new push gateway. In Cinny, for example, you would modify its `config.json` or use a homeserver that advertises these settings. + +Update the `pushNotificationDetails` section with the information from your server: + +```json +"pushNotificationDetails": { + "pushNotifyUrl": "https://sygnal.your-domain.com/_matrix/push/v1/notify", + "vapidPublicKey": "YOUR_VAPID_PUBLIC_KEY_FROM_STEP_3.1", + "webPushAppID": "cc.cinny.web" +} +``` + +- **`pushNotifyUrl`**: The public URL of your new Sygnal instance. +- **`vapidPublicKey`**: The public key you generated in step 3.1. +- **`webPushAppID`**: The application ID you defined in your `sygnal.yaml`. This must match exactly. + +After configuring your client, it will register for push notifications with your Sygnal instance, which will then handle delivering them. diff --git a/docs/sygnal.yaml b/docs/sygnal.yaml new file mode 100644 index 0000000000..3f5613fc1d --- /dev/null +++ b/docs/sygnal.yaml @@ -0,0 +1,9 @@ +http: + bind_addresses: ['0.0.0.0'] + port: 5000 + +apps: + cc.cinny.web: + type: webpush + vapid_private_key: /data/private_key.pem + vapid_contact_email: help@cinny.cc diff --git a/package-lock.json b/package-lock.json index 7dd2bf0a29..8d8ead2304 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cinny", - "version": "4.8.1", + "version": "4.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cinny", - "version": "4.8.1", + "version": "4.9.1", "license": "AGPL-3.0-only", "dependencies": { "@atlaskit/pragmatic-drag-and-drop": "1.1.6", @@ -16,7 +16,6 @@ "@tanstack/react-query": "5.24.1", "@tanstack/react-query-devtools": "5.24.1", "@tanstack/react-virtual": "3.2.0", - "@tippyjs/react": "4.2.6", "@vanilla-extract/css": "1.9.3", "@vanilla-extract/recipes": "0.3.0", "@vanilla-extract/vite-plugin": "3.7.1", @@ -32,10 +31,8 @@ "emojibase": "15.3.1", "emojibase-data": "15.3.2", "file-saver": "2.0.5", - "flux": "4.0.3", "focus-trap-react": "10.0.2", "folds": "2.2.0", - "formik": "2.4.6", "html-dom-parser": "4.0.0", "html-react-parser": "4.2.0", "i18next": "23.12.2", @@ -50,17 +47,14 @@ "millify": "6.1.0", "pdfjs-dist": "4.2.67", "prismjs": "1.30.0", - "prop-types": "15.8.1", "react": "18.2.0", "react-aria": "3.29.1", - "react-autosize-textarea": "7.1.0", "react-blurhash": "0.2.0", "react-colorful": "5.6.1", "react-dom": "18.2.0", "react-error-boundary": "4.0.13", "react-google-recaptcha": "2.1.0", "react-i18next": "15.0.0", - "react-modal": "3.16.1", "react-range": "1.8.14", "react-router-dom": "6.20.0", "sanitize-html": "2.12.1", @@ -68,7 +62,6 @@ "slate-dom": "0.112.2", "slate-history": "0.110.3", "slate-react": "0.112.1", - "tippy.js": "6.3.7", "ua-parser-js": "1.0.35" }, "devDependencies": { @@ -97,12 +90,12 @@ "eslint-plugin-react": "7.31.11", "eslint-plugin-react-hooks": "4.6.0", "prettier": "2.8.1", - "sass": "1.56.2", "typescript": "4.9.4", - "vite": "5.4.15", + "vite": "5.4.19", "vite-plugin-pwa": "0.20.5", "vite-plugin-static-copy": "1.0.4", - "vite-plugin-top-level-await": "1.4.4" + "vite-plugin-top-level-await": "1.4.4", + "workbox-precaching": "7.3.0" }, "engines": { "node": ">=16.0.0" @@ -2313,15 +2306,6 @@ "node": ">= 8" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@react-aria/breadcrumbs": { "version": "3.5.20", "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.20.tgz", @@ -4524,18 +4508,6 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, - "node_modules/@tippyjs/react": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz", - "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==", - "dependencies": { - "tippy.js": "^6.3.1" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4601,15 +4573,6 @@ "integrity": "sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ==", "dev": true }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", - "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "node_modules/@types/is-hotkey": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz", @@ -4643,12 +4606,14 @@ "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "dev": true }, "node_modules/@types/react": { "version": "18.2.39", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", + "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4697,7 +4662,8 @@ "node_modules/@types/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==" + "integrity": "sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==", + "dev": true }, "node_modules/@types/semver": { "version": "7.5.8", @@ -5320,11 +5286,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -5346,11 +5307,6 @@ "node": ">= 4.0.0" } }, - "node_modules/autosize": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/autosize/-/autosize-4.0.4.tgz", - "integrity": "sha512-5yxLQ22O0fCRGoxGfeLSNt3J8LB1v+umtpMnPW6XjkTWXKoN0AmXAIhelJcDtFT/Y/wYWmfE+oqU10Q0b8FhaQ==" - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -5830,11 +5786,6 @@ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", "license": "MIT" }, - "node_modules/computed-style": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz", - "integrity": "sha512-WpAmaKbMNmS3OProfHIdJiNleNJdgUrJfbKArXua28QF7+0CoZjlLn0lp6vlc+dl5r2/X9GQiQRQQU4BzSa69w==" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5896,14 +5847,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7019,11 +6962,6 @@ "node": ">=0.8.x" } }, - "node_modules/exenv": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7095,33 +7033,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fbemitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", - "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", - "dependencies": { - "fbjs": "^3.0.0" - } - }, - "node_modules/fbjs": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", - "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", - "dependencies": { - "cross-fetch": "^3.1.5", - "fbjs-css-vars": "^1.0.0", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^1.0.35" - } - }, - "node_modules/fbjs-css-vars": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", - "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" - }, "node_modules/fdir": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", @@ -7230,18 +7141,6 @@ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "dev": true }, - "node_modules/flux": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.3.tgz", - "integrity": "sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw==", - "dependencies": { - "fbemitter": "^3.0.0", - "fbjs": "^3.0.1" - }, - "peerDependencies": { - "react": "^15.0.2 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/focus-trap": { "version": "7.6.4", "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.4.tgz", @@ -7286,38 +7185,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/formik": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", - "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==", - "funding": [ - { - "type": "individual", - "url": "https://opencollective.com/formik" - } - ], - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.1", - "deepmerge": "^2.1.1", - "hoist-non-react-statics": "^3.3.0", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "react-fast-compare": "^2.0.1", - "tiny-warning": "^1.0.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/formik/node_modules/deepmerge": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", - "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fs-extra": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", @@ -7896,12 +7763,6 @@ "url": "https://opencollective.com/immer" } }, - "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "dev": true - }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -8636,17 +8497,6 @@ "node": ">=10" } }, - "node_modules/line-height": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/line-height/-/line-height-0.3.1.tgz", - "integrity": "sha512-YExecgqPwnp5gplD2+Y8e8A5+jKpr25+DzMbFdI1/1UAr0FJrTFv4VkHLf8/6B590i1wUPJWMKKldkd/bdQ//w==", - "dependencies": { - "computed-style": "~0.1.3" - }, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/linkify-react": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.1.3.tgz", @@ -8680,11 +8530,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -9541,14 +9386,6 @@ "node": ">=6" } }, - "node_modules/promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dependencies": { - "asap": "~2.0.3" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -9672,20 +9509,6 @@ "react": ">=16.4.1" } }, - "node_modules/react-autosize-textarea": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/react-autosize-textarea/-/react-autosize-textarea-7.1.0.tgz", - "integrity": "sha512-BHpjCDkuOlllZn3nLazY2F8oYO1tS2jHnWhcjTWQdcKiiMU6gHLNt/fzmqMSyerR0eTdKtfSIqtSeTtghNwS+g==", - "dependencies": { - "autosize": "^4.0.2", - "line-height": "^0.3.1", - "prop-types": "^15.5.6" - }, - "peerDependencies": { - "react": "^0.14.0 || ^15.0.0 || ^16.0.0", - "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0" - } - }, "node_modules/react-blurhash": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.2.0.tgz", @@ -9728,11 +9551,6 @@ "react": ">=16.13.1" } }, - "node_modules/react-fast-compare": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", - "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" - }, "node_modules/react-google-recaptcha": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-2.1.0.tgz", @@ -9771,29 +9589,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, - "node_modules/react-modal": { - "version": "3.16.1", - "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", - "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", - "dependencies": { - "exenv": "^1.2.0", - "prop-types": "^15.7.2", - "react-lifecycles-compat": "^3.0.0", - "warning": "^4.0.3" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", - "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" - } - }, "node_modules/react-property": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz", @@ -10252,23 +10047,6 @@ "postcss": "^8.3.11" } }, - "node_modules/sass": { - "version": "1.56.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.56.2.tgz", - "integrity": "sha512-ciEJhnyCRwzlBCB+h5cCPM6ie/6f8HrhZMQOf5vlU60Y1bI1rx5Zb0vlDZvaycHsg/MqFfF1Eq2eokAa32iw8w==", - "dev": true, - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -10368,11 +10146,6 @@ "node": ">= 0.4" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -10969,14 +10742,6 @@ "node": ">=12.0.0" } }, - "node_modules/tippy.js": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", - "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", - "dependencies": { - "@popperjs/core": "^2.9.0" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -11331,9 +11096,9 @@ } }, "node_modules/vite": { - "version": "5.4.15", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz", - "integrity": "sha512-6ANcZRivqL/4WtwPGTKNaosuNJr5tWiftOC7liM7G9+rMb8+oeJeyzymDu4rTN93seySBmbjSfsS3Vzr19KNtA==", + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -11891,14 +11656,6 @@ "node": ">=0.10.0" } }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -12212,6 +11969,7 @@ "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.3.0.tgz", "integrity": "sha512-ckp/3t0msgXclVAYaNndAGeAoWQUv7Rwc4fdhWL69CCAb2UHo3Cef0KIUctqfQj1p8h6aGyz3w8Cy3Ihq9OmIw==", "dev": true, + "license": "MIT", "dependencies": { "workbox-core": "7.3.0", "workbox-routing": "7.3.0", diff --git a/package.json b/package.json index 3c1cef8cbc..485e472910 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cinny", - "version": "4.8.1", + "version": "4.9.1", "description": "Yet another matrix client", "main": "index.js", "type": "module", @@ -27,7 +27,6 @@ "@tanstack/react-query": "5.24.1", "@tanstack/react-query-devtools": "5.24.1", "@tanstack/react-virtual": "3.2.0", - "@tippyjs/react": "4.2.6", "@vanilla-extract/css": "1.9.3", "@vanilla-extract/recipes": "0.3.0", "@vanilla-extract/vite-plugin": "3.7.1", @@ -43,10 +42,8 @@ "emojibase": "15.3.1", "emojibase-data": "15.3.2", "file-saver": "2.0.5", - "flux": "4.0.3", "focus-trap-react": "10.0.2", "folds": "2.2.0", - "formik": "2.4.6", "html-dom-parser": "4.0.0", "html-react-parser": "4.2.0", "i18next": "23.12.2", @@ -61,17 +58,14 @@ "millify": "6.1.0", "pdfjs-dist": "4.2.67", "prismjs": "1.30.0", - "prop-types": "15.8.1", "react": "18.2.0", "react-aria": "3.29.1", - "react-autosize-textarea": "7.1.0", "react-blurhash": "0.2.0", "react-colorful": "5.6.1", "react-dom": "18.2.0", "react-error-boundary": "4.0.13", "react-google-recaptcha": "2.1.0", "react-i18next": "15.0.0", - "react-modal": "3.16.1", "react-range": "1.8.14", "react-router-dom": "6.20.0", "sanitize-html": "2.12.1", @@ -79,7 +73,6 @@ "slate-dom": "0.112.2", "slate-history": "0.110.3", "slate-react": "0.112.1", - "tippy.js": "6.3.7", "ua-parser-js": "1.0.35" }, "devDependencies": { @@ -108,11 +101,11 @@ "eslint-plugin-react": "7.31.11", "eslint-plugin-react-hooks": "4.6.0", "prettier": "2.8.1", - "sass": "1.56.2", "typescript": "4.9.4", - "vite": "5.4.15", + "vite": "5.4.19", "vite-plugin-pwa": "0.20.5", "vite-plugin-static-copy": "1.0.4", - "vite-plugin-top-level-await": "1.4.4" + "vite-plugin-top-level-await": "1.4.4", + "workbox-precaching": "7.3.0" } } diff --git a/public/res/ic/filled/category.svg b/public/res/ic/filled/category.svg deleted file mode 100644 index 87b2588dd4..0000000000 --- a/public/res/ic/filled/category.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/public/res/ic/filled/pin.svg b/public/res/ic/filled/pin.svg deleted file mode 100644 index 6a70147405..0000000000 --- a/public/res/ic/filled/pin.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/public/res/ic/filled/star.svg b/public/res/ic/filled/star.svg deleted file mode 100644 index 378c891e46..0000000000 --- a/public/res/ic/filled/star.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/public/res/ic/outlined/add-pin.svg b/public/res/ic/outlined/add-pin.svg deleted file mode 100644 index 9634bede59..0000000000 --- a/public/res/ic/outlined/add-pin.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/add-user.svg b/public/res/ic/outlined/add-user.svg deleted file mode 100644 index c3803d80aa..0000000000 --- a/public/res/ic/outlined/add-user.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/public/res/ic/outlined/ball.svg b/public/res/ic/outlined/ball.svg deleted file mode 100644 index d4b89ff53f..0000000000 --- a/public/res/ic/outlined/ball.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/bell-off.svg b/public/res/ic/outlined/bell-off.svg deleted file mode 100644 index 79ce8a33f0..0000000000 --- a/public/res/ic/outlined/bell-off.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/public/res/ic/outlined/bell-ping.svg b/public/res/ic/outlined/bell-ping.svg deleted file mode 100644 index 3431bea1d1..0000000000 --- a/public/res/ic/outlined/bell-ping.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/public/res/ic/outlined/bell-ring.svg b/public/res/ic/outlined/bell-ring.svg deleted file mode 100644 index 57fc267967..0000000000 --- a/public/res/ic/outlined/bell-ring.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/public/res/ic/outlined/bell.svg b/public/res/ic/outlined/bell.svg deleted file mode 100644 index 43d470b532..0000000000 --- a/public/res/ic/outlined/bell.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/bin.svg b/public/res/ic/outlined/bin.svg deleted file mode 100644 index 984be62567..0000000000 --- a/public/res/ic/outlined/bin.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/public/res/ic/outlined/bulb.svg b/public/res/ic/outlined/bulb.svg deleted file mode 100644 index 00e8088615..0000000000 --- a/public/res/ic/outlined/bulb.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/category.svg b/public/res/ic/outlined/category.svg deleted file mode 100644 index c7c33b3824..0000000000 --- a/public/res/ic/outlined/category.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - diff --git a/public/res/ic/outlined/check.svg b/public/res/ic/outlined/check.svg deleted file mode 100644 index 72a1832728..0000000000 --- a/public/res/ic/outlined/check.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/chevron-bottom.svg b/public/res/ic/outlined/chevron-bottom.svg deleted file mode 100644 index 5562b7aaf0..0000000000 --- a/public/res/ic/outlined/chevron-bottom.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/public/res/ic/outlined/chevron-left.svg b/public/res/ic/outlined/chevron-left.svg deleted file mode 100644 index ba9e12cca7..0000000000 --- a/public/res/ic/outlined/chevron-left.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/public/res/ic/outlined/chevron-right.svg b/public/res/ic/outlined/chevron-right.svg deleted file mode 100644 index 7f6a806e66..0000000000 --- a/public/res/ic/outlined/chevron-right.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/public/res/ic/outlined/chevron-top.svg b/public/res/ic/outlined/chevron-top.svg deleted file mode 100644 index f5948fe902..0000000000 --- a/public/res/ic/outlined/chevron-top.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/public/res/ic/outlined/circle-plus.svg b/public/res/ic/outlined/circle-plus.svg deleted file mode 100644 index 41690a08ab..0000000000 --- a/public/res/ic/outlined/circle-plus.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/cmd.svg b/public/res/ic/outlined/cmd.svg deleted file mode 100644 index 75ae0d9824..0000000000 --- a/public/res/ic/outlined/cmd.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/public/res/ic/outlined/coin.svg b/public/res/ic/outlined/coin.svg deleted file mode 100644 index 025424e853..0000000000 --- a/public/res/ic/outlined/coin.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/cross.svg b/public/res/ic/outlined/cross.svg deleted file mode 100644 index 0acda8842a..0000000000 --- a/public/res/ic/outlined/cross.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/cup.svg b/public/res/ic/outlined/cup.svg deleted file mode 100644 index 8921e2c9ae..0000000000 --- a/public/res/ic/outlined/cup.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/dog.svg b/public/res/ic/outlined/dog.svg deleted file mode 100644 index 3b252956f1..0000000000 --- a/public/res/ic/outlined/dog.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/public/res/ic/outlined/download.svg b/public/res/ic/outlined/download.svg deleted file mode 100644 index 677014f366..0000000000 --- a/public/res/ic/outlined/download.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/public/res/ic/outlined/emoji-add.svg b/public/res/ic/outlined/emoji-add.svg deleted file mode 100644 index c4cacef25b..0000000000 --- a/public/res/ic/outlined/emoji-add.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - diff --git a/public/res/ic/outlined/emoji.svg b/public/res/ic/outlined/emoji.svg deleted file mode 100644 index 0daac8796e..0000000000 --- a/public/res/ic/outlined/emoji.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/public/res/ic/outlined/explore.svg b/public/res/ic/outlined/explore.svg deleted file mode 100644 index 7cc2a4793f..0000000000 --- a/public/res/ic/outlined/explore.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/external.svg b/public/res/ic/outlined/external.svg deleted file mode 100644 index adade1bd9a..0000000000 --- a/public/res/ic/outlined/external.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/res/ic/outlined/eye-blind.svg b/public/res/ic/outlined/eye-blind.svg deleted file mode 100644 index fbc8e2ae8a..0000000000 --- a/public/res/ic/outlined/eye-blind.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/res/ic/outlined/eye.svg b/public/res/ic/outlined/eye.svg deleted file mode 100644 index 1ce868bfdb..0000000000 --- a/public/res/ic/outlined/eye.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/res/ic/outlined/file.svg b/public/res/ic/outlined/file.svg deleted file mode 100644 index d6a2a27a57..0000000000 --- a/public/res/ic/outlined/file.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/flag.svg b/public/res/ic/outlined/flag.svg deleted file mode 100644 index 8fce98d689..0000000000 --- a/public/res/ic/outlined/flag.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/hash-globe.svg b/public/res/ic/outlined/hash-globe.svg deleted file mode 100644 index ce3df08317..0000000000 --- a/public/res/ic/outlined/hash-globe.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/hash-lock.svg b/public/res/ic/outlined/hash-lock.svg deleted file mode 100644 index ae263ced5b..0000000000 --- a/public/res/ic/outlined/hash-lock.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/hash-plus.svg b/public/res/ic/outlined/hash-plus.svg deleted file mode 100644 index 69737fd552..0000000000 --- a/public/res/ic/outlined/hash-plus.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/public/res/ic/outlined/hash-search.svg b/public/res/ic/outlined/hash-search.svg deleted file mode 100644 index f135e8986b..0000000000 --- a/public/res/ic/outlined/hash-search.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/hash-shield.svg b/public/res/ic/outlined/hash-shield.svg deleted file mode 100644 index dfd344b142..0000000000 --- a/public/res/ic/outlined/hash-shield.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/hash.svg b/public/res/ic/outlined/hash.svg deleted file mode 100644 index dcb8b9647c..0000000000 --- a/public/res/ic/outlined/hash.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/heart.svg b/public/res/ic/outlined/heart.svg deleted file mode 100644 index c5b940b605..0000000000 --- a/public/res/ic/outlined/heart.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/home.svg b/public/res/ic/outlined/home.svg deleted file mode 100644 index 3c7a02df40..0000000000 --- a/public/res/ic/outlined/home.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/horizontal-menu.svg b/public/res/ic/outlined/horizontal-menu.svg deleted file mode 100644 index a19b3c3553..0000000000 --- a/public/res/ic/outlined/horizontal-menu.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/public/res/ic/outlined/inbox.svg b/public/res/ic/outlined/inbox.svg deleted file mode 100644 index 6543587621..0000000000 --- a/public/res/ic/outlined/inbox.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/info.svg b/public/res/ic/outlined/info.svg deleted file mode 100644 index 30a578873b..0000000000 --- a/public/res/ic/outlined/info.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - diff --git a/public/res/ic/outlined/invite-arrow.svg b/public/res/ic/outlined/invite-arrow.svg deleted file mode 100644 index 370bf8e8cc..0000000000 --- a/public/res/ic/outlined/invite-arrow.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/invite-cancel-arrow.svg b/public/res/ic/outlined/invite-cancel-arrow.svg deleted file mode 100644 index 795a773a60..0000000000 --- a/public/res/ic/outlined/invite-cancel-arrow.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/invite.svg b/public/res/ic/outlined/invite.svg deleted file mode 100644 index 3896e15e81..0000000000 --- a/public/res/ic/outlined/invite.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/join-arrow.svg b/public/res/ic/outlined/join-arrow.svg deleted file mode 100644 index 90cfa6517e..0000000000 --- a/public/res/ic/outlined/join-arrow.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/leave-arrow.svg b/public/res/ic/outlined/leave-arrow.svg deleted file mode 100644 index a51ac1d125..0000000000 --- a/public/res/ic/outlined/leave-arrow.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/lock.svg b/public/res/ic/outlined/lock.svg deleted file mode 100644 index 77021f0f4f..0000000000 --- a/public/res/ic/outlined/lock.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/markdown.svg b/public/res/ic/outlined/markdown.svg deleted file mode 100644 index 775afbfb0c..0000000000 --- a/public/res/ic/outlined/markdown.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/message-unread.svg b/public/res/ic/outlined/message-unread.svg deleted file mode 100644 index fc5e9ff00a..0000000000 --- a/public/res/ic/outlined/message-unread.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/public/res/ic/outlined/message.svg b/public/res/ic/outlined/message.svg deleted file mode 100644 index d36e9a307a..0000000000 --- a/public/res/ic/outlined/message.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/public/res/ic/outlined/pause.svg b/public/res/ic/outlined/pause.svg deleted file mode 100644 index c312613b4e..0000000000 --- a/public/res/ic/outlined/pause.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - diff --git a/public/res/ic/outlined/peace.svg b/public/res/ic/outlined/peace.svg deleted file mode 100644 index 8a7c81a3cc..0000000000 --- a/public/res/ic/outlined/peace.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/pencil.svg b/public/res/ic/outlined/pencil.svg deleted file mode 100644 index 1b8ac24a7b..0000000000 --- a/public/res/ic/outlined/pencil.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/photo.svg b/public/res/ic/outlined/photo.svg deleted file mode 100644 index af01a3305f..0000000000 --- a/public/res/ic/outlined/photo.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/pin.svg b/public/res/ic/outlined/pin.svg deleted file mode 100644 index 211242cd8d..0000000000 --- a/public/res/ic/outlined/pin.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/play.svg b/public/res/ic/outlined/play.svg deleted file mode 100644 index 87b3a8f617..0000000000 --- a/public/res/ic/outlined/play.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/plus.svg b/public/res/ic/outlined/plus.svg deleted file mode 100644 index ce37594e87..0000000000 --- a/public/res/ic/outlined/plus.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/power.svg b/public/res/ic/outlined/power.svg deleted file mode 100644 index 8aeb6db831..0000000000 --- a/public/res/ic/outlined/power.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/recent-clock.svg b/public/res/ic/outlined/recent-clock.svg deleted file mode 100644 index 30b10d594a..0000000000 --- a/public/res/ic/outlined/recent-clock.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - diff --git a/public/res/ic/outlined/reply-arrow.svg b/public/res/ic/outlined/reply-arrow.svg deleted file mode 100644 index 3cda01cde9..0000000000 --- a/public/res/ic/outlined/reply-arrow.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/search.svg b/public/res/ic/outlined/search.svg deleted file mode 100644 index 75dd63207f..0000000000 --- a/public/res/ic/outlined/search.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/send.svg b/public/res/ic/outlined/send.svg deleted file mode 100644 index aa48713250..0000000000 --- a/public/res/ic/outlined/send.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/settings.svg b/public/res/ic/outlined/settings.svg deleted file mode 100644 index ee640b3936..0000000000 --- a/public/res/ic/outlined/settings.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - diff --git a/public/res/ic/outlined/shield-empty.svg b/public/res/ic/outlined/shield-empty.svg deleted file mode 100644 index 6bc9d3044d..0000000000 --- a/public/res/ic/outlined/shield-empty.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/shield-user.svg b/public/res/ic/outlined/shield-user.svg deleted file mode 100644 index bd5f07c551..0000000000 --- a/public/res/ic/outlined/shield-user.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/public/res/ic/outlined/shield.svg b/public/res/ic/outlined/shield.svg deleted file mode 100644 index 9bb46fa10b..0000000000 --- a/public/res/ic/outlined/shield.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/space-globe.svg b/public/res/ic/outlined/space-globe.svg deleted file mode 100644 index 63d71f1d05..0000000000 --- a/public/res/ic/outlined/space-globe.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/space-lock.svg b/public/res/ic/outlined/space-lock.svg deleted file mode 100644 index b15705caa6..0000000000 --- a/public/res/ic/outlined/space-lock.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/space-plus.svg b/public/res/ic/outlined/space-plus.svg deleted file mode 100644 index 4d69a1ef1b..0000000000 --- a/public/res/ic/outlined/space-plus.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/space.svg b/public/res/ic/outlined/space.svg deleted file mode 100644 index a4b54b3e70..0000000000 --- a/public/res/ic/outlined/space.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/star.svg b/public/res/ic/outlined/star.svg deleted file mode 100644 index 290f159a6f..0000000000 --- a/public/res/ic/outlined/star.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/sticker.svg b/public/res/ic/outlined/sticker.svg deleted file mode 100644 index bc486e5e9e..0000000000 --- a/public/res/ic/outlined/sticker.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/public/res/ic/outlined/sun.svg b/public/res/ic/outlined/sun.svg deleted file mode 100644 index d8ed06fd90..0000000000 --- a/public/res/ic/outlined/sun.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/res/ic/outlined/tick-mark.svg b/public/res/ic/outlined/tick-mark.svg deleted file mode 100644 index 8e76ed555f..0000000000 --- a/public/res/ic/outlined/tick-mark.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/public/res/ic/outlined/user.svg b/public/res/ic/outlined/user.svg deleted file mode 100644 index 6756a1b2f6..0000000000 --- a/public/res/ic/outlined/user.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/public/res/ic/outlined/vertical-menu.svg b/public/res/ic/outlined/vertical-menu.svg deleted file mode 100644 index ec5c544c80..0000000000 --- a/public/res/ic/outlined/vertical-menu.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/public/res/ic/outlined/vlc.svg b/public/res/ic/outlined/vlc.svg deleted file mode 100644 index 8a2b844f1e..0000000000 --- a/public/res/ic/outlined/vlc.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/public/res/ic/outlined/volume-full.svg b/public/res/ic/outlined/volume-full.svg deleted file mode 100644 index 20419e7281..0000000000 --- a/public/res/ic/outlined/volume-full.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - diff --git a/public/res/ic/outlined/volume-mute.svg b/public/res/ic/outlined/volume-mute.svg deleted file mode 100644 index beb067719d..0000000000 --- a/public/res/ic/outlined/volume-mute.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/src/app/atoms/avatar/Avatar.jsx b/src/app/atoms/avatar/Avatar.jsx deleted file mode 100644 index 27bf7c906b..0000000000 --- a/src/app/atoms/avatar/Avatar.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Avatar.scss'; - -import Text from '../text/Text'; -import RawIcon from '../system-icons/RawIcon'; - -import ImageBrokenSVG from '../../../../public/res/svg/image-broken.svg'; -import { avatarInitials } from '../../../util/common'; - -const Avatar = React.forwardRef(({ text, bgColor, iconSrc, iconColor, imageSrc, size }, ref) => { - let textSize = 's1'; - if (size === 'large') textSize = 'h1'; - if (size === 'small') textSize = 'b1'; - if (size === 'extra-small') textSize = 'b3'; - - return ( -
- {imageSrc !== null ? ( - { - e.target.style.backgroundColor = 'transparent'; - }} - onError={(e) => { - e.target.src = ImageBrokenSVG; - }} - alt="" - /> - ) : ( - - {iconSrc !== null ? ( - - ) : ( - text !== null && ( - - {avatarInitials(text)} - - ) - )} - - )} -
- ); -}); - -Avatar.defaultProps = { - text: null, - bgColor: 'transparent', - iconSrc: null, - iconColor: null, - imageSrc: null, - size: 'normal', -}; - -Avatar.propTypes = { - text: PropTypes.string, - bgColor: PropTypes.string, - iconSrc: PropTypes.string, - iconColor: PropTypes.string, - imageSrc: PropTypes.string, - size: PropTypes.oneOf(['large', 'normal', 'small', 'extra-small']), -}; - -export default Avatar; diff --git a/src/app/atoms/avatar/Avatar.scss b/src/app/atoms/avatar/Avatar.scss deleted file mode 100644 index ea69c9e8ac..0000000000 --- a/src/app/atoms/avatar/Avatar.scss +++ /dev/null @@ -1,56 +0,0 @@ -@use '../../partials/flex'; - -.avatar-container { - display: inline-flex; - width: 42px; - height: 42px; - border-radius: var(--bo-radius); - position: relative; - - &__large { - width: var(--av-large); - height: var(--av-large); - } - &__normal { - width: var(--av-normal); - height: var(--av-normal); - } - - &__small { - width: var(--av-small); - height: var(--av-small); - } - - &__extra-small { - width: var(--av-extra-small); - height: var(--av-extra-small); - } - - > img { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: inherit; - background-color: var(--bg-surface-hover); - } - - .avatar__border { - @extend .cp-fx__row--c-c; - - position: absolute; - top: 0; - left: 0; - - width: 100%; - height: 100%; - border-radius: inherit; - - .text { - color: white; - } - &--active { - @extend .avatar__border; - box-shadow: var(--bs-surface-border); - } - } -} \ No newline at end of file diff --git a/src/app/atoms/avatar/render.js b/src/app/atoms/avatar/render.js deleted file mode 100644 index e8cf1a6613..0000000000 --- a/src/app/atoms/avatar/render.js +++ /dev/null @@ -1,57 +0,0 @@ -import { avatarInitials, cssVar } from '../../../util/common'; - -// renders the avatar and returns it as an URL -export default async function renderAvatar({ - text, bgColor, imageSrc, size, borderRadius, scale, -}) { - try { - const canvas = document.createElement('canvas'); - canvas.width = size * scale; - canvas.height = size * scale; - - const ctx = canvas.getContext('2d'); - - ctx.scale(scale, scale); - - // rounded corners - ctx.beginPath(); - ctx.moveTo(size, size); - ctx.arcTo(0, size, 0, 0, borderRadius); - ctx.arcTo(0, 0, size, 0, borderRadius); - ctx.arcTo(size, 0, size, size, borderRadius); - ctx.arcTo(size, size, 0, size, borderRadius); - - if (imageSrc) { - // clip corners of image - ctx.closePath(); - ctx.clip(); - - const img = new Image(); - img.crossOrigin = 'anonymous'; - const promise = new Promise((resolve, reject) => { - img.onerror = reject; - img.onload = resolve; - }); - img.src = imageSrc; - await promise; - - ctx.drawImage(img, 0, 0, size, size); - } else { - // colored background - ctx.fillStyle = cssVar(bgColor); - ctx.fill(); - - // centered letter - ctx.fillStyle = '#fff'; - ctx.font = `${cssVar('--fs-s1')} ${cssVar('--font-primary')}`; - ctx.textBaseline = 'middle'; - ctx.textAlign = 'center'; - ctx.fillText(avatarInitials(text), size / 2, size / 2); - } - - return canvas.toDataURL(); - } catch (e) { - console.error(e); - return imageSrc; - } -} diff --git a/src/app/atoms/badge/NotificationBadge.jsx b/src/app/atoms/badge/NotificationBadge.jsx deleted file mode 100644 index 12c1bd4473..0000000000 --- a/src/app/atoms/badge/NotificationBadge.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './NotificationBadge.scss'; - -import Text from '../text/Text'; - -function NotificationBadge({ alert, content }) { - const notificationClass = alert ? ' notification-badge--alert' : ''; - return ( -
- {content !== null && {content}} -
- ); -} - -NotificationBadge.defaultProps = { - alert: false, - content: null, -}; - -NotificationBadge.propTypes = { - alert: PropTypes.bool, - content: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), -}; - -export default NotificationBadge; diff --git a/src/app/atoms/badge/NotificationBadge.scss b/src/app/atoms/badge/NotificationBadge.scss deleted file mode 100644 index f5cfa73f39..0000000000 --- a/src/app/atoms/badge/NotificationBadge.scss +++ /dev/null @@ -1,21 +0,0 @@ -.notification-badge { - min-width: 16px; - min-height: 8px; - padding: 0 var(--sp-ultra-tight); - background-color: var(--bg-badge); - border-radius: var(--bo-radius); - - .text { - color: var(--tc-badge); - text-align: center; - } - - &--alert { - background-color: var(--bg-positive); - } - - &:empty { - min-width: 8px; - margin: 0 var(--sp-ultra-tight); - } -} \ No newline at end of file diff --git a/src/app/atoms/button/Button.jsx b/src/app/atoms/button/Button.jsx deleted file mode 100644 index 1c1c950c3d..0000000000 --- a/src/app/atoms/button/Button.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Button.scss'; - -import Text from '../text/Text'; -import RawIcon from '../system-icons/RawIcon'; -import { blurOnBubbling } from './script'; - -const Button = React.forwardRef(({ - id, className, variant, iconSrc, - type, onClick, children, disabled, -}, ref) => { - const iconClass = (iconSrc === null) ? '' : `btn-${variant}--icon`; - return ( - - ); -}); - -Button.defaultProps = { - id: '', - className: null, - variant: 'surface', - iconSrc: null, - type: 'button', - onClick: null, - disabled: false, -}; - -Button.propTypes = { - id: PropTypes.string, - className: PropTypes.string, - variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']), - iconSrc: PropTypes.string, - type: PropTypes.oneOf(['button', 'submit', 'reset']), - onClick: PropTypes.func, - children: PropTypes.node.isRequired, - disabled: PropTypes.bool, -}; - -export default Button; diff --git a/src/app/atoms/button/Button.scss b/src/app/atoms/button/Button.scss deleted file mode 100644 index e1a01bb05e..0000000000 --- a/src/app/atoms/button/Button.scss +++ /dev/null @@ -1,81 +0,0 @@ -@use 'state'; -@use '../../partials/dir'; -@use '../../partials/text'; - -.btn-surface, -.btn-primary, -.btn-positive, -.btn-caution, -.btn-danger { - display: inline-flex; - align-items: center; - justify-content: center; - - min-width: 80px; - padding: var(--sp-extra-tight) var(--sp-normal); - background-color: transparent; - border: none; - border-radius: var(--bo-radius); - cursor: pointer; - @include state.disabled; - - & .text { - @extend .cp-txt__ellipsis; - } - - &--icon { - @include dir.side(padding, var(--sp-tight), var(--sp-loose)); - - } - .ic-raw { - @include dir.side(margin, 0, var(--sp-extra-tight)); - flex-shrink: 0; - } -} - -@mixin color($textColor, $iconColor) { - .text { - color: $textColor; - } - .ic-raw { - background-color: $iconColor; - } -} - - -.btn-surface { - box-shadow: var(--bs-surface-border); - @include color(var(--tc-surface-high), var(--ic-surface-normal)); - @include state.hover(var(--bg-surface-hover)); - @include state.focus(var(--bs-surface-outline)); - @include state.active(var(--bg-surface-active)); -} - -.btn-primary { - background-color: var(--bg-primary); - @include color(var(--tc-primary-high), var(--ic-primary-normal)); - @include state.hover(var(--bg-primary-hover)); - @include state.focus(var(--bs-primary-outline)); - @include state.active(var(--bg-primary-active)); -} -.btn-positive { - box-shadow: var(--bs-positive-border); - @include color(var(--tc-positive-high), var(--ic-positive-normal)); - @include state.hover(var(--bg-positive-hover)); - @include state.focus(var(--bs-positive-outline)); - @include state.active(var(--bg-positive-active)); -} -.btn-caution { - box-shadow: var(--bs-caution-border); - @include color(var(--tc-caution-high), var(--ic-caution-normal)); - @include state.hover(var(--bg-caution-hover)); - @include state.focus(var(--bs-caution-outline)); - @include state.active(var(--bg-caution-active)); -} -.btn-danger { - box-shadow: var(--bs-danger-border); - @include color(var(--tc-danger-high), var(--ic-danger-normal)); - @include state.hover(var(--bg-danger-hover)); - @include state.focus(var(--bs-danger-outline)); - @include state.active(var(--bg-danger-active)); -} \ No newline at end of file diff --git a/src/app/atoms/button/Checkbox.jsx b/src/app/atoms/button/Checkbox.jsx deleted file mode 100644 index 7fcea3b55f..0000000000 --- a/src/app/atoms/button/Checkbox.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Checkbox.scss'; - -function Checkbox({ - variant, isActive, onToggle, - disabled, tabIndex, -}) { - const className = `checkbox checkbox-${variant}${isActive ? ' checkbox--active' : ''}`; - if (onToggle === null) return ; - return ( - // eslint-disable-next-line jsx-a11y/control-has-associated-label - - ); - if (tooltip === null) return btn; - return ( - {tooltip}} - > - {btn} - - ); -}); - -IconButton.defaultProps = { - variant: 'surface', - size: 'normal', - type: 'button', - tooltip: null, - tooltipPlacement: 'top', - onClick: null, - tabIndex: 0, - disabled: false, - isImage: false, - className: '', -}; - -IconButton.propTypes = { - variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']), - size: PropTypes.oneOf(['normal', 'small', 'extra-small']), - type: PropTypes.oneOf(['button', 'submit', 'reset']), - tooltip: PropTypes.string, - tooltipPlacement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), - src: PropTypes.string.isRequired, - onClick: PropTypes.func, - tabIndex: PropTypes.number, - disabled: PropTypes.bool, - isImage: PropTypes.bool, - className: PropTypes.string, -}; - -export default IconButton; diff --git a/src/app/atoms/button/IconButton.scss b/src/app/atoms/button/IconButton.scss deleted file mode 100644 index aa6480c03b..0000000000 --- a/src/app/atoms/button/IconButton.scss +++ /dev/null @@ -1,56 +0,0 @@ -@use 'state'; - -.ic-btn { - padding: var(--sp-extra-tight); - border: none; - border-radius: var(--bo-radius); - background-color: transparent; - font-size: 0; - line-height: 0; - cursor: pointer; - @include state.disabled; -} - -@mixin color($color) { - .ic-raw { - background-color: $color; - } -} -@mixin focus($color) { - &:focus { - outline: none; - background-color: $color; - } -} - -.ic-btn-surface { - @include color(var(--ic-surface-normal)); - @include state.hover(var(--bg-surface-hover)); - @include focus(var(--bg-surface-hover)); - @include state.active(var(--bg-surface-active)); -} -.ic-btn-primary { - @include color(var(--ic-primary-normal)); - @include state.hover(var(--bg-primary-hover)); - @include focus(var(--bg-primary-hover)); - @include state.active(var(--bg-primary-active)); - background-color: var(--bg-primary); -} -.ic-btn-positive { - @include color(var(--ic-positive-normal)); - @include state.hover(var(--bg-positive-hover)); - @include focus(var(--bg-positive-hover)); - @include state.active(var(--bg-positive-active)); -} -.ic-btn-caution { - @include color(var(--ic-caution-normal)); - @include state.hover(var(--bg-caution-hover)); - @include focus(var(--bg-caution-hover)); - @include state.active(var(--bg-caution-active)); -} -.ic-btn-danger { - @include color(var(--ic-danger-normal)); - @include state.hover(var(--bg-danger-hover)); - @include focus(var(--bg-danger-hover)); - @include state.active(var(--bg-danger-active)); -} \ No newline at end of file diff --git a/src/app/atoms/button/RadioButton.jsx b/src/app/atoms/button/RadioButton.jsx deleted file mode 100644 index 35b68baf71..0000000000 --- a/src/app/atoms/button/RadioButton.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './RadioButton.scss'; - -function RadioButton({ isActive, onToggle, disabled }) { - if (onToggle === null) return ; - return ( - // eslint-disable-next-line jsx-a11y/control-has-associated-label - - ); -} - -Chip.propTypes = { - iconSrc: PropTypes.string, - iconColor: PropTypes.string, - text: PropTypes.string, - children: PropTypes.element, - onClick: PropTypes.func, -}; - -Chip.defaultProps = { - iconSrc: null, - iconColor: null, - text: null, - children: null, - onClick: null, -}; - -export default Chip; diff --git a/src/app/atoms/chip/Chip.scss b/src/app/atoms/chip/Chip.scss deleted file mode 100644 index 7396b0dc81..0000000000 --- a/src/app/atoms/chip/Chip.scss +++ /dev/null @@ -1,31 +0,0 @@ -@use '../../partials/dir'; - -.chip { - padding: var(--sp-ultra-tight) var(--sp-extra-tight); - - display: inline-flex; - flex-direction: row; - align-items: center; - - background: var(--bg-surface-low); - border-radius: var(--bo-radius); - box-shadow: var(--bs-surface-border); - cursor: pointer; - - @media (hover: hover) { - &:hover { - background-color: var(--bg-surface-hover); - } - } - - & > .text { - flex: 1; - color: var(--tc-surface-high); - } - - & > .ic-raw { - width: 16px; - height: 16px; - @include dir.side(margin, 0, var(--sp-ultra-tight)); - } -} \ No newline at end of file diff --git a/src/app/atoms/context-menu/ContextMenu.jsx b/src/app/atoms/context-menu/ContextMenu.jsx deleted file mode 100644 index 7d1acd4406..0000000000 --- a/src/app/atoms/context-menu/ContextMenu.jsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './ContextMenu.scss'; - -import Tippy from '@tippyjs/react'; -import 'tippy.js/animations/scale-extreme.css'; - -import Text from '../text/Text'; -import Button from '../button/Button'; -import ScrollView from '../scroll/ScrollView'; - -function ContextMenu({ - content, placement, maxWidth, render, afterToggle, -}) { - const [isVisible, setVisibility] = useState(false); - const showMenu = () => setVisibility(true); - const hideMenu = () => setVisibility(false); - - useEffect(() => { - if (afterToggle !== null) afterToggle(isVisible); - }, [isVisible]); - - return ( - {typeof content === 'function' ? content(hideMenu) : content}} - placement={placement} - interactive - arrow={false} - maxWidth={maxWidth} - duration={200} - > - {render(isVisible ? hideMenu : showMenu)} - - ); -} - -ContextMenu.defaultProps = { - maxWidth: 'unset', - placement: 'right', - afterToggle: null, -}; - -ContextMenu.propTypes = { - content: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.func, - ]).isRequired, - placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), - maxWidth: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), - render: PropTypes.func.isRequired, - afterToggle: PropTypes.func, -}; - -function MenuHeader({ children }) { - return ( -
- { children } -
- ); -} - -MenuHeader.propTypes = { - children: PropTypes.node.isRequired, -}; - -function MenuItem({ - variant, iconSrc, type, - onClick, children, disabled, -}) { - return ( -
- -
- ); -} - -MenuItem.defaultProps = { - variant: 'surface', - iconSrc: null, - type: 'button', - disabled: false, - onClick: null, -}; - -MenuItem.propTypes = { - variant: PropTypes.oneOf(['surface', 'positive', 'caution', 'danger']), - iconSrc: PropTypes.string, - type: PropTypes.oneOf(['button', 'submit']), - onClick: PropTypes.func, - children: PropTypes.node.isRequired, - disabled: PropTypes.bool, -}; - -function MenuBorder() { - return
; -} - -export { - ContextMenu as default, MenuHeader, MenuItem, MenuBorder, -}; diff --git a/src/app/atoms/context-menu/ContextMenu.scss b/src/app/atoms/context-menu/ContextMenu.scss deleted file mode 100644 index 2df9f0a44d..0000000000 --- a/src/app/atoms/context-menu/ContextMenu.scss +++ /dev/null @@ -1,81 +0,0 @@ -@use '../../partials/flex'; -@use '../../partials/text'; -@use '../../partials/dir'; - -.context-menu { - background-color: var(--bg-surface); - box-shadow: var(--bs-popup); - border-radius: var(--bo-radius); - overflow: hidden; - - &:focus { - outline: none; - } - & .tippy-content > div > .scrollbar { - max-height: 90vh; - } -} - -.context-menu__click-wrapper { - display: inline-flex; - - &:focus { - outline: none; - } -} - -.context-menu__header { - height: 34px; - padding: 0 var(--sp-normal); - margin-bottom: var(--sp-ultra-tight); - display: flex; - align-items: center; - border-bottom: 1px solid var(--bg-surface-border); - - .text { - @extend .cp-txt__ellipsis; - color: var(--tc-surface-low); - } - - &:not(:first-child) { - margin-top: var(--sp-extra-tight); - border-top: 1px solid var(--bg-surface-border); - } -} - -.context-menu__item { - display: flex; - button[class^="btn"] { - @extend .cp-fx__item-one; - justify-content: flex-start; - border-radius: 0; - box-shadow: none; - white-space: nowrap; - padding: var(--sp-extra-tight) var(--sp-normal); - - & > .ic-raw { - @include dir.side(margin, 0, var(--sp-tight)); - } - - // if item doesn't have icon - .text:first-child { - @include dir.side( - margin, - calc(var(--ic-small) + var(--sp-tight)), - 0 - ); - } - } - .btn-surface:focus { - background-color: var(--bg-surface-hover); - } - .btn-positive:focus { - background-color: var(--bg-positive-hover); - } - .btn-caution:focus { - background-color: var(--bg-caution-hover); - } - .btn-danger:focus { - background-color: var(--bg-danger-hover); - } -} \ No newline at end of file diff --git a/src/app/atoms/context-menu/ReusableContextMenu.jsx b/src/app/atoms/context-menu/ReusableContextMenu.jsx deleted file mode 100644 index 59bdb1425a..0000000000 --- a/src/app/atoms/context-menu/ReusableContextMenu.jsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; - -import cons from '../../../client/state/cons'; -import navigation from '../../../client/state/navigation'; - -import ContextMenu from './ContextMenu'; - -let key = null; -function ReusableContextMenu() { - const [data, setData] = useState(null); - const openerRef = useRef(null); - - const closeMenu = () => { - key = null; - if (data) openerRef.current.click(); - }; - - useEffect(() => { - if (data) { - const { cords } = data; - openerRef.current.style.transform = `translate(${cords.x}px, ${cords.y}px)`; - openerRef.current.style.width = `${cords.width}px`; - openerRef.current.style.height = `${cords.height}px`; - openerRef.current.click(); - } - const handleContextMenuOpen = (placement, cords, render, afterClose) => { - if (key) { - closeMenu(); - return; - } - setData({ - placement, cords, render, afterClose, - }); - }; - navigation.on(cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, handleContextMenuOpen); - return () => { - navigation.removeListener( - cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, - handleContextMenuOpen, - ); - }; - }, [data]); - - const handleAfterToggle = (isVisible) => { - if (isVisible) { - key = Math.random(); - return; - } - data?.afterClose?.(); - if (setData) setData(null); - - if (key === null) return; - const copyKey = key; - setTimeout(() => { - if (key === copyKey) key = null; - }, 200); - }; - - return ( - ( - - )} - /> - ); -} - -export default ReusableContextMenu; diff --git a/src/app/atoms/divider/Divider.jsx b/src/app/atoms/divider/Divider.jsx deleted file mode 100644 index 76721241f8..0000000000 --- a/src/app/atoms/divider/Divider.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Divider.scss'; - -import Text from '../text/Text'; - -function Divider({ text, variant, align }) { - const dividerClass = ` divider--${variant} divider--${align}`; - return ( -
- {text !== null && {text}} -
- ); -} - -Divider.defaultProps = { - text: null, - variant: 'surface', - align: 'center', -}; - -Divider.propTypes = { - text: PropTypes.string, - variant: PropTypes.oneOf(['surface', 'primary', 'positive', 'caution', 'danger']), - align: PropTypes.oneOf(['left', 'center', 'right']), -}; - -export default Divider; diff --git a/src/app/atoms/divider/Divider.scss b/src/app/atoms/divider/Divider.scss deleted file mode 100644 index 0f013ff0eb..0000000000 --- a/src/app/atoms/divider/Divider.scss +++ /dev/null @@ -1,68 +0,0 @@ -.divider-line { - content: ''; - display: inline-block; - flex: 1; - border-bottom: 1px solid var(--local-divider-color); - opacity: var(--local-divider-opacity); -} - -.divider { - display: flex; - align-items: center; - - &--center::before, - &--right::before { - @extend .divider-line; - } - &--center::after, - &--left::after { - @extend .divider-line; - } - - &__text { - padding: 2px var(--sp-extra-tight); - border-radius: calc(var(--bo-radius) / 2); - } -} - -.divider--surface { - --local-divider-color: var(--bg-divider); - --local-divider-opacity: 1; - - .divider__text { - color: var(--tc-surface-low); - border: 1px solid var(--bg-divider); - } -} -.divider--primary { - --local-divider-color: var(--bg-primary); - --local-divider-opacity: .8; - .divider__text { - color: var(--tc-primary-high); - background-color: var(--bg-primary); - } -} -.divider--positive { - --local-divider-color: var(--bg-positive); - --local-divider-opacity: .8; - .divider__text { - color: var(--bg-surface); - background-color: var(--bg-positive); - } -} -.divider--danger { - --local-divider-color: var(--bg-danger); - --local-divider-opacity: .8; - .divider__text { - color: var(--bg-surface); - background-color: var(--bg-danger); - } -} -.divider--caution { - --local-divider-color: var(--bg-caution); - --local-divider-opacity: .8; - .divider__text { - color: var(--bg-surface); - background-color: var(--bg-caution); - } -} diff --git a/src/app/atoms/header/Header.jsx b/src/app/atoms/header/Header.jsx deleted file mode 100644 index 3c81e4238d..0000000000 --- a/src/app/atoms/header/Header.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Header.scss'; - -function Header({ children }) { - return ( -
- {children} -
- ); -} - -Header.propTypes = { - children: PropTypes.node.isRequired, -}; - -function TitleWrapper({ children }) { - return ( -
- {children} -
- ); -} - -TitleWrapper.propTypes = { - children: PropTypes.node.isRequired, -}; - -export { Header as default, TitleWrapper }; diff --git a/src/app/atoms/header/Header.scss b/src/app/atoms/header/Header.scss deleted file mode 100644 index 9e45f82466..0000000000 --- a/src/app/atoms/header/Header.scss +++ /dev/null @@ -1,43 +0,0 @@ -@use '../../partials/text'; -@use '../../partials/dir'; - -.header { - @include dir.side(padding, var(--sp-normal), var(--sp-extra-tight)); - width: 100%; - height: var(--header-height); - border-bottom: 1px solid var(--bg-surface-border); - display: flex; - align-items: center; - - &__title-wrapper { - flex: 1; - min-width: 0; - display: flex; - align-items: center; - margin: 0 var(--sp-tight); - - &:first-child { - @include dir.side(margin, 0, var(--sp-tight)); - } - - & > .text:first-child { - @extend .cp-txt__ellipsis; - min-width: 0; - } - & > .text-b3{ - flex: 1; - min-width: 0; - - margin-top: var(--sp-ultra-tight); - @include dir.side(margin, var(--sp-tight), 0); - @include dir.side(padding, var(--sp-tight), 0); - @include dir.side(border, 1px solid var(--bg-surface-border), none); - - max-height: calc(2 * var(--lh-b3)); - overflow: hidden; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - display: -webkit-box; - } - } -} \ No newline at end of file diff --git a/src/app/atoms/input/Input.jsx b/src/app/atoms/input/Input.jsx deleted file mode 100644 index 96c94967f8..0000000000 --- a/src/app/atoms/input/Input.jsx +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Input.scss'; - -import TextareaAutosize from 'react-autosize-textarea'; - -function Input({ - id, label, name, value, placeholder, - required, type, onChange, forwardRef, - resizable, minHeight, onResize, state, - onKeyDown, disabled, autoFocus, -}) { - return ( -
- { label !== '' && } - { resizable - ? ( - - ) : ( - - )} -
- ); -} - -Input.defaultProps = { - id: null, - name: '', - label: '', - value: '', - placeholder: '', - type: 'text', - required: false, - onChange: null, - forwardRef: null, - resizable: false, - minHeight: 46, - onResize: null, - state: 'normal', - onKeyDown: null, - disabled: false, - autoFocus: false, -}; - -Input.propTypes = { - id: PropTypes.string, - name: PropTypes.string, - label: PropTypes.string, - value: PropTypes.string, - placeholder: PropTypes.string, - required: PropTypes.bool, - type: PropTypes.string, - onChange: PropTypes.func, - forwardRef: PropTypes.shape({}), - resizable: PropTypes.bool, - minHeight: PropTypes.number, - onResize: PropTypes.func, - state: PropTypes.oneOf(['normal', 'success', 'error']), - onKeyDown: PropTypes.func, - disabled: PropTypes.bool, - autoFocus: PropTypes.bool, -}; - -export default Input; diff --git a/src/app/atoms/input/Input.scss b/src/app/atoms/input/Input.scss deleted file mode 100644 index 40fe43ec54..0000000000 --- a/src/app/atoms/input/Input.scss +++ /dev/null @@ -1,52 +0,0 @@ -@use '../../atoms/scroll/scrollbar'; - -.input { - display: block; - width: 100%; - min-width: 0px; - margin: 0; - padding: var(--sp-tight) var(--sp-normal); - background-color: var(--bg-surface-low); - color: var(--tc-surface-normal); - box-shadow: none; - border-radius: var(--bo-radius); - border: 1px solid var(--bg-surface-border); - font-size: var(--fs-b2); - letter-spacing: var(--ls-b2); - line-height: var(--lh-b2); - - :disabled { - opacity: 0.4; - cursor: no-drop; - } - - &__label { - display: inline-block; - margin-bottom: var(--sp-ultra-tight); - color: var(--tc-surface-low); - } - - &--resizable { - resize: vertical !important; - overflow-y: auto !important; - @include scrollbar.scroll; - @include scrollbar.scroll__v; - @include scrollbar.scroll--auto-hide; - } - &--success { - border: 1px solid var(--bg-positive); - box-shadow: none !important; - } - &--error { - border: 1px solid var(--bg-danger); - box-shadow: none !important; - } - - &:focus { - outline: none; - box-shadow: var(--bs-primary-border); - } - &::placeholder { - color: var(--tc-surface-low) - } -} \ No newline at end of file diff --git a/src/app/atoms/modal/RawModal.jsx b/src/app/atoms/modal/RawModal.jsx deleted file mode 100644 index 450be0e008..0000000000 --- a/src/app/atoms/modal/RawModal.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './RawModal.scss'; - -import Modal from 'react-modal'; - -import navigation from '../../../client/state/navigation'; - -Modal.setAppElement('#root'); - -function RawModal({ - className, overlayClassName, - isOpen, size, onAfterOpen, onAfterClose, - onRequestClose, closeFromOutside, children, -}) { - let modalClass = (className !== null) ? `${className} ` : ''; - switch (size) { - case 'large': - modalClass += 'raw-modal__large '; - break; - case 'medium': - modalClass += 'raw-modal__medium '; - break; - case 'small': - default: - modalClass += 'raw-modal__small '; - } - - useEffect(() => { - navigation.setIsRawModalVisible(isOpen); - }, [isOpen]); - - const modalOverlayClass = (overlayClassName !== null) ? `${overlayClassName} ` : ''; - return ( - - {children} - - ); -} - -RawModal.defaultProps = { - className: null, - overlayClassName: null, - size: 'small', - onAfterOpen: null, - onAfterClose: null, - onRequestClose: null, - closeFromOutside: true, -}; - -RawModal.propTypes = { - className: PropTypes.string, - overlayClassName: PropTypes.string, - isOpen: PropTypes.bool.isRequired, - size: PropTypes.oneOf(['large', 'medium', 'small']), - onAfterOpen: PropTypes.func, - onAfterClose: PropTypes.func, - onRequestClose: PropTypes.func, - closeFromOutside: PropTypes.bool, - children: PropTypes.node.isRequired, -}; - -export default RawModal; diff --git a/src/app/atoms/modal/RawModal.scss b/src/app/atoms/modal/RawModal.scss deleted file mode 100644 index d612a4bc42..0000000000 --- a/src/app/atoms/modal/RawModal.scss +++ /dev/null @@ -1,66 +0,0 @@ -.raw-modal { - --small-modal-width: 525px; - --medium-modal-width: 712px; - --large-modal-width: 1024px; - - position: relative; - width: 100%; - max-height: 100%; - border-radius: var(--bo-radius); - box-shadow: var(--bs-popup); - outline: none; - overflow: hidden; - - &__small { - max-width: var(--small-modal-width); - } - &__medium { - max-width: var(--medium-modal-width); - } - &__large { - max-width: var(--large-modal-width); - } - - &__overlay { - position: fixed; - top: 0; - left: 0; - z-index: 999; - - display: flex; - justify-content: center; - align-items: center; - - padding: var(--sp-normal); - width: 100%; - height: 100%; - background-color: var(--bg-overlay); - } -} - -.ReactModal__Overlay { - animation: raw-modal--overlay 150ms; -} - -.ReactModal__Content { - animation: raw-modal--content 150ms; -} - -@keyframes raw-modal--content { - 0% { - transform: translateY(100px); - opacity: .5; - } - 100% { - transform: translateY(0); - opacity: 1; - } -} -@keyframes raw-modal--overlay { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} diff --git a/src/app/atoms/scroll/ScrollView.jsx b/src/app/atoms/scroll/ScrollView.jsx deleted file mode 100644 index 26c0c83a69..0000000000 --- a/src/app/atoms/scroll/ScrollView.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './ScrollView.scss'; - -const ScrollView = React.forwardRef(({ - horizontal, vertical, autoHide, invisible, onScroll, children, -}, ref) => { - let scrollbarClasses = ''; - if (horizontal) scrollbarClasses += ' scrollbar__h'; - if (vertical) scrollbarClasses += ' scrollbar__v'; - if (autoHide) scrollbarClasses += ' scrollbar--auto-hide'; - if (invisible) scrollbarClasses += ' scrollbar--invisible'; - return ( -
- {children} -
- ); -}); - -ScrollView.defaultProps = { - horizontal: false, - vertical: true, - autoHide: false, - invisible: false, - onScroll: null, -}; - -ScrollView.propTypes = { - horizontal: PropTypes.bool, - vertical: PropTypes.bool, - autoHide: PropTypes.bool, - invisible: PropTypes.bool, - onScroll: PropTypes.func, - children: PropTypes.node.isRequired, -}; - -export default ScrollView; diff --git a/src/app/atoms/scroll/ScrollView.scss b/src/app/atoms/scroll/ScrollView.scss deleted file mode 100644 index 251037e184..0000000000 --- a/src/app/atoms/scroll/ScrollView.scss +++ /dev/null @@ -1,31 +0,0 @@ -@use '../../partials/dir'; -@use '_scrollbar'; - -@mixin paddingForSafari($padding) { - @media not all and (min-resolution:.001dpcm) { - @include dir.side(padding, 0, $padding); - } -} - -.scrollbar { - width: 100%; - height: 100%; - @include scrollbar.scroll; - @include paddingForSafari(var(--sp-extra-tight)); - - &__h { - @include scrollbar.scroll__h; - } - - &__v { - @include scrollbar.scroll__v; - } - - &--auto-hide { - @include scrollbar.scroll--auto-hide; - } - &--invisible { - @include scrollbar.scroll--invisible; - @include paddingForSafari(0); - } -} \ No newline at end of file diff --git a/src/app/atoms/scroll/_scrollbar.scss b/src/app/atoms/scroll/_scrollbar.scss deleted file mode 100644 index 78ec75ad20..0000000000 --- a/src/app/atoms/scroll/_scrollbar.scss +++ /dev/null @@ -1,65 +0,0 @@ -.firefox-scrollbar { - scrollbar-width: thin; - scrollbar-color: var(--bg-surface-hover) transparent; - &--transparent { - scrollbar-color: transparent transparent; - } -} -.webkit-scrollbar { - &::-webkit-scrollbar { - width: 8px; - height: 8px; - } -} -.webkit-scrollbar-track { - &::-webkit-scrollbar-track { - background-color: transparent; - } -} -.webkit-scrollbar-thumb { - &::-webkit-scrollbar-thumb { - background-color: var(--bg-surface-hover); - } - &::-webkit-scrollbar-thumb:hover { - background-color: var(--bg-surface-active); - } - &--transparent { - &::-webkit-scrollbar-thumb { - background-color: transparent; - } - } -} - -@mixin scroll { - overflow: hidden; - // Below code stop scroll when x-scrollable content come in timeline - // overscroll-behavior: none; - @extend .firefox-scrollbar; - @extend .webkit-scrollbar; - @extend .webkit-scrollbar-track; - @extend .webkit-scrollbar-thumb; -} - -@mixin scroll__h { - overflow-x: scroll; -} -@mixin scroll__v { - overflow-y: scroll; -} -@mixin scroll--auto-hide { - @extend .firefox-scrollbar--transparent; - @extend .webkit-scrollbar-thumb--transparent; - - &:hover { - @extend .firefox-scrollbar; - @extend .webkit-scrollbar-thumb; - } -} -@mixin scroll--invisible { - -ms-overflow-style: none; - scrollbar-width: none; - - &::-webkit-scrollbar { - display: none; - } -} \ No newline at end of file diff --git a/src/app/atoms/segmented-controls/SegmentedControls.jsx b/src/app/atoms/segmented-controls/SegmentedControls.jsx deleted file mode 100644 index 1d54dd068e..0000000000 --- a/src/app/atoms/segmented-controls/SegmentedControls.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import './SegmentedControls.scss'; - -import { blurOnBubbling } from '../button/script'; - -import Text from '../text/Text'; -import RawIcon from '../system-icons/RawIcon'; - -function SegmentedControls({ - selected, segments, onSelect, -}) { - const [select, setSelect] = useState(selected); - - function selectSegment(segmentIndex) { - setSelect(segmentIndex); - onSelect(segmentIndex); - } - - useEffect(() => { - setSelect(selected); - }, [selected]); - - return ( -
- { - segments.map((segment, index) => ( - - )) - } -
- ); -} - -SegmentedControls.propTypes = { - selected: PropTypes.number.isRequired, - segments: PropTypes.arrayOf(PropTypes.shape({ - iconSrc: PropTypes.string, - text: PropTypes.string, - })).isRequired, - onSelect: PropTypes.func.isRequired, -}; - -export default SegmentedControls; diff --git a/src/app/atoms/segmented-controls/SegmentedControls.scss b/src/app/atoms/segmented-controls/SegmentedControls.scss deleted file mode 100644 index fb1fd9875d..0000000000 --- a/src/app/atoms/segmented-controls/SegmentedControls.scss +++ /dev/null @@ -1,57 +0,0 @@ -@use '../button/state'; -@use '../../partials/dir'; - -.segmented-controls { - background-color: var(--bg-surface-low); - border-radius: var(--bo-radius); - border: 1px solid var(--bg-surface-border); - - display: inline-flex; - overflow: hidden; -} - -.segment-btn { - padding: var(--sp-extra-tight) 0; - cursor: pointer; - @include state.hover(var(--bg-surface-hover)); - @include state.active(var(--bg-surface-active)); - - &__base { - padding: 0 var(--sp-normal); - display: flex; - align-items: center; - justify-content: center; - @include dir.side(border, 1px solid var(--bg-surface-border), none); - - & .text:nth-child(2) { - margin: 0 var(--sp-extra-tight); - } - } - &:first-child &__base { - border: none; - } - - &--active { - background-color: var(--bg-surface); - border: 1px solid var(--bg-surface-border); - border-width: 0 1px 0 1px; - - & .segment-btn__base, - & + .segment-btn .segment-btn__base { - border: none; - } - &:first-child{ - border-left: none; - } - &:last-child { - border-right: none; - } - [dir=rtl] & { - border-left: 1px solid var(--bg-surface-border); - border-right: 1px solid var(--bg-surface-border); - - &:first-child { border-right: none;} - &:last-child { border-left: none;} - } - } -} \ No newline at end of file diff --git a/src/app/atoms/spinner/Spinner.jsx b/src/app/atoms/spinner/Spinner.jsx deleted file mode 100644 index 61c9747eb9..0000000000 --- a/src/app/atoms/spinner/Spinner.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Spinner.scss'; - -function Spinner({ size }) { - return ( -
- ); -} - -Spinner.defaultProps = { - size: 'normal', -}; - -Spinner.propTypes = { - size: PropTypes.oneOf(['normal', 'small']), -}; - -export default Spinner; diff --git a/src/app/atoms/spinner/Spinner.scss b/src/app/atoms/spinner/Spinner.scss deleted file mode 100644 index 73fbf67678..0000000000 --- a/src/app/atoms/spinner/Spinner.scss +++ /dev/null @@ -1,22 +0,0 @@ -.donut-spinner { - display: inline-block; - border: 4px solid var(--bg-surface-border); - border-left-color: var(--tc-surface-normal); - border-radius: 50%; - animation: donut-spin 1.2s cubic-bezier(0.73, 0.32, 0.67, 0.86) infinite; - - &--normal { - width: 40px; - height: 40px; - } - &--small { - width: 28px; - height: 28px; - } -} - -@keyframes donut-spin { - to { - transform: rotate(1turn); - } -} \ No newline at end of file diff --git a/src/app/atoms/system-icons/RawIcon.jsx b/src/app/atoms/system-icons/RawIcon.jsx deleted file mode 100644 index a6697f4f43..0000000000 --- a/src/app/atoms/system-icons/RawIcon.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './RawIcon.scss'; - -function RawIcon({ color, size, src, isImage }) { - const style = {}; - if (color !== null) style.backgroundColor = color; - if (isImage) { - style.backgroundColor = 'transparent'; - style.backgroundImage = `url("${src}")`; - } else { - style.WebkitMaskImage = `url("${src}")`; - style.maskImage = `url("${src}")`; - } - - return ; -} - -RawIcon.defaultProps = { - color: null, - size: 'normal', - isImage: false, -}; - -RawIcon.propTypes = { - color: PropTypes.string, - size: PropTypes.oneOf(['large', 'normal', 'small', 'extra-small']), - src: PropTypes.string.isRequired, - isImage: PropTypes.bool, -}; - -export default RawIcon; diff --git a/src/app/atoms/system-icons/RawIcon.scss b/src/app/atoms/system-icons/RawIcon.scss deleted file mode 100644 index 56fc9b3c2a..0000000000 --- a/src/app/atoms/system-icons/RawIcon.scss +++ /dev/null @@ -1,28 +0,0 @@ -@mixin icSize($size) { - width: $size; - height: $size; -} - -.ic-raw { - display: inline-block; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-size: cover; - mask-size: cover; - background-color: var(--ic-surface-normal); - - background-size: cover; - background-repeat: no-repeat; -} -.ic-raw-large { - @include icSize(var(--ic-large)); -} -.ic-raw-normal { - @include icSize(var(--ic-normal)); -} -.ic-raw-small { - @include icSize(var(--ic-small)); -} -.ic-raw-extra-small { - @include icSize(var(--ic-extra-small)); -} \ No newline at end of file diff --git a/src/app/atoms/tabs/Tabs.jsx b/src/app/atoms/tabs/Tabs.jsx deleted file mode 100644 index bcdc8ef7ed..0000000000 --- a/src/app/atoms/tabs/Tabs.jsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import './Tabs.scss'; - -import Button from '../button/Button'; -import ScrollView from '../scroll/ScrollView'; - -function TabItem({ - selected, iconSrc, - onClick, children, disabled, -}) { - const isSelected = selected ? 'tab-item--selected' : ''; - - return ( - - ); -} - -TabItem.defaultProps = { - selected: false, - iconSrc: null, - onClick: null, - disabled: false, -}; - -TabItem.propTypes = { - selected: PropTypes.bool, - iconSrc: PropTypes.string, - onClick: PropTypes.func, - children: PropTypes.node.isRequired, - disabled: PropTypes.bool, -}; - -function Tabs({ items, defaultSelected, onSelect }) { - const [selectedItem, setSelectedItem] = useState(items[defaultSelected]); - - const handleTabSelection = (item, index, target) => { - if (selectedItem === item) return; - target.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); - setSelectedItem(item); - onSelect(item, index); - }; - - return ( -
- -
- {items.map((item, index) => ( - handleTabSelection(item, index, e.currentTarget)} - > - {item.text} - - ))} -
-
-
- ); -} - -Tabs.defaultProps = { - defaultSelected: 0, -}; - -Tabs.propTypes = { - items: PropTypes.arrayOf( - PropTypes.shape({ - iconSrc: PropTypes.string, - text: PropTypes.string, - disabled: PropTypes.bool, - }), - ).isRequired, - defaultSelected: PropTypes.number, - onSelect: PropTypes.func.isRequired, -}; - -export default Tabs; diff --git a/src/app/atoms/tabs/Tabs.scss b/src/app/atoms/tabs/Tabs.scss deleted file mode 100644 index 39ddddec3a..0000000000 --- a/src/app/atoms/tabs/Tabs.scss +++ /dev/null @@ -1,45 +0,0 @@ -@use '../../partials/dir'; - -.tabs { - height: var(--header-height); - box-shadow: inset 0 -1px 0 var(--bg-surface-border); - - &__content { - min-width: 100%; - height: 100%; - display: flex; - } -} - -.tab-item { - flex-shrink: 0; - - @include dir.side(padding, var(--sp-normal), 24px); - border-radius: 0; - height: 100%; - box-shadow: none; - border-radius: var(--bo-radius) var(--bo-radius) 0 0; - - &:focus, - &:active { - background-color: var(--bg-surface-active); - box-shadow: none; - } - - &--selected { - --bs-tab-selected: inset 0 -2px 0 var(--tc-surface-high); - box-shadow: var(--bs-tab-selected); - - & .ic-raw { - background-color: var(--ic-surface-high); - } - & .text { - font-weight: var(--fw-medium); - } - &:focus, - &:active { - background-color: var(--bg-surface-active); - box-shadow: var(--bs-tab-selected); - } - } -} \ No newline at end of file diff --git a/src/app/atoms/text/Text.jsx b/src/app/atoms/text/Text.jsx deleted file mode 100644 index 3f507ee304..0000000000 --- a/src/app/atoms/text/Text.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Text.scss'; - -function Text({ - className, style, variant, weight, - primary, span, children, -}) { - const classes = []; - if (className) classes.push(className); - - classes.push(`text text-${variant} text-${weight}`); - if (primary) classes.push('font-primary'); - - const textClass = classes.join(' '); - if (span) return { children }; - if (variant === 'h1') return

{ children }

; - if (variant === 'h2') return

{ children }

; - if (variant === 's1') return

{ children }

; - return

{ children }

; -} - -Text.defaultProps = { - className: null, - style: null, - variant: 'b1', - weight: 'normal', - primary: false, - span: false, -}; - -Text.propTypes = { - className: PropTypes.string, - style: PropTypes.shape({}), - variant: PropTypes.oneOf(['h1', 'h2', 's1', 'b1', 'b2', 'b3']), - weight: PropTypes.oneOf(['light', 'normal', 'medium', 'bold']), - primary: PropTypes.bool, - span: PropTypes.bool, - children: PropTypes.node.isRequired, -}; - -export default Text; diff --git a/src/app/atoms/text/Text.scss b/src/app/atoms/text/Text.scss deleted file mode 100644 index 256bf6ea98..0000000000 --- a/src/app/atoms/text/Text.scss +++ /dev/null @@ -1,61 +0,0 @@ -@mixin font($type) { - font-size: var(--fs-#{$type}); - letter-spacing: var(--ls-#{$type}); - line-height: var(--lh-#{$type}); - - & img.emoji, - & img[data-mx-emoticon] { - height: calc(var(--lh-#{$type}) - .25rem); - } -} - -.text { - margin: 0; - padding: 0; - color: var(--tc-surface-high); - - & img.emoji, - & img[data-mx-emoticon] { - margin: 0 !important; - margin-right: 2px !important; - padding: 0 !important; - position: relative; - top: -.1rem; - vertical-align: middle; - } -} - -.text-light { - font-weight: var(--fw-light); -} -.text-normal { - font-weight: var(--fw-normal); -} -.text-medium { - font-weight: var(--fw-medium); -} -.text-bold { - font-weight: var(--fw-bold); -} - -.text-h1 { - @include font(h1); -} -.text-h2 { - @include font(h2); -} -.text-s1 { - @include font(s1); -} -.text-b1 { - @include font(b1); - color: var(--tc-surface-normal); -} -.text-b2 { - @include font(b2); - color: var(--tc-surface-normal); -} -.text-b3 { - @include font(b3); - color: var(--tc-surface-low); -} \ No newline at end of file diff --git a/src/app/atoms/time/Time.jsx b/src/app/atoms/time/Time.jsx deleted file mode 100644 index 750b958fcf..0000000000 --- a/src/app/atoms/time/Time.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import dateFormat from 'dateformat'; -import { isInSameDay } from '../../../util/common'; - -function Time({ timestamp, fullTime }) { - const date = new Date(timestamp); - - const formattedFullTime = dateFormat(date, 'dd mmmm yyyy, hh:MM TT'); - let formattedDate = formattedFullTime; - - if (!fullTime) { - const compareDate = new Date(); - const isToday = isInSameDay(date, compareDate); - compareDate.setDate(compareDate.getDate() - 1); - const isYesterday = isInSameDay(date, compareDate); - - formattedDate = dateFormat(date, isToday || isYesterday ? 'hh:MM TT' : 'dd/mm/yyyy'); - if (isYesterday) { - formattedDate = `Yesterday, ${formattedDate}`; - } - } - - return ( - - ); -} - -Time.defaultProps = { - fullTime: false, -}; - -Time.propTypes = { - timestamp: PropTypes.number.isRequired, - fullTime: PropTypes.bool, -}; - -export default Time; diff --git a/src/app/atoms/tooltip/Tooltip.jsx b/src/app/atoms/tooltip/Tooltip.jsx deleted file mode 100644 index 0f9067f59e..0000000000 --- a/src/app/atoms/tooltip/Tooltip.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Tooltip.scss'; -import Tippy from '@tippyjs/react'; - -function Tooltip({ - className, placement, content, delay, children, -}) { - return ( - - {children} - - ); -} - -Tooltip.defaultProps = { - placement: 'top', - className: '', - delay: [200, 0], -}; - -Tooltip.propTypes = { - className: PropTypes.string, - placement: PropTypes.string, - content: PropTypes.node.isRequired, - delay: PropTypes.arrayOf(PropTypes.number), - children: PropTypes.node.isRequired, -}; - -export default Tooltip; diff --git a/src/app/atoms/tooltip/Tooltip.scss b/src/app/atoms/tooltip/Tooltip.scss deleted file mode 100644 index f609aa580a..0000000000 --- a/src/app/atoms/tooltip/Tooltip.scss +++ /dev/null @@ -1,10 +0,0 @@ -.tooltip { - padding: var(--sp-extra-tight) var(--sp-normal); - background-color: var(--bg-tooltip); - border-radius: var(--bo-radius); - box-shadow: var(--bs-popup); - - .text { - color: var(--tc-tooltip); - } -} \ No newline at end of file diff --git a/src/app/components/CapabilitiesAndMediaConfigLoader.tsx b/src/app/components/CapabilitiesAndMediaConfigLoader.tsx deleted file mode 100644 index 574d0ca79c..0000000000 --- a/src/app/components/CapabilitiesAndMediaConfigLoader.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { ReactNode, useCallback, useEffect } from 'react'; -import { Capabilities } from 'matrix-js-sdk'; -import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; -import { useMatrixClient } from '../hooks/useMatrixClient'; -import { MediaConfig } from '../hooks/useMediaConfig'; -import { promiseFulfilledResult } from '../utils/common'; - -type CapabilitiesAndMediaConfigLoaderProps = { - children: (capabilities?: Capabilities, mediaConfig?: MediaConfig) => ReactNode; -}; -export function CapabilitiesAndMediaConfigLoader({ - children, -}: CapabilitiesAndMediaConfigLoaderProps) { - const mx = useMatrixClient(); - - const [state, load] = useAsyncCallback< - [Capabilities | undefined, MediaConfig | undefined], - unknown, - [] - >( - useCallback(async () => { - const result = await Promise.allSettled([mx.getCapabilities(), mx.getMediaConfig()]); - const capabilities = promiseFulfilledResult(result[0]); - const mediaConfig = promiseFulfilledResult(result[1]); - return [capabilities, mediaConfig]; - }, [mx]) - ); - - useEffect(() => { - load(); - }, [load]); - - const [capabilities, mediaConfig] = - state.status === AsyncStatus.Success ? state.data : [undefined, undefined]; - return children(capabilities, mediaConfig); -} diff --git a/src/app/components/DeviceVerificationSetup.tsx b/src/app/components/DeviceVerificationSetup.tsx index fb09c150ff..433fa6a1db 100644 --- a/src/app/components/DeviceVerificationSetup.tsx +++ b/src/app/components/DeviceVerificationSetup.tsx @@ -20,7 +20,7 @@ import { PasswordInput } from './password-input'; import { ContainerColor } from '../styles/ContainerColor.css'; import { copyToClipboard } from '../utils/dom'; import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; -import { clearSecretStorageKeys } from '../../client/state/secretStorageKeys'; +import { clearSecretStorageKeys } from '../../client/secretStorageKeys'; import { ActionUIA, ActionUIAFlowsLoader } from './ActionUIA'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { useAlive } from '../hooks/useAlive'; diff --git a/src/app/components/ManualVerification.tsx b/src/app/components/ManualVerification.tsx index 03f3e710a9..f7cde92b4c 100644 --- a/src/app/components/ManualVerification.tsx +++ b/src/app/components/ManualVerification.tsx @@ -19,7 +19,7 @@ import { SecretStorageKeyContent } from '../../types/matrix/accountData'; import { SecretStorageRecoveryKey, SecretStorageRecoveryPassphrase } from './SecretStorage'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; -import { storePrivateKey } from '../../client/state/secretStorageKeys'; +import { storePrivateKey } from '../../client/secretStorageKeys'; export enum ManualVerificationMethod { RecoveryPassphrase = 'passphrase', diff --git a/src/app/components/ServerConfigsLoader.tsx b/src/app/components/ServerConfigsLoader.tsx new file mode 100644 index 0000000000..3c8ce8eb7b --- /dev/null +++ b/src/app/components/ServerConfigsLoader.tsx @@ -0,0 +1,52 @@ +import { ReactNode, useCallback, useMemo } from 'react'; +import { Capabilities, validateAuthMetadata, ValidatedAuthMetadata } from 'matrix-js-sdk'; +import { AsyncStatus, useAsyncCallbackValue } from '../hooks/useAsyncCallback'; +import { useMatrixClient } from '../hooks/useMatrixClient'; +import { MediaConfig } from '../hooks/useMediaConfig'; +import { promiseFulfilledResult } from '../utils/common'; + +export type ServerConfigs = { + capabilities?: Capabilities; + mediaConfig?: MediaConfig; + authMetadata?: ValidatedAuthMetadata; +}; + +type ServerConfigsLoaderProps = { + children: (configs: ServerConfigs) => ReactNode; +}; +export function ServerConfigsLoader({ children }: ServerConfigsLoaderProps) { + const mx = useMatrixClient(); + const fallbackConfigs = useMemo(() => ({}), []); + + const [configsState] = useAsyncCallbackValue( + useCallback(async () => { + const result = await Promise.allSettled([ + mx.getCapabilities(), + mx.getMediaConfig(), + mx.getAuthMetadata(), + ]); + + const capabilities = promiseFulfilledResult(result[0]); + const mediaConfig = promiseFulfilledResult(result[1]); + const authMetadata = promiseFulfilledResult(result[2]); + let validatedAuthMetadata: ValidatedAuthMetadata | undefined; + + try { + validatedAuthMetadata = validateAuthMetadata(authMetadata); + } catch (e) { + console.error(e); + } + + return { + capabilities, + mediaConfig, + authMetadata: validatedAuthMetadata, + }; + }, [mx]) + ); + + const configs: ServerConfigs = + configsState.status === AsyncStatus.Success ? configsState.data : fallbackConfigs; + + return children(configs); +} diff --git a/src/app/components/UserRoomProfileRenderer.tsx b/src/app/components/UserRoomProfileRenderer.tsx new file mode 100644 index 0000000000..ca7aa837ed --- /dev/null +++ b/src/app/components/UserRoomProfileRenderer.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Menu, PopOut, toRem } from 'folds'; +import FocusTrap from 'focus-trap-react'; +import { useCloseUserRoomProfile, useUserRoomProfileState } from '../state/hooks/userRoomProfile'; +import { UserRoomProfile } from './user-profile'; +import { UserRoomProfileState } from '../state/userRoomProfile'; +import { useAllJoinedRoomsSet, useGetRoom } from '../hooks/useGetRoom'; +import { stopPropagation } from '../utils/keyboard'; +import { SpaceProvider } from '../hooks/useSpace'; +import { RoomProvider } from '../hooks/useRoom'; + +function UserRoomProfileContextMenu({ state }: { state: UserRoomProfileState }) { + const { roomId, spaceId, userId, cords, position } = state; + const allJoinedRooms = useAllJoinedRoomsSet(); + const getRoom = useGetRoom(allJoinedRooms); + const room = getRoom(roomId); + const space = spaceId ? getRoom(spaceId) : undefined; + + const close = useCloseUserRoomProfile(); + + if (!room) return null; + + return ( + + + + + + + + + + } + /> + ); +} + +export function UserRoomProfileRenderer() { + const state = useUserRoomProfileState(); + + if (!state) return null; + return ; +} diff --git a/src/app/components/create-room/AdditionalCreatorInput.tsx b/src/app/components/create-room/AdditionalCreatorInput.tsx new file mode 100644 index 0000000000..84b922310b --- /dev/null +++ b/src/app/components/create-room/AdditionalCreatorInput.tsx @@ -0,0 +1,294 @@ +import { + Box, + Button, + Chip, + config, + Icon, + Icons, + Input, + Line, + Menu, + MenuItem, + PopOut, + RectCords, + Scroll, + Text, + toRem, +} from 'folds'; +import { isKeyHotkey } from 'is-hotkey'; +import FocusTrap from 'focus-trap-react'; +import React, { + ChangeEventHandler, + KeyboardEventHandler, + MouseEventHandler, + useMemo, + useState, +} from 'react'; +import { getMxIdLocalPart, getMxIdServer, isUserId } from '../../utils/matrix'; +import { useDirectUsers } from '../../hooks/useDirectUsers'; +import { SettingTile } from '../setting-tile'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { stopPropagation } from '../../utils/keyboard'; +import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch'; +import { highlightText, makeHighlightRegex } from '../../plugins/react-custom-html-parser'; + +export const useAdditionalCreators = (defaultCreators?: string[]) => { + const mx = useMatrixClient(); + const [additionalCreators, setAdditionalCreators] = useState( + () => defaultCreators?.filter((id) => id !== mx.getSafeUserId()) ?? [] + ); + + const addAdditionalCreator = (userId: string) => { + if (userId === mx.getSafeUserId()) return; + + setAdditionalCreators((creators) => { + const creatorsSet = new Set(creators); + creatorsSet.add(userId); + return Array.from(creatorsSet); + }); + }; + + const removeAdditionalCreator = (userId: string) => { + setAdditionalCreators((creators) => { + const creatorsSet = new Set(creators); + creatorsSet.delete(userId); + return Array.from(creatorsSet); + }); + }; + + return { + additionalCreators, + addAdditionalCreator, + removeAdditionalCreator, + }; +}; + +const SEARCH_OPTIONS: UseAsyncSearchOptions = { + limit: 1000, + matchOptions: { + contain: true, + }, +}; +const getUserIdString = (userId: string) => getMxIdLocalPart(userId) ?? userId; + +type AdditionalCreatorInputProps = { + additionalCreators: string[]; + onSelect: (userId: string) => void; + onRemove: (userId: string) => void; + disabled?: boolean; +}; +export function AdditionalCreatorInput({ + additionalCreators, + onSelect, + onRemove, + disabled, +}: AdditionalCreatorInputProps) { + const mx = useMatrixClient(); + const [menuCords, setMenuCords] = useState(); + const directUsers = useDirectUsers(); + + const [validUserId, setValidUserId] = useState(); + const filteredUsers = useMemo( + () => directUsers.filter((userId) => !additionalCreators.includes(userId)), + [directUsers, additionalCreators] + ); + const [result, search, resetSearch] = useAsyncSearch( + filteredUsers, + getUserIdString, + SEARCH_OPTIONS + ); + const queryHighlighRegex = result?.query ? makeHighlightRegex([result.query]) : undefined; + + const suggestionUsers = result + ? result.items + : filteredUsers.sort((a, b) => (a.toLocaleLowerCase() >= b.toLocaleLowerCase() ? 1 : -1)); + + const handleOpenMenu: MouseEventHandler = (evt) => { + setMenuCords(evt.currentTarget.getBoundingClientRect()); + }; + const handleCloseMenu = () => { + setMenuCords(undefined); + setValidUserId(undefined); + resetSearch(); + }; + + const handleCreatorChange: ChangeEventHandler = (evt) => { + const creatorInput = evt.currentTarget; + const creator = creatorInput.value.trim(); + if (isUserId(creator)) { + setValidUserId(creator); + } else { + setValidUserId(undefined); + const term = + getMxIdLocalPart(creator) ?? (creator.startsWith('@') ? creator.slice(1) : creator); + if (term) { + search(term); + } else { + resetSearch(); + } + } + }; + + const handleSelectUserId = (userId?: string) => { + if (userId && isUserId(userId)) { + onSelect(userId); + handleCloseMenu(); + } + }; + + const handleCreatorKeyDown: KeyboardEventHandler = (evt) => { + if (isKeyHotkey('enter', evt)) { + evt.preventDefault(); + const creator = evt.currentTarget.value.trim(); + handleSelectUserId(isUserId(creator) ? creator : suggestionUsers[0]); + } + }; + + const handleEnterClick = () => { + handleSelectUserId(validUserId); + }; + + return ( + + + + + {mx.getSafeUserId()} + + {additionalCreators.map((creator) => ( + } + onClick={() => onRemove(creator)} + disabled={disabled} + > + {creator} + + ))} + evt.key === 'ArrowDown', + isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', + escapeDeactivates: stopPropagation, + }} + > + + + + + + + + + + + {!validUserId && suggestionUsers.length > 0 ? ( + + + {suggestionUsers.map((userId) => ( + handleSelectUserId(userId)} + after={ + + {getMxIdServer(userId)} + + } + > + + + + {queryHighlighRegex + ? highlightText(queryHighlighRegex, [ + getMxIdLocalPart(userId) ?? userId, + ]) + : getMxIdLocalPart(userId)} + + + + + ))} + + + ) : ( + + + No Suggestions + + + Please provide the user ID and hit Enter. + + + )} + + + + + } + > + + + + + + + + ); +} diff --git a/src/app/components/create-room/CreateRoomAliasInput.tsx b/src/app/components/create-room/CreateRoomAliasInput.tsx new file mode 100644 index 0000000000..e84658c01e --- /dev/null +++ b/src/app/components/create-room/CreateRoomAliasInput.tsx @@ -0,0 +1,118 @@ +import React, { + FormEventHandler, + KeyboardEventHandler, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; +import { MatrixError } from 'matrix-js-sdk'; +import { Box, color, Icon, Icons, Input, Spinner, Text, toRem } from 'folds'; +import { isKeyHotkey } from 'is-hotkey'; +import { getMxIdServer } from '../../utils/matrix'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { replaceSpaceWithDash } from '../../utils/common'; +import { AsyncState, AsyncStatus, useAsync } from '../../hooks/useAsyncCallback'; +import { useDebounce } from '../../hooks/useDebounce'; + +export function CreateRoomAliasInput({ disabled }: { disabled?: boolean }) { + const mx = useMatrixClient(); + const aliasInputRef = useRef(null); + const [aliasAvail, setAliasAvail] = useState>({ + status: AsyncStatus.Idle, + }); + + useEffect(() => { + if (aliasAvail.status === AsyncStatus.Success && aliasInputRef.current?.value === '') { + setAliasAvail({ status: AsyncStatus.Idle }); + } + }, [aliasAvail]); + + const checkAliasAvail = useAsync( + useCallback( + async (aliasLocalPart: string) => { + const roomAlias = `#${aliasLocalPart}:${getMxIdServer(mx.getSafeUserId())}`; + try { + const result = await mx.getRoomIdForAlias(roomAlias); + return typeof result.room_id !== 'string'; + } catch (e) { + if (e instanceof MatrixError && e.httpStatus === 404) { + return true; + } + throw e; + } + }, + [mx] + ), + setAliasAvail + ); + const aliasAvailable: boolean | undefined = + aliasAvail.status === AsyncStatus.Success ? aliasAvail.data : undefined; + + const debounceCheckAliasAvail = useDebounce(checkAliasAvail, { wait: 500 }); + + const handleAliasChange: FormEventHandler = (evt) => { + const aliasInput = evt.currentTarget; + const aliasLocalPart = replaceSpaceWithDash(aliasInput.value); + if (aliasLocalPart) { + aliasInput.value = aliasLocalPart; + debounceCheckAliasAvail(aliasLocalPart); + } else { + setAliasAvail({ status: AsyncStatus.Idle }); + } + }; + + const handleAliasKeyDown: KeyboardEventHandler = (evt) => { + if (isKeyHotkey('enter', evt)) { + evt.preventDefault(); + + const aliasInput = evt.currentTarget; + const aliasLocalPart = replaceSpaceWithDash(aliasInput.value); + if (aliasLocalPart) { + checkAliasAvail(aliasLocalPart); + } else { + setAliasAvail({ status: AsyncStatus.Idle }); + } + } + }; + + return ( + + Address (Optional) + + Pick an unique address to make it discoverable. + + + ) : ( + + ) + } + after={ + + :{getMxIdServer(mx.getSafeUserId())} + + } + onKeyDown={handleAliasKeyDown} + name="aliasInput" + size="500" + variant={aliasAvailable === true ? 'Success' : 'SurfaceVariant'} + radii="400" + autoComplete="off" + disabled={disabled} + /> + {aliasAvailable === false && ( + + + + This address is already taken. Please select a different one. + + + )} + + ); +} diff --git a/src/app/components/create-room/CreateRoomKindSelector.tsx b/src/app/components/create-room/CreateRoomKindSelector.tsx new file mode 100644 index 0000000000..096954fbcb --- /dev/null +++ b/src/app/components/create-room/CreateRoomKindSelector.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { Box, Text, Icon, Icons, config, IconSrc } from 'folds'; +import { SequenceCard } from '../sequence-card'; +import { SettingTile } from '../setting-tile'; + +export enum CreateRoomKind { + Private = 'private', + Restricted = 'restricted', + Public = 'public', +} +type CreateRoomKindSelectorProps = { + value?: CreateRoomKind; + onSelect: (value: CreateRoomKind) => void; + canRestrict?: boolean; + disabled?: boolean; + getIcon: (kind: CreateRoomKind) => IconSrc; +}; +export function CreateRoomKindSelector({ + value, + onSelect, + canRestrict, + disabled, + getIcon, +}: CreateRoomKindSelectorProps) { + return ( + + {canRestrict && ( + onSelect(CreateRoomKind.Restricted)} + disabled={disabled} + > + } + after={value === CreateRoomKind.Restricted && } + > + Restricted + + Only member of parent space can join. + + + + )} + onSelect(CreateRoomKind.Private)} + disabled={disabled} + > + } + after={value === CreateRoomKind.Private && } + > + Private + + Only people with invite can join. + + + + onSelect(CreateRoomKind.Public)} + disabled={disabled} + > + } + after={value === CreateRoomKind.Public && } + > + Public + + Anyone with the address can join. + + + + + ); +} diff --git a/src/app/components/create-room/RoomVersionSelector.tsx b/src/app/components/create-room/RoomVersionSelector.tsx new file mode 100644 index 0000000000..219ded0cb0 --- /dev/null +++ b/src/app/components/create-room/RoomVersionSelector.tsx @@ -0,0 +1,117 @@ +import React, { MouseEventHandler, useState } from 'react'; +import { + Box, + Button, + Chip, + config, + Icon, + Icons, + Menu, + PopOut, + RectCords, + Text, + toRem, +} from 'folds'; +import FocusTrap from 'focus-trap-react'; +import { SettingTile } from '../setting-tile'; +import { SequenceCard } from '../sequence-card'; +import { stopPropagation } from '../../utils/keyboard'; + +export function RoomVersionSelector({ + versions, + value, + onChange, + disabled, +}: { + versions: string[]; + value: string; + onChange: (value: string) => void; + disabled?: boolean; +}) { + const [menuCords, setMenuCords] = useState(); + + const handleMenu: MouseEventHandler = (evt) => { + setMenuCords(evt.currentTarget.getBoundingClientRect()); + }; + + const handleSelect = (version: string) => { + setMenuCords(undefined); + onChange(version); + }; + + return ( + + setMenuCords(undefined), + clickOutsideDeactivates: true, + isKeyForward: (evt: KeyboardEvent) => + evt.key === 'ArrowDown' || evt.key === 'ArrowRight', + isKeyBackward: (evt: KeyboardEvent) => + evt.key === 'ArrowUp' || evt.key === 'ArrowLeft', + escapeDeactivates: stopPropagation, + }} + > + + + Versions + + {versions.map((version) => ( + handleSelect(version)} + type="button" + > + + {version} + + + ))} + + + + + } + > + + + } + /> + + ); +} diff --git a/src/app/components/create-room/index.ts b/src/app/components/create-room/index.ts new file mode 100644 index 0000000000..ffd9cb3d33 --- /dev/null +++ b/src/app/components/create-room/index.ts @@ -0,0 +1,5 @@ +export * from './CreateRoomKindSelector'; +export * from './CreateRoomAliasInput'; +export * from './RoomVersionSelector'; +export * from './utils'; +export * from './AdditionalCreatorInput'; diff --git a/src/app/components/create-room/utils.ts b/src/app/components/create-room/utils.ts new file mode 100644 index 0000000000..a0ca7488ae --- /dev/null +++ b/src/app/components/create-room/utils.ts @@ -0,0 +1,140 @@ +import { + ICreateRoomOpts, + ICreateRoomStateEvent, + JoinRule, + MatrixClient, + RestrictedAllowType, + Room, +} from 'matrix-js-sdk'; +import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; +import { CreateRoomKind } from './CreateRoomKindSelector'; +import { RoomType, StateEvent } from '../../../types/matrix/room'; +import { getViaServers } from '../../plugins/via-servers'; +import { getMxIdServer } from '../../utils/matrix'; + +export const createRoomCreationContent = ( + type: RoomType | undefined, + allowFederation: boolean, + additionalCreators: string[] | undefined +): object => { + const content: Record = {}; + if (typeof type === 'string') { + content.type = type; + } + if (allowFederation === false) { + content['m.federate'] = false; + } + if (Array.isArray(additionalCreators)) { + content.additional_creators = additionalCreators; + } + + return content; +}; + +export const createRoomJoinRulesState = ( + kind: CreateRoomKind, + parent: Room | undefined, + knock: boolean +) => { + let content: RoomJoinRulesEventContent = { + join_rule: knock ? JoinRule.Knock : JoinRule.Invite, + }; + + if (kind === CreateRoomKind.Public) { + content = { + join_rule: JoinRule.Public, + }; + } + + if (kind === CreateRoomKind.Restricted && parent) { + content = { + join_rule: knock ? ('knock_restricted' as JoinRule) : JoinRule.Restricted, + allow: [ + { + type: RestrictedAllowType.RoomMembership, + room_id: parent.roomId, + }, + ], + }; + } + + return { + type: StateEvent.RoomJoinRules, + state_key: '', + content, + }; +}; + +export const createRoomParentState = (parent: Room) => ({ + type: StateEvent.SpaceParent, + state_key: parent.roomId, + content: { + canonical: true, + via: getViaServers(parent), + }, +}); + +export const createRoomEncryptionState = () => ({ + type: 'm.room.encryption', + state_key: '', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + }, +}); + +export type CreateRoomData = { + version: string; + type?: RoomType; + parent?: Room; + kind: CreateRoomKind; + name: string; + topic?: string; + aliasLocalPart?: string; + encryption?: boolean; + knock: boolean; + allowFederation: boolean; + additionalCreators?: string[]; +}; +export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promise => { + const initialState: ICreateRoomStateEvent[] = []; + + if (data.encryption) { + initialState.push(createRoomEncryptionState()); + } + + if (data.parent) { + initialState.push(createRoomParentState(data.parent)); + } + + initialState.push(createRoomJoinRulesState(data.kind, data.parent, data.knock)); + + const options: ICreateRoomOpts = { + room_version: data.version, + name: data.name, + topic: data.topic, + room_alias_name: data.aliasLocalPart, + creation_content: createRoomCreationContent( + data.type, + data.allowFederation, + data.additionalCreators + ), + initial_state: initialState, + }; + + const result = await mx.createRoom(options); + + if (data.parent) { + await mx.sendStateEvent( + data.parent.roomId, + StateEvent.SpaceChild as any, + { + auto_join: false, + suggested: false, + via: [getMxIdServer(mx.getUserId() ?? '') ?? ''], + }, + result.room_id + ); + } + + return result.room_id; +}; diff --git a/src/app/components/editor/Editor.css.ts b/src/app/components/editor/Editor.css.ts index 09a444ec18..d128ed0746 100644 --- a/src/app/components/editor/Editor.css.ts +++ b/src/app/components/editor/Editor.css.ts @@ -41,21 +41,21 @@ export const EditorTextarea = style([ }, ]); -export const EditorPlaceholder = style([ +export const EditorPlaceholderContainer = style([ DefaultReset, { - position: 'absolute', - zIndex: 1, - width: '100%', opacity: config.opacity.Placeholder, pointerEvents: 'none', userSelect: 'none', + }, +]); - selectors: { - '&:not(:first-child)': { - display: 'none', - }, - }, +export const EditorPlaceholderTextVisual = style([ + DefaultReset, + { + display: 'block', + paddingTop: toRem(13), + paddingLeft: toRem(1), }, ]); diff --git a/src/app/components/editor/Editor.tsx b/src/app/components/editor/Editor.tsx index 044d083793..bd848dd5a3 100644 --- a/src/app/components/editor/Editor.tsx +++ b/src/app/components/editor/Editor.tsx @@ -106,22 +106,17 @@ export const CustomEditor = forwardRef( [editor, onKeyDown] ); - const renderPlaceholder = useCallback(({ attributes, children }: RenderPlaceholderProps) => { - // drop style attribute as we use our custom placeholder css. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { style, ...props } = attributes; - return ( - - {children} - - ); - }, []); + const renderPlaceholder = useCallback( + ({ attributes, children }: RenderPlaceholderProps) => ( + + {/* Inner component to style the actual text position and appearance */} + + {children} + + + ), + [] + ); return (
diff --git a/src/app/components/editor/Elements.tsx b/src/app/components/editor/Elements.tsx index a7438ecdfd..675c4542ef 100644 --- a/src/app/components/editor/Elements.tsx +++ b/src/app/components/editor/Elements.tsx @@ -157,7 +157,7 @@ export function RenderElement({ attributes, element, children }: RenderElementPr } + tooltip={} delay={500} > {(triggerRef) => ( diff --git a/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx b/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx index cc431f58d3..b0c64f60f1 100644 --- a/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/RoomMentionAutocomplete.tsx @@ -9,7 +9,7 @@ import { getDirectRoomAvatarUrl } from '../../../utils/room'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { AutocompleteQuery } from './autocompleteQuery'; import { AutocompleteMenu } from './AutocompleteMenu'; -import { getMxIdServer, validMxId } from '../../../utils/matrix'; +import { getMxIdServer, isRoomAlias } from '../../../utils/matrix'; import { UseAsyncSearchOptions, useAsyncSearch } from '../../../hooks/useAsyncSearch'; import { onTabPress } from '../../../utils/keyboard'; import { useKeyDown } from '../../../hooks/useKeyDown'; @@ -22,7 +22,7 @@ import { getViaServers } from '../../../plugins/via-servers'; type MentionAutoCompleteHandler = (roomAliasOrId: string, name: string) => void; const roomAliasFromQueryText = (mx: MatrixClient, text: string) => - validMxId(`#${text}`) + isRoomAlias(`#${text}`) ? `#${text}` : `#${text}${text.endsWith(':') ? '' : ':'}${getMxIdServer(mx.getUserId() ?? '')}`; diff --git a/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx b/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx index d6c0f30219..7a8012eb47 100644 --- a/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx +++ b/src/app/components/editor/autocomplete/UserMentionAutocomplete.tsx @@ -15,7 +15,7 @@ import { import { onTabPress } from '../../../utils/keyboard'; import { createMentionElement, moveCursor, replaceWithElement } from '../utils'; import { useKeyDown } from '../../../hooks/useKeyDown'; -import { getMxIdLocalPart, getMxIdServer, validMxId } from '../../../utils/matrix'; +import { getMxIdLocalPart, getMxIdServer, isUserId } from '../../../utils/matrix'; import { getMemberDisplayName, getMemberSearchStr } from '../../../utils/room'; import { UserAvatar } from '../../user-avatar'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; @@ -24,7 +24,7 @@ import { Membership } from '../../../../types/matrix/room'; type MentionAutoCompleteHandler = (userId: string, name: string) => void; const userIdFromQueryText = (mx: MatrixClient, text: string) => - validMxId(`@${text}`) + isUserId(`@${text}`) ? `@${text}` : `@${text}${text.endsWith(':') ? '' : ':'}${getMxIdServer(mx.getUserId() ?? '')}`; diff --git a/src/app/components/event-readers/EventReaders.tsx b/src/app/components/event-readers/EventReaders.tsx index de1416b6b1..c790023750 100644 --- a/src/app/components/event-readers/EventReaders.tsx +++ b/src/app/components/event-readers/EventReaders.tsx @@ -19,9 +19,11 @@ import { getMemberDisplayName } from '../../utils/room'; import { getMxIdLocalPart } from '../../utils/matrix'; import * as css from './EventReaders.css'; import { useMatrixClient } from '../../hooks/useMatrixClient'; -import { openProfileViewer } from '../../../client/action/navigation'; import { UserAvatar } from '../user-avatar'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; +import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile'; +import { useSpaceOptionally } from '../../hooks/useSpace'; +import { getMouseEventCords } from '../../utils/dom'; export type EventReadersProps = { room: Room; @@ -33,6 +35,8 @@ export const EventReaders = as<'div', EventReadersProps>( const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const latestEventReaders = useRoomEventReaders(room, eventId); + const openProfile = useOpenUserRoomProfile(); + const space = useSpaceOptionally(); const getName = (userId: string) => getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId; @@ -57,19 +61,32 @@ export const EventReaders = as<'div', EventReadersProps>( {latestEventReaders.map((readerId) => { const name = getName(readerId); - const avatarMxcUrl = room - .getMember(readerId) - ?.getMxcAvatarUrl(); - const avatarUrl = avatarMxcUrl ? mx.mxcUrlToHttp(avatarMxcUrl, 100, 100, 'crop', undefined, false, useAuthentication) : undefined; + const avatarMxcUrl = room.getMember(readerId)?.getMxcAvatarUrl(); + const avatarUrl = avatarMxcUrl + ? mx.mxcUrlToHttp( + avatarMxcUrl, + 100, + 100, + 'crop', + undefined, + false, + useAuthentication + ) + : undefined; return ( { - requestClose(); - openProfileViewer(readerId, room.roomId); + onClick={(event) => { + openProfile( + room.roomId, + space?.roomId, + readerId, + getMouseEventCords(event.nativeEvent), + 'Bottom' + ); }} before={ diff --git a/src/app/components/image-pack-view/RoomImagePack.tsx b/src/app/components/image-pack-view/RoomImagePack.tsx index 9dd45c1f3c..92b4ff218b 100644 --- a/src/app/components/image-pack-view/RoomImagePack.tsx +++ b/src/app/components/image-pack-view/RoomImagePack.tsx @@ -1,12 +1,14 @@ import React, { useCallback, useMemo } from 'react'; import { Room } from 'matrix-js-sdk'; -import { usePowerLevels, usePowerLevelsAPI } from '../../hooks/usePowerLevels'; +import { usePowerLevels } from '../../hooks/usePowerLevels'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { ImagePackContent } from './ImagePackContent'; import { ImagePack, PackContent } from '../../plugins/custom-emoji'; import { StateEvent } from '../../../types/matrix/room'; import { useRoomImagePack } from '../../hooks/useImagePacks'; import { randomStr } from '../../utils/common'; +import { useRoomPermissions } from '../../hooks/useRoomPermissions'; +import { useRoomCreators } from '../../hooks/useRoomCreators'; type RoomImagePackProps = { room: Room; @@ -17,9 +19,10 @@ export function RoomImagePack({ room, stateKey }: RoomImagePackProps) { const mx = useMatrixClient(); const userId = mx.getUserId()!; const powerLevels = usePowerLevels(room); + const creators = useRoomCreators(room); - const { getPowerLevel, canSendStateEvent } = usePowerLevelsAPI(powerLevels); - const canEditImagePack = canSendStateEvent(StateEvent.PoniesRoomEmotes, getPowerLevel(userId)); + const permissions = useRoomPermissions(creators, powerLevels); + const canEditImagePack = permissions.stateEvent(StateEvent.PoniesRoomEmotes, userId); const fallbackPack = useMemo(() => { const fakePackId = randomStr(4); diff --git a/src/app/components/invite-user-prompt/InviteUserPrompt.tsx b/src/app/components/invite-user-prompt/InviteUserPrompt.tsx new file mode 100644 index 0000000000..ddac0576ce --- /dev/null +++ b/src/app/components/invite-user-prompt/InviteUserPrompt.tsx @@ -0,0 +1,291 @@ +import React, { + ChangeEventHandler, + FormEventHandler, + KeyboardEventHandler, + useCallback, + useMemo, + useRef, + useState, +} from 'react'; +import { + Overlay, + OverlayBackdrop, + OverlayCenter, + Box, + Header, + config, + Text, + IconButton, + Icon, + Icons, + Input, + Button, + Spinner, + color, + TextArea, + Dialog, + Menu, + toRem, + Scroll, + MenuItem, +} from 'folds'; +import { Room } from 'matrix-js-sdk'; +import { isKeyHotkey } from 'is-hotkey'; +import FocusTrap from 'focus-trap-react'; +import { stopPropagation } from '../../utils/keyboard'; +import { useDirectUsers } from '../../hooks/useDirectUsers'; +import { getMxIdLocalPart, getMxIdServer, isUserId } from '../../utils/matrix'; +import { Membership } from '../../../types/matrix/room'; +import { useAsyncSearch, UseAsyncSearchOptions } from '../../hooks/useAsyncSearch'; +import { highlightText, makeHighlightRegex } from '../../plugins/react-custom-html-parser'; +import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; +import { useMatrixClient } from '../../hooks/useMatrixClient'; +import { BreakWord } from '../../styles/Text.css'; +import { useAlive } from '../../hooks/useAlive'; + +const SEARCH_OPTIONS: UseAsyncSearchOptions = { + limit: 1000, + matchOptions: { + contain: true, + }, +}; +const getUserIdString = (userId: string) => getMxIdLocalPart(userId) ?? userId; + +type InviteUserProps = { + room: Room; + requestClose: () => void; +}; +export function InviteUserPrompt({ room, requestClose }: InviteUserProps) { + const mx = useMatrixClient(); + const alive = useAlive(); + + const inputRef = useRef(null); + const directUsers = useDirectUsers(); + const [validUserId, setValidUserId] = useState(); + + const filteredUsers = useMemo( + () => + directUsers.filter((userId) => { + const membership = room.getMember(userId)?.membership; + return membership !== Membership.Join; + }), + [directUsers, room] + ); + const [result, search, resetSearch] = useAsyncSearch( + filteredUsers, + getUserIdString, + SEARCH_OPTIONS + ); + const queryHighlighRegex = result?.query + ? makeHighlightRegex(result.query.split(' ')) + : undefined; + + const [inviteState, invite] = useAsyncCallback( + useCallback( + async (userId, reason) => { + await mx.invite(room.roomId, userId, reason); + }, + [mx, room] + ) + ); + + const inviting = inviteState.status === AsyncStatus.Loading; + + const handleReset = () => { + if (inputRef.current) inputRef.current.value = ''; + setValidUserId(undefined); + resetSearch(); + }; + + const handleSubmit: FormEventHandler = (evt) => { + evt.preventDefault(); + const target = evt.target as HTMLFormElement | undefined; + + if (inviting || !validUserId) return; + + const reasonInput = target?.reasonInput as HTMLTextAreaElement | undefined; + const reason = reasonInput?.value.trim(); + + invite(validUserId, reason || undefined).then(() => { + if (alive()) { + handleReset(); + if (reasonInput) reasonInput.value = ''; + } + }); + }; + + const handleSearchChange: ChangeEventHandler = (evt) => { + const value = evt.currentTarget.value.trim(); + if (isUserId(value)) { + setValidUserId(value); + } else { + setValidUserId(undefined); + const term = getMxIdLocalPart(value) ?? (value.startsWith('@') ? value.slice(1) : value); + if (term) { + search(term); + } else { + resetSearch(); + } + } + }; + + const handleUserId = (userId: string) => { + if (inputRef.current) { + inputRef.current.value = userId; + setValidUserId(userId); + resetSearch(); + inputRef.current.focus(); + } + }; + + const handleKeyDown: KeyboardEventHandler = (evt) => { + if (isKeyHotkey('escape', evt)) { + resetSearch(); + return; + } + if (isKeyHotkey('tab', evt) && result && result.items.length > 0) { + evt.preventDefault(); + const userId = result.items[0]; + handleUserId(userId); + } + }; + + return ( + }> + + inputRef.current, + clickOutsideDeactivates: true, + onDeactivate: requestClose, + escapeDeactivates: stopPropagation, + }} + > + + +
+ + + Invite + + + + + + + +
+ + + User ID +
+ + {result && result.items.length > 0 && ( + isKeyHotkey('arrowdown', evt), + isKeyBackward: (evt: KeyboardEvent) => isKeyHotkey('arrowup', evt), + escapeDeactivates: stopPropagation, + }} + > + + + +
+ {result.items.map((userId) => { + const username = `${getMxIdLocalPart(userId)}`; + const userServer = getMxIdServer(userId); + + return ( + handleUserId(userId)} + after={ + + {userServer} + + } + disabled={inviting} + > + + + + {queryHighlighRegex + ? highlightText(queryHighlighRegex, [ + username ?? userId, + ]) + : username} + + + + + ); + })} +
+
+
+
+
+ )} +
+
+ + Reason (Optional) +