2023-07-23 15:08:43 +00:00
import { useState , useEffect , useMemo } from 'react'
2023-08-06 15:47:58 +00:00
import { useQuery } from '@apollo/client'
2021-08-17 18:15:24 +00:00
import Comment , { CommentSkeleton } from './comment'
2022-07-21 22:55:05 +00:00
import Item from './item'
import ItemJob from './item-job'
2023-08-06 15:47:58 +00:00
import { NOTIFICATIONS } from '../fragments/notifications'
2021-09-30 15:46:58 +00:00
import MoreFooter from './more-footer'
2022-01-19 21:02:38 +00:00
import Invite from './invite'
2022-01-20 19:03:48 +00:00
import { ignoreClick } from '../lib/clicks'
2022-03-31 16:49:35 +00:00
import { timeSince } from '../lib/time'
2022-03-17 20:13:19 +00:00
import Link from 'next/link'
2022-03-23 18:54:39 +00:00
import Check from '../svgs/check-double-line.svg'
2022-03-23 21:45:36 +00:00
import HandCoin from '../svgs/hand-coin-fill.svg'
2022-05-17 22:09:15 +00:00
import { COMMENT _DEPTH _LIMIT } from '../lib/constants'
2023-02-01 14:44:35 +00:00
import CowboyHatIcon from '../svgs/cowboy.svg'
import BaldIcon from '../svgs/bald.svg'
2023-05-06 21:51:17 +00:00
import { RootProvider } from './root'
2023-07-24 18:35:05 +00:00
import Alert from 'react-bootstrap/Alert'
2023-06-12 18:03:44 +00:00
import styles from './notifications.module.css'
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
import { useServiceWorker } from './serviceworker'
import { Checkbox , Form } from './form'
2023-07-23 15:08:43 +00:00
import { useRouter } from 'next/router'
2023-08-06 18:04:25 +00:00
import { useData } from './use-data'
2023-08-08 18:19:31 +00:00
import { nostrZapDetails } from '../lib/nostr'
import Text from './text'
import NostrIcon from '../svgs/nostr.svg'
2023-08-08 21:04:06 +00:00
import { numWithUnits } from '../lib/format'
2021-08-17 18:15:24 +00:00
2023-08-06 15:47:58 +00:00
function Notification ( { n , fresh } ) {
const type = n . _ _typename
return (
< NotificationLayout nid = { nid ( n ) } { ... defaultOnClick ( n ) } fresh = { fresh } >
{
( type === 'Earn' && < EarnNotification n = { n } / > ) ||
( type === 'Invitification' && < Invitification n = { n } / > ) ||
2023-08-08 18:19:31 +00:00
( type === 'InvoicePaid' && ( n . invoice . nostr ? < NostrZap n = { n } / > : < InvoicePaid n = { n } / > ) ) ||
2023-08-06 15:47:58 +00:00
( type === 'Referral' && < Referral n = { n } / > ) ||
( type === 'Streak' && < Streak n = { n } / > ) ||
( type === 'Votification' && < Votification n = { n } / > ) ||
( type === 'Mention' && < Mention n = { n } / > ) ||
( type === 'JobChanged' && < JobChanged n = { n } / > ) ||
( type === 'Reply' && < Reply n = { n } / > )
}
< / N o t i f i c a t i o n L a y o u t >
)
2023-06-01 00:51:30 +00:00
}
2022-01-20 19:03:48 +00:00
2023-08-06 15:47:58 +00:00
function NotificationLayout ( { children , nid , href , as , fresh } ) {
2023-07-23 15:08:43 +00:00
const router = useRouter ( )
2023-08-06 15:47:58 +00:00
if ( ! href ) return < div className = { fresh ? styles . fresh : '' } > { children } < / d i v >
2023-06-01 00:51:30 +00:00
return (
2023-06-01 18:22:39 +00:00
< div
2023-08-06 15:47:58 +00:00
className = {
` clickToContext ${ fresh ? styles . fresh : '' } ${ router ? . query ? . nid === nid ? 'outline-it' : '' } `
}
2023-08-04 17:07:44 +00:00
onClick = { async ( e ) => {
2023-08-04 00:14:04 +00:00
if ( ignoreClick ( e ) ) return
2023-08-04 17:07:44 +00:00
nid && await router . replace ( {
2023-08-04 00:14:04 +00:00
pathname : router . pathname ,
query : {
... router . query ,
nid
}
} , router . asPath , { ... router . options , shallow : true } )
router . push ( href , as )
} }
2023-06-01 18:22:39 +00:00
>
2023-06-01 00:51:30 +00:00
{ children }
2023-02-01 14:44:35 +00:00
< / d i v >
)
}
2023-07-23 15:08:43 +00:00
const defaultOnClick = n => {
2023-08-06 15:47:58 +00:00
const type = n . _ _typename
2023-08-15 17:41:51 +00:00
if ( type === 'Earn' ) return { href : ` /rewards/ ${ new Date ( n . sortTime ) . toISOString ( ) . slice ( 0 , 10 ) } ` }
2023-08-06 15:47:58 +00:00
if ( type === 'Invitification' ) return { href : '/invites' }
if ( type === 'InvoicePaid' ) return { href : ` /invoices/ ${ n . invoice . id } ` }
if ( type === 'Referral' ) return { href : '/referrals/month' }
if ( type === 'Streak' ) return { }
// Votification, Mention, JobChanged, Reply all have item
2023-06-01 00:51:30 +00:00
if ( ! n . item . title ) {
2023-07-06 15:00:38 +00:00
const path = n . item . path . split ( '.' )
if ( path . length > COMMENT _DEPTH _LIMIT + 1 ) {
const rootId = path . slice ( - ( COMMENT _DEPTH _LIMIT + 1 ) ) [ 0 ]
2023-07-23 15:08:43 +00:00
return {
href : {
pathname : '/items/[id]' ,
query : { id : rootId , commentId : n . item . id }
} ,
as : ` /items/ ${ rootId } `
}
2023-06-01 00:51:30 +00:00
} else {
2023-07-23 15:08:43 +00:00
return {
href : {
pathname : '/items/[id]' ,
query : { id : n . item . root . id , commentId : n . item . id }
} ,
as : ` /items/ ${ n . item . root . id } `
}
2023-07-21 22:33:11 +00:00
}
2023-07-23 14:16:12 +00:00
} else {
2023-07-23 15:08:43 +00:00
return {
href : {
pathname : '/items/[id]' ,
query : { id : n . item . id }
} ,
as : ` /items/ ${ n . item . id } `
}
2023-06-01 00:51:30 +00:00
}
}
2023-02-01 14:44:35 +00:00
function Streak ( { n } ) {
function blurb ( n ) {
const index = Number ( n . id ) % 6
const FOUND _BLURBS = [
'The harsh frontier is no place for the unprepared. This hat will protect you from the sun, dust, and other elements Mother Nature throws your way.' ,
'A cowboy is nothing without a cowboy hat. Take good care of it, and it will protect you from the sun, dust, and other elements on your journey.' ,
"This is not just a hat, it's a matter of survival. Take care of this essential tool, and it will shield you from the scorching sun and the elements." ,
"A cowboy hat isn't just a fashion statement. It's your last defense against the unforgiving elements of the Wild West. Hang onto it tight." ,
"A good cowboy hat is worth its weight in gold, shielding you from the sun, wind, and dust of the western frontier. Don't lose it." ,
'Your cowboy hat is the key to your survival in the wild west. Treat it with respect and it will protect you from the elements.'
]
const LOST _BLURBS = [
'your cowboy hat was taken by the wind storm that blew in from the west. No worries, a true cowboy always finds another hat.' ,
"you left your trusty cowboy hat in the saloon before leaving town. You'll need a replacement for the long journey west." ,
'you lost your cowboy hat in a wild shoot-out on the outskirts of town. Tough luck, tIme to start searching for another one.' ,
'you ran out of food and had to trade your hat for supplies. Better start looking for another hat.' ,
"your hat was stolen by a mischievous prairie dog. You won't catch the dog, but you can always find another hat." ,
'you lost your hat while crossing the river on your journey west. Maybe you can find a replacement hat in the next town.'
]
if ( n . days ) {
2023-08-08 21:31:43 +00:00
return ` After ${ numWithUnits ( n . days , {
abbreviate : false ,
unitSingular : 'day' ,
unitPlural : 'days'
} ) } , ` + LOST_BLURBS[index]
2023-02-01 14:44:35 +00:00
}
return FOUND _BLURBS [ index ]
}
return (
2023-07-24 18:35:05 +00:00
< div className = 'd-flex fw-bold ms-2 py-1' >
2023-02-01 14:44:35 +00:00
< div style = { { fontSize : '2rem' } } > { n . days ? < BaldIcon className = 'fill-grey' height = { 40 } width = { 40 } / > : < CowboyHatIcon className = 'fill-grey' height = { 40 } width = { 40 } / > } < / d i v >
2023-07-24 18:35:05 +00:00
< div className = 'ms-1 p-1' >
2023-02-01 14:44:35 +00:00
you { n . days ? 'lost your' : 'found a' } cowboy hat
< div > < small style = { { lineHeight : '140%' , display : 'inline-block' } } > { blurb ( n ) } < / s m a l l > < / d i v >
< / d i v >
2021-08-20 00:13:32 +00:00
< / d i v >
)
}
2023-06-01 18:22:39 +00:00
function EarnNotification ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-07-24 18:35:05 +00:00
< div className = 'd-flex ms-2 py-1' >
2023-07-23 15:08:43 +00:00
< HandCoin className = 'align-self-center fill-boost mx-1' width = { 24 } height = { 24 } style = { { flex : '0 0 24px' , transform : 'rotateY(180deg)' } } / >
2023-07-24 18:35:05 +00:00
< div className = 'ms-2' >
< div className = 'fw-bold text-boost' >
2023-08-08 21:04:06 +00:00
you stacked { numWithUnits ( n . earnedSats , { abbreviate : false } ) } in rewards < small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
2023-07-23 15:08:43 +00:00
< / d i v >
{ n . sources &&
< div style = { { fontSize : '80%' , color : 'var(--theme-grey)' } } >
2023-08-08 21:04:06 +00:00
{ n . sources . posts > 0 && < span > { numWithUnits ( n . sources . posts , { abbreviate : false } ) } for top posts < / s p a n > }
{ n . sources . comments > 0 && < span > { n . sources . posts > 0 && ' \\ ' } { numWithUnits ( n . sources . comments , { abbreviate : false } ) } for top comments < / s p a n > }
{ n . sources . tipPosts > 0 && < span > { ( n . sources . comments > 0 || n . sources . posts > 0 ) && ' \\ ' } { numWithUnits ( n . sources . tipPosts , { abbreviate : false } ) } for zapping top posts early < / s p a n > }
{ n . sources . tipComments > 0 && < span > { ( n . sources . comments > 0 || n . sources . posts > 0 || n . sources . tipPosts > 0 ) && ' \\ ' } { numWithUnits ( n . sources . tipComments , { abbreviate : false } ) } for zapping top comments early < / s p a n > }
2023-07-23 15:08:43 +00:00
< / d i v > }
< div className = 'pb-1' style = { { lineHeight : '140%' } } >
SN distributes the sats it earns back to its best stackers daily . These sats come from < Link href = '/~jobs' > jobs < / L i n k > , b o o s t s , p o s t i n g f e e s , a n d d o n a t i o n s . Y o u c a n s e e t h e d a i l y r e w a r d s p o o l a n d m a k e a d o n a t i o n < L i n k h r e f = ' / r e w a r d s ' > h e r e < / L i n k > .
2023-06-01 00:51:30 +00:00
< / d i v >
< / d i v >
2023-07-23 15:08:43 +00:00
< / d i v >
2023-06-01 18:22:39 +00:00
)
2023-06-01 00:51:30 +00:00
}
2023-06-01 18:22:39 +00:00
function Invitification ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< >
2023-07-24 18:35:05 +00:00
< small className = 'fw-bold text-secondary ms-2' >
2023-08-08 21:31:43 +00:00
your invite has been redeemed by
{ numWithUnits ( n . invite . invitees . length , {
abbreviate : false ,
unitSingular : 'stacker' ,
unitPlural : 'stackers'
} ) }
2023-06-01 00:51:30 +00:00
< / s m a l l >
2023-07-24 18:35:05 +00:00
< div className = 'ms-4 me-2 mt-1' >
2023-06-01 00:51:30 +00:00
< Invite
invite = { n . invite } active = {
! n . invite . revoked &&
! ( n . invite . limit && n . invite . invitees . length >= n . invite . limit )
}
/ >
< / d i v >
2023-08-06 15:47:58 +00:00
< / >
2023-06-01 00:51:30 +00:00
)
}
2023-08-08 18:19:31 +00:00
function NostrZap ( { n } ) {
const { nostr } = n . invoice
const { npub , content , note } = nostrZapDetails ( nostr )
return (
< >
< div className = 'fw-bold text-nostr ms-2 py-1' >
2023-08-08 21:31:43 +00:00
< NostrIcon width = { 24 } height = { 24 } className = 'fill-nostr me-1' / > { numWithUnits ( n . earnedSats ) } zap from
2023-08-08 18:19:31 +00:00
< Link className = 'mx-1 text-reset text-underline' target = '_blank' href = { ` https://snort.social/p/ ${ npub } ` } rel = 'noreferrer' >
{ npub . slice ( 0 , 10 ) } ...
< / L i n k >
on { note
? (
< Link className = 'mx-1 text-reset text-underline' target = '_blank' href = { ` https://snort.social/e/ ${ note } ` } rel = 'noreferrer' >
{ note . slice ( 0 , 12 ) } ...
< / L i n k > )
: 'nostr' }
< small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
{ content && < small className = 'd-block ms-4 ps-1 mt-1 mb-1 text-muted fw-normal' > < Text > { content } < / T e x t > < / s m a l l > }
< / d i v >
< / >
)
}
2023-06-01 18:22:39 +00:00
function InvoicePaid ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< div className = 'fw-bold text-info ms-2 py-1' >
2023-08-08 21:04:06 +00:00
< Check className = 'fill-info me-1' / > { numWithUnits ( n . earnedSats , { abbreviate : false } ) } were deposited in your account
2023-08-06 15:47:58 +00:00
< small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
< / d i v >
2023-06-01 00:51:30 +00:00
)
}
2023-06-01 18:22:39 +00:00
function Referral ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< small className = 'fw-bold text-secondary ms-2' >
someone joined via one of your referral links
< small className = 'text-muted ms-1 fw-normal' suppressHydrationWarning > { timeSince ( new Date ( n . sortTime ) ) } < / s m a l l >
< / s m a l l >
2023-06-01 00:51:30 +00:00
)
}
2023-06-01 18:22:39 +00:00
function Votification ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< >
2023-07-24 18:35:05 +00:00
< small className = 'fw-bold text-success ms-2' >
2023-08-08 21:04:06 +00:00
your { n . item . title ? 'post' : 'reply' } { n . item . fwdUser ? 'forwarded' : 'stacked' } { numWithUnits ( n . earnedSats , { abbreviate : false } ) } { n . item . fwdUser && ` to @ ${ n . item . fwdUser . name } ` }
2023-06-01 00:51:30 +00:00
< / s m a l l >
< div >
{ n . item . title
? < Item item = { n . item } / >
: (
< div className = 'pb-2' >
< RootProvider root = { n . item . root } >
< Comment item = { n . item } noReply includeParent clickToContext / >
< / R o o t P r o v i d e r >
< / d i v >
2023-06-01 18:22:39 +00:00
) }
2023-06-01 00:51:30 +00:00
< / d i v >
2023-08-06 15:47:58 +00:00
< / >
2023-06-01 00:51:30 +00:00
)
}
2023-06-01 18:22:39 +00:00
function Mention ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< >
2023-07-24 18:35:05 +00:00
< small className = 'fw-bold text-info ms-2' >
2023-06-01 00:51:30 +00:00
you were mentioned in
< / s m a l l >
< div >
2023-06-01 18:22:39 +00:00
{ n . item . title
? < Item item = { n . item } / >
: (
< div className = 'pb-2' >
< RootProvider root = { n . item . root } >
< Comment item = { n . item } noReply includeParent rootText = { n . _ _typename === 'Reply' ? 'replying on:' : undefined } clickToContext / >
< / R o o t P r o v i d e r >
< / d i v > ) }
2023-06-01 00:51:30 +00:00
< / d i v >
2023-08-06 15:47:58 +00:00
< / >
2023-06-01 00:51:30 +00:00
)
}
2023-06-01 18:22:39 +00:00
function JobChanged ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< >
2023-07-24 18:35:05 +00:00
< small className = { ` fw-bold text- ${ n . item . status === 'ACTIVE' ? 'success' : 'boost' } ms-1 ` } >
2023-06-01 00:51:30 +00:00
{ n . item . status === 'ACTIVE'
? 'your job is active again'
: ( n . item . status === 'NOSATS'
? 'your job promotion ran out of sats'
: 'your job has been stopped' ) }
< / s m a l l >
< ItemJob item = { n . item } / >
2023-08-06 15:47:58 +00:00
< / >
2023-06-01 00:51:30 +00:00
)
}
2023-06-01 18:22:39 +00:00
function Reply ( { n } ) {
2023-06-01 00:51:30 +00:00
return (
2023-08-06 15:47:58 +00:00
< div className = 'py-2' >
{ n . item . title
? < Item item = { n . item } / >
: (
< div className = 'pb-2' >
< RootProvider root = { n . item . root } >
< Comment item = { n . item } noReply includeParent clickToContext rootText = 'replying on:' / >
< / R o o t P r o v i d e r >
< / d i v >
) }
< / d i v >
2023-06-01 00:51:30 +00:00
)
}
2023-08-03 19:56:59 +00:00
export function NotificationAlert ( ) {
2023-06-12 18:03:44 +00:00
const [ showAlert , setShowAlert ] = useState ( false )
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
const [ hasSubscription , setHasSubscription ] = useState ( false )
const [ error , setError ] = useState ( null )
2023-07-04 23:44:03 +00:00
const [ supported , setSupported ] = useState ( false )
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
const sw = useServiceWorker ( )
2023-06-12 18:03:44 +00:00
useEffect ( ( ) => {
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
const isSupported = sw . support . serviceWorker && sw . support . pushManager && sw . support . notification
2023-07-04 23:44:03 +00:00
if ( isSupported ) {
const isDefaultPermission = sw . permission . notification === 'default'
2023-07-25 14:14:45 +00:00
setShowAlert ( isDefaultPermission && ! window . localStorage . getItem ( 'hideNotifyPrompt' ) )
2023-07-04 23:44:03 +00:00
sw . registration ? . pushManager . getSubscription ( ) . then ( subscription => setHasSubscription ( ! ! subscription ) )
setSupported ( true )
}
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
} , [ sw ] )
2023-06-12 18:03:44 +00:00
const close = ( ) => {
2023-07-25 14:14:45 +00:00
window . localStorage . setItem ( 'hideNotifyPrompt' , 'yep' )
2023-06-12 18:03:44 +00:00
setShowAlert ( false )
}
return (
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
error
2023-06-12 18:03:44 +00:00
? (
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
< Alert variant = 'danger' dismissible onClose = { ( ) => setError ( null ) } >
< span > { error . toString ( ) } < / s p a n >
2023-06-12 18:03:44 +00:00
< / A l e r t >
)
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
: showAlert
? (
< Alert variant = 'info' dismissible onClose = { close } >
< span className = 'align-middle' > Enable push notifications ? < / s p a n >
< button
className = { ` ${ styles . alertBtn } mx-1 ` }
onClick = { async ( ) => {
await sw . requestNotificationPermission ( )
. then ( close )
. catch ( setError )
} }
> Yes
< / b u t t o n >
2023-07-25 14:14:45 +00:00
< button className = { styles . alertBtn } onClick = { close } > No < / b u t t o n >
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
< / A l e r t >
)
: (
2023-07-23 15:08:43 +00:00
< Form className = { ` d-flex justify-content-end ${ supported ? 'visible' : 'invisible' } ` } initial = { { pushNotify : hasSubscription } } >
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
< Checkbox
2023-07-04 22:19:59 +00:00
name = 'pushNotify' label = { < span className = 'text-muted' > push notifications < / s p a n > }
2023-07-24 18:35:05 +00:00
groupClassName = { ` ${ styles . subFormGroup } mb-1 me-sm-3 me-0 ` }
2023-07-04 22:19:59 +00:00
inline checked = { hasSubscription } handleChange = { async ( ) => {
Service worker rework, Web Target Share API & Web Push API (#324)
* npm uninstall next-pwa
next-pwa was last updated in August 2022.
There is also an issue which mentions that next-pwa is abandoned (?): https://github.com/shadowwalker/next-pwa/issues/482
But the main reason for me uninstalling it is that it adds a lot of preconfigured stuff which is not necessary for us.
It even lead to a bug since pages were cached without our knowledge.
So I will go with a different PWA approach. This different approach should do the following:
- make it more transparent what the service worker is doing
- gives us more control to configure the service worker and thus making it easier
* Use workbox-webpack-plugin
Every other plugin (`next-offline`, `next-workbox-webpack-plugin`, `next-with-workbox`, ...) added unnecessary configuration which felt contrary to how PWAs should be built.
(PWAs should progressivly enhance the website in small steps, see https://web.dev/learn/pwa/getting-started/#focus-on-a-feature)
These default configurations even lead to worse UX since they made invalid assumptions about stacker.news:
We _do not_ want to cache our start url and we _do not_ want to cache anything unless explicitly told to.
Almost every page on SN should be fresh for the best UX.
To achieve this, by default, the service worker falls back to the network (as if the service worker wasn't there).
Therefore, this should be the simplest configuration with a valid precache and cache busting support.
In the future, we can try to use prefetching to improve performance of navigation requests.
* Add support for Web Share Target API
See https://developer.chrome.com/articles/web-share-target/
* Use Web Push API for push notifications
I followed this (very good!) guide: https://web.dev/notifications/
* Refactor code related to Web Push
* Send push notification to users on events
* Merge notifications
* Send notification to author of every parent recursively
* Remove unused userId param in savePushSubscription
As it should be, the user id is retrieved from the authenticated user in the backend.
* Resubscribe user if push subscription changed
* Update old subscription if oldEndpoint was given
* Allow users to unsubscribe
* Use LTREE operator instead of recursive query
* Always show checkbox for push notifications
* Justify checkbox to end
* Update title of first push notification
* Fix warning from uncontrolled to controlled
* Add comment about Notification.requestPermission
* Fix timestamp
* Catch error on push subscription toggle
* Wrap function bodies in try/catch
* Use Promise.allSettled
* Filter subscriptions by user notification settings
* Fix user notification filter
* Use skipWaiting
---------
Co-authored-by: ekzyis <ek@stacker.news>
2023-07-04 19:36:07 +00:00
await sw . togglePushSubscription ( ) . catch ( setError )
} }
/ >
< / F o r m >
)
2023-06-12 18:03:44 +00:00
)
}
2023-08-04 00:14:04 +00:00
const nid = n => n . _ _typename + n . id + n . sortTime
2023-07-23 15:08:43 +00:00
export default function Notifications ( { ssrData } ) {
const { data , fetchMore } = useQuery ( NOTIFICATIONS )
2023-08-03 19:56:59 +00:00
const router = useRouter ( )
2023-08-06 18:04:25 +00:00
const dat = useData ( data , ssrData )
2023-07-23 15:08:43 +00:00
2023-08-04 00:14:04 +00:00
const { notifications : { notifications , lastChecked , cursor } } = useMemo ( ( ) => {
2023-08-06 18:04:25 +00:00
return dat || { notifications : { } }
} , [ dat ] )
2021-08-20 00:13:32 +00:00
2023-08-03 19:56:59 +00:00
useEffect ( ( ) => {
2023-08-06 15:47:58 +00:00
if ( lastChecked && ! router ? . query ? . checkedAt ) {
2023-08-03 23:04:43 +00:00
router . replace ( {
pathname : router . pathname ,
query : {
... router . query ,
nodata : true , // make sure nodata is set so we don't fetch on back/forward
checkedAt : lastChecked
}
2023-08-04 17:07:44 +00:00
} , router . asPath , { ... router . options , shallow : true } )
2023-08-03 19:56:59 +00:00
}
2023-08-06 15:47:58 +00:00
} , [ router , lastChecked ] )
2023-07-23 15:08:43 +00:00
2023-08-06 18:04:25 +00:00
if ( ! dat ) return < CommentsFlatSkeleton / >
2021-08-17 23:59:22 +00:00
2021-08-17 18:15:24 +00:00
return (
< >
2023-08-06 15:47:58 +00:00
{ notifications . map ( n =>
< Notification
n = { n } key = { nid ( n ) }
fresh = { new Date ( n . sortTime ) > new Date ( router ? . query ? . checkedAt ) }
/ > ) }
2023-07-23 15:08:43 +00:00
< MoreFooter cursor = { cursor } count = { notifications ? . length } fetchMore = { fetchMore } Skeleton = { CommentsFlatSkeleton } noMoreText = 'NO MORE' / >
2021-08-17 18:15:24 +00:00
< / >
)
}
function CommentsFlatSkeleton ( ) {
const comments = new Array ( 21 ) . fill ( null )
return (
2023-07-23 15:08:43 +00:00
< div >
{ comments . map ( ( _ , i ) => (
< CommentSkeleton key = { i } skeletonChildren = { 0 } / >
) ) }
2021-08-17 18:15:24 +00:00
< / d i v >
)
}