Embed resources
27
web/.gitignore
vendored
|
@ -1,27 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# IDEs and editors
|
|
||||||
/.idea
|
|
||||||
/.vscode
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
|
@ -15,7 +15,7 @@
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="#317f6f">
|
<meta name="apple-mobile-web-app-status-bar-style" content="#317f6f">
|
||||||
|
|
||||||
<!-- Favicon, see favicon.io -->
|
<!-- Favicon, see favicon.io -->
|
||||||
<link rel="icon" type="image/png" href="%PUBLIC_URL%/static/img/favicon.png">
|
<link rel="icon" type="image/png" href="static/img/favicon.png">
|
||||||
|
|
||||||
<!-- Previews in Google, Slack, WhatsApp, etc. -->
|
<!-- Previews in Google, Slack, WhatsApp, etc. -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
|
||||||
<!-- Fonts -->
|
<!-- Fonts -->
|
||||||
<link rel="stylesheet" href="%PUBLIC_URL%/static/css/fonts.css" type="text/css">
|
<link rel="stylesheet" href="static/css/fonts.css" type="text/css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|
Before Width: | Height: | Size: 24 KiB |
|
@ -1,6 +1,7 @@
|
||||||
import {formatMessage, formatTitleWithDefault, openUrl, playSound, topicShortUrl} from "./utils";
|
import {formatMessage, formatTitleWithDefault, openUrl, playSound, topicShortUrl} from "./utils";
|
||||||
import prefs from "./Prefs";
|
import prefs from "./Prefs";
|
||||||
import subscriptionManager from "./SubscriptionManager";
|
import subscriptionManager from "./SubscriptionManager";
|
||||||
|
import logo from "../img/ntfy.png";
|
||||||
|
|
||||||
class Notifier {
|
class Notifier {
|
||||||
async notify(subscriptionId, notification, onClickFallback) {
|
async notify(subscriptionId, notification, onClickFallback) {
|
||||||
|
@ -17,7 +18,7 @@ class Notifier {
|
||||||
console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`);
|
console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}: ${message}`);
|
||||||
const n = new Notification(title, {
|
const n = new Notification(title, {
|
||||||
body: message,
|
body: message,
|
||||||
icon: '/static/img/favicon.png'
|
icon: logo
|
||||||
});
|
});
|
||||||
if (notification.click) {
|
if (notification.click) {
|
||||||
n.onclick = (e) => openUrl(notification.click);
|
n.onclick = (e) => openUrl(notification.click);
|
||||||
|
@ -32,7 +33,6 @@ class Notifier {
|
||||||
await playSound(sound);
|
await playSound(sound);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[Notifier, ${shortUrl}] Error playing audio`, e);
|
console.log(`[Notifier, ${shortUrl}] Error playing audio`, e);
|
||||||
// FIXME show no sound allowed popup
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Prefs {
|
||||||
|
|
||||||
async sound() {
|
async sound() {
|
||||||
const sound = await db.prefs.get('sound');
|
const sound = await db.prefs.get('sound');
|
||||||
return (sound) ? sound.value : "mixkit-correct-answer-tone";
|
return (sound) ? sound.value : "ding";
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMinPriority(minPriority) {
|
async setMinPriority(minPriority) {
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
import {rawEmojis} from "./emojis";
|
import {rawEmojis} from "./emojis";
|
||||||
|
import beep from "../sounds/beep.mp3";
|
||||||
|
import juntos from "../sounds/juntos.mp3";
|
||||||
|
import pristine from "../sounds/pristine.mp3";
|
||||||
|
import ding from "../sounds/ding.mp3";
|
||||||
|
import dadum from "../sounds/dadum.mp3";
|
||||||
|
import pop from "../sounds/pop.mp3";
|
||||||
|
import popSwoosh from "../sounds/pop-swoosh.mp3";
|
||||||
|
|
||||||
export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
|
export const topicUrl = (baseUrl, topic) => `${baseUrl}/${topic}`;
|
||||||
export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`
|
export const topicUrlWs = (baseUrl, topic) => `${topicUrl(baseUrl, topic)}/ws`
|
||||||
|
@ -34,7 +41,6 @@ const toEmojis = (tags) => {
|
||||||
else return tags.filter(tag => tag in emojis).map(tag => emojis[tag]);
|
else return tags.filter(tag => tag in emojis).map(tag => emojis[tag]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const formatTitleWithDefault = (m, fallback) => {
|
export const formatTitleWithDefault = (m, fallback) => {
|
||||||
if (m.title) {
|
if (m.title) {
|
||||||
return formatTitle(m);
|
return formatTitle(m);
|
||||||
|
@ -123,8 +129,18 @@ export const subscriptionRoute = (subscription) => {
|
||||||
return `/${subscription.topic}`;
|
return `/${subscription.topic}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sounds = {
|
||||||
|
"beep": beep,
|
||||||
|
"juntos": juntos,
|
||||||
|
"pristine": pristine,
|
||||||
|
"ding": ding,
|
||||||
|
"dadum": dadum,
|
||||||
|
"pop": pop,
|
||||||
|
"pop-swoosh": popSwoosh
|
||||||
|
};
|
||||||
|
|
||||||
export const playSound = async (sound) => {
|
export const playSound = async (sound) => {
|
||||||
const audio = new Audio(`/static/sounds/${sound}.mp3`);
|
const audio = new Audio(sounds[sound]);
|
||||||
return audio.play();
|
return audio.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||||
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
|
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
|
||||||
import api from "../app/Api";
|
import api from "../app/Api";
|
||||||
import subscriptionManager from "../app/SubscriptionManager";
|
import subscriptionManager from "../app/SubscriptionManager";
|
||||||
|
import logo from "../img/ntfy.svg"
|
||||||
|
|
||||||
const ActionBar = (props) => {
|
const ActionBar = (props) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
@ -44,7 +45,7 @@ const ActionBar = (props) => {
|
||||||
>
|
>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Box component="img" src="/static/img/ntfy.svg" sx={{
|
<Box component="img" src={logo} sx={{
|
||||||
display: { xs: 'none', sm: 'block' },
|
display: { xs: 'none', sm: 'block' },
|
||||||
marginRight: '10px',
|
marginRight: '10px',
|
||||||
height: '28px'
|
height: '28px'
|
||||||
|
@ -152,11 +153,7 @@ const SettingsIcons = (props) => {
|
||||||
>
|
>
|
||||||
<Paper>
|
<Paper>
|
||||||
<ClickAwayListener onClickAway={handleClose}>
|
<ClickAwayListener onClickAway={handleClose}>
|
||||||
<MenuList
|
<MenuList autoFocusItem={open} onKeyDown={handleListKeyDown}>
|
||||||
autoFocusItem={open}
|
|
||||||
id="composition-menu"
|
|
||||||
onKeyDown={handleListKeyDown}
|
|
||||||
>
|
|
||||||
<MenuItem onClick={handleSendTestMessage}>Send test notification</MenuItem>
|
<MenuItem onClick={handleSendTestMessage}>Send test notification</MenuItem>
|
||||||
<MenuItem onClick={handleClearAll}>Clear all notifications</MenuItem>
|
<MenuItem onClick={handleClearAll}>Clear all notifications</MenuItem>
|
||||||
<MenuItem onClick={handleUnsubscribe}>Unsubscribe</MenuItem>
|
<MenuItem onClick={handleUnsubscribe}>Unsubscribe</MenuItem>
|
||||||
|
|
|
@ -21,6 +21,16 @@ import Box from "@mui/material/Box";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import subscriptionManager from "../app/SubscriptionManager";
|
import subscriptionManager from "../app/SubscriptionManager";
|
||||||
import InfiniteScroll from "react-infinite-scroll-component";
|
import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
|
import fileApp from "../img/file-app.svg";
|
||||||
|
import fileAudio from "../img/file-audio.svg";
|
||||||
|
import fileDocument from "../img/file-document.svg";
|
||||||
|
import fileImage from "../img/file-image.svg";
|
||||||
|
import fileVideo from "../img/file-video.svg";
|
||||||
|
import priority1 from "../img/priority-1.svg";
|
||||||
|
import priority2 from "../img/priority-2.svg";
|
||||||
|
import priority4 from "../img/priority-4.svg";
|
||||||
|
import priority5 from "../img/priority-5.svg";
|
||||||
|
import logoOutline from "../img/ntfy-outline.svg";
|
||||||
|
|
||||||
const Notifications = (props) => {
|
const Notifications = (props) => {
|
||||||
if (props.mode === "all") {
|
if (props.mode === "all") {
|
||||||
|
@ -113,7 +123,7 @@ const NotificationItem = (props) => {
|
||||||
{date}
|
{date}
|
||||||
{[1,2,4,5].includes(notification.priority) &&
|
{[1,2,4,5].includes(notification.priority) &&
|
||||||
<img
|
<img
|
||||||
src={`/static/img/priority-${notification.priority}.svg`}
|
src={priorityFiles[notification.priority]}
|
||||||
alt={`Priority ${notification.priority}`}
|
alt={`Priority ${notification.priority}`}
|
||||||
style={{ verticalAlign: 'bottom' }}
|
style={{ verticalAlign: 'bottom' }}
|
||||||
/>}
|
/>}
|
||||||
|
@ -139,6 +149,13 @@ const NotificationItem = (props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const priorityFiles = {
|
||||||
|
1: priority1,
|
||||||
|
2: priority2,
|
||||||
|
4: priority4,
|
||||||
|
5: priority5
|
||||||
|
};
|
||||||
|
|
||||||
const Attachment = (props) => {
|
const Attachment = (props) => {
|
||||||
const attachment = props.attachment;
|
const attachment = props.attachment;
|
||||||
const expired = attachment.expires && attachment.expires < Date.now()/1000;
|
const expired = attachment.expires && attachment.expires < Date.now()/1000;
|
||||||
|
@ -218,7 +235,7 @@ const Image = (props) => {
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src={`${props.attachment.url}`}
|
src={props.attachment.url}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -239,7 +256,7 @@ const Image = (props) => {
|
||||||
<Fade in={open}>
|
<Fade in={open}>
|
||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src={`${props.attachment.url}`}
|
src={props.attachment.url}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: 1,
|
maxWidth: 1,
|
||||||
|
@ -261,22 +278,22 @@ const Icon = (props) => {
|
||||||
const type = props.type;
|
const type = props.type;
|
||||||
let imageFile;
|
let imageFile;
|
||||||
if (!type) {
|
if (!type) {
|
||||||
imageFile = 'file-document.svg';
|
imageFile = fileDocument;
|
||||||
} else if (type.startsWith('image/')) {
|
} else if (type.startsWith('image/')) {
|
||||||
imageFile = 'file-image.svg';
|
imageFile = fileImage;
|
||||||
} else if (type.startsWith('video/')) {
|
} else if (type.startsWith('video/')) {
|
||||||
imageFile = 'file-video.svg';
|
imageFile = fileVideo;
|
||||||
} else if (type.startsWith('audio/')) {
|
} else if (type.startsWith('audio/')) {
|
||||||
imageFile = 'file-audio.svg';
|
imageFile = fileAudio;
|
||||||
} else if (type === "application/vnd.android.package-archive") {
|
} else if (type === "application/vnd.android.package-archive") {
|
||||||
imageFile = 'file-app.svg';
|
imageFile = fileApp;
|
||||||
} else {
|
} else {
|
||||||
imageFile = 'file-document.svg';
|
imageFile = fileDocument;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src={`/static/img/${imageFile}`}
|
src={imageFile}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
sx={{
|
sx={{
|
||||||
width: '28px',
|
width: '28px',
|
||||||
|
@ -291,7 +308,7 @@ const NoNotifications = (props) => {
|
||||||
return (
|
return (
|
||||||
<VerticallyCenteredContainer maxWidth="xs">
|
<VerticallyCenteredContainer maxWidth="xs">
|
||||||
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
|
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
|
||||||
<img src="/static/img/ntfy-outline.svg" height="64" width="64" alt="No notifications"/><br />
|
<img src={logoOutline} height="64" width="64" alt="No notifications"/><br />
|
||||||
You haven't received any notifications for this topic yet.
|
You haven't received any notifications for this topic yet.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
|
@ -317,7 +334,7 @@ const NoNotificationsWithoutSubscription = (props) => {
|
||||||
return (
|
return (
|
||||||
<VerticallyCenteredContainer maxWidth="xs">
|
<VerticallyCenteredContainer maxWidth="xs">
|
||||||
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
|
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
|
||||||
<img src="/static/img/ntfy-outline.svg" height="64" width="64" alt="No notifications"/><br />
|
<img src={logoOutline} height="64" width="64" alt="No notifications"/><br />
|
||||||
You haven't received any notifications.
|
You haven't received any notifications.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
|
@ -342,7 +359,7 @@ const NoSubscriptions = () => {
|
||||||
return (
|
return (
|
||||||
<VerticallyCenteredContainer maxWidth="xs">
|
<VerticallyCenteredContainer maxWidth="xs">
|
||||||
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
|
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
|
||||||
<img src="/static/img/ntfy-outline.svg" height="64" width="64" alt="No topics"/><br />
|
<img src={logoOutline} height="64" width="64" alt="No topics"/><br />
|
||||||
It looks like you don't have any subscriptions yet.
|
It looks like you don't have any subscriptions yet.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
|
|
|
@ -75,12 +75,12 @@ const Sound = () => {
|
||||||
<FormControl fullWidth variant="standard" sx={{ margin: 1 }}>
|
<FormControl fullWidth variant="standard" sx={{ margin: 1 }}>
|
||||||
<Select value={sound} onChange={handleChange}>
|
<Select value={sound} onChange={handleChange}>
|
||||||
<MenuItem value={"none"}>No sound</MenuItem>
|
<MenuItem value={"none"}>No sound</MenuItem>
|
||||||
<MenuItem value={"mixkit-correct-answer-tone"}>Ding</MenuItem>
|
<MenuItem value={"ding"}>Ding</MenuItem>
|
||||||
<MenuItem value={"juntos"}>Juntos</MenuItem>
|
<MenuItem value={"juntos"}>Juntos</MenuItem>
|
||||||
<MenuItem value={"pristine"}>Pristine</MenuItem>
|
<MenuItem value={"pristine"}>Pristine</MenuItem>
|
||||||
<MenuItem value={"mixkit-software-interface-start"}>Dadum</MenuItem>
|
<MenuItem value={"dadum"}>Dadum</MenuItem>
|
||||||
<MenuItem value={"mixkit-message-pop-alert"}>Pop</MenuItem>
|
<MenuItem value={"pop"}>Pop</MenuItem>
|
||||||
<MenuItem value={"mixkit-long-pop"}>Pop swoosh</MenuItem>
|
<MenuItem value={"pop-swoosh"}>Pop swoosh</MenuItem>
|
||||||
<MenuItem value={"beep"}>Beep</MenuItem>
|
<MenuItem value={"beep"}>Beep</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
Before Width: | Height: | Size: 712 B After Width: | Height: | Size: 712 B |
Before Width: | Height: | Size: 312 B After Width: | Height: | Size: 312 B |
Before Width: | Height: | Size: 272 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 7 KiB |
BIN
web/src/img/ntfy.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |