diff --git a/docs/releases.md b/docs/releases.md
index 105e7a27..9f37d4cc 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -14,7 +14,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Bugs:**
-* Long-click selecting of notifications doesn't scoll to the top anymore ([#235](https://github.com/binwiederhier/ntfy/issues/235), thanks to [@wunter8](https://github.com/wunter8))
+* Long-click selecting of notifications doesn't scroll to the top anymore ([#235](https://github.com/binwiederhier/ntfy/issues/235), thanks to [@wunter8](https://github.com/wunter8))
* Add attachment and click URL extras to MESSAGE_RECEIVED broadcast ([#329](https://github.com/binwiederhier/ntfy/issues/329), thanks to [@wunter8](https://github.com/wunter8))
* Accessibility: Clear/choose service URL button in base URL dropdown now has a label ([#292](https://github.com/binwiederhier/ntfy/issues/292), thanks to [@mhameed](https://github.com/mhameed) for reporting)
diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json
index 3e7dd780..4d12c072 100644
--- a/web/public/static/langs/en.json
+++ b/web/public/static/langs/en.json
@@ -2,6 +2,7 @@
"action_bar_show_menu": "Show menu",
"action_bar_logo_alt": "ntfy logo",
"action_bar_settings": "Settings",
+ "action_bar_subscription_settings": "Subscription settings",
"action_bar_send_test_notification": "Send test notification",
"action_bar_clear_notifications": "Clear all notifications",
"action_bar_unsubscribe": "Unsubscribe",
@@ -59,6 +60,11 @@
"notifications_no_subscriptions_description": "Click the \"{{linktext}}\" link to create or subscribe to a topic. After that, you can send messages via PUT or POST and you'll receive notifications here.",
"notifications_example": "Example",
"notifications_more_details": "For more information, check out the website or documentation.",
+ "subscription_settings_dialog_title": "Subscription settings",
+ "subscription_settings_dialog_description": "Configure settings specifically for this topic subscription. Settings are currently only applied locally.",
+ "subscription_settings_dialog_display_name_placeholder": "Display name",
+ "subscription_settings_button_cancel": "Cancel",
+ "subscription_settings_button_save": "Save",
"notifications_loading": "Loading notifications …",
"publish_dialog_title_topic": "Publish to {{topic}}",
"publish_dialog_title_no_topic": "Publish notification",
diff --git a/web/src/app/Api.js b/web/src/app/Api.js
index 1e89bc02..a07f7a56 100644
--- a/web/src/app/Api.js
+++ b/web/src/app/Api.js
@@ -1,13 +1,12 @@
import {
- basicAuth,
- encodeBase64,
fetchLinesIterator,
maybeWithBasicAuth,
topicShortUrl,
topicUrl,
topicUrlAuth,
topicUrlJsonPoll,
- topicUrlJsonPollWithSince, userStatsUrl
+ topicUrlJsonPollWithSince,
+ userStatsUrl
} from "./utils";
import userManager from "./UserManager";
diff --git a/web/src/app/Notifier.js b/web/src/app/Notifier.js
index 04cc0cfa..613340cb 100644
--- a/web/src/app/Notifier.js
+++ b/web/src/app/Notifier.js
@@ -1,4 +1,4 @@
-import {formatMessage, formatTitleWithDefault, openUrl, playSound, topicShortUrl} from "./utils";
+import {formatMessage, formatTitleWithDefault, openUrl, playSound, topicDisplayName, topicShortUrl} from "./utils";
import prefs from "./Prefs";
import subscriptionManager from "./SubscriptionManager";
import logo from "../img/ntfy.png";
@@ -18,8 +18,9 @@ class Notifier {
return;
}
const shortUrl = topicShortUrl(subscription.baseUrl, subscription.topic);
+ const displayName = topicDisplayName(subscription);
const message = formatMessage(notification);
- const title = formatTitleWithDefault(notification, shortUrl);
+ const title = formatTitleWithDefault(notification, displayName);
// Show notification
console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`);
diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js
index 01418ee8..f6573489 100644
--- a/web/src/app/SubscriptionManager.js
+++ b/web/src/app/SubscriptionManager.js
@@ -133,6 +133,12 @@ class SubscriptionManager {
});
}
+ async setDisplayName(subscriptionId, displayName) {
+ await db.subscriptions.update(subscriptionId, {
+ displayName: displayName
+ });
+ }
+
async pruneNotifications(thresholdTimestamp) {
await db.notifications
.where("time").below(thresholdTimestamp)
diff --git a/web/src/app/utils.js b/web/src/app/utils.js
index aaf89111..ffc359b5 100644
--- a/web/src/app/utils.js
+++ b/web/src/app/utils.js
@@ -38,6 +38,15 @@ export const disallowedTopic = (topic) => {
return config.disallowedTopics.includes(topic);
}
+export const topicDisplayName = (subscription) => {
+ if (subscription.displayName) {
+ return subscription.displayName;
+ } else if (subscription.baseUrl === window.location.origin) {
+ return subscription.topic;
+ }
+ return topicShortUrl(subscription.baseUrl, subscription.topic);
+};
+
// Format emojis (see emoji.js)
const emojis = {};
rawEmojis.forEach(emoji => {
diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js
index 30ab271e..e284f922 100644
--- a/web/src/components/ActionBar.js
+++ b/web/src/components/ActionBar.js
@@ -7,7 +7,7 @@ import Typography from "@mui/material/Typography";
import * as React from "react";
import {useEffect, useRef, useState} from "react";
import Box from "@mui/material/Box";
-import {formatShortDateTime, shuffle, topicShortUrl} from "../app/utils";
+import {formatShortDateTime, shuffle, topicDisplayName, topicShortUrl} from "../app/utils";
import {useLocation, useNavigate} from "react-router-dom";
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Grow from '@mui/material/Grow';
@@ -24,13 +24,14 @@ import subscriptionManager from "../app/SubscriptionManager";
import logo from "../img/ntfy.svg";
import {useTranslation} from "react-i18next";
import {Portal, Snackbar} from "@mui/material";
+import SubscriptionSettingsDialog from "./SubscriptionSettingsDialog";
const ActionBar = (props) => {
const { t } = useTranslation();
const location = useLocation();
let title = "ntfy";
if (props.selected) {
- title = topicShortUrl(props.selected.baseUrl, props.selected.topic);
+ title = topicDisplayName(props.selected);
} else if (location.pathname === "/settings") {
title = t("action_bar_settings");
}
@@ -79,6 +80,7 @@ const SettingsIcons = (props) => {
const navigate = useNavigate();
const [open, setOpen] = useState(false);
const [snackOpen, setSnackOpen] = useState(false);
+ const [subscriptionSettingsOpen, setSubscriptionSettingsOpen] = useState(false);
const anchorRef = useRef(null);
const subscription = props.subscription;
@@ -116,6 +118,10 @@ const SettingsIcons = (props) => {
}
};
+ const handleSubscriptionSettings = async () => {
+ setSubscriptionSettingsOpen(true);
+ }
+
const handleSendTestMessage = async () => {
const baseUrl = props.subscription.baseUrl;
const topic = props.subscription.topic;
@@ -201,6 +207,7 @@ const SettingsIcons = (props) => {
+
@@ -218,6 +225,14 @@ const SettingsIcons = (props) => {
message={t("message_bar_error_publishing")}
/>
+
+ setSubscriptionSettingsOpen(false)}
+ />
+
>
);
};
diff --git a/web/src/components/Navigation.js b/web/src/components/Navigation.js
index af67cb9f..694da59e 100644
--- a/web/src/components/Navigation.js
+++ b/web/src/components/Navigation.js
@@ -14,7 +14,7 @@ import SubscribeDialog from "./SubscribeDialog";
import {Alert, AlertTitle, Badge, CircularProgress, Link, ListSubheader} from "@mui/material";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";
-import {openUrl, topicShortUrl, topicUrl} from "../app/utils";
+import {openUrl, topicDisplayName, topicUrl} from "../app/utils";
import routes from "./routes";
import {ConnectionState} from "../app/Connection";
import {useLocation, useNavigate} from "react-router-dom";
@@ -173,12 +173,10 @@ const SubscriptionItem = (props) => {
const icon = (subscription.state === ConnectionState.Connecting)
?
: ;
- const label = (subscription.baseUrl === window.location.origin)
- ? subscription.topic
- : topicShortUrl(subscription.baseUrl, subscription.topic);
+ const displayName = topicDisplayName(subscription);
const ariaLabel = (subscription.state === ConnectionState.Connecting)
- ? `${label} (${t("nav_button_connecting")})`
- : label;
+ ? `${displayName} (${t("nav_button_connecting")})`
+ : displayName;
const handleClick = async () => {
navigate(routes.forSubscription(subscription));
await subscriptionManager.markNotificationsRead(subscription.id);
@@ -186,7 +184,7 @@ const SubscriptionItem = (props) => {
return (
{icon}
-
+
{subscription.mutedUntil > 0 &&
}
diff --git a/web/src/components/SubscriptionSettingsDialog.js b/web/src/components/SubscriptionSettingsDialog.js
new file mode 100644
index 00000000..a31fe0fd
--- /dev/null
+++ b/web/src/components/SubscriptionSettingsDialog.js
@@ -0,0 +1,59 @@
+import * as React from 'react';
+import {useState} from 'react';
+import Button from '@mui/material/Button';
+import TextField from '@mui/material/TextField';
+import Dialog from '@mui/material/Dialog';
+import DialogContent from '@mui/material/DialogContent';
+import DialogContentText from '@mui/material/DialogContentText';
+import DialogTitle from '@mui/material/DialogTitle';
+import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/material";
+import theme from "./theme";
+import api from "../app/Api";
+import {topicUrl, validTopic, validUrl} from "../app/utils";
+import userManager from "../app/UserManager";
+import subscriptionManager from "../app/SubscriptionManager";
+import poller from "../app/Poller";
+import DialogFooter from "./DialogFooter";
+import {useTranslation} from "react-i18next";
+
+const SubscriptionSettingsDialog = (props) => {
+ const { t } = useTranslation();
+ const subscription = props.subscription;
+ const [displayName, setDisplayName] = useState(subscription.displayName ?? "");
+ const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
+ const handleSave = async () => {
+ await subscriptionManager.setDisplayName(subscription.id, displayName);
+ props.onClose();
+ }
+ return (
+
+ );
+};
+
+export default SubscriptionSettingsDialog;