diff --git a/README.md b/README.md index f90f06b9..eaf89040 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/web/src/components/App.js b/web/src/components/App.js index 21a0aa10..872681fc 100644 --- a/web/src/components/App.js +++ b/web/src/components/App.js @@ -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 diff --git a/web/src/components/Notifications.js b/web/src/components/Notifications.js index 652a40c5..38dc683b 100644 --- a/web/src/components/Notifications.js +++ b/web/src/components/Notifications.js @@ -1,5 +1,16 @@ 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={

aa

} + loader={<>Loading ...} scrollThreshold={0.7} scrollableTarget="main" > @@ -91,7 +103,14 @@ const NotificationList = (props) => { setSnackOpen(true)} />)} + setSnackOpen(false)} + message="Copied to clipboard" + /> @@ -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) => { } {notification.title && {formatTitle(notification)}} - {formatMessage(notification)} + {autolink(formatMessage(notification))} {attachment && } {tags && Tags: {tags}} {showActions && {showAttachmentActions && <> - - + + + + + + + } + {showClickAction && <> + + + + + + } - {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,