Feature complete

pull/149/head
Philipp Heckel 2022-03-11 11:46:19 -05:00
parent 7b186af765
commit 0544a6f00d
3 changed files with 57 additions and 10 deletions

View File

@ -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) * [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 * [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 * [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) * [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) * [Linked tabs in mkdocs](https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs)

View File

@ -20,9 +20,6 @@ import ErrorBoundary from "./ErrorBoundary";
import routes from "./routes"; import routes from "./routes";
import {useAutoSubscribe, useConnectionListeners} from "./hooks"; import {useAutoSubscribe, useConnectionListeners} from "./hooks";
// TODO link lighlighting
// TODO "copy url" toast
// TODO "copy link url" button
// TODO add drag and drop // TODO add drag and drop
// TODO races when two tabs are open // TODO races when two tabs are open
// TODO investigate service workers // TODO investigate service workers

View File

@ -1,5 +1,16 @@
import Container from "@mui/material/Container"; 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 Card from "@mui/material/Card";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import * as React from "react"; import * as React from "react";
@ -9,7 +20,7 @@ import {
formatMessage, formatMessage,
formatShortDateTime, formatShortDateTime,
formatTitle, formatTitle,
openUrl, openUrl, shortUrl,
topicShortUrl, topicShortUrl,
unmatchedTags unmatchedTags
} from "../app/utils"; } from "../app/utils";
@ -66,6 +77,7 @@ const SingleSubscription = (props) => {
const NotificationList = (props) => { const NotificationList = (props) => {
const pageSize = 20; const pageSize = 20;
const notifications = props.notifications; const notifications = props.notifications;
const [snackOpen, setSnackOpen] = useState(false);
const [maxCount, setMaxCount] = useState(pageSize); const [maxCount, setMaxCount] = useState(pageSize);
const count = Math.min(notifications.length, maxCount); const count = Math.min(notifications.length, maxCount);
@ -81,7 +93,7 @@ const NotificationList = (props) => {
dataLength={count} dataLength={count}
next={() => setMaxCount(prev => prev + pageSize)} next={() => setMaxCount(prev => prev + pageSize)}
hasMore={count < notifications.length} hasMore={count < notifications.length}
loader={<h1>aa</h1>} loader={<>Loading ...</>}
scrollThreshold={0.7} scrollThreshold={0.7}
scrollableTarget="main" scrollableTarget="main"
> >
@ -91,7 +103,14 @@ const NotificationList = (props) => {
<NotificationItem <NotificationItem
key={notification.id} key={notification.id}
notification={notification} notification={notification}
onShowSnack={() => setSnackOpen(true)}
/>)} />)}
<Snackbar
open={snackOpen}
autoHideDuration={3000}
onClose={() => setSnackOpen(false)}
message="Copied to clipboard"
/>
</Stack> </Stack>
</Container> </Container>
</InfiniteScroll> </InfiniteScroll>
@ -109,6 +128,10 @@ const NotificationItem = (props) => {
console.log(`[Notifications] Deleting notification ${notification.id} from ${subscriptionId}`); console.log(`[Notifications] Deleting notification ${notification.id} from ${subscriptionId}`);
await subscriptionManager.deleteNotification(notification.id) 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 expired = attachment && attachment.expires && attachment.expires < Date.now()/1000;
const showAttachmentActions = attachment && !expired; const showAttachmentActions = attachment && !expired;
const showClickAction = notification.click; const showClickAction = notification.click;
@ -133,22 +156,48 @@ const NotificationItem = (props) => {
</svg>} </svg>}
</Typography> </Typography>
{notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</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}/>} {attachment && <Attachment attachment={attachment}/>}
{tags && <Typography sx={{ fontSize: 14 }} color="text.secondary">Tags: {tags}</Typography>} {tags && <Typography sx={{ fontSize: 14 }} color="text.secondary">Tags: {tags}</Typography>}
</CardContent> </CardContent>
{showActions && {showActions &&
<CardActions sx={{paddingTop: 0}}> <CardActions sx={{paddingTop: 0}}>
{showAttachmentActions && <> {showAttachmentActions && <>
<Button onClick={() => navigator.clipboard.writeText(attachment.url)}>Copy URL</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> <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>} </CardActions>}
</Card> </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 = { const priorityFiles = {
1: priority1, 1: priority1,
2: priority2, 2: priority2,