diff --git a/docs/static/css/extra.css b/docs/static/css/extra.css index 20b339c..41694c2 100644 --- a/docs/static/css/extra.css +++ b/docs/static/css/extra.css @@ -1,7 +1,7 @@ :root { - --md-primary-fg-color: #3a9784; - --md-primary-fg-color--light: #3a9784; - --md-primary-fg-color--dark: #3a9784; + --md-primary-fg-color: #338574; + --md-primary-fg-color--light: #338574; + --md-primary-fg-color--dark: #338574; } .md-header__button.md-logo :is(img, svg) { diff --git a/server/server.go b/server/server.go index 181f060..ddbdb9c 100644 --- a/server/server.go +++ b/server/server.go @@ -881,8 +881,8 @@ func parseSince(r *http.Request, poll bool) (sinceMarker, error) { func (s *Server) handleOptions(w http.ResponseWriter, _ *http.Request) error { w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST") - w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests - w.Header().Set("Access-Control-Allow-Headers", "Authorization") // CORS, allow auth via JS + w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests + w.Header().Set("Access-Control-Allow-Headers", "*") // CORS, allow auth via JS // FIXME is this terrible? return nil } diff --git a/web/src/app/Api.js b/web/src/app/Api.js index ae14c71..096f375 100644 --- a/web/src/app/Api.js +++ b/web/src/app/Api.js @@ -26,14 +26,24 @@ class Api { return messages; } - async publish(baseUrl, topic, message) { + async publish(baseUrl, topic, message, title, priority, tags) { const user = await userManager.get(baseUrl); const url = topicUrl(baseUrl, topic); console.log(`[Api] Publishing message to ${url}`); + const headers = {}; + if (title) { + headers["X-Title"] = title; + } + if (priority !== 3) { + headers["X-Priority"] = `${priority}`; + } + if (tags.length > 0) { + headers["X-Tags"] = tags.join(","); + } await fetch(url, { method: 'PUT', body: message, - headers: maybeWithBasicAuth({}, user) + headers: maybeWithBasicAuth(headers, user) }); } diff --git a/web/src/app/utils.js b/web/src/app/utils.js index c7aabaf..5e3644a 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -104,6 +104,17 @@ export const encodeBase64Url = (s) => { return Base64.encodeURI(s); } +export const shuffle = (arr) => { + let j, x; + for (let index = arr.length - 1; index > 0; index--) { + j = Math.floor(Math.random() * (index + 1)); + x = arr[index]; + arr[index] = arr[j]; + arr[j] = x; + } + return arr; +} + // https://jameshfisher.com/2017/10/30/web-cryptography-api-hello-world/ export const sha256 = async (s) => { const buf = await crypto.subtle.digest("SHA-256", new TextEncoder("utf-8").encode(s)); diff --git a/web/src/components/ActionBar.js b/web/src/components/ActionBar.js index 6a0820f..f997209 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 {topicShortUrl} from "../app/utils"; +import {formatShortDateTime, shuffle, topicShortUrl} from "../app/utils"; import {useLocation, useNavigate} from "react-router-dom"; import ClickAwayListener from '@mui/material/ClickAwayListener'; import Grow from '@mui/material/Grow'; @@ -108,8 +108,31 @@ const SettingsIcons = (props) => { const handleSendTestMessage = () => { const baseUrl = props.subscription.baseUrl; const topic = props.subscription.topic; - api.publish(baseUrl, topic, - `This is a test notification sent by the ntfy Web UI at ${new Date().toString()}.`); // FIXME result ignored + const tags = shuffle([ + "grinning", "octopus", "upside_down_face", "palm_tree", "maple_leaf", "apple", "skull", "warning", "jack_o_lantern", + "de-server-1", "backups", "cron-script", "script-error", "phils-automation", "mouse", "go-rocks", "hi-ben"]) + .slice(0, Math.round(Math.random() * 4)); + const priority = shuffle([1, 2, 3, 4, 5])[0]; + const title = shuffle([ + "", + "", + "", // Higher chance of no title + "Oh my, another test message?", + "Titles are optional, did you know that?", + "ntfy is open source, and will always be free. Cool, right?", + "I don't really like apples", + "My favorite TV show is The Wire. You should watch it!" + ])[0]; + const message = shuffle([ + `Hello friend, this is a test notification from ntfy web. It's ${formatShortDateTime(Date.now())} right now. Is that early or late?`, + `So I heard you like ntfy? If that's true, go to GitHub and star it, or to the Play store and rate it. Thanks! Oh yeah, this is a test notification.`, + `It's almost like you want to hear what I have to say. I'm not even a machine. I'm just a sentence that Phil typed on a random Thursday.`, + `Alright then, it's ${formatShortDateTime(Date.now())} already. Boy oh boy, where did the time go? I hope you're alright, friend.`, + `There are nine million bicycles in Beijing That's a fact; It's a thing we can't deny. I wonder if that's true ...`, + `I'm really excited that you're trying out ntfy. Did you know that there are a few public topics, such as ntfy.sh/stats and ntfy.sh/annoucements.`, + `It's interesting to hear what people use ntfy for. I've heard people talk about using it for so many cool things. What do you use it for?` + ])[0]; + api.publish(baseUrl, topic, message, title, priority, tags); setOpen(false); } diff --git a/web/src/components/App.js b/web/src/components/App.js index fc58f16..1f6e3f2 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -20,7 +20,6 @@ import ErrorBoundary from "./ErrorBoundary"; import routes from "./routes"; import {useAutoSubscribe, useConnectionListeners} from "./hooks"; -// TODO better "send test message" (a la android app) // TODO docs // TODO screenshot on homepage // TODO "copy url" toast diff --git a/web/src/components/Navigation.js b/web/src/components/Navigation.js index fce88d6..2f6a932 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, ListSubheader} from "@mui/material"; import Button from "@mui/material/Button"; import Typography from "@mui/material/Typography"; -import {topicShortUrl, topicUrl} from "../app/utils"; +import {openUrl, topicShortUrl, topicUrl} from "../app/utils"; import routes from "./routes"; import {ConnectionState} from "../app/Connection"; import {useLocation, useNavigate} from "react-router-dom"; @@ -23,6 +23,7 @@ import {ChatBubble, NotificationsOffOutlined} from "@mui/icons-material"; import Box from "@mui/material/Box"; import notifier from "../app/Notifier"; import config from "../app/config"; +import ArticleIcon from '@mui/icons-material/Article'; const navWidth = 280; @@ -113,6 +114,10 @@ const NavList = (props) => { + openUrl("/docs")}> + + + setSubscribeDialogOpen(true)}>