mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-11-21 10:53:25 +01:00
Styling
This commit is contained in:
parent
39574c954b
commit
317621c696
10 changed files with 252 additions and 135 deletions
BIN
assets/favicon.xcf
Normal file
BIN
assets/favicon.xcf
Normal file
Binary file not shown.
|
@ -1,6 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
<title>ntfy.sh: EventSource Example</title>
|
<title>ntfy.sh: EventSource Example</title>
|
||||||
<style>
|
<style>
|
||||||
body { font-size: 1.2em; line-height: 130%; }
|
body { font-size: 1.2em; line-height: 130%; }
|
||||||
|
|
|
@ -1,19 +1,39 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>ntfy.sh</title>
|
<meta charset="UTF-8">
|
||||||
<style>
|
|
||||||
body { font-size: 1.2em; line-height: 130%; }
|
<title>ntfy.sh | simple HTTP-based pub-sub</title>
|
||||||
#error { color: darkred; font-style: italic; }
|
<link rel="stylesheet" href="static/css/app.css" type="text/css">
|
||||||
#main { max-width: 900px; margin: 0 auto 50px auto; }
|
|
||||||
</style>
|
<!-- Mobile view -->
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="HandheldFriendly" content="true">
|
||||||
|
|
||||||
|
<!-- Mobile browsers, background color -->
|
||||||
|
<meta name="theme-color" content="#004c79">
|
||||||
|
<meta name="msapplication-navbutton-color" content="#004c79">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="#004c79">
|
||||||
|
|
||||||
|
<!-- Favicon, see favicon.io -->
|
||||||
|
<link rel="icon" type="image/png" href="static/img/favicon.png">
|
||||||
|
|
||||||
|
<!-- Previews in Google, Slack, WhatsApp, etc. -->
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:locale" content="en_US" />
|
||||||
|
<meta property="og:site_name" content="ntfy.sh" />
|
||||||
|
<meta property="og:title" content="ntfy.sh | simple HTTP-based pub-sub" />
|
||||||
|
<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:url" content="https://ntfy.sh" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<h1>ntfy.sh - simple HTTP-based pub-sub</h1>
|
<h1>ntfy.sh - simple HTTP-based pub-sub</h1>
|
||||||
<p>
|
<p>
|
||||||
<b>ntfy</b> (pronounce: <i>notify</i>) is a simple <b>HTTP-based pub-sub notification service and tool</b>.
|
<b>ntfy</b> (pronounce: <i>notify</i>) is a simple HTTP-based pub-sub notification service and tool.
|
||||||
It allows you to send <b>desktop notifications via scripts</b>, entirely <b>without signup or cost</b>.
|
It allows you to send <b>desktop notifications via scripts from any computer</b>, entirely <b>without signup or cost</b>.
|
||||||
It's also <a href="https://github.com/binwiederhier/ntfy">open source</a> if you want to run your own.
|
It's also <a href="https://github.com/binwiederhier/ntfy">open source</a> if you want to run your own.
|
||||||
</p>
|
</p>
|
||||||
<p id="error"></p>
|
<p id="error"></p>
|
||||||
|
@ -37,151 +57,31 @@
|
||||||
<p>
|
<p>
|
||||||
<label for="topicField">Topic ID:</label>
|
<label for="topicField">Topic ID:</label>
|
||||||
<input type="text" id="topicField" placeholder="Letters, numbers, _ and -" pattern="[-_A-Za-z]{1,64}" autofocus />
|
<input type="text" id="topicField" placeholder="Letters, numbers, _ and -" pattern="[-_A-Za-z]{1,64}" autofocus />
|
||||||
<input type="submit" id="subscribeButton" value="Subscribe topic" />
|
<input type="submit" id="subscribeButton" value="Subscribe" />
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
<p id="topicsHeader">Subscribed topics:</p>
|
<p id="topicsHeader">Subscribed topics:</p>
|
||||||
<ul id="topicsList"></ul>
|
<ul id="topicsList"></ul>
|
||||||
|
|
||||||
<h3>Subscribe via your app, or via the CLI</h3>
|
<h3>Subscribe via your app, or via the CLI</h3>
|
||||||
<tt>
|
<code>
|
||||||
curl -s ntfy.sh/mytopic/raw # one message per line (\n are replaced with a space)<br/>
|
curl -s ntfy.sh/mytopic/raw # one message per line (\n are replaced with a space)<br/>
|
||||||
curl -s ntfy.sh/mytopic/json # one JSON message per line<br/>
|
curl -s ntfy.sh/mytopic/json # one JSON message per line<br/>
|
||||||
curl -s ntfy.sh/mytopic/sse # server-sent events (SSE) stream
|
curl -s ntfy.sh/mytopic/sse # server-sent events (SSE) stream
|
||||||
</tt>
|
</code>
|
||||||
|
|
||||||
<h3>Publishing messages</h3>
|
<h2>Publishing messages</h2>
|
||||||
<p>
|
<p>
|
||||||
Publishing messages can be done via PUT or POST using. Here's an example using <tt>curl</tt>:
|
Publishing messages can be done via PUT or POST using. Here's an example using <tt>curl</tt>:
|
||||||
</p>
|
</p>
|
||||||
<tt>
|
<code>
|
||||||
curl -d "long process is done" ntfy.sh/mytopic
|
curl -d "long process is done" ntfy.sh/mytopic
|
||||||
</tt>
|
</code>
|
||||||
<p>
|
<p>
|
||||||
Messages published to a non-existing topic or a topic without subscribers will not be delivered later.
|
Messages published to a non-existing topic or a topic without subscribers will not be delivered later.
|
||||||
There is (currently) no buffering of any kind. If you're not listening, the message won't be delivered.
|
There is (currently) no buffering of any kind. If you're not listening, the message won't be delivered.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="static/js/app.js"></script>
|
||||||
<script type="text/javascript">
|
|
||||||
let topics = {};
|
|
||||||
|
|
||||||
const topicsHeader = document.getElementById("topicsHeader");
|
|
||||||
const topicsList = document.getElementById("topicsList");
|
|
||||||
const topicField = document.getElementById("topicField");
|
|
||||||
const subscribeButton = document.getElementById("subscribeButton");
|
|
||||||
const subscribeForm = document.getElementById("subscribeForm");
|
|
||||||
const errorField = document.getElementById("error");
|
|
||||||
|
|
||||||
const subscribe = (topic) => {
|
|
||||||
if (Notification.permission !== "granted") {
|
|
||||||
Notification.requestPermission().then((permission) => {
|
|
||||||
if (permission === "granted") {
|
|
||||||
subscribeInternal(topic, 0);
|
|
||||||
} else {
|
|
||||||
showNotificationDeniedError();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
subscribeInternal(topic, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const subscribeInternal = (topic, delaySec) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
// Render list entry
|
|
||||||
let topicEntry = document.getElementById(`topic-${topic}`);
|
|
||||||
if (!topicEntry) {
|
|
||||||
topicEntry = document.createElement('li');
|
|
||||||
topicEntry.id = `topic-${topic}`;
|
|
||||||
topicEntry.innerHTML = `${topic} <button onclick="test('${topic}')">Test</button> <button onclick="unsubscribe('${topic}')">Unsubscribe</button>`;
|
|
||||||
topicsList.appendChild(topicEntry);
|
|
||||||
}
|
|
||||||
topicsHeader.style.display = '';
|
|
||||||
|
|
||||||
// Open event source
|
|
||||||
let eventSource = new EventSource(`${topic}/sse`);
|
|
||||||
eventSource.onopen = () => {
|
|
||||||
topicEntry.innerHTML = `${topic} <button onclick="test('${topic}')">Test</button> <button onclick="unsubscribe('${topic}')">Unsubscribe</button>`;
|
|
||||||
delaySec = 0; // Reset on successful connection
|
|
||||||
};
|
|
||||||
eventSource.onerror = (e) => {
|
|
||||||
const newDelaySec = (delaySec + 5 <= 15) ? delaySec + 5 : 15;
|
|
||||||
topicEntry.innerHTML = `${topic} <i>(Reconnecting in ${newDelaySec}s ...)</i> <button disabled="disabled">Test</button> <button onclick="unsubscribe('${topic}')">Unsubscribe</button>`;
|
|
||||||
eventSource.close()
|
|
||||||
subscribeInternal(topic, newDelaySec);
|
|
||||||
};
|
|
||||||
eventSource.onmessage = (e) => {
|
|
||||||
const event = JSON.parse(e.data);
|
|
||||||
new Notification(event.message);
|
|
||||||
};
|
|
||||||
topics[topic] = eventSource;
|
|
||||||
localStorage.setItem('topics', JSON.stringify(Object.keys(topics)));
|
|
||||||
}, delaySec * 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const unsubscribe = (topic) => {
|
|
||||||
topics[topic].close();
|
|
||||||
delete topics[topic];
|
|
||||||
localStorage.setItem('topics', JSON.stringify(Object.keys(topics)));
|
|
||||||
document.getElementById(`topic-${topic}`).remove();
|
|
||||||
if (Object.keys(topics).length === 0) {
|
|
||||||
topicsHeader.style.display = 'none';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const test = (topic) => {
|
|
||||||
fetch(`/${topic}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
body: `This is a test notification for topic ${topic}!`
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
const showError = (msg) => {
|
|
||||||
errorField.innerHTML = msg;
|
|
||||||
topicField.disabled = true;
|
|
||||||
subscribeButton.disabled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const showBrowserIncompatibleError = () => {
|
|
||||||
showError("Your browser is not compatible to use the web-based desktop notifications.");
|
|
||||||
};
|
|
||||||
|
|
||||||
const showNotificationDeniedError = () => {
|
|
||||||
showError("You have blocked desktop notifications for this website. Please unblock them and refresh to use the web-based desktop notifications.");
|
|
||||||
};
|
|
||||||
|
|
||||||
subscribeForm.onsubmit = function () {
|
|
||||||
if (!topicField.value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
subscribe(topicField.value);
|
|
||||||
topicField.value = "";
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Disable Web UI if notifications of EventSource are not available
|
|
||||||
if (!window["Notification"] || !window["EventSource"]) {
|
|
||||||
showBrowserIncompatibleError();
|
|
||||||
} else if (Notification.permission === "denied") {
|
|
||||||
showNotificationDeniedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset UI
|
|
||||||
topicField.value = "";
|
|
||||||
|
|
||||||
// Restore topics
|
|
||||||
const storedTopics = localStorage.getItem('topics');
|
|
||||||
if (storedTopics && Notification.permission === "granted") {
|
|
||||||
const storedTopicsArray = JSON.parse(storedTopics)
|
|
||||||
storedTopicsArray.forEach((topic) => { subscribeInternal(topic, 0); });
|
|
||||||
if (storedTopicsArray.length === 0) {
|
|
||||||
topicsHeader.style.display = 'none';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
topicsHeader.style.display = 'none';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"embed"
|
||||||
_ "embed" // required for go:embed
|
_ "embed" // required for go:embed
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -51,10 +52,14 @@ var (
|
||||||
jsonRegex = regexp.MustCompile(`^/[^/]+/json$`)
|
jsonRegex = regexp.MustCompile(`^/[^/]+/json$`)
|
||||||
sseRegex = regexp.MustCompile(`^/[^/]+/sse$`)
|
sseRegex = regexp.MustCompile(`^/[^/]+/sse$`)
|
||||||
rawRegex = regexp.MustCompile(`^/[^/]+/raw$`)
|
rawRegex = regexp.MustCompile(`^/[^/]+/raw$`)
|
||||||
|
staticRegex = regexp.MustCompile(`^/static/.+`)
|
||||||
|
|
||||||
//go:embed "index.html"
|
//go:embed "index.html"
|
||||||
indexSource string
|
indexSource string
|
||||||
|
|
||||||
|
//go:embed static
|
||||||
|
webStaticFs embed.FS
|
||||||
|
|
||||||
errHTTPNotFound = &errHTTP{http.StatusNotFound, http.StatusText(http.StatusNotFound)}
|
errHTTPNotFound = &errHTTP{http.StatusNotFound, http.StatusText(http.StatusNotFound)}
|
||||||
errHTTPTooManyRequests = &errHTTP{http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests)}
|
errHTTPTooManyRequests = &errHTTP{http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests)}
|
||||||
)
|
)
|
||||||
|
@ -123,6 +128,8 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
if r.Method == http.MethodGet && r.URL.Path == "/" {
|
if r.Method == http.MethodGet && r.URL.Path == "/" {
|
||||||
return s.handleHome(w, r)
|
return s.handleHome(w, r)
|
||||||
|
} else if r.Method == http.MethodGet && staticRegex.MatchString(r.URL.Path) {
|
||||||
|
return s.handleStatic(w, r)
|
||||||
} else if r.Method == http.MethodGet && jsonRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && jsonRegex.MatchString(r.URL.Path) {
|
||||||
return s.handleSubscribeJSON(w, r)
|
return s.handleSubscribeJSON(w, r)
|
||||||
} else if r.Method == http.MethodGet && sseRegex.MatchString(r.URL.Path) {
|
} else if r.Method == http.MethodGet && sseRegex.MatchString(r.URL.Path) {
|
||||||
|
@ -241,6 +248,11 @@ func (s *Server) handleOptions(w http.ResponseWriter, r *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
http.FileServer(http.FS(webStaticFs)).ServeHTTP(w, r)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) createTopic(id string) *topic {
|
func (s *Server) createTopic(id string) *topic {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
76
server/static/css/app.css
Normal file
76
server/static/css/app.css
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/* general styling */
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
font-family: 'Lato', sans-serif;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #39005a;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 25px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 1.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt {
|
||||||
|
background: #eee;
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: block;
|
||||||
|
background: #eee;
|
||||||
|
font-family: monospace;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lato font (OFL), https://fonts.google.com/specimen/Lato#about,
|
||||||
|
embedded with the help of https://google-webfonts-helper.herokuapp.com/fonts/lato?subsets=latin */
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local(''),
|
||||||
|
url('../font/lato-v17-latin-ext_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||||
|
url('../font/lato-v17-latin-ext_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main page */
|
||||||
|
|
||||||
|
#main {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto 50px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#error {
|
||||||
|
color: darkred;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
BIN
server/static/font/lato-v17-latin-ext_latin-regular.woff
Normal file
BIN
server/static/font/lato-v17-latin-ext_latin-regular.woff
Normal file
Binary file not shown.
BIN
server/static/font/lato-v17-latin-ext_latin-regular.woff2
Normal file
BIN
server/static/font/lato-v17-latin-ext_latin-regular.woff2
Normal file
Binary file not shown.
BIN
server/static/img/favicon.png
Normal file
BIN
server/static/img/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
server/static/img/ntfy.png
Normal file
BIN
server/static/img/ntfy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
128
server/static/js/app.js
Normal file
128
server/static/js/app.js
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hello, dear curious visitor. I am not a web-guy, so please don't judge my horrible JS code.
|
||||||
|
* In fact, please do tell me about all the things I did wrong and that I could improve. I've been trying
|
||||||
|
* to read up on modern JS, but it's just a little much.
|
||||||
|
*
|
||||||
|
* Feel free to open tickets at https://github.com/binwiederhier/ntfy/issues. Thank you!
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* All the things */
|
||||||
|
|
||||||
|
let topics = {};
|
||||||
|
|
||||||
|
const topicsHeader = document.getElementById("topicsHeader");
|
||||||
|
const topicsList = document.getElementById("topicsList");
|
||||||
|
const topicField = document.getElementById("topicField");
|
||||||
|
const subscribeButton = document.getElementById("subscribeButton");
|
||||||
|
const subscribeForm = document.getElementById("subscribeForm");
|
||||||
|
const errorField = document.getElementById("error");
|
||||||
|
|
||||||
|
const subscribe = (topic) => {
|
||||||
|
if (Notification.permission !== "granted") {
|
||||||
|
Notification.requestPermission().then((permission) => {
|
||||||
|
if (permission === "granted") {
|
||||||
|
subscribeInternal(topic, 0);
|
||||||
|
} else {
|
||||||
|
showNotificationDeniedError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
subscribeInternal(topic, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribeInternal = (topic, delaySec) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Render list entry
|
||||||
|
let topicEntry = document.getElementById(`topic-${topic}`);
|
||||||
|
if (!topicEntry) {
|
||||||
|
topicEntry = document.createElement('li');
|
||||||
|
topicEntry.id = `topic-${topic}`;
|
||||||
|
topicEntry.innerHTML = `${topic} <button onclick="test('${topic}')">Test</button> <button onclick="unsubscribe('${topic}')">Unsubscribe</button>`;
|
||||||
|
topicsList.appendChild(topicEntry);
|
||||||
|
}
|
||||||
|
topicsHeader.style.display = '';
|
||||||
|
|
||||||
|
// Open event source
|
||||||
|
let eventSource = new EventSource(`${topic}/sse`);
|
||||||
|
eventSource.onopen = () => {
|
||||||
|
topicEntry.innerHTML = `${topic} <button onclick="test('${topic}')">Test</button> <button onclick="unsubscribe('${topic}')">Unsubscribe</button>`;
|
||||||
|
delaySec = 0; // Reset on successful connection
|
||||||
|
};
|
||||||
|
eventSource.onerror = (e) => {
|
||||||
|
const newDelaySec = (delaySec + 5 <= 15) ? delaySec + 5 : 15;
|
||||||
|
topicEntry.innerHTML = `${topic} <i>(Reconnecting in ${newDelaySec}s ...)</i> <button disabled="disabled">Test</button> <button onclick="unsubscribe('${topic}')">Unsubscribe</button>`;
|
||||||
|
eventSource.close()
|
||||||
|
subscribeInternal(topic, newDelaySec);
|
||||||
|
};
|
||||||
|
eventSource.onmessage = (e) => {
|
||||||
|
const event = JSON.parse(e.data);
|
||||||
|
new Notification(event.message);
|
||||||
|
};
|
||||||
|
topics[topic] = eventSource;
|
||||||
|
localStorage.setItem('topics', JSON.stringify(Object.keys(topics)));
|
||||||
|
}, delaySec * 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsubscribe = (topic) => {
|
||||||
|
topics[topic].close();
|
||||||
|
delete topics[topic];
|
||||||
|
localStorage.setItem('topics', JSON.stringify(Object.keys(topics)));
|
||||||
|
document.getElementById(`topic-${topic}`).remove();
|
||||||
|
if (Object.keys(topics).length === 0) {
|
||||||
|
topicsHeader.style.display = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const test = (topic) => {
|
||||||
|
fetch(`/${topic}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: `This is a test notification for topic ${topic}!`
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const showError = (msg) => {
|
||||||
|
errorField.innerHTML = msg;
|
||||||
|
topicField.disabled = true;
|
||||||
|
subscribeButton.disabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const showBrowserIncompatibleError = () => {
|
||||||
|
showError("Your browser is not compatible to use the web-based desktop notifications.");
|
||||||
|
};
|
||||||
|
|
||||||
|
const showNotificationDeniedError = () => {
|
||||||
|
showError("You have blocked desktop notifications for this website. Please unblock them and refresh to use the web-based desktop notifications.");
|
||||||
|
};
|
||||||
|
|
||||||
|
subscribeForm.onsubmit = function () {
|
||||||
|
if (!topicField.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
subscribe(topicField.value);
|
||||||
|
topicField.value = "";
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Disable Web UI if notifications of EventSource are not available
|
||||||
|
if (!window["Notification"] || !window["EventSource"]) {
|
||||||
|
showBrowserIncompatibleError();
|
||||||
|
} else if (Notification.permission === "denied") {
|
||||||
|
showNotificationDeniedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset UI
|
||||||
|
topicField.value = "";
|
||||||
|
|
||||||
|
// Restore topics
|
||||||
|
const storedTopics = localStorage.getItem('topics');
|
||||||
|
if (storedTopics && Notification.permission === "granted") {
|
||||||
|
const storedTopicsArray = JSON.parse(storedTopics)
|
||||||
|
storedTopicsArray.forEach((topic) => { subscribeInternal(topic, 0); });
|
||||||
|
if (storedTopicsArray.length === 0) {
|
||||||
|
topicsHeader.style.display = 'none';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
topicsHeader.style.display = 'none';
|
||||||
|
}
|
Loading…
Reference in a new issue