import Container from "@mui/material/Container"; import { ButtonBase, CardActions, CardContent, CircularProgress, Fade, Link, Modal, Snackbar, Stack, Tooltip } from "@mui/material"; import Card from "@mui/material/Card"; import Typography from "@mui/material/Typography"; import * as React from "react"; import {useEffect, useState} from "react"; import { formatBytes, formatMessage, formatShortDateTime, formatTitle, openUrl, shortUrl, topicShortUrl, unmatchedTags } from "../app/utils"; import IconButton from "@mui/material/IconButton"; import CloseIcon from '@mui/icons-material/Close'; import {LightboxBackdrop, Paragraph, VerticallyCenteredContainer} from "./styles"; import {useLiveQuery} from "dexie-react-hooks"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import subscriptionManager from "../app/SubscriptionManager"; 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) => { if (props.mode === "all") { return (props.subscriptions) ? : ; } return (props.subscription) ? : ; } const AllSubscriptions = (props) => { const subscriptions = props.subscriptions; const notifications = useLiveQuery(() => subscriptionManager.getAllNotifications(), []); if (notifications === null || notifications === undefined) { return ; } else if (subscriptions.length === 0) { return ; } else if (notifications.length === 0) { return ; } return ; } const SingleSubscription = (props) => { const subscription = props.subscription; const notifications = useLiveQuery(() => subscriptionManager.getNotifications(subscription.id), [subscription]); if (notifications === null || notifications === undefined) { return ; } else if (notifications.length === 0) { return ; } return ; } const NotificationList = (props) => { const pageSize = 20; const notifications = props.notifications; const [snackOpen, setSnackOpen] = useState(false); const [maxCount, setMaxCount] = useState(pageSize); const count = Math.min(notifications.length, maxCount); useEffect(() => { return () => { setMaxCount(pageSize); document.getElementById("main").scrollTo(0, 0); } }, [props.id]); return ( setMaxCount(prev => prev + pageSize)} hasMore={count < notifications.length} loader={<>Loading ...} scrollThreshold={0.7} scrollableTarget="main" > {notifications.slice(0, count).map(notification => setSnackOpen(true)} />)} setSnackOpen(false)} message="Copied to clipboard" /> ); } const NotificationItem = (props) => { const notification = props.notification; const subscriptionId = notification.subscriptionId; const attachment = notification.attachment; const date = formatShortDateTime(notification.time); const otherTags = unmatchedTags(notification.tags); const tags = (otherTags.length > 0) ? otherTags.join(', ') : null; const handleDelete = async () => { console.log(`[Notifications] Deleting notification ${notification.id} from ${subscriptionId}`); await subscriptionManager.deleteNotification(notification.id) } const handleCopy = (s) => { navigator.clipboard.writeText(s); props.onShowSnack(); }; const expired = attachment && attachment.expires && attachment.expires < Date.now()/1000; const showAttachmentActions = attachment && !expired; const showClickAction = notification.click; const showActions = showAttachmentActions || showClickAction; return ( {date} {[1,2,4,5].includes(notification.priority) && {`Priority} {notification.new === 1 && } {notification.title && {formatTitle(notification)}} {autolink(formatMessage(notification))} {attachment && } {tags && Tags: {tags}} {showActions && {showAttachmentActions && <> } {showClickAction && <> } } ); } /** * Replace links with components; this is a combination of the genius function * in [1] and the regex in [2]. * * [1] https://github.com/facebook/react/issues/3386#issuecomment-78605760 * [2] https://github.com/bryanwoods/autolink-js/blob/master/autolink.js#L9 */ const autolink = (s) => { const parts = s.split(/(\bhttps?:\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|]\b)/gi); for (let i = 1; i < parts.length; i += 2) { parts[i] = {shortUrl(parts[i])}; } return <>{parts}; }; const priorityFiles = { 1: priority1, 2: priority2, 4: priority4, 5: priority5 }; const Attachment = (props) => { const attachment = props.attachment; const expired = attachment.expires && attachment.expires < Date.now()/1000; const expires = attachment.expires && attachment.expires > Date.now()/1000; const displayableImage = !expired && attachment.type && attachment.type.startsWith("image/"); // Unexpired image if (displayableImage) { return ; } // Anything else: Show box const infos = []; if (attachment.size) { infos.push(formatBytes(attachment.size)); } if (expires) { infos.push(`link expires ${formatShortDateTime(attachment.expires)}`); } if (expired) { infos.push(`download link expired`); } const maybeInfoText = (infos.length > 0) ? <>
{infos.join(", ")} : null; // If expired, just show infos without click target if (expired) { return ( {attachment.name} {maybeInfoText} ); } // Not expired return ( {attachment.name} {maybeInfoText} ); }; const Image = (props) => { const [open, setOpen] = useState(false); return ( <> setOpen(true)} sx={{ marginTop: 2, borderRadius: '4px', boxShadow: 2, width: 1, maxHeight: '400px', objectFit: 'cover', cursor: 'pointer' }} /> setOpen(false)} BackdropComponent={LightboxBackdrop} > ); } const Icon = (props) => { const type = props.type; let imageFile; if (!type) { imageFile = fileDocument; } else if (type.startsWith('image/')) { imageFile = fileImage; } else if (type.startsWith('video/')) { imageFile = fileVideo; } else if (type.startsWith('audio/')) { imageFile = fileAudio; } else if (type === "application/vnd.android.package-archive") { imageFile = fileApp; } else { imageFile = fileDocument; } return ( ); } const NoNotifications = (props) => { const shortUrl = topicShortUrl(props.subscription.baseUrl, props.subscription.topic); return ( No notifications
You haven't received any notifications for this topic yet.
To send notifications to this topic, simply PUT or POST to the topic URL. Example:
$ curl -d "Hi" {shortUrl}
For more detailed instructions, check out the website or {" "}documentation.
); }; const NoNotificationsWithoutSubscription = (props) => { const subscription = props.subscriptions[0]; const shortUrl = topicShortUrl(subscription.baseUrl, subscription.topic); return ( No notifications
You haven't received any notifications.
To send notifications to a topic, simply PUT or POST to the topic URL. Here's an example using one of your topics. Example:
$ curl -d "Hi" {shortUrl}
For more detailed instructions, check out the website or {" "}documentation.
); }; const NoSubscriptions = () => { return ( No topics
It looks like you don't have any subscriptions yet.
Click the "Add subscription" link to create or subscribe to a topic. After that, you can send messages via PUT or POST and you'll receive notifications here. For more information, check out the website or {" "}documentation.
); }; const Loading = () => { return (
Loading notifications ...
); }; export default Notifications;