소스 검색

Support external routes

Philipp Heckel 4 년 전
부모
커밋
52a55f71e6

+ 3 - 0
web/public/config.js

@@ -0,0 +1,3 @@
+var config = {
+    defaultBaseUrl: 'https://ntfy.sh'
+};

+ 6 - 4
web/public/index.html

@@ -2,7 +2,6 @@
 <html lang="en">
 <head>
   <meta charset="UTF-8">
-
   <title>ntfy web</title>
 
   <!-- Mobile view -->
@@ -24,11 +23,14 @@
   <meta property="og:site_name" content="ntfy.sh" />
   <meta property="og:title" content="ntfy.sh | Send push notifications to your phone or desktop via PUT/POST" />
   <meta property="og:description" content="ntfy is a simple HTTP-based pub-sub notification service. It allows you to send desktop notifications via scripts from any computer, entirely without signup or cost. Made with ❤ by Philipp C. Heckel, Apache License 2.0, source at https://heckel.io/ntfy." />
-  <meta property="og:image" content="/static/img/ntfy.png" />
+  <meta property="og:image" content="%PUBLIC_URL%/static/img/ntfy.png" />
   <meta property="og:url" content="https://ntfy.sh" />
 
-  <!-- FIXME Never index topic page -->
-  <!-- <meta name="robots" content="noindex, nofollow" /> -->
+  <!-- Never index -->
+  <meta name="robots" content="noindex, nofollow" />
+
+  <!-- Server configuration -->
+  <script src="%PUBLIC_URL%/config.js"></script>
 
   <!-- FIXME Roboto -->
   <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />

+ 2 - 0
web/src/app/config.js

@@ -0,0 +1,2 @@
+const config = window.config;
+export default config;

+ 4 - 0
web/src/app/utils.js

@@ -1,4 +1,5 @@
 import {rawEmojis} from "./emojis";
+import config from "./config";
 
 export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
 export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`
@@ -115,6 +116,9 @@ export const openUrl = (url) => {
 };
 
 export const subscriptionRoute = (subscription) => {
+    if (subscription.baseUrl !== config.defaultBaseUrl) {
+        return `/${shortUrl(subscription.baseUrl)}/${subscription.topic}`;
+    }
     return `/${subscription.topic}`;
 }
 

+ 1 - 1
web/src/components/ActionBar.js

@@ -33,7 +33,7 @@ const ActionBar = (props) => {
                 >
                     <MenuIcon />
                 </IconButton>
-                <Box component="img" src="static/img/ntfy.svg" sx={{
+                <Box component="img" src="/static/img/ntfy.svg" sx={{
                     display: { xs: 'none', sm: 'block' },
                     marginRight: '10px',
                     height: '28px'

+ 16 - 4
web/src/components/App.js

@@ -20,9 +20,11 @@ import userManager from "../app/UserManager";
 import {BrowserRouter, Route, Routes, useLocation, useNavigate} from "react-router-dom";
 import {subscriptionRoute} from "../app/utils";
 
-// TODO make default server functional
+// TODO support unsubscribed routes
 // TODO embed into ntfy server
+// TODO googlefonts
 // TODO new notification indicator
+// TODO sound
 
 const App = () => {
     return (
@@ -42,7 +44,7 @@ const Root = () => {
     const location = useLocation();
     const users = useLiveQuery(() => userManager.all());
     const subscriptions = useLiveQuery(() => subscriptionManager.all());
-    const [selectedSubscription] = (subscriptions && location) ? subscriptions.filter(s => location.pathname === subscriptionRoute(s)) : [];
+    const selectedSubscription = findSelected(location, subscriptions);
 
     const handleSubscriptionClick = async (subscriptionId) => {
         const subscription = await subscriptionManager.get(subscriptionId);
@@ -74,7 +76,7 @@ const Root = () => {
             try {
                 const added = await subscriptionManager.addNotification(subscriptionId, notification);
                 if (added) {
-                    const defaultClickAction = (subscription) => navigate(subscriptionRoute(subscription));
+                    const defaultClickAction = (subscription) => navigate(subscriptionRoute(subscription)); // FIXME
                     await notificationManager.notify(subscriptionId, notification, defaultClickAction)
                 }
             } catch (e) {
@@ -115,7 +117,8 @@ const Root = () => {
                 <Routes>
                     <Route path="/" element={<NoTopics />} />
                     <Route path="settings" element={<Preferences />} />
-                    <Route path=":topic" element={<Notifications subscriptions={subscriptions}/>} />
+                    <Route path=":baseUrl/:topic" element={<Notifications subscription={selectedSubscription}/>} />
+                    <Route path=":topic" element={<Notifications subscription={selectedSubscription}/>} />
                 </Routes>
             </Main>
         </Box>
@@ -142,4 +145,13 @@ const Main = (props) => {
     );
 };
 
+const findSelected = (location, subscriptions) => {
+    if (!subscriptions || !location)  {
+        return null;
+    }
+    const [subscription] = subscriptions
+        .filter(s => location.pathname === subscriptionRoute(s));
+    return subscription;
+};
+
 export default App;

+ 10 - 3
web/src/components/Navigation.js

@@ -14,9 +14,10 @@ import SubscribeDialog from "./SubscribeDialog";
 import {Alert, AlertTitle, CircularProgress, ListSubheader} from "@mui/material";
 import Button from "@mui/material/Button";
 import Typography from "@mui/material/Typography";
-import {subscriptionRoute, topicShortUrl} from "../app/utils";
+import {subscriptionRoute, topicShortUrl, topicUrl} from "../app/utils";
 import {ConnectionState} from "../app/Connection";
 import {useLocation, useNavigate} from "react-router-dom";
+import config from "../app/config";
 
 const navWidth = 240;
 
@@ -103,9 +104,12 @@ const NavList = (props) => {
 };
 
 const SubscriptionList = (props) => {
+    const sortedSubscriptions = props.subscriptions.sort( (a, b) => {
+        return (topicUrl(a.baseUrl, a.topic) < topicUrl(b.baseUrl, b.topic)) ? -1 : 1;
+    });
     return (
         <>
-            {props.subscriptions.map(subscription =>
+            {sortedSubscriptions.map(subscription =>
                 <SubscriptionItem
                     key={subscription.id}
                     subscription={subscription}
@@ -121,10 +125,13 @@ const SubscriptionItem = (props) => {
     const icon = (subscription.state === ConnectionState.Connecting)
         ? <CircularProgress size="24px"/>
         : <ChatBubbleOutlineIcon/>;
+    const label = (subscription.baseUrl === config.defaultBaseUrl)
+        ? subscription.topic
+        : topicShortUrl(subscription.baseUrl, subscription.topic);
     return (
         <ListItemButton onClick={() => navigate(subscriptionRoute(subscription))} selected={props.selected}>
             <ListItemIcon>{icon}</ListItemIcon>
-            <ListItemText primary={topicShortUrl(subscription.baseUrl, subscription.topic)}/>
+            <ListItemText primary={label}/>
         </ListItemButton>
     );
 };

+ 3 - 8
web/src/components/Notifications.js

@@ -20,19 +20,14 @@ import {useLiveQuery} from "dexie-react-hooks";
 import Box from "@mui/material/Box";
 import Button from "@mui/material/Button";
 import subscriptionManager from "../app/SubscriptionManager";
-import { useParams } from "react-router-dom";
 
 const Notifications = (props) => {
-    const params = useParams();
-    if (!props.subscriptions) {
-        return null;
-    }
-    const [subscription] = props.subscriptions.filter(s => s.topic === params.topic);
+    const subscription = props.subscription;
     if (!subscription) {
-        return null; // FIXME
+        return null;
     }
     return <NotificationList subscription={subscription}/>;
-};
+}
 
 const NotificationList = (props) => {
     const subscription = props.subscription;

+ 0 - 24
web/src/components/Preferences.js

@@ -37,7 +37,6 @@ const Preferences = () => {
         <Container maxWidth="md" sx={{marginTop: 3, marginBottom: 3}}>
             <Stack spacing={3}>
                 <Notifications/>
-                <DefaultServer/>
                 <Users/>
             </Stack>
         </Container>
@@ -140,29 +139,6 @@ const Pref = (props) => {
     );
 };
 
-const DefaultServer = (props) => {
-    return (
-        <Card sx={{ padding: 1 }}>
-            <CardContent>
-                <Typography variant="h5">
-                    Default server
-                </Typography>
-                <Paragraph>
-                    This server is used as a default when adding new topics.
-                </Paragraph>
-                <TextField
-                    margin="dense"
-                    id="defaultBaseUrl"
-                    placeholder="https://ntfy.sh"
-                    type="text"
-                    fullWidth
-                    variant="standard"
-                />
-            </CardContent>
-        </Card>
-    );
-};
-
 const Users = () => {
     const [dialogKey, setDialogKey] = useState(0);
     const [dialogOpen, setDialogOpen] = useState(false);

+ 7 - 8
web/src/components/SubscribeDialog.js

@@ -10,6 +10,7 @@ import DialogTitle from '@mui/material/DialogTitle';
 import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/material";
 import theme from "./theme";
 import api from "../app/Api";
+import config from "../app/config";
 import {topicUrl, validTopic, validUrl} from "../app/utils";
 import Box from "@mui/material/Box";
 import userManager from "../app/UserManager";
@@ -17,8 +18,6 @@ import subscriptionManager from "../app/SubscriptionManager";
 import poller from "../app/Poller";
 
 const publicBaseUrl = "https://ntfy.sh"
-const defaultBaseUrl = "http://127.0.0.1"
-//const defaultBaseUrl = "https://ntfy.sh"
 
 const SubscribeDialog = (props) => {
     const [baseUrl, setBaseUrl] = useState("");
@@ -26,7 +25,7 @@ const SubscribeDialog = (props) => {
     const [showLoginPage, setShowLoginPage] = useState(false);
     const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
     const handleSuccess = async () => {
-        const actualBaseUrl = (baseUrl) ? baseUrl : defaultBaseUrl; // FIXME
+        const actualBaseUrl = (baseUrl) ? baseUrl : config.defaultBaseUrl;
         const subscription = {
             id: topicUrl(actualBaseUrl, topic),
             baseUrl: actualBaseUrl,
@@ -62,11 +61,11 @@ const SubscribeDialog = (props) => {
 const SubscribePage = (props) => {
     const [anotherServerVisible, setAnotherServerVisible] = useState(false);
     const [errorText, setErrorText] = useState("");
-    const baseUrl = (anotherServerVisible) ? props.baseUrl : defaultBaseUrl;
+    const baseUrl = (anotherServerVisible) ? props.baseUrl : config.defaultBaseUrl;
     const topic = props.topic;
     const existingTopicUrls = props.subscriptions.map(s => topicUrl(s.baseUrl, s.topic));
     const existingBaseUrls = Array.from(new Set([publicBaseUrl, ...props.subscriptions.map(s => s.baseUrl)]))
-        .filter(s => s !== defaultBaseUrl);
+        .filter(s => s !== config.defaultBaseUrl);
     const handleSubscribe = async () => {
         const user = await userManager.get(baseUrl); // May be undefined
         const username = (user) ? user.username : "anonymous";
@@ -93,7 +92,7 @@ const SubscribePage = (props) => {
             const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(baseUrl, topic));
             return validTopic(topic) && validUrl(baseUrl) && !isExistingTopicUrl;
         } else {
-            const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(defaultBaseUrl, topic)); // FIXME
+            const isExistingTopicUrl = existingTopicUrls.includes(topicUrl(config.defaultBaseUrl, topic)); // FIXME
             return validTopic(topic) && !isExistingTopicUrl;
         }
     })();
@@ -127,7 +126,7 @@ const SubscribePage = (props) => {
                     inputValue={props.baseUrl}
                     onInputChange={(ev, newVal) => props.setBaseUrl(newVal)}
                     renderInput={ (params) =>
-                        <TextField {...params} placeholder={defaultBaseUrl} variant="standard"/>
+                        <TextField {...params} placeholder={config.defaultBaseUrl} variant="standard"/>
                     }
                 />}
             </DialogContent>
@@ -143,7 +142,7 @@ const LoginPage = (props) => {
     const [username, setUsername] = useState("");
     const [password, setPassword] = useState("");
     const [errorText, setErrorText] = useState("");
-    const baseUrl = (props.baseUrl) ? props.baseUrl : defaultBaseUrl;
+    const baseUrl = (props.baseUrl) ? props.baseUrl : config.defaultBaseUrl;
     const topic = props.topic;
     const handleLogin = async () => {
         const user = {baseUrl, username, password};