Compare commits

...

25 Commits

Author SHA1 Message Date
binwiederhier
88eb728fe3 Changelog 2023-06-30 21:51:03 -04:00
binwiederhier
26c835cdd1 Install notes, background change for xs dark mode drawer 2023-06-30 09:58:56 -04:00
binwiederhier
7d3d697a20 Fix goreleaser 2023-06-30 09:30:36 -04:00
binwiederhier
798ee3c23c Merge branch 'main' of github.com:binwiederhier/ntfy 2023-06-30 08:45:44 -04:00
binwiederhier
7581058c93 Bump Go version in pipelines 2023-06-30 08:45:28 -04:00
Philipp C. Heckel
4f0ddfc30d Merge pull request #795 from nimbleghost/pwa-improvements
PWA: Fix reload, Firefox mp3 load, reduce mobile padding
2023-06-30 08:43:19 -04:00
nimbleghost
0b918464c1 Move registerSW out 2023-06-30 08:59:31 +02:00
nimbleghost
57bd37ef2f Fix sidebar colour on mobile 2023-06-29 15:22:59 +02:00
nimbleghost
9fa1288dbc Fix update behaviour 2023-06-29 15:07:18 +02:00
nimbleghost
55eed868fa Reduce padding on mobile / narrow screens 2023-06-29 13:15:06 +02:00
nimbleghost
abb1baeecd Don’t include mp3 due to Firefox sw issue 2023-06-29 13:15:06 +02:00
binwiederhier
5784b07f14 Bump 2023-06-28 20:23:10 -04:00
binwiederhier
8e1e0b3740 Overflow auto 2023-06-28 20:17:49 -04:00
binwiederhier
3f42e0e945 Merge branch 'main' into fix-permission-handling 2023-06-28 20:05:26 -04:00
binwiederhier
9146e439d2 Merge branch 'main' of github.com:binwiederhier/ntfy 2023-06-28 20:03:36 -04:00
binwiederhier
7a14a0b81f Merge branch 'main' of https://hosted.weblate.org/git/ntfy/web 2023-06-28 20:03:32 -04:00
Philipp C. Heckel
9247475ab2 Merge pull request #793 from nimbleghost/pwa-action-bar
Make action bar match theme colour when run as PWA
2023-06-28 19:58:41 -04:00
nimbleghost
6b4c04c390 Make action bar match theme colour when run as PWA 2023-06-29 00:22:58 +02:00
nimbleghost
e8216ae9e7 Fix resubscribing when notifications are re-granted
(case: from denied to granted)
2023-06-29 00:02:18 +02:00
nimbleghost
365a0b2832 Fix preferences warnings 2023-06-28 23:38:57 +02:00
Philipp C. Heckel
f78389b6ef Merge pull request #792 from nimbleghost/fix-ntfy-banner
Fix ntfy upgrade banner in dark mode
2023-06-28 15:53:54 -04:00
nimbleghost
0d231d8bd9 Fix snackbars in dark mode 2023-06-28 21:18:04 +02:00
nimbleghost
d838790b8f Fix ntfy upgrade banner in dark mode 2023-06-28 20:43:42 +02:00
nimbleghost
9ce3545901 Fix refreshing things when permission is granted
We refreshed some things but not everything, this makes it more
responsive if you have the settings page open when granting permissions,
for example.
2023-06-28 20:26:54 +02:00
怪盗kidou
8db569e8a5 Translated using Weblate (Chinese (Simplified))
Currently translated at 94.5% (361 of 382 strings)

Translation: ntfy/Web app
Translate-URL: https://hosted.weblate.org/projects/ntfy/web/zh_Hans/
2023-06-28 16:52:23 +02:00
27 changed files with 309 additions and 220 deletions

View File

@@ -11,7 +11,7 @@ jobs:
name: Install Go
uses: actions/setup-go@v4
with:
go-version: '1.19.x'
go-version: '1.20.x'
-
name: Install node
uses: actions/setup-node@v3

View File

@@ -14,7 +14,7 @@ jobs:
name: Install Go
uses: actions/setup-go@v4
with:
go-version: '1.19.x'
go-version: '1.20.x'
-
name: Install node
uses: actions/setup-node@v3

View File

@@ -11,7 +11,7 @@ jobs:
name: Install Go
uses: actions/setup-go@v4
with:
go-version: '1.19.x'
go-version: '1.20.x'
-
name: Install node
uses: actions/setup-node@v3

View File

@@ -119,8 +119,6 @@ archives:
- server/ntfy.service
- client/client.yml
- client/ntfy-client.service
replacements:
amd64: x86_64
-
id: ntfy_windows
builds:
@@ -131,8 +129,6 @@ archives:
- LICENSE
- README.md
- client/client.yml
replacements:
amd64: x86_64
-
id: ntfy_darwin
builds:
@@ -142,8 +138,6 @@ archives:
- LICENSE
- README.md
- client/client.yml
replacements:
darwin: macOS
universal_binaries:
-
id: ntfy_darwin_all

View File

@@ -29,37 +29,37 @@ deb/rpm packages.
=== "x86_64/amd64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_x86_64.tar.gz
tar zxvf ntfy_2.6.0_linux_x86_64.tar.gz
sudo cp -a ntfy_2.6.0_linux_x86_64/ntfy /usr/local/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.0_linux_x86_64/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_amd64.tar.gz
tar zxvf ntfy_2.6.2_linux_amd64.tar.gz
sudo cp -a ntfy_2.6.2_linux_amd64/ntfy /usr/local/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_amd64/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
=== "armv6"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv6.tar.gz
tar zxvf ntfy_2.6.0_linux_armv6.tar.gz
sudo cp -a ntfy_2.6.0_linux_armv6/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.0_linux_armv6/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv6.tar.gz
tar zxvf ntfy_2.6.2_linux_armv6.tar.gz
sudo cp -a ntfy_2.6.2_linux_armv6/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_armv6/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
=== "armv7/armhf"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv7.tar.gz
tar zxvf ntfy_2.6.0_linux_armv7.tar.gz
sudo cp -a ntfy_2.6.0_linux_armv7/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.0_linux_armv7/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv7.tar.gz
tar zxvf ntfy_2.6.2_linux_armv7.tar.gz
sudo cp -a ntfy_2.6.2_linux_armv7/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_armv7/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
=== "arm64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_arm64.tar.gz
tar zxvf ntfy_2.6.0_linux_arm64.tar.gz
sudo cp -a ntfy_2.6.0_linux_arm64/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.0_linux_arm64/{client,server}/*.yml /etc/ntfy
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_arm64.tar.gz
tar zxvf ntfy_2.6.2_linux_arm64.tar.gz
sudo cp -a ntfy_2.6.2_linux_arm64/ntfy /usr/bin/ntfy
sudo mkdir /etc/ntfy && sudo cp ntfy_2.6.2_linux_arm64/{client,server}/*.yml /etc/ntfy
sudo ntfy serve
```
@@ -109,7 +109,7 @@ Manually installing the .deb file:
=== "x86_64/amd64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_amd64.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_amd64.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -117,7 +117,7 @@ Manually installing the .deb file:
=== "armv6"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv6.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv6.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -125,7 +125,7 @@ Manually installing the .deb file:
=== "armv7/armhf"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv7.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv7.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -133,7 +133,7 @@ Manually installing the .deb file:
=== "arm64"
```bash
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_arm64.deb
wget https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_arm64.deb
sudo dpkg -i ntfy_*.deb
sudo systemctl enable ntfy
sudo systemctl start ntfy
@@ -143,34 +143,36 @@ Manually installing the .deb file:
=== "x86_64/amd64"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_amd64.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_amd64.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
=== "armv6"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv6.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv6.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
=== "armv7/armhf"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_armv7.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_armv7.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
=== "arm64"
```bash
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_linux_arm64.rpm
sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_linux_arm64.rpm
sudo systemctl enable ntfy
sudo systemctl start ntfy
```
## Arch Linux
ntfy can be installed using an [AUR package](https://aur.archlinux.org/packages/ntfysh-bin/). You can use an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like `paru`, `yay` or others to download, build and install ntfy and keep it up to date.
ntfy can be installed using an [AUR package](https://aur.archlinux.org/packages/ntfysh-bin/).
You can use an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers) like `paru`, `yay` or others to download,
build and install ntfy and keep it up to date.
```
paru -S ntfysh-bin
```
@@ -192,18 +194,18 @@ NixOS also supports [declarative setup of the ntfy server](https://search.nixos.
## macOS
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well.
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_macOS_all.tar.gz),
To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_darwin_all.tar.gz),
extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`).
If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For all other users, it'll look for it at
`~/Library/Application Support/ntfy/client.yml` (sample included in the tarball).
```bash
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_macOS_all.tar.gz > ntfy_2.6.0_macOS_all.tar.gz
tar zxvf ntfy_2.6.0_macOS_all.tar.gz
sudo cp -a ntfy_2.6.0_macOS_all/ntfy /usr/local/bin/ntfy
curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_darwin_all.tar.gz > ntfy_2.6.2_darwin_all.tar.gz
tar zxvf ntfy_2.6.2_darwin_all.tar.gz
sudo cp -a ntfy_2.6.2_darwin_all/ntfy /usr/local/bin/ntfy
mkdir ~/Library/Application\ Support/ntfy
cp ntfy_2.6.0_macOS_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
cp ntfy_2.6.2_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml
ntfy --help
```
@@ -221,7 +223,7 @@ brew install ntfy
## Windows
The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well.
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.6.0/ntfy_2.6.0_windows_x86_64.zip),
To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.6.2/ntfy_2.6.2_windows_amd64.zip),
extract it and place the `ntfy.exe` binary somewhere in your `%Path%`.
The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file).

View File

@@ -2,13 +2,14 @@
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
### ntfy server v2.6.0
Released June 28, 2023
### ntfy server v2.6.2
Released June 30, 2023
With this release, the ntfy web app now contains a **[progressive web app](https://docs.ntfy.sh/subscribe/pwa/) (PWA)
With this release, the ntfy web app now contains a **[progressive web app](subscribe/pwa.md) (PWA)
with Web Push support**, which means you'll be able to **install the ntfy web app on your desktop or phone** similar
to a native app (__even on iOS!__ 🥳). Installing the PWA gives ntfy web its own launcher, a standalone window,
push notifications, and an app badge with the unread notification count.
push notifications, and an app badge with the unread notification count. Note that for self-hosted servers,
[Web Push](config.md#web-push) must be configured.
On top of that, this release also brings **dark mode** 🧛🌙 to the web app.
@@ -30,6 +31,8 @@ if you use promo code `MYTOPIC`). ntfy will always remain open source.
* Do not forward poll requests for UnifiedPush messages (no ticket, thanks to NoName for reporting)
* Fix `ntfy pub %` segfaulting ([#760](https://github.com/binwiederhier/ntfy/issues/760), thanks to [@clesmian](https://github.com/clesmian) for reporting)
* Newly created access tokens are now lowercase only to fully support `<topic>+<token>@<domain>` email syntax ([#773](https://github.com/binwiederhier/ntfy/issues/773), thanks to gingervitiz for reporting)
* The .1 release fixes a few visual issues with dark mode, and other web app updates ([#791](https://github.com/binwiederhier/ntfy/pull/791), [#793](https://github.com/binwiederhier/ntfy/pull/793), [#792](https://github.com/binwiederhier/ntfy/pull/792), thanks to [@nimbleghost](https://github.com/nimbleghost))
* The .2 release fixes issues with the service worker in Firefox and adds automatic service worker updates ([#795](https://github.com/binwiederhier/ntfy/pull/795), thanks to [@nimbleghost](https://github.com/nimbleghost))
**Maintenance:**
@@ -38,6 +41,14 @@ if you use promo code `MYTOPIC`). ntfy will always remain open source.
* Web: Add eslint with eslint-config-airbnb ([#748](https://github.com/binwiederhier/ntfy/pull/748), thanks to [@nimbleghost](https://github.com/nimbleghost))
* Web: Switch to Vite ([#749](https://github.com/binwiederhier/ntfy/pull/749), thanks to [@nimbleghost](https://github.com/nimbleghost))
**Changes in tarball/zip naming:**
Due to a [change in GoReleaser](https://goreleaser.com/deprecations/#archivesreplacements), some of the binary release
archives now have slightly different names. My apologies if this causes issues in the downstream projects that use ntfy:
- `ntfy_v${VERSION}_windows_x86_64.zip` -> `ntfy_v${VERSION}_windows_amd64.zip`
- `ntfy_v${VERSION}_linux_x86_64.tar.gz` -> `ntfy_v${VERSION}_linux_amd64.tar.gz`
- `ntfy_v${VERSION}_macOS_all.tar.gz` -> `ntfy_v${VERSION}_darwin_all.tar.gz`
## ntfy server v2.5.0
Released May 18, 2023

View File

@@ -12,6 +12,8 @@ Web app installation is **supported on** (see [compatibility table](https://cani
- **Firefox:** Android, as well as on Windows/Linux [via an extension](https://addons.mozilla.org/en-US/firefox/addon/pwas-for-firefox/)
- **Edge:** Windows
Note that for self-hosted servers, [Web Push](../config.md#web-push) must be configured for the PWA to work.
## Installation
### Chrome on Desktop

10
go.mod
View File

@@ -4,7 +4,7 @@ go 1.18
require (
cloud.google.com/go/firestore v1.11.0 // indirect
cloud.google.com/go/storage v1.30.1 // indirect
cloud.google.com/go/storage v1.31.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/emersion/go-smtp v0.16.0
@@ -29,7 +29,7 @@ require (
firebase.google.com/go/v4 v4.11.0
github.com/SherClockHolmes/webpush-go v1.2.0
github.com/prometheus/client_golang v1.16.0
github.com/stripe/stripe-go/v74 v74.23.0
github.com/stripe/stripe-go/v74 v74.24.0
)
require (
@@ -69,9 +69,9 @@ require (
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/appengine/v2 v2.0.3 // indirect
google.golang.org/genproto v0.0.0-20230626202813-9b080da550b3 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230626202813-9b080da550b3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230626202813-9b080da550b3 // indirect
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 // indirect
google.golang.org/grpc v1.56.1 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

20
go.sum
View File

@@ -12,8 +12,8 @@ cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y=
cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI=
cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc=
cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM=
cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E=
cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI=
cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0=
firebase.google.com/go/v4 v4.11.0 h1:szjBoiF33A2FavRLIDZjW1mw+OsW/XAtHoYNIqWOjRk=
firebase.google.com/go/v4 v4.11.0/go.mod h1:60c36dWLK4+j05Vw5XMllek3b3PCynU3BfI46OSwsUE=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
@@ -143,8 +143,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stripe/stripe-go/v74 v74.23.0 h1:9spORjBMhg8SieRrlrqQdlrw+JllpL6gZnD3QGsCN6Q=
github.com/stripe/stripe-go/v74 v74.23.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
github.com/stripe/stripe-go/v74 v74.24.0 h1:h+hXEI5avC5moAh2YLtphMFTBnp11TfXTcP4suuWDLk=
github.com/stripe/stripe-go/v74 v74.24.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
@@ -242,12 +242,12 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20230626202813-9b080da550b3 h1:Yofj1/U0xc/Zi5KEpoIxm51I2f85X+eGyY4YzAujRdw=
google.golang.org/genproto v0.0.0-20230626202813-9b080da550b3/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
google.golang.org/genproto/googleapis/api v0.0.0-20230626202813-9b080da550b3 h1:wl7z+A0jkB3Rl8Hz74SqGDlnnn5VlL2CV+9UTdZOo00=
google.golang.org/genproto/googleapis/api v0.0.0-20230626202813-9b080da550b3/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230626202813-9b080da550b3 h1:QJuqz7YzNTyKDspkp2lrzqtq4lf2AhUSpXTsGP5SbLw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230626202813-9b080da550b3/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529 h1:9JucMWR7sPvCxUFd6UsOUNmA5kCcWOfORaT3tpAsKQs=
google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64=
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529 h1:s5YSX+ZH5b5vS9rnpGymvIyMpLRJizowqDlOuyjXnTk=
google.golang.org/genproto/googleapis/api v0.0.0-20230629202037-9506855d4529/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529 h1:DEH99RbiLZhMxrpEJCZ0A+wdTe0EOgou/poSLx9vWf4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230629202037-9506855d4529/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=

96
web/package-lock.json generated
View File

@@ -2257,14 +2257,14 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
"integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz",
"integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.5.2",
"espree": "^9.6.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
@@ -2307,9 +2307,9 @@
}
},
"node_modules/@eslint/js": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
"integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz",
"integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2381,14 +2381,10 @@
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
"integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.4.tgz",
"integrity": "sha512-KE/SxsDqNs3rrWwFHcRh15ZLVFrI0YoZtgAdIyIq9k5hUNmiWRXXThPomIxHuL20sLdgzbDFyvkUMna14bvtrw==",
"dev": true
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
@@ -2698,9 +2694,9 @@
}
},
"node_modules/@remix-run/router": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0.tgz",
"integrity": "sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw==",
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.1.tgz",
"integrity": "sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ==",
"engines": {
"node": ">=14"
}
@@ -2730,9 +2726,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.3.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz",
"integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==",
"version": "20.3.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz",
"integrity": "sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==",
"dev": true
},
"node_modules/@types/parse-json": {
@@ -3441,9 +3437,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.442",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.442.tgz",
"integrity": "sha512-RkrZF//Ya+0aJq2NM3OdisNh5ZodZq1rdXOS96G8DdDgpDKqKE81yTbbQ3F/4CKm1JBPsGu1Lp/akkna2xO06Q==",
"version": "1.4.447",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.447.tgz",
"integrity": "sha512-sxX0LXh+uL41hSJsujAN86PjhrV/6c79XmpY0TvjZStV6VxIgarf8SRkUoUTuYmFcZQTemsoqo8qXOGw5npWfw==",
"dev": true
},
"node_modules/emoji-regex": {
@@ -3614,15 +3610,15 @@
}
},
"node_modules/eslint": {
"version": "8.43.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
"integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
"version": "8.44.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz",
"integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0",
"@eslint/eslintrc": "^2.0.3",
"@eslint/js": "8.43.0",
"@eslint/eslintrc": "^2.1.0",
"@eslint/js": "8.44.0",
"@humanwhocodes/config-array": "^0.11.10",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@@ -3634,7 +3630,7 @@
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.0",
"eslint-visitor-keys": "^3.4.1",
"espree": "^9.5.2",
"espree": "^9.6.0",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -3654,7 +3650,7 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
@@ -4043,12 +4039,12 @@
}
},
"node_modules/espree": {
"version": "9.5.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
"integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz",
"integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==",
"dev": true,
"dependencies": {
"acorn": "^8.8.0",
"acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.4.1"
},
@@ -4114,9 +4110,9 @@
"dev": true
},
"node_modules/fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
"integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -5828,11 +5824,11 @@
}
},
"node_modules/react-router": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.0.tgz",
"integrity": "sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA==",
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.1.tgz",
"integrity": "sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g==",
"dependencies": {
"@remix-run/router": "1.7.0"
"@remix-run/router": "1.7.1"
},
"engines": {
"node": ">=14"
@@ -5842,12 +5838,12 @@
}
},
"node_modules/react-router-dom": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0.tgz",
"integrity": "sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==",
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.1.tgz",
"integrity": "sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw==",
"dependencies": {
"@remix-run/router": "1.7.0",
"react-router": "6.14.0"
"@remix-run/router": "1.7.1",
"react-router": "6.14.1"
},
"engines": {
"node": ">=14"
@@ -6018,9 +6014,9 @@
}
},
"node_modules/rollup": {
"version": "3.25.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.3.tgz",
"integrity": "sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==",
"version": "3.26.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.0.tgz",
"integrity": "sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"

View File

@@ -55,14 +55,14 @@
"nav_upgrade_banner_label": "Upgrade to ntfy Pro",
"nav_upgrade_banner_description": "Reserve topics, more messages & emails, and larger attachments",
"alert_notification_permission_required_title": "Notifications are disabled",
"alert_notification_permission_required_description": "Grant your browser permission to display desktop notifications.",
"alert_notification_permission_required_description": "Grant your browser permission to display desktop notifications",
"alert_notification_permission_required_button": "Grant now",
"alert_notification_permission_denied_title": "Notifications are blocked",
"alert_notification_permission_denied_description": "Please re-enable them in your browser and refresh the page to receive notifications",
"alert_notification_permission_denied_description": "Please re-enable them in your browser",
"alert_notification_ios_install_required_title": "iOS install required",
"alert_notification_ios_install_required_description": "Click on the Share icon and Add to Home Screen to enable notifications on iOS",
"alert_not_supported_title": "Notifications not supported",
"alert_not_supported_description": "Notifications are not supported in your browser.",
"alert_not_supported_description": "Notifications are not supported in your browser",
"alert_not_supported_context_description": "Notifications are only supported over HTTPS. This is a limitation of the <mdnLink>Notifications API</mdnLink>.",
"notifications_list": "Notifications list",
"notifications_list_item": "Notification",

View File

@@ -352,5 +352,12 @@
"account_upgrade_dialog_tier_price_billed_monthly": "{{price}} 每年。按月计费。",
"account_upgrade_dialog_tier_price_billed_yearly": "{{价格}} 按年计费。节省 {{save}}。",
"account_upgrade_dialog_billing_contact_email": "有关账单问题,请直接<Link>联系我们 </Link>。",
"account_upgrade_dialog_billing_contact_website": "有关账单问题,请参考我们的<Link>网站 </Link>。"
"account_upgrade_dialog_billing_contact_website": "有关账单问题,请参考我们的<Link>网站 </Link>。",
"publish_dialog_call_item": "拨打电话 {{number}}",
"publish_dialog_call_label": "拨号",
"publish_dialog_chip_call_label": "拨号",
"publish_dialog_chip_call_no_verified_numbers_tooltip": "未验证的手机号",
"account_basics_phone_numbers_title": "电话号码",
"account_basics_phone_numbers_description": "电话通知",
"account_basics_phone_numbers_dialog_description": "要使用来电通知功能,您需要添加并验证至少一个电话号码。可以通过短信或电话进行验证。"
}

View File

@@ -2,6 +2,7 @@
import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from "workbox-precaching";
import { NavigationRoute, registerRoute } from "workbox-routing";
import { NetworkFirst } from "workbox-strategies";
import { clientsClaim } from "workbox-core";
import { dbAsync } from "../src/app/db";
@@ -224,6 +225,8 @@ precacheAndRoute(
self.__WB_MANIFEST
);
// Claim all open windows
clientsClaim();
// Delete any cached old dist files from previous service worker versions
cleanupOutdatedCaches();

View File

@@ -44,9 +44,6 @@ class Notifier {
}
async webPushSubscription(hasWebPushTopics) {
if (!this.pushPossible()) {
throw new Error("Unsupported or denied");
}
const pushManager = await this.pushManager();
const existingSubscription = await pushManager.getSubscription();
if (existingSubscription) {

View File

@@ -27,7 +27,7 @@ class SubscriptionManager {
* It is important to note that "mutedUntil" must be part of the where() query, otherwise the Dexie live query
* will not react to it, and the Web Push topics will not be updated when the user mutes a topic.
*/
async webPushTopics(pushPossible = notifier.pushPossible()) {
async webPushTopics(pushPossible) {
if (!pushPossible) {
return [];
}
@@ -120,13 +120,14 @@ class SubscriptionManager {
);
}
async updateWebPushSubscriptions(presetTopics) {
const topics = presetTopics ?? (await this.webPushTopics());
async updateWebPushSubscriptions(topics) {
const hasWebPushTopics = topics.length > 0;
const browserSubscription = await notifier.webPushSubscription(hasWebPushTopics);
if (!browserSubscription) {
console.log("[SubscriptionManager] No browser subscription currently exists, so web push was never enabled. Skipping.");
console.log(
"[SubscriptionManager] No browser subscription currently exists, so web push was never enabled or the notification permission was removed. Skipping."
);
return;
}

View File

@@ -787,7 +787,7 @@ const Tokens = () => {
}}
/>
</Paragraph>
<div style={{ width: "100%", overflowX: "scroll" }}>{tokens?.length > 0 && <TokensTable tokens={tokens} />}</div>
<div style={{ width: "100%", overflowX: "auto" }}>{tokens?.length > 0 && <TokensTable tokens={tokens} />}</div>
</CardContent>
<CardActions>
<Button onClick={handleCreateClick}>{t("account_tokens_table_create_token_button")}</Button>

View File

@@ -19,11 +19,14 @@ import Navigation from "./Navigation";
import accountApi from "../app/AccountApi";
import PopupMenu from "./PopupMenu";
import { SubscriptionPopup } from "./SubscriptionPopup";
import { useIsLaunchedPWA } from "./hooks";
const ActionBar = (props) => {
const theme = useTheme();
const { t } = useTranslation();
const location = useLocation();
const isLaunchedPWA = useIsLaunchedPWA();
let title = "ntfy";
if (props.selected) {
title = topicDisplayName(props.selected);
@@ -32,6 +35,22 @@ const ActionBar = (props) => {
} else if (location.pathname === routes.account) {
title = t("action_bar_account");
}
const getActionBarBackground = () => {
if (isLaunchedPWA) {
return "#317f6f";
}
switch (theme.palette.mode) {
case "dark":
return "linear-gradient(150deg, #203631 0%, #2a6e60 100%)";
case "light":
default:
return "linear-gradient(150deg, #338574 0%, #56bda8 100%)";
}
};
return (
<AppBar
position="fixed"
@@ -44,7 +63,7 @@ const ActionBar = (props) => {
<Toolbar
sx={{
pr: "24px",
background: theme.palette.actionBarBackground,
background: getActionBarBackground(),
}}
>
<IconButton

View File

@@ -1,14 +1,12 @@
import * as React from "react";
import { createContext, Suspense, useContext, useEffect, useState, useMemo } from "react";
import { Box, Toolbar, CssBaseline, Backdrop, CircularProgress, useMediaQuery } from "@mui/material";
import { ThemeProvider, createTheme } from "@mui/material/styles";
import { Box, Toolbar, CssBaseline, Backdrop, CircularProgress, useMediaQuery, ThemeProvider, createTheme } from "@mui/material";
import { useLiveQuery } from "dexie-react-hooks";
import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom";
import { AllSubscriptions, SingleSubscription } from "./Notifications";
import themeOptions, { darkPalette, lightPalette } from "./theme";
import { darkTheme, lightTheme } from "./theme";
import Navigation from "./Navigation";
import ActionBar from "./ActionBar";
import notifier from "../app/Notifier";
import Preferences from "./Preferences";
import subscriptionManager from "../app/SubscriptionManager";
import userManager from "../app/UserManager";
@@ -46,13 +44,7 @@ const App = () => {
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
const themePreference = useLiveQuery(() => prefs.theme());
const theme = React.useMemo(
() =>
createTheme({
...themeOptions,
palette: {
...(darkModeEnabled(prefersDarkMode, themePreference) ? darkPalette : lightPalette),
},
}),
() => createTheme(darkModeEnabled(prefersDarkMode, themePreference) ? darkTheme : lightTheme),
[prefersDarkMode, themePreference]
);
@@ -91,7 +83,6 @@ const Layout = () => {
const params = useParams();
const { account, setAccount } = useContext(AccountContext);
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted());
const [sendDialogOpenMode, setSendDialogOpenMode] = useState("");
const users = useLiveQuery(() => userManager.all());
const subscriptions = useLiveQuery(() => subscriptionManager.all());
@@ -115,10 +106,8 @@ const Layout = () => {
<Navigation
subscriptions={subscriptionsWithoutInternal}
selectedSubscription={selected}
notificationsGranted={notificationsGranted}
mobileDrawerOpen={mobileDrawerOpen}
onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
onNotificationGranted={setNotificationsGranted}
onPublishMessageClick={() => setSendDialogOpenMode(PublishDialog.OPEN_MODE_DEFAULT)}
/>
<Main>
@@ -143,7 +132,7 @@ const Main = (props) => (
display: "flex",
flexGrow: 1,
flexDirection: "column",
padding: 3,
padding: { xs: 0, md: 3 },
width: { sm: `calc(100% - ${Navigation.width}px)` },
height: "100dvh",
overflow: "auto",

View File

@@ -18,6 +18,7 @@ import {
Box,
IconButton,
Button,
useTheme,
} from "@mui/material";
import * as React from "react";
import { useContext, useState } from "react";
@@ -43,6 +44,7 @@ import UpgradeDialog from "./UpgradeDialog";
import { AccountContext } from "./App";
import { PermissionDenyAll, PermissionRead, PermissionReadWrite, PermissionWrite } from "./ReserveIcons";
import { SubscriptionPopup } from "./SubscriptionPopup";
import { useNotificationPermissionListener } from "./hooks";
const navWidth = 280;
@@ -59,7 +61,7 @@ const Navigation = (props) => {
ModalProps={{ keepMounted: true }} // Better open performance on mobile.
sx={{
display: { xs: "block", sm: "none" },
"& .MuiDrawer-paper": { boxSizing: "border-box", width: navWidth },
"& .MuiDrawer-paper": { boxSizing: "border-box", width: navWidth, backgroundImage: "none" },
}}
>
{navigationList}
@@ -82,6 +84,7 @@ const Navigation = (props) => {
Navigation.width = navWidth;
const NavList = (props) => {
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
@@ -109,17 +112,12 @@ const NavList = (props) => {
const isPaid = account?.billing?.subscription;
const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid;
const showSubscriptionsList = props.subscriptions?.length > 0;
const [showNotificationPermissionRequired, setShowNotificationPermissionRequired] = useState(notifier.notRequested());
const [showNotificationPermissionDenied, setShowNotificationPermissionDenied] = useState(notifier.denied());
const showNotificationPermissionRequired = useNotificationPermissionListener(() => notifier.notRequested());
const showNotificationPermissionDenied = useNotificationPermissionListener(() => notifier.denied());
const showNotificationIOSInstallRequired = notifier.iosSupportedButInstallRequired();
const showNotificationBrowserNotSupportedBox = !showNotificationIOSInstallRequired && !notifier.browserSupported();
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
const refreshPermissions = () => {
setShowNotificationPermissionRequired(notifier.notRequested());
setShowNotificationPermissionDenied(notifier.denied());
};
const alertVisible =
showNotificationPermissionRequired ||
showNotificationPermissionDenied ||
@@ -130,8 +128,8 @@ const NavList = (props) => {
return (
<>
<Toolbar sx={{ display: { xs: "none", sm: "block" } }} />
<List component="nav" sx={{ paddingTop: alertVisible ? "0" : "" }}>
{showNotificationPermissionRequired && <NotificationPermissionRequired refreshPermissions={refreshPermissions} />}
<List component="nav" sx={{ paddingTop: { xs: 0, sm: alertVisible ? 0 : "" } }}>
{showNotificationPermissionRequired && <NotificationPermissionRequired />}
{showNotificationPermissionDenied && <NotificationPermissionDeniedAlert />}
{showNotificationBrowserNotSupportedBox && <NotificationBrowserNotSupportedAlert />}
{showNotificationContextNotSupportedBox && <NotificationContextNotSupportedAlert />}
@@ -190,7 +188,11 @@ const NavList = (props) => {
</ListItemIcon>
<ListItemText primary={t("nav_button_subscribe")} />
</ListItemButton>
{showUpgradeBanner && <UpgradeBanner />}
{showUpgradeBanner && (
// The text background gradient didn't seem to do well with switching between light/dark mode,
// So adding a `key` forces React to replace the entire component when the theme changes
<UpgradeBanner key={`upgrade-banner-${theme.palette.mode}`} mode={theme.palette.mode} />
)}
</List>
<SubscribeDialog
key={`subscribeDialog${subscribeDialogKey}`} // Resets dialog when canceled/closed
@@ -203,7 +205,7 @@ const NavList = (props) => {
);
};
const UpgradeBanner = () => {
const UpgradeBanner = ({ mode }) => {
const { t } = useTranslation();
const [dialogKey, setDialogKey] = useState(0);
const [dialogOpen, setDialogOpen] = useState(false);
@@ -220,13 +222,16 @@ const UpgradeBanner = () => {
width: `${Navigation.width - 1}px`,
bottom: 0,
mt: "auto",
background: "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)",
background:
mode === "light"
? "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)"
: "linear-gradient(150deg, #203631 0%, #2a6e60 100%)",
}}
>
<Divider />
<ListItemButton onClick={handleClick} sx={{ pt: 2, pb: 2 }}>
<ListItemIcon>
<CelebrationIcon sx={{ color: "#55b86e" }} fontSize="large" />
<CelebrationIcon sx={{ color: mode === "light" ? "#55b86e" : "#00ff95" }} fontSize="large" />
</ListItemIcon>
<ListItemText
sx={{ ml: 1 }}
@@ -236,7 +241,10 @@ const UpgradeBanner = () => {
style: {
fontWeight: 500,
fontSize: "1.1rem",
background: "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)",
background:
mode === "light"
? "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)"
: "-webkit-linear-gradient(45deg,rgb(255, 255, 255), #00ff95 80%)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
},
@@ -354,11 +362,10 @@ const SubscriptionItem = (props) => {
);
};
const NotificationPermissionRequired = ({ refreshPermissions }) => {
const NotificationPermissionRequired = () => {
const { t } = useTranslation();
const requestPermission = async () => {
await notifier.maybeRequestPermission();
refreshPermissions();
};
return (
<Alert severity="warning" sx={{ paddingTop: 2 }}>

View File

@@ -184,7 +184,7 @@ const NotificationItem = (props) => {
const hasUserActions = notification.actions && notification.actions.length > 0;
const showActions = hasAttachmentActions || hasClickAction || hasUserActions;
return (
<Card sx={{ minWidth: 275, padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
<Card sx={{ padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
<CardContent>
<Tooltip title={t("notifications_delete")} enterDelay={500}>
<IconButton onClick={handleDelete} sx={{ float: "right", marginRight: -1, marginTop: -1 }} aria-label={t("notifications_delete")}>

View File

@@ -11,14 +11,14 @@ const PrefRow = styled("div")`
display: flex;
flex-direction: row;
> :first-child {
> div:first-of-type {
flex: 1 0 40%;
display: flex;
flex-direction: column;
justify-content: ${(props) => (props.alignTop ? "normal" : "center")};
}
> :last-child {
> div:last-of-type {
flex: 1 0 calc(60% - 50px);
display: flex;
flex-direction: column;
@@ -29,12 +29,12 @@ const PrefRow = styled("div")`
flex-direction: column;
gap: 10px;
> :first-child,
> :last-child {
> :div:first-of-type,
> :div:last-of-type {
flex: unset;
}
> :last-child {
> div:last-of-type {
.MuiFormControl-root {
margin: 0;
}

View File

@@ -49,7 +49,7 @@ import { ReserveAddDialog, ReserveDeleteDialog, ReserveEditDialog } from "./Rese
import { UnauthorizedError } from "../app/errors";
import { subscribeTopic } from "./SubscribeDialog";
import notifier from "../app/Notifier";
import { useIsLaunchedPWA } from "./hooks";
import { useIsLaunchedPWA, useNotificationPermissionListener } from "./hooks";
const maybeUpdateAccountSettings = async (payload) => {
if (!session.exists()) {
@@ -79,6 +79,7 @@ const Preferences = () => (
const Notifications = () => {
const { t } = useTranslation();
const isLaunchedPWA = useIsLaunchedPWA();
const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible());
return (
<Card sx={{ p: 3 }} aria-label={t("prefs_notifications_title")}>
@@ -89,7 +90,7 @@ const Notifications = () => {
<Sound />
<MinPriority />
<DeleteAfter />
{!isLaunchedPWA && notifier.pushPossible() && <WebPushEnabled />}
{!isLaunchedPWA && pushPossible && <WebPushEnabled />}
</PrefGroup>
</Card>
);
@@ -240,7 +241,7 @@ const DeleteAfter = () => {
const Theme = () => {
const { t } = useTranslation();
const labelId = "prefTheme";
const enabled = useLiveQuery(async () => prefs.theme());
const theme = useLiveQuery(async () => prefs.theme());
const handleChange = async (ev) => {
await prefs.setTheme(ev.target.value);
};
@@ -248,7 +249,7 @@ const Theme = () => {
return (
<Pref labelId={labelId} title={t("prefs_appearance_theme_title")}>
<FormControl fullWidth variant="standard" sx={{ m: 1 }}>
<Select value={enabled ?? false} onChange={handleChange} aria-labelledby={labelId}>
<Select value={theme ?? THEME.SYSTEM} onChange={handleChange} aria-labelledby={labelId}>
<MenuItem value={THEME.SYSTEM}>{t("prefs_appearance_theme_system")}</MenuItem>
<MenuItem value={THEME.DARK}>{t("prefs_appearance_theme_dark")}</MenuItem>
<MenuItem value={THEME.LIGHT}>{t("prefs_appearance_theme_light")}</MenuItem>

View File

@@ -136,8 +136,31 @@ export const useAutoSubscribe = (subscriptions, selected) => {
};
const webPushBroadcastChannel = new BroadcastChannel("web-push-broadcast");
const matchMedia = window.matchMedia("(display-mode: standalone)");
const isIOSStandalone = window.navigator.standalone === true;
/**
* Hook to return a value that's refreshed when the notification permission changes
*/
export const useNotificationPermissionListener = (query) => {
const [result, setResult] = useState(query());
useEffect(() => {
const handler = () => {
setResult(query());
};
if ("permissions" in navigator) {
navigator.permissions.query({ name: "notifications" }).then((permission) => {
permission.addEventListener("change", handler);
return () => {
permission.removeEventListener("change", handler);
};
});
}
}, []);
return result;
};
/**
* Updates the Web Push subscriptions when the list of topics changes,
@@ -145,11 +168,12 @@ const isIOSStandalone = window.navigator.standalone === true;
* the service worker, since the service worker cannot play sounds.
*/
const useWebPushListener = (topics) => {
const [lastTopics, setLastTopics] = useState();
const [prevUpdate, setPrevUpdate] = useState();
const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible());
useEffect(() => {
const topicsChanged = JSON.stringify(topics) !== JSON.stringify(lastTopics);
if (!notifier.pushPossible() || !topicsChanged) {
const nextUpdate = JSON.stringify({ topics, pushPossible });
if (topics === undefined || nextUpdate === prevUpdate) {
return;
}
@@ -157,12 +181,12 @@ const useWebPushListener = (topics) => {
try {
console.log("[useWebPushListener] Refreshing web push subscriptions", topics);
await subscriptionManager.updateWebPushSubscriptions(topics);
setLastTopics(topics);
setPrevUpdate(nextUpdate);
} catch (e) {
console.error("[useWebPushListener] Error refreshing web push subscriptions", e);
}
})();
}, [topics, lastTopics]);
}, [topics, pushPossible, prevUpdate]);
useEffect(() => {
const onMessage = () => {
@@ -183,25 +207,7 @@ const useWebPushListener = (topics) => {
* automatically.
*/
export const useWebPushTopics = () => {
const [pushPossible, setPushPossible] = useState(notifier.pushPossible());
useEffect(() => {
const handler = () => {
const newPushPossible = notifier.pushPossible();
console.log(`[useWebPushTopics] Notification Permission changed`, { pushPossible: newPushPossible });
setPushPossible(newPushPossible);
};
if ("permissions" in navigator) {
navigator.permissions.query({ name: "notifications" }).then((permission) => {
permission.addEventListener("change", handler);
return () => {
permission.removeEventListener("change", handler);
};
});
}
});
const pushPossible = useNotificationPermissionListener(() => notifier.pushPossible());
const topics = useLiveQuery(
async () => subscriptionManager.webPushTopics(pushPossible),
@@ -214,6 +220,9 @@ export const useWebPushTopics = () => {
return topics;
};
const matchMedia = window.matchMedia("(display-mode: standalone)");
const isIOSStandalone = window.navigator.standalone === true;
/*
* Watches the "display-mode" to detect if the app is running as a standalone app (PWA).
*/

View File

@@ -1,5 +1,5 @@
/** @type {import("@mui/material").ThemeOptions} */
const themeOptions = {
const baseThemeOptions = {
components: {
MuiListItemIcon: {
styleOverrides: {
@@ -22,37 +22,53 @@ const themeOptions = {
// https://github.com/binwiederhier/ntfy-android/blob/main/app/src/main/res/values/colors.xml
/** @type {import("@mui/material").ThemeOptions['palette']} */
export const lightPalette = {
mode: "light",
primary: {
main: "#338574",
/** @type {import("@mui/material").ThemeOptions} */
export const lightTheme = {
...baseThemeOptions,
components: {
...baseThemeOptions.components,
},
secondary: {
main: "#6cead0",
palette: {
mode: "light",
primary: {
main: "#338574",
},
secondary: {
main: "#6cead0",
},
error: {
main: "#c30000",
},
},
error: {
main: "#c30000",
},
actionBarBackground: "linear-gradient(150deg, #338574 0%, #56bda8 100%)",
};
/** @type {import("@mui/material").ThemeOptions['palette']} */
export const darkPalette = {
mode: "dark",
background: {
paper: "#1b2124",
/** @type {import("@mui/material").ThemeOptions} */
export const darkTheme = {
...baseThemeOptions,
components: {
...baseThemeOptions.components,
MuiSnackbarContent: {
styleOverrides: {
root: {
color: "#000",
backgroundColor: "#aeaeae",
},
},
},
},
primary: {
main: "#65b5a3",
palette: {
mode: "dark",
background: {
paper: "#1b2124",
},
primary: {
main: "#65b5a3",
},
secondary: {
main: "#6cead0",
},
error: {
main: "#fe4d2e",
},
},
secondary: {
main: "#6cead0",
},
error: {
main: "#fe4d2e",
},
actionBarBackground: "linear-gradient(150deg, #203631 0%, #2a6e60 100%)",
};
export default themeOptions;

View File

@@ -1,6 +1,9 @@
import * as React from "react";
import { createRoot } from "react-dom/client";
import App from "./components/App";
import registerSW from "./registerSW";
registerSW();
const root = createRoot(document.querySelector("#root"));
root.render(<App />);

31
web/src/registerSW.js Normal file
View File

@@ -0,0 +1,31 @@
// eslint-disable-next-line import/no-unresolved
import { registerSW as viteRegisterSW } from "virtual:pwa-register";
// fetch new sw every hour, i.e. update app every hour while running
const intervalMS = 60 * 60 * 1000;
// https://vite-pwa-org.netlify.app/guide/periodic-sw-updates.html
const registerSW = () =>
viteRegisterSW({
onRegisteredSW(swUrl, registration) {
if (!registration) {
return;
}
setInterval(async () => {
if (registration.installing || navigator?.onLine === false) return;
const resp = await fetch(swUrl, {
cache: "no-store",
headers: {
cache: "no-store",
"cache-control": "no-cache",
},
});
if (resp?.status === 200) await registration.update();
}, intervalMS);
},
});
export default registerSW;

View File

@@ -16,7 +16,8 @@ export default defineConfig(({ mode }) => ({
react(),
VitePWA({
registerType: "autoUpdate",
injectRegister: "inline",
// see registerSW.js imported by index.jsx
injectRegister: null,
strategies: "injectManifest",
devOptions: {
enabled: true,
@@ -25,7 +26,7 @@ export default defineConfig(({ mode }) => ({
navigateFallback: "index.html",
},
injectManifest: {
globPatterns: ["**/*.{js,css,html,mp3,ico,png,svg,json}"],
globPatterns: ["**/*.{js,css,html,ico,png,svg,json}"],
globIgnores: ["config.js"],
manifestTransforms: [
(entries) => ({