Compare commits

..

5 Commits

Author SHA1 Message Date
Ajay Bura 1e1c487642 fix success and critical color hover+active color 2026-03-12 13:29:14 +05:30
Ajay Bura 43e3a9c290 add bluish tone to text color 2026-03-12 12:50:25 +05:30
Ajay Bura 66a18bdc10 make call room name transparent 2026-03-12 12:38:32 +05:30
Ajay Bura 649c30bcc7 change gradient direction toward top 2026-03-12 12:33:54 +05:30
Ajay Bura e039299e83 Gradient Theme - Moonlight 2026-03-12 12:16:03 +05:30
80 changed files with 675 additions and 7841 deletions
@@ -1,127 +0,0 @@
labels: ["needs-confirmation"]
body:
- type: markdown #add faqs in future
attributes:
value: |
> [!IMPORTANT]
> Please read through [the Discussion rules](https://github.com/cinnyapp/cinny/discussions/2653) and check for both existing [Discussions](https://github.com/cinnyapp/cinny/discussions?discussions_q=) and [Issues](https://github.com/cinnyapp/cinny/issues?q=sort%3Areactions-desc) prior to opening a new Discussion.
- type: markdown
attributes:
value: "# Issue Details"
- type: textarea
attributes:
label: Issue Description
description: |
Provide a detailed description of the issue. Include relevant information, such as:
- The feature or configuration option you encounter the issue with.
- Screenshots, screen recordings, or other supporting media (as needed).
- If this is a regression of an existing issue that was closed or resolved, please include the previous item reference (Discussion, Issue, PR, commit) in your description.
placeholder: |
When I try to send a message in a room, the message doesn't appear in the timeline.
OR
The application crashes when I click on the settings button.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: |
Describe how you expect Cinny to behave in this situation.
placeholder: |
I expected the message to appear in the room timeline immediately after sending.
OR
The settings panel should open smoothly without any crashes.
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: |
Describe how Cinny actually behaves in this situation. If it is not immediately obvious how the actual behavior differs from the expected behavior described above, please be sure to mention the deviation specifically.
placeholder: |
The application freezes for 3 seconds and then shows a white screen.
validations:
required: true
- type: textarea
attributes:
label: Reproduction Steps
description: |
Provide a detailed set of step-by-step instructions for reproducing this issue.
placeholder: |
1. Open Cinny and log in to my account
2. Navigate to the #general room
3. Type a message in the message box
4. Press Enter to send
5. Notice that the message doesn't appear in the timeline
validations:
required: true
- type: textarea
attributes:
label: Environement
description: |
Please provide information about your environment. Include the following:
- OS:
- Browser:
- Cinny Web Version: (app.cinny.in or self hosted)
- Cinny desktop Version: (appimage or deb or flatpak)
- Matrix Homeserver:
placeholder: |
- OS: Windows 11
- Browser: Chrome 120.0.6099.109
- Cinny Web Version: 3.2.0 (app.cinny.in or self hosted)
- Cinny desktop Version: 3.2.0 (appimage or deb or flatpak)
- Matrix Homeserver: matrix.org (Synapse 1.97.0)
render: text
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant Logs
description: |
If applicable, add browser console logs to help explain your problem.
**To get browser console logs:**
- Chrome/Edge: Press F12 → Console tab
- Firefox: Press F12 → Console tab
- Safari: Develop → Show Web Inspector → Console
Please wrap large log outputs in code blocks with triple backticks (```).
placeholder: |
```
Error: Failed to send message
at MessageComposer.sendMessage (composer.js:245)
at HTMLButtonElement.onClick (composer.js:189)
TypeError: Cannot read property 'content' of undefined
at RoomTimeline.render (timeline.js:567)
```
render: shell
validations:
required: false
- type: textarea
attributes:
label: Additional context
description: |
Add any other context about the problem here (e.g., when did this start happening, does it happen on different homeservers, etc.)
placeholder: |
- This started happening after I updated to version 3.2.0
- It only happens in encrypted rooms, not in public rooms
- I've tried on both Firefox and Chrome with the same result
- It works fine on my phone using the same account
- This happens on all homeservers I've tested (matrix.org, mozilla.org)
validations:
required: false
- type: markdown
attributes:
value: |
# User Acknowledgements
> [!TIP]
> Use these links to review the existing Cinny [Discussions](https://github.com/cinnyapp/cinny/discussions?discussions_q=) and [Issues](https://github.com/cinnyapp/cinny/issues?q=sort%3Areactions-desc).
- type: checkboxes #add faqs in future
attributes:
label: "I acknowledge that:"
options:
- label: I have searched the Cinny repository (both open and closed Discussions and Issues) and confirm this is not a duplicate of an existing issue or discussion.
required: true
- label: I have checked the "Preview" tab on all text fields to ensure that everything looks right, and have wrapped all configuration and code in code blocks with a group of three backticks (` ``` `) on separate lines.
required: true
+57
View File
@@ -0,0 +1,57 @@
name: 🐞 Bug Report
description: Report a bug
body:
- type: markdown
attributes:
value: |
## First of all
1. Please search for [existing issues](https://github.com/ajbura/cinny/issues?q=is%3Aissue) about this problem first.
2. Make sure Cinny is up to date.
3. Make sure it's an issue with Cinny and not something else you are using.
4. Remember to be friendly.
- type: textarea
id: description
attributes:
label: Describe the bug
description: A clear description of what the bug is. Include screenshots if applicable.
placeholder: Bug description
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: Steps to reproduce the behavior.
placeholder: |
1. Go to ...
2. Click on ...
3. See error
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: A clear description of what you expected to happen.
- type: textarea
id: info
attributes:
label: Platform and versions
description: "Provide OS, browser and Cinny version with your Homeserver."
placeholder: |
1. OS: [e.g. Windows 10, MacOS]
2. Browser: [e.g. chrome 99.5, firefox 97.2]
3. Cinny version: [e.g. 1.8.1 (app.cinny.in)]
4. Matrix homeserver: [e.g. matrix.org]
render: shell
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context about the problem here.
+3 -4
View File
@@ -1,5 +1,4 @@
blank_issues_enabled: false
contact_links: contact_links:
- name: Features, Bug Reports, Questions - name: 💬 Matrix Chat
url: https://github.com/cinnyapp/cinny/discussions/new/choose url: https://matrix.to/#/#cinny:matrix.org
about: Our preferred starting point if you have any questions or suggestions about features or behavior. about: Ask questions and talk to other Cinny users and the maintainers
@@ -0,0 +1,33 @@
name: 💡 Feature Request
description: Suggest an idea
body:
- type: textarea
id: problem
attributes:
label: Describe the problem
description: A clear description of the problem this feature would solve
placeholder: "I'm always frustrated when..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: "Describe the solution you'd like"
description: A clear description of what change you would like
placeholder: "I would like to..."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: "Any alternative solutions you've considered"
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context about the problem here.
-9
View File
@@ -1,9 +0,0 @@
---
name: Pre-Discussed and Approved Topics
about: |-
Only for topics already discussed and approved in the GitHub Discussions section.
---
**DO NOT OPEN A NEW ISSUE. PLEASE USE THE DISCUSSIONS SECTION.**
**I DIDN'T READ THE ABOVE LINE. PLEASE CLOSE THIS ISSUE.**
+22
View File
@@ -0,0 +1,22 @@
<!-- Please read https://github.com/ajbura/cinny/blob/dev/CONTRIBUTING.md before submitting your pull request -->
### Description
<!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. -->
Fixes #
#### Type of change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
### Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
+3
View File
@@ -0,0 +1,3 @@
# Reporting a Vulnerability
**If you've found a security vulnerability, please report it to cinnyapp@gmail.com**
+1 -9
View File
@@ -3,20 +3,12 @@
"extends": [ "extends": [
"config:recommended", "config:recommended",
":dependencyDashboardApproval", ":dependencyDashboardApproval",
":semanticCommits", ":semanticCommits"
"group:monorepos"
], ],
"labels": ["Dependencies"], "labels": ["Dependencies"],
"rebaseWhen": "conflicted",
"packageRules": [ "packageRules": [
{ {
"matchUpdateTypes": ["lockFileMaintenance"] "matchUpdateTypes": ["lockFileMaintenance"]
},
{
"matchPackageNames": ["slate", "slate-dom", "slate-history", "slate-react"]
},
{
"matchPackageNames": ["linkifyjs", "linkify-react"]
} }
], ],
"lockFileMaintenance": { "lockFileMaintenance": {
+3 -3
View File
@@ -14,7 +14,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup node - name: Setup node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version-file: ".node-version" node-version-file: ".node-version"
package-manager-cache: false package-manager-cache: false
@@ -25,7 +25,7 @@ jobs:
NODE_OPTIONS: '--max_old_space_size=4096' NODE_OPTIONS: '--max_old_space_size=4096'
run: npm run build run: npm run build
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with: with:
name: preview name: preview
path: dist path: dist
@@ -33,7 +33,7 @@ jobs:
- name: Save pr number - name: Save pr number
run: echo ${PR_NUMBER} > ./pr.txt run: echo ${PR_NUMBER} > ./pr.txt
- name: Upload pr number - name: Upload pr number
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with: with:
name: pr name: pr
path: ./pr.txt path: ./pr.txt
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'success' }} if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps: steps:
- name: Download pr number - name: Download pr number
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 uses: dawidd6/action-download-artifact@2536c51d3d126276eb39f74d6bc9c72ac6ef30d3 # v16
with: with:
workflow: ${{ github.event.workflow.id }} workflow: ${{ github.event.workflow.id }}
run_id: ${{ github.event.workflow_run.id }} run_id: ${{ github.event.workflow_run.id }}
@@ -25,7 +25,7 @@ jobs:
id: pr id: pr
run: echo "id=$(<pr.txt)" >> $GITHUB_OUTPUT run: echo "id=$(<pr.txt)" >> $GITHUB_OUTPUT
- name: Download artifact - name: Download artifact
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 uses: dawidd6/action-download-artifact@2536c51d3d126276eb39f74d6bc9c72ac6ef30d3 # v16
with: with:
workflow: ${{ github.event.workflow.id }} workflow: ${{ github.event.workflow.id }}
run_id: ${{ github.event.workflow_run.id }} run_id: ${{ github.event.workflow_run.id }}
+3 -3
View File
@@ -26,7 +26,7 @@ jobs:
- name: Login to Docker Hub #Do not update this action from a outside PR - name: Login to Docker Hub #Do not update this action from a outside PR
if: github.event.pull_request.head.repo.fork == false if: github.event.pull_request.head.repo.fork == false
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
@@ -34,7 +34,7 @@ jobs:
- name: Login to the Github Container registry #Do not update this action from a outside PR - name: Login to the Github Container registry #Do not update this action from a outside PR
if: github.event.pull_request.head.repo.fork == false if: github.event.pull_request.head.repo.fork == false
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -50,7 +50,7 @@ jobs:
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
- name: Build Docker image (no push) - name: Build Docker image (no push)
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with: with:
context: . context: .
platforms: linux/amd64 platforms: linux/amd64
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup node - name: Setup node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version-file: ".node-version" node-version-file: ".node-version"
package-manager-cache: false package-manager-cache: false
+12 -34
View File
@@ -1,39 +1,23 @@
name: Production deploy name: Production deploy
on: on:
workflow_dispatch: release:
types: [published]
jobs: jobs:
deploy-and-tarball: deploy-and-tarball:
name: Netlify deploy and tarball name: Netlify deploy and tarball
outputs:
version: ${{ steps.vars.outputs.tag }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup node - name: Setup node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with: with:
node-version-file: ".node-version" node-version-file: ".node-version"
package-manager-cache: false package-manager-cache: false
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Run semantic release
run: npm run semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_COMMITTER_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
GIT_COMMITTER_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
- name: Get version from tag
id: vars
run: |
TAG=$(git describe --tags --abbrev=0)
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Build app - name: Build app
env: env:
NODE_OPTIONS: '--max_old_space_size=4096' NODE_OPTIONS: '--max_old_space_size=4096'
@@ -42,7 +26,7 @@ jobs:
uses: nwtgck/actions-netlify@4cbaf4c08f1a7bfa537d6113472ef4424e4eb654 # v3.0.0 uses: nwtgck/actions-netlify@4cbaf4c08f1a7bfa537d6113472ef4424e4eb654 # v3.0.0
with: with:
publish-dir: dist publish-dir: dist
deploy-message: 'Prod deploy ${{ steps.vars.outputs.tag }}' deploy-message: 'Prod deploy ${{ github.ref_name }}'
enable-commit-comment: false enable-commit-comment: false
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
production-deploy: true production-deploy: true
@@ -52,6 +36,9 @@ jobs:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_APP }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_APP }}
timeout-minutes: 1 timeout-minutes: 1
- name: Get version from tag
id: vars
run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
- name: Create tar.gz - name: Create tar.gz
run: tar -czvf cinny-${{ steps.vars.outputs.tag }}.tar.gz dist run: tar -czvf cinny-${{ steps.vars.outputs.tag }}.tar.gz dist
- name: Sign tar.gz - name: Sign tar.gz
@@ -65,18 +52,14 @@ jobs:
gpg --export | xxd -p 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 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 - name: Upload tagged release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
with: with:
tag_name: ${{ steps.vars.outputs.tag }}
files: | files: |
cinny-${{ steps.vars.outputs.tag }}.tar.gz cinny-${{ steps.vars.outputs.tag }}.tar.gz
cinny-${{ steps.vars.outputs.tag }}.tar.gz.asc cinny-${{ steps.vars.outputs.tag }}.tar.gz.asc
publish-image: publish-image:
name: Push Docker image to Docker Hub, GHCR name: Push Docker image to Docker Hub, GHCR
needs: deploy-and-tarball
env:
VERSION: ${{ needs.deploy-and-tarball.outputs.version }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -84,19 +67,17 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Login to Docker Hub #Do not update this action from a outside PR - name: Login to Docker Hub #Do not update this action from a outside PR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to the Github Container registry #Do not update this action from a outside PR - name: Login to the Github Container registry #Do not update this action from a outside PR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -108,14 +89,11 @@ jobs:
images: | images: |
${{ secrets.DOCKER_USERNAME }}/cinny ${{ secrets.DOCKER_USERNAME }}/cinny
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ env.VERSION }}
type=raw,value=latest
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
-3
View File
@@ -1,3 +0,0 @@
# These are commented until we enable lint and typecheck
# npx tsc -p tsconfig.json --noEmit
# npx lint-staged
+1 -1
View File
@@ -18,7 +18,7 @@ Bug reports and feature suggestions must use descriptive and concise titles and
## Pull requests ## Pull requests
> ### Legal Notice > ### Legal Notice
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license. You will also be asked to [sign the CLA](https://github.com/cinnyapp/cla) upon submiting your pull request. > When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
**NOTE: If you want to add new features, please discuss with maintainers before coding or opening a pull request.** This is to ensure that we are on same track and following our roadmap. **NOTE: If you want to add new features, please discuss with maintainers before coding or opening a pull request.** This is to ensure that we are on same track and following our roadmap.
+1 -1
View File
@@ -11,7 +11,7 @@ RUN npm run build
## App ## App
FROM nginx:1.29.8-alpine FROM nginx:1.29.5-alpine
COPY --from=builder /src/dist /app COPY --from=builder /src/dist /app
COPY --from=builder /src/docker-nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /src/docker-nginx.conf /etc/nginx/conf.d/default.conf
-4
View File
@@ -16,10 +16,6 @@ A Matrix client focusing primarily on simple, elegant and secure interface. The
- [Roadmap](https://github.com/orgs/cinnyapp/projects/1) - [Roadmap](https://github.com/orgs/cinnyapp/projects/1)
- [Contributing](./CONTRIBUTING.md) - [Contributing](./CONTRIBUTING.md)
> [!IMPORTANT]
We are currently in the [process of replacing the matrix-js-sdk](https://github.com/cinnyapp/cinny/issues/257#issuecomment-3714406704) with our own SDK. As a result, we will not be accepting any pull requests until further notice.
Thank you for your understanding.
<img align="center" src="https://raw.githubusercontent.com/cinnyapp/cinny-site/main/assets/preview2-light.png" height="380"> <img align="center" src="https://raw.githubusercontent.com/cinnyapp/cinny-site/main/assets/preview2-light.png" height="380">
## Getting started ## Getting started
+2 -3
View File
@@ -11,8 +11,7 @@
"#space:unredacted.org", "#space:unredacted.org",
"#science-space:matrix.org", "#science-space:matrix.org",
"#libregaming-games:tchncs.de", "#libregaming-games:tchncs.de",
"#mathematics-on:matrix.org", "#mathematics-on:matrix.org"
"#stickers-and-emojis:tastytea.de"
], ],
"rooms": [ "rooms": [
"#cinny:matrix.org", "#cinny:matrix.org",
@@ -22,7 +21,7 @@
"#PrivSec.dev:arcticfoxes.net", "#PrivSec.dev:arcticfoxes.net",
"#disroot:aria-net.org" "#disroot:aria-net.org"
], ],
"servers": ["matrixrooms.info", "matrix.org", "mozilla.org", "unredacted.org"] "servers": ["matrix.org", "mozilla.org", "unredacted.org"]
}, },
"hashRouter": { "hashRouter": {
+66 -7091
View File
File diff suppressed because it is too large Load Diff
+7 -54
View File
@@ -1,6 +1,6 @@
{ {
"name": "cinny", "name": "cinny",
"version": "4.11.1", "version": "4.11.0",
"description": "Yet another matrix client", "description": "Yet another matrix client",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@@ -11,52 +11,11 @@
"start": "vite", "start": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"lint": "npm run check:eslint && npm run check:prettier", "lint": "yarn check:eslint && yarn check:prettier",
"check:eslint": "eslint src/*", "check:eslint": "eslint src/*",
"check:prettier": "prettier --check .", "check:prettier": "prettier --check .",
"fix:prettier": "prettier --write .", "fix:prettier": "prettier --write .",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit"
"prepare": "husky install",
"commit": "git-cz",
"semantic-release": "semantic-release"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": "eslint",
"*": "prettier --ignore-unknown --write"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"release": {
"branches": [
"dev"
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@semantic-release/exec",
{
"prepareCmd": "node scripts/update-version.js ${nextRelease.version}"
}
],
[
"@semantic-release/git",
{
"assets": [
"package.json",
"package-lock.json",
"src/app/features/settings/about/About.tsx",
"src/app/pages/auth/AuthFooter.tsx",
"src/app/pages/client/WelcomePage.tsx"
],
"message": "chore(release): ${nextRelease.version} [skip ci]"
}
],
"@semantic-release/github"
]
}, },
"keywords": [], "keywords": [],
"author": "Ajay Bura", "author": "Ajay Bura",
@@ -112,10 +71,10 @@
"react-range": "1.8.14", "react-range": "1.8.14",
"react-router-dom": "6.30.3", "react-router-dom": "6.30.3",
"sanitize-html": "2.12.1", "sanitize-html": "2.12.1",
"slate": "0.123.0", "slate": "0.112.0",
"slate-dom": "0.123.0", "slate-dom": "0.112.2",
"slate-history": "0.113.1", "slate-history": "0.110.3",
"slate-react": "0.123.0", "slate-react": "0.112.1",
"ua-parser-js": "1.0.35" "ua-parser-js": "1.0.35"
}, },
"devDependencies": { "devDependencies": {
@@ -123,8 +82,6 @@
"@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rollup/plugin-inject": "5.0.3", "@rollup/plugin-inject": "5.0.3",
"@rollup/plugin-wasm": "6.1.1", "@rollup/plugin-wasm": "6.1.1",
"@semantic-release/exec": "7.1.0",
"@semantic-release/git": "10.0.1",
"@types/chroma-js": "3.1.1", "@types/chroma-js": "3.1.1",
"@types/file-saver": "2.0.5", "@types/file-saver": "2.0.5",
"@types/is-hotkey": "0.1.10", "@types/is-hotkey": "0.1.10",
@@ -139,7 +96,6 @@
"@typescript-eslint/parser": "5.46.1", "@typescript-eslint/parser": "5.46.1",
"@vitejs/plugin-react": "4.2.0", "@vitejs/plugin-react": "4.2.0",
"buffer": "6.0.3", "buffer": "6.0.3",
"cz-conventional-changelog": "3.3.0",
"eslint": "8.29.0", "eslint": "8.29.0",
"eslint-config-airbnb": "19.0.4", "eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
@@ -147,10 +103,7 @@
"eslint-plugin-jsx-a11y": "6.6.1", "eslint-plugin-jsx-a11y": "6.6.1",
"eslint-plugin-react": "7.31.11", "eslint-plugin-react": "7.31.11",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"husky": "9.1.7",
"lint-staged": "16.3.2",
"prettier": "2.8.1", "prettier": "2.8.1",
"semantic-release": "25.0.3",
"typescript": "4.9.4", "typescript": "4.9.4",
"vite": "5.4.19", "vite": "5.4.19",
"vite-plugin-pwa": "0.20.5", "vite-plugin-pwa": "0.20.5",
-48
View File
@@ -1,48 +0,0 @@
import fs from "fs";
import path from "path";
import { execSync } from "child_process";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const version = process.argv[2];
if (!version) {
console.error("Version argument missing");
process.exit(1);
}
const root = path.resolve(__dirname, "..");
const newVersionTag = `v${version}`;
// Update package.json + package-lock.json safely
execSync(`npm version ${version} --no-git-tag-version`, {
cwd: root,
stdio: "inherit",
});
console.log(`Updated package.json and package-lock.json → ${version}`);
// Update UI version references
const files = [
"src/app/features/settings/about/About.tsx",
"src/app/pages/auth/AuthFooter.tsx",
"src/app/pages/client/WelcomePage.tsx",
];
files.forEach((filePath) => {
const absPath = path.join(root, filePath);
if (!fs.existsSync(absPath)) {
console.warn(`File not found: ${filePath}`);
return;
}
const content = fs.readFileSync(absPath, "utf8");
const updated = content.replace(/v\d+\.\d+\.\d+/g, newVersionTag);
fs.writeFileSync(absPath, updated);
console.log(`Updated ${filePath}${newVersionTag}`);
});
+1
View File
@@ -1,5 +1,6 @@
import React, { ReactNode, useCallback, useRef } from 'react'; import React, { ReactNode, useCallback, useRef } from 'react';
import { useAtomValue, useSetAtom } from 'jotai'; import { useAtomValue, useSetAtom } from 'jotai';
import { config } from 'folds';
import { import {
CallEmbedContextProvider, CallEmbedContextProvider,
CallEmbedRefContextProvider, CallEmbedRefContextProvider,
-45
View File
@@ -1,45 +0,0 @@
import FocusTrap from 'focus-trap-react';
import { as, Modal, Overlay, OverlayBackdrop, OverlayCenter } from 'folds';
import React, { ReactNode } from 'react';
import { ModalWide } from '../styles/Modal.css';
import { stopPropagation } from '../utils/keyboard';
export type RenderViewerProps = {
src: string;
alt: string;
requestClose: () => void;
};
type ImageOverlayProps = RenderViewerProps & {
viewer: boolean;
renderViewer: (props: RenderViewerProps) => ReactNode;
};
export const ImageOverlay = as<'div', ImageOverlayProps>(
({ src, alt, viewer, requestClose, renderViewer, ...props }, ref) => (
<Overlay {...props} ref={ref} open={viewer} backdrop={<OverlayBackdrop />}>
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: () => requestClose(),
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Modal
className={ModalWide}
size="500"
onContextMenu={(evt: any) => evt.stopPropagation()}
>
{renderViewer({
src,
alt,
requestClose,
})}
</Modal>
</FocusTrap>
</OverlayCenter>
</Overlay>
)
);
+26 -88
View File
@@ -157,12 +157,10 @@ const getInlineElement = (node: ChildNode, processText: ProcessTextCallback): In
return children; return children;
} }
const children = node.childNodes.flatMap((child) => getInlineElement(child, processText)); return node.childNodes.flatMap((child) => getInlineElement(child, processText));
if (children.length === 0) return [{ text: '' }];
return children;
} }
return [{ text: '' }]; return [];
}; };
const parseBlockquoteNode = ( const parseBlockquoteNode = (
@@ -193,7 +191,7 @@ const parseBlockquoteNode = (
if (child.name === 'p') { if (child.name === 'p') {
appendLine(); appendLine();
quoteLines.push(getInlineElement(child, processText)); quoteLines.push(child.children.flatMap((c) => getInlineElement(c, processText)));
return; return;
} }
@@ -230,13 +228,9 @@ const parseCodeBlockNode = (node: Element): CodeBlockElement[] | ParagraphElemen
children: [{ text }], children: [{ text }],
})); }));
const childCode = node.children[0]; const childCode = node.children[0];
const attribs = const className =
isTag(childCode) && childCode.tagName === 'code' ? childCode.attribs : undefined; isTag(childCode) && childCode.tagName === 'code' ? childCode.attribs.class ?? '' : '';
const languageClass = attribs?.class; const prefix = { text: `${mdSequence}${className.replace('language-', '')}` };
const customLabel = attribs?.['data-label'];
const prefix = {
text: `${mdSequence}${customLabel ?? languageClass?.replace('language-', '') ?? ''}`,
};
const suffix = { text: mdSequence }; const suffix = { text: mdSequence };
return [ return [
{ type: BlockType.Paragraph, children: [prefix] }, { type: BlockType.Paragraph, children: [prefix] },
@@ -255,67 +249,10 @@ const parseCodeBlockNode = (node: Element): CodeBlockElement[] | ParagraphElemen
}, },
]; ];
}; };
const parseListNode = (
const parseListMarkdown = (
node: Element, node: Element,
processText: ProcessTextCallback, processText: ProcessTextCallback
depth = 0 ): OrderedListElement[] | UnorderedListElement[] | ParagraphElement[] => {
): ParagraphElement[] => {
const md = isTag(node) && node.name === 'ul' ? '*' : '-';
const prefix = node.attribs['data-md'] ?? md;
const [starOrHyphen] = prefix.match(/^\*|-$/) ?? [];
const [digitOrChar] = prefix.match(/^[\da-zA-Z]/) ?? [];
const digit = digitOrChar ? parseInt(digitOrChar, 10) : undefined;
const lines: ParagraphElement[] = [];
let lineNo = digit === undefined || Number.isNaN(digit) ? digitOrChar ?? 1 : digit;
const pushLine = (line: InlineElement[]) => {
lines.push({
type: BlockType.Paragraph,
children: [
{
text: `${Array(depth + 1).join(' ')}${starOrHyphen ? `${starOrHyphen} ` : `${lineNo}. `}`,
},
...line,
],
});
if (typeof lineNo === 'string') {
lineNo = String.fromCharCode(lineNo.charCodeAt(0) + 1);
} else {
lineNo += 1;
}
};
node.children.forEach((child) => {
if (isText(child)) {
pushLine([{ text: processText(child.data) }]);
return;
}
if (isTag(child)) {
if (child.name === 'ul' || child.name === 'ol') {
lines.push(...parseListMarkdown(child, processText, depth + 1));
return;
}
if (child.name === 'li') {
child.children.forEach((c) => {
if (isTag(c) && (c.name === 'ul' || c.name === 'ol')) {
lines.push(...parseListMarkdown(c, processText, depth + 1));
return;
}
pushLine(getInlineElement(c, processText));
});
return;
}
}
pushLine(getInlineElement(child, processText));
});
return lines;
};
const parseListLines = (children: ChildNode[], processText: ProcessTextCallback) => {
const listLines: Array<InlineElement[]> = []; const listLines: Array<InlineElement[]> = [];
let lineHolder: InlineElement[] = []; let lineHolder: InlineElement[] = [];
@@ -326,7 +263,7 @@ const parseListLines = (children: ChildNode[], processText: ProcessTextCallback)
lineHolder = []; lineHolder = [];
}; };
children.forEach((child) => { node.children.forEach((child) => {
if (isText(child)) { if (isText(child)) {
lineHolder.push({ text: processText(child.data) }); lineHolder.push({ text: processText(child.data) });
return; return;
@@ -340,7 +277,7 @@ const parseListLines = (children: ChildNode[], processText: ProcessTextCallback)
if (child.name === 'li') { if (child.name === 'li') {
appendLine(); appendLine();
listLines.push(getInlineElement(child, processText)); listLines.push(child.children.flatMap((c) => getInlineElement(c, processText)));
return; return;
} }
@@ -349,23 +286,24 @@ const parseListLines = (children: ChildNode[], processText: ProcessTextCallback)
}); });
appendLine(); appendLine();
return listLines; const mdSequence = node.attribs['data-md'];
}; if (mdSequence !== undefined) {
const parseListNode = ( const prefix = mdSequence || '-';
node: Element, const [starOrHyphen] = prefix.match(/^\*|-$/) ?? [];
processText: ProcessTextCallback return listLines.map((lineChildren) => ({
): OrderedListElement[] | UnorderedListElement[] | ParagraphElement[] => { type: BlockType.Paragraph,
if (node.attribs['data-md'] !== undefined) { children: [
return parseListMarkdown(node, processText); { text: `${starOrHyphen ? `${starOrHyphen} ` : `${prefix}. `} ` },
...lineChildren,
],
}));
} }
const lines = parseListLines(node.childNodes, processText);
if (node.name === 'ol') { if (node.name === 'ol') {
return [ return [
{ {
type: BlockType.OrderedList, type: BlockType.OrderedList,
children: lines.map((lineChildren) => ({ children: listLines.map((lineChildren) => ({
type: BlockType.ListItem, type: BlockType.ListItem,
children: lineChildren, children: lineChildren,
})), })),
@@ -376,7 +314,7 @@ const parseListNode = (
return [ return [
{ {
type: BlockType.UnorderedList, type: BlockType.UnorderedList,
children: lines.map((lineChildren) => ({ children: listLines.map((lineChildren) => ({
type: BlockType.ListItem, type: BlockType.ListItem,
children: lineChildren, children: lineChildren,
})), })),
@@ -387,7 +325,7 @@ const parseHeadingNode = (
node: Element, node: Element,
processText: ProcessTextCallback processText: ProcessTextCallback
): HeadingElement | ParagraphElement => { ): HeadingElement | ParagraphElement => {
const children = getInlineElement(node, processText); const children = node.children.flatMap((child) => getInlineElement(child, processText));
const headingMatch = node.name.match(/^h([123456])$/); const headingMatch = node.name.match(/^h([123456])$/);
const [, g1AsLevel] = headingMatch ?? ['h3', '3']; const [, g1AsLevel] = headingMatch ?? ['h3', '3'];
@@ -450,7 +388,7 @@ export const domToEditorInput = (
appendLine(); appendLine();
children.push({ children.push({
type: BlockType.Paragraph, type: BlockType.Paragraph,
children: getInlineElement(node, processText), children: node.children.flatMap((child) => getInlineElement(child, processText)),
}); });
return; return;
} }
+2 -2
View File
@@ -11,7 +11,7 @@ import {
} from '../../plugins/markdown'; } from '../../plugins/markdown';
import { findAndReplace } from '../../utils/findAndReplace'; import { findAndReplace } from '../../utils/findAndReplace';
import { sanitizeForRegex } from '../../utils/regex'; import { sanitizeForRegex } from '../../utils/regex';
import { isUserId } from '../../utils/matrix'; import { getCanonicalAliasOrRoomId, isUserId } from '../../utils/matrix';
export type OutputOptions = { export type OutputOptions = {
allowTextFormatting?: boolean; allowTextFormatting?: boolean;
@@ -215,7 +215,7 @@ export const getMentions = (mx: MatrixClient, roomId: string, editor: Editor): M
if (node.name === '@room') { if (node.name === '@room') {
mentionData.room = true; mentionData.room = true;
} }
if (isUserId(node.id) && node.id !== mx.getUserId()) { if (isUserId(node.id) && node.id !== mx.getUserId()) {
mentionData.users.add(node.id); mentionData.users.add(node.id);
} }
-1
View File
@@ -49,7 +49,6 @@ const NavItemBase = style({
display: 'flex', display: 'flex',
justifyContent: 'start', justifyContent: 'start',
cursor: 'pointer', cursor: 'pointer',
backgroundColor: Container,
color: OnContainer, color: OnContainer,
outline: 'none', outline: 'none',
minHeight: toRem(36), minHeight: toRem(36),
+3 -3
View File
@@ -14,7 +14,7 @@ export function PageRoot({ nav, children }: PageRootProps) {
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
return ( return (
<Box grow="Yes" className={ContainerColor({ variant: 'Background' })}> <Box grow="Yes">
{nav} {nav}
{screenSize !== ScreenSize.Mobile && ( {screenSize !== ScreenSize.Mobile && (
<Line variant="Background" size="300" direction="Vertical" /> <Line variant="Background" size="300" direction="Vertical" />
@@ -79,11 +79,11 @@ export function PageNavContent({
); );
} }
export const Page = as<'div'>(({ className, ...props }, ref) => ( export const Page = as<'div', css.PageVariants>(({ className, transparent, ...props }, ref) => (
<Box <Box
grow="Yes" grow="Yes"
direction="Column" direction="Column"
className={classNames(ContainerColor({ variant: 'Surface' }), className)} className={classNames(css.Page({ transparent }), className)}
{...props} {...props}
ref={ref} ref={ref}
/> />
+14
View File
@@ -1,6 +1,7 @@
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
import { DefaultReset, color, config, toRem } from 'folds'; import { DefaultReset, color, config, toRem } from 'folds';
import { ContainerColor } from '../../styles/ContainerColor.css';
export const PageNav = recipe({ export const PageNav = recipe({
variants: { variants: {
@@ -59,6 +60,19 @@ export const PageNavContent = style({
paddingBottom: config.space.S700, paddingBottom: config.space.S700,
}); });
export const Page = recipe({
base: [ContainerColor({ variant: 'Surface' })],
variants: {
transparent: {
true: {
background: 'transparent',
},
},
},
});
export type PageVariants = RecipeVariants<typeof Page>;
export const PageHeader = recipe({ export const PageHeader = recipe({
base: { base: {
paddingLeft: config.space.S400, paddingLeft: config.space.S400,
@@ -1,13 +1,11 @@
import { createVar, style } from '@vanilla-extract/css'; import { createVar, style } from '@vanilla-extract/css';
import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
import { color, config, DefaultReset, Disabled, FocusOutline, toRem } from 'folds'; import { color, config, DefaultReset, Disabled, FocusOutline, toRem } from 'folds';
import { ContainerColor } from '../../styles/ContainerColor.css';
export const Sidebar = style([ export const Sidebar = style([
DefaultReset, DefaultReset,
{ {
width: toRem(66), width: toRem(66),
backgroundColor: color.Background.Container,
borderRight: `${config.borderWidth.B300} solid ${color.Background.ContainerLine}`, borderRight: `${config.borderWidth.B300} solid ${color.Background.ContainerLine}`,
display: 'flex', display: 'flex',
@@ -187,7 +185,6 @@ export type SidebarAvatarVariants = RecipeVariants<typeof SidebarAvatar>;
export const SidebarFolder = recipe({ export const SidebarFolder = recipe({
base: [ base: [
ContainerColor({ variant: 'Background' }),
{ {
padding: config.space.S100, padding: config.space.S100,
width: toRem(42), width: toRem(42),
@@ -3,7 +3,6 @@ import { color, config } from 'folds';
export const SplashScreen = style({ export const SplashScreen = style({
minHeight: '100%', minHeight: '100%',
backgroundColor: color.Background.Container,
color: color.Background.OnContainer, color: color.Background.OnContainer,
}); });
@@ -23,11 +23,6 @@ export const UrlPreviewImg = style([
objectPosition: 'center', objectPosition: 'center',
flexShrink: 0, flexShrink: 0,
overflow: 'hidden', overflow: 'hidden',
cursor: 'pointer',
':hover': {
filter: 'brightness(0.8)',
},
}, },
]); ]);
@@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { IPreviewUrlResponse } from 'matrix-js-sdk'; import { IPreviewUrlResponse } from 'matrix-js-sdk';
import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds'; import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds';
import { ImageOverlay } from '../ImageOverlay';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
import { UrlPreview, UrlPreviewContent, UrlPreviewDescription, UrlPreviewImg } from './UrlPreview'; import { UrlPreview, UrlPreviewContent, UrlPreviewDescription, UrlPreviewImg } from './UrlPreview';
@@ -13,8 +12,6 @@ import * as css from './UrlPreviewCard.css';
import { tryDecodeURIComponent } from '../../utils/dom'; import { tryDecodeURIComponent } from '../../utils/dom';
import { mxcUrlToHttp } from '../../utils/matrix'; import { mxcUrlToHttp } from '../../utils/matrix';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { ImageViewer } from '../image-viewer';
import { onEnterOrSpace } from '../../utils/keyboard';
const linkStyles = { color: color.Success.Main }; const linkStyles = { color: color.Success.Main };
@@ -22,7 +19,6 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
({ url, ts, ...props }, ref) => { ({ url, ts, ...props }, ref) => {
const mx = useMatrixClient(); const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const [viewer, setViewer] = useState(false);
const [previewStatus, loadPreview] = useAsyncCallback( const [previewStatus, loadPreview] = useAsyncCallback(
useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx]) useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx])
); );
@@ -34,7 +30,7 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
if (previewStatus.status === AsyncStatus.Error) return null; if (previewStatus.status === AsyncStatus.Error) return null;
const renderContent = (prev: IPreviewUrlResponse) => { const renderContent = (prev: IPreviewUrlResponse) => {
const thumbUrl = mxcUrlToHttp( const imgUrl = mxcUrlToHttp(
mx, mx,
prev['og:image'] || '', prev['og:image'] || '',
useAuthentication, useAuthentication,
@@ -44,31 +40,9 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
false false
); );
const imgUrl = mxcUrlToHttp(mx, prev['og:image'] || '', useAuthentication);
return ( return (
<> <>
{thumbUrl && ( {imgUrl && <UrlPreviewImg src={imgUrl} alt={prev['og:title']} title={prev['og:title']} />}
<UrlPreviewImg
src={thumbUrl}
alt={prev['og:title']}
title={prev['og:title']}
tabIndex={0}
onKeyDown={(evt) => onEnterOrSpace(() => setViewer(true))(evt)}
onClick={() => setViewer(true)}
/>
)}
{imgUrl && (
<ImageOverlay
src={imgUrl}
alt={prev['og:title']}
viewer={viewer}
requestClose={() => {
setViewer(false);
}}
renderViewer={(p) => <ImageViewer {...p} />}
/>
)}
<UrlPreviewContent> <UrlPreviewContent>
<Text <Text
style={linkStyles} style={linkStyles}
@@ -36,6 +36,7 @@ export function CallRoomName({ room }: CallRoomNameProps) {
return ( return (
<Chip <Chip
variant="Background" variant="Background"
fill="None"
radii="Pill" radii="Pill"
before={ before={
<RoomIcon size="200" joinRule={room.getJoinRule()} roomType={room.getType()} filled /> <RoomIcon size="200" joinRule={room.getJoinRule()} roomType={room.getType()} filled />
+1 -3
View File
@@ -1,11 +1,9 @@
import React from 'react'; import React from 'react';
import { Box, Spinner } from 'folds'; import { Box, Spinner } from 'folds';
import classNames from 'classnames';
import { LiveChip } from './LiveChip'; import { LiveChip } from './LiveChip';
import * as css from './styles.css'; import * as css from './styles.css';
import { CallRoomName } from './CallRoomName'; import { CallRoomName } from './CallRoomName';
import { CallControl } from './CallControl'; import { CallControl } from './CallControl';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { useCallMembers, useCallSession } from '../../hooks/useCall'; import { useCallMembers, useCallSession } from '../../hooks/useCall';
import { ScreenSize, useScreenSize } from '../../hooks/useScreenSize'; import { ScreenSize, useScreenSize } from '../../hooks/useScreenSize';
import { MemberGlance } from './MemberGlance'; import { MemberGlance } from './MemberGlance';
@@ -33,7 +31,7 @@ export function CallStatus({ callEmbed }: CallStatusProps) {
return ( return (
<Box <Box
className={classNames(css.CallStatus, ContainerColor({ variant: 'Background' }))} className={css.CallStatus}
shrink="No" shrink="No"
gap="400" gap="400"
alignItems={compact ? undefined : 'Center'} alignItems={compact ? undefined : 'Center'}
+1 -6
View File
@@ -1,7 +1,6 @@
import React, { RefObject, useRef } from 'react'; import React, { RefObject, useRef } from 'react';
import { Badge, Box, color, Header, Scroll, Text, toRem } from 'folds'; import { Badge, Box, color, Header, Scroll, Text, toRem } from 'folds';
import { useCallEmbed, useCallJoined, useCallEmbedPlacementSync } from '../../hooks/useCallEmbed'; import { useCallEmbed, useCallJoined, useCallEmbedPlacementSync } from '../../hooks/useCallEmbed';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { PrescreenControls } from './PrescreenControls'; import { PrescreenControls } from './PrescreenControls';
import { usePowerLevelsContext } from '../../hooks/usePowerLevels'; import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
import { useRoom } from '../../hooks/useRoom'; import { useRoom } from '../../hooks/useRoom';
@@ -138,11 +137,7 @@ export function CallView() {
const currentJoined = callEmbed?.roomId === room.roomId && callJoined; const currentJoined = callEmbed?.roomId === room.roomId && callJoined;
return ( return (
<Box <Box style={{ minWidth: toRem(280) }} grow="Yes">
className={ContainerColor({ variant: 'Surface' })}
style={{ minWidth: toRem(280) }}
grow="Yes"
>
{!currentJoined && <CallPrescreen />} {!currentJoined && <CallPrescreen />}
<CallJoined joined={currentJoined} containerRef={callContainerRef} /> <CallJoined joined={currentJoined} containerRef={callContainerRef} />
</Box> </Box>
@@ -213,6 +213,7 @@ export function CreateRoomForm({
<Text size="L400">Options</Text> <Text size="L400">Options</Text>
<Box grow="Yes" justifyContent="End"> <Box grow="Yes" justifyContent="End">
<Chip <Chip
fill="None"
radii="Pill" radii="Pill"
before={<Icon src={advance ? Icons.ChevronTop : Icons.ChevronBottom} size="50" />} before={<Icon src={advance ? Icons.ChevronTop : Icons.ChevronBottom} size="50" />}
onClick={() => setAdvance(!advance)} onClick={() => setAdvance(!advance)}
@@ -180,6 +180,7 @@ export function CreateSpaceForm({ defaultAccess, space, onCreate }: CreateSpaceF
<Box grow="Yes" justifyContent="End"> <Box grow="Yes" justifyContent="End">
<Chip <Chip
radii="Pill" radii="Pill"
fill="None"
before={<Icon src={advance ? Icons.ChevronTop : Icons.ChevronBottom} size="50" />} before={<Icon src={advance ? Icons.ChevronTop : Icons.ChevronBottom} size="50" />}
onClick={() => setAdvance(!advance)} onClick={() => setAdvance(!advance)}
type="button" type="button"
@@ -10,6 +10,7 @@ import { useMatrixClient } from '../../hooks/useMatrixClient';
import { allRoomsAtom } from '../../state/room-list/roomList'; import { allRoomsAtom } from '../../state/room-list/roomList';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { BackRouteHandler } from '../../components/BackRouteHandler'; import { BackRouteHandler } from '../../components/BackRouteHandler';
import { useTheme } from '../../hooks/useTheme';
type JoinBeforeNavigateProps = { roomIdOrAlias: string; eventId?: string; viaServers?: string[] }; type JoinBeforeNavigateProps = { roomIdOrAlias: string; eventId?: string; viaServers?: string[] };
export function JoinBeforeNavigate({ export function JoinBeforeNavigate({
@@ -21,6 +22,7 @@ export function JoinBeforeNavigate({
const allRooms = useAtomValue(allRoomsAtom); const allRooms = useAtomValue(allRoomsAtom);
const { navigateRoom, navigateSpace } = useRoomNavigate(); const { navigateRoom, navigateSpace } = useRoomNavigate();
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
const theme = useTheme();
const handleView = (roomId: string) => { const handleView = (roomId: string) => {
if (mx.getRoom(roomId)?.isSpaceRoom()) { if (mx.getRoom(roomId)?.isSpaceRoom()) {
@@ -31,7 +33,7 @@ export function JoinBeforeNavigate({
}; };
return ( return (
<Page> <Page transparent={theme.flat}>
<PageHeader balance> <PageHeader balance>
<Box grow="Yes" gap="200"> <Box grow="Yes" gap="200">
<Box shrink="No"> <Box shrink="No">
+3 -1
View File
@@ -56,6 +56,7 @@ import { useGetRoom } from '../../hooks/useGetRoom';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { getRoomPermissionsAPI } from '../../hooks/useRoomPermissions'; import { getRoomPermissionsAPI } from '../../hooks/useRoomPermissions';
import { getRoomCreatorsForRoomId } from '../../hooks/useRoomCreators'; import { getRoomCreatorsForRoomId } from '../../hooks/useRoomCreators';
import { useTheme } from '../../hooks/useTheme';
const useCanDropLobbyItem = ( const useCanDropLobbyItem = (
space: Room, space: Room,
@@ -151,6 +152,7 @@ const useCanDropLobbyItem = (
export function Lobby() { export function Lobby() {
const navigate = useNavigate(); const navigate = useNavigate();
const mx = useMatrixClient(); const mx = useMatrixClient();
const theme = useTheme();
const mDirects = useAtomValue(mDirectAtom); const mDirects = useAtomValue(mDirectAtom);
const allRooms = useAtomValue(allRoomsAtom); const allRooms = useAtomValue(allRoomsAtom);
const allJoinedRooms = useMemo(() => new Set(allRooms), [allRooms]); const allJoinedRooms = useMemo(() => new Set(allRooms), [allRooms]);
@@ -430,7 +432,7 @@ export function Lobby() {
return ( return (
<PowerLevelsContextProvider value={spacePowerLevels}> <PowerLevelsContextProvider value={spacePowerLevels}>
<Box grow="Yes"> <Box grow="Yes">
<Page> <Page transparent={theme.flat}>
<LobbyHeader <LobbyHeader
showProfile={!onTop} showProfile={!onTop}
powerLevels={roomsPowerLevels.get(space.roomId) ?? {}} powerLevels={roomsPowerLevels.get(space.roomId) ?? {}}
+4
View File
@@ -64,6 +64,7 @@ function InaccessibleSpaceProfile({ roomId, suggested }: InaccessibleSpaceProfil
as="span" as="span"
className={css.HeaderChip} className={css.HeaderChip}
variant="Surface" variant="Surface"
fill="None"
size="500" size="500"
before={ before={
<Avatar size="200" radii="300"> <Avatar size="200" radii="300">
@@ -121,6 +122,7 @@ function UnjoinedSpaceProfile({
<Chip <Chip
className={css.HeaderChip} className={css.HeaderChip}
variant="Surface" variant="Surface"
fill="None"
size="500" size="500"
onClick={join} onClick={join}
disabled={!canJoin} disabled={!canJoin}
@@ -187,6 +189,7 @@ function SpaceProfile({
onClick={handleClose} onClick={handleClose}
className={css.HeaderChip} className={css.HeaderChip}
variant="Surface" variant="Surface"
fill="None"
size="500" size="500"
before={ before={
<Avatar size="200" radii="300"> <Avatar size="200" radii="300">
@@ -230,6 +233,7 @@ function RootSpaceProfile({ closed, categoryId, handleClose }: RootSpaceProfileP
onClick={handleClose} onClick={handleClose}
className={css.HeaderChip} className={css.HeaderChip}
variant="Surface" variant="Surface"
fill="None"
size="500" size="500"
after={<Icon src={closed ? Icons.ChevronRight : Icons.ChevronBottom} size="50" />} after={<Icon src={closed ? Icons.ChevronRight : Icons.ChevronBottom} size="50" />}
> >
@@ -9,6 +9,7 @@ export const RoomNavCategoryButton = as<'button', { closed?: boolean }>(
className={classNames(css.CategoryButton, className)} className={classNames(css.CategoryButton, className)}
variant="Background" variant="Background"
radii="Pill" radii="Pill"
fill="None"
before={ before={
<Icon <Icon
className={css.CategoryButtonIcon} className={css.CategoryButtonIcon}
+6 -17
View File
@@ -23,12 +23,12 @@ import { useAtom, useAtomValue } from 'jotai';
import { NavItem, NavItemContent, NavItemOptions, NavLink } from '../../components/nav'; import { NavItem, NavItemContent, NavItemOptions, NavLink } from '../../components/nav';
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge'; import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
import { RoomAvatar, RoomIcon } from '../../components/room-avatar'; import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
import { getDirectRoomAvatarUrl, getRoomAvatarUrl, getStateEvent } from '../../utils/room'; import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../../utils/room';
import { nameInitials } from '../../utils/common'; import { nameInitials } from '../../utils/common';
import { useMatrixClient } from '../../hooks/useMatrixClient'; import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomUnread } from '../../state/hooks/unread'; import { useRoomUnread } from '../../state/hooks/unread';
import { roomToUnreadAtom } from '../../state/room/roomToUnread'; import { roomToUnreadAtom } from '../../state/room/roomToUnread';
import { getPowersLevelFromMatrixEvent, usePowerLevels } from '../../hooks/usePowerLevels'; import { usePowerLevels } from '../../hooks/usePowerLevels';
import { copyToClipboard } from '../../utils/dom'; import { copyToClipboard } from '../../utils/dom';
import { markAsRead } from '../../utils/notifications'; import { markAsRead } from '../../utils/notifications';
import { UseStateProvider } from '../../components/UseStateProvider'; import { UseStateProvider } from '../../components/UseStateProvider';
@@ -49,8 +49,8 @@ import {
RoomNotificationMode, RoomNotificationMode,
} from '../../hooks/useRoomsNotificationPreferences'; } from '../../hooks/useRoomsNotificationPreferences';
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher'; import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
import { getRoomCreatorsForRoomId, useRoomCreators } from '../../hooks/useRoomCreators'; import { useRoomCreators } from '../../hooks/useRoomCreators';
import { getRoomPermissionsAPI, useRoomPermissions } from '../../hooks/useRoomPermissions'; import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { InviteUserPrompt } from '../../components/invite-user-prompt'; import { InviteUserPrompt } from '../../components/invite-user-prompt';
import { useRoomName } from '../../hooks/useRoomMeta'; import { useRoomName } from '../../hooks/useRoomMeta';
import { useCallMembers, useCallSession } from '../../hooks/useCall'; import { useCallMembers, useCallSession } from '../../hooks/useCall';
@@ -59,7 +59,6 @@ import { callChatAtom } from '../../state/callEmbed';
import { useCallPreferencesAtom } from '../../state/hooks/callPreferences'; import { useCallPreferencesAtom } from '../../state/hooks/callPreferences';
import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo'; import { useAutoDiscoveryInfo } from '../../hooks/useAutoDiscoveryInfo';
import { livekitSupport } from '../../hooks/useLivekitSupport'; import { livekitSupport } from '../../hooks/useLivekitSupport';
import { StateEvent } from '../../../types/matrix/room';
type RoomNavItemMenuProps = { type RoomNavItemMenuProps = {
room: Room; room: Room;
@@ -288,18 +287,8 @@ export function RoomNavItem({
const autoDiscoveryInfo = useAutoDiscoveryInfo(); const autoDiscoveryInfo = useAutoDiscoveryInfo();
const handleStartCall: MouseEventHandler<HTMLAnchorElement> = (evt) => { const handleStartCall: MouseEventHandler<HTMLAnchorElement> = (evt) => {
const powerLevelsEvent = getStateEvent(room, StateEvent.RoomPowerLevels); // Do not join if no livekit support or call is not started by others
const powerLevels = getPowersLevelFromMatrixEvent(powerLevelsEvent); if (!livekitSupport(autoDiscoveryInfo) && callMembers.length === 0) {
const creators = getRoomCreatorsForRoomId(mx, room.roomId);
const permissions = getRoomPermissionsAPI(creators, powerLevels);
const hasCallPermission = permissions.event(
StateEvent.GroupCallMemberPrefix,
mx.getSafeUserId()
);
// Do not join if missing permissions or no livekit support and call is not started by others
if (!hasCallPermission || (!livekitSupport(autoDiscoveryInfo) && callMembers.length === 0)) {
return; return;
} }
+4 -1
View File
@@ -6,9 +6,11 @@ import { Page, PageHeader } from '../../components/page';
import { callChatAtom } from '../../state/callEmbed'; import { callChatAtom } from '../../state/callEmbed';
import { RoomView } from './RoomView'; import { RoomView } from './RoomView';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { useTheme } from '../../hooks/useTheme';
export function CallChatView() { export function CallChatView() {
const { eventId } = useParams(); const { eventId } = useParams();
const theme = useTheme();
const setChat = useSetAtom(callChatAtom); const setChat = useSetAtom(callChatAtom);
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
@@ -16,6 +18,7 @@ export function CallChatView() {
return ( return (
<Page <Page
transparent={theme.flat}
style={{ style={{
width: screenSize === ScreenSize.Desktop ? toRem(456) : '100%', width: screenSize === ScreenSize.Desktop ? toRem(456) : '100%',
flexShrink: 0, flexShrink: 0,
@@ -41,7 +44,7 @@ export function CallChatView() {
} }
> >
{(triggerRef) => ( {(triggerRef) => (
<IconButton ref={triggerRef} variant="Surface" onClick={handleClose}> <IconButton ref={triggerRef} variant="Surface" fill="None" onClick={handleClose}>
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
</IconButton> </IconButton>
)} )}
+5 -6
View File
@@ -56,7 +56,6 @@ import { MembershipFilterMenu } from '../../components/MembershipFilterMenu';
import { MemberSortMenu } from '../../components/MemberSortMenu'; import { MemberSortMenu } from '../../components/MemberSortMenu';
import { useOpenUserRoomProfile, useUserRoomProfileState } from '../../state/hooks/userRoomProfile'; import { useOpenUserRoomProfile, useUserRoomProfileState } from '../../state/hooks/userRoomProfile';
import { useSpaceOptionally } from '../../hooks/useSpace'; import { useSpaceOptionally } from '../../hooks/useSpace';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag'; import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
import { useRoomCreators } from '../../hooks/useRoomCreators'; import { useRoomCreators } from '../../hooks/useRoomCreators';
@@ -89,6 +88,7 @@ function MemberDrawerHeader({ room }: MemberDrawerHeaderProps) {
<IconButton <IconButton
ref={triggerRef} ref={triggerRef}
variant="Background" variant="Background"
fill="None"
onClick={() => setPeopleDrawer(false)} onClick={() => setPeopleDrawer(false)}
> >
<Icon src={Icons.Cross} /> <Icon src={Icons.Cross} />
@@ -132,6 +132,7 @@ function MemberItem({
aria-pressed={pressed} aria-pressed={pressed}
data-user-id={member.userId} data-user-id={member.userId}
variant="Background" variant="Background"
fill="None"
radii="400" radii="400"
onClick={onClick} onClick={onClick}
before={ before={
@@ -245,11 +246,7 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
}; };
return ( return (
<Box <Box className={classNames(css.MembersDrawer)} shrink="No" direction="Column">
className={classNames(css.MembersDrawer, ContainerColor({ variant: 'Background' }))}
shrink="No"
direction="Column"
>
<MemberDrawerHeader room={room} /> <MemberDrawerHeader room={room} />
<Box className={css.MemberDrawerContentBase} grow="Yes"> <Box className={css.MemberDrawerContentBase} grow="Yes">
<Scroll ref={scrollRef} variant="Background" size="300" visibility="Hover" hideTrack> <Scroll ref={scrollRef} variant="Background" size="300" visibility="Hover" hideTrack>
@@ -279,6 +276,7 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
)) as MouseEventHandler<HTMLButtonElement> )) as MouseEventHandler<HTMLButtonElement>
} }
variant="Background" variant="Background"
fill="None"
size="400" size="400"
radii="300" radii="300"
before={<Icon src={Icons.Filter} size="50" />} before={<Icon src={Icons.Filter} size="50" />}
@@ -311,6 +309,7 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
)) as MouseEventHandler<HTMLButtonElement> )) as MouseEventHandler<HTMLButtonElement>
} }
variant="Background" variant="Background"
fill="None"
size="400" size="400"
radii="300" radii="300"
after={<Icon src={Icons.Sort} size="50" />} after={<Icon src={Icons.Sort} size="50" />}
+7 -4
View File
@@ -18,11 +18,14 @@ import { CallView } from '../call/CallView';
import { RoomViewHeader } from './RoomViewHeader'; import { RoomViewHeader } from './RoomViewHeader';
import { callChatAtom } from '../../state/callEmbed'; import { callChatAtom } from '../../state/callEmbed';
import { CallChatView } from './CallChatView'; import { CallChatView } from './CallChatView';
import { Page } from '../../components/page';
import { useTheme } from '../../hooks/useTheme';
export function Room() { export function Room() {
const { eventId } = useParams(); const { eventId } = useParams();
const room = useRoom(); const room = useRoom();
const mx = useMatrixClient(); const mx = useMatrixClient();
const theme = useTheme();
const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer'); const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer');
const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
@@ -49,20 +52,20 @@ export function Room() {
<PowerLevelsContextProvider value={powerLevels}> <PowerLevelsContextProvider value={powerLevels}>
<Box grow="Yes"> <Box grow="Yes">
{callView && (screenSize === ScreenSize.Desktop || !chat) && ( {callView && (screenSize === ScreenSize.Desktop || !chat) && (
<Box grow="Yes" direction="Column"> <Page transparent={theme.flat}>
<RoomViewHeader callView /> <RoomViewHeader callView />
<Box grow="Yes"> <Box grow="Yes">
<CallView /> <CallView />
</Box> </Box>
</Box> </Page>
)} )}
{!callView && ( {!callView && (
<Box grow="Yes" direction="Column"> <Page transparent={theme.flat}>
<RoomViewHeader /> <RoomViewHeader />
<Box grow="Yes"> <Box grow="Yes">
<RoomView eventId={eventId} /> <RoomView eventId={eventId} />
</Box> </Box>
</Box> </Page>
)} )}
{callView && chat && ( {callView && chat && (
+2 -3
View File
@@ -14,7 +14,6 @@ import { RoomViewTyping } from './RoomViewTyping';
import { RoomTombstone } from './RoomTombstone'; import { RoomTombstone } from './RoomTombstone';
import { RoomInput } from './RoomInput'; import { RoomInput } from './RoomInput';
import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing'; import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing';
import { Page } from '../../components/page';
import { useKeyDown } from '../../hooks/useKeyDown'; import { useKeyDown } from '../../hooks/useKeyDown';
import { editableActiveElement } from '../../utils/dom'; import { editableActiveElement } from '../../utils/dom';
import { settingsAtom } from '../../state/settings'; import { settingsAtom } from '../../state/settings';
@@ -91,7 +90,7 @@ export function RoomView({ eventId }: { eventId?: string }) {
); );
return ( return (
<Page ref={roomViewRef}> <Box grow="Yes" direction="Column" ref={roomViewRef}>
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
<RoomTimeline <RoomTimeline
key={roomId} key={roomId}
@@ -135,6 +134,6 @@ export function RoomView({ eventId }: { eventId?: string }) {
</div> </div>
{hideActivity ? <RoomViewFollowingPlaceholder /> : <RoomViewFollowing room={room} />} {hideActivity ? <RoomViewFollowingPlaceholder /> : <RoomViewFollowing room={room} />}
</Box> </Box>
</Page> </Box>
); );
} }
@@ -16,8 +16,6 @@ export const RoomViewFollowing = recipe({
minHeight: toRem(28), minHeight: toRem(28),
padding: `0 ${config.space.S400}`, padding: `0 ${config.space.S400}`,
width: '100%', width: '100%',
backgroundColor: color.Surface.Container,
color: color.Surface.OnContainer,
outline: 'none', outline: 'none',
}, },
], ],
+1 -5
View File
@@ -66,7 +66,6 @@ import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { useRoomCreators } from '../../hooks/useRoomCreators'; import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../hooks/useRoomPermissions'; import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { InviteUserPrompt } from '../../components/invite-user-prompt'; import { InviteUserPrompt } from '../../components/invite-user-prompt';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { RoomSettingsPage } from '../../state/roomSettings'; import { RoomSettingsPage } from '../../state/roomSettings';
type RoomMenuProps = { type RoomMenuProps = {
@@ -305,10 +304,7 @@ export function RoomViewHeader({ callView }: { callView?: boolean }) {
}; };
return ( return (
<PageHeader <PageHeader balance={screenSize === ScreenSize.Mobile}>
className={ContainerColor({ variant: 'Surface' })}
balance={screenSize === ScreenSize.Mobile}
>
<Box grow="Yes" gap="300"> <Box grow="Yes" gap="300">
{screenSize === ScreenSize.Mobile && ( {screenSize === ScreenSize.Mobile && (
<BackRouteHandler> <BackRouteHandler>
+1 -1
View File
@@ -286,7 +286,7 @@ export function Search({ requestClose }: SearchProps) {
gap="100" gap="100"
> >
<Text size="H6" align="Center"> <Text size="H6" align="Center">
{result ? 'No Match Found' : 'No Rooms'} {result ? 'No Match Found' : `No Rooms'}`}
</Text> </Text>
<Text size="T200" align="Center"> <Text size="T200" align="Center">
{result {result
+1 -1
View File
@@ -46,7 +46,7 @@ export function About({ requestClose }: AboutProps) {
<Box direction="Column" gap="100"> <Box direction="Column" gap="100">
<Box gap="100" alignItems="End"> <Box gap="100" alignItems="End">
<Text size="H3">Cinny</Text> <Text size="H3">Cinny</Text>
<Text size="T200">v4.11.1</Text> <Text size="T200">v4.11.0</Text>
</Box> </Box>
<Text>Yet another matrix client.</Text> <Text>Yet another matrix client.</Text>
</Box> </Box>
-4
View File
@@ -25,10 +25,6 @@ export const useDateFormatItems = (): DateFormatItem[] =>
format: 'YYYY/MM/DD', format: 'YYYY/MM/DD',
name: 'YYYY/MM/DD', name: 'YYYY/MM/DD',
}, },
{
format: 'YYYY-MM-DD',
name: 'YYYY-MM-DD',
},
{ {
format: '', format: '',
name: 'Custom', name: 'Custom',
+1 -1
View File
@@ -57,7 +57,7 @@ const fillMissingPowers = (powerLevels: IPowerLevels): IPowerLevels =>
return draftPl; return draftPl;
}); });
export const getPowersLevelFromMatrixEvent = (mEvent?: MatrixEvent): IPowerLevels => { const getPowersLevelFromMatrixEvent = (mEvent?: MatrixEvent): IPowerLevels => {
const plContent = mEvent?.getContent<IPowerLevels>(); const plContent = mEvent?.getContent<IPowerLevels>();
const powerLevels = !plContent ? DEFAULT_POWER_LEVELS : fillMissingPowers(plContent); const powerLevels = !plContent ? DEFAULT_POWER_LEVELS : fillMissingPowers(plContent);
+18 -6
View File
@@ -1,7 +1,6 @@
import { lightTheme } from 'folds';
import { createContext, useContext, useEffect, useMemo, useState } from 'react'; import { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { onDarkFontWeight, onLightFontWeight } from '../../config.css'; import { onDarkFontWeight, onLightFontWeight } from '../../config.css';
import { butterTheme, darkTheme, silverTheme } from '../../colors.css'; import { lightTheme, butterTheme, darkTheme, silverTheme, moonlightTheme } from '../../colors.css';
import { settingsAtom } from '../state/settings'; import { settingsAtom } from '../state/settings';
import { useSetting } from '../state/hooks/settings'; import { useSetting } from '../state/hooks/settings';
@@ -14,6 +13,8 @@ export type Theme = {
id: string; id: string;
kind: ThemeKind; kind: ThemeKind;
classNames: string[]; classNames: string[];
flat?: boolean;
gradient?: string;
}; };
export const LightTheme: Theme = { export const LightTheme: Theme = {
@@ -25,21 +26,31 @@ export const LightTheme: Theme = {
export const SilverTheme: Theme = { export const SilverTheme: Theme = {
id: 'silver-theme', id: 'silver-theme',
kind: ThemeKind.Light, kind: ThemeKind.Light,
classNames: ['silver-theme', silverTheme, onLightFontWeight, 'prism-light'], classNames: [silverTheme, onLightFontWeight, 'prism-light'],
}; };
export const DarkTheme: Theme = { export const DarkTheme: Theme = {
id: 'dark-theme', id: 'dark-theme',
kind: ThemeKind.Dark, kind: ThemeKind.Dark,
classNames: ['dark-theme', darkTheme, onDarkFontWeight, 'prism-dark'], classNames: ['global-dark', darkTheme, onDarkFontWeight, 'prism-dark'],
}; };
export const ButterTheme: Theme = { export const ButterTheme: Theme = {
id: 'butter-theme', id: 'butter-theme',
kind: ThemeKind.Dark, kind: ThemeKind.Dark,
classNames: ['butter-theme', butterTheme, onDarkFontWeight, 'prism-dark'], classNames: ['global-dark', butterTheme, onDarkFontWeight, 'prism-dark'],
};
export const MoonlightTheme: Theme = {
id: 'moonlight-theme',
kind: ThemeKind.Dark,
classNames: ['global-dark', moonlightTheme, onDarkFontWeight, 'prism-dark'],
flat: true,
}; };
export const useThemes = (): Theme[] => { export const useThemes = (): Theme[] => {
const themes: Theme[] = useMemo(() => [LightTheme, SilverTheme, DarkTheme, ButterTheme], []); const themes: Theme[] = useMemo(
() => [LightTheme, SilverTheme, DarkTheme, ButterTheme, MoonlightTheme],
[]
);
return themes; return themes;
}; };
@@ -51,6 +62,7 @@ export const useThemeNames = (): Record<string, string> =>
[SilverTheme.id]: 'Silver', [SilverTheme.id]: 'Silver',
[DarkTheme.id]: 'Dark', [DarkTheme.id]: 'Dark',
[ButterTheme.id]: 'Butter', [ButterTheme.id]: 'Butter',
[MoonlightTheme.id]: 'Moonlight',
}), }),
[] []
); );
+2 -2
View File
@@ -39,9 +39,9 @@ export function AuthRouteThemeManager({ children }: { children: ReactNode }) {
document.body.classList.add(...activeTheme.classNames); document.body.classList.add(...activeTheme.classNames);
if (monochromeMode) { if (monochromeMode) {
document.body.style.filter = 'grayscale(1)'; document.body.style.setProperty('filter', 'grayscale(1)');
} else { } else {
document.body.style.filter = ''; document.body.style.removeProperty('filter');
} }
}, [activeTheme, monochromeMode]); }, [activeTheme, monochromeMode]);
+1 -1
View File
@@ -15,7 +15,7 @@ export function AuthFooter() {
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
v4.11.1 v4.11.0
</Text> </Text>
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer"> <Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
Twitter Twitter
+1 -1
View File
@@ -22,7 +22,7 @@ export function SpecVersions({ baseUrl, children }: { baseUrl: string; children:
<Dialog> <Dialog>
<Box direction="Column" gap="400" style={{ padding: config.space.S400 }}> <Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
<Text> <Text>
Unable to connect to the homeserver. The homeserver or your internet connection may be down. Failed to connect to homeserver. Either homeserver is down or your internet.
</Text> </Text>
<Button variant="Critical" onClick={retry}> <Button variant="Critical" onClick={retry}>
<Text as="span" size="B400"> <Text as="span" size="B400">
+4 -2
View File
@@ -2,10 +2,12 @@ import React from 'react';
import { Box, Button, Icon, Icons, Text, config, toRem } from 'folds'; import { Box, Button, Icon, Icons, Text, config, toRem } from 'folds';
import { Page, PageHero, PageHeroSection } from '../../components/page'; import { Page, PageHero, PageHeroSection } from '../../components/page';
import CinnySVG from '../../../../public/res/svg/cinny.svg'; import CinnySVG from '../../../../public/res/svg/cinny.svg';
import { useTheme } from '../../hooks/useTheme';
export function WelcomePage() { export function WelcomePage() {
const theme = useTheme();
return ( return (
<Page> <Page transparent={theme.flat}>
<Box <Box
grow="Yes" grow="Yes"
style={{ padding: config.space.S400, paddingBottom: config.space.S700 }} style={{ padding: config.space.S400, paddingBottom: config.space.S700 }}
@@ -24,7 +26,7 @@ export function WelcomePage() {
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
v4.11.1 v4.11.0
</a> </a>
</span> </span>
} }
+3 -1
View File
@@ -9,12 +9,14 @@ import {
} from '../../../components/page'; } from '../../../components/page';
import { CreateSpaceForm } from '../../../features/create-space'; import { CreateSpaceForm } from '../../../features/create-space';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate'; import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { useTheme } from '../../../hooks/useTheme';
export function Create() { export function Create() {
const { navigateSpace } = useRoomNavigate(); const { navigateSpace } = useRoomNavigate();
const theme = useTheme();
return ( return (
<Page> <Page transparent={theme.flat}>
<Box grow="Yes"> <Box grow="Yes">
<Scroll hideTrack visibility="Hover"> <Scroll hideTrack visibility="Hover">
<PageContent> <PageContent>
+6 -1
View File
@@ -107,7 +107,12 @@ function DirectHeader() {
</Text> </Text>
</Box> </Box>
<Box> <Box>
<IconButton aria-pressed={!!menuAnchor} variant="Background" onClick={handleOpenMenu}> <IconButton
aria-pressed={!!menuAnchor}
variant="Background"
fill="None"
onClick={handleOpenMenu}
>
<Icon src={Icons.VerticalDots} size="200" /> <Icon src={Icons.VerticalDots} size="200" />
</IconButton> </IconButton>
</Box> </Box>
+3 -1
View File
@@ -17,10 +17,12 @@ import {
} from '../../../components/page'; } from '../../../components/page';
import { BackRouteHandler } from '../../../components/BackRouteHandler'; import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { CreateChat } from '../../../features/create-chat'; import { CreateChat } from '../../../features/create-chat';
import { useTheme } from '../../../hooks/useTheme';
export function DirectCreate() { export function DirectCreate() {
const mx = useMatrixClient(); const mx = useMatrixClient();
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
@@ -38,7 +40,7 @@ export function DirectCreate() {
}, [mx, navigate, directs, userId]); }, [mx, navigate, directs, userId]);
return ( return (
<Page> <Page transparent={theme.flat}>
{screenSize === ScreenSize.Mobile && ( {screenSize === ScreenSize.Mobile && (
<PageHeader balance outlined={false}> <PageHeader balance outlined={false}>
<Box grow="Yes" alignItems="Center" gap="200"> <Box grow="Yes" alignItems="Center" gap="200">
+3 -1
View File
@@ -18,8 +18,10 @@ import * as css from './style.css';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate'; import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler'; import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { useTheme } from '../../../hooks/useTheme';
export function FeaturedRooms() { export function FeaturedRooms() {
const theme = useTheme();
const { featuredCommunities } = useClientConfig(); const { featuredCommunities } = useClientConfig();
const { rooms, spaces } = featuredCommunities ?? {}; const { rooms, spaces } = featuredCommunities ?? {};
const allRooms = useAtomValue(allRoomsAtom); const allRooms = useAtomValue(allRoomsAtom);
@@ -27,7 +29,7 @@ export function FeaturedRooms() {
const { navigateSpace, navigateRoom } = useRoomNavigate(); const { navigateSpace, navigateRoom } = useRoomNavigate();
return ( return (
<Page> <Page transparent={theme.flat}>
{screenSize === ScreenSize.Mobile && ( {screenSize === ScreenSize.Mobile && (
<PageHeader> <PageHeader>
<Box shrink="No"> <Box shrink="No">
+3 -1
View File
@@ -45,6 +45,7 @@ import { getMxIdServer } from '../../../utils/matrix';
import { stopPropagation } from '../../../utils/keyboard'; import { stopPropagation } from '../../../utils/keyboard';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler'; import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { useTheme } from '../../../hooks/useTheme';
const useServerSearchParams = (searchParams: URLSearchParams): ExploreServerPathSearchParams => const useServerSearchParams = (searchParams: URLSearchParams): ExploreServerPathSearchParams =>
useMemo( useMemo(
@@ -343,6 +344,7 @@ function LimitButton({ limit, onLimitChange }: LimitButtonProps) {
export function PublicRooms() { export function PublicRooms() {
const { server } = useParams(); const { server } = useParams();
const mx = useMatrixClient(); const mx = useMatrixClient();
const theme = useTheme();
const userId = mx.getUserId(); const userId = mx.getUserId();
const userServer = userId && getMxIdServer(userId); const userServer = userId && getMxIdServer(userId);
const allRooms = useAtomValue(allRoomsAtom); const allRooms = useAtomValue(allRoomsAtom);
@@ -469,7 +471,7 @@ export function PublicRooms() {
}; };
return ( return (
<Page> <Page transparent={theme.flat}>
<PageHeader balance> <PageHeader balance>
{isSearch ? ( {isSearch ? (
<> <>
+3 -1
View File
@@ -12,14 +12,16 @@ import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler'; import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { CreateRoomForm } from '../../../features/create-room'; import { CreateRoomForm } from '../../../features/create-room';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate'; import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { useTheme } from '../../../hooks/useTheme';
export function HomeCreateRoom() { export function HomeCreateRoom() {
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
const theme = useTheme();
const { navigateRoom } = useRoomNavigate(); const { navigateRoom } = useRoomNavigate();
return ( return (
<Page> <Page transparent={theme.flat}>
{screenSize === ScreenSize.Mobile && ( {screenSize === ScreenSize.Mobile && (
<PageHeader balance outlined={false}> <PageHeader balance outlined={false}>
<Box grow="Yes" alignItems="Center" gap="200"> <Box grow="Yes" alignItems="Center" gap="200">
+6 -1
View File
@@ -121,7 +121,12 @@ function HomeHeader() {
</Text> </Text>
</Box> </Box>
<Box> <Box>
<IconButton aria-pressed={!!menuAnchor} variant="Background" onClick={handleOpenMenu}> <IconButton
aria-pressed={!!menuAnchor}
variant="Background"
fill="None"
onClick={handleOpenMenu}
>
<Icon src={Icons.VerticalDots} size="200" /> <Icon src={Icons.VerticalDots} size="200" />
</IconButton> </IconButton>
</Box> </Box>
+3 -1
View File
@@ -5,14 +5,16 @@ import { MessageSearch } from '../../../features/message-search';
import { useHomeRooms } from './useHomeRooms'; import { useHomeRooms } from './useHomeRooms';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler'; import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { useTheme } from '../../../hooks/useTheme';
export function HomeSearch() { export function HomeSearch() {
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const rooms = useHomeRooms(); const rooms = useHomeRooms();
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
const theme = useTheme();
return ( return (
<Page> <Page transparent={theme.flat}>
<PageHeader balance> <PageHeader balance>
<Box grow="Yes" alignItems="Center" gap="200"> <Box grow="Yes" alignItems="Center" gap="200">
<Box grow="Yes" basis="No"> <Box grow="Yes" basis="No">
+3 -1
View File
@@ -67,6 +67,7 @@ import { useIgnoredUsers } from '../../../hooks/useIgnoredUsers';
import { useReportRoomSupported } from '../../../hooks/useReportRoomSupported'; import { useReportRoomSupported } from '../../../hooks/useReportRoomSupported';
import { useSetting } from '../../../state/hooks/settings'; import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings'; import { settingsAtom } from '../../../state/settings';
import { useTheme } from '../../../hooks/useTheme';
const COMPACT_CARD_WIDTH = 548; const COMPACT_CARD_WIDTH = 548;
@@ -693,6 +694,7 @@ function SpamInvites({
export function Invites() { export function Invites() {
const mx = useMatrixClient(); const mx = useMatrixClient();
const theme = useTheme();
const useAuthentication = useMediaAuthentication(); const useAuthentication = useMediaAuthentication();
const { navigateRoom, navigateSpace } = useRoomNavigate(); const { navigateRoom, navigateSpace } = useRoomNavigate();
const allRooms = useAtomValue(allRoomsAtom); const allRooms = useAtomValue(allRoomsAtom);
@@ -746,7 +748,7 @@ export function Invites() {
}; };
return ( return (
<Page> <Page transparent={theme.flat}>
<PageHeader balance> <PageHeader balance>
<Box grow="Yes" gap="200"> <Box grow="Yes" gap="200">
<Box grow="Yes" basis="No"> <Box grow="Yes" basis="No">
+2 -1
View File
@@ -563,6 +563,7 @@ const DEFAULT_REFRESH_MS = 7000;
export function Notifications() { export function Notifications() {
const mx = useMatrixClient(); const mx = useMatrixClient();
const theme = useTheme();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity'); const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad'); const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
const [urlPreview] = useSetting(settingsAtom, 'urlPreview'); const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
@@ -635,7 +636,7 @@ export function Notifications() {
}, [timelineState, notificationTimeline, lastVItemIndex, loadTimeline]); }, [timelineState, notificationTimeline, lastVItemIndex, loadTimeline]);
return ( return (
<Page> <Page transparent={theme.flat}>
<PageHeader balance> <PageHeader balance>
<Box grow="Yes" gap="200"> <Box grow="Yes" gap="200">
<Box grow="Yes" basis="No"> <Box grow="Yes" basis="No">
+3 -1
View File
@@ -11,9 +11,11 @@ import { roomToParentsAtom } from '../../../state/room/roomToParents';
import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { BackRouteHandler } from '../../../components/BackRouteHandler'; import { BackRouteHandler } from '../../../components/BackRouteHandler';
import { useTheme } from '../../../hooks/useTheme';
export function SpaceSearch() { export function SpaceSearch() {
const mx = useMatrixClient(); const mx = useMatrixClient();
const theme = useTheme();
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const space = useSpace(); const space = useSpace();
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
@@ -27,7 +29,7 @@ export function SpaceSearch() {
); );
return ( return (
<Page> <Page transparent={theme.flat}>
<PageHeader balance> <PageHeader balance>
<Box grow="Yes" alignItems="Center" gap="200"> <Box grow="Yes" alignItems="Center" gap="200">
<Box grow="Yes" basis="No"> <Box grow="Yes" basis="No">
+6 -1
View File
@@ -273,7 +273,12 @@ function SpaceHeader() {
{joinRules?.join_rule !== JoinRule.Public && <Icon src={Icons.Lock} size="50" />} {joinRules?.join_rule !== JoinRule.Public && <Icon src={Icons.Lock} size="50" />}
</Box> </Box>
<Box shrink="No"> <Box shrink="No">
<IconButton aria-pressed={!!menuAnchor} variant="Background" onClick={handleOpenMenu}> <IconButton
aria-pressed={!!menuAnchor}
variant="Background"
fill="None"
onClick={handleOpenMenu}
>
<Icon src={Icons.VerticalDots} size="200" /> <Icon src={Icons.VerticalDots} size="200" />
</IconButton> </IconButton>
</Box> </Box>
+10 -2
View File
@@ -1,5 +1,12 @@
import { replaceMatch } from '../internal'; import { replaceMatch } from '../internal';
import { BlockQuoteRule, CodeBlockRule, ESC_BLOCK_SEQ, HeadingRule, ListRule } from './rules'; import {
BlockQuoteRule,
CodeBlockRule,
ESC_BLOCK_SEQ,
HeadingRule,
OrderedListRule,
UnorderedListRule,
} from './rules';
import { runBlockRule } from './runner'; import { runBlockRule } from './runner';
import { BlockMDParser } from './type'; import { BlockMDParser } from './type';
@@ -16,7 +23,8 @@ export const parseBlockMD: BlockMDParser = (text, parseInline) => {
if (!result) result = runBlockRule(text, CodeBlockRule, parseBlockMD, parseInline); if (!result) result = runBlockRule(text, CodeBlockRule, parseBlockMD, parseInline);
if (!result) result = runBlockRule(text, BlockQuoteRule, parseBlockMD, parseInline); if (!result) result = runBlockRule(text, BlockQuoteRule, parseBlockMD, parseInline);
if (!result) result = runBlockRule(text, ListRule, parseBlockMD, parseInline); if (!result) result = runBlockRule(text, OrderedListRule, parseBlockMD, parseInline);
if (!result) result = runBlockRule(text, UnorderedListRule, parseBlockMD, parseInline);
if (!result) result = runBlockRule(text, HeadingRule, parseBlockMD, parseInline); if (!result) result = runBlockRule(text, HeadingRule, parseBlockMD, parseInline);
// replace \n with <br/> because want to preserve empty lines // replace \n with <br/> because want to preserve empty lines
+49 -148
View File
@@ -10,22 +10,14 @@ export const HeadingRule: BlockMDRule = {
}, },
}; };
// opening fence: 3 or more backticks const CODEBLOCK_MD_1 = '```';
// capture the exact fence length in group 1 const CODEBLOCK_REG_1 = /^`{3}(\S*)\n((?:.*\n)+?)`{3} *(?!.)\n?/m;
// optional info string in group 2
// code content in group 3
// closing fence must match the exact same fence sequence via \1
const CODEBLOCK_REG_1 = /^(`{3,})(?!`)(\S*)\n((?:.*\n)+?)\1 *(?!.)\n?/m;
export const CodeBlockRule: BlockMDRule = { export const CodeBlockRule: BlockMDRule = {
match: (text) => text.match(CODEBLOCK_REG_1), match: (text) => text.match(CODEBLOCK_REG_1),
html: (match) => { html: (match) => {
const [, fence, g1, g2] = match; const [, g1, g2] = match;
// use last identifier after dot, e.g. for "example.json" gets us "json" as language code. const classNameAtt = g1 ? ` class="language-${g1}"` : '';
const langCode = g1 ? g1.substring(g1.lastIndexOf('.') + 1) : null; return `<pre data-md="${CODEBLOCK_MD_1}"><code${classNameAtt}>${g2}</code></pre>`;
const filename = g1 !== langCode ? g1 : null;
const classNameAtt = langCode ? ` class="language-${langCode}"` : '';
const filenameAtt = filename ? ` data-label="${filename}"` : '';
return `<pre data-md="${fence}"><code${classNameAtt}${filenameAtt}>${g2}</code></pre>`;
}, },
}; };
@@ -52,146 +44,55 @@ export const BlockQuoteRule: BlockMDRule = {
}; };
const ORDERED_LIST_MD_1 = '-'; const ORDERED_LIST_MD_1 = '-';
const O_LIST_ITEM_PREFIX = /^(-|[\da-zA-Z]\.) */;
const O_LIST_START = /^([\d])\./;
const O_LIST_TYPE = /^([aAiI])\./;
const O_LIST_TRAILING_NEWLINE = /\n$/;
const ORDERED_LIST_REG_1 = /(^(?:-|[\da-zA-Z]\.) +.+\n?)+/m;
export const OrderedListRule: BlockMDRule = {
match: (text) => text.match(ORDERED_LIST_REG_1),
html: (match, parseInline) => {
const [listText] = match;
const [, listStart] = listText.match(O_LIST_START) ?? [];
const [, listType] = listText.match(O_LIST_TYPE) ?? [];
const lines = listText
.replace(O_LIST_TRAILING_NEWLINE, '')
.split('\n')
.map((lineText) => {
const line = lineText.replace(O_LIST_ITEM_PREFIX, '');
const txt = parseInline ? parseInline(line) : line;
return `<li><p>${txt}</p></li>`;
})
.join('');
const dataMdAtt = `data-md="${listType || listStart || ORDERED_LIST_MD_1}"`;
const startAtt = listStart ? ` start="${listStart}"` : '';
const typeAtt = listType ? ` type="${listType}"` : '';
return `<ol ${dataMdAtt}${startAtt}${typeAtt}>${lines}</ol>`;
},
};
const UNORDERED_LIST_MD_1 = '*'; const UNORDERED_LIST_MD_1 = '*';
const LIST_ITEM_REG = /^( *)([-*]|[\da-zA-Z]\.) +(.+)$/; const U_LIST_ITEM_PREFIX = /^\* */;
type ListType = 'ol' | 'ul'; const U_LIST_TRAILING_NEWLINE = /\n$/;
const UNORDERED_LIST_REG_1 = /(^\* +.+\n?)+/m;
function getListType(marker: string): ListType { export const UnorderedListRule: BlockMDRule = {
return marker === '*' ? 'ul' : 'ol'; match: (text) => text.match(UNORDERED_LIST_REG_1),
}
function getOrderedMeta(marker: string) {
const startMatch = marker.match(/^(\d)\./);
const typeMatch = marker.match(/^([aAiI])\./);
return {
start: startMatch?.[1],
type: typeMatch?.[1],
};
}
interface ParsedLine {
indent: number;
marker: string;
content: string;
listType: ListType;
}
function parseLines(text: string): ParsedLine[] {
return text
.replace(/\n$/, '')
.split('\n')
.map((line) => {
const match = line.match(LIST_ITEM_REG);
if (!match) return null;
const [, spaces, marker, content] = match;
return {
indent: spaces.length,
marker,
content,
listType: getListType(marker),
};
})
.filter(Boolean) as ParsedLine[];
}
function openList(line: ParsedLine) {
if (line.listType === 'ul') {
return `<ul data-md="${UNORDERED_LIST_MD_1}">`;
}
const { type, start } = getOrderedMeta(line.marker);
const dataMdAtt = `data-md="${type || start || ORDERED_LIST_MD_1}"`;
const startAtt = start ? ` start="${start}"` : '';
const typeAtt = type ? ` type="${type}"` : '';
return `<ol ${dataMdAtt}${startAtt}${typeAtt}>`;
}
function closeList(listType: ListType) {
return listType === 'ul' ? '</ul>' : '</ol>';
}
function buildList(lines: ParsedLine[], parseInline?: (s: string) => string): string {
let html = '';
const stack: ('ul' | 'ol')[] = [];
lines.forEach((line, index) => {
const prev = lines[index - 1];
const next = lines[index + 1];
const content = parseInline ? parseInline(line.content) : line.content;
// FIRST ITEM
if (!prev) {
html += openList(line);
stack.push(line.listType);
}
// DEEPER INDENT > open nested list
else if (line.indent > prev.indent) {
html += openList(line);
stack.push(line.listType);
}
// SAME LEVEL
else if (line.indent === prev.indent) {
html += '</li>';
// different list type
if (line.listType !== prev.listType) {
html += closeList(stack.pop()!);
html += openList(line);
stack.push(line.listType);
}
}
// GOING BACK UP
else if (line.indent < prev.indent) {
html += '</li>';
while (stack.length > line.indent + 1) {
html += closeList(stack.pop()!);
html += '</li>';
}
if (line.listType !== stack[stack.length - 1]) {
html += closeList(stack.pop()!);
html += openList(line);
stack.push(line.listType);
}
}
html += `<li><p>${content}</p>`;
// LAST ITEM cleanup
if (!next) {
html += '</li>';
while (stack.length) {
html += closeList(stack.pop()!);
}
}
});
return html;
}
const LIST_REG_1 = /^(?: *(?:[-*]|[\da-zA-Z]\.) +.+\n?)+/m;
export const ListRule: BlockMDRule = {
match: (text) => text.match(LIST_REG_1),
html: (match, parseInline) => { html: (match, parseInline) => {
const [listText] = match; const [listText] = match;
const lines = parseLines(listText); const lines = listText
.replace(U_LIST_TRAILING_NEWLINE, '')
.split('\n')
.map((lineText) => {
const line = lineText.replace(U_LIST_ITEM_PREFIX, '');
const txt = parseInline ? parseInline(line) : line;
return `<li><p>${txt}</p></li>`;
})
.join('');
const html = buildList(lines, parseInline); return `<ul data-md="${UNORDERED_LIST_MD_1}">${lines}</ul>`;
return html;
}, },
}; };
+3 -4
View File
@@ -232,9 +232,8 @@ export function CodeBlock({
opts: HTMLReactParserOptions; opts: HTMLReactParserOptions;
}) { }) {
const code = children[0]; const code = children[0];
const attribs = code instanceof Element && code.name === 'code' ? code.attribs : undefined; const languageClass =
const languageClass = attribs?.class; code instanceof Element && code.name === 'code' ? code.attribs.class : undefined;
const customLabel = attribs?.['data-label'];
const language = const language =
languageClass && languageClass.startsWith('language-') languageClass && languageClass.startsWith('language-')
? languageClass.replace('language-', '') ? languageClass.replace('language-', '')
@@ -263,7 +262,7 @@ export function CodeBlock({
<Header variant="Surface" size="400" className={css.CodeBlockHeader}> <Header variant="Surface" size="400" className={css.CodeBlockHeader}>
<Box grow="Yes"> <Box grow="Yes">
<Text size="L400" truncate> <Text size="L400" truncate>
{customLabel ?? language ?? 'Code'} {language ?? 'Code'}
</Text> </Text>
</Box> </Box>
<Box shrink="No" gap="200"> <Box shrink="No" gap="200">
+1 -7
View File
@@ -1,13 +1,7 @@
import { atom } from 'jotai'; import { atom } from 'jotai';
const STORAGE_KEY = 'settings'; const STORAGE_KEY = 'settings';
export type DateFormat = export type DateFormat = 'D MMM YYYY' | 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD' | '';
| 'D MMM YYYY'
| 'DD/MM/YYYY'
| 'MM/DD/YYYY'
| 'YYYY/MM/DD'
| 'YYYY-MM-DD'
| '';
export type MessageSpacing = '0' | '100' | '200' | '300' | '400' | '500'; export type MessageSpacing = '0' | '100' | '200' | '300' | '400' | '500';
export enum MessageLayout { export enum MessageLayout {
Modern = 0, Modern = 0,
-11
View File
@@ -120,23 +120,12 @@ export const CodeBlockBottomShadow = style({
background: `linear-gradient(to top, #00000022, #00000000)`, background: `linear-gradient(to top, #00000022, #00000000)`,
}); });
const BaseList = style({});
export const List = style([ export const List = style([
BaseList,
DefaultReset, DefaultReset,
MarginSpaced, MarginSpaced,
{ {
padding: `0 ${config.space.S100}`, padding: `0 ${config.space.S100}`,
paddingLeft: config.space.S600, paddingLeft: config.space.S600,
selectors: {
'& &': {
marginTop: config.space.S200,
marginBottom: config.space.S200,
},
'li:last-child &': {
marginBottom: 0,
},
},
}, },
]); ]);
+3 -3
View File
@@ -2,8 +2,8 @@ import { style } from '@vanilla-extract/css';
import { color, toRem } from 'folds'; import { color, toRem } from 'folds';
export const BackgroundDotPattern = style({ export const BackgroundDotPattern = style({
backgroundImage: `radial-gradient(${color.Background.ContainerActive} ${toRem(2)}, ${ backgroundImage: `radial-gradient(${color.Background.ContainerActive} ${toRem(
color.Background.Container 2
} ${toRem(2)})`, )}, transparent ${toRem(2)})`,
backgroundSize: `${toRem(40)} ${toRem(40)}`, backgroundSize: `${toRem(40)} ${toRem(40)}`,
}); });
+1 -1
View File
@@ -71,7 +71,7 @@ const permittedTagToAttributes = {
ul: ['data-md'], ul: ['data-md'],
a: ['name', 'target', 'href', 'rel', 'data-md'], a: ['name', 'target', 'href', 'rel', 'data-md'],
img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'], img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'],
code: ['class', 'data-md', 'data-label'], code: ['class', 'data-md'],
strong: ['data-md'], strong: ['data-md'],
i: ['data-md'], i: ['data-md'],
em: ['data-md'], em: ['data-md'],
+217 -4
View File
@@ -1,7 +1,115 @@
import { createTheme } from '@vanilla-extract/css'; import { createTheme, createThemeContract } from '@vanilla-extract/css';
import { color } from 'folds'; import { color } from 'folds';
export const silverTheme = createTheme(color, { export const extendedColor = createThemeContract({
background: '',
});
const ThemeContract = {
...extendedColor,
...color,
};
export const lightTheme = createTheme(ThemeContract, {
background: '#F2F2F2',
Background: {
Container: '#F2F2F2',
ContainerHover: '#E5E5E5',
ContainerActive: '#D9D9D9',
ContainerLine: '#CCCCCC',
OnContainer: '#000000',
},
Surface: {
Container: '#FFFFFF',
ContainerHover: '#F2F2F2',
ContainerActive: '#E5E5E5',
ContainerLine: '#D9D9D9',
OnContainer: '#000000',
},
SurfaceVariant: {
Container: '#F2F2F2',
ContainerHover: '#E5E5E5',
ContainerActive: '#D9D9D9',
ContainerLine: '#CCCCCC',
OnContainer: '#000000',
},
Primary: {
Main: '#1858D5',
MainHover: '#164FC0',
MainActive: '#144BB5',
MainLine: '#1346AA',
OnMain: '#FFFFFF',
Container: '#E8EEFB',
ContainerHover: '#DCE6F9',
ContainerActive: '#D1DEF7',
ContainerLine: '#C5D5F5',
OnContainer: '#113E95',
},
Secondary: {
Main: '#000000',
MainHover: '#1A1A1A',
MainActive: '#262626',
MainLine: '#333333',
OnMain: '#FFFFFF',
Container: '#D9D9D9',
ContainerHover: '#CCCCCC',
ContainerActive: '#BFBFBF',
ContainerLine: '#B2B2B2',
OnContainer: '#0D0D0D',
},
Success: {
Main: '#00844C',
MainHover: '#007744',
MainActive: '#007041',
MainLine: '#006A3D',
OnMain: '#FFFFFF',
Container: '#E5F3ED',
ContainerHover: '#D9EDE4',
ContainerActive: '#CCE6DB',
ContainerLine: '#BFE0D2',
OnContainer: '#005C35',
},
Warning: {
Main: '#A85400',
MainHover: '#974C00',
MainActive: '#8F4700',
MainLine: '#864300',
OnMain: '#FFFFFF',
Container: '#F6EEE5',
ContainerHover: '#F2E5D9',
ContainerActive: '#EEDDCC',
ContainerLine: '#E9D4BF',
OnContainer: '#763B00',
},
Critical: {
Main: '#C40E0E',
MainHover: '#AC0909',
MainActive: '#A60C0C',
MainLine: '#9C0B0B',
OnMain: '#FFFFFF',
Container: '#F9E7E7',
ContainerHover: '#F6DBDB',
ContainerActive: '#F3CFCF',
ContainerLine: '#F0C3C3',
OnContainer: '#890A0A',
},
Other: {
FocusRing: 'rgba(0 0 0 / 50%)',
Shadow: 'rgba(0 0 0 / 20%)',
Overlay: 'rgba(0 0 0 / 50%)',
},
});
export const silverTheme = createTheme(ThemeContract, {
background: '#DEDEDE',
Background: { Background: {
Container: '#DEDEDE', Container: '#DEDEDE',
ContainerHover: '#D3D3D3', ContainerHover: '#D3D3D3',
@@ -99,6 +207,7 @@ export const silverTheme = createTheme(color, {
}); });
const darkThemeData = { const darkThemeData = {
background: color.Background.Container,
Background: { Background: {
Container: '#1A1A1A', Container: '#1A1A1A',
ContainerHover: '#262626', ContainerHover: '#262626',
@@ -195,9 +304,9 @@ const darkThemeData = {
}, },
}; };
export const darkTheme = createTheme(color, darkThemeData); export const darkTheme = createTheme(ThemeContract, darkThemeData);
export const butterTheme = createTheme(color, { export const butterTheme = createTheme(ThemeContract, {
...darkThemeData, ...darkThemeData,
Background: { Background: {
Container: '#1A1916', Container: '#1A1916',
@@ -236,3 +345,107 @@ export const butterTheme = createTheme(color, {
OnContainer: '#F2EED3', OnContainer: '#F2EED3',
}, },
}); });
export const moonlightTheme = createTheme(ThemeContract, {
background: 'linear-gradient(to top right, #000546, #000)',
Background: {
Container: '#000000',
ContainerHover: '#01032B',
ContainerActive: '#020546',
ContainerLine: '#030977',
OnContainer: '#EAEBFF',
},
Surface: {
Container: '#01032B',
ContainerHover: '#020546',
ContainerActive: '#03075E',
ContainerLine: '#030977',
OnContainer: '#EAEBFF',
},
SurfaceVariant: {
Container: '#020546',
ContainerHover: '#03075E',
ContainerActive: '#030977',
ContainerLine: '#030977',
OnContainer: '#EAEBFF',
},
Primary: {
Main: '#0043FF',
MainHover: '#1A56FF',
MainActive: '#3369FF',
MainLine: '#4D7BFF',
OnMain: '#FFFFFF',
Container: '#00144D',
ContainerHover: '#001B66',
ContainerActive: '#002180',
ContainerLine: '#002899',
OnContainer: '#FFFFFF',
},
Secondary: {
Main: '#E5E7FF',
MainHover: '#CCCFFF',
MainActive: '#B2B6FF',
MainLine: '#999EFF',
OnMain: '#000000',
Container: '#030763',
ContainerHover: '#04097C',
ContainerActive: '#050B94',
ContainerLine: '#050CAD',
OnContainer: '#FFFFFF',
},
Success: {
Main: '#00FF95',
MainHover: '#00E586',
MainActive: '#00CC77',
MainLine: '#4DFFB5',
OnMain: '#0B2E22',
Container: '#004D3B',
ContainerHover: '#00664F',
ContainerActive: '#008062',
ContainerLine: '#008062',
OnContainer: '#CCF2E2',
},
Warning: {
Main: '#FF9933',
MainHover: '#FFA64D',
MainActive: '#FFB266',
MainLine: '#FFBF80',
OnMain: '#000000',
Container: '#4D2600',
ContainerHover: '#663200',
ContainerActive: '#803F00',
ContainerLine: '#994C00',
OnContainer: '#F3E2D1',
},
Critical: {
Main: '#FF0000',
MainHover: '#E50000',
MainActive: '#CC0000',
MainLine: '#FF4D4D',
OnMain: '#ffffff',
Container: '#4D0000',
ContainerHover: '#660000',
ContainerActive: '#800000',
ContainerLine: '#990000',
OnContainer: '#F5D6D6',
},
Other: {
FocusRing: 'hsla(237, 100%, 70%, 0.6)',
Shadow: 'rgba(0, 0, 0, 0.9)',
Overlay: 'rgba(0, 0, 0, 0.8)',
},
});
+1 -8
View File
@@ -22,8 +22,7 @@
--font-secondary: 'InterVariable', var(--font-emoji), sans-serif; --font-secondary: 'InterVariable', var(--font-emoji), sans-serif;
} }
.dark-theme, .global-dark {
.butter-theme {
--tc-link: hsl(213deg 100% 80%); --tc-link: hsl(213deg 100% 80%);
--mx-uc-1: hsl(208, 100%, 75%); --mx-uc-1: hsl(208, 100%, 75%);
@@ -129,9 +128,3 @@ textarea {
audio:not([controls]) { audio:not([controls]) {
display: none !important; display: none !important;
} }
/* Fix Firefox rendering lists that have empty items with those items collapsed in on eachother */
li p::before {
content: '';
display: inline-block;
}
+10
View File
@@ -0,0 +1,10 @@
import { globalStyle } from '@vanilla-extract/css';
import { color } from 'folds';
import { extendedColor } from './colors.css';
globalStyle('body', {
background: extendedColor.background,
borderColor: color.Background.ContainerLine,
outlineColor: color.Background.ContainerLine,
color: color.Background.OnContainer,
});
+1
View File
@@ -5,6 +5,7 @@ import { enableMapSet } from 'immer';
import '@fontsource/inter/variable.css'; import '@fontsource/inter/variable.css';
import 'folds/dist/style.css'; import 'folds/dist/style.css';
import { configClass, varsClass } from 'folds'; import { configClass, varsClass } from 'folds';
import './index.css.ts';
enableMapSet(); enableMapSet();