diff --git a/src/components/ICONS.jsx b/src/components/ICONS.jsx index c05f5bd..d2cb8ed 100644 --- a/src/components/ICONS.jsx +++ b/src/components/ICONS.jsx @@ -104,4 +104,5 @@ export const ICONS = { code: () => import('@iconify-icons/mingcute/code-line'), copy: () => import('@iconify-icons/mingcute/copy-2-line'), quote: () => import('@iconify-icons/mingcute/quote-left-line'), + settings: () => import('@iconify-icons/mingcute/settings-6-line'), }; diff --git a/src/data/features.json b/src/data/features.json index 69be747..e0cbb87 100644 --- a/src/data/features.json +++ b/src/data/features.json @@ -1,4 +1,5 @@ { "@mastodon/edit-media-attributes": ">=4.1", - "@mastodon/list-exclusive": ">=4.2" + "@mastodon/list-exclusive": ">=4.2", + "@mastodon/filtered-notifications": "~4.3 || >=4.3" } diff --git a/src/pages/notifications.css b/src/pages/notifications.css index 7a9bf70..61410fd 100644 --- a/src/pages/notifications.css +++ b/src/pages/notifications.css @@ -421,3 +421,130 @@ color: var(--text-color); background-color: var(--link-faded-color); } + +/* FILTERED NOTIFICATIONS */ + +.filtered-notifications { + padding-block-end: 16px; + + summary { + padding: 8px 16px; + cursor: pointer; + font-weight: 600; + user-select: none; + margin: 16px 0 0; + color: var(--text-insignificant-color); + + &::marker, + &::-webkit-details-marker { + color: var(--text-insignificant-color); + } + } + details[open] summary { + color: var(--text-color); + } + + summary + ul { + } + ul { + list-style: none; + padding: 0; + margin: 0; + max-height: 50vh; + max-height: 50dvh; + overflow: auto; + border-top: 1px solid var(--outline-color); + border-bottom: 1px solid var(--outline-color); + background-color: var(--bg-faded-color); + + @media (min-width: 40em) { + background-color: var(--bg-color); + border-radius: 16px; + border-width: 0; + } + + li { + display: flex; + padding: 16px; + row-gap: 8px; + column-gap: 16px; + border-bottom: 1px solid var(--outline-color); + } + li:last-child { + border-bottom: none; + } + + .request-notifcations { + flex-grow: 1; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + + .last-post { + > .status-link { + border-radius: 8px; + overflow: hidden; + --max-height: 160px; + max-height: var(--max-height); + border: 1px solid var(--outline-color); + + &:is(:hover, :focus-visible) { + border-color: var(--outline-hover-color); + } + + .status { + mask-image: linear-gradient( + to bottom, + black calc(var(--max-height) / 2), + transparent calc(var(--max-height) - 8px) + ); + font-size: calc(var(--text-size) * 0.9); + + .content-container { + pointer-events: none; + filter: saturate(0.5); + } + } + } + } + + .request-notifications-account { + display: flex; + align-items: center; + gap: 4px; + } + } + + .notification-request-buttons { + grid-area: buttons; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 4px; + + button { + max-width: 30vw; + } + + .notification-request-states { + min-height: 32px; + text-align: center; + vertical-align: middle; + + .icon { + margin-inline: 8px; + + &.notification-accepted { + color: var(--green-color); + } + + &.notification-dismissed { + color: var(--red-color); + } + } + } + } + } +} diff --git a/src/pages/notifications.jsx b/src/pages/notifications.jsx index aa08aaf..b8ad3da 100644 --- a/src/pages/notifications.jsx +++ b/src/pages/notifications.jsx @@ -14,8 +14,10 @@ import FollowRequestButtons from '../components/follow-request-buttons'; import Icon from '../components/icon'; import Link from '../components/link'; import Loader from '../components/loader'; +import Modal from '../components/modal'; import NavMenu from '../components/nav-menu'; import Notification from '../components/notification'; +import Status from '../components/status'; import { api } from '../utils/api'; import enhanceContent from '../utils/enhance-content'; import groupNotifications from '../utils/group-notifications'; @@ -23,8 +25,10 @@ import handleContentLinks from '../utils/handle-content-links'; import niceDateTime from '../utils/nice-date-time'; import { getRegistration } from '../utils/push-notifications'; import shortenNumber from '../utils/shorten-number'; +import showToast from '../utils/show-toast'; import states, { saveStatus } from '../utils/states'; import { getCurrentInstance } from '../utils/store-utils'; +import supports from '../utils/supports'; import usePageVisibility from '../utils/usePageVisibility'; import useScroll from '../utils/useScroll'; import useTitle from '../utils/useTitle'; @@ -136,6 +140,28 @@ function Notifications({ columnMode }) { } } + const supportsFilteredNotifications = supports( + '@mastodon/filtered-notifications', + ); + const [showNotificationsSettings, setShowNotificationsSettings] = + useState(false); + const [notificationsPolicy, setNotificationsPolicy] = useState({}); + function fetchNotificationsPolicy() { + return masto.v1.notifications.policy.fetch().catch(() => {}); + } + function loadNotificationsPolicy() { + fetchNotificationsPolicy() + .then((policy) => { + console.log('✨ Notifications policy', policy); + setNotificationsPolicy(policy); + }) + .catch(() => {}); + } + const [notificationsRequests, setNotificationsRequests] = useState(null); + function fetchNotificationsRequest() { + return masto.v1.notifications.requests.list(); + } + const loadNotifications = (firstLoad) => { setShowNew(false); setUIState('loading'); @@ -161,6 +187,10 @@ function Notifications({ columnMode }) { setFollowRequests(requests); }) .catch(() => {}); + + if (supportsFilteredNotifications) { + loadNotificationsPolicy(); + } } const { done } = await fetchNotificationsPromise; @@ -384,7 +414,17 @@ function Notifications({ columnMode }) {

Notifications

- {/*
{showNew && uiState !== 'loading' && ( @@ -489,6 +529,70 @@ function Notifications({ columnMode }) { )} )} + {supportsFilteredNotifications && + notificationsPolicy?.summary?.pendingRequestsCount > 0 && ( +
+
{ + const { open } = e.target; + if (open) { + const requests = await fetchNotificationsRequest(); + setNotificationsRequests(requests); + console.log({ open, requests }); + } + }} + > + + Filtered notifications from{' '} + {notificationsPolicy.summary.pendingRequestsCount} people + + {!notificationsRequests ? ( +

+ +

+ ) : ( + notificationsRequests?.length > 0 && ( + + ) + )} +
+
+ )}
+ {supportsFilteredNotifications && showNotificationsSettings && ( + { + if (e.target === e.currentTarget) { + setShowNotificationsSettings(false); + } + }} + > +
+ +
+

Notifications settings

+
+
+
{ + e.preventDefault(); + const { + filterNotFollowing, + filterNotFollowers, + filterNewAccounts, + filterPrivateMentions, + } = e.target; + const allFilters = { + filterNotFollowing: filterNotFollowing.checked, + filterNotFollowers: filterNotFollowers.checked, + filterNewAccounts: filterNewAccounts.checked, + filterPrivateMentions: filterPrivateMentions.checked, + }; + setNotificationsPolicy({ + ...notificationsPolicy, + ...allFilters, + }); + setShowNotificationsSettings(false); + (async () => { + try { + await masto.v1.notifications.policy.update(allFilters); + showToast('Notifications settings updated'); + } catch (e) { + console.error(e); + } + })(); + }} + > +

Filter out notifications from people:

+

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+
+
+
+
+ )} ); } @@ -679,4 +886,186 @@ function AnnouncementBlock({ announcement }) { ); } +function fetchNotficationsByAccount(accountID) { + const { masto } = api(); + return masto.v1.notifications.list({ + accountID, + }); +} +function NotificationRequestModalButton({ request }) { + const { instance } = api(); + const [uiState, setUIState] = useState('loading'); + const { account, lastStatus } = request; + const [showModal, setShowModal] = useState(false); + const [notifications, setNotifications] = useState([]); + + function onClose() { + setShowModal(false); + } + + useEffect(() => { + if (!request?.account?.id) return; + if (!showModal) return; + setUIState('loading'); + (async () => { + const notifs = await fetchNotficationsByAccount(request.account.id); + setNotifications(notifs || []); + setUIState('default'); + })(); + }, [showModal, request?.account?.id]); + + return ( + <> + + {showModal && ( + { + if (e.target === e.currentTarget) { + onClose(); + } + }} + > +
+ +
+ Notifications from @{account.username} +
+
+ {uiState === 'loading' ? ( +

+ +

+ ) : ( + notifications.map((notification) => ( +
{ + const { target } = e; + // If button or links + if ( + e.target.tagName === 'BUTTON' || + e.target.tagName === 'A' + ) { + onClose(); + } + }} + > + +
+ )) + )} +
+
+
+ )} + + ); +} + +function NotificationRequestButtons({ request, onChange }) { + const { masto } = api(); + const [uiState, setUIState] = useState('default'); + const [requestState, setRequestState] = useState(null); // accept, dismiss + const hasRequestState = requestState !== null; + + return ( +

+ {' '} + + + {uiState === 'loading' ? ( + + ) : requestState === 'accept' ? ( + + ) : ( + requestState === 'dismiss' && ( + + ) + )} + +

+ ); +} + export default memo(Notifications);