forked from Mirrors/gomuks
Compare commits
32 commits
Author | SHA1 | Date | |
---|---|---|---|
d5666f9f00 | |||
338c7d9adc | |||
7c5ab68cf2 | |||
![]() |
295d1f156e | ||
![]() |
c312a2b523 | ||
![]() |
99b3fd0e5e | ||
![]() |
c48f24d2de | ||
![]() |
696687f60c | ||
![]() |
4262b5abfa | ||
![]() |
87ec9d60a5 | ||
![]() |
9c17ce001d | ||
![]() |
0f2263ce8d | ||
![]() |
92d3ab64bf | ||
![]() |
746e26fbc1 | ||
![]() |
3041fb18e3 | ||
![]() |
ede1c92906 | ||
![]() |
0ed5dfcc12 | ||
![]() |
7f80301276 | ||
![]() |
3f4333003d | ||
![]() |
218481f3a4 | ||
![]() |
ef05bc71f9 | ||
![]() |
86843d61f6 | ||
![]() |
6b9f6bebd5 | ||
![]() |
aee4cff572 | ||
![]() |
678940618f | ||
![]() |
6425f68c88 | ||
![]() |
0db18f1e94 | ||
![]() |
fbad48129b | ||
![]() |
b3b255e71c | ||
![]() |
5da85acfe0 | ||
![]() |
f79678a87f | ||
![]() |
23f2699909 |
43 changed files with 871 additions and 569 deletions
11
.forgeo/workflows
Normal file
11
.forgeo/workflows
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- uses: actions/cascading-pr@v1.0.1
|
||||||
|
with:
|
||||||
|
GOPATH="$CI_PROJECT_DIR/.cache"
|
||||||
|
GOCACHE="$CI_PROJECT_DIR/.cache/build"
|
||||||
|
MAUTRIX_VERSION=$(cat go.mod | grep 'maunium.net/go/mautrix ' | awk '{ print $2 }')
|
||||||
|
GO_LDFLAGS="-s -w -linkmode external -extldflags -static -X go.mau.fi/gomuks/version.Tag=$CI_COMMIT_TAG -X go.mau.fi/gomuks/version.Commit=$CI_COMMIT_SHA -X 'go.mau.fi/gomuks/version.BuildTime=`date -Iseconds`' -X 'maunium.net/go/mautrix.GoModVersion=$MAUTRIX_VERSION'"
|
19
README.md
19
README.md
|
@ -1,18 +1,7 @@
|
||||||
# gomuks
|
# nyxmuks
|
||||||

|
|
||||||
[](LICENSE)
|
|
||||||
[](https://github.com/tulir/gomuks/releases)
|
|
||||||
[](https://mau.dev/tulir/gomuks/pipelines)
|
|
||||||
|
|
||||||
A Matrix client written in Go using [mautrix](https://github.com/mautrix/go).
|
Soft fork of Tulir's Gomuks.
|
||||||
|
|
||||||
This branch contains gomuks web. For legacy gomuks terminal, see the
|
# why?
|
||||||
[master branch](https://github.com/tulir/gomuks/tree/master). The new
|
|
||||||
version will get a terminal frontend in the future. See also:
|
|
||||||
<https://github.com/tulir/gomuks/issues/476>.
|
|
||||||
|
|
||||||
## Docs
|
Gomuks is adding unneccesary features, and the developer is acting maliciously. This fork aims to remove a few features that seem to be made with a malicious intent, like the "un-redact" button.
|
||||||
For installation and usage instructions, see [docs.mau.fi](https://docs.mau.fi/gomuks/).
|
|
||||||
|
|
||||||
## Discussion
|
|
||||||
Matrix room: [#gomuks:maunium.net](https://matrix.to/#/#gomuks:maunium.net)
|
|
|
@ -8,7 +8,7 @@ require github.com/wailsapp/wails/v3 v3.0.0-alpha.9
|
||||||
|
|
||||||
require (
|
require (
|
||||||
go.mau.fi/gomuks v0.4.0
|
go.mau.fi/gomuks v0.4.0
|
||||||
go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95
|
go.mau.fi/util v0.8.6
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -22,7 +22,7 @@ require (
|
||||||
github.com/buckket/go-blurhash v1.1.0 // indirect
|
github.com/buckket/go-blurhash v1.1.0 // indirect
|
||||||
github.com/chzyer/readline v1.5.1 // indirect
|
github.com/chzyer/readline v1.5.1 // indirect
|
||||||
github.com/cloudflare/circl v1.3.8 // indirect
|
github.com/cloudflare/circl v1.3.8 // indirect
|
||||||
github.com/coder/websocket v1.8.12 // indirect
|
github.com/coder/websocket v1.8.13 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||||
github.com/disintegration/imaging v1.6.2 // indirect
|
github.com/disintegration/imaging v1.6.2 // indirect
|
||||||
|
@ -47,7 +47,7 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a // indirect
|
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
@ -66,20 +66,20 @@ require (
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
github.com/yuin/goldmark v1.7.8 // indirect
|
||||||
go.mau.fi/webp v0.2.0 // indirect
|
go.mau.fi/webp v0.2.0 // indirect
|
||||||
go.mau.fi/zeroconfig v0.1.3 // indirect
|
go.mau.fi/zeroconfig v0.1.3 // indirect
|
||||||
golang.org/x/crypto v0.34.0 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||||
golang.org/x/image v0.24.0 // indirect
|
golang.org/x/image v0.25.0 // indirect
|
||||||
golang.org/x/mod v0.23.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/net v0.35.0 // indirect
|
golang.org/x/net v0.37.0 // indirect
|
||||||
golang.org/x/sync v0.11.0 // indirect
|
golang.org/x/sync v0.12.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
golang.org/x/tools v0.30.0 // indirect
|
golang.org/x/tools v0.31.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7 // indirect
|
maunium.net/go/mautrix v0.23.2 // indirect
|
||||||
mvdan.cc/xurls/v2 v2.6.0 // indirect
|
mvdan.cc/xurls/v2 v2.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||||
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
|
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
|
||||||
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
|
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
|
||||||
|
@ -113,8 +113,8 @@ github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBW
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a h1:ckxP/kGzsxvxXo8jO6E/0QJ8MMmwI7IRj4Fys9QbAZA=
|
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs=
|
||||||
github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||||
|
@ -166,8 +166,8 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95 h1:5EfVWWjU2Hte9uE6B/hBgvjnVfBx/7SYDZBnsuo+EBs=
|
go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54=
|
||||||
go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M=
|
go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE=
|
||||||
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||||
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
||||||
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
||||||
|
@ -177,17 +177,17 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f h1:oFMYAjX0867ZD2jcNiLBrI9BdpmEkvPyi5YrBGXbamg=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
@ -196,13 +196,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -222,15 +222,15 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
@ -238,14 +238,14 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
@ -261,7 +261,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7 h1:AeNHqITptzOpmfMxnqmQRw6xN7DUDCgsN00BaPyRd4k=
|
maunium.net/go/mautrix v0.23.2 h1:Bo3tPrQJwkxyL7aMmy/T+d2tqIrypZjHqeHe8fyeAOg=
|
||||||
maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7/go.mod h1:IHMaSJh7YIxMrZSDVefS+nLdr3RbeLowsCSa6ibONZ0=
|
maunium.net/go/mautrix v0.23.2/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE=
|
||||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
||||||
|
|
22
go.mod
22
go.mod
|
@ -2,13 +2,13 @@ module go.mau.fi/gomuks
|
||||||
|
|
||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
toolchain go1.24.0
|
toolchain go1.24.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma/v2 v2.15.0
|
github.com/alecthomas/chroma/v2 v2.15.0
|
||||||
github.com/buckket/go-blurhash v1.1.0
|
github.com/buckket/go-blurhash v1.1.0
|
||||||
github.com/chzyer/readline v1.5.1
|
github.com/chzyer/readline v1.5.1
|
||||||
github.com/coder/websocket v1.8.12
|
github.com/coder/websocket v1.8.13
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8
|
github.com/gabriel-vasile/mimetype v1.4.8
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||||
|
@ -18,16 +18,16 @@ require (
|
||||||
github.com/tidwall/gjson v1.18.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
github.com/tidwall/sjson v1.2.5
|
github.com/tidwall/sjson v1.2.5
|
||||||
github.com/yuin/goldmark v1.7.8
|
github.com/yuin/goldmark v1.7.8
|
||||||
go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95
|
go.mau.fi/util v0.8.6
|
||||||
go.mau.fi/webp v0.2.0
|
go.mau.fi/webp v0.2.0
|
||||||
go.mau.fi/zeroconfig v0.1.3
|
go.mau.fi/zeroconfig v0.1.3
|
||||||
golang.org/x/crypto v0.34.0
|
golang.org/x/crypto v0.36.0
|
||||||
golang.org/x/image v0.24.0
|
golang.org/x/image v0.25.0
|
||||||
golang.org/x/net v0.35.0
|
golang.org/x/net v0.37.0
|
||||||
golang.org/x/text v0.22.0
|
golang.org/x/text v0.23.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
maunium.net/go/mauflag v1.0.0
|
maunium.net/go/mauflag v1.0.0
|
||||||
maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7
|
maunium.net/go/mautrix v0.23.2
|
||||||
mvdan.cc/xurls/v2 v2.6.0
|
mvdan.cc/xurls/v2 v2.6.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,11 +37,11 @@ require (
|
||||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a // indirect
|
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
40
go.sum
40
go.sum
|
@ -16,8 +16,8 @@ github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI
|
||||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
@ -42,8 +42,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a h1:ckxP/kGzsxvxXo8jO6E/0QJ8MMmwI7IRj4Fys9QbAZA=
|
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs=
|
||||||
github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
@ -68,30 +68,30 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95 h1:5EfVWWjU2Hte9uE6B/hBgvjnVfBx/7SYDZBnsuo+EBs=
|
go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54=
|
||||||
go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M=
|
go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE=
|
||||||
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
|
||||||
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
|
||||||
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
|
||||||
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
|
||||||
golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f h1:oFMYAjX0867ZD2jcNiLBrI9BdpmEkvPyi5YrBGXbamg=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||||
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
@ -100,7 +100,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
||||||
maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7 h1:AeNHqITptzOpmfMxnqmQRw6xN7DUDCgsN00BaPyRd4k=
|
maunium.net/go/mautrix v0.23.2 h1:Bo3tPrQJwkxyL7aMmy/T+d2tqIrypZjHqeHe8fyeAOg=
|
||||||
maunium.net/go/mautrix v0.23.2-0.20250304004736-7d3791ace3b7/go.mod h1:IHMaSJh7YIxMrZSDVefS+nLdr3RbeLowsCSa6ibONZ0=
|
maunium.net/go/mautrix v0.23.2/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE=
|
||||||
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
|
||||||
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
|
||||||
|
|
|
@ -157,12 +157,18 @@ func (pn *PushNotification) Split(yield func(*PushNotification) bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gmx *Gomuks) SendPushNotification(ctx context.Context, pushRegs []*database.PushRegistration, notif *PushNotification) {
|
func (gmx *Gomuks) SendPushNotification(ctx context.Context, pushRegs []*database.PushRegistration, notif *PushNotification) {
|
||||||
|
log := zerolog.Ctx(ctx).With().
|
||||||
|
Bool("important", notif.HasImportant).
|
||||||
|
Int("message_count", len(notif.RawMessages)).
|
||||||
|
Int("dismiss_count", len(notif.Dismiss)).
|
||||||
|
Logger()
|
||||||
|
ctx = log.WithContext(ctx)
|
||||||
rawPayload, err := json.Marshal(notif)
|
rawPayload, err := json.Marshal(notif)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to marshal push notification")
|
log.Err(err).Msg("Failed to marshal push notification")
|
||||||
return
|
return
|
||||||
} else if base64.StdEncoding.EncodedLen(len(rawPayload)) >= 4000 {
|
} else if base64.StdEncoding.EncodedLen(len(rawPayload)) >= 4000 {
|
||||||
zerolog.Ctx(ctx).Error().Msg("Generated push payload too long")
|
log.Error().Msg("Generated push payload too long")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, reg := range pushRegs {
|
for _, reg := range pushRegs {
|
||||||
|
@ -172,7 +178,7 @@ func (gmx *Gomuks) SendPushNotification(ctx context.Context, pushRegs []*databas
|
||||||
var err error
|
var err error
|
||||||
devicePayload, err = encryptPush(rawPayload, reg.Encryption.Key)
|
devicePayload, err = encryptPush(rawPayload, reg.Encryption.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Str("device_id", reg.DeviceID).Msg("Failed to encrypt push payload")
|
log.Err(err).Str("device_id", reg.DeviceID).Msg("Failed to encrypt push payload")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
encrypted = true
|
encrypted = true
|
||||||
|
@ -180,7 +186,7 @@ func (gmx *Gomuks) SendPushNotification(ctx context.Context, pushRegs []*databas
|
||||||
switch reg.Type {
|
switch reg.Type {
|
||||||
case database.PushTypeFCM:
|
case database.PushTypeFCM:
|
||||||
if !encrypted {
|
if !encrypted {
|
||||||
zerolog.Ctx(ctx).Warn().
|
log.Warn().
|
||||||
Str("device_id", reg.DeviceID).
|
Str("device_id", reg.DeviceID).
|
||||||
Msg("FCM push registration doesn't have encryption key")
|
Msg("FCM push registration doesn't have encryption key")
|
||||||
continue
|
continue
|
||||||
|
@ -188,7 +194,7 @@ func (gmx *Gomuks) SendPushNotification(ctx context.Context, pushRegs []*databas
|
||||||
var token string
|
var token string
|
||||||
err = json.Unmarshal(reg.Data, &token)
|
err = json.Unmarshal(reg.Data, &token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Str("device_id", reg.DeviceID).Msg("Failed to unmarshal FCM token")
|
log.Err(err).Str("device_id", reg.DeviceID).Msg("Failed to unmarshal FCM token")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
gmx.SendFCMPush(ctx, token, devicePayload, notif.HasImportant)
|
gmx.SendFCMPush(ctx, token, devicePayload, notif.HasImportant)
|
||||||
|
|
|
@ -80,7 +80,7 @@ const (
|
||||||
AND (type IN ('m.room.message', 'm.sticker')
|
AND (type IN ('m.room.message', 'm.sticker')
|
||||||
OR (type = 'm.room.encrypted'
|
OR (type = 'm.room.encrypted'
|
||||||
AND decrypted_type IN ('m.room.message', 'm.sticker')))
|
AND decrypted_type IN ('m.room.message', 'm.sticker')))
|
||||||
AND relation_type <> 'm.replace'
|
AND (relation_type IS NULL OR relation_type <> 'm.replace')
|
||||||
AND redacted_by IS NULL
|
AND redacted_by IS NULL
|
||||||
ORDER BY timestamp DESC
|
ORDER BY timestamp DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
|
@ -132,6 +132,9 @@ func (rq *RoomQuery) UpdatePreviewIfLaterOnTimeline(ctx context.Context, roomID
|
||||||
|
|
||||||
func (rq *RoomQuery) RecalculatePreview(ctx context.Context, roomID id.RoomID) (rowID EventRowID, err error) {
|
func (rq *RoomQuery) RecalculatePreview(ctx context.Context, roomID id.RoomID) (rowID EventRowID, err error) {
|
||||||
err = rq.GetDB().QueryRow(ctx, recalculateRoomPreviewEventQuery, roomID).Scan(&rowID)
|
err = rq.GetDB().QueryRow(ctx, recalculateRoomPreviewEventQuery, roomID).Scan(&rowID)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +218,7 @@ func (r *Room) CheckChangesAndCopyInto(other *Room) (hasChanges bool) {
|
||||||
hasChanges = true
|
hasChanges = true
|
||||||
other.HasMemberList = true
|
other.HasMemberList = true
|
||||||
}
|
}
|
||||||
if r.PreviewEventRowID > other.PreviewEventRowID {
|
if r.PreviewEventRowID != 0 {
|
||||||
other.PreviewEventRowID = r.PreviewEventRowID
|
other.PreviewEventRowID = r.PreviewEventRowID
|
||||||
hasChanges = true
|
hasChanges = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -414,24 +414,42 @@ var HTMLSanitizerImgSrcTemplate = "mxc://%s/%s"
|
||||||
|
|
||||||
func writeImg(w *strings.Builder, attr []html.Attribute) id.ContentURI {
|
func writeImg(w *strings.Builder, attr []html.Attribute) id.ContentURI {
|
||||||
src, alt, title, isCustomEmoji, width, height := parseImgAttributes(attr)
|
src, alt, title, isCustomEmoji, width, height := parseImgAttributes(attr)
|
||||||
|
mxc := id.ContentURIString(src).ParseOrIgnore()
|
||||||
|
if !mxc.IsValid() {
|
||||||
|
w.WriteString("<span")
|
||||||
|
writeAttribute(w, "class", "hicli-inline-img-fallback hicli-invalid-inline-img")
|
||||||
|
w.WriteString(">")
|
||||||
|
writeEscapedString(w, alt)
|
||||||
|
w.WriteString("</span>")
|
||||||
|
return id.ContentURI{}
|
||||||
|
}
|
||||||
|
url := fmt.Sprintf(HTMLSanitizerImgSrcTemplate, mxc.Homeserver, mxc.FileID)
|
||||||
|
|
||||||
|
w.WriteString("<a")
|
||||||
|
writeAttribute(w, "class", "hicli-inline-img-fallback hicli-mxc-url")
|
||||||
|
writeAttribute(w, "title", title)
|
||||||
|
writeAttribute(w, "style", "display: none;")
|
||||||
|
writeAttribute(w, "target", "_blank")
|
||||||
|
writeAttribute(w, "data-mxc", mxc.String())
|
||||||
|
writeAttribute(w, "href", url)
|
||||||
|
w.WriteString(">")
|
||||||
|
writeEscapedString(w, alt)
|
||||||
|
w.WriteString("</a>")
|
||||||
|
|
||||||
w.WriteString("<img")
|
w.WriteString("<img")
|
||||||
writeAttribute(w, "alt", alt)
|
writeAttribute(w, "alt", alt)
|
||||||
if title != "" {
|
if title != "" {
|
||||||
writeAttribute(w, "title", title)
|
writeAttribute(w, "title", title)
|
||||||
}
|
}
|
||||||
mxc := id.ContentURIString(src).ParseOrIgnore()
|
writeAttribute(w, "src", url)
|
||||||
if !mxc.IsValid() {
|
|
||||||
return id.ContentURI{}
|
|
||||||
}
|
|
||||||
writeAttribute(w, "src", fmt.Sprintf(HTMLSanitizerImgSrcTemplate, mxc.Homeserver, mxc.FileID))
|
|
||||||
writeAttribute(w, "loading", "lazy")
|
writeAttribute(w, "loading", "lazy")
|
||||||
if isCustomEmoji {
|
if isCustomEmoji {
|
||||||
writeAttribute(w, "class", "hicli-custom-emoji")
|
writeAttribute(w, "class", "hicli-inline-img hicli-custom-emoji")
|
||||||
} else if cWidth, cHeight, sizeOK := calculateMediaSize(width, height); sizeOK {
|
} else if cWidth, cHeight, sizeOK := calculateMediaSize(width, height); sizeOK {
|
||||||
writeAttribute(w, "class", "hicli-sized-inline-img")
|
writeAttribute(w, "class", "hicli-inline-img hicli-sized-inline-img")
|
||||||
writeAttribute(w, "style", fmt.Sprintf("width: %.2fpx; height: %.2fpx;", cWidth, cHeight))
|
writeAttribute(w, "style", fmt.Sprintf("width: %.2fpx; height: %.2fpx;", cWidth, cHeight))
|
||||||
} else {
|
} else {
|
||||||
writeAttribute(w, "class", "hicli-sizeless-inline-img")
|
writeAttribute(w, "class", "hicli-inline-img hicli-sizeless-inline-img")
|
||||||
}
|
}
|
||||||
return mxc
|
return mxc
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,16 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any
|
||||||
})
|
})
|
||||||
case "set_state":
|
case "set_state":
|
||||||
return unmarshalAndCall(req.Data, func(params *sendStateEventParams) (id.EventID, error) {
|
return unmarshalAndCall(req.Data, func(params *sendStateEventParams) (id.EventID, error) {
|
||||||
return h.SetState(ctx, params.RoomID, params.EventType, params.StateKey, params.Content)
|
return h.SetState(ctx, params.RoomID, params.EventType, params.StateKey, params.Content, mautrix.ReqSendEvent{
|
||||||
|
UnstableDelay: time.Duration(params.DelayMS) * time.Millisecond,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
case "update_delayed_event":
|
||||||
|
return unmarshalAndCall(req.Data, func(params *updateDelayedEventParams) (*mautrix.RespUpdateDelayedEvent, error) {
|
||||||
|
return h.Client.UpdateDelayedEvent(ctx, &mautrix.ReqUpdateDelayedEvent{
|
||||||
|
DelayID: params.DelayID,
|
||||||
|
Action: params.Action,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
case "set_membership":
|
case "set_membership":
|
||||||
return unmarshalAndCall(req.Data, func(params *setMembershipParams) (any, error) {
|
return unmarshalAndCall(req.Data, func(params *setMembershipParams) (any, error) {
|
||||||
|
@ -308,6 +317,12 @@ type sendStateEventParams struct {
|
||||||
EventType event.Type `json:"type"`
|
EventType event.Type `json:"type"`
|
||||||
StateKey string `json:"state_key"`
|
StateKey string `json:"state_key"`
|
||||||
Content json.RawMessage `json:"content"`
|
Content json.RawMessage `json:"content"`
|
||||||
|
DelayMS int `json:"delay_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type updateDelayedEventParams struct {
|
||||||
|
DelayID string `json:"delay_id"`
|
||||||
|
Action string `json:"action"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type setMembershipParams struct {
|
type setMembershipParams struct {
|
||||||
|
|
|
@ -296,12 +296,12 @@ func (h *HiClient) PaginateServer(ctx context.Context, roomID id.RoomID, limit i
|
||||||
if resp.End == "" {
|
if resp.End == "" {
|
||||||
resp.End = database.PrevBatchPaginationComplete
|
resp.End = database.PrevBatchPaginationComplete
|
||||||
}
|
}
|
||||||
if resp.End == database.PrevBatchPaginationComplete || len(resp.Chunk) == 0 {
|
if len(resp.Chunk) == 0 {
|
||||||
err = h.DB.Room.SetPrevBatch(ctx, room.ID, resp.End)
|
err = h.DB.Room.SetPrevBatch(ctx, room.ID, resp.End)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to set prev_batch: %w", err)
|
return nil, fmt.Errorf("failed to set prev_batch: %w", err)
|
||||||
}
|
}
|
||||||
return &PaginationResponse{Events: events, HasMore: resp.End != ""}, nil
|
return &PaginationResponse{Events: events, HasMore: resp.End != database.PrevBatchPaginationComplete}, nil
|
||||||
}
|
}
|
||||||
wakeupSessionRequests := false
|
wakeupSessionRequests := false
|
||||||
err = h.DB.DoTxn(ctx, nil, func(ctx context.Context) error {
|
err = h.DB.DoTxn(ctx, nil, func(ctx context.Context) error {
|
||||||
|
@ -366,5 +366,5 @@ func (h *HiClient) PaginateServer(ctx context.Context, roomID id.RoomID, limit i
|
||||||
if err == nil && wakeupSessionRequests {
|
if err == nil && wakeupSessionRequests {
|
||||||
h.WakeupRequestQueue()
|
h.WakeupRequestQueue()
|
||||||
}
|
}
|
||||||
return &PaginationResponse{Events: events, HasMore: true}, err
|
return &PaginationResponse{Events: events, HasMore: resp.End != database.PrevBatchPaginationComplete}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,6 +226,7 @@ func (h *HiClient) SetState(
|
||||||
evtType event.Type,
|
evtType event.Type,
|
||||||
stateKey string,
|
stateKey string,
|
||||||
content any,
|
content any,
|
||||||
|
extra ...mautrix.ReqSendEvent,
|
||||||
) (id.EventID, error) {
|
) (id.EventID, error) {
|
||||||
room, err := h.DB.Room.Get(ctx, roomID)
|
room, err := h.DB.Room.Get(ctx, roomID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -233,10 +234,14 @@ func (h *HiClient) SetState(
|
||||||
} else if room == nil {
|
} else if room == nil {
|
||||||
return "", fmt.Errorf("unknown room")
|
return "", fmt.Errorf("unknown room")
|
||||||
}
|
}
|
||||||
resp, err := h.Client.SendStateEvent(ctx, room.ID, evtType, stateKey, content)
|
resp, err := h.Client.SendStateEvent(ctx, room.ID, evtType, stateKey, content, extra...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
if resp.UnstableDelayID != "" {
|
||||||
|
// Mildly hacky, but it's fine'
|
||||||
|
return id.EventID(resp.UnstableDelayID), nil
|
||||||
|
}
|
||||||
return resp.EventID, nil
|
return resp.EventID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,6 +516,9 @@ func (h *HiClient) shouldShareKeysToInvitedUsers(ctx context.Context, roomID id.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to get history visibility event")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to get history visibility event")
|
||||||
return false
|
return false
|
||||||
|
} else if historyVisibility == nil {
|
||||||
|
zerolog.Ctx(ctx).Warn().Msg("History visibility event not found")
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
mautrixEvt := historyVisibility.AsRawMautrix()
|
mautrixEvt := historyVisibility.AsRawMautrix()
|
||||||
err = mautrixEvt.Content.ParseRaw(mautrixEvt.Type)
|
err = mautrixEvt.Content.ParseRaw(mautrixEvt.Type)
|
||||||
|
|
|
@ -137,7 +137,7 @@ func (h *HiClient) maybeDiscardOutboundSession(ctx context.Context, newMembershi
|
||||||
prevMembership = event.Membership(gjson.GetBytes(cs.Content, "membership").Str)
|
prevMembership = event.Membership(gjson.GetBytes(cs.Content, "membership").Str)
|
||||||
}
|
}
|
||||||
if prevMembership == newMembership ||
|
if prevMembership == newMembership ||
|
||||||
(prevMembership == event.MembershipInvite && newMembership == event.MembershipJoin) ||
|
(prevMembership == event.MembershipInvite && newMembership == event.MembershipJoin && h.shouldShareKeysToInvitedUsers(ctx, evt.RoomID)) ||
|
||||||
(prevMembership == event.MembershipJoin && newMembership == event.MembershipInvite) ||
|
(prevMembership == event.MembershipJoin && newMembership == event.MembershipInvite) ||
|
||||||
(prevMembership == event.MembershipBan && newMembership == event.MembershipLeave) ||
|
(prevMembership == event.MembershipBan && newMembership == event.MembershipLeave) ||
|
||||||
(prevMembership == event.MembershipLeave && newMembership == event.MembershipBan) {
|
(prevMembership == event.MembershipLeave && newMembership == event.MembershipBan) {
|
||||||
|
@ -598,7 +598,7 @@ func (h *HiClient) calculateLocalContent(ctx context.Context, dbEvt *database.Ev
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const CurrentHTMLSanitizerVersion = 8
|
const CurrentHTMLSanitizerVersion = 10
|
||||||
|
|
||||||
func (h *HiClient) ReprocessExistingEvent(ctx context.Context, evt *database.Event) {
|
func (h *HiClient) ReprocessExistingEvent(ctx context.Context, evt *database.Event) {
|
||||||
if (evt.Type != event.EventMessage.Type && evt.DecryptedType != event.EventMessage.Type) ||
|
if (evt.Type != event.EventMessage.Type && evt.DecryptedType != event.EventMessage.Type) ||
|
||||||
|
@ -785,7 +785,7 @@ func (h *HiClient) processStateAndTimeline(
|
||||||
return fmt.Errorf("failed to get relation target of redaction target: %w", err)
|
return fmt.Errorf("failed to get relation target of redaction target: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if updatedRoom.PreviewEventRowID == dbEvt.RowID {
|
if updatedRoom.PreviewEventRowID == dbEvt.RowID || (updatedRoom.PreviewEventRowID == 0 && room.PreviewEventRowID == dbEvt.RowID) {
|
||||||
updatedRoom.PreviewEventRowID = 0
|
updatedRoom.PreviewEventRowID = 0
|
||||||
recalculatePreviewEvent = true
|
recalculatePreviewEvent = true
|
||||||
}
|
}
|
||||||
|
@ -969,12 +969,13 @@ func (h *HiClient) processStateAndTimeline(
|
||||||
updatedRoom.PreviewEventRowID, err = h.DB.Room.RecalculatePreview(ctx, room.ID)
|
updatedRoom.PreviewEventRowID, err = h.DB.Room.RecalculatePreview(ctx, room.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to recalculate preview event: %w", err)
|
return fmt.Errorf("failed to recalculate preview event: %w", err)
|
||||||
}
|
} else if updatedRoom.PreviewEventRowID != 0 {
|
||||||
_, err = addOldEvent(updatedRoom.PreviewEventRowID, "")
|
_, err = addOldEvent(updatedRoom.PreviewEventRowID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get preview event: %w", err)
|
return fmt.Errorf("failed to get preview event: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Calculate name from participants if participants changed and current name was generated from participants, or if the room name was unset
|
// Calculate name from participants if participants changed and current name was generated from participants, or if the room name was unset
|
||||||
if (heroesChanged && updatedRoom.NameQuality <= database.NameQualityParticipants) || updatedRoom.NameQuality == database.NameQualityNil {
|
if (heroesChanged && updatedRoom.NameQuality <= database.NameQualityParticipants) || updatedRoom.NameQuality == database.NameQualityNil {
|
||||||
name, dmAvatarURL, dmUserID, err := h.calculateRoomParticipantName(ctx, room.ID, summary)
|
name, dmAvatarURL, dmUserID, err := h.calculateRoomParticipantName(ctx, room.ID, summary)
|
||||||
|
|
771
web/package-lock.json
generated
771
web/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -78,11 +78,16 @@ function getFallbackCharacter(from: unknown, idx: number): string {
|
||||||
return Array.from(from.slice(0, (idx + 1) * 2))[idx]?.toUpperCase().toWellFormed() ?? ""
|
return Array.from(from.slice(0, (idx + 1) * 2))[idx]?.toUpperCase().toWellFormed() ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAvatarURL = (userID: UserID, content?: UserProfile | null, thumbnail = false): string | undefined => {
|
export const getAvatarURL = (
|
||||||
|
userID: UserID,
|
||||||
|
content?: UserProfile | null,
|
||||||
|
thumbnail = false,
|
||||||
|
forceFallback = false,
|
||||||
|
): string | undefined => {
|
||||||
const fallbackCharacter = getFallbackCharacter(content?.displayname, 0) || getFallbackCharacter(userID, 1)
|
const fallbackCharacter = getFallbackCharacter(content?.displayname, 0) || getFallbackCharacter(userID, 1)
|
||||||
const backgroundColor = getUserColor(userID)
|
const backgroundColor = getUserColor(userID)
|
||||||
const [server, mediaID] = parseMXC(content?.avatar_file?.url ?? content?.avatar_url)
|
const [server, mediaID] = parseMXC(content?.avatar_file?.url ?? content?.avatar_url)
|
||||||
if (!mediaID) {
|
if (!mediaID || forceFallback) {
|
||||||
return makeFallbackAvatar(backgroundColor, fallbackCharacter)
|
return makeFallbackAvatar(backgroundColor, fallbackCharacter)
|
||||||
}
|
}
|
||||||
const encrypted = !!content?.avatar_file
|
const encrypted = !!content?.avatar_file
|
||||||
|
@ -91,8 +96,12 @@ export const getAvatarURL = (userID: UserID, content?: UserProfile | null, thumb
|
||||||
return thumbnail ? `${url}&thumbnail=avatar` : url
|
return thumbnail ? `${url}&thumbnail=avatar` : url
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAvatarThumbnailURL = (userID: UserID, content?: UserProfile | null): string | undefined => {
|
export const getAvatarThumbnailURL = (
|
||||||
return getAvatarURL(userID, content, true)
|
userID: UserID,
|
||||||
|
content?: UserProfile | null,
|
||||||
|
forceFallback = false,
|
||||||
|
): string | undefined => {
|
||||||
|
return getAvatarURL(userID, content, true, forceFallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RoomForAvatarURL {
|
interface RoomForAvatarURL {
|
||||||
|
@ -104,14 +113,21 @@ interface RoomForAvatarURL {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getRoomAvatarURL = (
|
export const getRoomAvatarURL = (
|
||||||
room: RoomForAvatarURL, avatarOverride?: ContentURI, thumbnail = false,
|
room: RoomForAvatarURL,
|
||||||
|
avatarOverride?: ContentURI,
|
||||||
|
thumbnail = false,
|
||||||
|
forceFallback = false,
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
return getAvatarURL(room.dm_user_id ?? room.room_id, {
|
return getAvatarURL(room.dm_user_id ?? room.room_id, {
|
||||||
displayname: room.name,
|
displayname: room.name,
|
||||||
avatar_url: avatarOverride ?? room.avatar ?? room.avatar_url,
|
avatar_url: avatarOverride ?? room.avatar ?? room.avatar_url,
|
||||||
}, thumbnail)
|
}, thumbnail, forceFallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getRoomAvatarThumbnailURL = (room: RoomForAvatarURL, avatarOverride?: ContentURI): string | undefined => {
|
export const getRoomAvatarThumbnailURL = (
|
||||||
return getRoomAvatarURL(room, avatarOverride, true)
|
room: RoomForAvatarURL,
|
||||||
|
avatarOverride?: ContentURI,
|
||||||
|
forceFallback = false,
|
||||||
|
): string | undefined => {
|
||||||
|
return getRoomAvatarURL(room, avatarOverride, true, forceFallback)
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,8 +174,13 @@ export default abstract class RPCClient {
|
||||||
|
|
||||||
setState(
|
setState(
|
||||||
room_id: RoomID, type: EventType, state_key: string, content: Record<string, unknown>,
|
room_id: RoomID, type: EventType, state_key: string, content: Record<string, unknown>,
|
||||||
|
extra: { delay_ms?: number } = {},
|
||||||
): Promise<EventID> {
|
): Promise<EventID> {
|
||||||
return this.request("set_state", { room_id, type, state_key, content })
|
return this.request("set_state", { room_id, type, state_key, content, ...extra })
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDelayedEvent(delay_id: string, action: string): Promise<void> {
|
||||||
|
return this.request("update_delayed_event", { delay_id, action })
|
||||||
}
|
}
|
||||||
|
|
||||||
setMembership(room_id: RoomID, user_id: UserID, action: MembershipAction, reason?: string): Promise<void> {
|
setMembership(room_id: RoomID, user_id: UserID, action: MembershipAction, reason?: string): Promise<void> {
|
||||||
|
|
|
@ -45,6 +45,7 @@ export class InvitedRoomStore implements RoomListEntry, RoomSummary {
|
||||||
readonly invited_by?: UserID
|
readonly invited_by?: UserID
|
||||||
readonly inviter_profile?: MemberEventContent
|
readonly inviter_profile?: MemberEventContent
|
||||||
readonly is_direct: boolean
|
readonly is_direct: boolean
|
||||||
|
readonly is_invite = true
|
||||||
|
|
||||||
constructor(public readonly meta: DBInvitedRoom, parent: StateStore) {
|
constructor(public readonly meta: DBInvitedRoom, parent: StateStore) {
|
||||||
this.room_id = meta.room_id
|
this.room_id = meta.room_id
|
||||||
|
|
|
@ -55,6 +55,7 @@ export interface RoomListEntry {
|
||||||
unread_notifications: number
|
unread_notifications: number
|
||||||
unread_highlights: number
|
unread_highlights: number
|
||||||
marked_unread: boolean
|
marked_unread: boolean
|
||||||
|
is_invite?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GCSettings {
|
export interface GCSettings {
|
||||||
|
@ -255,8 +256,10 @@ export class StateStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
applySync(sync: SyncCompleteData) {
|
applySync(sync: SyncCompleteData) {
|
||||||
|
let prevActiveRoom: RoomID | null = null
|
||||||
if (sync.clear_state && this.rooms.size > 0) {
|
if (sync.clear_state && this.rooms.size > 0) {
|
||||||
console.info("Clearing state store as sync told to reset and there are rooms in the store")
|
console.info("Clearing state store as sync told to reset and there are rooms in the store")
|
||||||
|
prevActiveRoom = this.activeRoomID
|
||||||
this.clear()
|
this.clear()
|
||||||
}
|
}
|
||||||
const resyncRoomList = this.roomList.current.length === 0
|
const resyncRoomList = this.roomList.current.length === 0
|
||||||
|
@ -387,6 +390,10 @@ export class StateStore {
|
||||||
this.topLevelSpaces.emit(sync.top_level_spaces)
|
this.topLevelSpaces.emit(sync.top_level_spaces)
|
||||||
this.spaceOrphans.children = sync.top_level_spaces.map(child_id => ({ child_id }))
|
this.spaceOrphans.children = sync.top_level_spaces.map(child_id => ({ child_id }))
|
||||||
}
|
}
|
||||||
|
if (prevActiveRoom) {
|
||||||
|
// TODO this will fail if the room is not in the top 100 recent rooms
|
||||||
|
this.switchRoom?.(prevActiveRoom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateEmojiPackKeyCache() {
|
invalidateEmojiPackKeyCache() {
|
||||||
|
|
|
@ -116,6 +116,9 @@ export interface PolicyRuleContent {
|
||||||
entity: string
|
entity: string
|
||||||
reason: string
|
reason: string
|
||||||
recommendation: string
|
recommendation: string
|
||||||
|
"org.matrix.msc4205.hashes"?: {
|
||||||
|
sha256: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PowerLevelEventContent {
|
export interface PowerLevelEventContent {
|
||||||
|
|
|
@ -55,10 +55,22 @@ export const preferences = {
|
||||||
}),
|
}),
|
||||||
show_media_previews: new Preference<boolean>({
|
show_media_previews: new Preference<boolean>({
|
||||||
displayName: "Show image and video previews",
|
displayName: "Show image and video previews",
|
||||||
description: "If disabled, images and videos will only be visible after clicking and will not be downloaded automatically.",
|
description: "If disabled, images and videos will only be visible after clicking and will not be downloaded automatically. This will also disable images in URL previews.",
|
||||||
allowedContexts: anyContext,
|
allowedContexts: anyContext,
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
}),
|
}),
|
||||||
|
show_inline_images: new Preference<boolean>({
|
||||||
|
displayName: "Show inline images",
|
||||||
|
description: "If disabled, custom emojis and other inline images will not be rendered and the alt attribute will be shown instead.",
|
||||||
|
allowedContexts: anyContext,
|
||||||
|
defaultValue: true,
|
||||||
|
}),
|
||||||
|
show_invite_avatars: new Preference<boolean>({
|
||||||
|
displayName: "Show avatars in invites",
|
||||||
|
description: "If disabled, the avatar of the room or inviter will not be shown in the invite view.",
|
||||||
|
allowedContexts: anyGlobalContext,
|
||||||
|
defaultValue: true,
|
||||||
|
}),
|
||||||
code_block_line_wrap: new Preference<boolean>({
|
code_block_line_wrap: new Preference<boolean>({
|
||||||
displayName: "Code block line wrap",
|
displayName: "Code block line wrap",
|
||||||
description: "Whether to wrap long lines in code blocks instead of scrolling horizontally.",
|
description: "Whether to wrap long lines in code blocks instead of scrolling horizontally.",
|
||||||
|
|
|
@ -167,7 +167,6 @@ body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-size: 16px;
|
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
@ -176,6 +175,7 @@ body {
|
||||||
html {
|
html {
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
|
|
|
@ -117,6 +117,15 @@ const StylePreferences = ({ client, activeRoom }: StylePreferencesProps) => {
|
||||||
--timeline-status-size: 2rem;
|
--timeline-status-size: 2rem;
|
||||||
}
|
}
|
||||||
`, [preferences.display_read_receipts])
|
`, [preferences.display_read_receipts])
|
||||||
|
useStyle(() => !preferences.show_inline_images && css`
|
||||||
|
a.hicli-inline-img-fallback {
|
||||||
|
display: inline !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.hicli-inline-img {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`, [preferences.show_inline_images])
|
||||||
useAsyncStyle(() => preferences.code_block_theme === "auto" ? `
|
useAsyncStyle(() => preferences.code_block_theme === "auto" ? `
|
||||||
@import url("_gomuks/codeblock/github.css") (prefers-color-scheme: light);
|
@import url("_gomuks/codeblock/github.css") (prefers-color-scheme: light);
|
||||||
@import url("_gomuks/codeblock/github-dark.css") (prefers-color-scheme: dark);
|
@import url("_gomuks/codeblock/github-dark.css") (prefers-color-scheme: dark);
|
||||||
|
|
|
@ -181,10 +181,5 @@ export const useSecondaryItems = (
|
||||||
title={pendingTitle}
|
title={pendingTitle}
|
||||||
className="redact-button"
|
className="redact-button"
|
||||||
><DeleteIcon/>{names && "Remove"}</button>}
|
><DeleteIcon/>{names && "Remove"}</button>}
|
||||||
{canUnredact && (evt.viewing_redacted ? <button onClick={onClickHideUnredacted}>
|
|
||||||
<DeleteIcon/>{names && "Hide content"}
|
|
||||||
</button> : <button onClick={onClickUnredact}>
|
|
||||||
<RestoreTrashIcon/>{names && "View content"}
|
|
||||||
</button>)}
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ div.overlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
> div.modal-box-inner {
|
> div.modal-box-inner {
|
||||||
overflow: scroll;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ const ModalWrapper = ({ children, ContextType, historyStateKey }: ModalWrapperPr
|
||||||
return newState
|
return newState
|
||||||
}, null)
|
}, null)
|
||||||
const onClickWrapper = useCallback((evt?: React.MouseEvent) => {
|
const onClickWrapper = useCallback((evt?: React.MouseEvent) => {
|
||||||
if (evt && evt.target !== evt.currentTarget) {
|
if (evt && (evt.target !== evt.currentTarget || state?.noDismiss)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
evt?.stopPropagation()
|
evt?.stopPropagation()
|
||||||
|
@ -37,9 +37,9 @@ const ModalWrapper = ({ children, ContextType, historyStateKey }: ModalWrapperPr
|
||||||
if (history.state?.[historyStateKey]) {
|
if (history.state?.[historyStateKey]) {
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
}, [historyStateKey])
|
}, [historyStateKey, state])
|
||||||
const onKeyWrapper = (evt: React.KeyboardEvent<HTMLDivElement>) => {
|
const onKeyWrapper = (evt: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (evt.key === "Escape") {
|
if (evt.key === "Escape" && !state?.noDismiss) {
|
||||||
setState(null)
|
setState(null)
|
||||||
if (history.state?.[historyStateKey]) {
|
if (history.state?.[historyStateKey]) {
|
||||||
history.back()
|
history.back()
|
||||||
|
@ -55,12 +55,17 @@ const ModalWrapper = ({ children, ContextType, historyStateKey }: ModalWrapperPr
|
||||||
}, [historyStateKey])
|
}, [historyStateKey])
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null)
|
const wrapperRef = useRef<HTMLDivElement>(null)
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
window.closeModal = onClickWrapper
|
||||||
|
if (historyStateKey === "nestable_modal") {
|
||||||
|
window.openNestableModal = openModal
|
||||||
|
} else {
|
||||||
|
window.openModal = openModal
|
||||||
|
}
|
||||||
if (wrapperRef.current && (!document.activeElement || !wrapperRef.current.contains(document.activeElement))) {
|
if (wrapperRef.current && (!document.activeElement || !wrapperRef.current.contains(document.activeElement))) {
|
||||||
wrapperRef.current.focus()
|
wrapperRef.current.focus()
|
||||||
}
|
}
|
||||||
}, [state])
|
}, [state, onClickWrapper, historyStateKey, openModal])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.closeModal = onClickWrapper
|
|
||||||
const listener = (evt: PopStateEvent) => {
|
const listener = (evt: PopStateEvent) => {
|
||||||
if (!evt.state?.[historyStateKey]) {
|
if (!evt.state?.[historyStateKey]) {
|
||||||
setState(null)
|
setState(null)
|
||||||
|
@ -68,7 +73,7 @@ const ModalWrapper = ({ children, ContextType, historyStateKey }: ModalWrapperPr
|
||||||
}
|
}
|
||||||
window.addEventListener("popstate", listener)
|
window.addEventListener("popstate", listener)
|
||||||
return () => window.removeEventListener("popstate", listener)
|
return () => window.removeEventListener("popstate", listener)
|
||||||
}, [historyStateKey, onClickWrapper])
|
}, [historyStateKey])
|
||||||
let modal: JSX.Element | null = null
|
let modal: JSX.Element | null = null
|
||||||
if (state) {
|
if (state) {
|
||||||
let content = <ModalCloseContext value={onClickWrapper}>
|
let content = <ModalCloseContext value={onClickWrapper}>
|
||||||
|
@ -97,9 +102,6 @@ const ModalWrapper = ({ children, ContextType, historyStateKey }: ModalWrapperPr
|
||||||
modal = content
|
modal = content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (historyStateKey === "nestable_modal") {
|
|
||||||
window.openNestableModal = openModal
|
|
||||||
}
|
|
||||||
return <ContextType value={openModal}>
|
return <ContextType value={openModal}>
|
||||||
{children}
|
{children}
|
||||||
{modal}
|
{modal}
|
||||||
|
|
|
@ -33,6 +33,7 @@ export interface ModalState {
|
||||||
innerBoxClass?: string
|
innerBoxClass?: string
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
captureInput?: boolean
|
captureInput?: boolean
|
||||||
|
noDismiss?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type openModal = (state: ModalState) => void
|
export type openModal = (state: ModalState) => void
|
||||||
|
|
|
@ -68,8 +68,6 @@ div.right-panel-content.widgets {
|
||||||
}
|
}
|
||||||
|
|
||||||
div.right-panel-content.user {
|
div.right-panel-content.user {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|
||||||
div.avatar-container {
|
div.avatar-container {
|
||||||
|
@ -79,7 +77,6 @@ div.right-panel-content.user {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
flex-shrink: 0;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
> img {
|
> img {
|
||||||
|
@ -105,6 +102,7 @@ div.right-panel-content.user {
|
||||||
-webkit-line-clamp: 4;
|
-webkit-line-clamp: 4;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.userid, div.extended-profile, div.devices, div.user-moderation, div.mutual-rooms, div.errors {
|
div.userid, div.extended-profile, div.devices, div.user-moderation, div.mutual-rooms, div.errors {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import type { IWidget } from "matrix-widget-api"
|
import type { IWidget } from "matrix-widget-api"
|
||||||
import { JSX, use } from "react"
|
import { JSX, use } from "react"
|
||||||
import type { UserID } from "@/api/types"
|
import type { UserID } from "@/api/types"
|
||||||
import MainScreenContext from "../MainScreenContext.ts"
|
import MainScreenContext, { MainScreenContextFields } from "../MainScreenContext.ts"
|
||||||
import ErrorBoundary from "../util/ErrorBoundary.tsx"
|
import ErrorBoundary from "../util/ErrorBoundary.tsx"
|
||||||
import ElementCall from "../widget/ElementCall.tsx"
|
import ElementCall from "../widget/ElementCall.tsx"
|
||||||
import LazyWidget from "../widget/LazyWidget.tsx"
|
import LazyWidget from "../widget/LazyWidget.tsx"
|
||||||
|
@ -63,7 +63,7 @@ function getTitle(props: RightPanelProps): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderRightPanelContent(props: RightPanelProps): JSX.Element | null {
|
function renderRightPanelContent(props: RightPanelProps, mainScreen: MainScreenContextFields): JSX.Element | null {
|
||||||
switch (props.type) {
|
switch (props.type) {
|
||||||
case "pinned-messages":
|
case "pinned-messages":
|
||||||
return <PinnedMessages />
|
return <PinnedMessages />
|
||||||
|
@ -72,9 +72,9 @@ function renderRightPanelContent(props: RightPanelProps): JSX.Element | null {
|
||||||
case "widgets":
|
case "widgets":
|
||||||
return <WidgetList />
|
return <WidgetList />
|
||||||
case "element-call":
|
case "element-call":
|
||||||
return <ElementCall />
|
return <ElementCall onClose={mainScreen.closeRightPanel} />
|
||||||
case "widget":
|
case "widget":
|
||||||
return <LazyWidget info={props.info} />
|
return <LazyWidget info={props.info} onClose={mainScreen.closeRightPanel} />
|
||||||
case "user":
|
case "user":
|
||||||
return <UserInfo userID={props.userID} />
|
return <UserInfo userID={props.userID} />
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ const RightPanel = (props: RightPanelProps) => {
|
||||||
</div>
|
</div>
|
||||||
<div className={`right-panel-content ${props.type}`}>
|
<div className={`right-panel-content ${props.type}`}>
|
||||||
<ErrorBoundary thing="right panel content">
|
<ErrorBoundary thing="right panel content">
|
||||||
{renderRightPanelContent(props)}
|
{renderRightPanelContent(props, mainScreen)}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,6 +29,7 @@ export interface RoomListEntryProps {
|
||||||
room: RoomListEntry
|
room: RoomListEntry
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
hidden: boolean
|
hidden: boolean
|
||||||
|
hideAvatar?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPreviewText(evt?: MemDBEvent, senderMemberEvt?: MemDBEvent | null): [string, JSX.Element | null] {
|
function getPreviewText(evt?: MemDBEvent, senderMemberEvt?: MemDBEvent | null): [string, JSX.Element | null] {
|
||||||
|
@ -57,7 +58,7 @@ function getPreviewText(evt?: MemDBEvent, senderMemberEvt?: MemDBEvent | null):
|
||||||
return ["", null]
|
return ["", null]
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderEntry(room: RoomListEntry) {
|
function renderEntry(room: RoomListEntry, hideAvatar: boolean | undefined) {
|
||||||
const [previewText, croppedPreviewText] = getPreviewText(room.preview_event, room.preview_sender)
|
const [previewText, croppedPreviewText] = getPreviewText(room.preview_event, room.preview_sender)
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
@ -65,7 +66,7 @@ function renderEntry(room: RoomListEntry) {
|
||||||
<img
|
<img
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className="avatar room-avatar"
|
className="avatar room-avatar"
|
||||||
src={getRoomAvatarThumbnailURL(room)}
|
src={getRoomAvatarThumbnailURL(room, undefined, hideAvatar)}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -77,7 +78,7 @@ function renderEntry(room: RoomListEntry) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
const Entry = ({ room, isActive, hidden }: RoomListEntryProps) => {
|
const Entry = ({ room, isActive, hidden, hideAvatar }: RoomListEntryProps) => {
|
||||||
const [isVisible, divRef] = useContentVisibility<HTMLDivElement>()
|
const [isVisible, divRef] = useContentVisibility<HTMLDivElement>()
|
||||||
const openModal = use(ModalContext)
|
const openModal = use(ModalContext)
|
||||||
const mainScreen = use(MainScreenContext)
|
const mainScreen = use(MainScreenContext)
|
||||||
|
@ -105,7 +106,7 @@ const Entry = ({ room, isActive, hidden }: RoomListEntryProps) => {
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
data-room-id={room.room_id}
|
data-room-id={room.room_id}
|
||||||
>
|
>
|
||||||
{isVisible ? renderEntry(room) : null}
|
{isVisible ? renderEntry(room, hideAvatar) : null}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import React, { use, useCallback, useRef, useState } from "react"
|
import React, { use, useCallback, useRef, useState } from "react"
|
||||||
import { RoomListFilter, Space as SpaceStore, SpaceUnreadCounts } from "@/api/statestore"
|
import { RoomListFilter, Space as SpaceStore, SpaceUnreadCounts, usePreference } from "@/api/statestore"
|
||||||
import type { RoomID } from "@/api/types"
|
import type { RoomID } from "@/api/types"
|
||||||
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
import { useEventAsState } from "@/util/eventdispatcher.ts"
|
||||||
import reverseMap from "@/util/reversemap.ts"
|
import reverseMap from "@/util/reversemap.ts"
|
||||||
|
@ -103,6 +103,7 @@ const RoomList = ({ activeRoomID, space }: RoomListProps) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showInviteAvatars = usePreference(client.store, null, "show_invite_avatars")
|
||||||
const roomListFilter = client.store.roomListFilterFunc
|
const roomListFilter = client.store.roomListFilterFunc
|
||||||
return <div className="room-list-wrapper">
|
return <div className="room-list-wrapper">
|
||||||
<div className="room-search-wrapper">
|
<div className="room-search-wrapper">
|
||||||
|
@ -145,6 +146,7 @@ const RoomList = ({ activeRoomID, space }: RoomListProps) => {
|
||||||
isActive={room.room_id === activeRoomID}
|
isActive={room.room_id === activeRoomID}
|
||||||
hidden={roomListFilter ? !roomListFilter(room) : false}
|
hidden={roomListFilter ? !roomListFilter(room) : false}
|
||||||
room={room}
|
room={room}
|
||||||
|
hideAvatar={room.is_invite && !showInviteAvatars}
|
||||||
/>,
|
/>,
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import { use, useEffect, useState } from "react"
|
import { use, useEffect, useState } from "react"
|
||||||
import { ScaleLoader } from "react-spinners"
|
import { ScaleLoader } from "react-spinners"
|
||||||
import { getAvatarThumbnailURL, getAvatarURL, getRoomAvatarURL } from "@/api/media.ts"
|
import { getAvatarThumbnailURL, getAvatarURL, getRoomAvatarURL } from "@/api/media.ts"
|
||||||
|
import { usePreference } from "@/api/statestore/hooks.ts"
|
||||||
import { InvitedRoomStore } from "@/api/statestore/invitedroom.ts"
|
import { InvitedRoomStore } from "@/api/statestore/invitedroom.ts"
|
||||||
import { RoomID, RoomSummary } from "@/api/types"
|
import { RoomID, RoomSummary } from "@/api/types"
|
||||||
import { getDisplayname, getServerName } from "@/util/validation.ts"
|
import { getDisplayname, getServerName } from "@/util/validation.ts"
|
||||||
|
@ -84,13 +85,15 @@ const RoomPreview = ({ roomID, via, alias, invite }: RoomPreviewProps) => {
|
||||||
const name = summary?.name ?? summary?.canonical_alias ?? invite?.name ?? invite?.canonical_alias ?? alias ?? roomID
|
const name = summary?.name ?? summary?.canonical_alias ?? invite?.name ?? invite?.canonical_alias ?? alias ?? roomID
|
||||||
const memberCount = summary?.num_joined_members || null
|
const memberCount = summary?.num_joined_members || null
|
||||||
const topic = summary?.topic ?? invite?.topic ?? ""
|
const topic = summary?.topic ?? invite?.topic ?? ""
|
||||||
|
const showInviteAvatars = usePreference(client.store, null, "show_invite_avatars")
|
||||||
|
const noAvatarPreview = invite && !showInviteAvatars
|
||||||
return <div className="room-view preview">
|
return <div className="room-view preview">
|
||||||
<div className="preview-inner">
|
<div className="preview-inner">
|
||||||
{invite?.invited_by && !invite.dm_user_id ? <div className="inviter-info">
|
{invite?.invited_by && !invite.dm_user_id ? <div className="inviter-info">
|
||||||
<img
|
<img
|
||||||
className="small avatar"
|
className="small avatar"
|
||||||
onClick={use(LightboxContext)}
|
onClick={use(LightboxContext)}
|
||||||
src={getAvatarThumbnailURL(invite.invited_by, invite.inviter_profile)}
|
src={getAvatarThumbnailURL(invite.invited_by, invite.inviter_profile, noAvatarPreview)}
|
||||||
data-full-src={getAvatarURL(invite.invited_by, invite.inviter_profile)}
|
data-full-src={getAvatarURL(invite.invited_by, invite.inviter_profile)}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
|
@ -102,7 +105,8 @@ const RoomPreview = ({ roomID, via, alias, invite }: RoomPreviewProps) => {
|
||||||
<h2 className="room-name">{name}</h2>
|
<h2 className="room-name">{name}</h2>
|
||||||
<img
|
<img
|
||||||
// this is a big avatar (120px), use full resolution
|
// this is a big avatar (120px), use full resolution
|
||||||
src={getRoomAvatarURL(invite ?? summary ?? { room_id: roomID })}
|
src={getRoomAvatarURL(invite ?? summary ?? { room_id: roomID }, undefined, false, noAvatarPreview)}
|
||||||
|
data-full-src={getRoomAvatarURL(invite ?? summary ?? { room_id: roomID })}
|
||||||
className="large avatar"
|
className="large avatar"
|
||||||
onClick={use(LightboxContext)}
|
onClick={use(LightboxContext)}
|
||||||
alt=""
|
alt=""
|
||||||
|
|
|
@ -23,6 +23,7 @@ blockquote.reply-body {
|
||||||
height: calc(var(--small-font-size) * 1.5);
|
height: calc(var(--small-font-size) * 1.5);
|
||||||
border-left: none;
|
border-left: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
> div.reply-spine {
|
> div.reply-spine {
|
||||||
margin-top: calc(var(--small-font-size) * 0.75 - 1px);
|
margin-top: calc(var(--small-font-size) * 0.75 - 1px);
|
||||||
|
|
|
@ -246,10 +246,15 @@ div.event-content > div.event-reactions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockquote.reply-body.small > div.reply-sender > span.event-sender {
|
||||||
|
max-width: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
div.small-event > div.sender-avatar, blockquote.reply-body > div.reply-sender > div.sender-avatar {
|
div.small-event > div.sender-avatar, blockquote.reply-body > div.reply-sender > div.sender-avatar {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.date-separator {
|
div.date-separator {
|
||||||
|
|
|
@ -28,6 +28,8 @@ const URLPreviews = ({ event, room }: {
|
||||||
}) => {
|
}) => {
|
||||||
const client = use(ClientContext)!
|
const client = use(ClientContext)!
|
||||||
const renderPreviews = usePreference(client.store, room, "render_url_previews")
|
const renderPreviews = usePreference(client.store, room, "render_url_previews")
|
||||||
|
// TODO support blurhashes and clicking to view image previews here?
|
||||||
|
const showPreviewImages = usePreference(client.store, room, "show_media_previews")
|
||||||
if (event.redacted_by || !renderPreviews) {
|
if (event.redacted_by || !renderPreviews) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -72,7 +74,7 @@ const URLPreviews = ({ event, room }: {
|
||||||
<a href={url} title={title} target="_blank" rel="noreferrer noopener">{title}</a>
|
<a href={url} title={title} target="_blank" rel="noreferrer noopener">{title}</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="description" title={p["og:description"]}>{p["og:description"]}</div>
|
<div className="description" title={p["og:description"]}>{p["og:description"]}</div>
|
||||||
{mediaURL && (inline
|
{mediaURL && showPreviewImages && (inline
|
||||||
? <div className="inline-media-wrapper">{mediaContainer}</div>
|
? <div className="inline-media-wrapper">{mediaContainer}</div>
|
||||||
: mediaContainer)}
|
: mediaContainer)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -99,6 +99,8 @@ function useChangeDescription(
|
||||||
if (sender === target) {
|
if (sender === target) {
|
||||||
if (prevContent?.membership === "knock") {
|
if (prevContent?.membership === "knock") {
|
||||||
return "cancelled their join request"
|
return "cancelled their join request"
|
||||||
|
} else if (prevContent?.membership === "invite") {
|
||||||
|
return "rejected the invite"
|
||||||
}
|
}
|
||||||
return "left the room"
|
return "left the room"
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,21 @@ const PolicyRuleBody = ({ event, sender }: EventContentProps) => {
|
||||||
const mainScreen = use(MainScreenContext)
|
const mainScreen = use(MainScreenContext)
|
||||||
|
|
||||||
const entity = content.entity ?? prevContent?.entity
|
const entity = content.entity ?? prevContent?.entity
|
||||||
|
const hashedEntity = content["org.matrix.msc4205.hashes"]?.sha256
|
||||||
|
?? prevContent?.["org.matrix.msc4205.hashes"]?.sha256
|
||||||
const recommendation = content.recommendation ?? prevContent?.recommendation
|
const recommendation = content.recommendation ?? prevContent?.recommendation
|
||||||
if (!entity || !recommendation) {
|
if ((!entity && !hashedEntity) || !recommendation) {
|
||||||
return <div className="policy-body">
|
return <div className="policy-body">
|
||||||
{getDisplayname(event.sender, sender?.content)} sent an invalid policy rule
|
{getDisplayname(event.sender, sender?.content)} sent an invalid policy rule
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
const target = event.type.replace(/^m\.policy\.rule\./, "")
|
||||||
let entityElement = <>{entity}</>
|
let entityElement = <>{entity}</>
|
||||||
if(event.type === "m.policy.rule.user" && !entity?.includes("*") && !entity?.includes("?")) {
|
let matchingWord = `${target}s matching`
|
||||||
|
if (!entity && hashedEntity) {
|
||||||
|
matchingWord = `the ${target} with hash`
|
||||||
|
entityElement = <>{hashedEntity}</>
|
||||||
|
} else if (event.type === "m.policy.rule.user" && entity && !entity.includes("*") && !entity.includes("?")) {
|
||||||
entityElement = (
|
entityElement = (
|
||||||
<a
|
<a
|
||||||
className="hicli-matrix-uri hicli-matrix-uri-user"
|
className="hicli-matrix-uri hicli-matrix-uri-user"
|
||||||
|
@ -44,16 +51,18 @@ const PolicyRuleBody = ({ event, sender }: EventContentProps) => {
|
||||||
{entity}
|
{entity}
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
matchingWord = "user"
|
||||||
}
|
}
|
||||||
let recommendationElement: JSX.Element | string = <code>{recommendation}</code>
|
let recommendationElement: JSX.Element | string = <code>{recommendation}</code>
|
||||||
if (recommendation === "m.ban") {
|
if (recommendation === "m.ban") {
|
||||||
recommendationElement = "ban"
|
recommendationElement = "ban"
|
||||||
|
} else if (recommendation === "org.matrix.msc4204.takedown") {
|
||||||
|
recommendationElement = "takedown"
|
||||||
}
|
}
|
||||||
const action = prevContent ? ((content.entity && content.recommendation) ? "updated" : "removed") : "added"
|
const action = prevContent ? ((content.entity && content.recommendation) ? "updated" : "removed") : "added"
|
||||||
const target = event.type.replace(/^m\.policy\.rule\./, "")
|
|
||||||
return <div className="policy-body">
|
return <div className="policy-body">
|
||||||
{getDisplayname(event.sender, sender?.content)} {action} a {recommendationElement} rule
|
{getDisplayname(event.sender, sender?.content)} {action} a {recommendationElement} rule
|
||||||
for {target}s matching <code>{entityElement}</code>
|
for {matchingWord} <code>{entityElement}</code>
|
||||||
{content.reason ? <> for <code>{content.reason}</code></> : null}
|
{content.reason ? <> for <code>{content.reason}</code></> : null}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ const elementCallParams = new URLSearchParams({
|
||||||
appPrompt: "false",
|
appPrompt: "false",
|
||||||
}).toString().replaceAll("%24", "$")
|
}).toString().replaceAll("%24", "$")
|
||||||
|
|
||||||
const ElementCall = () => {
|
const ElementCall = ({ onClose }: { onClose?: () => void }) => {
|
||||||
const room = use(RoomContext)?.store ?? null
|
const room = use(RoomContext)?.store ?? null
|
||||||
const client = use(ClientContext)!
|
const client = use(ClientContext)!
|
||||||
const baseURL = usePreference(client.store, room, "element_call_base_url")
|
const baseURL = usePreference(client.store, room, "element_call_base_url")
|
||||||
|
@ -51,7 +51,7 @@ const ElementCall = () => {
|
||||||
if (!room || !client) {
|
if (!room || !client) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return <LazyWidget info={widgetInfo} />
|
return <LazyWidget info={widgetInfo} onClose={onClose} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ElementCall
|
export default ElementCall
|
||||||
|
|
|
@ -28,9 +28,10 @@ const widgetLoader = <div className="widget-container widget-loading">
|
||||||
|
|
||||||
export interface LazyWidgetProps {
|
export interface LazyWidgetProps {
|
||||||
info: IWidget
|
info: IWidget
|
||||||
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const LazyWidget = ({ info }: LazyWidgetProps) => {
|
const LazyWidget = ({ info, onClose }: LazyWidgetProps) => {
|
||||||
const room = use(RoomContext)?.store
|
const room = use(RoomContext)?.store
|
||||||
const client = use(ClientContext)
|
const client = use(ClientContext)
|
||||||
if (!room || !client) {
|
if (!room || !client) {
|
||||||
|
@ -38,7 +39,7 @@ const LazyWidget = ({ info }: LazyWidgetProps) => {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={widgetLoader}>
|
<Suspense fallback={widgetLoader}>
|
||||||
<Widget info={info} room={room} client={client} />
|
<Widget info={info} room={room} client={client} onClose={onClose} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
111
web/src/ui/widget/PermissionPrompt.tsx
Normal file
111
web/src/ui/widget/PermissionPrompt.tsx
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
// gomuks - A Matrix client written in Go.
|
||||||
|
// Copyright (C) 2025 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
import { MatrixCapabilities } from "matrix-widget-api"
|
||||||
|
import { use, useState } from "react"
|
||||||
|
import { ModalCloseContext } from "../modal"
|
||||||
|
|
||||||
|
interface PermissionPromptProps {
|
||||||
|
capabilities: Set<string>
|
||||||
|
onConfirm: (approvedCapabilities: Set<string>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCapabilityName = (capability: string): string => {
|
||||||
|
const paramIdx = capability.indexOf(":")
|
||||||
|
const capabilityID = paramIdx === -1 ? capability : capability.slice(0, paramIdx)
|
||||||
|
const parameter = paramIdx === -1 ? null : capability.slice(paramIdx + 1)
|
||||||
|
|
||||||
|
// Map capability IDs to human-readable names
|
||||||
|
const capabilityNames: Record<string, string> = {
|
||||||
|
[MatrixCapabilities.MSC2931Navigate]: "Navigate to other rooms",
|
||||||
|
[MatrixCapabilities.MSC3846TurnServers]: "Request TURN servers from the homeserver",
|
||||||
|
[MatrixCapabilities.MSC4157SendDelayedEvent]: "Send delayed events",
|
||||||
|
[MatrixCapabilities.MSC4157UpdateDelayedEvent]: "Update delayed events",
|
||||||
|
[MatrixCapabilities.MSC4039UploadFile]: "Upload files",
|
||||||
|
[MatrixCapabilities.MSC4039DownloadFile]: "Download files",
|
||||||
|
"org.matrix.msc2762.timeline": "Read room history",
|
||||||
|
"org.matrix.msc2762.send.event": "Send timeline events",
|
||||||
|
"org.matrix.msc2762.receive.event": "Receive timeline events",
|
||||||
|
"org.matrix.msc2762.send.state_event": "Send state events",
|
||||||
|
"org.matrix.msc2762.receive.state_event": "Receive state events",
|
||||||
|
"org.matrix.msc3819.send.to_device": "Send to-device events",
|
||||||
|
"org.matrix.msc3819.receive.to_device": "Receive to-device events",
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = capabilityNames[capabilityID] || capabilityID
|
||||||
|
|
||||||
|
if (parameter) {
|
||||||
|
return `${name} (${parameter})`
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
const PermissionPrompt = ({ capabilities, onConfirm }: PermissionPromptProps) => {
|
||||||
|
const [selectedCapabilities, setSelectedCapabilities] = useState<Set<string>>(() => new Set(capabilities))
|
||||||
|
const closeModal = use(ModalCloseContext)
|
||||||
|
|
||||||
|
const handleToggleCapability = (capability: string) => {
|
||||||
|
const newCapabilities = new Set(selectedCapabilities)
|
||||||
|
if (newCapabilities.has(capability)) {
|
||||||
|
newCapabilities.delete(capability)
|
||||||
|
} else {
|
||||||
|
newCapabilities.add(capability)
|
||||||
|
}
|
||||||
|
setSelectedCapabilities(newCapabilities)
|
||||||
|
}
|
||||||
|
|
||||||
|
const doConfirm = () => {
|
||||||
|
onConfirm(selectedCapabilities)
|
||||||
|
closeModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
const doReject = () => {
|
||||||
|
onConfirm(new Set())
|
||||||
|
closeModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<h2>Widget Permissions</h2>
|
||||||
|
<p>This widget is requesting the following permissions:</p>
|
||||||
|
|
||||||
|
<div className="capability-list">
|
||||||
|
{Array.from(capabilities).map((capability) => (
|
||||||
|
<div key={capability} className="capability-item">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedCapabilities.has(capability)}
|
||||||
|
onChange={() => handleToggleCapability(capability)}
|
||||||
|
/>
|
||||||
|
{getCapabilityName(capability)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="permission-actions">
|
||||||
|
<button onClick={doReject}>Reject all</button>
|
||||||
|
<button
|
||||||
|
onClick={doConfirm}
|
||||||
|
className="confirm-button"
|
||||||
|
>
|
||||||
|
Accept selected
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PermissionPrompt
|
|
@ -1,9 +1,25 @@
|
||||||
div.right-panel-content.widget, div.right-panel-content.element-call {
|
div.right-panel-content.widget, div.right-panel-content.element-call {
|
||||||
overflow: hidden !important;
|
overflow: hidden !important;
|
||||||
|
|
||||||
> iframe.widget-iframe {
|
}
|
||||||
|
|
||||||
|
iframe.widget-iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.permission-prompt {
|
||||||
|
> h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div.permission-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
> button {
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import type Client from "@/api/client"
|
||||||
import type { RoomStateStore, WidgetListener } from "@/api/statestore"
|
import type { RoomStateStore, WidgetListener } from "@/api/statestore"
|
||||||
import type { MemDBEvent, RoomID, SyncToDevice } from "@/api/types"
|
import type { MemDBEvent, RoomID, SyncToDevice } from "@/api/types"
|
||||||
import { getDisplayname } from "@/util/validation"
|
import { getDisplayname } from "@/util/validation"
|
||||||
|
import PermissionPrompt from "./PermissionPrompt"
|
||||||
import { memDBEventToIRoomEvent } from "./util"
|
import { memDBEventToIRoomEvent } from "./util"
|
||||||
import GomuksWidgetDriver from "./widgetDriver"
|
import GomuksWidgetDriver from "./widgetDriver"
|
||||||
import "./Widget.css"
|
import "./Widget.css"
|
||||||
|
@ -27,6 +28,7 @@ export interface WidgetProps {
|
||||||
info: IWidget
|
info: IWidget
|
||||||
room: RoomStateStore
|
room: RoomStateStore
|
||||||
client: Client
|
client: Client
|
||||||
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove this after widgets start using a parameter for it
|
// TODO remove this after widgets start using a parameter for it
|
||||||
|
@ -68,9 +70,24 @@ class WidgetListenerImpl implements WidgetListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ReactWidget = ({ room, info, client }: WidgetProps) => {
|
const openPermissionPrompt = (requested: Set<string>): Promise<Set<string>> => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
window.openModal({
|
||||||
|
content: <PermissionPrompt
|
||||||
|
capabilities={requested}
|
||||||
|
onConfirm={resolve}
|
||||||
|
/>,
|
||||||
|
dimmed: true,
|
||||||
|
boxed: true,
|
||||||
|
noDismiss: true,
|
||||||
|
innerBoxClass: "permission-prompt",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReactWidget = ({ room, info, client, onClose }: WidgetProps) => {
|
||||||
const wrappedWidget = new WrappedWidget(info)
|
const wrappedWidget = new WrappedWidget(info)
|
||||||
const driver = new GomuksWidgetDriver(client, room)
|
const driver = new GomuksWidgetDriver(client, room, openPermissionPrompt)
|
||||||
const widgetURL = addLegacyParams(wrappedWidget.getCompleteUrl({
|
const widgetURL = addLegacyParams(wrappedWidget.getCompleteUrl({
|
||||||
widgetRoomId: room.roomID,
|
widgetRoomId: room.roomID,
|
||||||
currentUserId: client.userID,
|
currentUserId: client.userID,
|
||||||
|
@ -92,13 +109,16 @@ const ReactWidget = ({ room, info, client }: WidgetProps) => {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
clientAPI.transport.reply(evt.detail, {})
|
clientAPI.transport.reply(evt.detail, {})
|
||||||
}
|
}
|
||||||
|
const closeWidget = (evt: CustomEvent) => {
|
||||||
|
noopReply(evt)
|
||||||
|
onClose?.()
|
||||||
|
}
|
||||||
clientAPI.on("action:io.element.join", noopReply)
|
clientAPI.on("action:io.element.join", noopReply)
|
||||||
clientAPI.on("action:im.vector.hangup", noopReply)
|
clientAPI.on("action:im.vector.hangup", noopReply)
|
||||||
clientAPI.on("action:io.element.device_mute", noopReply)
|
clientAPI.on("action:io.element.device_mute", noopReply)
|
||||||
clientAPI.on("action:io.element.tile_layout", noopReply)
|
clientAPI.on("action:io.element.tile_layout", noopReply)
|
||||||
clientAPI.on("action:io.element.spotlight_layout", noopReply)
|
clientAPI.on("action:io.element.spotlight_layout", noopReply)
|
||||||
// TODO handle this one?
|
clientAPI.on("action:io.element.close", closeWidget)
|
||||||
clientAPI.on("action:io.element.close", noopReply)
|
|
||||||
clientAPI.on("action:set_always_on_screen", noopReply)
|
clientAPI.on("action:set_always_on_screen", noopReply)
|
||||||
const removeListener = client.addWidgetListener(new WidgetListenerImpl(clientAPI))
|
const removeListener = client.addWidgetListener(new WidgetListenerImpl(clientAPI))
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,13 @@ import {
|
||||||
IOpenIDUpdate,
|
IOpenIDUpdate,
|
||||||
IRoomAccountData,
|
IRoomAccountData,
|
||||||
IRoomEvent,
|
IRoomEvent,
|
||||||
|
ISendDelayedEventDetails,
|
||||||
ISendEventDetails,
|
ISendEventDetails,
|
||||||
ITurnServer,
|
ITurnServer,
|
||||||
OpenIDRequestState,
|
OpenIDRequestState,
|
||||||
SimpleObservable,
|
SimpleObservable,
|
||||||
Symbols,
|
Symbols,
|
||||||
|
UpdateDelayedEventAction,
|
||||||
WidgetDriver,
|
WidgetDriver,
|
||||||
} from "matrix-widget-api"
|
} from "matrix-widget-api"
|
||||||
import Client from "@/api/client.ts"
|
import Client from "@/api/client.ts"
|
||||||
|
@ -35,12 +37,16 @@ class GomuksWidgetDriver extends WidgetDriver {
|
||||||
private openIDToken: IOpenIDCredentials | null = null
|
private openIDToken: IOpenIDCredentials | null = null
|
||||||
private openIDExpiry: number | null = null
|
private openIDExpiry: number | null = null
|
||||||
|
|
||||||
constructor(private client: Client, private room: RoomStateStore) {
|
constructor(
|
||||||
|
private client: Client,
|
||||||
|
private room: RoomStateStore,
|
||||||
|
private openPermissionPrompt: (requested: Set<string>) => Promise<Set<string>>,
|
||||||
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateCapabilities(requested: Set<string>): Promise<Set<string>> {
|
async validateCapabilities(requested: Set<string>): Promise<Set<string>> {
|
||||||
return new Set(requested)
|
return this.openPermissionPrompt(requested)
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendEvent(
|
async sendEvent(
|
||||||
|
@ -62,23 +68,31 @@ class GomuksWidgetDriver extends WidgetDriver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// async sendDelayedEvent(
|
async sendDelayedEvent(
|
||||||
// delay: number | null,
|
delay: number | null,
|
||||||
// parentDelayID: string | null,
|
parentDelayID: string | null,
|
||||||
// eventType: string,
|
eventType: string,
|
||||||
// content: unknown,
|
content: unknown,
|
||||||
// stateKey: string | null = null,
|
stateKey: string | null = null,
|
||||||
// roomID: string | null = null,
|
roomID: string | null = null,
|
||||||
// ): Promise<ISendDelayedEventDetails> {
|
): Promise<ISendDelayedEventDetails> {
|
||||||
// if (!isRecord(content)) {
|
if (!isRecord(content)) {
|
||||||
// throw new Error("Content must be an object")
|
throw new Error("Content must be an object")
|
||||||
// }
|
} else if (stateKey === null) {
|
||||||
// throw new Error("Delayed events are not supported")
|
throw new Error("Non-state delayed events are not supported")
|
||||||
// }
|
} else if (parentDelayID !== null) {
|
||||||
|
throw new Error("Parent delayed events are not supported")
|
||||||
|
} else if (!delay) {
|
||||||
|
throw new Error("Delay must be a number")
|
||||||
|
}
|
||||||
|
roomID = roomID ?? this.room.roomID
|
||||||
|
const delayID = await this.client.rpc.setState(roomID, eventType, stateKey, content, { delay_ms: delay })
|
||||||
|
return { delayId: delayID, roomId: roomID }
|
||||||
|
}
|
||||||
|
|
||||||
// async updateDelayedEvent(delayID: string, action: UpdateDelayedEventAction): Promise<void> {
|
async updateDelayedEvent(delayID: string, action: UpdateDelayedEventAction): Promise<void> {
|
||||||
// throw new Error("Delayed events are not supported")
|
await this.client.rpc.updateDelayedEvent(delayID, action)
|
||||||
// }
|
}
|
||||||
|
|
||||||
async sendToDevice(
|
async sendToDevice(
|
||||||
eventType: string,
|
eventType: string,
|
||||||
|
|
1
web/src/vite-env.d.ts
vendored
1
web/src/vite-env.d.ts
vendored
|
@ -17,6 +17,7 @@ declare global {
|
||||||
gcSettings: GCSettings
|
gcSettings: GCSettings
|
||||||
hackyOpenEventContextMenu?: string
|
hackyOpenEventContextMenu?: string
|
||||||
closeModal: () => void
|
closeModal: () => void
|
||||||
|
openModal: openModal
|
||||||
openNestableModal: openModal
|
openNestableModal: openModal
|
||||||
gomuksAndroid?: true
|
gomuksAndroid?: true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue