Feature complete
parent
7b186af765
commit
0544a6f00d
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue