mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2025-07-29 23:00:08 +02:00

[web] Editor redesign: Add full project search GitOrigin-RevId: b4327c4ba0ddd7387ec8d6640e31200ca0fe4a6e
173 lines
5 KiB
TypeScript
173 lines
5 KiB
TypeScript
import { sendSearchEvent } from '@/features/event-tracking/search-events'
|
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
|
import { useLayoutContext } from '@/shared/context/layout-context'
|
|
import { closeSearchPanel, SearchQuery } from '@codemirror/search'
|
|
import { forwardRef, memo, Ref, useCallback, useEffect, useRef } from 'react'
|
|
import { useCodeMirrorViewContext } from './codemirror-context'
|
|
import MaterialIcon from '@/shared/components/material-icon'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { Overlay, Popover } from 'react-bootstrap'
|
|
import Close from '@/shared/components/close'
|
|
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
|
import { useEditorContext } from '@/shared/context/editor-context'
|
|
import getMeta from '@/utils/meta'
|
|
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
|
|
import { useRailContext } from '@/features/ide-redesign/contexts/rail-context'
|
|
|
|
const PROMOTION_SIGNUP_CUT_OFF_DATE = new Date('2025-04-22T00:00:00Z')
|
|
|
|
export const FullProjectSearchButton = ({ query }: { query: SearchQuery }) => {
|
|
const view = useCodeMirrorViewContext()
|
|
const { t } = useTranslation()
|
|
const { setProjectSearchIsOpen } = useLayoutContext()
|
|
const newEditor = useIsNewEditorEnabled()
|
|
const { openTab } = useRailContext()
|
|
const ref = useRef<HTMLButtonElement>(null)
|
|
|
|
const { inactiveTutorials } = useEditorContext()
|
|
|
|
const hasCompletedTutorial = inactiveTutorials.includes(
|
|
'full-project-search-promo'
|
|
)
|
|
|
|
const { showPopup, tryShowingPopup, hideUntilReload, completeTutorial } =
|
|
useTutorial('full-project-search-promo', {
|
|
name: 'full-project-search-promotion',
|
|
})
|
|
|
|
let isEligibleForPromotion = true
|
|
const signUpDateString = getMeta('ol-user')?.signUpDate
|
|
if (!signUpDateString) {
|
|
isEligibleForPromotion = false
|
|
} else {
|
|
const signupDate = new Date(signUpDateString)
|
|
if (signupDate > PROMOTION_SIGNUP_CUT_OFF_DATE) {
|
|
isEligibleForPromotion = false
|
|
}
|
|
}
|
|
|
|
const openFullProjectSearch = useCallback(() => {
|
|
if (newEditor) {
|
|
openTab('full-project-search')
|
|
} else {
|
|
setProjectSearchIsOpen(true)
|
|
}
|
|
closeSearchPanel(view)
|
|
window.setTimeout(() => {
|
|
window.dispatchEvent(
|
|
new CustomEvent('editor:full-project-search', { detail: query })
|
|
)
|
|
}, 200)
|
|
}, [setProjectSearchIsOpen, query, view, newEditor, openTab])
|
|
|
|
const onClick = useCallback(() => {
|
|
sendSearchEvent('search-open', {
|
|
searchType: 'full-project',
|
|
method: 'button',
|
|
location: 'search-form',
|
|
})
|
|
openFullProjectSearch()
|
|
if (!hasCompletedTutorial && isEligibleForPromotion) {
|
|
completeTutorial({ action: 'complete', event: 'promo-click' })
|
|
}
|
|
}, [
|
|
completeTutorial,
|
|
openFullProjectSearch,
|
|
hasCompletedTutorial,
|
|
isEligibleForPromotion,
|
|
])
|
|
|
|
return (
|
|
<>
|
|
<OLTooltip
|
|
id="open-full-project-search"
|
|
overlayProps={{ placement: 'bottom' }}
|
|
description={t('search_all_project_files')}
|
|
>
|
|
<OLButton variant="secondary" size="sm" ref={ref} onClick={onClick}>
|
|
<MaterialIcon
|
|
type="manage_search"
|
|
accessibilityLabel={t('search_all_project_files')}
|
|
/>
|
|
</OLButton>
|
|
</OLTooltip>
|
|
{!hasCompletedTutorial && isEligibleForPromotion && (
|
|
<PromotionOverlay
|
|
ref={ref}
|
|
showPopup={showPopup}
|
|
tryShowingPopup={tryShowingPopup}
|
|
completeTutorial={completeTutorial}
|
|
hideUntilReload={hideUntilReload}
|
|
/>
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
type PromotionOverlayProps = {
|
|
showPopup: boolean
|
|
tryShowingPopup: () => void
|
|
completeTutorial: (event: {
|
|
action: 'complete'
|
|
event: 'promo-dismiss'
|
|
}) => void
|
|
hideUntilReload: () => void
|
|
}
|
|
|
|
const PromotionOverlay = forwardRef<HTMLButtonElement, PromotionOverlayProps>(
|
|
function PromotionOverlay(
|
|
props: PromotionOverlayProps,
|
|
ref: Ref<HTMLButtonElement>
|
|
) {
|
|
if (typeof ref === 'function' || !ref?.current) {
|
|
return null
|
|
}
|
|
|
|
return <PromotionContent target={ref.current} {...props} />
|
|
}
|
|
)
|
|
|
|
const PromotionContent = memo(function PromotionContent({
|
|
showPopup,
|
|
tryShowingPopup,
|
|
completeTutorial,
|
|
hideUntilReload,
|
|
target,
|
|
}: PromotionOverlayProps & {
|
|
target: HTMLButtonElement
|
|
}) {
|
|
const { t } = useTranslation()
|
|
|
|
useEffect(() => {
|
|
tryShowingPopup()
|
|
}, [tryShowingPopup])
|
|
|
|
const onHide = useCallback(() => {
|
|
hideUntilReload()
|
|
}, [hideUntilReload])
|
|
|
|
const onClose = useCallback(() => {
|
|
completeTutorial({
|
|
action: 'complete',
|
|
event: 'promo-dismiss',
|
|
})
|
|
}, [completeTutorial])
|
|
|
|
return (
|
|
<Overlay
|
|
placement="top"
|
|
show={showPopup}
|
|
target={target}
|
|
rootClose
|
|
onHide={onHide}
|
|
>
|
|
<Popover>
|
|
<Popover.Body>
|
|
<Close variant="dark" onDismiss={onClose} />
|
|
{t('now_you_can_search_your_whole_project_not_just_this_file')}
|
|
</Popover.Body>
|
|
</Popover>
|
|
</Overlay>
|
|
)
|
|
})
|