Merge branch 'dev' into 'main'

Merge version 0.7.3

See merge request mx-moment/moment!6
This commit is contained in:
Maze 2022-01-24 00:22:48 +00:00
commit 9eaf104b48
37 changed files with 1081 additions and 527 deletions

View File

@ -1,34 +0,0 @@
---
name: Bug report
about: 'Report an unexpected behavior '
title: ''
labels: bug
assignees: ''
---
### Description
Describe your issue in details, provide error logs or screenshots if possible.
### Your environment
- OS or distribution (e.g. Arch Linux, macOS 10.15, Windows 7...)
- Architecture (e.g. x86 64bit)
- For Linux users: your desktop environment or window manager (e.g. GNOME 3.34 Wayland, i3 4.17, etc)
- How did you install Mirage? (e.g. manual build, distribution repository, AppImage, Flatpak...)
- For manual installations: your Qt version
- For manual installations: your Python version
### Steps to reproduce
1. Do this...
2. Do that...
### Expected behavior
Tell us what should happen
### Actual behavior
Tell us what happens instead

View File

@ -1,10 +0,0 @@
---
name: Feature request
about: Suggest an idea for the application or project
title: ''
labels: enhancement
assignees: ''
---

View File

@ -1,10 +0,0 @@
---
name: Question
about: Ask a question about the application or project
title: ''
labels: question
assignees: ''
---

2
.gitignore vendored
View File

@ -16,6 +16,8 @@ dist
Makefile
mirage
mirage.pro.user
moment
moment.pro.user
*.AppImage
tags

View File

@ -1,5 +1,4 @@
# Moment
https://img.shields.io/gitlab/v/release/mx-moment/moment
[![Latest release](https://img.shields.io/gitlab/v/release/mx-moment/moment)](https://gitlab.com/mx-moment/moment/-/releases)
[![Built with matrix-nio](https://img.shields.io/badge/built%20with-matrix--nio-brightgreen)](https://github.com/poljar/matrix-nio)
[![#moment-client:matrix.org](https://img.shields.io/matrix/moment-client:matrix.org?color=blueviolet)](https://matrix.to/#/#moment-client:matrix.org)
@ -20,7 +19,7 @@ Written in Qt/QML and Python, **currently in alpha**.
Moment is based on [Mirage](https://github.com/mirukana/mirage),
see differences [here](docs/MIRAGEDIFF.md).
![Chat screenshot](docs/screenshots/01-chat.png?raw=true)
![Chat screenshot](docs/screenshots/m01-chat.png?raw=true)
## Currently Implemented Features
@ -96,10 +95,10 @@ see differences [here](docs/MIRAGEDIFF.md).
## Screenshots
![Sign-in](docs/screenshots/02-sign-in.png)
![Account settings](docs/screenshots/03-account-settings.png)
![Room creation](docs/screenshots/04-create-room.png)
![Chat](docs/screenshots/01-chat.png?raw=true)
![Main pane in small window](docs/screenshots/05-main-pane-small.png)
![Chat in small window](docs/screenshots/06-chat-small.png)
![Room pane in small window](docs/screenshots/07-room-pane-small.png)
![Sign-in](docs/screenshots/m02-sign-in.png)
![Account settings](docs/screenshots/m03-account-settings.png)
![Room creation](docs/screenshots/m04-create-room.png)
![Chat](docs/screenshots/m01-chat.png?raw=true)
![Main pane in small window](docs/screenshots/m05-main-pane-small.png)
![Chat in small window](docs/screenshots/m06-chat-small.png)
![Room pane in small window](docs/screenshots/m07-room-pane-small.png)

View File

@ -6,6 +6,7 @@ The format is based on
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- [0.7.3 (2022-01-24)](#073-2022-01-24)
- [0.7.2 (2021-07-26)](#072-2021-07-26)
- [0.7.1 (2021-03-04)](#071-2021-03-04)
- [0.7.0 (2021-02-28)](#070-2021-02-28)
@ -23,6 +24,44 @@ and this project adheres to
- [0.4.0 (2020-03-21)](#040-2020-03-21)
## 0.7.3 (2022-01-24)
### Added
- [Keybindings page](KEYBINDINGS.md)
- When first starting Moment, offer to migrate config
- Foliage theme
### Changed
- Renamed Mirage to Moment
- Default config, cache and data directories
- Default keybinds:
- quit is now Ctrl+Q
- reply is now Ctrl+R
- remove is now Ctrl+Shift+R
- "focus filter" is now Ctrl+K
- "previous message" is now Ctrl+I
- In-app links lead to
[the Moment repository](https://gitlab.com/mx-moment/moment)
- Default theme is now Foliage.qpl
### Removed
- Appimage: remove Appimage installation method
### Fixed
- Compatibility with Python 3.10
- Minor UI fixes
## 0.7.2 (2021-07-26)
### Added

View File

@ -42,7 +42,7 @@ By sending your changes, you agree to license them under the LGPL 3.0 or later.
### Procedure
Start by forking the main repository from GitHub, then
Start by forking the main repository from GitLab, then
clone your fork and switch to a new branch based on `dev`, in which
you will make your changes:
@ -62,7 +62,7 @@ You will then be able to make a pull request by going
to the [main repo](https://gitlab.com/mx-moment/moment).
Once your pull request is merged, you can update `dev`, and delete your
GitHub and local branch:
GitLab and local branch:
```sh
git fetch upstream

View File

@ -5,7 +5,6 @@ but compiling on Windows and macOS should be possible with the right tools.
- [Packages](#packages)
- [Linux](#linux)
- [AppImage](#appimage)
- [Flatpak](#flatpak)
- [Alpine Linux / postmarketOS](#alpine-linux--postmarketOS)
- [Arch Linux](#arch-linux)
@ -37,22 +36,9 @@ but compiling on Windows and macOS should be possible with the right tools.
For developement, or if none of the package options are satisfying,
see [manual installation](#manual-installation).
Packages other than the AppImage and Flatpak are not maintained by the Mirage
Packages other than the Flatpak are not maintained by the Mirage
authors, and thus might be outdated.
#### AppImage (Leftover instructions from Mirage; not supported for Moment)
For **x86 64bit glibc-based systems**, Mirage is available as an AppImage
on the [release page](https://github.com/mirukana/mirage/releases).
AppImages are single executable files that contain the app and all
its dependencies.
Mirage images are built in Ubuntu 16.04, and should therefore run on any distro
released in April 2016 or later.
[How to start AppImages](https://docs.appimage.org/introduction/quickstart.html#how-to-run-an-appimage)
(TL;DR: `chmod +x Mirage-*.AppImage && ./Mirage-*.AppImage`)
#### Flatpak (Leftover instructions from Mirage; not supported for Moment)
Mirage is also available as a Flatpak.

View File

@ -6,114 +6,114 @@ Keybindings as defined in [the default configuration file](src/config/settings.p
Key | Function
------ | ------
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>C</kbd> | Toggle compact interface
<kbd>Ctrl</kbd> + <kbd>+</kbd> | Zoom in
<kbd>Ctrl</kbd> + <kbd>-</kbd> | Zoom out
<kbd>Ctrl</kbd> + <kbd>=</kbd> | Reset zoom
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Left</kbd> <br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>H</kbd> | Previous tab
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Right</kbd> <br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> | Next tab
<kbd>Ctrl</kbd> + <kbd>Tab</kbd> | Switch to the last opened page
<kbd>Ctrl</kbd> + <kbd>H</kbd> | Earlier page in history (page back)
<kbd>Ctrl</kbd> + <kbd>L</kbd> | Later page in history (page forward)
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>H</kbd> | Toggle notifications, except highlights
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>N</kbd> | Toggle notifications
<kbd>Ctrl + Alt + C</kbd> | Toggle compact interface
<kbd>Ctrl + +</kbd> | Zoom in
<kbd>Ctrl + -</kbd> | Zoom out
<kbd>Ctrl + =</kbd> | Reset zoom
<kbd>Alt + Shift + Left</kbd> <br> <kbd>Alt + Shift + H</kbd> | Previous tab
<kbd>Alt + Shift + Right</kbd> <br> <kbd>Alt + Shift + L</kbd> | Next tab
<kbd>Ctrl + Tab</kbd> | Switch to the last opened page
<kbd>Ctrl + H</kbd> | Earlier page in history (page back)
<kbd>Ctrl + L</kbd> | Later page in history (page forward)
<kbd>Ctrl + Alt + H</kbd> | Toggle notifications, except highlights
<kbd>Ctrl + Alt + N</kbd> | Toggle notifications
<kbd>F1</kbd> | QML developer console
<kbd>Shift</kbd> + <kbd>F1</kbd> | Python debugger
<kbd>Alt</kbd> + <kbd>F1</kbd> | Python remote debugger
<kbd>Ctrl</kbd> + <kbd>Q</kbd> | Quit Moment *
<kbd>Shift + F1</kbd> | Python debugger
<kbd>Alt + F1</kbd> | Python remote debugger
<kbd>Ctrl + Q</kbd> | Quit Moment *
## Scrolling bindings
Key | Function
------ | ------
<kbd>Alt</kbd> + <kbd>Up</kbd> <br> <kbd>Alt</kbd> + <kbd>K</kbd> | Scroll up
<kbd>Alt</kbd> + <kbd>Down</kbd> <br> <kbd>Alt</kbd> + <kbd>J</kbd> | Scroll down
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Up</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>K</kbd> <br> <kbd>PgUp</kbd> | Page up
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Down</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>J</kbd> <br> <kbd>PgDown</kbd> | Page down
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Up</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> <br> <kbd>Home</kbd> | Scroll to top
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Down</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>J</kbd> <br> <kbd>End</kbd> | Scroll to bottom
<kbd>Alt + Up</kbd> <br> <kbd>Alt + K</kbd> | Scroll up
<kbd>Alt + Down</kbd> <br> <kbd>Alt + J</kbd> | Scroll down
<kbd>Ctrl + Alt + Up</kbd> <br> <kbd>Ctrl + Alt + K</kbd> <br> <kbd>PgUp</kbd> | Page up
<kbd>Ctrl + Alt + Down</kbd> <br> <kbd>Ctrl + Alt + J</kbd> <br> <kbd>PgDown</kbd> | Page down
<kbd>Ctrl + Alt + Shift + Up</kbd> <br> <kbd>Ctrl + Alt + Shift + K</kbd> <br> <kbd>Home</kbd> | Scroll to top
<kbd>Ctrl + Alt + Shift + Down</kbd> <br> <kbd>Ctrl + Alt + Shift + J</kbd> <br> <kbd>End</kbd> | Scroll to bottom
## Account bindings
Key | Function
------ | ------
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> | Add new account
<kbd>Alt</kbd> + <kbd>O</kbd> | Collapse current account
<kbd>Alt</kbd> + <kbd>A</kbd> | Current account settings
<kbd>Alt</kbd> + <kbd>P</kbd> | Current account context menu
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>U</kbd> <br> <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>A</kbd> | Unavailable status
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>I</kbd> | Invisible status
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>O</kbd> | Offline status
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>N</kbd> | Previous account
<kbd>Alt</kbd> + <kbd>N</kbd> | Next account
<kbd>Ctrl</kbd> + <kbd>1</kbd> | Switch to account 1
<kbd>Ctrl</kbd> + <kbd>2</kbd> | Switch to account 2
<kbd>Ctrl</kbd> + <kbd>3</kbd> | Switch to account 3
<kbd>Ctrl</kbd> + <kbd>4</kbd> | Switch to account 4
<kbd>Ctrl</kbd> + <kbd>5</kbd> | Switch to account 5
<kbd>Ctrl</kbd> + <kbd>6</kbd> | Switch to account 6
<kbd>Ctrl</kbd> + <kbd>7</kbd> | Switch to account 7
<kbd>Ctrl</kbd> + <kbd>8</kbd> | Switch to account 8
<kbd>Ctrl</kbd> + <kbd>9</kbd> | Switch to account 9
<kbd>Ctrl</kbd> + <kbd>0</kbd> | Switch to account 10
<kbd>Alt + Shift + A</kbd> | Add new account
<kbd>Alt + O</kbd> | Collapse current account
<kbd>Alt + A</kbd> | Current account settings
<kbd>Alt + P</kbd> | Current account context menu
<kbd>Ctrl + Alt + U</kbd> <br> <kbd>Ctrl + Alt + A</kbd> | Unavailable status
<kbd>Ctrl + Alt + I</kbd> | Invisible status
<kbd>Ctrl + Alt + O</kbd> | Offline status
<kbd>Alt + Shift + N</kbd> | Previous account
<kbd>Alt + N</kbd> | Next account
<kbd>Ctrl + 1</kbd> | Switch to account 1
<kbd>Ctrl + 2</kbd> | Switch to account 2
<kbd>Ctrl + 3</kbd> | Switch to account 3
<kbd>Ctrl + 4</kbd> | Switch to account 4
<kbd>Ctrl + 5</kbd> | Switch to account 5
<kbd>Ctrl + 6</kbd> | Switch to account 6
<kbd>Ctrl + 7</kbd> | Switch to account 7
<kbd>Ctrl + 8</kbd> | Switch to account 8
<kbd>Ctrl + 9</kbd> | Switch to account 9
<kbd>Ctrl + 0</kbd> | Switch to account 10
## Room bindings
Key | Function
------ | ------
<kbd>Alt</kbd> + <kbd>C</kbd> | Create a new room (start chat)
<kbd>Alt</kbd> + <kbd>F</kbd> | Focus filter
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>F</kbd> | Clear filter
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Up</kbd> <br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> | Previous room
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Down</kbd> <br> <kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>J</kbd> | Next room
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd> | Previous unread
<kbd>Alt</kbd> + <kbd>U</kbd> | Next unread
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd> | Oldest unread
<kbd>Ctrl</kbd> + <kbd>U</kbd> | Latest unread
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd> | Previous highlight
<kbd>Alt</kbd> + <kbd>M</kbd> | Next highlight
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd> | Oldest highlight
<kbd>Ctrl</kbd> + <kbd>M</kbd> | Latest highlight
<kbd>Alt</kbd> + <kbd>1</kbd> | Room number 1 in account
<kbd>Alt</kbd> + <kbd>2</kbd> | Room number 2 in account
<kbd>Alt</kbd> + <kbd>3</kbd> | Room number 3 in account
<kbd>Alt</kbd> + <kbd>4</kbd> | Room number 4 in account
<kbd>Alt</kbd> + <kbd>5</kbd> | Room number 5 in account
<kbd>Alt</kbd> + <kbd>6</kbd> | Room number 6 in account
<kbd>Alt</kbd> + <kbd>7</kbd> | Room number 7 in account
<kbd>Alt</kbd> + <kbd>8</kbd> | Room number 8 in account
<kbd>Alt</kbd> + <kbd>9</kbd> | Room number 9 in account
<kbd>Alt</kbd> + <kbd>0</kbd> | Room number 10 in account
<kbd>Alt + C</kbd> | Create a new room (start chat)
<kbd>Alt + F</kbd> <br> <kbd>Ctrl + K</kbd> | Focus filter *
<kbd>Alt + Shift + F</kbd> | Clear filter
<kbd>Alt + Shift + Up</kbd> <br> <kbd>Alt + Shift + K</kbd> | Previous room
<kbd>Alt + Shift + Down</kbd> <br> <kbd>Alt + Shift + J</kbd> | Next room
<kbd>Alt + Shift + U</kbd> | Previous unread
<kbd>Alt + U</kbd> | Next unread
<kbd>Ctrl + Shift + U</kbd> | Oldest unread
<kbd>Ctrl + U</kbd> | Latest unread
<kbd>Alt + Shift + M</kbd> | Previous highlight
<kbd>Alt + M</kbd> | Next highlight
<kbd>Ctrl + Shift + M</kbd> | Oldest highlight
<kbd>Ctrl + M</kbd> | Latest highlight
<kbd>Alt + 1</kbd> | Room number 1 in account
<kbd>Alt + 2</kbd> | Room number 2 in account
<kbd>Alt + 3</kbd> | Room number 3 in account
<kbd>Alt + 4</kbd> | Room number 4 in account
<kbd>Alt + 5</kbd> | Room number 5 in account
<kbd>Alt + 6</kbd> | Room number 6 in account
<kbd>Alt + 7</kbd> | Room number 7 in account
<kbd>Alt + 8</kbd> | Room number 8 in account
<kbd>Alt + 9</kbd> | Room number 9 in account
<kbd>Alt + 0</kbd> | Room number 10 in account
(no binding) | Jump to specific room by ID
## Chat bindings
Key | Function
------ | ------
<kbd>Alt</kbd> + <kbd>R</kbd> | Focus room pane
<kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>R</kbd> | Hide room pane
<kbd>Alt</kbd> + <kbd>I</kbd> | Invite members
<kbd>Alt</kbd> + <kbd>Escape</kbd> | Leave current chat
<kbd>Alt</kbd> + <kbd>S</kbd> | Upload file
<kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd> | Send file at clipboard path
<kbd>Alt + R</kbd> | Focus room pane
<kbd>Ctrl + Alt + R</kbd> | Hide room pane
<kbd>Alt + I</kbd> | Invite members
<kbd>Alt + Escape</kbd> | Leave current chat
<kbd>Alt + S</kbd> | Upload file
<kbd>Alt + Shift + S</kbd> | Send file at clipboard path
## Message bindings
Key | Function
------ | ------
<kbd>Ctrl</kbd> + <kbd>Up</kbd> <br> <kbd>Ctrl</kbd> + <kbd>K</kbd> | Focus previous message
<kbd>Ctrl</kbd> + <kbd>Down</kbd> <br> <kbd>Ctrl</kbd> + <kbd>J</kbd> | Focus next message
<kbd>Ctrl</kbd> + <kbd>Space</kbd> | Select focused message
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Space</kbd> | Select messages until here
<kbd>Ctrl</kbd> + <kbd>D</kbd> | Unfocus or deselect
<kbd>Ctrl</kbd> + <kbd>S</kbd> | Display seen tooltips
<kbd>Ctrl</kbd> + <kbd>R</kbd> <br> <kbd>Alt</kbd> + <kbd>Del</kbd> | Remove message
<kbd>Ctrl</kbd> + <kbd>Q</kbd> | Reply
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd> | Debug message
<kbd>Ctrl</kbd> + <kbd>O</kbd> | Open link/file in message
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>O</kbd> | Open link/file externally
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>C</kbd> | Copy downloaded file path
<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>L</kbd> | Clear messages
<kbd>Ctrl + Up</kbd> <br> <kbd>Ctrl + I</kbd> | Focus previous message *
<kbd>Ctrl + Down</kbd> <br> <kbd>Ctrl + J</kbd> | Focus next message
<kbd>Ctrl + Space</kbd> | Select focused message
<kbd>Ctrl + Shift + Space</kbd> | Select messages until here
<kbd>Ctrl + D</kbd> | Unfocus or deselect
<kbd>Ctrl + S</kbd> | Display seen tooltips
<kbd>Ctrl + Shift + R</kbd> <br> <kbd>Alt + Del</kbd> | Remove message *
<kbd>Ctrl + R</kbd> | Reply *
<kbd>Ctrl + Shift + D</kbd> | Debug message
<kbd>Ctrl + O</kbd> | Open link/file in message
<kbd>Ctrl + Shift + O</kbd> | Open link/file externally
<kbd>Ctrl + Shift + C</kbd> | Copy downloaded file path
<kbd>Ctrl + Shift + L</kbd> | Clear messages
## Image viewer bindings
@ -121,20 +121,20 @@ Key | Function
------ | ------
<kbd>X</kbd> <br> <kbd>Q</kbd> | Close image viewer
<kbd>E</kbd> | Expand image viewer
<kbd>F</kbd> <br> <kbd>F11</kbd> <br> <kbd>Alt</kbd> + <kbd>Return</kbd> <br> <kbd>Alt</kbd> + <kbd>Enter</kbd> | Fullscreen image viewer
<kbd>H</kbd> <br> <kbd>Left</kbd> <br> <kbd>Alt</kbd> + <kbd>H</kbd> <br> <kbd>Alt</kbd> + <kbd>Left</kbd> | Pan image left
<kbd>J</kbd> <br> <kbd>Down</kbd> <br> <kbd>Alt</kbd> + <kbd>J</kbd> <br> <kbd>Alt</kbd> + <kbd>Down</kbd> | Pan image down
<kbd>K</kbd> <br> <kbd>Up</kbd> <br> <kbd>Alt</kbd> + <kbd>K</kbd> <br> <kbd>Alt</kbd> + <kbd>Up</kbd> | Pan image up
<kbd>L</kbd> <br> <kbd>Right</kbd> <br> <kbd>Alt</kbd> + <kbd>L</kbd> <br> <kbd>Alt</kbd> + <kbd>Right</kbd> | Pan image right
<kbd>Z</kbd> <br> <kbd>+</kbd> <br> <kbd>Ctrl</kbd> + <kbd>+</kbd> | Zoom in
<kbd>Shift</kbd> + <kbd>Z</kbd> <br> <kbd>-</kbd> <br> <kbd>Ctrl</kbd> + <kbd>-</kbd> | Zoom out
<kbd>Alt</kbd> + <kbd>Z</kbd> <br> <kbd>=</kbd> <br> <kbd>Ctrl</kbd> + <kbd>=</kbd> | Reset zoom
<kbd>F</kbd> <br> <kbd>F11</kbd> <br> <kbd>Alt + Return</kbd> <br> <kbd>Alt + Enter</kbd> | Fullscreen image viewer
<kbd>H</kbd> <br> <kbd>Left</kbd> <br> <kbd>Alt + H</kbd> <br> <kbd>Alt + Left</kbd> | Pan image left
<kbd>J</kbd> <br> <kbd>Down</kbd> <br> <kbd>Alt + J</kbd> <br> <kbd>Alt + Down</kbd> | Pan image down
<kbd>K</kbd> <br> <kbd>Up</kbd> <br> <kbd>Alt + K</kbd> <br> <kbd>Alt + Up</kbd> | Pan image up
<kbd>L</kbd> <br> <kbd>Right</kbd> <br> <kbd>Alt + L</kbd> <br> <kbd>Alt + Right</kbd> | Pan image right
<kbd>Z</kbd> <br> <kbd>+</kbd> <br> <kbd>Ctrl + +</kbd> | Zoom in
<kbd>Shift + Z</kbd> <br> <kbd>-</kbd> <br> <kbd>Ctrl + -</kbd> | Zoom out
<kbd>Alt + Z</kbd> <br> <kbd>=</kbd> <br> <kbd>Ctrl + =</kbd> | Reset zoom
<kbd>R</kbd> | Rotate image right
<kbd>Shift</kbd> + <kbd>R</kbd> | Rotate image left
<kbd>Alt</kbd> + <kbd>R</kbd> | Reset image rotation
<kbd>Shift + R</kbd> | Rotate image left
<kbd>Alt + R</kbd> | Reset image rotation
<kbd>S</kbd> | Speed up gif
<kbd>Shift</kbd> + <kbd>S</kbd> | Slow down gif
<kbd>Alt</kbd> + <kbd>S</kbd> | Reset gif speed
<kbd>Shift + S</kbd> | Slow down gif
<kbd>Alt + S</kbd> | Reset gif speed
<kbd>Space</kbd> | Pause gif
## Security tab bindings
@ -142,8 +142,10 @@ Key | Function
Key | Function
------ | ------
<kbd>Tab</kbd> | Navigate next
<kbd>Shift</kbd> + <kbd>Tab</kbd> | Navigate previous
<kbd>Shift + Tab</kbd> | Navigate previous
<kbd>Space</kbd> | Toggle check
<kbd>Menu</kbd> | Session context menu
<kbd>Alt</kbd> + <kbd>R</kbd> <br> <kbd>F5</kbd> | Refresh session list
<kbd>Alt</kbd> + <kbd>S</kbd> <br> <kbd>Delete</kbd> | Sign out session
<kbd>Alt + R</kbd> <br> <kbd>F5</kbd> | Refresh session list
<kbd>Alt + S</kbd> <br> <kbd>Delete</kbd> | Sign out session
*Binding different than in Mirage

69
docs/MIRAGEDIFF.md Normal file
View File

@ -0,0 +1,69 @@
# Differences between Moment and Mirage
## Maintenance
Due to access issues, it is unlikely
<a href="https://github.com/mirukana/mirage">Mirage</a>
will continue being maintained. You should check the
<a href="https://github.com/mirukana/mirage/commits/master">
date of the last commit</a> to make sure.
## UI
Moment adds a new default theme. If you would rather have the default
Mirage theme instead, add this to `~/.config/moment/settings.py`:
``` python
class General:
theme: str = "Midnight.qpl"
```
## Features
There are currently no differences in features,
although Moment does have some bug fixes that Mirage does not.
## Configuration
Moment has a different configuration directory
(you will get an option to migrate your Mirage
config automatically on first startup).
Moment has different default keybindings:
- <kbd>Ctrl + Q</kbd> is `Quit`
- `Reply` is <kbd>Ctrl + R</kbd> instead of <kbd>Ctrl + Q</kbd>
- `Remove` is <kbd>Ctrl + Shift + R</kbd> instead of <kbd>Ctrl + R</kbd>
- <kbd>Ctrl + K</kbd> is `Focus filter`
- `Focus previous message` is <kbd>Ctrl + I</kbd> instead of <kbd>Ctrl + K</kbd>
If you wish to have `Focus previous message` and `Focus next message`
adjacent on <kbd>Ctrl + U</kbd> and <kbd>Ctrl + I</kbd>,
we recommend swapping <kbd>Ctrl + U</kbd> and <kbd>Ctrl + J</kbd>
by adding this to `~/.config/moment/settings.py`:
``` python
class Keys:
class Rooms:
latest_unread = ["Ctrl+J"]
class Messages:
next = ["Ctrl+Down", "Ctrl+U"]
```
## Reverting to default Mirage bindings
If you wish to revert to default Mirage bindings,
add this to `~/.config/moment/settings.py`:
``` python
class Keys:
quit = []
class Rooms:
focus_filter = ["Alt+F"]
latest_unread = ["Ctrl+J"]
class Messages:
reply = ["Ctrl+Q"]
remove = ["Ctrl+R"]
previous = ["Ctrl+Up", "Ctrl+K"]
```

View File

@ -11,7 +11,7 @@ for example:
Or for Flatpak users:
```sh
cp mirage/src/themes/Midnight.qpl \
cp mirage/src/themes/Foliage.qpl \
~/.var/app/io.github.mirukana.mirage/data/mirage/themes/MyTheme.qpl
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -27,7 +27,7 @@ QRC_FILE = $$BUILD_DIR/resources.qrc
RESOURCES += $$QRC_FILE
HEADERS += $$glob_filenames(*.h) submodules/hsluv-c/src/hsluv.h
SOURCES += $$glob_filenames(*.cpp) submodules/hsluv-c/src/hsluv.c
TARGET = mirage
TARGET = moment
unix:!macx {
LIBS += -lX11 -lXss
@ -61,12 +61,12 @@ no-x11 {
executables.files = $$TARGET
shortcuts.path = $$PREFIX/share/applications
shortcuts.files = packaging/mirage.desktop
shortcuts.files = packaging/moment.desktop
icons256.path = $$PREFIX/share/icons/hicolor/256x256/apps
icons256.files = packaging/mirage.png
icons256.files = packaging/moment.png
examples.path = $$PREFIX/share/examples/mirage
examples.path = $$PREFIX/share/examples/moment
examples.files = src/config/settings.py
INSTALLS += executables shortcuts icons256 examples
@ -101,7 +101,7 @@ for(file, $$list($$glob_filenames(*.py))) {
}
QMAKE_CLEAN *= $$MOC_DIR $$OBJECTS_DIR $$RCC_DIR $$PYCACHE_DIRS $$QRC_FILE
QMAKE_CLEAN *= $$BUILD_DIR $$TARGET Makefile mirage.pro.user .qmake.stash
QMAKE_CLEAN *= $$BUILD_DIR $$TARGET Makefile moment.pro.user .qmake.stash
QMAKE_CLEAN *= $$glob_filenames(*.pyc, *.qmlc, *.jsc, *.egg-info)
QMAKE_CLEAN *= packaging/flatpak/flatpak-env
QMAKE_CLEAN *= packaging/flatpak/flatpak-pip-generator

View File

@ -1,16 +0,0 @@
#!/usr/bin/env sh
set -e
here="$(dirname "$(readlink -f "$0")")"
export RESTORE_LD_LIBRARY_PATH="$LD_LIBRARY_PATH"
export RESTORE_PYTHONHOME="$PYTHONHOME"
export RESTORE_PYTHONUSERBASE="$PYTHONUSERBASE"
export SSL_CERT_FILE="$here/usr/lib/python$PY_XY/site-packages/certifi/cacert.pem"
export LD_LIBRARY_PATH="$here/usr/lib:$LD_LIBRARY_PATH"
export PYTHONHOME="$here/usr"
export PYTHONUSERBASE="$here/usr"
cd "$here"
exec "$here/usr/bin/mirage" "$@"

View File

@ -1,32 +0,0 @@
# AppImage building
The image must be built on Ubuntu 16.04 Xenial, to ensure compatibility with
older systems.
LXD can be used to setup a suitable container from any distro.
If not done already (all default settings are usually fine):
lxd init
Initialize a new container named `ubuntu`:
lxc launch images:ubuntu/xenial/amd64 ubuntu
Now, you can either clone the repo from inside the container...:
lxc exec ubuntu -- apt install -y git
lxc exec ubuntu -- git pull https://github.com/mirukana/mirage
...or directly copy a repository from your local filesystem inside:
lxc exec ubuntu -- /bin/mkdir -p /root/mirage
lxc file push -vr <path to repo root>/* ubuntu/root/mirage
Run the build script inside the container:
lxc exec ubuntu -- /root/mirage/packaging/appimage/build.sh
You can also start a shell inside (e.g. if something goes wrong):
lxc exec ubuntu -- /bin/bash

View File

@ -1,252 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
HERE="$(dirname "$(readlink -f "$0")")"
MIRAGE_REPO_URL='https://github.com/mirukana/mirage'
PY_XYZ=3.9.6
PY_XY="$(cut -d . -f 1-2 <<< "$PY_XYZ")"
check_distro() {
if grep -q '^\s*Ubuntu\s*16.04' /etc/issue; then return; fi
echo "Not running on expected distribution or version, aborting!" >&2
echo "See <repo root>/packaging/appimage/README.md for more info." >&2
exit 99
}
parse_cli_arguments() {
if [ "$1" = --skip-install-prerequisites ] || [ "$1" = -s ]; then
skip_pre=true
else
skip_pre=false
fi
}
setup_dns() {
if ! grep -q 'dns-nameservers 9.9.9.9' /etc/network/interfaces; then
sed -i '/iface eth0 inet dhcp/a dns-nameservers 9.9.9.9' \
/etc/network/interfaces
invoke-rc.d networking restart
fi
}
install_apt_packages() {
apt install -y software-properties-common
add-apt-repository -y ppa:beineri/opt-qt-5.12.9-xenial
add-apt-repository -y ppa:beineri/opt-qt-5.12.9-xenial
apt update -y
apt install -y \
qt512base qt512declarative qt512graphicaleffects \
qt512imageformats qt512quickcontrols2 qt512svg \
zip git wget cmake ccache \
build-essential mesa-common-dev libglu1-mesa-dev freeglut3-dev \
libglfw3-dev libgles2-mesa-dev libssl-dev \
python3-dev python3-setuptools python3-pip libgdbm-dev libc6-dev \
libsqlite3-dev libffi-dev openssl libreadline-dev \
libjpeg-turbo8-dev zlib1g-dev \
libtiff5-dev liblcms2-dev libwebp-dev libopenjp2-7-dev \
libx11-dev libxss-dev libasound2-dev \
pkg-config libdbus-1-dev libglib2.0-dev \
appstream-util desktop-file-utils # for appimage-lint.sh
/usr/sbin/update-ccache-symlinks
}
setup_env() {
set +euo pipefail
# shellcheck disable=SC1091
source /opt/qt512/bin/qt512-env.sh
set -euo pipefail
export PATH="/usr/lib/ccache:$PATH"
export LD_LIBRARY_PATH="$HOME/.local/lib/python$PY_XY/site-packages/PIL/.libs/:$HOME/.local/lib/python$PY_XY/site-packages/.libs_cffi_backend/:/usr/lib/x86_64-linux-gnu/:/usr/lib:$LD_LIBRARY_PATH"
export PREFIX=/usr
export CFLAGS="-march=x86-64 -O2 -pipe -fPIC"
export CXXFLAGS="$CFLAGS"
export MAKEFLAGS="-j$(($(nproc) + 1))"
}
install_python() {
cd ~
if ! [ -d ~/.pyenv ]; then
wget -O - https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash
fi
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
set +euo pipefail
eval "$(pyenv init --path)"
set -euo pipefail
export PYTHON_CFLAGS="$CFLAGS"
export PYTHON_CONFIGURE_OPTS='--enable-shared --enable-optimizations --with-lto'
pyenv update
pyenv install --verbose --skip-existing $PY_XYZ
pyenv global $PY_XYZ
}
install_olm() {
cd ~
rm -rf olm-master.tar.gz olm-master
wget 'https://gitlab.matrix.org/matrix-org/olm/-/archive/master/olm-master.tar.gz'
tar xf olm-master.tar.gz
cd olm-master
make clean
cmake . -Bbuild
cmake --build build
make install
}
install_pyotherside() {
cd ~
if ! [ -f 1.5.9.tar.gz ]; then
wget 'https://github.com/thp/pyotherside/archive/1.5.9.tar.gz'
fi
tar xf 1.5.9.tar.gz
cd pyotherside-1.5.9
make clean
find . -name Makefile -delete
qmake
make install
}
get_app_and_pip_dependencies() {
cd ~
if ! [ -d mirage ]; then
git clone --recursive "$MIRAGE_REPO_URL"
fi
cd mirage
if pip3 show Pillow; then pip3 uninstall Pillow --yes; fi
pip3 install Pillow --no-binary :all:
pip3 install --user -Ur requirements.txt
pip3 install --user -U certifi
}
initialize_appdir() {
cd ~/mirage
rm -rf .qmake.stash Makefile build
qmake mirage.pro PREFIX=/usr
make install INSTALL_ROOT=build/appdir
}
complete_appdir() {
cd ~/mirage/build
cp -r ~/.pyenv/versions/$PY_XYZ/* appdir/usr
cp -r "$HOME/.local/lib/python$PY_XY/site-packages/"* \
"appdir/usr/lib/python$PY_XY/site-packages"
cd ~/mirage/build/appdir/usr/lib
ln -s "python$PY_XY/site-packages/Pillow.libs/"* .
cd ~/mirage/build
if ! [ -f ~/linuxdeployqt.AppImage ]; then
wget 'https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage' \
-O ~/linuxdeployqt.AppImage
fi
chmod +x ~/linuxdeployqt.AppImage
~/linuxdeployqt.AppImage appdir/usr/share/applications/mirage.desktop \
-bundle-non-qt-libs -qmldir=../src/gui
mkdir -p appdir/usr/share/metainfo
cp ~/mirage/packaging/mirage.appdata.xml appdir/usr/share/metainfo
cp /opt/qt512/qml/io/thp/pyotherside/qmldir \
appdir/usr/qml/io/thp/pyotherside
# Remove useless heavy test data
rm -rf "appdir/usr/lib/python$PY_XY/test"
rm -rf "appdir/usr/lib/python$PY_XY/site-packages/Crypto/SelfTest/"
# Remove python cache files
find appdir -name '*.pyc' -delete
}
fix_apprun_launcher() {
cd ~/mirage/build/appdir
rm -f AppRun # because it's a symlink
sed "s/\\\$PY_XY/$PY_XY/" "$HERE/AppRun.sh" > AppRun
chmod +x AppRun
}
generate_appimage() {
cd ~/mirage/build
if ! [ -f ~/appimagetool.AppImage ]; then
wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" \
-O ~/appimagetool.AppImage
fi
chmod +x ~/appimagetool.AppImage
~/appimagetool.AppImage --no-appstream appdir
}
lint_appdir() {
cd ~
cat << 'EOF' > /usr/local/bin/mimetype
#!/usr/bin/env sh
file --mime-type "$@" | tr -d ';'
EOF
chmod +x /usr/local/bin/mimetype
if ! [ -d pkg2appimage ]; then
git clone https://github.com/AppImage/pkg2appimage
fi
chmod +x pkg2appimage/appdir-lint.sh
cd ~/mirage/build
echo -e "\e[34m\nAppDir linting result:\n\e[0m"
~/pkg2appimage/appdir-lint.sh appdir
}
check_distro
parse_cli_arguments "$@"
setup_dns
if [ "$skip_pre" = false ]; then install_apt_packages; fi
setup_env
if [ "$skip_pre" = false ]; then
install_python
install_olm
install_pyotherside
get_app_and_pip_dependencies
fi
initialize_appdir
complete_appdir
fix_apprun_launcher
generate_appimage
lint_appdir

View File

@ -19,4 +19,4 @@ async_generator >= 1.10, < 2; python_version < "3.7"
dataclasses >= 0.6, < 0.7; python_version < "3.7"
pyfastcopy >= 1.0.3, < 2; python_version < "3.8"
git+https://github.com/mirukana/matrix-nio#egg-matrix-nio[e2e]
git+https://github.com/MRAAGH/matrix-nio#egg-matrix-nio[e2e]

View File

@ -1,7 +1,8 @@
# Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
# and Moment contributors <https://gitlab.com/mx-moment/moment>
# SPDX-License-Identifier: LGPL-3.0-or-later
"""This package provides Mirage's backend side that can interact with the UI.
"""This package provides Moment's backend side that can interact with the UI.
To learn more about how this package works, you might want to check the
documentation in the following modules first:
@ -12,7 +13,7 @@ documentation in the following modules first:
- `nio_callbacks`
"""
__app_name__ = "mirage"
__display_name__ = "Mirage"
__reverse_dns__ = "io.github.mirukana.mirage"
__version__ = "0.7.2"
__app_name__ = "moment"
__display_name__ = "Moment"
__reverse_dns__ = "xyz.mx-moment"
__version__ = "0.7.3"

View File

@ -1,4 +1,5 @@
# Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
# and Moment contributors <https://gitlab.com/mx-moment/moment>
# SPDX-License-Identifier: LGPL-3.0-or-later
import asyncio
@ -104,7 +105,7 @@ class Backend:
media_cache: A matrix media cache for downloaded files.
presences: A `{user_id: Presence}` dict for storing presence info about
matrix users registered on Mirage.
matrix users registered on Moment.
mxc_events: A dict storing media `Event` model items for any account
that have the same mxc URI
@ -137,7 +138,7 @@ class Backend:
DefaultDict(asyncio.Lock) # {room_id: lock}
cache_dir = Path(
os.environ.get("MIRAGE_CACHE_DIR") or self.appdirs.user_cache_dir,
os.environ.get("MOMENT_CACHE_DIR") or self.appdirs.user_cache_dir,
)
self.media_cache: MediaCache = MediaCache(self, cache_dir)
@ -552,7 +553,13 @@ class Backend:
)
api_list = "https://publiclist.anchel.nl/publiclist.json"
try:
response = await session.get(api_list)
except:
await session.close()
print("Unable to fetch", api_list)
return
coros = []
for server in (await response.json()):

View File

@ -1,4 +1,5 @@
# Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
# and Moment contributors <https://gitlab.com/mx-moment/moment>
# SPDX-License-Identifier: LGPL-3.0-or-later
"""Matrix media downloading, caching and retrieval."""
@ -95,7 +96,7 @@ class Media:
<base download folder>/<homeserver domain>/
<file title>_<mxc id>.<file extension>`
```
e.g. `~/.cache/mirage/downloads/matrix.org/foo_Hm24ar11i768b0el.png`.
e.g. `~/.cache/moment/downloads/matrix.org/foo_Hm24ar11i768b0el.png`.
"""
parsed = urlparse(self.mxc)
@ -304,7 +305,7 @@ class Thumbnail(Media):
<file title>_<mxc id>.<file extension>`
```
e.g.
`~/.cache/mirage/thumbnails/matrix.org/32x32/foo_Hm24ar11i768b0el.png`.
`~/.cache/moment/thumbnails/matrix.org/32x32/foo_Hm24ar11i768b0el.png`.
"""
size = self.normalize_size(self.server_size or self.wanted_size)

View File

@ -1,4 +1,5 @@
# Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
# and Moment contributors <https://gitlab.com/mx-moment/moment>
# SPDX-License-Identifier: LGPL-3.0-or-later
"""User data and configuration files definitions."""
@ -197,7 +198,7 @@ class ConfigFile(UserFile):
@property
def path(self) -> Path:
return Path(
os.environ.get("MIRAGE_CONFIG_DIR") or
os.environ.get("MOMENT_CONFIG_DIR") or
self.backend.appdirs.user_config_dir,
) / self.filename
@ -209,7 +210,7 @@ class UserDataFile(UserFile):
@property
def path(self) -> Path:
return Path(
os.environ.get("MIRAGE_DATA_DIR") or
os.environ.get("MOMENT_DATA_DIR") or
self.backend.appdirs.user_data_dir,
) / self.filename
@ -460,7 +461,7 @@ class NewTheme(UserDataFile, PCNFile):
@property
def path(self) -> Path:
data_dir = Path(
os.environ.get("MIRAGE_DATA_DIR") or
os.environ.get("MOMENT_DATA_DIR") or
self.backend.appdirs.user_data_dir,
)
return data_dir / "themes" / self.filename
@ -515,17 +516,17 @@ class Theme(UserDataFile):
@property
def path(self) -> Path:
data_dir = Path(
os.environ.get("MIRAGE_DATA_DIR") or
os.environ.get("MOMENT_DATA_DIR") or
self.backend.appdirs.user_data_dir,
)
return data_dir / "themes" / self.filename
@property
def default_data(self) -> str:
if self.filename in ("Midnight.qpl", "Glass.qpl"):
if self.filename in ("Foliage.qpl", "Midnight.qpl", "Glass.qpl"):
path = f"src/themes/{self.filename}"
else:
path = "src/themes/Midnight.qpl"
path = "src/themes/Foliage.qpl"
try:
byte_content = pyotherside.qrc_get_file_contents(path)

View File

@ -26,13 +26,14 @@ class General:
tooltips_delay: float = 0.7
# Application theme to use.
# Can be the name of a built-in theme (Mirage.qpl or Glass.qpl), or
# the name (including extension) of a file in the user theme folder, which
# is "$XDG_DATA_HOME/mirage/themes" if that environment variable is set,
# else "~/.local/share/mirage/themes".
# Can be the name of a built-in theme (Foliage.qpl, Midnight.qpl or
# Glass.qpl), or the name (including extension) of a file in the user theme
# folder, which is "$XDG_DATA_HOME/moment/themes" if that environment
# variable is set,
# else "~/.local/share/moment/themes".
# For Flatpak, it is
# "~/.var/app/io.github.mirukana.mirage/data/mirage/themes".
theme: str = "Midnight.qpl"
theme: str = "Foliage.qpl"
# Interface scale multiplier, e.g. 0.5 makes everything half-size.
zoom: float = 1.0
@ -204,7 +205,7 @@ class Chat:
auto_play_gif: bool = True
# When clicking on a file in the timeline, open it in an external
# program instead of displaying it using Mirage's interface.
# program instead of displaying it using Moment's interface.
# On Linux, the xdg-open command is called.
click_opens_externally: bool = False
@ -234,7 +235,7 @@ class Keys:
# would need to be pressed.
#
# A list of default bindings can be found at:
# https://github.com/mirukana/mirage/blob/master/docs/KEYBINDINGS.md
# https://gitlab.com/mx-moment/moment/-/blob/main/docs/KEYBINDINGS.md
# Helper functions
@ -280,7 +281,7 @@ class Keys:
qml_console = ["F1"]
# Start the Python backend debugger.
# Mirage must be connected to a terminal for this to work.
# Moment must be connected to a terminal for this to work.
python_debugger = ["Shift+F1"]
# Start the Python backend debugger in remote access mode.
@ -288,8 +289,8 @@ class Keys:
# From any terminal, run `socat readline tcp:127.0.0.1:4444` to connect.
python_remote_debugger = ["Alt+F1"]
# Quit Mirage
quit = []
# Quit Moment
quit = ["Ctrl+Q"]
class Scrolling: # Keys.Scrolling
# Pages and chat timeline scrolling
@ -349,7 +350,7 @@ class Keys:
# When focusing the field, use Tab/Shift+Tab or the arrows to navigate
# the list, Enter to switch to focused account/room, Escape to cancel,
# Menu to open the context menu.
focus_filter = ["Alt+F"]
focus_filter = ["Alt+F","Ctrl+K"]
clear_filter = ["Alt+Shift+F"]
# Switch to the previous/next room in the list.
@ -431,7 +432,7 @@ class Keys:
# Focus the previous/next message in the timeline.
# Keybinds defined below in this section affect the focused message.
# The Menu key can open the context menu for a focused message.
previous = ["Ctrl+Up", "Ctrl+K"]
previous = ["Ctrl+Up", "Ctrl+I"]
next = ["Ctrl+Down", "Ctrl+J"]
# Select the currently focused message, same as clicking on it.
@ -461,12 +462,12 @@ class Keys:
# Remove the selected messages if any, else the focused message if any,
# else the last message you posted.
remove = ["Ctrl+R", "Alt+Del"]
remove = ["Ctrl+Shift+R", "Alt+Del"]
# Reply/cancel reply to the focused message if any,
# else the last message posted by someone else.
# Replying can also be cancelled by pressing Escape.
reply = ["Ctrl+Q"]
reply = ["Ctrl+R"]
# Open the QML developer console for the focused message if any,
# and display the event source.

View File

@ -1,4 +1,5 @@
// Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
// and Moment contributors <https://gitlab.com/mx-moment/moment>
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma Singleton
@ -18,9 +19,9 @@ QtObject {
-h, --help Show this help and exit
Environment variables:
MIRAGE_CONFIG_DIR Override the default configuration folder
MIRAGE_DATA_DIR Override the default application data folder
MIRAGE_CACHE_DIR Override the default cache and downloads folder
MOMENT_CONFIG_DIR Override the default configuration folder
MOMENT_DATA_DIR Override the default application data folder
MOMENT_CACHE_DIR Override the default cache and downloads folder
http_proxy Override the General.proxy setting, see settings.py
`

View File

@ -54,7 +54,7 @@ Rectangle {
icon.name: "documentation"
text: qsTr("Online documentation")
onTriggered: Qt.openUrlExternally(
"https://github.com/mirukana/mirage/tree/master/docs"
"https://gitlab.com/mx-moment/moment/-/tree/main/docs"
)
}

View File

@ -129,7 +129,7 @@ HListView {
Timer {
id: autoSaveTimer
interval: 30000
interval: 3000
onTriggered: root.save()
}

View File

@ -246,7 +246,7 @@ HBox {
Timer {
interval: 1000
running:
fetchServersFutureId === "" &&
// fetchServersFutureId === "" &&
ModelStore.get("homeservers").count === 0
repeat: true

View File

@ -11,7 +11,7 @@ HFlickableColumnPopup {
property string path
readonly property string docs:
"https://github.com/mirukana/mirage/tree/master/docs"
"https://gitlab.com/mx-moment/moment/-/tree/main/docs"
page.footer: AutoDirectionLayout {
CancelButton {

View File

@ -27,7 +27,7 @@ HColumnPopup {
text: qsTr("Report")
icon.name: "report-error"
onClicked: Qt.openUrlExternally(
"https://github.com/mirukana/mirage/blob/master/docs/" +
"https://gitlab.com/mx-moment/moment/-/blob/main/docs/" +
"CONTRIBUTING.md#issues"
)
}

BIN
src/images/foliage.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

View File

@ -1,4 +1,5 @@
// Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
// and Moment contributors <https://gitlab.com/mx-moment/moment>
// SPDX-License-Identifier: LGPL-3.0-or-later
// This file creates the application, registers custom objects for QML
@ -17,6 +18,7 @@
#include <QDir>
#include <QFile>
#include <QLockFile>
#include <QMessageBox>
#include <signal.h>
#ifdef Q_OS_UNIX
@ -97,6 +99,236 @@ void onExitSignal(int signum) {
}
void migrateFile(QDir source, QDir destination, QString fname) {
if (! QFile::copy(source.filePath(fname), destination.filePath(fname)))
qWarning() << "Could not migrate" << fname;
}
void migrateShallowDirectory(
QDir sourceParent, QDir destinationParent, QString dname
) {
if (sourceParent.exists(dname)) {
if (! destinationParent.mkpath(dname)) {
qWarning() << "Could not create directory"
<< destinationParent.filePath(dname);
exit(EXIT_FAILURE);
}
QDir source(sourceParent.filePath(dname));
QDir destination(destinationParent.filePath(dname));
QFileInfoList files = source.entryInfoList();
for (QFileInfo file : files) {
if(file.fileName() == "." || file.fileName() == "..")
continue;
migrateFile(source, destination, file.fileName());
}
}
}
void offerMigrateFromMirage(
QDir configDirMoment, QDir configDirMirage,
QDir dataDirMoment, QDir dataDirMirage
) {
QMessageBox dialog;
dialog.setText("Would you like Moment to re-use your logins, "
"encryption keys, configuration and themes from Mirage?");
dialog.setInformativeText(
"Will copy "+configDirMirage.path()+""+configDirMoment.path()
+"\nWill copy "+dataDirMirage.path()+""+dataDirMoment.path());
dialog.addButton(QMessageBox::Yes);
dialog.addButton(QMessageBox::No);
dialog.addButton(QMessageBox::Cancel);
int result = dialog.exec();
if (result == QMessageBox::Yes) {
qWarning("Migrating config and data from Mirage");
if (! configDirMoment.mkpath(".")) {
qFatal("Could not create config directory");
exit(EXIT_FAILURE);
}
if (! dataDirMoment.mkpath(".")) {
qFatal("Could not create data directory");
exit(EXIT_FAILURE);
}
migrateFile(configDirMirage, configDirMoment, "settings.py");
migrateFile(configDirMirage, configDirMoment, "settings.gui.json");
migrateFile(configDirMirage, configDirMoment, "accounts.json");
migrateFile(dataDirMirage, dataDirMoment, "history.json");
migrateFile(dataDirMirage, dataDirMoment, "state.json");
migrateShallowDirectory(dataDirMirage, dataDirMoment, "themes");
migrateShallowDirectory(dataDirMirage, dataDirMoment, "encryption");
} else if (result == QMessageBox::No) {
// Nothing to do. Proceed with starting the app
qWarning("Not migrating");
return;
} else {
// Neither "Yes" nor "No" was chosen.
// We can't know what the user wants. Just quit.
qWarning("Quitting. You can decide about migration next time.");
exit(EXIT_SUCCESS);
}
}
bool shouldMigrateFromMirage() {
QString genericConfig = QStandardPaths::writableLocation(
QStandardPaths::GenericConfigLocation);
QString genericData = QStandardPaths::writableLocation(
QStandardPaths::GenericDataLocation);
// Check whether Moment config already exists
{
QString customConfigDirMoment(
qEnvironmentVariable("MOMENT_CONFIG_DIR")
);
if (! customConfigDirMoment.isEmpty()) {
// MOMENT_CONFIG_DIR is set.
// Moment would definitely use this as the config directory.
if (QDir(customConfigDirMoment).exists())
// But it already exists.
// So this is not the first time Moment was started.
return false;
} else {
// No MOMENT_CONFIG_DIR, so check the default config directory.
if (QDir(genericConfig + "/moment").exists())
// The default config folder exists.
// So this is not the first time Moment was started.
return false;
}
}
// Check whether Moment data already exists
{
QString customDataDirMoment(qEnvironmentVariable("MOMENT_DATA_DIR"));
if (! customDataDirMoment.isEmpty()) {
// MOMENT_DATA_DIR is set.
// Moment would definitely use this as the data directory.
if (QDir(customDataDirMoment).exists())
// But it already exists.
// So this is not the first time Moment was started.
return false;
} else {
// No MOMENT_DATA_DIR, so check the default data directory.
if (QDir(genericData + "/moment").exists())
// The default data folder exists.
// So this is not the first time Moment was started.
return false;
}
}
// Check whether Mirage config exists
{
QString customConfigDirMirage(
qEnvironmentVariable("MIRAGE_CONFIG_DIR")
);
if (! customConfigDirMirage.isEmpty()) {
// MIRAGE_CONFIG_DIR is set.
// Mirage would definitely use this as the config directory.
if (! QDir(customConfigDirMirage).exists())
// But this directory does not exist.
// So there is nowhere to migrate from.
return false;
} else {
// No MIRAGE_CONFIG_DIR, so check the default config directory.
// Check /matrix-mirage (Debian) first, since it is more specific
if (! QDir(genericConfig + "/matrix-mirage").exists())
// Default Debian config folder does not exist,
// so check whether the normal /mirage exists
if (! QDir(genericConfig + "/mirage").exists())
// No, neither /matrix-mirage nor /mirage exist.
// So there is nowhere to migrate from.
return false;
}
}
// We found out that there is no Moment config dir nor Moment data dir,
// but there is a Mirage config dir which can be migrated from.
// We could also check for Mirage data dir but it doesn't really matter.
// User should definitely be prompted for migration.
return true;
}
void tryMigrateFromMirage() {
QString genericConfig = QStandardPaths::writableLocation(
QStandardPaths::GenericConfigLocation);
QString genericData = QStandardPaths::writableLocation(
QStandardPaths::GenericDataLocation);
QDir configDirMoment, configDirMirage, dataDirMoment, dataDirMirage;
QString customConfigDirMoment(qEnvironmentVariable("MOMENT_CONFIG_DIR"));
configDirMoment = QDir(customConfigDirMoment.isEmpty()
? genericConfig + "/moment" : customConfigDirMoment);
QString customDataDirMoment(qEnvironmentVariable("MOMENT_DATA_DIR"));
dataDirMoment = QDir(customDataDirMoment.isEmpty()
? genericData + "/moment" : customDataDirMoment);
QString customConfigDirMirage(qEnvironmentVariable("MIRAGE_CONFIG_DIR"));
if (! customConfigDirMirage.isEmpty()) {
// MIRAGE_CONFIG_DIR is set.
// Mirage would definitely use this as the config directory.
// So this is where we should migrate from.
configDirMirage = QDir(customConfigDirMirage);
} else {
// No MIRAGE_CONFIG_DIR.
// Check if Mirage default config directory exists
// Check /matrix-mirage (Debian) first
QDir dirDeb(genericConfig + "/matrix-mirage");
if (dirDeb.exists()) {
// Default Debian config dir exists.
// So this is where we should migrate from.
configDirMirage = dirDeb;
} else {
// No /matrix-mirage found, so check /mirage
QDir dir(genericConfig + "/mirage");
if (dir.exists())
// Default config dir exists.
// So this is where we should migrate from.
configDirMirage = dir;
else
// No Mirage config dir found.
// Do not migrate.
return;
}
}
QString customDataDirMirage(qEnvironmentVariable("MIRAGE_DATA_DIR"));
if (! customDataDirMirage.isEmpty()) {
// MIRAGE_DATA_DIR is set.
// Mirage would definitely use this as the data directory.
// So this is where we should migrate from.
dataDirMirage = QDir(customDataDirMirage);
} else {
// No MIRAGE_DATA_DIR.
// Check if Mirage default data directory exists
// Check /matrix-mirage (Debian) first
QDir dirDeb(genericData + "/matrix-mirage");
if (dirDeb.exists()) {
// Default Debian data dir exists.
// So this is where we should migrate from.
dataDirMirage = dirDeb;
} else {
// No /matrix-mirage found, so check /mirage
QDir dir(genericData + "/mirage");
if (dir.exists())
// Default data dir exists.
// So this is where we should migrate from.
dataDirMirage = dir;
else
// No Mirage data dir found.
// Do not migrate.
return;
}
}
offerMigrateFromMirage(
configDirMoment, configDirMirage, dataDirMoment, dataDirMirage
);
}
bool setLockFile(QString configPath) {
QDir settingsFolder(configPath);
@ -130,13 +362,19 @@ int main(int argc, char *argv[]) {
qInstallMessageHandler(loggingHandler);
// Define some basic info about the app before creating the QApplication
QApplication::setOrganizationName("mirage");
QApplication::setApplicationName("mirage");
QApplication::setApplicationDisplayName("Mirage");
QApplication::setApplicationVersion("0.7.2");
QApplication::setOrganizationName("moment");
QApplication::setApplicationName("moment");
QApplication::setApplicationDisplayName("Moment");
QApplication::setApplicationVersion("0.7.3");
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QString customConfigDir(qEnvironmentVariable("MIRAGE_CONFIG_DIR"));
// app needs to be constructed before attempting to migrate
// because migrate displays a popup dialog
QApplication app(argc, argv);
if (shouldMigrateFromMirage()) tryMigrateFromMirage();
QString customConfigDir(qEnvironmentVariable("MOMENT_CONFIG_DIR"));
QString settingsFolder(
customConfigDir.isEmpty() ?
QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)
@ -144,8 +382,8 @@ int main(int argc, char *argv[]) {
customConfigDir
);
// Attempt to create a lockfile in the settings folder
if (! setLockFile(settingsFolder)) return EXIT_SUCCESS;
QApplication app(argc, argv);
// Register handlers for quit signals, e.g. SIGINT/Ctrl-C in unix terminals
signal(SIGINT, onExitSignal);

561
src/themes/Foliage.qpl Normal file
View File

@ -0,0 +1,561 @@
// vim: syntax=qml
// Base variables
real uiScale: window.settings.General.zoom
int minimumSupportedWidth: 240 * uiScale
int minimumSupportedHeight: 120 * uiScale
int contentIsWideAbove: 472 * uiScale
int baseElementsHeight: 36 * uiScale
int spacing: 12 * uiScale
int radius: 4 * uiScale
int animationDuration: 100
real loadingElementsOpacity: 0.8
real disabledElementsOpacity: 0.3
fontSize:
int smaller: 13 * uiScale
int small: 13 * uiScale
int normal: 16 * uiScale
int big: 20 * uiScale
int bigger: 32 * uiScale
int biggest: 48 * uiScale
fontFamily:
string sans: "Roboto"
string mono: "Hack"
// Fonts are comma-separated.
// For example, if you want colorful emoji, you could do:
// string sans: "Roboto,Joypixels"
// string mono: "Hack,Joypixels"
colors:
int hue: 200
real intensity: 1.0
real coloredTextIntensity: intensity * 71
real dimColoredTextIntensity: intensity * 60
int saturation: 40
int bgSaturation: saturation
int coloredTextSaturation: saturation + 20
int dimColoredTextSaturation: saturation
real opacity: 0.7
color weakBackground:
hsluv(hue, bgSaturation, intensity * 2.5, opacity)
color mediumBackground:
hsluv(hue, bgSaturation, intensity * 7, opacity)
color strongBackground:
hsluv(hue, bgSaturation * 2, intensity, opacity)
color accentBackground: hsluv(hue, saturation, intensity * 40, 1)
color accentElement: hsluv(hue, saturation * 1.5, intensity * 52, 1)
color strongAccentElement: hsluv(hue, saturation * 1.5, intensity * 72, 1)
color positiveBackground:
hsluv(155, saturation * 1.5, intensity * 65, 1)
color middleBackground:
hsluv(60, saturation * 1.5, intensity * 65, 1)
color negativeBackground:
hsluv(0, saturation * 1.5, intensity * 54, 1)
color alertBackground: negativeBackground
color brightText: hsluv(0, 0, intensity * 100)
color text: hsluv(0, 0, intensity * 85)
color halfDimText: hsluv(0, 0, intensity * 72)
color dimText: hsluv(0, 0, intensity * 60)
color positiveText: hsluv(155, coloredTextSaturation, coloredTextIntensity)
color warningText: hsluv(60, coloredTextSaturation, coloredTextIntensity)
color errorText: hsluv(0, coloredTextSaturation, coloredTextIntensity)
color accentText: hsluv(hue, coloredTextSaturation, coloredTextIntensity)
color link: hsluv(hue, coloredTextSaturation, coloredTextIntensity)
color code: hsluv(hue + 10, coloredTextSaturation, coloredTextIntensity)
// Example of an animation, set running: true to enable
NumberAnimation on hue
running: false
from: 0
to: 360
duration: 10000
loops: Animation.Infinite
icons:
string preferredPack: "thin"
// "transparent" to disable colorizing
color colorize: hsluv(0, 0, colors.intensity * 90)
color disabledColorize: "white"
int smallDimension: 16 * uiScale
int dimension: 22 * uiScale
// Generic UI controls
controls:
scrollBar:
int width: theme.spacing
color track: colors.strongBackground
color slider: colors.accentElement
color hoveredSlider: colors.accentElement
color pressedSlider: colors.strongAccentElement
int sliderPadding: 2
int sliderRadius: theme.radius
box:
int defaultWidth: minimumSupportedWidth
color background: colors.mediumBackground
int radius: theme.radius
popup:
int defaultWidth: minimumSupportedWidth * 1.75
color background: colors.mediumBackground
color opaqueBackground: hsluv(
colors.hue,
colors.bgSaturation,
colors.intensity * 7,
1
)
color windowOverlay: hsluv(0, 0, 0, 0.7)
header:
color background: colors.strongBackground
button:
color background: colors.strongBackground
color text: colors.text
color focusedBorder: colors.accentElement
int focusedBorderWidth: 2
color hoveredOverlay: hsluv(0, 0, 50, 0.2)
color pressedOverlay: hsluv(0, 0, 50, 0.5)
color checkedOverlay: colors.accentBackground
tab:
color text: controls.button.text
color background: controls.button.background
color alternateBackground: hsluv(
colors.hue,
colors.bgSaturation * 1.25,
colors.intensity * 4,
Math.max(0.6, colors.opacity)
)
color bottomLine: background
color focusedBorder: colors.accentElement
int focusedBorderWidth: 1
color hoveredOverlay: controls.button.hoveredOverlay
color pressedOverlay: controls.button.pressedOverlay
color checkedOverlay: controls.button.checkedOverlay
menu:
color background: hsluv(
colors.hue,
colors.bgSaturation * 2,
colors.intensity,
Math.max(0.9, colors.opacity),
)
color border: "black"
real borderWidth: 2
menuItem:
color background: "transparent"
color text: controls.button.text
color hoveredOverlay: controls.button.hoveredOverlay
color pressedOverlay: controls.button.hoveredOverlay
color checkedOverlay: controls.button.hoveredOverlay
checkBox:
color checkIconColorize: colors.accentElement
color boxBackground: controls.button.background
int boxSize: 24 * uiScale
color boxBorder: "black"
color boxHoveredBorder: colors.accentElement
color boxPressedBorder: colors.strongAccentElement
color text: controls.button.text
color subtitle: colors.dimText
listView:
color highlight: hsluv(
colors.hue,
colors.bgSaturation * 2,
colors.intensity * 1,
colors.opacity / 1.5,
)
color highlightBorder: colors.strongAccentElement
int highlightBorderThickness: 1
textField:
color background: colors.strongBackground
color focusedBackground: background
int borderWidth: 1
color border: "transparent"
color focusedBorder: colors.accentElement
color errorBorder: colors.negativeBackground
color text: colors.text
color focusedText: colors.text
color placeholderText: colors.dimText
textArea:
color background: colors.strongBackground
int borderWidth: 1
color border: "transparent"
color focusedBorder: colors.accentElement
color errorBorder: colors.negativeBackground
color text: colors.text
color placeholderText: controls.textField.placeholderText
toolTip:
color background: colors.strongBackground
color text: colors.text
color border: "black"
int borderWidth: 2
progressBar:
int height: Math.max(2, spacing / 2)
color background: colors.strongBackground
color foreground: colors.accentElement
color pausedForeground: colors.middleBackground
color errorForeground: colors.negativeBackground
circleProgressBar:
int thickness: Math.max(2, spacing / 2)
color background: colors.strongBackground
color foreground: colors.accentElement
color errorForeground: colors.negativeBackground
color text: colors.text
real indeterminateSpan: 0.5 // 0-1
slider:
int radius: 2
int height: controls.progressBar.height
color background: controls.progressBar.background
color foreground: controls.progressBar.foreground
handle:
int size: 20
color inside: hsluv(0, 0, 90)
color pressedInside: "white"
color border: "black"
color pressedBorder: colors.strongAccentElement
avatar:
int size: baseElementsHeight
int compactSize: baseElementsHeight / 2
int radius: theme.radius
hoveredImage:
int size: 192
color background: hsluv(0, 0, 0, 0.4)
background:
int saturation: colors.saturation
int lightness: Math.min(50, colors.intensity * 25)
real opacity: 1.0
letter:
int saturation: colors.saturation + 20
int lightness: colors.intensity * 60
real opacity: 1.0
displayName:
int saturation: colors.coloredTextSaturation
int lightness: colors.coloredTextIntensity
int dimSaturation: colors.dimColoredTextSaturation
int dimLightness: colors.dimColoredTextIntensity
presence:
color online: colors.positiveBackground
color unavailable: colors.middleBackground
color offline: hsluv(0, 0, 60, 1)
color border: "black"
int borderWidth: 2 * uiScale
real opacity: 1.0
real radius: 6.0 * uiScale
// Specific interface parts
ui:
// The background image can be an URL or local file path
// (in the form file://<path>, e.g. file:///home/user/images/foo.png).
// If not specified, the gradient will be shown instead.
url image: "../../images/foliage.jpg"
point gradientStart: Qt.point(0, 0)
point gradientEnd: Qt.point(window.width, window.height)
color gradientStartColor:
hsluv(colors.hue, 100, colors.intensity * 8)
color gradientEndColor:
hsluv(colors.hue + 50, 30, colors.intensity * 22)
// To have a solid color instead,
// set gradientStartColor and gradientEndColor to the same value, e.g.:
// color gradientStartColor: hsluv(0, 0, 0, 0.5)
// color gradientEndColor: hsluv(0, 0, 0, 0.5)
mainPane:
color background: "transparent"
topBar:
color background: colors.strongBackground
color nameVersionLabel: colors.text
accountBar:
color background: colors.mediumBackground
account:
color selectedBackground: colors.accentBackground
real selectedBackgroundOpacity: 0.3
color selectedBorder: colors.strongAccentElement
int selectedBorderSize: 1
unreadIndicator:
color background: colors.strongBackground
color text: colors.accentText
bool bold: false
color border: Qt.darker(text, 2)
int borderWidth: 1
int radius: theme.radius / 2
color highlightBackground: colors.strongBackground
color highlightText: colors.errorText
bool highlightBold: false
color highlightBorder: Qt.darker(highlightText, 2)
int highlightBorderWidth: 1
int highlightRadius: theme.radius / 2
listView:
color background: colors.mediumBackground
real offlineOpacity: 0.5
account:
real collapsedOpacity: 0.3
color background: "transparent"
color name: colors.text
int avatarRadius: controls.avatar.radius
int collapsedAvatarRadius: controls.avatar.size / 2
room:
real leftRoomOpacity: 0.65
color background: "transparent"
color name: colors.text
color unreadName: colors.brightText
color lastEventDate: colors.halfDimText
color subtitle: colors.dimText
color subtitleQuote: chat.message.quote
int avatarRadius: controls.avatar.radius
int collapsedAvatarRadius: controls.avatar.radius
unreadIndicator:
color background: colors.strongBackground
color text: colors.accentText
bool bold: false
color border: Qt.darker(text, 2)
int borderWidth: 1
int radius: theme.radius / 2
color highlightBackground: colors.strongBackground
color highlightText: colors.errorText
bool highlightBold: false
color highlightBorder: Qt.darker(highlightText, 2)
int highlightBorderWidth: 1
int highlightRadius: theme.radius / 2
bottomBar:
color background: "transparent"
color settingsButtonBackground: colors.strongBackground
color filterFieldBackground: colors.strongBackground
chat:
roomHeader:
color background: controls.header.background
color name: colors.text
color topic: colors.dimText
roomPane:
color background: "transparent"
topBar:
color background: colors.strongBackground
listView:
color background: colors.mediumBackground
member:
real invitedOpacity: 0.5
color background: "transparent"
color name: colors.text
color subtitle: colors.dimText
color adminIcon: hsluv(60, colors.saturation * 2.25, 60)
color moderatorIcon: adminIcon
color invitedIcon: hsluv(0, colors.saturation * 2.25, 60)
roomSettings:
color background: colors.mediumBackground
bottomBar:
color background: colors.strongBackground
inviteButton:
color background: "transparent"
filterMembers:
color background: "transparent"
eventList:
color background: "transparent"
message:
int avatarSize: 56 * uiScale
int collapsedAvatarSize: 32 * uiScale
int avatarRadius: controls.avatar.radius
int radius: theme.radius
int horizontalSpacing: theme.spacing / 1.25
int verticalSpacing: theme.spacing / 1.75
color focusedHighlight: colors.accentBackground
real focusedHighlightOpacity: 0.4
color background: colors.weakBackground
color ownBackground: colors.mediumBackground
color checkedBackground: colors.accentBackground
color body: colors.text
color date: colors.dimText
color localEcho: colors.dimText
color readCounter: colors.accentText
color redactedBody: colors.dimText
color noticeBody: colors.halfDimText
int noticeLineWidth: 1 * uiScale
color quote: hsluv(
135, colors.coloredTextSaturation, colors.coloredTextIntensity,
)
color link: colors.link
color code: colors.code
string styleSheet:
"* { white-space: pre-wrap }" +
"a { color: " + link + " }" +
"p { margin-top: 0 }" +
"code { font-family: " + fontFamily.mono + "; " +
"color: " + code + " }" +
"h1, h2, h3 { font-weight: normal }" +
"h1 { font-size: " + fontSize.biggest + "px }" +
"h2 { font-size: " + fontSize.bigger + "px }" +
"h3 { font-size: " + fontSize.big + "px }" +
"h4 { font-size: " + fontSize.normal + "px }" +
"h5 { font-size: " + fontSize.small + "px }" +
"h6 { font-size: " + fontSize.smaller + "px }" +
"table { margin-top: " + theme.spacing + "px; " +
" margin-bottom: " + theme.spacing + "px }" +
"td { padding-left: " + theme.spacing / 2 + "px; " +
" padding-right: " + theme.spacing / 2 + "px; " +
" padding-top: " + theme.spacing / 4 + "px; " +
" padding-bottom: " + theme.spacing / 4 + "px } " +
"li { margin-top: " + theme.spacing / 2 + "px; " +
" margin-bottom: " + theme.spacing / 2 + "px; }" +
".sender { margin-bottom: " + spacing / 2 + " }" +
".quote { color: " + quote + " }" +
".mention { text-decoration: none; }" +
".room-id-mention, .room-alias-mention { font-weight: bold; }"
string styleInclude:
'<style type"text/css">\n' + styleSheet + '\n</style>\n'
real thumbnailCheckedOverlayOpacity: 0.4
daybreak:
color background: colors.mediumBackground
color text: colors.text
int radius: theme.radius
inviteBanner:
color background: colors.mediumBackground
leftBanner:
color background: colors.mediumBackground
unknownDevices:
color background: colors.mediumBackground
typingMembers:
color background: hsluv(
colors.hue, colors.saturation, colors.intensity * 9, 0.52
)
replyBar:
color background: chat.typingMembers.background
fileTransfer:
color background: chat.typingMembers.background
userAutoCompletion:
color background: chat.typingMembers.background
int avatarsRadius: controls.avatar.radius
color displayNames: colors.text
color userIds: colors.dimText
composer:
color background: colors.strongBackground
uploadButton:
color background: "transparent"
mediaPlayer:
hoverPreview:
int maxHeight: 192
progress:
int height: 8
color background: hsluv(0, 0, 0, 0.5)
controls:
int iconSize: icons.dimension
int volumeSliderWidth: 100
int speedSliderWidth: 100
color background: hsluv(
colors.hue, colors.saturation * 1.25, colors.intensity * 2, 0.85,
)