Compare commits
26 Commits
v0.0.11-al
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93fc47d019 | ||
|
|
a32149f604 | ||
|
|
3149f79d11 | ||
|
|
7ccb1cbf2c | ||
|
|
1ec311590d | ||
|
|
0ddded3b8b | ||
|
|
a8cbc352ff | ||
|
|
dfa0937077 | ||
|
|
43485270ee | ||
|
|
28fea03625 | ||
|
|
e021d4a55d | ||
|
|
b01dbe5a5d | ||
|
|
4b2382bf93 | ||
|
|
0f2442566f | ||
|
|
8c9a2714a1 | ||
|
|
d44f861871 | ||
|
|
14aa97251c | ||
|
|
55456dbc1e | ||
|
|
d5c330ac72 | ||
|
|
7b1dc93f3a | ||
|
|
745f547904 | ||
|
|
6ebb7ac7fd | ||
|
|
1bb93c18fb | ||
|
|
e3090e537f | ||
|
|
ad10082c2f | ||
|
|
67603d0623 |
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -45,3 +45,25 @@ jobs:
|
|||||||
reporter: 'github-check'
|
reporter: 'github-check'
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --locked
|
run: cargo test --locked
|
||||||
|
|
||||||
|
nix-flake-test:
|
||||||
|
name: Flake checks ❄️
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [ubuntu-latest, macos-latest]
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- uses: cachix/install-nix-action@v31
|
||||||
|
with:
|
||||||
|
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- uses: cachix/cachix-action@v15
|
||||||
|
with:
|
||||||
|
name: iamb-prs
|
||||||
|
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||||
|
- name: Flake check
|
||||||
|
run: |
|
||||||
|
nix flake show
|
||||||
|
nix flake check --print-build-logs
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
unstable_features = true
|
unstable_features = true
|
||||||
max_width = 100
|
max_width = 100
|
||||||
fn_call_width = 90
|
fn_call_width = 88
|
||||||
struct_lit_width = 50
|
struct_lit_width = 50
|
||||||
struct_variant_width = 50
|
struct_variant_width = 50
|
||||||
chain_width = 75
|
chain_width = 75
|
||||||
|
|||||||
2331
Cargo.lock
generated
2331
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "iamb"
|
name = "iamb"
|
||||||
version = "0.0.11-alpha.1"
|
version = "0.0.11"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Ulyssa <git@ulyssa.dev>"]
|
authors = ["Ulyssa <git@ulyssa.dev>"]
|
||||||
repository = "https://github.com/ulyssa/iamb"
|
repository = "https://github.com/ulyssa/iamb"
|
||||||
@@ -11,7 +11,7 @@ license = "Apache-2.0"
|
|||||||
exclude = [".github", "CONTRIBUTING.md"]
|
exclude = [".github", "CONTRIBUTING.md"]
|
||||||
keywords = ["matrix", "chat", "tui", "vim"]
|
keywords = ["matrix", "chat", "tui", "vim"]
|
||||||
categories = ["command-line-utilities"]
|
categories = ["command-line-utilities"]
|
||||||
rust-version = "1.83"
|
rust-version = "1.88"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@@ -65,6 +65,7 @@ url = {version = "^2.2.2", features = ["serde"]}
|
|||||||
edit = "0.1.4"
|
edit = "0.1.4"
|
||||||
humansize = "2.0.0"
|
humansize = "2.0.0"
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
|
shellexpand = "3.1.1"
|
||||||
|
|
||||||
[dependencies.comrak]
|
[dependencies.comrak]
|
||||||
version = "0.22.0"
|
version = "0.22.0"
|
||||||
@@ -78,18 +79,18 @@ features = ["zbus", "serde"]
|
|||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[dependencies.modalkit]
|
[dependencies.modalkit]
|
||||||
version = "0.0.23"
|
version = "0.0.24"
|
||||||
default-features = false
|
default-features = false
|
||||||
#git = "https://github.com/ulyssa/modalkit"
|
#git = "https://github.com/ulyssa/modalkit"
|
||||||
#rev = "e40dbb0bfeabe4cfd08facd2acb446080a330d75"
|
#rev = "e40dbb0bfeabe4cfd08facd2acb446080a330d75"
|
||||||
|
|
||||||
[dependencies.modalkit-ratatui]
|
[dependencies.modalkit-ratatui]
|
||||||
version = "0.0.23"
|
version = "0.0.24"
|
||||||
#git = "https://github.com/ulyssa/modalkit"
|
#git = "https://github.com/ulyssa/modalkit"
|
||||||
#rev = "e40dbb0bfeabe4cfd08facd2acb446080a330d75"
|
#rev = "e40dbb0bfeabe4cfd08facd2acb446080a330d75"
|
||||||
|
|
||||||
[dependencies.matrix-sdk]
|
[dependencies.matrix-sdk]
|
||||||
version = "0.10.0"
|
version = "0.14.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["e2e-encryption", "sqlite", "sso-login"]
|
features = ["e2e-encryption", "sqlite", "sso-login"]
|
||||||
|
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -51,6 +51,15 @@ url = "https://example.com"
|
|||||||
user_id = "@user:example.com"
|
user_id = "@user:example.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Installation (from source)
|
||||||
|
|
||||||
|
Install Rust and Cargo using [rustup], and then run from the directory
|
||||||
|
containing the sources (ie: from a git clone):
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo install --locked --path .
|
||||||
|
```
|
||||||
|
|
||||||
## Installation (via `crates.io`)
|
## Installation (via `crates.io`)
|
||||||
|
|
||||||
Install Rust (1.83.0 or above) and Cargo, and then run:
|
Install Rust (1.83.0 or above) and Cargo, and then run:
|
||||||
@@ -145,3 +154,4 @@ iamb is released under the [Apache License, Version 2.0].
|
|||||||
[crates-io-iamb]: https://crates.io/crates/iamb
|
[crates-io-iamb]: https://crates.io/crates/iamb
|
||||||
[iamb.chat]: https://iamb.chat
|
[iamb.chat]: https://iamb.chat
|
||||||
[well_known_entry]: https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixclient
|
[well_known_entry]: https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixclient
|
||||||
|
[rustup]: https://rustup.rs/
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ View a list of unread rooms.
|
|||||||
Mark all rooms as read.
|
Mark all rooms as read.
|
||||||
.It Sy ":welcome"
|
.It Sy ":welcome"
|
||||||
View the startup Welcome window.
|
View the startup Welcome window.
|
||||||
|
.It Sy ":forget"
|
||||||
|
Remove all left rooms from the internal database.
|
||||||
.El
|
.El
|
||||||
|
|
||||||
.Sh "E2EE COMMANDS"
|
.Sh "E2EE COMMANDS"
|
||||||
@@ -114,6 +116,8 @@ Redact the selected message with the optional reason.
|
|||||||
Reply to the selected message.
|
Reply to the selected message.
|
||||||
.It Sy ":cancel"
|
.It Sy ":cancel"
|
||||||
Cancel the currently drafted message including replies.
|
Cancel the currently drafted message including replies.
|
||||||
|
.It Sy ":replied"
|
||||||
|
Go to the message the current message replied to.
|
||||||
.It Sy ":upload [path]"
|
.It Sy ":upload [path]"
|
||||||
Upload an attachment and send it to the currently selected room.
|
Upload an attachment and send it to the currently selected room.
|
||||||
.El
|
.El
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<url type="homepage">https://iamb.chat</url>
|
<url type="homepage">https://iamb.chat</url>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.0.11" date="2026-01-19"/>
|
||||||
<release version="0.0.10" date="2024-08-20"/>
|
<release version="0.0.10" date="2024-08-20"/>
|
||||||
<release version="0.0.9" date="2024-03-28"/>
|
<release version="0.0.9" date="2024-03-28"/>
|
||||||
</releases>
|
</releases>
|
||||||
|
|||||||
84
flake.lock
generated
84
flake.lock
generated
@@ -1,5 +1,41 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"crane": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1759893430,
|
||||||
|
"narHash": "sha256-yAy4otLYm9iZ+NtQwTMEbqHwswSFUbhn7x826RR6djw=",
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"rev": "1979a2524cb8c801520bd94c38bb3d5692419d93",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fenix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1760510549,
|
||||||
|
"narHash": "sha256-NP+kmLMm7zSyv4Fufv+eSJXyqjLMUhUfPT6lXRlg/bU=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"rev": "ef7178cf086f267113b5c48fdeb6e510729c8214",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
@@ -20,11 +56,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736883708,
|
"lastModified": 1760284886,
|
||||||
"narHash": "sha256-uQ+NQ0/xYU0N1CnXsa2zghgNaOPxWpMJXSUJJ9W7140=",
|
"narHash": "sha256-TK9Kr0BYBQ/1P5kAsnNQhmWWKgmZXwUQr4ZMjCzWf2c=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "eb62e6aa39ea67e0b8018ba8ea077efe65807dc8",
|
"rev": "cf3f5c4def3c7b5f1fc012b3d839575dbe552d43",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -34,44 +70,28 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1736320768,
|
|
||||||
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"crane": "crane",
|
||||||
|
"fenix": "fenix",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs"
|
||||||
"rust-overlay": "rust-overlay"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-overlay": {
|
"rust-analyzer-src": {
|
||||||
"inputs": {
|
"flake": false,
|
||||||
"nixpkgs": "nixpkgs_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736994333,
|
"lastModified": 1760457219,
|
||||||
"narHash": "sha256-v4Jrok5yXsZ6dwj2+2uo5cSyUi9fBTurHqHvNHLT1XA=",
|
"narHash": "sha256-WJOUGx42hrhmvvYcGkwea+BcJuQJLcns849OnewQqX4=",
|
||||||
"owner": "oxalica",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-analyzer",
|
||||||
"rev": "848db855cb9e88785996e961951659570fc58814",
|
"rev": "8747cf81540bd1bbbab9ee2702f12c33aa887b46",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "oxalica",
|
"owner": "rust-lang",
|
||||||
"repo": "rust-overlay",
|
"ref": "nightly",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
116
flake.nix
116
flake.nix
@@ -5,41 +5,107 @@
|
|||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
crane.url = "github:ipetkov/crane";
|
||||||
|
fenix = {
|
||||||
|
url = "github:nix-community/fenix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, rust-overlay, ... }:
|
outputs =
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
|
||||||
let
|
|
||||||
# We only need the nightly overlay in the devShell because .rs files are formatted with nightly.
|
|
||||||
overlays = [ (import rust-overlay) ];
|
|
||||||
pkgs = import nixpkgs { inherit system overlays; };
|
|
||||||
rustNightly = pkgs.rust-bin.nightly."2024-12-12".default;
|
|
||||||
in
|
|
||||||
with pkgs;
|
|
||||||
{
|
{
|
||||||
packages.default = rustPlatform.buildRustPackage {
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
crane,
|
||||||
|
flake-utils,
|
||||||
|
fenix,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
|
rustToolchain = fenix.packages.${system}.fromToolchainFile {
|
||||||
|
file = ./rust-toolchain.toml;
|
||||||
|
# When the file changes, this hash must be updated.
|
||||||
|
sha256 = "sha256-Qxt8XAuaUR2OMdKbN4u8dBJOhSHxS+uS06Wl9+flVEk=";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Nightly toolchain for rustfmt (pinned to current flake lock)
|
||||||
|
# Note that the github CI uses "current nightly" for formatting, it 's not pinned.
|
||||||
|
rustNightly = fenix.packages.${system}.latest;
|
||||||
|
|
||||||
|
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
|
||||||
|
craneLibNightly = (crane.mkLib pkgs).overrideToolchain rustNightly.toolchain;
|
||||||
|
|
||||||
|
src = lib.fileset.toSource {
|
||||||
|
root = ./.;
|
||||||
|
fileset = lib.fileset.unions [
|
||||||
|
(craneLib.fileset.commonCargoSources ./.)
|
||||||
|
./src/windows/welcome.md
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
commonArgs = {
|
||||||
|
inherit src;
|
||||||
|
strictDeps = true;
|
||||||
pname = "iamb";
|
pname = "iamb";
|
||||||
version = self.shortRev or self.dirtyShortRev;
|
version = self.shortRev or self.dirtyShortRev;
|
||||||
src = ./.;
|
|
||||||
cargoLock = {
|
|
||||||
lockFile = ./Cargo.lock;
|
|
||||||
};
|
|
||||||
nativeBuildInputs = [ pkg-config ];
|
|
||||||
buildInputs = [ openssl ] ++ lib.optionals stdenv.isDarwin
|
|
||||||
(with darwin.apple_sdk.frameworks; [ AppKit Security Cocoa ]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
devShell = mkShell {
|
# Build *just* the cargo dependencies, so we can reuse
|
||||||
buildInputs = [
|
# all of that work (e.g. via cachix) when running in CI
|
||||||
(rustNightly.override {
|
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||||
extensions = [ "rust-src" "rust-analyzer-preview" "rustfmt" "clippy" ];
|
|
||||||
})
|
# Build the actual crate
|
||||||
pkg-config
|
iamb = craneLib.buildPackage (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
checks = {
|
||||||
|
# Build the crate as part of `nix flake check`
|
||||||
|
inherit iamb;
|
||||||
|
|
||||||
|
iamb-clippy = craneLib.cargoClippy (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
|
||||||
|
});
|
||||||
|
|
||||||
|
iamb-fmt = craneLibNightly.cargoFmt {
|
||||||
|
inherit src;
|
||||||
|
};
|
||||||
|
|
||||||
|
iamb-nextest = craneLib.cargoNextest (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
partitions = 1;
|
||||||
|
partitionType = "count";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
packages.default = iamb;
|
||||||
|
|
||||||
|
apps.default = flake-utils.lib.mkApp {
|
||||||
|
drv = iamb;
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.default = craneLib.devShell {
|
||||||
|
# Inherit inputs from checks
|
||||||
|
checks = self.checks.${system};
|
||||||
|
|
||||||
|
packages = with pkgs; [
|
||||||
cargo-tarpaulin
|
cargo-tarpaulin
|
||||||
cargo-watch
|
cargo-watch
|
||||||
sqlite
|
sqlite
|
||||||
];
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
# Prepend nightly rustfmt to PATH.
|
||||||
|
export PATH="${rustNightly.rustfmt}/bin:$PATH"
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.83"
|
channel = "1.88"
|
||||||
components = [ "clippy" ]
|
components = [ "clippy" ]
|
||||||
|
|||||||
104
src/base.rs
104
src/base.rs
@@ -13,6 +13,7 @@ use std::time::{Duration, Instant};
|
|||||||
|
|
||||||
use emojis::Emoji;
|
use emojis::Emoji;
|
||||||
use matrix_sdk::ruma::events::receipt::ReceiptThread;
|
use matrix_sdk::ruma::events::receipt::ReceiptThread;
|
||||||
|
use matrix_sdk::ruma::room_version_rules::RedactionRules;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
@@ -57,7 +58,6 @@ use matrix_sdk::{
|
|||||||
OwnedRoomId,
|
OwnedRoomId,
|
||||||
OwnedUserId,
|
OwnedUserId,
|
||||||
RoomId,
|
RoomId,
|
||||||
RoomVersionId,
|
|
||||||
UserId,
|
UserId,
|
||||||
},
|
},
|
||||||
RoomState as MatrixRoomState,
|
RoomState as MatrixRoomState,
|
||||||
@@ -169,6 +169,9 @@ pub enum MessageAction {
|
|||||||
/// Reply to a message.
|
/// Reply to a message.
|
||||||
Reply,
|
Reply,
|
||||||
|
|
||||||
|
/// Go to the message the hovered message replied to.
|
||||||
|
Replied,
|
||||||
|
|
||||||
/// Unreact to a message.
|
/// Unreact to a message.
|
||||||
///
|
///
|
||||||
/// If no specific Emoji to remove to is specified, then all reactions from the user on the
|
/// If no specific Emoji to remove to is specified, then all reactions from the user on the
|
||||||
@@ -491,6 +494,8 @@ pub enum HomeserverAction {
|
|||||||
/// Create a new room with an optional localpart.
|
/// Create a new room with an optional localpart.
|
||||||
CreateRoom(Option<String>, CreateRoomType, CreateRoomFlags),
|
CreateRoom(Option<String>, CreateRoomType, CreateRoomFlags),
|
||||||
Logout(String, bool),
|
Logout(String, bool),
|
||||||
|
/// Forget all left rooms
|
||||||
|
Forget,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An action performed against the user's room keys.
|
/// An action performed against the user's room keys.
|
||||||
@@ -783,6 +788,10 @@ pub enum IambError {
|
|||||||
#[error("Invalid room alias id: {0}")]
|
#[error("Invalid room alias id: {0}")]
|
||||||
InvalidRoomAliasId(#[from] matrix_sdk::ruma::IdParseError),
|
InvalidRoomAliasId(#[from] matrix_sdk::ruma::IdParseError),
|
||||||
|
|
||||||
|
/// An invalid space child order was specified.
|
||||||
|
#[error("Invalid space child order: {0}")]
|
||||||
|
InvalidSpaceChildOrder(matrix_sdk::ruma::IdParseError),
|
||||||
|
|
||||||
/// A failure occurred during verification.
|
/// A failure occurred during verification.
|
||||||
#[error("Verification request error: {0}")]
|
#[error("Verification request error: {0}")]
|
||||||
VerificationRequestError(#[from] matrix_sdk::encryption::identities::RequestVerificationError),
|
VerificationRequestError(#[from] matrix_sdk::encryption::identities::RequestVerificationError),
|
||||||
@@ -1023,7 +1032,7 @@ impl RoomInfo {
|
|||||||
self.messages.get_mut(self.keys.get(event_id)?.to_message_key()?)
|
self.messages.get_mut(self.keys.get(event_id)?.to_message_key()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redact(&mut self, ev: OriginalSyncRoomRedactionEvent, room_version: &RoomVersionId) {
|
pub fn redact(&mut self, ev: OriginalSyncRoomRedactionEvent, rules: &RedactionRules) {
|
||||||
let Some(redacts) = &ev.redacts else {
|
let Some(redacts) = &ev.redacts else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -1033,20 +1042,20 @@ impl RoomInfo {
|
|||||||
Some(EventLocation::State(key)) => {
|
Some(EventLocation::State(key)) => {
|
||||||
if let Some(msg) = self.messages.get_mut(key) {
|
if let Some(msg) = self.messages.get_mut(key) {
|
||||||
let ev = SyncRoomRedactionEvent::Original(ev);
|
let ev = SyncRoomRedactionEvent::Original(ev);
|
||||||
msg.redact(ev, room_version);
|
msg.redact(ev, rules);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(EventLocation::Message(None, key)) => {
|
Some(EventLocation::Message(None, key)) => {
|
||||||
if let Some(msg) = self.messages.get_mut(key) {
|
if let Some(msg) = self.messages.get_mut(key) {
|
||||||
let ev = SyncRoomRedactionEvent::Original(ev);
|
let ev = SyncRoomRedactionEvent::Original(ev);
|
||||||
msg.redact(ev, room_version);
|
msg.redact(ev, rules);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(EventLocation::Message(Some(root), key)) => {
|
Some(EventLocation::Message(Some(root), key)) => {
|
||||||
if let Some(thread) = self.threads.get_mut(root) {
|
if let Some(thread) = self.threads.get_mut(root) {
|
||||||
if let Some(msg) = thread.get_mut(key) {
|
if let Some(msg) = thread.get_mut(key) {
|
||||||
let ev = SyncRoomRedactionEvent::Original(ev);
|
let ev = SyncRoomRedactionEvent::Original(ev);
|
||||||
msg.redact(ev, room_version);
|
msg.redact(ev, rules);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1133,14 +1142,34 @@ impl RoomInfo {
|
|||||||
/// Indicates whether this room has unread messages.
|
/// Indicates whether this room has unread messages.
|
||||||
pub fn unreads(&self, settings: &ApplicationSettings) -> UnreadInfo {
|
pub fn unreads(&self, settings: &ApplicationSettings) -> UnreadInfo {
|
||||||
let last_message = self.messages.last_key_value();
|
let last_message = self.messages.last_key_value();
|
||||||
|
|
||||||
let last_receipt = self
|
let last_receipt = self
|
||||||
.user_receipts
|
.user_receipts
|
||||||
.get(&ReceiptThread::Main)
|
.get(&ReceiptThread::Main)
|
||||||
.and_then(|receipts| receipts.get(&settings.profile.user_id));
|
.and_then(|receipts| receipts.get(&settings.profile.user_id));
|
||||||
|
let last_receipt = last_receipt.as_ref().and_then(|event_id| {
|
||||||
|
match &self.keys.get(*event_id)? {
|
||||||
|
EventLocation::Message(_, key) | EventLocation::State(key) => Some(key),
|
||||||
|
EventLocation::Reaction(_) => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let last_unthreaded = self
|
||||||
|
.user_receipts
|
||||||
|
.get(&ReceiptThread::Unthreaded)
|
||||||
|
.and_then(|receipts| receipts.get(&settings.profile.user_id));
|
||||||
|
let last_unthreaded = last_unthreaded.as_ref().and_then(|event_id| {
|
||||||
|
match &self.keys.get(*event_id)? {
|
||||||
|
EventLocation::Message(_, key) | EventLocation::State(key) => Some(key),
|
||||||
|
EventLocation::Reaction(_) => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let last_receipt = std::cmp::max(last_receipt, last_unthreaded);
|
||||||
|
|
||||||
match (last_message, last_receipt) {
|
match (last_message, last_receipt) {
|
||||||
(Some(((ts, recent), _)), Some(last_read)) => {
|
(Some(((ts, _), _)), Some((read_ts, _))) => {
|
||||||
UnreadInfo { unread: last_read != recent, latest: Some(*ts) }
|
UnreadInfo { unread: ts > read_ts, latest: Some(*ts) }
|
||||||
},
|
},
|
||||||
(Some(((ts, _), _)), None) => {
|
(Some(((ts, _), _)), None) => {
|
||||||
// If we've never loaded/generated a room's receipt (example,
|
// If we've never loaded/generated a room's receipt (example,
|
||||||
@@ -1477,14 +1506,19 @@ impl SyncInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags::bitflags! {
|
static MESSAGE_NEED_TTL: u8 = 30;
|
||||||
/// Load-needs
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Need: u32 {
|
/// Load messages until the event is loaded or `ttl` loads are exceeded
|
||||||
const EMPTY = 0b00000000;
|
pub struct MessageNeed {
|
||||||
const MESSAGES = 0b00000001;
|
pub event_id: OwnedEventId,
|
||||||
const MEMBERS = 0b00000010;
|
pub ttl: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq)]
|
||||||
|
pub struct Need {
|
||||||
|
pub members: bool,
|
||||||
|
pub messages: Option<Vec<MessageNeed>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Things that need loading for different rooms.
|
/// Things that need loading for different rooms.
|
||||||
@@ -1494,9 +1528,31 @@ pub struct RoomNeeds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RoomNeeds {
|
impl RoomNeeds {
|
||||||
/// Mark a room for needing something to be loaded.
|
/// Mark a room for needing to load members.
|
||||||
pub fn insert(&mut self, room_id: OwnedRoomId, need: Need) {
|
pub fn need_members(&mut self, room_id: OwnedRoomId) {
|
||||||
self.needs.entry(room_id).or_default().insert(need);
|
self.needs.entry(room_id).or_default().members = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a room for needing to load messages.
|
||||||
|
pub fn need_messages(&mut self, room_id: OwnedRoomId) {
|
||||||
|
self.needs.entry(room_id).or_default().messages.get_or_insert_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a room for needing to load messages until the given message is loaded or a retry limit
|
||||||
|
/// is exceeded.
|
||||||
|
pub fn need_message(&mut self, room_id: OwnedRoomId, event_id: OwnedEventId) {
|
||||||
|
let messages = &mut self.needs.entry(room_id).or_default().messages.get_or_insert_default();
|
||||||
|
|
||||||
|
messages.push(MessageNeed { event_id, ttl: MESSAGE_NEED_TTL });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn need_messages_all(&mut self, room_id: OwnedRoomId, message_needs: Vec<MessageNeed>) {
|
||||||
|
self.needs
|
||||||
|
.entry(room_id)
|
||||||
|
.or_default()
|
||||||
|
.messages
|
||||||
|
.get_or_insert_default()
|
||||||
|
.extend(message_needs);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rooms(&self) -> usize {
|
pub fn rooms(&self) -> usize {
|
||||||
@@ -1993,7 +2049,7 @@ fn complete_msgbar(text: &EditRope, cursor: &mut Cursor, store: &ChatStore) -> V
|
|||||||
// Complete Emoji shortcodes.
|
// Complete Emoji shortcodes.
|
||||||
Some(':') => {
|
Some(':') => {
|
||||||
let list = store.emojis.complete(&id[1..]);
|
let list = store.emojis.complete(&id[1..]);
|
||||||
let iter = list.into_iter().take(200).map(|s| format!(":{}:", s));
|
let iter = list.into_iter().take(200).map(|s| format!(":{s}:"));
|
||||||
|
|
||||||
return iter.collect();
|
return iter.collect();
|
||||||
},
|
},
|
||||||
@@ -2139,7 +2195,7 @@ pub mod tests {
|
|||||||
));
|
));
|
||||||
|
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
let event_id = format!("$house_{}", i);
|
let event_id = format!("$house_{i}");
|
||||||
info.insert_reaction(MessageLikeEvent::Original(
|
info.insert_reaction(MessageLikeEvent::Original(
|
||||||
matrix_sdk::ruma::events::OriginalMessageLikeEvent {
|
matrix_sdk::ruma::events::OriginalMessageLikeEvent {
|
||||||
content: content.clone(),
|
content: content.clone(),
|
||||||
@@ -2158,7 +2214,7 @@ pub mod tests {
|
|||||||
));
|
));
|
||||||
|
|
||||||
for i in 0..2 {
|
for i in 0..2 {
|
||||||
let event_id = format!("$smile_{}", i);
|
let event_id = format!("$smile_{i}");
|
||||||
info.insert_reaction(MessageLikeEvent::Original(
|
info.insert_reaction(MessageLikeEvent::Original(
|
||||||
matrix_sdk::ruma::events::OriginalMessageLikeEvent {
|
matrix_sdk::ruma::events::OriginalMessageLikeEvent {
|
||||||
content: content.clone(),
|
content: content.clone(),
|
||||||
@@ -2172,7 +2228,7 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i in 2..4 {
|
for i in 2..4 {
|
||||||
let event_id = format!("$smile_{}", i);
|
let event_id = format!("$smile_{i}");
|
||||||
info.insert_reaction(MessageLikeEvent::Original(
|
info.insert_reaction(MessageLikeEvent::Original(
|
||||||
matrix_sdk::ruma::events::OriginalMessageLikeEvent {
|
matrix_sdk::ruma::events::OriginalMessageLikeEvent {
|
||||||
content: content.clone(),
|
content: content.clone(),
|
||||||
@@ -2274,12 +2330,12 @@ pub mod tests {
|
|||||||
|
|
||||||
let mut need_load = RoomNeeds::default();
|
let mut need_load = RoomNeeds::default();
|
||||||
|
|
||||||
need_load.insert(room_id.clone(), Need::MESSAGES);
|
need_load.need_messages(room_id.clone());
|
||||||
need_load.insert(room_id.clone(), Need::MEMBERS);
|
need_load.need_members(room_id.clone());
|
||||||
|
|
||||||
assert_eq!(need_load.into_iter().collect::<Vec<(OwnedRoomId, Need)>>(), vec![(
|
assert_eq!(need_load.into_iter().collect::<Vec<(OwnedRoomId, Need)>>(), vec![(
|
||||||
room_id,
|
room_id,
|
||||||
Need::MESSAGES | Need::MEMBERS,
|
Need { members: true, messages: Some(Vec::new()) }
|
||||||
)],);
|
)],);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -200,6 +200,17 @@ fn iamb_leave(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
|||||||
return Ok(step);
|
return Ok(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn iamb_forget(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
||||||
|
if !desc.arg.text.is_empty() {
|
||||||
|
return Result::Err(CommandError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
let forget = IambAction::Homeserver(HomeserverAction::Forget);
|
||||||
|
let step = CommandStep::Continue(forget.into(), ctx.context.clone());
|
||||||
|
|
||||||
|
return Ok(step);
|
||||||
|
}
|
||||||
|
|
||||||
fn iamb_cancel(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
fn iamb_cancel(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
||||||
if !desc.arg.text.is_empty() {
|
if !desc.arg.text.is_empty() {
|
||||||
return Result::Err(CommandError::InvalidArgument);
|
return Result::Err(CommandError::InvalidArgument);
|
||||||
@@ -275,6 +286,17 @@ fn iamb_reply(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
|||||||
return Ok(step);
|
return Ok(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn iamb_replied(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
||||||
|
if !desc.arg.text.is_empty() {
|
||||||
|
return Result::Err(CommandError::InvalidArgument);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ract = IambAction::from(MessageAction::Replied);
|
||||||
|
let step = CommandStep::Continue(ract.into(), ctx.context.clone());
|
||||||
|
|
||||||
|
return Ok(step);
|
||||||
|
}
|
||||||
|
|
||||||
fn iamb_editor(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
fn iamb_editor(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
|
||||||
if !desc.arg.text.is_empty() {
|
if !desc.arg.text.is_empty() {
|
||||||
return Result::Err(CommandError::InvalidArgument);
|
return Result::Err(CommandError::InvalidArgument);
|
||||||
@@ -731,6 +753,11 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) {
|
|||||||
aliases: vec![],
|
aliases: vec![],
|
||||||
f: iamb_leave,
|
f: iamb_leave,
|
||||||
});
|
});
|
||||||
|
cmds.add_command(ProgramCommand {
|
||||||
|
name: "forget".into(),
|
||||||
|
aliases: vec![],
|
||||||
|
f: iamb_forget,
|
||||||
|
});
|
||||||
cmds.add_command(ProgramCommand {
|
cmds.add_command(ProgramCommand {
|
||||||
name: "members".into(),
|
name: "members".into(),
|
||||||
aliases: vec![],
|
aliases: vec![],
|
||||||
@@ -751,6 +778,11 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) {
|
|||||||
aliases: vec![],
|
aliases: vec![],
|
||||||
f: iamb_reply,
|
f: iamb_reply,
|
||||||
});
|
});
|
||||||
|
cmds.add_command(ProgramCommand {
|
||||||
|
name: "replied".into(),
|
||||||
|
aliases: vec![],
|
||||||
|
f: iamb_replied,
|
||||||
|
});
|
||||||
cmds.add_command(ProgramCommand {
|
cmds.add_command(ProgramCommand {
|
||||||
name: "rooms".into(),
|
name: "rooms".into(),
|
||||||
aliases: vec![],
|
aliases: vec![],
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ pub struct Session {
|
|||||||
impl From<Session> for MatrixSession {
|
impl From<Session> for MatrixSession {
|
||||||
fn from(session: Session) -> Self {
|
fn from(session: Session) -> Self {
|
||||||
MatrixSession {
|
MatrixSession {
|
||||||
tokens: matrix_sdk::authentication::matrix::MatrixSessionTokens {
|
tokens: matrix_sdk::authentication::SessionTokens {
|
||||||
access_token: session.access_token,
|
access_token: session.access_token,
|
||||||
refresh_token: session.refresh_token,
|
refresh_token: session.refresh_token,
|
||||||
},
|
},
|
||||||
@@ -483,6 +483,8 @@ pub struct Notifications {
|
|||||||
pub via: NotifyVia,
|
pub via: NotifyVia,
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub show_message: bool,
|
pub show_message: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub sound_hint: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -705,11 +707,11 @@ impl DirectoryValues {
|
|||||||
|
|
||||||
#[derive(Clone, Default, Deserialize)]
|
#[derive(Clone, Default, Deserialize)]
|
||||||
pub struct Directories {
|
pub struct Directories {
|
||||||
pub cache: Option<PathBuf>,
|
pub cache: Option<String>,
|
||||||
pub data: Option<PathBuf>,
|
pub data: Option<String>,
|
||||||
pub logs: Option<PathBuf>,
|
pub logs: Option<String>,
|
||||||
pub downloads: Option<PathBuf>,
|
pub downloads: Option<String>,
|
||||||
pub image_previews: Option<PathBuf>,
|
pub image_previews: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Directories {
|
impl Directories {
|
||||||
@@ -726,6 +728,11 @@ impl Directories {
|
|||||||
fn values(self) -> DirectoryValues {
|
fn values(self) -> DirectoryValues {
|
||||||
let cache = self
|
let cache = self
|
||||||
.cache
|
.cache
|
||||||
|
.map(|dir| {
|
||||||
|
let dir = shellexpand::full(&dir)
|
||||||
|
.expect("unable to expand shell variables in dirs.cache");
|
||||||
|
Path::new(dir.as_ref()).to_owned()
|
||||||
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
let mut dir = dirs::cache_dir()?;
|
let mut dir = dirs::cache_dir()?;
|
||||||
dir.push("iamb");
|
dir.push("iamb");
|
||||||
@@ -735,6 +742,11 @@ impl Directories {
|
|||||||
|
|
||||||
let data = self
|
let data = self
|
||||||
.data
|
.data
|
||||||
|
.map(|dir| {
|
||||||
|
let dir = shellexpand::full(&dir)
|
||||||
|
.expect("unable to expand shell variables in dirs.cache");
|
||||||
|
Path::new(dir.as_ref()).to_owned()
|
||||||
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
let mut dir = dirs::data_dir()?;
|
let mut dir = dirs::data_dir()?;
|
||||||
dir.push("iamb");
|
dir.push("iamb");
|
||||||
@@ -742,15 +754,36 @@ impl Directories {
|
|||||||
})
|
})
|
||||||
.expect("no dirs.data value configured!");
|
.expect("no dirs.data value configured!");
|
||||||
|
|
||||||
let logs = self.logs.unwrap_or_else(|| {
|
let logs = self
|
||||||
|
.logs
|
||||||
|
.map(|dir| {
|
||||||
|
let dir = shellexpand::full(&dir)
|
||||||
|
.expect("unable to expand shell variables in dirs.cache");
|
||||||
|
Path::new(dir.as_ref()).to_owned()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
let mut dir = cache.clone();
|
let mut dir = cache.clone();
|
||||||
dir.push("logs");
|
dir.push("logs");
|
||||||
dir
|
dir
|
||||||
});
|
});
|
||||||
|
|
||||||
let downloads = self.downloads.or_else(dirs::download_dir);
|
let downloads = self
|
||||||
|
.downloads
|
||||||
|
.map(|dir| {
|
||||||
|
let dir = shellexpand::full(&dir)
|
||||||
|
.expect("unable to expand shell variables in dirs.cache");
|
||||||
|
Path::new(dir.as_ref()).to_owned()
|
||||||
|
})
|
||||||
|
.or_else(dirs::download_dir);
|
||||||
|
|
||||||
let image_previews = self.image_previews.unwrap_or_else(|| {
|
let image_previews = self
|
||||||
|
.image_previews
|
||||||
|
.map(|dir| {
|
||||||
|
let dir = shellexpand::full(&dir)
|
||||||
|
.expect("unable to expand shell variables in dirs.cache");
|
||||||
|
Path::new(dir.as_ref()).to_owned()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
let mut dir = cache.clone();
|
let mut dir = cache.clone();
|
||||||
dir.push("image_preview_downloads");
|
dir.push("image_preview_downloads");
|
||||||
dir
|
dir
|
||||||
@@ -899,10 +932,7 @@ impl ApplicationSettings {
|
|||||||
} else {
|
} else {
|
||||||
loop {
|
loop {
|
||||||
println!("\nNo profile specified. Available profiles:");
|
println!("\nNo profile specified. Available profiles:");
|
||||||
profiles
|
profiles.keys().enumerate().for_each(|(i, name)| println!("{i}: {name}"));
|
||||||
.keys()
|
|
||||||
.enumerate()
|
|
||||||
.for_each(|(i, name)| println!("{}: {}", i, name));
|
|
||||||
|
|
||||||
print!("Select a number or 'q' to quit: ");
|
print!("Select a number or 'q' to quit: ");
|
||||||
let _ = std::io::stdout().flush();
|
let _ = std::io::stdout().flush();
|
||||||
@@ -1001,7 +1031,7 @@ impl ApplicationSettings {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_user_char_span(&self, user_id: &UserId) -> Span {
|
pub fn get_user_char_span(&self, user_id: &UserId) -> Span<'_> {
|
||||||
let (color, c) = self
|
let (color, c) = self
|
||||||
.tunables
|
.tunables
|
||||||
.users
|
.users
|
||||||
|
|||||||
@@ -663,6 +663,13 @@ impl Application {
|
|||||||
|
|
||||||
Err(UIError::NeedConfirm(prompt))
|
Err(UIError::NeedConfirm(prompt))
|
||||||
},
|
},
|
||||||
|
HomeserverAction::Forget => {
|
||||||
|
let client = &store.application.worker.client;
|
||||||
|
for room in client.left_rooms() {
|
||||||
|
room.forget().await.map_err(IambError::from)?;
|
||||||
|
}
|
||||||
|
Ok(vec![])
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1036,7 +1043,7 @@ async fn run(settings: ApplicationSettings) -> IambResult<()> {
|
|||||||
match res {
|
match res {
|
||||||
Err(UIError::Application(IambError::Matrix(e))) => {
|
Err(UIError::Application(IambError::Matrix(e))) => {
|
||||||
if let Some(ErrorKind::UnknownToken { .. }) = e.client_api_error_kind() {
|
if let Some(ErrorKind::UnknownToken { .. }) = e.client_api_error_kind() {
|
||||||
print_exit("Server did not recognize our API token; did you log out from this session elsewhere?")
|
print_exit(format!("Server did not recognize our API token; did you log out from this session elsewhere?\nTry deleting `{}` to force a clean login.", settings.session_json.display()))
|
||||||
} else {
|
} else {
|
||||||
print_exit(e)
|
print_exit(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -820,7 +820,8 @@ fn h2t(hdl: &Handle, state: &mut TreeGenState) -> StyleTreeChildren {
|
|||||||
*c2t(&node.children.borrow(), state)
|
*c2t(&node.children.borrow(), state)
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => return vec![],
|
// Treat unknown tags as plain text.
|
||||||
|
_ => *c2t(&node.children.borrow(), state),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use std::ops::{Deref, DerefMut};
|
|||||||
use chrono::{DateTime, Local as LocalTz};
|
use chrono::{DateTime, Local as LocalTz};
|
||||||
use humansize::{format_size, DECIMAL};
|
use humansize::{format_size, DECIMAL};
|
||||||
use matrix_sdk::ruma::events::receipt::ReceiptThread;
|
use matrix_sdk::ruma::events::receipt::ReceiptThread;
|
||||||
|
use matrix_sdk::ruma::room_version_rules::RedactionRules;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
@@ -43,7 +44,6 @@ use matrix_sdk::ruma::{
|
|||||||
MilliSecondsSinceUnixEpoch,
|
MilliSecondsSinceUnixEpoch,
|
||||||
OwnedEventId,
|
OwnedEventId,
|
||||||
OwnedUserId,
|
OwnedUserId,
|
||||||
RoomVersionId,
|
|
||||||
UInt,
|
UInt,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -172,7 +172,8 @@ fn placeholder_frame(
|
|||||||
image_preview_size: &ImagePreviewSize,
|
image_preview_size: &ImagePreviewSize,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
let ImagePreviewSize { width, height } = image_preview_size;
|
let ImagePreviewSize { width, height } = image_preview_size;
|
||||||
if outer_width < *width || (*width < 2 || *height < 2) {
|
let width = usize::min(*width, outer_width);
|
||||||
|
if width < 2 || *height < 2 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut placeholder = "\u{230c}".to_string();
|
let mut placeholder = "\u{230c}".to_string();
|
||||||
@@ -233,13 +234,13 @@ impl MessageTimeStamp {
|
|||||||
dt1.date_naive() == dt2.date_naive()
|
dt1.date_naive() == dt2.date_naive()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_date(&self) -> Option<Span> {
|
fn show_date(&self) -> Option<Span<'_>> {
|
||||||
let time = self.as_datetime().format("%A, %B %d %Y").to_string();
|
let time = self.as_datetime().format("%A, %B %d %Y").to_string();
|
||||||
|
|
||||||
Span::styled(time, BOLD_STYLE).into()
|
Span::styled(time, BOLD_STYLE).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_time(&self) -> Option<Span> {
|
fn show_time(&self) -> Option<Span<'_>> {
|
||||||
match self {
|
match self {
|
||||||
MessageTimeStamp::OriginServer(ms) => {
|
MessageTimeStamp::OriginServer(ms) => {
|
||||||
let time = millis_to_datetime(*ms).format("%T");
|
let time = millis_to_datetime(*ms).format("%T");
|
||||||
@@ -510,7 +511,7 @@ impl MessageEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redact(&mut self, redaction: SyncRoomRedactionEvent, version: &RoomVersionId) {
|
fn redact(&mut self, redaction: SyncRoomRedactionEvent, rules: &RedactionRules) {
|
||||||
match self {
|
match self {
|
||||||
MessageEvent::EncryptedOriginal(_) => return,
|
MessageEvent::EncryptedOriginal(_) => return,
|
||||||
MessageEvent::EncryptedRedacted(_) => return,
|
MessageEvent::EncryptedRedacted(_) => return,
|
||||||
@@ -519,7 +520,7 @@ impl MessageEvent {
|
|||||||
MessageEvent::Local(_, _) => return,
|
MessageEvent::Local(_, _) => return,
|
||||||
MessageEvent::Original(ev) => {
|
MessageEvent::Original(ev) => {
|
||||||
let redacted = RedactedRoomMessageEvent {
|
let redacted = RedactedRoomMessageEvent {
|
||||||
content: ev.content.clone().redact(version),
|
content: ev.content.clone().redact(rules),
|
||||||
event_id: ev.event_id.clone(),
|
event_id: ev.event_id.clone(),
|
||||||
sender: ev.sender.clone(),
|
sender: ev.sender.clone(),
|
||||||
origin_server_ts: ev.origin_server_ts,
|
origin_server_ts: ev.origin_server_ts,
|
||||||
@@ -573,27 +574,18 @@ fn body_cow_content(content: &RoomMessageEventContent) -> Cow<'_, str> {
|
|||||||
MessageType::Video(content) => {
|
MessageType::Video(content) => {
|
||||||
display_file_to_text!(Video, content);
|
display_file_to_text!(Video, content);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => content.body(),
|
||||||
match content.msgtype() {
|
|
||||||
// Just show the body text for the special Element messages.
|
|
||||||
"nic.custom.confetti" |
|
|
||||||
"nic.custom.fireworks" |
|
|
||||||
"io.element.effect.hearts" |
|
|
||||||
"io.element.effect.rainfall" |
|
|
||||||
"io.element.effect.snowfall" |
|
|
||||||
"io.element.effects.space_invaders" => content.body(),
|
|
||||||
other => {
|
|
||||||
return Cow::Owned(format!("[Unknown message type: {other:?}]"));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Cow::Borrowed(s)
|
Cow::Borrowed(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn body_cow_reason(unsigned: &RedactedUnsigned) -> Cow<'_, str> {
|
fn body_cow_reason(unsigned: &RedactedUnsigned) -> Cow<'_, str> {
|
||||||
let reason = unsigned.redacted_because.content.reason.as_ref();
|
let reason = unsigned
|
||||||
|
.redacted_because
|
||||||
|
.deserialize()
|
||||||
|
.ok()
|
||||||
|
.and_then(|ev| ev.content.reason);
|
||||||
|
|
||||||
if let Some(r) = reason {
|
if let Some(r) = reason {
|
||||||
Cow::Owned(format!("[Redacted: {r:?}]"))
|
Cow::Owned(format!("[Redacted: {r:?}]"))
|
||||||
@@ -740,22 +732,28 @@ impl<'a> MessageFormatter<'a> {
|
|||||||
info: &'a RoomInfo,
|
info: &'a RoomInfo,
|
||||||
settings: &'a ApplicationSettings,
|
settings: &'a ApplicationSettings,
|
||||||
) -> Option<ProtocolPreview<'a>> {
|
) -> Option<ProtocolPreview<'a>> {
|
||||||
|
let reply_style = if settings.tunables.message_user_color {
|
||||||
|
style.patch(settings.get_user_color(&msg.sender))
|
||||||
|
} else {
|
||||||
|
style
|
||||||
|
};
|
||||||
|
|
||||||
let width = self.width();
|
let width = self.width();
|
||||||
let w = width.saturating_sub(2);
|
let w = width.saturating_sub(2);
|
||||||
let (mut replied, proto) = msg.show_msg(w, style, true, settings);
|
let (mut replied, proto) = msg.show_msg(w, reply_style, true, settings);
|
||||||
let mut sender = msg.sender_span(info, self.settings);
|
let mut sender = msg.sender_span(info, self.settings);
|
||||||
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
|
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
|
||||||
let trailing = w.saturating_sub(sender_width + 1);
|
let trailing = w.saturating_sub(sender_width + 1);
|
||||||
|
|
||||||
sender.style = sender.style.patch(style);
|
sender.style = sender.style.patch(reply_style);
|
||||||
|
|
||||||
self.push_spans(
|
self.push_spans(
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled(" ", style),
|
Span::styled(" ", style),
|
||||||
Span::styled(THICK_VERTICAL, style),
|
Span::styled(THICK_VERTICAL, style),
|
||||||
sender,
|
sender,
|
||||||
Span::styled(":", style),
|
Span::styled(":", reply_style),
|
||||||
space_span(trailing, style),
|
space_span(trailing, reply_style),
|
||||||
]),
|
]),
|
||||||
style,
|
style,
|
||||||
text,
|
text,
|
||||||
@@ -774,7 +772,7 @@ impl<'a> MessageFormatter<'a> {
|
|||||||
line.spans.insert(0, Span::styled(" ", style));
|
line.spans.insert(0, Span::styled(" ", style));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.push_text(replied, style, text);
|
self.push_text(replied, reply_style, text);
|
||||||
|
|
||||||
proto
|
proto
|
||||||
}
|
}
|
||||||
@@ -1090,7 +1088,7 @@ impl Message {
|
|||||||
},
|
},
|
||||||
ImageStatus::Loaded(backend) => {
|
ImageStatus::Loaded(backend) => {
|
||||||
proto = Some(backend);
|
proto = Some(backend);
|
||||||
placeholder_frame(Some("Cut off..."), width, &backend.area().into())
|
placeholder_frame(Some("No Space..."), width, &backend.area().into())
|
||||||
},
|
},
|
||||||
ImageStatus::Error(err) => Some(format!("[Image error: {err}]\n")),
|
ImageStatus::Error(err) => Some(format!("[Image error: {err}]\n")),
|
||||||
};
|
};
|
||||||
@@ -1141,8 +1139,8 @@ impl Message {
|
|||||||
Span::styled(sender, style).into()
|
Span::styled(sender, style).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redact(&mut self, redaction: SyncRoomRedactionEvent, version: &RoomVersionId) {
|
pub fn redact(&mut self, redaction: SyncRoomRedactionEvent, rules: &RedactionRules) {
|
||||||
self.event.redact(redaction, version);
|
self.event.redact(redaction, rules);
|
||||||
self.html = None;
|
self.html = None;
|
||||||
self.downloaded = false;
|
self.downloaded = false;
|
||||||
self.image_preview = ImageStatus::None;
|
self.image_preview = ImageStatus::None;
|
||||||
@@ -1348,7 +1346,17 @@ pub mod tests {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(placeholder_frame(None, 2, &ImagePreviewSize { width: 4, height: 4 }), None);
|
assert_eq!(
|
||||||
|
placeholder_frame(None, 2, &ImagePreviewSize { width: 4, height: 4 }),
|
||||||
|
pretty_frame_test(
|
||||||
|
r#"
|
||||||
|
⌌⌍
|
||||||
|
|
||||||
|
|
||||||
|
⌎⌏
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
);
|
||||||
assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 1, height: 4 }), None);
|
assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 1, height: 4 }), None);
|
||||||
|
|
||||||
assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 1 }), None);
|
assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 1 }), None);
|
||||||
|
|||||||
@@ -121,11 +121,11 @@ pub fn body_cow_state(ev: &AnySyncStateEvent) -> Cow<'static, str> {
|
|||||||
|
|
||||||
match (old_canon, new_canon) {
|
match (old_canon, new_canon) {
|
||||||
(None, Some(canon)) => {
|
(None, Some(canon)) => {
|
||||||
format!("* updated the canonical alias for the room to: {}", canon)
|
format!("* updated the canonical alias for the room to: {canon}")
|
||||||
},
|
},
|
||||||
(Some(old), Some(new)) => {
|
(Some(old), Some(new)) => {
|
||||||
if old != new {
|
if old != new {
|
||||||
format!("* updated the canonical alias for the room to: {}", new)
|
format!("* updated the canonical alias for the room to: {new}")
|
||||||
} else {
|
} else {
|
||||||
return Cow::Borrowed("* removed the canonical alias for the room");
|
return Cow::Borrowed("* removed the canonical alias for the room");
|
||||||
}
|
}
|
||||||
@@ -187,10 +187,10 @@ pub fn body_cow_state(ev: &AnySyncStateEvent) -> Cow<'static, str> {
|
|||||||
|
|
||||||
match change {
|
match change {
|
||||||
MembershipChange::None => {
|
MembershipChange::None => {
|
||||||
format!("* did nothing to {}", state_key)
|
format!("* did nothing to {state_key}")
|
||||||
},
|
},
|
||||||
MembershipChange::Error => {
|
MembershipChange::Error => {
|
||||||
format!("* failed to calculate membership change to {}", state_key)
|
format!("* failed to calculate membership change to {state_key}")
|
||||||
},
|
},
|
||||||
MembershipChange::Joined => {
|
MembershipChange::Joined => {
|
||||||
return Cow::Borrowed("* joined the room");
|
return Cow::Borrowed("* joined the room");
|
||||||
@@ -199,19 +199,19 @@ pub fn body_cow_state(ev: &AnySyncStateEvent) -> Cow<'static, str> {
|
|||||||
return Cow::Borrowed("* left the room");
|
return Cow::Borrowed("* left the room");
|
||||||
},
|
},
|
||||||
MembershipChange::Banned => {
|
MembershipChange::Banned => {
|
||||||
format!("* banned {} from the room", state_key)
|
format!("* banned {state_key} from the room")
|
||||||
},
|
},
|
||||||
MembershipChange::Unbanned => {
|
MembershipChange::Unbanned => {
|
||||||
format!("* unbanned {} from the room", state_key)
|
format!("* unbanned {state_key} from the room")
|
||||||
},
|
},
|
||||||
MembershipChange::Kicked => {
|
MembershipChange::Kicked => {
|
||||||
format!("* kicked {} from the room", state_key)
|
format!("* kicked {state_key} from the room")
|
||||||
},
|
},
|
||||||
MembershipChange::Invited => {
|
MembershipChange::Invited => {
|
||||||
format!("* invited {} to the room", state_key)
|
format!("* invited {state_key} to the room")
|
||||||
},
|
},
|
||||||
MembershipChange::KickedAndBanned => {
|
MembershipChange::KickedAndBanned => {
|
||||||
format!("* kicked and banned {} from the room", state_key)
|
format!("* kicked and banned {state_key} from the room")
|
||||||
},
|
},
|
||||||
MembershipChange::InvitationAccepted => {
|
MembershipChange::InvitationAccepted => {
|
||||||
return Cow::Borrowed("* accepted an invitation to join the room");
|
return Cow::Borrowed("* accepted an invitation to join the room");
|
||||||
@@ -220,26 +220,26 @@ pub fn body_cow_state(ev: &AnySyncStateEvent) -> Cow<'static, str> {
|
|||||||
return Cow::Borrowed("* rejected an invitation to join the room");
|
return Cow::Borrowed("* rejected an invitation to join the room");
|
||||||
},
|
},
|
||||||
MembershipChange::InvitationRevoked => {
|
MembershipChange::InvitationRevoked => {
|
||||||
format!("* revoked an invitation for {} to join the room", state_key)
|
format!("* revoked an invitation for {state_key} to join the room")
|
||||||
},
|
},
|
||||||
MembershipChange::Knocked => {
|
MembershipChange::Knocked => {
|
||||||
return Cow::Borrowed("* would like to join the room");
|
return Cow::Borrowed("* would like to join the room");
|
||||||
},
|
},
|
||||||
MembershipChange::KnockAccepted => {
|
MembershipChange::KnockAccepted => {
|
||||||
format!("* accepted the room knock from {}", state_key)
|
format!("* accepted the room knock from {state_key}")
|
||||||
},
|
},
|
||||||
MembershipChange::KnockRetracted => {
|
MembershipChange::KnockRetracted => {
|
||||||
return Cow::Borrowed("* retracted their room knock");
|
return Cow::Borrowed("* retracted their room knock");
|
||||||
},
|
},
|
||||||
MembershipChange::KnockDenied => {
|
MembershipChange::KnockDenied => {
|
||||||
format!("* rejected the room knock from {}", state_key)
|
format!("* rejected the room knock from {state_key}")
|
||||||
},
|
},
|
||||||
MembershipChange::ProfileChanged { displayname_change, avatar_url_change } => {
|
MembershipChange::ProfileChanged { displayname_change, avatar_url_change } => {
|
||||||
match (displayname_change, avatar_url_change) {
|
match (displayname_change, avatar_url_change) {
|
||||||
(Some(change), avatar_change) => {
|
(Some(change), avatar_change) => {
|
||||||
let mut m = match (change.old, change.new) {
|
let mut m = match (change.old, change.new) {
|
||||||
(None, Some(new)) => {
|
(None, Some(new)) => {
|
||||||
format!("* set their display name to {:?}", new)
|
format!("* set their display name to {new:?}")
|
||||||
},
|
},
|
||||||
(Some(old), Some(new)) => {
|
(Some(old), Some(new)) => {
|
||||||
format!("* changed their display name from {old} to {new}")
|
format!("* changed their display name from {old} to {new}")
|
||||||
@@ -280,7 +280,7 @@ pub fn body_cow_state(ev: &AnySyncStateEvent) -> Cow<'static, str> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ev => {
|
ev => {
|
||||||
format!("* made an unknown membership change to {}: {:?}", state_key, ev)
|
format!("* made an unknown membership change to {state_key}: {ev:?}")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -727,7 +727,7 @@ pub fn html_state(ev: &AnySyncStateEvent) -> StyleTree {
|
|||||||
ev => {
|
ev => {
|
||||||
let prefix =
|
let prefix =
|
||||||
StyleTreeNode::Text("* made an unknown membership change to ".into());
|
StyleTreeNode::Text("* made an unknown membership change to ".into());
|
||||||
let suffix = StyleTreeNode::Text(format!(": {:?}", ev).into());
|
let suffix = StyleTreeNode::Text(format!(": {ev:?}").into());
|
||||||
vec![prefix, user_id, suffix]
|
vec![prefix, user_id, suffix]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use matrix_sdk::{
|
|||||||
RoomId,
|
RoomId,
|
||||||
},
|
},
|
||||||
Client,
|
Client,
|
||||||
|
EncryptionState,
|
||||||
};
|
};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ pub async fn register_notifications(
|
|||||||
}
|
}
|
||||||
let notify_via = settings.tunables.notifications.via;
|
let notify_via = settings.tunables.notifications.via;
|
||||||
let show_message = settings.tunables.notifications.show_message;
|
let show_message = settings.tunables.notifications.show_message;
|
||||||
|
let sound_hint = settings.tunables.notifications.sound_hint.clone();
|
||||||
let server_settings = client.notification_settings().await;
|
let server_settings = client.notification_settings().await;
|
||||||
let Some(startup_ts) = MilliSecondsSinceUnixEpoch::from_system_time(SystemTime::now()) else {
|
let Some(startup_ts) = MilliSecondsSinceUnixEpoch::from_system_time(SystemTime::now()) else {
|
||||||
return;
|
return;
|
||||||
@@ -60,6 +62,7 @@ pub async fn register_notifications(
|
|||||||
.register_notification_handler(move |notification, room: MatrixRoom, client: Client| {
|
.register_notification_handler(move |notification, room: MatrixRoom, client: Client| {
|
||||||
let store = store.clone();
|
let store = store.clone();
|
||||||
let server_settings = server_settings.clone();
|
let server_settings = server_settings.clone();
|
||||||
|
let sound_hint = sound_hint.clone();
|
||||||
async move {
|
async move {
|
||||||
let mode = global_or_room_mode(&server_settings, &room).await;
|
let mode = global_or_room_mode(&server_settings, &room).await;
|
||||||
if mode == RoomNotificationMode::Mute {
|
if mode == RoomNotificationMode::Mute {
|
||||||
@@ -89,6 +92,7 @@ pub async fn register_notifications(
|
|||||||
body.as_deref(),
|
body.as_deref(),
|
||||||
room_id,
|
room_id,
|
||||||
&store,
|
&store,
|
||||||
|
sound_hint.as_deref(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
},
|
},
|
||||||
@@ -113,10 +117,11 @@ async fn send_notification(
|
|||||||
body: Option<&str>,
|
body: Option<&str>,
|
||||||
room_id: OwnedRoomId,
|
room_id: OwnedRoomId,
|
||||||
store: &AsyncProgramStore,
|
store: &AsyncProgramStore,
|
||||||
|
sound_hint: Option<&str>,
|
||||||
) {
|
) {
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
if via.desktop {
|
if via.desktop {
|
||||||
send_notification_desktop(summary, body, room_id, store).await;
|
send_notification_desktop(summary, body, room_id, store, sound_hint).await;
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "desktop"))]
|
#[cfg(not(feature = "desktop"))]
|
||||||
{
|
{
|
||||||
@@ -134,11 +139,13 @@ async fn send_notification_bell(store: &AsyncProgramStore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
|
#[cfg_attr(target_os = "macos", allow(unused_variables))]
|
||||||
async fn send_notification_desktop(
|
async fn send_notification_desktop(
|
||||||
summary: &str,
|
summary: &str,
|
||||||
body: Option<&str>,
|
body: Option<&str>,
|
||||||
room_id: OwnedRoomId,
|
room_id: OwnedRoomId,
|
||||||
_store: &AsyncProgramStore,
|
_store: &AsyncProgramStore,
|
||||||
|
sound_hint: Option<&str>,
|
||||||
) {
|
) {
|
||||||
let mut desktop_notification = notify_rust::Notification::new();
|
let mut desktop_notification = notify_rust::Notification::new();
|
||||||
desktop_notification
|
desktop_notification
|
||||||
@@ -147,6 +154,10 @@ async fn send_notification_desktop(
|
|||||||
.icon(IAMB_XDG_NAME)
|
.icon(IAMB_XDG_NAME)
|
||||||
.action("default", "default");
|
.action("default", "default");
|
||||||
|
|
||||||
|
if let Some(sound_hint) = sound_hint {
|
||||||
|
desktop_notification.sound_name(sound_hint);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
desktop_notification.urgency(notify_rust::Urgency::Normal);
|
desktop_notification.urgency(notify_rust::Urgency::Normal);
|
||||||
|
|
||||||
@@ -182,8 +193,8 @@ async fn global_or_room_mode(
|
|||||||
Ok(true) => IsOneToOne::Yes,
|
Ok(true) => IsOneToOne::Yes,
|
||||||
_ => IsOneToOne::No,
|
_ => IsOneToOne::No,
|
||||||
};
|
};
|
||||||
let is_encrypted = match room.is_encrypted().await {
|
let is_encrypted = match room.latest_encryption_state().await {
|
||||||
Ok(true) => IsEncrypted::Yes,
|
Ok(EncryptionState::Encrypted) => IsEncrypted::Yes,
|
||||||
_ => IsEncrypted::No,
|
_ => IsEncrypted::No,
|
||||||
};
|
};
|
||||||
settings
|
settings
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ use crate::{
|
|||||||
const TEST_ROOM1_ALIAS: &str = "#room1:example.com";
|
const TEST_ROOM1_ALIAS: &str = "#room1:example.com";
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref TEST_ROOM1_ID: OwnedRoomId = RoomId::new(server_name!("example.com")).to_owned();
|
pub static ref TEST_ROOM1_ID: OwnedRoomId =
|
||||||
|
RoomId::new_v1(server_name!("example.com")).to_owned();
|
||||||
pub static ref TEST_USER1: OwnedUserId = user_id!("@user1:example.com").to_owned();
|
pub static ref TEST_USER1: OwnedUserId = user_id!("@user1:example.com").to_owned();
|
||||||
pub static ref TEST_USER2: OwnedUserId = user_id!("@user2:example.com").to_owned();
|
pub static ref TEST_USER2: OwnedUserId = user_id!("@user2:example.com").to_owned();
|
||||||
pub static ref TEST_USER3: OwnedUserId = user_id!("@user3:example.com").to_owned();
|
pub static ref TEST_USER3: OwnedUserId = user_id!("@user3:example.com").to_owned();
|
||||||
@@ -196,6 +197,7 @@ pub fn mock_tunables() -> TunableValues {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
via: NotifyVia::default(),
|
via: NotifyVia::default(),
|
||||||
show_message: true,
|
show_message: true,
|
||||||
|
sound_hint: None,
|
||||||
},
|
},
|
||||||
image_preview: None,
|
image_preview: None,
|
||||||
user_gutter_width: 30,
|
user_gutter_width: 30,
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ use crate::base::{
|
|||||||
IambInfo,
|
IambInfo,
|
||||||
IambResult,
|
IambResult,
|
||||||
MessageAction,
|
MessageAction,
|
||||||
Need,
|
|
||||||
ProgramAction,
|
ProgramAction,
|
||||||
ProgramContext,
|
ProgramContext,
|
||||||
ProgramStore,
|
ProgramStore,
|
||||||
@@ -97,12 +96,12 @@ fn bold_style() -> Style {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn bold_span(s: &str) -> Span {
|
fn bold_span(s: &str) -> Span<'_> {
|
||||||
Span::styled(s, bold_style())
|
Span::styled(s, bold_style())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn bold_spans(s: &str) -> Line {
|
fn bold_spans(s: &str) -> Line<'_> {
|
||||||
bold_span(s).into()
|
bold_span(s).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,12 +115,12 @@ fn selected_style(selected: bool) -> Style {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn selected_span(s: &str, selected: bool) -> Span {
|
fn selected_span(s: &str, selected: bool) -> Span<'_> {
|
||||||
Span::styled(s, selected_style(selected))
|
Span::styled(s, selected_style(selected))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn selected_text(s: &str, selected: bool) -> Text {
|
fn selected_text(s: &str, selected: bool) -> Text<'_> {
|
||||||
Text::from(selected_span(s, selected))
|
Text::from(selected_span(s, selected))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,7 +640,7 @@ impl WindowOps<IambInfo> for IambWindow {
|
|||||||
state.set(items);
|
state.set(items);
|
||||||
|
|
||||||
List::new(store)
|
List::new(store)
|
||||||
.empty_message("You do not have rooms or dms yet")
|
.empty_message("You do not have any unreads yet")
|
||||||
.empty_alignment(Alignment::Center)
|
.empty_alignment(Alignment::Center)
|
||||||
.focus(focused)
|
.focus(focused)
|
||||||
.render(area, buf, state);
|
.render(area, buf, state);
|
||||||
@@ -743,7 +742,7 @@ impl Window<IambInfo> for IambWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_tab_title(&self, store: &mut ProgramStore) -> Line {
|
fn get_tab_title(&self, store: &mut ProgramStore) -> Line<'_> {
|
||||||
match self {
|
match self {
|
||||||
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
|
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
|
||||||
IambWindow::RoomList(_) => bold_spans("Rooms"),
|
IambWindow::RoomList(_) => bold_spans("Rooms"),
|
||||||
@@ -771,7 +770,7 @@ impl Window<IambInfo> for IambWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_win_title(&self, store: &mut ProgramStore) -> Line {
|
fn get_win_title(&self, store: &mut ProgramStore) -> Line<'_> {
|
||||||
match self {
|
match self {
|
||||||
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
|
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
|
||||||
IambWindow::RoomList(_) => bold_spans("Rooms"),
|
IambWindow::RoomList(_) => bold_spans("Rooms"),
|
||||||
@@ -801,7 +800,7 @@ impl Window<IambInfo> for IambWindow {
|
|||||||
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
||||||
let room = RoomState::new(room, thread, name, tags, store);
|
let room = RoomState::new(room, thread, name, tags, store);
|
||||||
|
|
||||||
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
|
store.application.need_load.need_members(room.id().to_owned());
|
||||||
return Ok(room.into());
|
return Ok(room.into());
|
||||||
},
|
},
|
||||||
IambId::DirectList => {
|
IambId::DirectList => {
|
||||||
@@ -863,7 +862,7 @@ impl Window<IambInfo> for IambWindow {
|
|||||||
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
let (room, name, tags) = store.application.worker.get_room(room_id)?;
|
||||||
let room = RoomState::new(room, None, name, tags, store);
|
let room = RoomState::new(room, None, name, tags, store);
|
||||||
|
|
||||||
store.application.need_load.insert(room.id().to_owned(), Need::MEMBERS);
|
store.application.need_load.need_members(room.id().to_owned());
|
||||||
Ok(room.into())
|
Ok(room.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -959,7 +958,12 @@ impl Display for GenericChatItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ListItem<IambInfo> for GenericChatItem {
|
impl ListItem<IambInfo> for GenericChatItem {
|
||||||
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
fn show(
|
||||||
|
&self,
|
||||||
|
selected: bool,
|
||||||
|
_: &ViewportContext<ListCursor>,
|
||||||
|
_: &mut ProgramStore,
|
||||||
|
) -> Text<'_> {
|
||||||
let unread = self.unread.is_unread();
|
let unread = self.unread.is_unread();
|
||||||
let style = selected_style(selected);
|
let style = selected_style(selected);
|
||||||
let (name, mut labels) = name_and_labels(&self.name, unread, style);
|
let (name, mut labels) = name_and_labels(&self.name, unread, style);
|
||||||
@@ -1073,7 +1077,12 @@ impl Display for RoomItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ListItem<IambInfo> for RoomItem {
|
impl ListItem<IambInfo> for RoomItem {
|
||||||
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
fn show(
|
||||||
|
&self,
|
||||||
|
selected: bool,
|
||||||
|
_: &ViewportContext<ListCursor>,
|
||||||
|
_: &mut ProgramStore,
|
||||||
|
) -> Text<'_> {
|
||||||
let unread = self.unread.is_unread();
|
let unread = self.unread.is_unread();
|
||||||
let style = selected_style(selected);
|
let style = selected_style(selected);
|
||||||
let (name, mut labels) = name_and_labels(&self.name, unread, style);
|
let (name, mut labels) = name_and_labels(&self.name, unread, style);
|
||||||
@@ -1177,7 +1186,12 @@ impl Display for DirectItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ListItem<IambInfo> for DirectItem {
|
impl ListItem<IambInfo> for DirectItem {
|
||||||
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
fn show(
|
||||||
|
&self,
|
||||||
|
selected: bool,
|
||||||
|
_: &ViewportContext<ListCursor>,
|
||||||
|
_: &mut ProgramStore,
|
||||||
|
) -> Text<'_> {
|
||||||
let unread = self.unread.is_unread();
|
let unread = self.unread.is_unread();
|
||||||
let style = selected_style(selected);
|
let style = selected_style(selected);
|
||||||
let (name, mut labels) = name_and_labels(&self.name, unread, style);
|
let (name, mut labels) = name_and_labels(&self.name, unread, style);
|
||||||
@@ -1280,7 +1294,12 @@ impl Display for SpaceItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ListItem<IambInfo> for SpaceItem {
|
impl ListItem<IambInfo> for SpaceItem {
|
||||||
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
fn show(
|
||||||
|
&self,
|
||||||
|
selected: bool,
|
||||||
|
_: &ViewportContext<ListCursor>,
|
||||||
|
_: &mut ProgramStore,
|
||||||
|
) -> Text<'_> {
|
||||||
selected_text(self.name.as_str(), selected)
|
selected_text(self.name.as_str(), selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1411,7 +1430,12 @@ impl Display for VerifyItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ListItem<IambInfo> for VerifyItem {
|
impl ListItem<IambInfo> for VerifyItem {
|
||||||
fn show(&self, selected: bool, _: &ViewportContext<ListCursor>, _: &mut ProgramStore) -> Text {
|
fn show(
|
||||||
|
&self,
|
||||||
|
selected: bool,
|
||||||
|
_: &ViewportContext<ListCursor>,
|
||||||
|
_: &mut ProgramStore,
|
||||||
|
) -> Text<'_> {
|
||||||
let mut lines = vec![];
|
let mut lines = vec![];
|
||||||
|
|
||||||
let bold = Style::default().add_modifier(StyleModifier::BOLD);
|
let bold = Style::default().add_modifier(StyleModifier::BOLD);
|
||||||
@@ -1521,7 +1545,7 @@ impl ListItem<IambInfo> for MemberItem {
|
|||||||
selected: bool,
|
selected: bool,
|
||||||
_: &ViewportContext<ListCursor>,
|
_: &ViewportContext<ListCursor>,
|
||||||
store: &mut ProgramStore,
|
store: &mut ProgramStore,
|
||||||
) -> Text {
|
) -> Text<'_> {
|
||||||
let info = store.application.rooms.get_or_default(self.room_id.clone());
|
let info = store.application.rooms.get_or_default(self.room_id.clone());
|
||||||
let user_id = self.member.user_id();
|
let user_id = self.member.user_id();
|
||||||
|
|
||||||
@@ -1565,6 +1589,10 @@ impl ListItem<IambInfo> for MemberItem {
|
|||||||
fn get_word(&self) -> Option<String> {
|
fn get_word(&self) -> Option<String> {
|
||||||
self.member.user_id().to_string().into()
|
self.member.user_id().to_string().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn matches(&self, needle: ®ex::Regex) -> bool {
|
||||||
|
needle.is_match(self.member.name()) || needle.is_match(self.member.user_id().as_str())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Promptable<ProgramContext, ProgramStore, IambInfo> for MemberItem {
|
impl Promptable<ProgramContext, ProgramStore, IambInfo> for MemberItem {
|
||||||
@@ -1644,7 +1672,7 @@ mod tests {
|
|||||||
let server = server_name!("example.com");
|
let server = server_name!("example.com");
|
||||||
|
|
||||||
let room1 = TestRoomItem {
|
let room1 = TestRoomItem {
|
||||||
room_id: RoomId::new(server).to_owned(),
|
room_id: RoomId::new_v1(server).to_owned(),
|
||||||
tags: vec![TagName::Favorite],
|
tags: vec![TagName::Favorite],
|
||||||
alias: Some(room_alias_id!("#room1:example.com").to_owned()),
|
alias: Some(room_alias_id!("#room1:example.com").to_owned()),
|
||||||
name: "Z",
|
name: "Z",
|
||||||
@@ -1653,7 +1681,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let room2 = TestRoomItem {
|
let room2 = TestRoomItem {
|
||||||
room_id: RoomId::new(server).to_owned(),
|
room_id: RoomId::new_v1(server).to_owned(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
alias: Some(room_alias_id!("#a:example.com").to_owned()),
|
alias: Some(room_alias_id!("#a:example.com").to_owned()),
|
||||||
name: "Unnamed Room",
|
name: "Unnamed Room",
|
||||||
@@ -1662,7 +1690,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let room3 = TestRoomItem {
|
let room3 = TestRoomItem {
|
||||||
room_id: RoomId::new(server).to_owned(),
|
room_id: RoomId::new_v1(server).to_owned(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
alias: None,
|
alias: None,
|
||||||
name: "Cool Room",
|
name: "Cool Room",
|
||||||
@@ -1710,7 +1738,7 @@ mod tests {
|
|||||||
let server = server_name!("example.com");
|
let server = server_name!("example.com");
|
||||||
|
|
||||||
let room1 = TestRoomItem {
|
let room1 = TestRoomItem {
|
||||||
room_id: RoomId::new(server).to_owned(),
|
room_id: RoomId::new_v1(server).to_owned(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
alias: None,
|
alias: None,
|
||||||
name: "Room 1",
|
name: "Room 1",
|
||||||
@@ -1719,7 +1747,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let room2 = TestRoomItem {
|
let room2 = TestRoomItem {
|
||||||
room_id: RoomId::new(server).to_owned(),
|
room_id: RoomId::new_v1(server).to_owned(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
alias: None,
|
alias: None,
|
||||||
name: "Room 2",
|
name: "Room 2",
|
||||||
@@ -1731,7 +1759,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let room3 = TestRoomItem {
|
let room3 = TestRoomItem {
|
||||||
room_id: RoomId::new(server).to_owned(),
|
room_id: RoomId::new_v1(server).to_owned(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
alias: None,
|
alias: None,
|
||||||
name: "Room 3",
|
name: "Room 3",
|
||||||
@@ -1762,7 +1790,7 @@ mod tests {
|
|||||||
let server = server_name!("example.com");
|
let server = server_name!("example.com");
|
||||||
|
|
||||||
let room1 = TestRoomItem {
|
let room1 = TestRoomItem {
|
||||||
room_id: RoomId::new(server).to_owned(),
|
room_id: RoomId::new_v1(server).to_owned(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
alias: None,
|
alias: None,
|
||||||
name: "Old room 1",
|
name: "Old room 1",
|
||||||
@@ -1771,7 +1799,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let room2 = TestRoomItem {
|
let room2 = TestRoomItem {
|
||||||
room_id: RoomId::new(server).to_owned(),
|
room_id: RoomId::new_v1(server).to_owned(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
alias: None,
|
alias: None,
|
||||||
name: "Old room 2",
|
name: "Old room 2",
|
||||||
@@ -1780,7 +1808,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let room3 = TestRoomItem {
|
let room3 = TestRoomItem {
|
||||||
room_id: RoomId::new(server).to_owned(),
|
room_id: RoomId::new_v1(server).to_owned(),
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
alias: None,
|
alias: None,
|
||||||
name: "New Fancy Room",
|
name: "New Fancy Room",
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use edit::edit_with_builder as external_edit;
|
use edit::edit_with_builder as external_edit;
|
||||||
use edit::Builder;
|
use edit::Builder;
|
||||||
|
use matrix_sdk::EncryptionState;
|
||||||
use modalkit::editing::store::RegisterError;
|
use modalkit::editing::store::RegisterError;
|
||||||
|
use ratatui::style::{Color, Style};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use tokio;
|
use tokio;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -220,12 +222,10 @@ impl ChatState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (source, msg_filename) = match &ev.content.msgtype {
|
let (source, msg_filename) = match &ev.content.msgtype {
|
||||||
MessageType::Audio(c) => (c.source.clone(), c.body.as_str()),
|
MessageType::Audio(c) => (c.source.clone(), c.filename()),
|
||||||
MessageType::File(c) => {
|
MessageType::File(c) => (c.source.clone(), c.filename()),
|
||||||
(c.source.clone(), c.filename.as_deref().unwrap_or(c.body.as_str()))
|
MessageType::Image(c) => (c.source.clone(), c.filename()),
|
||||||
},
|
MessageType::Video(c) => (c.source.clone(), c.filename()),
|
||||||
MessageType::Image(c) => (c.source.clone(), c.body.as_str()),
|
|
||||||
MessageType::Video(c) => (c.source.clone(), c.body.as_str()),
|
|
||||||
_ => {
|
_ => {
|
||||||
if !flags.contains(DownloadFlags::OPEN) {
|
if !flags.contains(DownloadFlags::OPEN) {
|
||||||
return Err(IambError::NoAttachment.into());
|
return Err(IambError::NoAttachment.into());
|
||||||
@@ -263,7 +263,7 @@ impl ChatState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if filename.is_dir() {
|
if filename.is_dir() {
|
||||||
filename.push(msg_filename);
|
filename.push(msg_filename.replace(std::path::MAIN_SEPARATOR_STR, "_"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if filename.exists() && !flags.contains(DownloadFlags::FORCE) {
|
if filename.exists() && !flags.contains(DownloadFlags::FORCE) {
|
||||||
@@ -273,9 +273,9 @@ impl ChatState {
|
|||||||
let mut filename_incr = filename.clone();
|
let mut filename_incr = filename.clone();
|
||||||
for n in 1..=1000 {
|
for n in 1..=1000 {
|
||||||
if let Some(ext) = ext.and_then(OsStr::to_str) {
|
if let Some(ext) = ext.and_then(OsStr::to_str) {
|
||||||
filename_incr.set_file_name(format!("{}-{}.{}", stem, n, ext));
|
filename_incr.set_file_name(format!("{stem}-{n}.{ext}"));
|
||||||
} else {
|
} else {
|
||||||
filename_incr.set_file_name(format!("{}-{}", stem, n));
|
filename_incr.set_file_name(format!("{stem}-{n}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filename_incr.exists() {
|
if !filename_incr.exists() {
|
||||||
@@ -401,7 +401,7 @@ impl ChatState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if info.user_reactions_contains(&settings.profile.user_id, &event_id, &emoji) {
|
if info.user_reactions_contains(&settings.profile.user_id, &event_id, &emoji) {
|
||||||
let msg = format!("You’ve already reacted to this message with {}", emoji);
|
let msg = format!("You’ve already reacted to this message with {emoji}");
|
||||||
let err = UIError::Failure(msg);
|
let err = UIError::Failure(msg);
|
||||||
|
|
||||||
return Err(err);
|
return Err(err);
|
||||||
@@ -450,6 +450,21 @@ impl ChatState {
|
|||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
},
|
},
|
||||||
|
MessageAction::Replied => {
|
||||||
|
let Some(reply) = msg.reply_to() else {
|
||||||
|
let msg = "Selected message is not a reply";
|
||||||
|
return Err(UIError::Failure(msg.into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(key) = info.get_message_key(&reply) else {
|
||||||
|
store.application.need_load.need_message(self.room_id.clone(), reply);
|
||||||
|
let msg = "Replied to message will be loaded in the background";
|
||||||
|
return Err(UIError::Failure(msg.into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
self.scrollback.goto_message(key.clone());
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
MessageAction::Unreact(reaction, literal) => {
|
MessageAction::Unreact(reaction, literal) => {
|
||||||
let emoji = match reaction {
|
let emoji = match reaction {
|
||||||
reaction if literal => reaction,
|
reaction if literal => reaction,
|
||||||
@@ -612,8 +627,7 @@ impl ChatState {
|
|||||||
let mut buff = std::io::Cursor::new(bytes);
|
let mut buff = std::io::Cursor::new(bytes);
|
||||||
dynimage.write_to(&mut buff, image::ImageFormat::Png)?;
|
dynimage.write_to(&mut buff, image::ImageFormat::Png)?;
|
||||||
Ok(buff.into_inner())
|
Ok(buff.into_inner())
|
||||||
})
|
})?;
|
||||||
.map_err(IambError::from)?;
|
|
||||||
let mime = mime::IMAGE_PNG;
|
let mime = mime::IMAGE_PNG;
|
||||||
|
|
||||||
let name = "Clipboard.png";
|
let name = "Clipboard.png";
|
||||||
@@ -978,7 +992,16 @@ impl StatefulWidget for Chat<'_> {
|
|||||||
Paragraph::new(desc_spans).render(descarea, buf);
|
Paragraph::new(desc_spans).render(descarea, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
let prompt = if self.focused { "> " } else { " " };
|
let prompt = match (self.focused, state.room().encryption_state()) {
|
||||||
|
(false, _) => Span::raw(" "),
|
||||||
|
(_, EncryptionState::Encrypted) => {
|
||||||
|
Span::styled("\u{1F512}\u{FE0E} ", Style::new().fg(Color::LightGreen))
|
||||||
|
},
|
||||||
|
(_, EncryptionState::NotEncrypted) => {
|
||||||
|
Span::styled("\u{1F513}\u{FE0E} ", Style::new().fg(Color::Red))
|
||||||
|
},
|
||||||
|
(_, EncryptionState::Unknown) => Span::styled("> ", Style::new().fg(Color::Red)),
|
||||||
|
};
|
||||||
|
|
||||||
let tbox = TextBox::new().prompt(prompt);
|
let tbox = TextBox::new().prompt(prompt);
|
||||||
tbox.render(textarea, buf, &mut state.tbox);
|
tbox.render(textarea, buf, &mut state.tbox);
|
||||||
|
|||||||
@@ -120,19 +120,19 @@ fn hist_visibility_mode(name: impl Into<String>) -> IambResult<HistoryVisibility
|
|||||||
/// that operations like sending and accepting invites, opening the members window, etc., all work
|
/// that operations like sending and accepting invites, opening the members window, etc., all work
|
||||||
/// similarly.
|
/// similarly.
|
||||||
pub enum RoomState {
|
pub enum RoomState {
|
||||||
Chat(ChatState),
|
Chat(Box<ChatState>),
|
||||||
Space(SpaceState),
|
Space(Box<SpaceState>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ChatState> for RoomState {
|
impl From<ChatState> for RoomState {
|
||||||
fn from(chat: ChatState) -> Self {
|
fn from(chat: ChatState) -> Self {
|
||||||
RoomState::Chat(chat)
|
RoomState::Chat(Box::new(chat))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SpaceState> for RoomState {
|
impl From<SpaceState> for RoomState {
|
||||||
fn from(space: SpaceState) -> Self {
|
fn from(space: SpaceState) -> Self {
|
||||||
RoomState::Space(space)
|
RoomState::Space(Box::new(space))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,7 +658,7 @@ impl RoomState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_title(&self, store: &mut ProgramStore) -> Line {
|
pub fn get_title(&self, store: &mut ProgramStore) -> Line<'_> {
|
||||||
let title = store.application.get_room_title(self.id());
|
let title = store.application.get_room_title(self.id());
|
||||||
let style = Style::default().add_modifier(StyleModifier::BOLD);
|
let style = Style::default().add_modifier(StyleModifier::BOLD);
|
||||||
let mut spans = vec![];
|
let mut spans = vec![];
|
||||||
@@ -776,8 +776,8 @@ impl WindowOps<IambInfo> for RoomState {
|
|||||||
|
|
||||||
fn dup(&self, store: &mut ProgramStore) -> Self {
|
fn dup(&self, store: &mut ProgramStore) -> Self {
|
||||||
match self {
|
match self {
|
||||||
RoomState::Chat(chat) => RoomState::Chat(chat.dup(store)),
|
RoomState::Chat(chat) => RoomState::Chat(Box::new(chat.dup(store))),
|
||||||
RoomState::Space(space) => RoomState::Space(space.dup(store)),
|
RoomState::Space(space) => RoomState::Space(Box::new(space.dup(store))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ use crate::{
|
|||||||
IambId,
|
IambId,
|
||||||
IambInfo,
|
IambInfo,
|
||||||
IambResult,
|
IambResult,
|
||||||
Need,
|
|
||||||
ProgramContext,
|
ProgramContext,
|
||||||
ProgramStore,
|
ProgramStore,
|
||||||
RoomFetchStatus,
|
RoomFetchStatus,
|
||||||
@@ -165,6 +164,12 @@ impl ScrollbackState {
|
|||||||
self.cursor = MessageCursor::latest();
|
self.cursor = MessageCursor::latest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn goto_message(&mut self, target: MessageKey) {
|
||||||
|
let mut cursor = MessageCursor::new(target, 0);
|
||||||
|
std::mem::swap(&mut cursor, &mut self.cursor);
|
||||||
|
self.jumped.push(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the dimensions and placement within the terminal window for this list.
|
/// Set the dimensions and placement within the terminal window for this list.
|
||||||
pub fn set_term_info(&mut self, area: Rect) {
|
pub fn set_term_info(&mut self, area: Rect) {
|
||||||
self.viewctx.dimensions = (area.width as usize, area.height as usize);
|
self.viewctx.dimensions = (area.width as usize, area.height as usize);
|
||||||
@@ -689,10 +694,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||||||
|
|
||||||
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
|
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
|
||||||
if needs_load {
|
if needs_load {
|
||||||
store
|
store.application.need_load.need_messages(self.room_id.clone());
|
||||||
.application
|
|
||||||
.need_load
|
|
||||||
.insert(self.room_id.clone(), Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
mc
|
mc
|
||||||
},
|
},
|
||||||
@@ -768,10 +770,7 @@ impl EditorActions<ProgramContext, ProgramStore, IambInfo> for ScrollbackState {
|
|||||||
|
|
||||||
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
|
let (mc, needs_load) = self.find_message(key, dir, &needle, count, info);
|
||||||
if needs_load {
|
if needs_load {
|
||||||
store
|
store.application.need_load.need_messages(self.room_id.to_owned());
|
||||||
.application
|
|
||||||
.need_load
|
|
||||||
.insert(self.room_id.to_owned(), Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mc.map(|c| self._range_to(c))
|
mc.map(|c| self._range_to(c))
|
||||||
@@ -1328,10 +1327,7 @@ impl StatefulWidget for Scrollback<'_> {
|
|||||||
k
|
k
|
||||||
} else {
|
} else {
|
||||||
if state.need_more_messages(info) {
|
if state.need_more_messages(info) {
|
||||||
self.store
|
self.store.application.need_load.need_messages(state.room_id.to_owned());
|
||||||
.application
|
|
||||||
.need_load
|
|
||||||
.insert(state.room_id.to_owned(), Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -1435,10 +1431,7 @@ impl StatefulWidget for Scrollback<'_> {
|
|||||||
// Check whether we should load older messages for this room.
|
// Check whether we should load older messages for this room.
|
||||||
if state.need_more_messages(info) {
|
if state.need_more_messages(info) {
|
||||||
// If the top of the screen is the older message, load more.
|
// If the top of the screen is the older message, load more.
|
||||||
self.store
|
self.store.application.need_load.need_messages(state.room_id.to_owned());
|
||||||
.application
|
|
||||||
.need_load
|
|
||||||
.insert(state.room_id.to_owned(), Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info.draw_last = self.store.application.draw_curr;
|
info.draw_last = self.store.application.draw_curr;
|
||||||
@@ -1448,7 +1441,7 @@ impl StatefulWidget for Scrollback<'_> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::tests::*;
|
use crate::{base::Need, tests::*};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_search_messages() {
|
async fn test_search_messages() {
|
||||||
@@ -1493,7 +1486,7 @@ mod tests {
|
|||||||
std::mem::take(&mut store.application.need_load)
|
std::mem::take(&mut store.application.need_load)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<(OwnedRoomId, Need)>>(),
|
.collect::<Vec<(OwnedRoomId, Need)>>(),
|
||||||
vec![(room_id.clone(), Need::MESSAGES)]
|
vec![(room_id.clone(), Need { messages: Some(Vec::new()), members: false })]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Search forward twice to MSG1.
|
// Search forward twice to MSG1.
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
//! Window for Matrix spaces
|
//! Window for Matrix spaces
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::str::FromStr;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use matrix_sdk::ruma::events::space::child::SpaceChildEventContent;
|
use matrix_sdk::ruma::events::space::child::SpaceChildEventContent;
|
||||||
use matrix_sdk::ruma::events::StateEventType;
|
use matrix_sdk::ruma::events::StateEventType;
|
||||||
|
use matrix_sdk::ruma::OwnedSpaceChildOrder;
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
room::Room as MatrixRoom,
|
room::Room as MatrixRoom,
|
||||||
ruma::{OwnedRoomId, RoomId},
|
ruma::{OwnedRoomId, RoomId},
|
||||||
@@ -91,19 +93,25 @@ impl SpaceState {
|
|||||||
SpaceAction::SetChild(child_id, order, suggested) => {
|
SpaceAction::SetChild(child_id, order, suggested) => {
|
||||||
if !self
|
if !self
|
||||||
.room
|
.room
|
||||||
.can_user_send_state(
|
.power_levels()
|
||||||
|
.await
|
||||||
|
.map_err(matrix_sdk::Error::from)
|
||||||
|
.map_err(IambError::from)?
|
||||||
|
.user_can_send_state(
|
||||||
&store.application.settings.profile.user_id,
|
&store.application.settings.profile.user_id,
|
||||||
StateEventType::SpaceChild,
|
StateEventType::SpaceChild,
|
||||||
)
|
)
|
||||||
.await
|
|
||||||
.map_err(IambError::from)?
|
|
||||||
{
|
{
|
||||||
return Err(IambError::InsufficientPermission.into());
|
return Err(IambError::InsufficientPermission.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let via = self.room.route().await.map_err(IambError::from)?;
|
let via = self.room.route().await.map_err(IambError::from)?;
|
||||||
let mut ev = SpaceChildEventContent::new(via);
|
let mut ev = SpaceChildEventContent::new(via);
|
||||||
ev.order = order;
|
ev.order = order
|
||||||
|
.as_deref()
|
||||||
|
.map(OwnedSpaceChildOrder::from_str)
|
||||||
|
.transpose()
|
||||||
|
.map_err(IambError::InvalidSpaceChildOrder)?;
|
||||||
ev.suggested = suggested;
|
ev.suggested = suggested;
|
||||||
let _ = self
|
let _ = self
|
||||||
.room
|
.room
|
||||||
@@ -117,12 +125,14 @@ impl SpaceState {
|
|||||||
let space = self.list.get().ok_or(IambError::NoSelectedRoomOrSpaceItem)?;
|
let space = self.list.get().ok_or(IambError::NoSelectedRoomOrSpaceItem)?;
|
||||||
if !self
|
if !self
|
||||||
.room
|
.room
|
||||||
.can_user_send_state(
|
.power_levels()
|
||||||
|
.await
|
||||||
|
.map_err(matrix_sdk::Error::from)
|
||||||
|
.map_err(IambError::from)?
|
||||||
|
.user_can_send_state(
|
||||||
&store.application.settings.profile.user_id,
|
&store.application.settings.profile.user_id,
|
||||||
StateEventType::SpaceChild,
|
StateEventType::SpaceChild,
|
||||||
)
|
)
|
||||||
.await
|
|
||||||
.map_err(IambError::from)?
|
|
||||||
{
|
{
|
||||||
return Err(IambError::InsufficientPermission.into());
|
return Err(IambError::InsufficientPermission.into());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ use matrix_sdk::{
|
|||||||
use modalkit::errors::UIError;
|
use modalkit::errors::UIError;
|
||||||
use modalkit::prelude::{EditInfo, InfoMessage};
|
use modalkit::prelude::{EditInfo, InfoMessage};
|
||||||
|
|
||||||
use crate::base::Need;
|
use crate::base::MessageNeed;
|
||||||
use crate::notifications::register_notifications;
|
use crate::notifications::register_notifications;
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{
|
base::{
|
||||||
@@ -216,7 +216,7 @@ async fn update_event_receipts(info: &mut RoomInfo, room: &MatrixRoom, event_id:
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Plan {
|
enum Plan {
|
||||||
Messages(OwnedRoomId, Option<String>),
|
Messages(OwnedRoomId, Option<String>, Vec<MessageNeed>),
|
||||||
Members(OwnedRoomId),
|
Members(OwnedRoomId),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,8 +225,8 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
|
|||||||
let ChatStore { need_load, rooms, .. } = &mut locked.application;
|
let ChatStore { need_load, rooms, .. } = &mut locked.application;
|
||||||
let mut plan = Vec::with_capacity(need_load.rooms() * 2);
|
let mut plan = Vec::with_capacity(need_load.rooms() * 2);
|
||||||
|
|
||||||
for (room_id, mut need) in std::mem::take(need_load).into_iter() {
|
for (room_id, need) in std::mem::take(need_load).into_iter() {
|
||||||
if need.contains(Need::MESSAGES) {
|
if let Some(message_need) = need.messages {
|
||||||
let info = rooms.get_or_default(room_id.clone());
|
let info = rooms.get_or_default(room_id.clone());
|
||||||
|
|
||||||
if !info.recently_fetched() && !info.fetching {
|
if !info.recently_fetched() && !info.fetching {
|
||||||
@@ -239,16 +239,11 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
|
|||||||
RoomFetchStatus::NotStarted => None,
|
RoomFetchStatus::NotStarted => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
plan.push(Plan::Messages(room_id.to_owned(), fetch_id));
|
plan.push(Plan::Messages(room_id.to_owned(), fetch_id, message_need));
|
||||||
need.remove(Need::MESSAGES);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if need.contains(Need::MEMBERS) {
|
if need.members {
|
||||||
plan.push(Plan::Members(room_id.to_owned()));
|
plan.push(Plan::Members(room_id.to_owned()));
|
||||||
need.remove(Need::MEMBERS);
|
|
||||||
}
|
|
||||||
if !need.is_empty() {
|
|
||||||
need_load.insert(room_id, need);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,14 +253,14 @@ async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
|
|||||||
async fn run_plan(client: &Client, store: &AsyncProgramStore, plan: Plan, permits: &Semaphore) {
|
async fn run_plan(client: &Client, store: &AsyncProgramStore, plan: Plan, permits: &Semaphore) {
|
||||||
let permit = permits.acquire().await;
|
let permit = permits.acquire().await;
|
||||||
match plan {
|
match plan {
|
||||||
Plan::Messages(room_id, fetch_id) => {
|
Plan::Messages(room_id, fetch_id, message_need) => {
|
||||||
let limit = MIN_MSG_LOAD;
|
let limit = MIN_MSG_LOAD;
|
||||||
let client = client.clone();
|
let client = client.clone();
|
||||||
let store_clone = store.clone();
|
let store_clone = store.clone();
|
||||||
|
|
||||||
let res = load_older_one(&client, &room_id, fetch_id, limit).await;
|
let res = load_older_one(&client, &room_id, fetch_id, limit).await;
|
||||||
let mut locked = store.lock().await;
|
let mut locked = store.lock().await;
|
||||||
load_insert(room_id, res, locked.deref_mut(), store_clone);
|
load_insert(room_id, res, locked.deref_mut(), store_clone, message_need);
|
||||||
},
|
},
|
||||||
Plan::Members(room_id) => {
|
Plan::Members(room_id) => {
|
||||||
let res = members_load(client, &room_id).await;
|
let res = members_load(client, &room_id).await;
|
||||||
@@ -283,6 +278,9 @@ async fn load_older_one(
|
|||||||
limit: u32,
|
limit: u32,
|
||||||
) -> MessageFetchResult {
|
) -> MessageFetchResult {
|
||||||
if let Some(room) = client.get_room(room_id) {
|
if let Some(room) = client.get_room(room_id) {
|
||||||
|
// Update cached encryption state. This is a noop if the state is already cached.
|
||||||
|
let _ = room.request_encryption_state().await;
|
||||||
|
|
||||||
let mut opts = match &fetch_id {
|
let mut opts = match &fetch_id {
|
||||||
Some(id) => MessagesOptions::backward().from(id.as_str()),
|
Some(id) => MessagesOptions::backward().from(id.as_str()),
|
||||||
None => MessagesOptions::backward(),
|
None => MessagesOptions::backward(),
|
||||||
@@ -325,6 +323,7 @@ fn load_insert(
|
|||||||
res: MessageFetchResult,
|
res: MessageFetchResult,
|
||||||
locked: &mut ProgramStore,
|
locked: &mut ProgramStore,
|
||||||
store: AsyncProgramStore,
|
store: AsyncProgramStore,
|
||||||
|
message_needs: Vec<MessageNeed>,
|
||||||
) {
|
) {
|
||||||
let ChatStore { presences, rooms, worker, picker, settings, .. } = &mut locked.application;
|
let ChatStore { presences, rooms, worker, picker, settings, .. } = &mut locked.application;
|
||||||
let info = rooms.get_or_default(room_id.clone());
|
let info = rooms.get_or_default(room_id.clone());
|
||||||
@@ -370,12 +369,25 @@ fn load_insert(
|
|||||||
}
|
}
|
||||||
|
|
||||||
info.fetch_id = fetch_id.map_or(RoomFetchStatus::Done, RoomFetchStatus::HaveMore);
|
info.fetch_id = fetch_id.map_or(RoomFetchStatus::Done, RoomFetchStatus::HaveMore);
|
||||||
|
|
||||||
|
// check if more are needed
|
||||||
|
let needs: Vec<_> = message_needs
|
||||||
|
.into_iter()
|
||||||
|
.filter(|need| !info.keys.contains_key(&need.event_id) && need.ttl > 0)
|
||||||
|
.map(|mut need| {
|
||||||
|
need.ttl -= 1;
|
||||||
|
need
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if !needs.is_empty() {
|
||||||
|
locked.application.need_load.need_messages_all(room_id, needs);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages");
|
warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages");
|
||||||
|
|
||||||
// Wait and try again.
|
// Wait and try again.
|
||||||
locked.application.need_load.insert(room_id, Need::MESSAGES);
|
locked.application.need_load.need_messages_all(room_id, message_needs);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -557,7 +569,7 @@ pub async fn do_first_sync(client: &Client, store: &AsyncProgramStore) -> Result
|
|||||||
let mut filter = FilterDefinition::default();
|
let mut filter = FilterDefinition::default();
|
||||||
filter.room = room_ev;
|
filter.room = room_ev;
|
||||||
|
|
||||||
let settings = SyncSettings::new().filter(filter.into());
|
let settings = SyncSettings::new().filter(filter.into()).timeout(Duration::from_secs(0));
|
||||||
|
|
||||||
client.sync_once(settings).await?;
|
client.sync_once(settings).await?;
|
||||||
|
|
||||||
@@ -570,12 +582,12 @@ pub async fn do_first_sync(client: &Client, store: &AsyncProgramStore) -> Result
|
|||||||
|
|
||||||
for room in sync_info.rooms.iter() {
|
for room in sync_info.rooms.iter() {
|
||||||
let room_id = room.as_ref().0.room_id().to_owned();
|
let room_id = room.as_ref().0.room_id().to_owned();
|
||||||
need_load.insert(room_id, Need::MESSAGES);
|
need_load.need_messages(room_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
for room in sync_info.dms.iter() {
|
for room in sync_info.dms.iter() {
|
||||||
let room_id = room.as_ref().0.room_id().to_owned();
|
let room_id = room.as_ref().0.room_id().to_owned();
|
||||||
need_load.insert(room_id, Need::MESSAGES);
|
need_load.need_messages(room_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -708,7 +720,7 @@ async fn create_client_inner(
|
|||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let req_config = RequestConfig::new().timeout(req_timeout).retry_timeout(req_timeout);
|
let req_config = RequestConfig::new().timeout(req_timeout).max_retry_time(req_timeout);
|
||||||
|
|
||||||
// Set up the Matrix client for the selected profile.
|
// Set up the Matrix client for the selected profile.
|
||||||
let builder = Client::builder()
|
let builder = Client::builder()
|
||||||
@@ -1084,11 +1096,15 @@ impl ClientWorker {
|
|||||||
async move {
|
async move {
|
||||||
let room_id = room.room_id();
|
let room_id = room.room_id();
|
||||||
let room_info = room.clone_info();
|
let room_info = room.clone_info();
|
||||||
let room_version = room_info.room_version().unwrap_or(&RoomVersionId::V1);
|
let rules = &room_info
|
||||||
|
.room_version()
|
||||||
|
.and_then(RoomVersionId::rules)
|
||||||
|
.unwrap_or(RoomVersionId::V1.rules().unwrap())
|
||||||
|
.redaction;
|
||||||
|
|
||||||
let mut locked = store.lock().await;
|
let mut locked = store.lock().await;
|
||||||
let info = locked.application.get_room_info(room_id.to_owned());
|
let info = locked.application.get_room_info(room_id.to_owned());
|
||||||
info.redact(ev, room_version);
|
info.redact(ev, rules);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1106,7 +1122,7 @@ impl ClientWorker {
|
|||||||
ev.content.displayname.as_deref().unwrap_or_else(|| user_id.as_str()),
|
ev.content.displayname.as_deref().unwrap_or_else(|| user_id.as_str()),
|
||||||
);
|
);
|
||||||
let ambiguous = client
|
let ambiguous = client
|
||||||
.store()
|
.state_store()
|
||||||
.get_users_with_display_name(room_id, &ambiguous_name)
|
.get_users_with_display_name(room_id, &ambiguous_name)
|
||||||
.await
|
.await
|
||||||
.map(|users| users.len() > 1)
|
.map(|users| users.len() > 1)
|
||||||
@@ -1244,7 +1260,7 @@ impl ClientWorker {
|
|||||||
let settings = self.settings.clone();
|
let settings = self.settings.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
while !client.logged_in() {
|
while !client.is_active() {
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1418,7 +1434,7 @@ impl ClientWorker {
|
|||||||
|
|
||||||
let resp = self.client.send(req).await.map_err(IambError::from)?;
|
let resp = self.client.send(req).await.map_err(IambError::from)?;
|
||||||
|
|
||||||
let rooms = resp.rooms.into_iter().map(|chunk| chunk.room_id).collect();
|
let rooms = resp.rooms.into_iter().map(|chunk| chunk.summary.room_id).collect();
|
||||||
|
|
||||||
Ok(rooms)
|
Ok(rooms)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user