diff --git a/api/resolvers/item.js b/api/resolvers/item.js index 1f55c991..51085ba2 100644 --- a/api/resolvers/item.js +++ b/api/resolvers/item.js @@ -743,7 +743,7 @@ export default { subMaxBoost: subAgg?._max.boost || 0 } }, - newComments: async (parent, { topLevelId, after }, { models, me }) => { + newComments: async (parent, { itemId, after }, { models, me }) => { const comments = await itemQueryWithMeta({ me, models, @@ -757,7 +757,7 @@ export default { '"Item"."created_at" > $2' )} ORDER BY "Item"."created_at" ASC` - }, Number(topLevelId), after) + }, Number(itemId), after) return { comments } } diff --git a/api/typeDefs/item.js b/api/typeDefs/item.js index 1824547e..dfc638fa 100644 --- a/api/typeDefs/item.js +++ b/api/typeDefs/item.js @@ -12,7 +12,7 @@ export default gql` auctionPosition(sub: String, id: ID, boost: Int): Int! boostPosition(sub: String, id: ID, boost: Int): BoostPositions! itemRepetition(parentId: ID): Int! - newComments(topLevelId: ID, after: Date): Comments! + newComments(itemId: ID, after: Date): Comments! } type BoostPositions { @@ -151,7 +151,6 @@ export default gql` ncomments: Int! nDirectComments: Int! comments(sort: String, cursor: String): Comments! - injected: Boolean! path: String position: Int prior: Int diff --git a/components/comment.js b/components/comment.js index 0130591a..f9cbcfa8 100644 --- a/components/comment.js +++ b/components/comment.js @@ -120,11 +120,11 @@ export default function Comment ({ const classes = ref.current.classList const hasOutline = classes.contains('outline-new-comment') - const hasInjectedOutline = classes.contains('outline-new-injected-comment') + const hasLiveOutline = classes.contains('outline-new-live-comment') const hasOutlineUnset = classes.contains('outline-new-comment-unset') // don't try to untrack and unset the outline if the comment is not outlined or we already unset the outline - if (!(hasInjectedOutline || hasOutline) || hasOutlineUnset) return + if (!(hasLiveOutline || hasOutline) || hasOutlineUnset) return classes.add('outline-new-comment-unset') // untrack new comment and its descendants if it's not a live comment @@ -166,22 +166,22 @@ export default function Comment ({ const viewedAt = me?.id ? meViewedAt : router.query.commentsViewedAt const isNewComment = viewedAt && itemCreatedAt > viewedAt - // injected comments are new regardless of me or anon view time + // live comments are new regardless of me or anon view time const rootLast = new Date(root.lastCommentAt || root.createdAt).getTime() - const isNewInjectedComment = item.injected && itemCreatedAt > (meViewedAt || rootLast) + const isNewLiveComment = item.live && itemCreatedAt > (meViewedAt || rootLast) - if (!isNewComment && !isNewInjectedComment) return + if (!isNewComment && !isNewLiveComment) return - if (item.injected) { - // newly injected comments (item.injected) have to use a different class to outline every new comment - ref.current.classList.add('outline-new-injected-comment') + if (item.live) { + // live comments (item.live) have to use a different class to outline every new comment + ref.current.classList.add('outline-new-live-comment') // wait for the injection animation to end before removing its class ref.current.addEventListener('animationend', () => { - ref.current.classList.remove(styles.injectedComment) + ref.current.classList.remove(styles.liveComment) }, { once: true }) // animate the live comment injection - ref.current.classList.add(styles.injectedComment) + ref.current.classList.add(styles.liveComment) } else { ref.current.classList.add('outline-new-comment') } diff --git a/components/comment.module.css b/components/comment.module.css index efd2eb96..ee2189ae 100644 --- a/components/comment.module.css +++ b/components/comment.module.css @@ -139,7 +139,7 @@ padding-top: .5rem; } -.injectedComment { +.liveComment { animation: fadeIn 0.5s ease-out; } diff --git a/components/comments.js b/components/comments.js index 713f37c6..98e7cd24 100644 --- a/components/comments.js +++ b/components/comments.js @@ -71,7 +71,7 @@ export default function Comments ({ const router = useRouter() // fetch new comments that arrived after the lastCommentAt, and update the item.comments field in cache - useLiveComments(parentId, lastCommentAt || parentCreatedAt, router.query.sort) + useLiveComments(parentId, lastCommentAt || parentCreatedAt) // new comments navigator, tracks new comments and provides navigation controls const { navigator } = useCommentsNavigatorContext() diff --git a/components/preserve-scroll.js b/components/preserve-scroll.js index 6a4ab093..ea472699 100644 --- a/components/preserve-scroll.js +++ b/components/preserve-scroll.js @@ -4,8 +4,7 @@ export default function preserveScroll (callback) { // if the scroll position is at the top, we don't need to preserve it, just call the callback if (scrollTop <= 0) { - callback() - return + return callback() } // get a reference element at the center of the viewport to track if content is added above it @@ -49,5 +48,5 @@ export default function preserveScroll (callback) { observer.observe(document.body, { childList: true, subtree: true }) - callback() + return callback() } diff --git a/components/reply.js b/components/reply.js index 5d6c5837..b47c9757 100644 --- a/components/reply.js +++ b/components/reply.js @@ -1,6 +1,5 @@ import { Form, MarkdownInput } from '@/components/form' import styles from './reply.module.css' -import { COMMENTS } from '@/fragments/comments' import { useMe } from './me' import { forwardRef, useCallback, useEffect, useState, useRef, useMemo } from 'react' import { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button' @@ -10,9 +9,9 @@ import { useShowModal } from './modal' import { Button } from 'react-bootstrap' import { useRoot } from './root' import { CREATE_COMMENT } from '@/fragments/paidAction' +import { injectComment } from '@/lib/comments' import useItemSubmit from './use-item-submit' import gql from 'graphql-tag' -import { updateAncestorsCommentCount } from '@/lib/comments' import useCommentsView from './use-comments-view' export default forwardRef(function Reply ({ @@ -52,23 +51,11 @@ export default forwardRef(function Reply ({ update (cache, { data: { upsertComment: { result, invoice } } }) { if (!result) return - cache.modify({ - id: `Item:${parentId}`, - fields: { - comments (existingComments = {}) { - const newCommentRef = cache.writeFragment({ - data: result, - fragment: COMMENTS, - fragmentName: 'CommentsRecursive' - }) - return { - cursor: existingComments.cursor, - comments: [newCommentRef, ...(existingComments?.comments || [])] - } - } - }, - optimistic: true - }) + // inject the new comment into the cache + const injected = injectComment(cache, result) + if (injected) { + markCommentViewedAt(result.createdAt, { ncomments: 1 }) + } // no lag for itemRepetition if (!item.mine && me) { @@ -80,15 +67,6 @@ export default forwardRef(function Reply ({ } }) } - - const ancestors = item.path.split('.') - - // update all ancestors - updateAncestorsCommentCount(cache, ancestors, 1, parentId) - - // so that we don't see indicator for our own comments, we record this comments as the latest time - // but we also have record num comments, in case someone else commented when we did - markCommentViewedAt(result.createdAt, { ncomments: 1 }) } }, onSuccessfulSubmit: (data, { resetForm }) => { diff --git a/components/use-can-edit.js b/components/use-can-edit.js index b97596e4..282c46c4 100644 --- a/components/use-can-edit.js +++ b/components/use-can-edit.js @@ -20,7 +20,8 @@ export default function useCanEdit (item) { const anonEdit = !!invParams && !me && Number(item.user.id) === USER_ID.anon // anonEdit should not override canEdit, but only allow edits if they aren't already allowed setCanEdit(canEdit => canEdit || anonEdit) - }, []) + // update when the hmac gets set + }, [item?.invoice?.hmac]) return [canEdit, setCanEdit, editThreshold] } diff --git a/components/use-comments-navigator.js b/components/use-comments-navigator.js index d425e977..bc27d4d4 100644 --- a/components/use-comments-navigator.js +++ b/components/use-comments-navigator.js @@ -109,7 +109,7 @@ export function useCommentsNavigator () { node.classList.remove( 'outline-it', 'outline-new-comment', - 'outline-new-injected-comment' + 'outline-new-live-comment' ) node.classList.add('outline-new-comment-unset') } diff --git a/components/use-live-comments.js b/components/use-live-comments.js index 91249ecc..f462e4f7 100644 --- a/components/use-live-comments.js +++ b/components/use-live-comments.js @@ -1,156 +1,97 @@ +import { useEffect, useState, useCallback } from 'react' +import { useQuery, useApolloClient } from '@apollo/client' +import { SSR } from '../lib/constants' import preserveScroll from './preserve-scroll' import { GET_NEW_COMMENTS } from '../fragments/comments' -import { useEffect, useState, useCallback } from 'react' -import { SSR, COMMENT_DEPTH_LIMIT } from '../lib/constants' -import { useQuery, useApolloClient } from '@apollo/client' +import { injectComment } from '../lib/comments' import useCommentsView from './use-comments-view' -import { - updateItemQuery, - updateCommentFragment, - updateAncestorsCommentCount, - calculateDepth -} from '../lib/comments' -const POLL_INTERVAL = 1000 * 5 // 5 seconds +// live comments polling interval +const POLL_INTERVAL = 1000 * 5 +// live comments toggle keys +const STORAGE_DISABLE_KEY = 'disableLiveComments' +const TOGGLE_EVENT = 'liveComments:toggle' -// prepares and creates a fragment for injection into the cache -// also handles side effects like updating comment counts and viewedAt timestamps -function prepareComments (item, cache, newComment) { - const existingComments = item.comments?.comments || [] - - // is the incoming new comment already in item's existing comments? - // if so, we don't need to update the cache - if (existingComments.some(comment => comment.id === newComment.id)) return item - - // count the new comment (+1) and its children (+ncomments) - const totalNComments = newComment.ncomments + 1 - - const itemHierarchy = item.path.split('.') - // update all ancestors comment count, but not the item itself - const ancestors = itemHierarchy.slice(0, -1) - updateAncestorsCommentCount(cache, ancestors, totalNComments) - - // add a flag to the new comment to indicate it was injected - const injectedComment = { ...newComment, injected: true } - - // an item can either have a comments.comments field, or not - const payload = item.comments - ? { - ...item, - ncomments: item.ncomments + totalNComments, - nDirectComments: item.nDirectComments + 1, - comments: { - ...item.comments, - comments: [injectedComment, ...item.comments.comments] - } - } - // when the fragment doesn't have a comments field, we just update stats fields - : { - ...item, - ncomments: item.ncomments + totalNComments, - nDirectComments: item.nDirectComments + 1 - } - - return payload +const readStoredLatest = (key, latest) => { + const stored = window.sessionStorage.getItem(key) + return stored && stored > latest ? stored : latest } -function cacheNewComments (cache, latest, topLevelId, newComments, sort) { - let injectedLatest = latest - for (const newComment of newComments) { - const { parentId } = newComment - const topLevel = Number(parentId) === Number(topLevelId) - let injected = false +// cache new comments and return the most recent timestamp between current latest and new comment +// regardless of whether the comments were injected or not +function cacheNewComments (cache, latest, itemId, newComments, markCommentViewedAt) { + let injected = 0 - // if the comment is a top level comment, update the item, else update the parent comment - if (topLevel) { - updateItemQuery(cache, topLevelId, sort, (item) => prepareComments(item, cache, newComment)) - injected = true - } else { - // if the comment is too deep, we can skip it - const depth = calculateDepth(newComment.path, topLevelId, parentId) - if (depth > COMMENT_DEPTH_LIMIT) continue - // inject the new comment into the parent comment's comments field - const updated = updateCommentFragment(cache, parentId, (parent) => prepareComments(parent, cache, newComment)) - injected = !!updated - } + const injectedLatest = newComments.reduce((latestTimestamp, newComment) => { + const result = injectComment(cache, newComment, { live: true, rootId: itemId }) + // if any comment was injected, increment injected + injected = result ? injected + 1 : injected + return new Date(newComment.createdAt) > new Date(latestTimestamp) + ? newComment.createdAt + : latestTimestamp + }, latest) - // update latest timestamp to the latest comment created at - if (injected && new Date(newComment.createdAt).getTime() > new Date(injectedLatest).getTime()) { - injectedLatest = newComment.createdAt - } + if (injected > 0) { + markCommentViewedAt(injectedLatest, { ncomments: injected }) } return injectedLatest } -// useLiveComments fetches new comments under an item (topLevelId), -// that are newer than the latest comment createdAt (after), and injects them into the cache. -export default function useLiveComments (topLevelId, after, sort) { - const latestKey = `liveCommentsLatest:${topLevelId}` +// fetches comments for an item that are newer than the latest comment createdAt (after), +// injects them into cache, and keeps scroll position stable. +export default function useLiveComments (itemId, after) { + const latestKey = `liveCommentsLatest:${itemId}` const { cache } = useApolloClient() - const { markCommentViewedAt } = useCommentsView(topLevelId) + const { markCommentViewedAt } = useCommentsView(itemId) const [disableLiveComments] = useLiveCommentsToggle() + const [latest, setLatest] = useState(after) const [initialized, setInitialized] = useState(false) useEffect(() => { - const storedLatest = window.sessionStorage.getItem(latestKey) - if (storedLatest && storedLatest > after) { - setLatest(storedLatest) - } else { - setLatest(after) - } - + setLatest(readStoredLatest(latestKey, after)) // Apollo might update the cache before the page has fully rendered, causing reads of stale cached data // this prevents GET_NEW_COMMENTS from producing results before the page has fully rendered setInitialized(true) - }, [topLevelId, after]) + }, [itemId, after]) const { data } = useQuery(GET_NEW_COMMENTS, { pollInterval: POLL_INTERVAL, // only get comments newer than the passed latest timestamp - variables: { topLevelId, after: latest }, + variables: { itemId, after: latest }, nextFetchPolicy: 'cache-and-network', skip: SSR || !initialized || disableLiveComments }) useEffect(() => { - if (!data?.newComments?.comments?.length) return + const newComments = data?.newComments?.comments + if (!newComments?.length) return // directly inject new comments into the cache, preserving scroll position // quirk: scroll is preserved even if we are not injecting new comments due to dedupe - let injectedLatest = latest - preserveScroll(() => { - injectedLatest = cacheNewComments(cache, injectedLatest, topLevelId, data.newComments.comments, sort) - }) + const injectedLatest = preserveScroll(() => cacheNewComments(cache, latest, itemId, newComments, markCommentViewedAt)) - // sync view time if we successfully injected new comments - if (new Date(injectedLatest).getTime() > new Date(latest).getTime()) { - markCommentViewedAt(injectedLatest) + // if we didn't process any newer comments, bail + if (new Date(injectedLatest).getTime() <= new Date(latest).getTime()) return - // update latest timestamp to the latest comment created at - // save it to session storage, to persist between client-side navigations - setLatest(injectedLatest) - if (typeof window !== 'undefined') { - window.sessionStorage.setItem(latestKey, injectedLatest) - } - } - }, [data, cache, topLevelId, sort, latest, markCommentViewedAt]) + // update latest timestamp to the latest comment created at + // save it to session storage, to persist between client-side navigations + setLatest(injectedLatest) + window.sessionStorage.setItem(latestKey, injectedLatest) + }, [data, cache, itemId, latest, markCommentViewedAt]) } -const STORAGE_KEY = 'disableLiveComments' -const TOGGLE_EVENT = 'liveComments:toggle' - export function useLiveCommentsToggle () { const [disableLiveComments, setDisableLiveComments] = useState(false) useEffect(() => { // preference: local storage - const read = () => setDisableLiveComments(window.localStorage.getItem(STORAGE_KEY) === 'true') + const read = () => setDisableLiveComments(window.localStorage.getItem(STORAGE_DISABLE_KEY) === 'true') read() // update across tabs - const onStorage = e => { if (e.key === STORAGE_KEY) read() } + const onStorage = e => { if (e.key === STORAGE_DISABLE_KEY) read() } // update this tab const onToggle = () => read() @@ -163,12 +104,11 @@ export function useLiveCommentsToggle () { }, []) const toggle = useCallback(() => { - const current = window.localStorage.getItem(STORAGE_KEY) === 'true' - - window.localStorage.setItem(STORAGE_KEY, !current) + const current = window.localStorage.getItem(STORAGE_DISABLE_KEY) === 'true' + window.localStorage.setItem(STORAGE_DISABLE_KEY, !current) // trigger local event to update this tab window.dispatchEvent(new Event(TOGGLE_EVENT)) - }, [disableLiveComments]) + }, []) return [disableLiveComments, toggle] } diff --git a/components/use-paid-mutation.js b/components/use-paid-mutation.js index 110ac17b..022f1056 100644 --- a/components/use-paid-mutation.js +++ b/components/use-paid-mutation.js @@ -207,7 +207,8 @@ export const paidActionCacheMods = { fields: { actionState: () => 'PAID', confirmedAt: () => new Date().toISOString(), - satsReceived: () => invoice.satsRequested + satsReceived: () => invoice.satsRequested, + hmac: () => invoice.hmac } }) } diff --git a/fragments/comments.js b/fragments/comments.js index e9ddfd16..db60e10d 100644 --- a/fragments/comments.js +++ b/fragments/comments.js @@ -47,7 +47,7 @@ export const COMMENT_FIELDS = gql` otsHash ncomments nDirectComments - injected @client + live @client imgproxyUrls rel apiKey @@ -55,6 +55,7 @@ export const COMMENT_FIELDS = gql` id actionState confirmedAt + hmac } cost } @@ -94,6 +95,7 @@ export const COMMENT_FIELDS_NO_CHILD_COMMENTS = gql` commentCredits mine otsHash + live @client imgproxyUrls rel apiKey @@ -101,6 +103,7 @@ export const COMMENT_FIELDS_NO_CHILD_COMMENTS = gql` id actionState confirmedAt + hmac } cost } @@ -174,48 +177,19 @@ export const COMMENTS = gql` } }` -export const COMMENT_WITH_NEW_RECURSIVE = gql` - ${COMMENT_FIELDS} - ${COMMENTS} - - fragment CommentWithNewRecursive on Item { - ...CommentFields - comments { - comments { - ...CommentsRecursive - } - } - } -` - -export const COMMENT_WITH_NEW_LIMITED = gql` - ${COMMENT_FIELDS} - - fragment CommentWithNewLimited on Item { - ...CommentFields - comments { - comments { - ...CommentFields - } - } - } -` - -export const COMMENT_WITH_NEW_MINIMAL = gql` - ${COMMENT_FIELDS} - - fragment CommentWithNewMinimal on Item { - ...CommentFields +export const HAS_COMMENTS = gql` + fragment HasComments on Item { + comments } ` export const GET_NEW_COMMENTS = gql` - ${COMMENTS} + ${COMMENT_FIELDS_NO_CHILD_COMMENTS} - query GetNewComments($topLevelId: ID, $after: Date) { - newComments(topLevelId: $topLevelId, after: $after) { + query GetNewComments($itemId: ID, $after: Date) { + newComments(itemId: $itemId, after: $after) { comments { - ...CommentsRecursive + ...CommentFieldsNoChildComments } } } diff --git a/lib/apollo.js b/lib/apollo.js index f1887015..b05d1a94 100644 --- a/lib/apollo.js +++ b/lib/apollo.js @@ -324,9 +324,9 @@ function getClient (uri) { } } }, - injected: { - read (injected) { - return injected || false + live: { + read (live) { + return live || false } }, meAnonSats: { diff --git a/lib/comments.js b/lib/comments.js index 15e7a80f..264d8db2 100644 --- a/lib/comments.js +++ b/lib/comments.js @@ -1,98 +1,87 @@ -import { COMMENT_WITH_NEW_RECURSIVE, COMMENT_WITH_NEW_LIMITED, COMMENT_WITH_NEW_MINIMAL } from '../fragments/comments' -import { ITEM_FULL } from '../fragments/items' +import { COMMENTS, HAS_COMMENTS } from '../fragments/comments' -// updates the ncomments field of all ancestors of an item/comment in the cache -export function updateAncestorsCommentCount (cache, ancestors, increment, parentId) { - // update all ancestors +// adds a comment to the cache, under its parent item +function cacheComment (cache, newComment, { live = false }) { + return cache.modify({ + id: `Item:${newComment.parentId}`, + fields: { + comments: (existingComments = {}, { readField }) => { + // if the comment already exists, return + if (existingComments?.comments?.some(c => readField('id', c) === newComment.id)) return existingComments + + // we need to make sure we're writing a fragment that matches the comments query (comments and count fields) + const newCommentRef = cache.writeFragment({ + data: { + comments: { + comments: [] + }, + ncomments: 0, + nDirectComments: 0, + ...newComment, + live + }, + fragment: COMMENTS, + fragmentName: 'CommentsRecursive' + }) + + return { + cursor: existingComments.cursor, + comments: [newCommentRef, ...(existingComments?.comments || [])] + } + } + }, + optimistic: true + }) +} + +// handles cache injection and side-effects for both live and non-live comments +export function injectComment (cache, newComment, { live = false, rootId } = {}) { + // if live and a reply (not top level), check if the parent has comments + const hasComments = live && !(Number(rootId) === Number(newComment.parentId)) + ? !!(cache.readFragment({ + id: `Item:${newComment.parentId}`, + fragment: HAS_COMMENTS + })) + // if not live, we can assume the parent has the comments field since user replied to it + : true + + const updated = hasComments && cacheComment(cache, newComment, { live }) + + // run side effects if injection succeeded or if injecting live comment into SSR item without comments field + if (updated || (live && !hasComments)) { + // update all ancestors comment count, excluding the comment itself + const ancestors = newComment.path.split('.').slice(0, -1) + updateAncestorsCommentCount(cache, ancestors) + + return true + } + + return false +} + +// updates the ncomments and nDirectComments fields of all ancestors of an item/comment in the cache +function updateAncestorsCommentCount (cache, ancestors, { ncomments = 1, nDirectComments = 1 } = {}) { + // update nDirectComments of immediate parent + cache.modify({ + id: `Item:${ancestors[ancestors.length - 1]}`, + fields: { + nDirectComments (existingNDirectComments = 0) { + return existingNDirectComments + nDirectComments + } + }, + optimistic: true + }) + + // update ncomments of all ancestors ancestors.forEach(id => { cache.modify({ id: `Item:${id}`, fields: { ncomments (existingNComments = 0) { - return existingNComments + increment - }, - nDirectComments (existingNDirectComments = 0) { - // only increment nDirectComments for the immediate parent - if (parentId && Number(id) === Number(parentId)) { - return existingNDirectComments + 1 - } - return existingNDirectComments + return existingNComments + ncomments } }, optimistic: true }) }) } - -// updates the item query in the cache -// this is used by live comments to update a top level item's comments field -export function updateItemQuery (cache, id, sort, fn) { - cache.updateQuery({ - query: ITEM_FULL, - // updateQuery needs the correct variables to update the correct item - // the Item query might have the router.query.sort in the variables, so we need to pass it in if it exists - variables: sort ? { id, sort } : { id } - }, (data) => { - if (!data) return data - return { item: fn(data.item) } - }) -} - -// updates a comment fragment in the cache, with fallbacks for comments lacking CommentsRecursive or Comments altogether -export function updateCommentFragment (cache, id, fn) { - let result = cache.updateFragment({ - id: `Item:${id}`, - fragment: COMMENT_WITH_NEW_RECURSIVE, - fragmentName: 'CommentWithNewRecursive' - }, (data) => { - if (!data) return data - return fn(data) - }) - - // sometimes comments can start to reach their depth limit, and lack adherence to the CommentsRecursive fragment - // for this reason, we update the fragment with a limited version that only includes the CommentFields fragment - if (!result) { - result = cache.updateFragment({ - id: `Item:${id}`, - fragment: COMMENT_WITH_NEW_LIMITED, - fragmentName: 'CommentWithNewLimited' - }, (data) => { - if (!data) return data - return fn(data) - }) - } - - // at the deepest level, the comment can't have any children, here we update only the newComments field. - if (!result) { - result = cache.updateFragment({ - id: `Item:${id}`, - fragment: COMMENT_WITH_NEW_MINIMAL, - fragmentName: 'CommentWithNewMinimal' - }, (data) => { - if (!data) return data - return fn(data) - }) - } - - return result -} - -export function calculateDepth (path, rootId, parentId) { - // calculate depth by counting path segments from root to parent - const pathSegments = path.split('.') - const rootIndex = pathSegments.indexOf(rootId.toString()) - const parentIndex = pathSegments.indexOf(parentId.toString()) - - // depth is the distance from root to parent in the path - const depth = parentIndex - rootIndex - - return depth -} - -// finds the most recent createdAt timestamp from an array of comments -export function getLatestCommentCreatedAt (comments, latest) { - return comments.reduce( - (max, { createdAt }) => (createdAt > max ? createdAt : max), - latest - ) -} diff --git a/styles/globals.scss b/styles/globals.scss index 0d4a7bd4..8d2d174a 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -903,7 +903,7 @@ div[contenteditable]:focus, box-shadow: inset 0 0 1px 1px rgba(0, 123, 190, 0.25); } -.outline-new-injected-comment { +.outline-new-live-comment { box-shadow: inset 0 0 1px 1px rgba(0, 123, 190, 0.25); } @@ -911,7 +911,7 @@ div[contenteditable]:focus, box-shadow: none; } -.outline-new-injected-comment.outline-new-comment-unset { +.outline-new-live-comment.outline-new-comment-unset { box-shadow: none; }