mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 06:33:55 +01:00
[web] open comments and changes via url (#36546)
* [web] open a comment from a URL * [web] add "View comment" button to digest email * [web] open a tracked change from a URL * [web] add "View suggestion" button to digest email * [web] open the review panel from an open=review-panel URL * [web] drop the digest email CTA and collapse the deep links to t=review * [web] add an Open project button to the section overflow row and plain-text view links * [web] validate the comment and change deep-link ids as object ids * [web] collapse the deep-link resolvers into a single DeepLink component * fix: scrolling into view when below the fold * fix: address claude review * fix: style adjustments * fix: updating types to accomodate accepted/rejected changes --------- Co-authored-by: Jimmy Domagala-Tang <jimmy.domagala-tang@overleaf.com> GitOrigin-RevId: dab535591a93e1c3849a6b2a0dcc3e3934354385
This commit is contained in:
committed by
Copybot
co-authored by
Jimmy Domagala-Tang
parent
5dc8c2b0c3
commit
44910202f3
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
createContext,
|
||||
FC,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { ThreadId } from '../../../../../types/review-panel/review-panel'
|
||||
import { showCommentNotFoundToast } from '@/features/ide-react/components/deep-link-toasts'
|
||||
|
||||
const DEEP_LINK_PARAMS = ['doc', 'comment', 'change', 'open'] as const
|
||||
|
||||
// the `open` param names either an editor panel or a settings tab, so it
|
||||
// resolves into one of two fields and the other stays null
|
||||
type DeepLinkPanel = 'review-panel'
|
||||
type DeepLinkSettings = 'project-notifications'
|
||||
|
||||
type DeepLinkParams = {
|
||||
docId: string | null
|
||||
threadId: ThreadId | null
|
||||
changeId: string | null
|
||||
panel: DeepLinkPanel | null
|
||||
settings: DeepLinkSettings | null
|
||||
}
|
||||
|
||||
type DeepLinkContextValue = {
|
||||
deepLinkedDocId: string | null
|
||||
deepLinkedThreadId: ThreadId | null
|
||||
deepLinkedChangeId: string | null
|
||||
deepLinkedPanel: DeepLinkPanel | null
|
||||
deepLinkedSettings: DeepLinkSettings | null
|
||||
clearDeepLinkedThread: () => void
|
||||
clearDeepLinkedChange: () => void
|
||||
reportDeepLinkedThreadNotFound: () => void
|
||||
}
|
||||
|
||||
const DeepLinkContext = createContext<DeepLinkContextValue | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
function readPanel(value: string | null): DeepLinkPanel | null {
|
||||
if (value === 'review-panel') {
|
||||
return value
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function readSettings(value: string | null): DeepLinkSettings | null {
|
||||
if (value === 'project-notifications') {
|
||||
return value
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function readDeepLinkParams(): DeepLinkParams {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const open = params.get('open')
|
||||
|
||||
return {
|
||||
docId: params.get('doc'),
|
||||
threadId: params.get('comment') as ThreadId | null,
|
||||
changeId: params.get('change'),
|
||||
panel: readPanel(open),
|
||||
settings: readSettings(open),
|
||||
}
|
||||
}
|
||||
|
||||
function stripDeepLinkParams() {
|
||||
const url = new URL(window.location.href)
|
||||
let changed = false
|
||||
|
||||
for (const param of DEEP_LINK_PARAMS) {
|
||||
if (url.searchParams.has(param)) {
|
||||
url.searchParams.delete(param)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
window.history.replaceState(window.history.state, '', url.toString())
|
||||
}
|
||||
}
|
||||
|
||||
export const DeepLinkProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const paramsRef = useRef<DeepLinkParams | null>(null)
|
||||
if (paramsRef.current === null) {
|
||||
paramsRef.current = readDeepLinkParams()
|
||||
}
|
||||
|
||||
const { docId, panel, settings } = paramsRef.current
|
||||
const [threadId, setThreadId] = useState(paramsRef.current.threadId)
|
||||
const [changeId, setChangeId] = useState(paramsRef.current.changeId)
|
||||
|
||||
useEffect(() => {
|
||||
stripDeepLinkParams()
|
||||
}, [])
|
||||
|
||||
const clearDeepLinkedThread = useCallback(() => setThreadId(null), [])
|
||||
const clearDeepLinkedChange = useCallback(() => setChangeId(null), [])
|
||||
|
||||
// several places give up on the comment at different points, so keep the
|
||||
// toast and the clear together in one call rather than pairing them by hand
|
||||
const reportDeepLinkedThreadNotFound = useCallback(() => {
|
||||
showCommentNotFoundToast()
|
||||
setThreadId(null)
|
||||
}, [])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
deepLinkedDocId: docId,
|
||||
deepLinkedThreadId: threadId,
|
||||
deepLinkedChangeId: changeId,
|
||||
deepLinkedPanel: panel,
|
||||
deepLinkedSettings: settings,
|
||||
clearDeepLinkedThread,
|
||||
clearDeepLinkedChange,
|
||||
reportDeepLinkedThreadNotFound,
|
||||
}),
|
||||
[
|
||||
docId,
|
||||
threadId,
|
||||
changeId,
|
||||
panel,
|
||||
settings,
|
||||
clearDeepLinkedThread,
|
||||
clearDeepLinkedChange,
|
||||
reportDeepLinkedThreadNotFound,
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<DeepLinkContext.Provider value={value}>
|
||||
{children}
|
||||
</DeepLinkContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useDeepLinkContext(): DeepLinkContextValue {
|
||||
const context = useContext(DeepLinkContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useDeepLinkContext is only available inside DeepLinkProvider'
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
Reference in New Issue
Block a user