Skip to content
Snippets Groups Projects
Commit 0544a6f0 authored by Philipp Heckel's avatar Philipp Heckel
Browse files

Feature complete

parent 7b186af7
No related branches found
No related tags found
No related merge requests found
......@@ -62,5 +62,6 @@ Third party libraries and resources:
* [github/gemoji](https://github.com/github/gemoji) (MIT) is used for emoji support (specifically the [emoji.json](https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json) file)
* [Lightbox with vanilla JS](https://yossiabramov.com/blog/vanilla-js-lightbox) as a lightbox on the landing page
* [HTTP middleware for gzip compression](https://gist.github.com/CJEnright/bc2d8b8dc0c1389a9feeddb110f822d7) (MIT) is used for serving static files
* [Regex for auto-linking](https://github.com/bryanwoods/autolink-js) (MIT) is used to highlight links (the library is not used)
* [Statically linking go-sqlite3](https://www.arp242.net/static-go.html)
* [Linked tabs in mkdocs](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs)
......@@ -20,9 +20,6 @@ import ErrorBoundary from "./ErrorBoundary";
import routes from "./routes";
import {useAutoSubscribe, useConnectionListeners} from "./hooks";
// TODO link lighlighting
// TODO "copy url" toast
// TODO "copy link url" button
// TODO add drag and drop
// TODO races when two tabs are open
// TODO investigate service workers
......
import Container from "@mui/material/Container";
import {ButtonBase, CardActions, CardContent, CircularProgress, Fade, Link, Modal, Stack} from "@mui/material";
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";
......@@ -9,7 +20,7 @@ import {
formatMessage,
formatShortDateTime,
formatTitle,
openUrl,
openUrl, shortUrl,
topicShortUrl,
unmatchedTags
} from "../app/utils";
......@@ -66,6 +77,7 @@ const SingleSubscription = (props) => {
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);
......@@ -81,7 +93,7 @@ const NotificationList = (props) => {
dataLength={count}
next={() => setMaxCount(prev => prev + pageSize)}
hasMore={count < notifications.length}
loader={<h1>aa</h1>}
loader={<>Loading ...</>}
scrollThreshold={0.7}
scrollableTarget="main"
>
......@@ -91,7 +103,14 @@ const NotificationList = (props) => {
<NotificationItem
key={notification.id}
notification={notification}
onShowSnack={() => setSnackOpen(true)}
/>)}
<Snackbar
open={snackOpen}
autoHideDuration={3000}
onClose={() => setSnackOpen(false)}
message="Copied to clipboard"
/>
</Stack>
</Container>
</InfiniteScroll>
......@@ -109,6 +128,10 @@ const NotificationItem = (props) => {
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;
......@@ -133,22 +156,48 @@ const NotificationItem = (props) => {
</svg>}
</Typography>
{notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</Typography>}
<Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>{formatMessage(notification)}</Typography>
<Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>{autolink(formatMessage(notification))}</Typography>
{attachment && <Attachment attachment={attachment}/>}
{tags && <Typography sx={{ fontSize: 14 }} color="text.secondary">Tags: {tags}</Typography>}
</CardContent>
{showActions &&
<CardActions sx={{paddingTop: 0}}>
{showAttachmentActions && <>
<Button onClick={() => navigator.clipboard.writeText(attachment.url)}>Copy URL</Button>
<Button onClick={() => openUrl(attachment.url)}>Open attachment</Button>
<Tooltip title="Copy attachment URL to clipboard">
<Button onClick={() => handleCopy(attachment.url)}>Copy URL</Button>
</Tooltip>
<Tooltip title={`Go to ${attachment.url}`}>
<Button onClick={() => openUrl(attachment.url)}>Open attachment</Button>
</Tooltip>
</>}
{showClickAction && <>
<Tooltip title="Copy link URL to clipboard">
<Button onClick={() => handleCopy(notification.click)}>Copy link</Button>
</Tooltip>
<Tooltip title={`Go to ${notification.click}`}>
<Button onClick={() => openUrl(notification.click)}>Open link</Button>
</Tooltip>
</>}
{showClickAction && <Button onClick={() => openUrl(notification.click)}>Open link</Button>}
</CardActions>}
</Card>
);
}
/**
* Replace links with <Link/> 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] = <Link key={i} href={parts[i]} underline="hover" target="_blank" rel="noreferrer,noopener">{shortUrl(parts[i])}</Link>;
}
return <>{parts}</>;
};
const priorityFiles = {
1: priority1,
2: priority2,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment