Merge pull request #23940 from overleaf/td-react-18

Upgrade to React 18

GitOrigin-RevId: 9b81936e6eea2bccd97fe5c2c5841f0b946371b8
This commit is contained in:
Tim Down
2025-05-01 10:45:44 +01:00
committed by Copybot
parent 668011c38f
commit aae0d42002
213 changed files with 2469 additions and 2105 deletions
+1179 -1101
View File
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,4 @@
import { mount } from 'cypress/react'
import { mount } from 'cypress/react18'
// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-namespace
declare global {
@@ -190,7 +190,7 @@ export const ChatContext = createContext<
| undefined
>(undefined)
export const ChatProvider: FC = ({ children }) => {
export const ChatProvider: FC<React.PropsWithChildren> = ({ children }) => {
const chatEnabled = getMeta('ol-chatEnabled')
const clientId = useRef<string>()
@@ -283,7 +283,7 @@ export const ChatProvider: FC = ({ children }) => {
])
const sendMessage = useCallback(
content => {
(content: string) => {
if (!chatEnabled) {
debugConsole.warn(`chat is disabled, won't send message`)
return
@@ -33,7 +33,7 @@ export default function DictionaryModalContent({
const { isError, runAsync } = useAsync()
const handleRemove = useCallback(
word => {
(word: string) => {
runAsync(postJSON('/spelling/unlearn', { body: { word } }))
.then(() => {
setLearnedWords(prevLearnedWords => {
@@ -9,17 +9,19 @@ export const EditorLeftMenuContext = createContext<
EditorLeftMenuState | undefined
>(undefined)
export const EditorLeftMenuProvider: FC = ({ children }) => {
export const EditorLeftMenuProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const [value, setValue] = useState<EditorLeftMenuState>(() => ({
settingToFocus: undefined,
}))
useEventListener(
'ui.focus-setting',
useCallback(event => {
useCallback((event: CustomEvent<string>) => {
setValue(value => ({
...value,
settingToFocus: (event as CustomEvent<string>).detail,
settingToFocus: event.detail,
}))
}, [])
)
@@ -37,7 +37,9 @@ export const ProjectSettingsContext = createContext<
ProjectSettingsContextValue | undefined
>(undefined)
export const ProjectSettingsProvider: FC = ({ children }) => {
export const ProjectSettingsProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const {
compiler,
setCompiler,
@@ -56,7 +56,7 @@ const EditorNavigationToolbarRoot = React.memo(
}, [chatIsOpen, setChatIsOpen, markMessagesAsRead])
const toggleReviewPanelOpen = useCallback(
event => {
(event: any) => {
event.preventDefault()
eventTracking.sendMB('navigation-clicked-review', {
action: isOpentoString(!reviewPanelOpen),
@@ -93,7 +93,7 @@ const EditorNavigationToolbarRoot = React.memo(
}, [setLeftMenuShown])
const goToUser = useCallback(
user => {
(user: any) => {
if (user.doc && typeof user.row === 'number') {
openDoc(user.doc, { gotoLine: user.row + 1 })
}
@@ -9,13 +9,15 @@ import { FC } from 'react'
// FileTreeActionable: global UI state for actions (rename, delete, etc.)
// FileTreeMutable: provides entities mutation operations
// FileTreeSelectable: handles selection and multi-selection
const FileTreeContext: FC<{
refProviders: Record<string, boolean>
setRefProviderEnabled: (provider: string, value: boolean) => void
setStartedFreeTrial: (value: boolean) => void
onSelect: () => void
fileTreeContainer?: HTMLDivElement
}> = ({
const FileTreeContext: FC<
React.PropsWithChildren<{
refProviders: Record<string, boolean>
setRefProviderEnabled: (provider: string, value: boolean) => void
setStartedFreeTrial: (value: boolean) => void
onSelect: () => void
fileTreeContainer?: HTMLDivElement
}>
> = ({
refProviders,
setRefProviderEnabled,
setStartedFreeTrial,
@@ -50,7 +50,7 @@ export default function FileTreeImportFromProject() {
// use the basename of a path as the file name
const setNameFromPath = useCallback(
path => {
(path: string) => {
const filename = path.split('/').pop()
if (filename) {
@@ -1,7 +1,7 @@
import { useFileTreeSelectable } from '../contexts/file-tree-selectable'
import { FC, useCallback } from 'react'
const FileTreeInner: FC = ({ children }) => {
const FileTreeInner: FC<React.PropsWithChildren> = ({ children }) => {
const { setIsRootFolderSelected, selectedEntityIds, select } =
useFileTreeSelectable()
@@ -218,7 +218,9 @@ function fileTreeActionableReducer(state: State, action: Action) {
}
}
export const FileTreeActionableProvider: FC = ({ children }) => {
export const FileTreeActionableProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { _id: projectId } = useProjectContext()
const { fileTreeReadOnly } = useFileTreeData()
const { indexAllReferences } = useReferencesContext()
@@ -400,7 +402,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
}, [fileTreeData, selectedEntityIds])
const finishCreatingEntity = useCallback(
entity => {
(entity: any) => {
const error = validateCreate(fileTreeData, parentFolderId, entity)
if (error) {
return Promise.reject(error)
@@ -412,7 +414,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
)
const finishCreatingFolder = useCallback(
name => {
(name: any) => {
dispatch({ type: ACTION_TYPES.CREATING_FOLDER })
return finishCreatingEntity({ endpoint: 'folder', name })
.then(() => {
@@ -425,7 +427,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
[finishCreatingEntity]
)
const startCreatingFile = useCallback(newFileCreateMode => {
const startCreatingFile = useCallback((newFileCreateMode: any) => {
dispatch({ type: ACTION_TYPES.START_CREATE_FILE, newFileCreateMode })
}, [])
@@ -438,7 +440,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
}, [startCreatingFile])
const finishCreatingDocOrFile = useCallback(
entity => {
(entity: any) => {
dispatch({ type: ACTION_TYPES.CREATING_FILE })
return finishCreatingEntity(entity)
@@ -453,7 +455,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
)
const finishCreatingDoc = useCallback(
entity => {
(entity: any) => {
entity.endpoint = 'doc'
return finishCreatingDocOrFile(entity)
},
@@ -461,7 +463,7 @@ export const FileTreeActionableProvider: FC = ({ children }) => {
)
const finishCreatingLinkedFile = useCallback(
entity => {
(entity: any) => {
entity.endpoint = 'linked_file'
return finishCreatingDocOrFile(entity)
},
@@ -16,7 +16,9 @@ export const useFileTreeCreateForm = () => {
return context
}
const FileTreeCreateFormProvider: FC = ({ children }) => {
const FileTreeCreateFormProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
// is the form valid
const [valid, setValid] = useState(false)
@@ -28,10 +28,9 @@ type State = {
touchedName: boolean
}
const FileTreeCreateNameProvider: FC<{ initialName?: string }> = ({
children,
initialName = '',
}) => {
const FileTreeCreateNameProvider: FC<
React.PropsWithChildren<{ initialName?: string }>
> = ({ children, initialName = '' }) => {
const [state, setName] = useReducer(
(state: State, name: string) => ({
name, // the file name
@@ -18,9 +18,11 @@ import { isAcceptableFile } from '@/features/file-tree/util/is-acceptable-file'
import { FileTreeFindResult } from '@/features/ide-react/types/file-tree'
const DRAGGABLE_TYPE = 'ENTITY'
export const FileTreeDraggableProvider: FC<{
fileTreeContainer?: HTMLDivElement
}> = ({ fileTreeContainer, children }) => {
export const FileTreeDraggableProvider: FC<
React.PropsWithChildren<{
fileTreeContainer?: HTMLDivElement
}>
> = ({ fileTreeContainer, children }) => {
const options = useMemo(
() => ({ rootElement: fileTreeContainer }),
[fileTreeContainer]
@@ -25,11 +25,13 @@ export function useFileTreeMainContext() {
return context
}
export const FileTreeMainProvider: FC<{
refProviders: object
setRefProviderEnabled: (provider: string, value: boolean) => void
setStartedFreeTrial: (value: boolean) => void
}> = ({
export const FileTreeMainProvider: FC<
React.PropsWithChildren<{
refProviders: object
setRefProviderEnabled: (provider: string, value: boolean) => void
setStartedFreeTrial: (value: boolean) => void
}>
> = ({
refProviders,
setRefProviderEnabled,
setStartedFreeTrial,
@@ -22,7 +22,9 @@ export const FileTreePathContext = createContext<
FileTreePathContextValue | undefined
>(undefined)
export const FileTreePathProvider: FC = ({ children }) => {
export const FileTreePathProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { fileTreeData }: { fileTreeData: Folder } = useFileTreeData()
const projectId = getMeta('ol-project_id')
@@ -1,3 +1,6 @@
// TODO: The types in this file have mismatches between string and string[] and
// it's not immediately clear how to resolve it. I've therefore left in a bunch
// of `any` types. We should fix this.
import {
createContext,
useCallback,
@@ -120,9 +123,11 @@ function fileTreeSelectableReadOnlyReducer(
}
}
export const FileTreeSelectableProvider: FC<{
onSelect: (value: FindResult[]) => void
}> = ({ onSelect, children }) => {
export const FileTreeSelectableProvider: FC<
React.PropsWithChildren<{
onSelect: (value: FindResult[]) => void
}>
> = ({ onSelect, children }) => {
const { _id: projectId, rootDocId } = useProjectContext()
const [initialSelectedEntityId] = usePersistedState(
@@ -206,21 +211,24 @@ export const FileTreeSelectableProvider: FC<{
)
)
const select = useCallback(id => {
const select = useCallback((id: any) => {
dispatch({ type: ACTION_TYPES.SELECT, id })
}, [])
const unselect = useCallback(id => {
const unselect = useCallback((id: any) => {
dispatch({ type: ACTION_TYPES.UNSELECT, id })
}, [])
const selectOrMultiSelectEntity = useCallback((id, isMultiSelect) => {
const actionType = isMultiSelect
? ACTION_TYPES.MULTI_SELECT
: ACTION_TYPES.SELECT
const selectOrMultiSelectEntity = useCallback(
(id: any, isMultiSelect: any) => {
const actionType = isMultiSelect
? ACTION_TYPES.MULTI_SELECT
: ACTION_TYPES.SELECT
dispatch({ type: actionType, id })
}, [])
dispatch({ type: actionType, id })
},
[]
)
// TODO: wrap in useMemo
const value = {
@@ -254,7 +262,7 @@ export function useSelectableEntity(id: string, type: string) {
const isSelected = selectedEntityIds.has(id)
const buildSelectedRange = useCallback(
id => {
(id: string) => {
const selected = []
let started = false
@@ -306,7 +314,7 @@ export function useSelectableEntity(id: string, type: string) {
}, [fileTreeData, selectedEntityIds, view])
const handleEvent = useCallback(
ev => {
(ev: any) => {
ev.stopPropagation()
// use Command (macOS) or Ctrl (other OS) to select multiple items,
// as long as the root folder wasn't selected
@@ -342,7 +350,7 @@ export function useSelectableEntity(id: string, type: string) {
)
const handleClick = useCallback(
ev => {
(ev: any) => {
handleEvent(ev)
if (!ev.ctrlKey && !ev.metaKey) {
setContextMenuCoords(null)
@@ -352,7 +360,7 @@ export function useSelectableEntity(id: string, type: string) {
)
const handleKeyPress = useCallback(
ev => {
(ev: any) => {
if (ev.key === 'Enter' || ev.key === ' ') {
handleEvent(ev)
}
@@ -361,7 +369,7 @@ export function useSelectableEntity(id: string, type: string) {
)
const handleContextMenu = useCallback(
ev => {
(ev: any) => {
// make sure the right-clicked entity gets selected
if (!selectedEntityIds.has(id)) {
handleEvent(ev)
@@ -25,7 +25,7 @@ export function useFileTreeSocketListener(onDelete: (entity: any) => void) {
const selectEntityIfCreatedByUser = useCallback(
// hack to automatically re-open refreshed linked files
(entityId, entityName, userId) => {
(entityId: any, entityName: any, userId: string) => {
// If the created entity's user exists and is the current user
if (userId && user?.id === userId) {
// And we're expecting a refreshed socket for this entity
@@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react'
import React, { ChangeEvent, useCallback, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
import getMeta from '../../../utils/meta'
@@ -37,7 +37,7 @@ export default function GroupMembers() {
const canUseAddSeatsFeature = getMeta('ol-canUseAddSeatsFeature')
const handleEmailsChange = useCallback(
e => {
(e: ChangeEvent<HTMLInputElement>) => {
setEmailString(e.target.value)
},
[setEmailString]
@@ -1,4 +1,4 @@
import { useCallback, useState } from 'react'
import { ChangeEvent, FormEvent, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { deleteJSON, FetchError, postJSON } from '@/infrastructure/fetch-json'
import getMeta from '../../../utils/meta'
@@ -58,7 +58,7 @@ export function ManagersTable({
const [removeMemberError, setRemoveMemberError] = useState<APIError>()
const addManagers = useCallback(
e => {
(e: FormEvent | React.MouseEvent) => {
e.preventDefault()
setInviteError(undefined)
const emails = parseEmails(emailString)
@@ -92,7 +92,7 @@ export function ManagersTable({
)
const removeManagers = useCallback(
e => {
(e: React.MouseEvent) => {
e.preventDefault()
setRemoveMemberError(undefined)
;(async () => {
@@ -131,7 +131,7 @@ export function ManagersTable({
)
const handleEmailsChange = useCallback(
e => {
(e: ChangeEvent<HTMLInputElement>) => {
setEmailString(e.target.value)
},
[setEmailString]
@@ -64,7 +64,7 @@ export default function DropdownButton({
const isUserManaged = !userPending && user.enrollment?.managedBy === groupId
const handleResendManagedUserInvite = useCallback(
async user => {
async (user: User) => {
try {
const result = await runResendManagedUserInviteAsync(
postJSON(
@@ -96,7 +96,7 @@ export default function DropdownButton({
)
const handleResendLinkSSOInviteAsync = useCallback(
async user => {
async (user: User) => {
try {
const result = await runResendLinkSSOInviteAsync(
postJSON(`/manage/groups/${groupId}/resendSSOLinkInvite/${user._id}`)
@@ -126,7 +126,7 @@ export default function DropdownButton({
)
const handleResendGroupInvite = useCallback(
async user => {
async (user: User) => {
try {
await runResendGroupInviteAsync(
postJSON(`/manage/groups/${groupId}/resendInvite/`, {
@@ -101,7 +101,6 @@ export default function OffboardManagedUserModal({
<OLFormSelect
aria-label={t('select_user')}
required
placeholder={t('choose_from_group_members')}
value={selectedRecipientId || ''}
onChange={e => setSelectedRecipientId(e.target.value)}
>
@@ -16,7 +16,7 @@ export default function SelectUserCheckbox({
useGroupMembersContext()
const handleSelectUser = useCallback(
(event, user) => {
(event: React.ChangeEvent<HTMLInputElement>, user: User) => {
if (event.target.checked) {
selectUser(user)
} else {
@@ -47,7 +47,7 @@ export default function UnlinkUserModal({
}, [groupId, updateMemberView, user])
const handleUnlink = useCallback(
event => {
(event: React.MouseEvent) => {
event.preventDefault()
setHasError(undefined)
if (!user) {
@@ -21,7 +21,7 @@ export default function UserRow({
const { t } = useTranslation()
const handleSelectUser = useCallback(
(event, user) => {
(event: React.ChangeEvent<HTMLInputElement>, user: User) => {
if (event.target.checked) {
selectUser(user)
} else {
@@ -72,7 +72,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
)
const addMembers = useCallback(
emailString => {
(emailString: string) => {
setInviteError(undefined)
const emails = parseEmails(emailString)
mapSeries(emails, async email => {
@@ -102,7 +102,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
)
const removeMember = useCallback(
async user => {
async (user: User) => {
let url
if (paths.removeInvite && user.invite && user._id == null) {
url = `${paths.removeInvite}/${encodeURIComponent(user.email)}`
@@ -126,7 +126,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
)
const removeMembers = useCallback(
e => {
(e: any) => {
e.preventDefault()
setRemoveMemberError(undefined)
;(async () => {
@@ -142,7 +142,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
)
const updateMemberView = useCallback(
(userId, updatedUser) => {
(userId: string, updatedUser: User) => {
setUsers(
users.map(u => {
if (u._id === userId) {
@@ -1,4 +1,4 @@
import ReactDOM from 'react-dom'
import { createRoot } from 'react-dom/client'
import getMeta from '@/utils/meta'
import DefaultNavbar from '@/features/ui/components/bootstrap-5/navbar/default-navbar'
import Footer from '@/features/ui/components/bootstrap-5/footer/footer'
@@ -7,16 +7,17 @@ import { SplitTestProvider } from '@/shared/context/split-test-context'
const navbarElement = document.getElementById('navbar-container')
if (navbarElement) {
const navbarProps = getMeta('ol-navbar')
ReactDOM.render(
const root = createRoot(navbarElement)
root.render(
<SplitTestProvider>
<DefaultNavbar {...navbarProps} />
</SplitTestProvider>,
navbarElement
</SplitTestProvider>
)
}
const footerElement = document.getElementById('footer-container')
if (footerElement) {
const footerProps = getMeta('ol-footer')
ReactDOM.render(<Footer {...footerProps} />, footerElement)
const root = createRoot(footerElement)
root.render(<Footer {...footerProps} />)
}
@@ -77,7 +77,7 @@ function DocumentDiffViewer({
// Append the editor view DOM to the container node when mounted
const containerRef = useCallback(
node => {
(node: HTMLDivElement) => {
if (node) {
node.appendChild(view.dom)
}
@@ -15,4 +15,6 @@ const IdeRoot: FC = () => {
)
}
export default withErrorBoundary(memo(IdeRoot), GenericErrorBoundaryFallback)
export default withErrorBoundary(memo(IdeRoot), () => (
<GenericErrorBoundaryFallback />
))
@@ -10,7 +10,9 @@ type HorizontalResizeHandleOwnProps = {
}
export const HorizontalResizeHandle: FC<
HorizontalResizeHandleOwnProps & PanelResizeHandleProps
React.PropsWithChildren<
HorizontalResizeHandleOwnProps & PanelResizeHandleProps
>
> = ({ children, resizable = true, onDoubleClick, ...props }) => {
const { t } = useTranslation()
const [isDragging, setIsDragging] = useState(false)
@@ -30,7 +30,7 @@ export const UnsavedDocs: FC = () => {
useEventListener(
'beforeunload',
useCallback(
event => {
(event: BeforeUnloadEvent) => {
if (openDocs.hasUnsavedChanges()) {
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
event.preventDefault()
@@ -23,7 +23,9 @@ type CommandRegistry = {
unregister: (...id: string[]) => void
}
export const CommandRegistryProvider: React.FC = ({ children }) => {
export const CommandRegistryProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [registry, setRegistry] = useState(new Map<string, Command>())
const register = useCallback((...elements: Command[]) => {
setRegistry(
@@ -36,7 +36,9 @@ export const ConnectionContext = createContext<
ConnectionContextValue | undefined
>(undefined)
export const ConnectionProvider: FC = ({ children }) => {
export const ConnectionProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const location = useLocation()
const [connectionManager] = useState(() => new ConnectionManager())
@@ -88,7 +88,9 @@ export const EditorManagerContext = createContext<EditorManager | undefined>(
undefined
)
export const EditorManagerProvider: FC = ({ children }) => {
export const EditorManagerProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { t } = useTranslation()
const { scopeStore } = useIdeContext()
const { reportError, eventEmitter, projectId } = useIdeReactContext()
@@ -36,7 +36,9 @@ const FileTreeOpenContext = createContext<
| undefined
>(undefined)
export const FileTreeOpenProvider: FC = ({ children }) => {
export const FileTreeOpenProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { rootDocId, owner } = useProjectContext()
const { eventEmitter, projectJoined } = useIdeReactContext()
const { openDocWithId, currentDocumentId, openInitialDoc } =
@@ -4,7 +4,9 @@ const GlobalAlertsContext = createContext<HTMLDivElement | null | undefined>(
undefined
)
export const GlobalAlertsProvider: FC = ({ children }) => {
export const GlobalAlertsProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const [globalAlertsContainer, setGlobalAlertsContainer] =
useState<HTMLDivElement | null>(null)
@@ -77,7 +77,7 @@ export function createReactScopeValueStore(projectId: string) {
return scopeStore
}
export const IdeReactProvider: FC = ({ children }) => {
export const IdeReactProvider: FC<React.PropsWithChildren> = ({ children }) => {
const projectId = getMeta('ol-project_id')
const [scopeStore] = useState(() => createReactScopeValueStore(projectId))
const [eventEmitter] = useState(createIdeEventEmitter)
@@ -16,7 +16,9 @@ export const IdeRedesignSwitcherContext = createContext<
IdeRedesignSwitcherContextValue | undefined
>(undefined)
export const IdeRedesignSwitcherProvider: FC = ({ children }) => {
export const IdeRedesignSwitcherProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const [showSwitcherModal, setShowSwitcherModal] = useState(false)
return (
@@ -47,7 +47,7 @@ export const MetadataContext = createContext<
| undefined
>(undefined)
export const MetadataProvider: FC = ({ children }) => {
export const MetadataProvider: FC<React.PropsWithChildren> = ({ children }) => {
const { t } = useTranslation()
const { eventEmitter, projectId } = useIdeReactContext()
const { socket } = useConnectionContext()
@@ -30,7 +30,9 @@ type ModalsContextValue = {
const ModalsContext = createContext<ModalsContextValue | undefined>(undefined)
export const ModalsContextProvider: FC = ({ children }) => {
export const ModalsContextProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const [showGenericModal, setShowGenericModal] = useState(false)
const [showConfirmModal, setShowConfirmModal] = useState(false)
const [genericMessageModalData, setGenericMessageModalData] =
@@ -65,7 +65,9 @@ export const OnlineUsersContext = createContext<
OnlineUsersContextValue | undefined
>(undefined)
export const OnlineUsersProvider: FC = ({ children }) => {
export const OnlineUsersProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { eventEmitter } = useIdeReactContext()
const { socket } = useConnectionContext()
const { currentDocumentId } = useEditorManagerContext()
@@ -42,7 +42,7 @@ const OutlineContext = createContext<
| undefined
>(undefined)
export const OutlineProvider: FC = ({ children }) => {
export const OutlineProvider: FC<React.PropsWithChildren> = ({ children }) => {
const [flatOutline, setFlatOutline] = useState<FlatOutlineState>(undefined)
const [currentlyHighlightedLine, setCurrentlyHighlightedLine] =
useState<number>(-1)
@@ -55,7 +55,7 @@ export const OutlineProvider: FC = ({ children }) => {
useEventListener(
'file-view:file-opened',
useCallback(_ => {
useCallback(() => {
setBinaryFileOpened(true)
}, [])
)
@@ -63,7 +63,7 @@ export const OutlineProvider: FC = ({ children }) => {
useEventListener(
'scroll:editor:update',
useCallback(
evt => {
(evt: CustomEvent) => {
if (ignoreNextScroll) {
setIgnoreNextScroll(false)
return
@@ -77,7 +77,7 @@ export const OutlineProvider: FC = ({ children }) => {
useEventListener(
'cursor:editor:update',
useCallback(
evt => {
(evt: CustomEvent) => {
if (ignoreNextCursorUpdate) {
setIgnoreNextCursorUpdate(false)
return
@@ -90,7 +90,7 @@ export const OutlineProvider: FC = ({ children }) => {
useEventListener(
'doc:after-opened',
useCallback(evt => {
useCallback((evt: CustomEvent) => {
if (evt.detail.isNewDoc) {
setIgnoreNextCursorUpdate(true)
}
@@ -79,7 +79,9 @@ const noTrackChangesPermissionsMap: typeof permissionsMap = {
owner: permissionsMap.owner,
}
export const PermissionsProvider: React.FC = ({ children }) => {
export const PermissionsProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [permissions, setPermissions] =
useScopeValue<Readonly<Permissions>>('permissions')
const { connectionState } = useConnectionContext()
@@ -27,10 +27,11 @@ import { UserSettingsProvider } from '@/shared/context/user-settings-context'
import { IdeRedesignSwitcherProvider } from './ide-redesign-switcher-context'
import { CommandRegistryProvider } from './command-registry-context'
export const ReactContextRoot: FC<{ providers?: Record<string, FC> }> = ({
children,
providers = {},
}) => {
export const ReactContextRoot: FC<
React.PropsWithChildren<{
providers?: Record<string, FC>
}>
> = ({ children, providers = {} }) => {
const Providers = {
ChatProvider,
ConnectionProvider,
@@ -26,7 +26,9 @@ export const ReferencesContext = createContext<
| undefined
>(undefined)
export const ReferencesProvider: FC = ({ children }) => {
export const ReferencesProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { fileTreeData } = useFileTreeData()
const { eventEmitter, projectId } = useIdeReactContext()
const { socket } = useConnectionContext()
@@ -50,7 +50,7 @@ export const SnapshotContext = createContext<
| undefined
>(undefined)
export const SnapshotProvider: FC = ({ children }) => {
export const SnapshotProvider: FC<React.PropsWithChildren> = ({ children }) => {
const { _id: projectId } = useProjectContext()
const [snapshotLoadingState, setSnapshotLoadingState] =
useState<SnapshotLoadingState>('')
@@ -27,7 +27,7 @@ export const ToolbarProjectTitle = () => {
const hasRenamePermissions = permissionsLevel === 'owner'
const [isRenaming, setIsRenaming] = useState(false)
const onRename = useCallback(
name => {
(name: string) => {
if (name) {
renameProject(name)
}
@@ -39,7 +39,7 @@ const RailContext = createContext<
| undefined
>(undefined)
export const RailProvider: FC = ({ children }) => {
export const RailProvider: FC<React.PropsWithChildren> = ({ children }) => {
const [isOpen, setIsOpen] = useState(true)
const [resizing, setResizing] = useState(false)
const [activeModal, setActiveModalInternal] = useState<RailModalKey | null>(
@@ -15,7 +15,7 @@ function CompileTimeWarningUpgradePrompt() {
>(`has-dismissed-10s-compile-time-warning-until`)
const handleNewCompile = useCallback(
compileTime => {
(compileTime: number) => {
setShowWarning(false)
if (compileTime > 10000) {
if (isProjectOwner) {
@@ -62,10 +62,10 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
// create the viewer when the container is mounted
const handleContainer = useCallback(
parent => {
(parent: HTMLDivElement | null) => {
if (parent) {
try {
setPdfJsWrapper(new PDFJSWrapper(parent.firstChild))
setPdfJsWrapper(new PDFJSWrapper(parent.firstChild as HTMLDivElement))
} catch (error: any) {
setLoadingError(true)
captureException(error)
@@ -385,7 +385,7 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
// set the scale in response to zoom option changes
const setZoom = useCallback(
zoom => {
(zoom: any) => {
switch (zoom) {
case 'zoom-in':
if (pdfJsWrapper) {
@@ -430,7 +430,7 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
}, [pdfJsWrapper])
const handleKeyDown = useCallback(
event => {
(event: React.KeyboardEvent) => {
if (!initialised || !pdfJsWrapper) {
return
}
@@ -17,7 +17,7 @@ export default function PdfLogEntryRawContent({
const { elementRef } = useResizeObserver(
useCallback(
element => {
(element: Element) => {
if (element.scrollHeight === 0) return // skip update when logs-pane is closed
setNeedsExpander(element.scrollHeight > collapsedSize)
},
@@ -1,4 +1,4 @@
import ReactDOM from 'react-dom'
import { createRoot } from 'react-dom/client'
import PdfPreview from './pdf-preview'
import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
@@ -21,5 +21,6 @@ export default PdfPreviewDetachedRoot // for testing
const element = document.getElementById('pdf-preview-detached-root')
if (element) {
ReactDOM.render(<PdfPreviewDetachedRoot />, element)
const root = createRoot(element)
root.render(<PdfPreviewDetachedRoot />)
}
@@ -1,5 +1,7 @@
import { FC } from 'react'
export const PdfPreviewMessages: FC = ({ children }) => {
export const PdfPreviewMessages: FC<React.PropsWithChildren> = ({
children,
}) => {
return <div className="pdf-preview-messages">{children}</div>
}
@@ -18,7 +18,9 @@ export const usePdfPreviewContext = () => {
return context
}
export const PdfPreviewProvider: FC = ({ children }) => {
export const PdfPreviewProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const [loadingError, setLoadingError] = useState(false)
const value = useMemo(
@@ -36,8 +36,8 @@ function PdfViewerControlsToolbar({
const [availableWidth, setAvailableWidth] = useState<number>(1000)
const handleResize = useCallback(
element => {
setAvailableWidth(element.offsetWidth)
(element: Element) => {
setAvailableWidth((element as HTMLElement).offsetWidth)
},
[setAvailableWidth]
)
@@ -33,8 +33,8 @@ export default function usePresentationMode(
}, [handlePageChange, page])
const clickListener = useCallback(
event => {
if (event.target.tagName === 'A') {
(event: MouseEvent) => {
if ((event.target as HTMLElement).tagName === 'A') {
return
}
@@ -48,7 +48,7 @@ export default function usePresentationMode(
)
const arrowKeyListener = useCallback(
event => {
(event: KeyboardEvent) => {
switch (event.key) {
case 'ArrowLeft':
case 'ArrowUp':
@@ -51,7 +51,7 @@ export default function useSynctex(): {
useEventListener(
'cursor:editor:update',
useCallback(event => setCursorPosition(event.detail), [])
useCallback((event: CustomEvent) => setCursorPosition(event.detail), [])
)
const [syncToPdfInFlight, setSyncToPdfInFlight] = useState(false)
@@ -86,7 +86,7 @@ export default function useSynctex(): {
}, [dirname, getCurrentDocumentId, pathInFolder, rootDocId])
const goToCodeLine = useCallback(
(file, line) => {
(file?: string, line?: number) => {
if (file) {
const doc = findEntityByPath(file)?.entity
if (doc) {
@@ -102,7 +102,7 @@ export default function useSynctex(): {
)
const goToPdfLocation = useCallback(
params => {
(params: string) => {
setSyncToPdfInFlight(true)
if (clsiServerId) {
@@ -251,7 +251,10 @@ export default function useSynctex(): {
useEventListener(
'synctex:sync-to-position',
useCallback(event => syncToCode({ position: event.detail }), [syncToCode])
useCallback(
(event: CustomEvent) => syncToCode({ position: event.detail }),
[syncToCode]
)
)
const [hasSingleSelectedDoc, setHasSingleSelectedDoc] = useDetachState(
@@ -55,7 +55,7 @@ export default function CreateTagModal({
}, [runAsync, tagName, selectedColor, onCreate])
const handleSubmit = useCallback(
e => {
(e: React.FormEvent) => {
e.preventDefault()
runCreateTag()
},
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'
import { FormEvent, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Tag } from '../../../../../../app/src/Features/Tags/types'
import useAsync from '../../../../shared/hooks/use-async'
@@ -56,7 +56,7 @@ export function EditTagModal({ id, tag, onEdit, onClose }: EditTagModalProps) {
)
const handleSubmit = useCallback(
e => {
(e: FormEvent) => {
e.preventDefault()
if (tag) {
runEditTag(tag._id)
@@ -1,4 +1,4 @@
import { useCallback, useState } from 'react'
import { FormEvent, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import useAsync from '../../../../shared/hooks/use-async'
import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus'
@@ -73,7 +73,7 @@ export function ManageTagModal({
)
const handleSubmit = useCallback(
e => {
(e: FormEvent) => {
e.preventDefault()
if (tag) {
runUpdateTag(tag._id)
@@ -1,4 +1,11 @@
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import {
FormEvent,
memo,
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import * as eventTracking from '../../../../infrastructure/event-tracking'
import { Project } from '../../../../../../types/project/dashboard/api'
@@ -58,7 +65,7 @@ function RenameProjectModal({
}, [project.name])
const handleSubmit = useCallback(
event => {
(event: FormEvent) => {
event.preventDefault()
if (!isValid) return
@@ -100,4 +100,6 @@ function ProjectListPageContent() {
)
}
export default withErrorBoundary(ProjectListRoot, GenericErrorBoundaryFallback)
export default withErrorBoundary(ProjectListRoot, () => (
<GenericErrorBoundaryFallback />
))
@@ -1,4 +1,4 @@
import { memo, useCallback } from 'react'
import { ChangeEvent, memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useProjectListContext } from '@/features/project-list/context/project-list-context'
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
@@ -10,7 +10,7 @@ export const ProjectCheckbox = memo<{ projectId: string; projectName: string }>(
useProjectListContext()
const handleCheckboxChange = useCallback(
event => {
(event: ChangeEvent<HTMLInputElement>) => {
toggleSelectedProject(projectId, event.target.checked)
},
[projectId, toggleSelectedProject]
@@ -14,6 +14,7 @@ import {
DropdownMenu,
DropdownToggle,
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
import { Tag } from '../../../../../../../../app/src/Features/Tags/types'
function TagsDropdown() {
const {
@@ -26,7 +27,7 @@ function TagsDropdown() {
const { openCreateTagModal, CreateTagModal } = useTag()
const handleOpenCreateTagModal = useCallback(
e => {
(e: React.MouseEvent) => {
e.preventDefault()
openCreateTagModal()
},
@@ -34,7 +35,7 @@ function TagsDropdown() {
)
const handleAddTagToSelectedProjects = useCallback(
(e, tagId) => {
(e: React.MouseEvent, tagId: string) => {
e.preventDefault()
const tag = tags.find(tag => tag._id === tagId)
const projectIds = []
@@ -50,7 +51,7 @@ function TagsDropdown() {
)
const handleRemoveTagFromSelectedProjects = useCallback(
(e, tagId) => {
(e: React.MouseEvent, tagId: string) => {
e.preventDefault()
for (const selectedProject of selectedProjects) {
removeProjectFromTagInView(tagId, selectedProject.id)
@@ -64,7 +65,7 @@ function TagsDropdown() {
)
const containsAllSelectedProjects = useCallback(
tag => {
(tag: Tag) => {
for (const project of selectedProjects) {
if (!(tag.project_ids || []).includes(project.id)) {
return false
@@ -6,9 +6,11 @@ import { createContext, type FC, type ReactNode, useContext } from 'react'
*/
const DsNavStyleContext = createContext<boolean | undefined>(undefined)
export const DsNavStyleProvider: FC<{
children: ReactNode
}> = ({ children }) => (
export const DsNavStyleProvider: FC<
React.PropsWithChildren<{
children: ReactNode
}>
> = ({ children }) => (
<DsNavStyleContext.Provider value>{children}</DsNavStyleContext.Provider>
)
@@ -293,7 +293,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
}, [selectedProjectIds, visibleProjects])
const selectOrUnselectAllProjects = useCallback(
checked => {
(checked: any) => {
setSelectedProjectIds(prevSelectedProjectIds => {
const selectedProjectIds = new Set(prevSelectedProjectIds)
for (const project of visibleProjects) {
@@ -50,7 +50,7 @@ function useTag() {
)
const handleEditTag = useCallback(
(e, tagId) => {
(e: React.MouseEvent, tagId: string) => {
e.preventDefault()
const tag = find(tags, ['_id', tagId])
if (tag) {
@@ -69,7 +69,7 @@ function useTag() {
)
const handleDeleteTag = useCallback(
(e, tagId) => {
(e: React.MouseEvent, tagId: string) => {
e.preventDefault()
const tag = find(tags, ['_id', tagId])
if (tag) {
@@ -80,7 +80,7 @@ function useTag() {
)
const onDelete = useCallback(
tagId => {
(tagId: string) => {
deleteTag(tagId)
setDeletingTag(undefined)
},
@@ -88,7 +88,7 @@ function useTag() {
)
const handleManageTag = useCallback(
(e, tagId) => {
(e: React.MouseEvent, tagId: string) => {
e.preventDefault()
const tag = find(tags, ['_id', tagId])
if (tag) {
@@ -36,7 +36,7 @@ export const ReviewPanelAddComment = memo<{
}, [view, threadId])
const submitForm = useCallback(
async message => {
async (message: string) => {
setSubmitting(true)
const content = view.state.sliceDoc(from, to)
@@ -85,14 +85,15 @@ export const ReviewPanelAddComment = memo<{
// We cannot use the autofocus attribute as we need to wait until the parent element
// has been positioned (with the "top" attribute) to avoid scrolling to the initial
// position of the element
const observerCallback = useCallback(mutationList => {
const observerCallback = useCallback((mutationList: MutationRecord[]) => {
if (hasBeenFocused.current) {
return
}
for (const mutation of mutationList) {
if (mutation.target.style.top) {
const textArea = mutation.target.getElementsByTagName('textarea')[0]
const target = mutation.target as HTMLElement
if (target.style.top) {
const textArea = target.getElementsByTagName('textarea')[0]
if (textArea) {
textArea.focus()
hasBeenFocused.current = true
@@ -1,4 +1,4 @@
import { memo, useCallback, useState } from 'react'
import { Dispatch, memo, SetStateAction, useCallback, useState } from 'react'
import { Change, CommentOperation } from '../../../../../types/change'
import { ReviewPanelMessage } from './review-panel-message'
import { useTranslation } from 'react-i18next'
@@ -41,7 +41,7 @@ export const ReviewPanelCommentContent = memo<{
const [submitting, setSubmitting] = useState(false)
const handleSubmit = useCallback(
(content, setContent) => {
(content: string, setContent: Dispatch<SetStateAction<string>>) => {
if (!onReply || submitting) {
return
}
@@ -16,19 +16,21 @@ import { EditorSelection } from '@codemirror/state'
import MaterialIcon from '@/shared/components/material-icon'
import { OFFSET_FOR_ENTRIES_ABOVE } from '../utils/position-items'
export const ReviewPanelEntry: FC<{
position: number
op: AnyOperation
docId: string
top?: number
className?: string
selectLineOnFocus?: boolean
hoverRanges?: boolean
disabled?: boolean
onEnterEntryIndicator?: () => void
onLeaveEntryIndicator?: () => void
entryIndicator?: 'comment' | 'edit'
}> = ({
export const ReviewPanelEntry: FC<
React.PropsWithChildren<{
position: number
op: AnyOperation
docId: string
top?: number
className?: string
selectLineOnFocus?: boolean
hoverRanges?: boolean
disabled?: boolean
onEnterEntryIndicator?: () => void
onLeaveEntryIndicator?: () => void
entryIndicator?: 'comment' | 'edit'
}>
> = ({
children,
position,
top,
@@ -58,7 +60,7 @@ export const ReviewPanelEntry: FC<{
}, [setReviewPanelOpen])
const selectEntry = useCallback(
event => {
(event: React.FocusEvent | React.MouseEvent) => {
setFocused(true)
if (event.target instanceof HTMLTextAreaElement) {
@@ -26,7 +26,9 @@ export const ChangesUsersContext = createContext<ChangesUsers | undefined>(
undefined
)
export const ChangesUsersProvider: FC = ({ children }) => {
export const ChangesUsersProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { _id: projectId, members, owner } = useProjectContext()
const { isRestrictedTokenMember } = useEditorContext()
@@ -78,7 +78,7 @@ const buildRanges = (currentDocument: DocumentContainer | null) => {
const RangesActionsContext = createContext<RangesActions | undefined>(undefined)
export const RangesProvider: FC = ({ children }) => {
export const RangesProvider: FC<React.PropsWithChildren> = ({ children }) => {
const view = useCodeMirrorViewContext()
const { projectId } = useIdeReactContext()
const { currentDocument } = useEditorManagerContext()
@@ -5,7 +5,9 @@ import { TrackChangesStateProvider } from './track-changes-state-context'
import { ThreadsProvider } from './threads-context'
import { ReviewPanelViewProvider } from './review-panel-view-context'
export const ReviewPanelProviders: FC = ({ children }) => {
export const ReviewPanelProviders: FC<React.PropsWithChildren> = ({
children,
}) => {
return (
<ReviewPanelViewProvider>
<ChangesUsersProvider>
@@ -20,7 +20,9 @@ const ReviewPanelViewActionsContext = createContext<ViewActions | undefined>(
undefined
)
export const ReviewPanelViewProvider: FC = ({ children }) => {
export const ReviewPanelViewProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const [view, setView] = useState<View>('cur_file')
const actions = useMemo(
@@ -48,7 +48,7 @@ const ThreadsActionsContext = createContext<ThreadsActions | undefined>(
undefined
)
export const ThreadsProvider: FC = ({ children }) => {
export const ThreadsProvider: FC<React.PropsWithChildren> = ({ children }) => {
const { _id: projectId } = useProjectContext()
const { currentDocument } = useEditorManagerContext()
const { isRestrictedTokenMember } = useEditorContext()
@@ -225,7 +225,7 @@ export const ThreadsProvider: FC = ({ children }) => {
useSocketListener(
socket,
'new-comment-threads',
useCallback(threads => {
useCallback((threads: any) => {
setData(prevState => {
const newThreads = { ...prevState }
for (const threadId of Object.keys(threads)) {
@@ -44,7 +44,9 @@ const TrackChangesStateActionsContext = createContext<
TrackChangesStateActions | undefined
>(undefined)
export const TrackChangesStateProvider: FC = ({ children }) => {
export const TrackChangesStateProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const permissions = usePermissionsContext()
const { socket } = useConnectionContext()
const project = useProjectContext()
@@ -126,7 +126,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
}, [handleHide, inFlight])
// update `error` and `inFlight` while sending a request
const monitorRequest = useCallback(request => {
const monitorRequest = useCallback((request: () => any) => {
setError(undefined)
setInFlight(true)
@@ -149,7 +149,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
// merge the new data with the old project data
const updateProject = useCallback(
data => Object.assign(project, data),
(data: ProjectContextUpdateValue) => Object.assign(project, data),
[project]
)
@@ -2,7 +2,15 @@ import {
useCodeMirrorStateContext,
useCodeMirrorViewContext,
} from './codemirror-context'
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
FC,
FormEvent,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { runScopeHandlers } from '@codemirror/view'
import {
closeSearchPanel,
@@ -49,7 +57,7 @@ type MatchPositions = {
interrupted: boolean
}
const CodeMirrorSearchForm: FC = () => {
const CodeMirrorSearchForm: FC<React.PropsWithChildren> = () => {
const view = useCodeMirrorViewContext()
const state = useCodeMirrorStateContext()
const { setProjectSearchIsOpen } = useLayoutContext()
@@ -78,7 +86,7 @@ const CodeMirrorSearchForm: FC = () => {
const newEditor = useIsNewEditorEnabled()
const handleInputRef = useCallback(node => {
const handleInputRef = useCallback((node: HTMLInputElement) => {
inputRef.current = node
// focus the search input when the panel opens
@@ -88,11 +96,11 @@ const CodeMirrorSearchForm: FC = () => {
}
}, [])
const handleReplaceRef = useCallback(node => {
const handleReplaceRef = useCallback((node: HTMLInputElement) => {
replaceRef.current = node
}, [])
const handleSubmit = useCallback(event => {
const handleSubmit = useCallback((event: FormEvent) => {
event.preventDefault()
}, [])
@@ -125,8 +133,8 @@ const CodeMirrorSearchForm: FC = () => {
}, [handleChange, state, view])
const handleFormKeyDown = useCallback(
event => {
if (runScopeHandlers(view, event, 'search-panel')) {
(event: React.KeyboardEvent<HTMLFormElement>) => {
if (runScopeHandlers(view, event.nativeEvent, 'search-panel')) {
event.preventDefault()
}
},
@@ -135,7 +143,7 @@ const CodeMirrorSearchForm: FC = () => {
// Returns true if the event was handled, false otherwise
const handleEmacsNavigation = useCallback(
event => {
(event: KeyboardEvent) => {
const emacsCtrlSeq =
emacsKeybindingsActive &&
event.ctrlKey &&
@@ -163,7 +171,14 @@ const CodeMirrorSearchForm: FC = () => {
event.stopPropagation()
event.preventDefault()
closeSearchPanel(view)
document.dispatchEvent(new CustomEvent('cm:emacs-close-search-panel'))
// Wait for the search panel to close before moving the cursor
window.setTimeout(
() =>
document.dispatchEvent(
new CustomEvent('cm:emacs-close-search-panel')
),
0
)
return true
}
default: {
@@ -175,7 +190,7 @@ const CodeMirrorSearchForm: FC = () => {
)
const handleSearchKeyDown = useCallback(
event => {
(event: React.KeyboardEvent<HTMLInputElement>) => {
switch (event.key) {
case 'Enter':
event.preventDefault()
@@ -191,13 +206,13 @@ const CodeMirrorSearchForm: FC = () => {
}
break
}
handleEmacsNavigation(event)
handleEmacsNavigation(event.nativeEvent)
},
[view, handleEmacsNavigation, emacsKeybindingsActive]
)
const handleReplaceKeyDown = useCallback(
event => {
(event: React.KeyboardEvent<HTMLInputElement>) => {
switch (event.key) {
case 'Enter':
event.preventDefault()
@@ -216,7 +231,7 @@ const CodeMirrorSearchForm: FC = () => {
}
}
}
handleEmacsNavigation(event)
handleEmacsNavigation(event.nativeEvent)
},
[view, handleEmacsNavigation]
)
@@ -106,7 +106,7 @@ const Toolbar = memo(function Toolbar() {
// calculate overflow when buttons change
const observerRef = useRef<MutationObserver | null>(null)
const handleButtons = useCallback(
node => {
(node: HTMLDivElement) => {
if (!('MutationObserver' in window)) {
return
}
@@ -10,7 +10,7 @@ function CodeMirrorView() {
// append the editor view dom to the container node when mounted
const containerRef = useCallback(
node => {
(node: HTMLDivElement) => {
if (node) {
node.appendChild(view.dom)
}
@@ -1,4 +1,4 @@
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import { FC, FormEvent, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
useCodeMirrorStateContext,
@@ -71,7 +71,7 @@ export const HrefTooltipContent: FC = () => {
}, [view])
const handleSubmit = useCallback(
event => {
(event: FormEvent) => {
event.preventDefault()
view.dispatch(closeCommandTooltip())
view.focus()
@@ -14,7 +14,7 @@ function EditorSwitch() {
const richTextAvailable = openDocName ? isValidTeXFile(openDocName) : false
const handleChange = useCallback(
event => {
(event: ChangeEvent<HTMLInputElement>) => {
const editorType = event.target.value
switch (editorType) {
@@ -80,7 +80,9 @@ const FigureModalExistingFigureContext = createContext<
| undefined
>(undefined)
export const FigureModalProvider: FC = ({ children }) => {
export const FigureModalProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const [state, dispatch] = useReducer(reducer, {
source: FigureModalSource.NONE,
helpShown: false,
@@ -1,7 +1,10 @@
import { FC } from 'react'
import { Trans, useTranslation } from 'react-i18next'
const LearnWikiLink: FC<{ article: string }> = ({ article, children }) => {
const LearnWikiLink: FC<React.PropsWithChildren<{ article: string }>> = ({
article,
children,
}) => {
return <a href={`/learn/latex/${article}`}>{children}</a>
}
@@ -104,7 +104,8 @@ const FigureModalContent = () => {
const hide = useCallback(() => {
dispatch({ source: FigureModalSource.NONE })
view.requestMeasure()
view.focus()
// Wait for the modal to close before focusing the editor
window.setTimeout(() => view.focus(), 0)
}, [dispatch, view])
useEventListener(
@@ -176,8 +176,8 @@ export const Cell: FC<{
}, [cellData.content, editing, view])
const onInput = useCallback(
e => {
update(filterInput(e.target.value))
(e: React.FormEvent<HTMLTextAreaElement>) => {
update(filterInput((e.target as HTMLTextAreaElement).value))
},
[update, filterInput]
)
@@ -38,7 +38,9 @@ export const useEditingContext = () => {
return context
}
export const EditingContextProvider: FC = ({ children }) => {
export const EditingContextProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const { table } = useTableContext()
const [cellData, setCellData] = useState<EditingContextData | null>(null)
const [initialContent, setInitialContent] = useState<string | undefined>(
@@ -85,7 +87,7 @@ export const EditingContextProvider: FC = ({ children }) => {
}, [setCellData])
const startEditing = useCallback(
(rowIndex: number, cellIndex: number, initialContent = undefined) => {
(rowIndex: number, cellIndex: number, initialContent?: string) => {
if (cellData?.dirty) {
// We're already editing something else
commitCellData()
@@ -411,7 +411,9 @@ export const useSelectionContext = () => {
return context
}
export const SelectionContextProvider: FC = ({ children }) => {
export const SelectionContextProvider: FC<React.PropsWithChildren> = ({
children,
}) => {
const [selection, setSelection] = useState<TableSelection | null>(null)
const [dragging, setDragging] = useState(false)
return (
@@ -34,13 +34,15 @@ const TableContext = createContext<
| undefined
>(undefined)
export const TableProvider: FC<{
tableData: ParsedTableData
tableNode: SyntaxNode | null
tabularNode: SyntaxNode
view: EditorView
directTableChild?: boolean
}> = ({
export const TableProvider: FC<
React.PropsWithChildren<{
tableData: ParsedTableData
tableNode: SyntaxNode | null
tabularNode: SyntaxNode
view: EditorView
directTableChild?: boolean
}>
> = ({
tableData,
children,
tableNode,
@@ -21,7 +21,7 @@ const TabularContext = createContext<
| undefined
>(undefined)
export const TabularProvider: FC = ({ children }) => {
export const TabularProvider: FC<React.PropsWithChildren> = ({ children }) => {
const ref = useRef<HTMLDivElement>(null)
const [helpShown, setHelpShown] = useState(false)
const [columnWidthModalShown, setColumnWidthModalShown] = useState(false)
@@ -273,7 +273,7 @@ export const Tabular: FC<{
)
}
const TabularWrapper: FC = () => {
const TabularWrapper: FC<React.PropsWithChildren> = () => {
const { setSelection, selection } = useSelectionContext()
const { commitCellData, cellData } = useEditingContext()
const { ref } = useTabularContext()
@@ -7,13 +7,15 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import MaterialIcon from '../../../../../shared/components/material-icon'
import { useTabularContext } from '../contexts/tabular-context'
export const ToolbarButtonMenu: FC<{
id: string
label: string
icon: string
disabled?: boolean
disabledLabel?: string
}> = memo(function ButtonMenu({
export const ToolbarButtonMenu: FC<
React.PropsWithChildren<{
id: string
label: string
icon: string
disabled?: boolean
disabledLabel?: string
}>
> = memo(function ButtonMenu({
icon,
id,
label,
@@ -28,12 +28,12 @@ export const ToolbarButton = memo<{
disabledLabel,
}) {
const view = useCodeMirrorViewContext()
const handleMouseDown = useCallback(event => {
const handleMouseDown = useCallback((event: React.MouseEvent) => {
event.preventDefault()
}, [])
const handleClick = useCallback(
event => {
(event: React.MouseEvent) => {
if (command) {
emitTableGeneratorEvent(view, id)
event.preventDefault()
@@ -9,16 +9,18 @@ import { emitTableGeneratorEvent } from '../analytics'
import { useCodeMirrorViewContext } from '../../codemirror-context'
import classNames from 'classnames'
export const ToolbarDropdown: FC<{
id: string
label?: string
btnClassName?: string
icon?: string
tooltip?: string
disabled?: boolean
disabledTooltip?: string
showCaret?: boolean
}> = ({
export const ToolbarDropdown: FC<
React.PropsWithChildren<{
id: string
label?: string
btnClassName?: string
icon?: string
tooltip?: string
disabled?: boolean
disabledTooltip?: string
showCaret?: boolean
}>
> = ({
id,
label,
children,
@@ -112,12 +114,14 @@ export const ToolbarDropdown: FC<{
}
export const ToolbarDropdownItem: FC<
Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> & {
command: () => void
id: string
icon?: string
active?: boolean
}
React.PropsWithChildren<
Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> & {
command: () => void
id: string
icon?: string
active?: boolean
}
>
> = ({ children, command, id, icon, active, ...props }) => {
const view = useCodeMirrorViewContext()
const onClick = useCallback(() => {
@@ -8,12 +8,14 @@ import { EditorView } from '@codemirror/view'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import { useCodeMirrorViewContext } from '../codemirror-context'
export const ToolbarButtonMenu: FC<{
id: string
label: string
icon: React.ReactNode
altCommand?: (view: EditorView) => void
}> = memo(function ButtonMenu({ icon, id, label, altCommand, children }) {
export const ToolbarButtonMenu: FC<
React.PropsWithChildren<{
id: string
label: string
icon: React.ReactNode
altCommand?: (view: EditorView) => void
}>
> = memo(function ButtonMenu({ icon, id, label, altCommand, children }) {
const target = useRef<any>(null)
const { open, onToggle, ref } = useDropdown()
const view = useCodeMirrorViewContext()
@@ -6,14 +6,15 @@ import { useCodeMirrorViewContext } from '../codemirror-context'
import OLOverlay from '@/features/ui/components/ol/ol-overlay'
import OLPopover from '@/features/ui/components/ol/ol-popover'
export const ToolbarOverflow: FC<{
overflowed: boolean
overflowOpen: boolean
setOverflowOpen: (open: boolean) => void
overflowRef?: React.Ref<HTMLDivElement>
}> = ({ overflowed, overflowOpen, setOverflowOpen, overflowRef, children }) => {
export const ToolbarOverflow: FC<
React.PropsWithChildren<{
overflowed: boolean
overflowOpen: boolean
setOverflowOpen: (open: boolean) => void
overflowRef?: React.Ref<HTMLDivElement>
}>
> = ({ overflowed, overflowOpen, setOverflowOpen, overflowRef, children }) => {
const { t } = useTranslation()
const buttonRef = useRef<HTMLButtonElement>(null)
const keyboardInputRef = useRef(false)
const view = useCodeMirrorViewContext()
@@ -31,12 +31,12 @@ export const ToolbarButton = memo<{
}) {
const view = useCodeMirrorViewContext()
const handleMouseDown = useCallback(event => {
const handleMouseDown = useCallback((event: React.MouseEvent) => {
event.preventDefault()
}, [])
const handleClick = useCallback(
event => {
(event: React.MouseEvent) => {
emitToolbarEvent(view, id)
if (command) {
event.preventDefault()
@@ -1,3 +1,4 @@
import { createRoot } from 'react-dom/client'
import {
StateField,
StateEffect,
@@ -12,7 +13,6 @@ import {
getSpellCheckLanguage,
} from '@/features/source-editor/extensions/spelling/index'
import { sendMB } from '@/infrastructure/event-tracking'
import ReactDOM from 'react-dom'
import { SpellingSuggestions } from '@/features/source-editor/extensions/spelling/spelling-suggestions'
import { SplitTestProvider } from '@/shared/context/split-test-context'
import { addLearnedWord } from '@/features/source-editor/extensions/spelling/learned-words'
@@ -159,7 +159,8 @@ const createSpellingSuggestionList = (word: Word) => (view: EditorView) => {
const dom = document.createElement('div')
dom.classList.add('ol-cm-spelling-context-menu-tooltip')
ReactDOM.render(
const root = createRoot(dom)
root.render(
<SplitTestProvider>
<SpellingSuggestions
word={word}
@@ -235,12 +236,11 @@ const createSpellingSuggestionList = (word: Word) => (view: EditorView) => {
})
}}
/>
</SplitTestProvider>,
dom
</SplitTestProvider>
)
const destroy = () => {
ReactDOM.unmountComponentAtNode(dom)
root.unmount()
}
return { dom, destroy }
@@ -7,7 +7,7 @@ import {
import { Decoration, EditorView, WidgetType } from '@codemirror/view'
import { undo } from '@codemirror/commands'
import { ancestorNodeOfType } from '../../utils/tree-operations/ancestors'
import ReactDOM from 'react-dom'
import { createRoot } from 'react-dom/client'
import { PastedContentMenu } from '../../components/paste-html/pasted-content-menu'
import { SplitTestProvider } from '../../../../shared/context/split-test-context'
@@ -171,7 +171,8 @@ class PastedContentMenuWidget extends WidgetType {
toDOM(view: EditorView) {
const element = document.createElement('span')
ReactDOM.render(
const root = createRoot(element)
root.render(
<SplitTestProvider>
<PastedContentMenu
insertPastedContent={this.insertPastedContent}
@@ -179,8 +180,7 @@ class PastedContentMenuWidget extends WidgetType {
formatted={this.formatted}
pastedContent={this.pastedContent}
/>
</SplitTestProvider>,
element
</SplitTestProvider>
)
return element
}
@@ -1,10 +1,12 @@
import { createRoot, Root } from 'react-dom/client'
import { EditorView, WidgetType } from '@codemirror/view'
import { SyntaxNode } from '@lezer/common'
import * as ReactDOM from 'react-dom'
import { Tabular } from '../../../components/table-generator/tabular'
import { ParsedTableData } from '../../../components/table-generator/utils'
export class TabularWidget extends WidgetType {
static roots: WeakMap<HTMLElement, Root> = new WeakMap()
constructor(
private parsedTableData: ParsedTableData,
private tabularNode: SyntaxNode,
@@ -15,13 +17,21 @@ export class TabularWidget extends WidgetType {
super()
}
renderInDOMContainer(children: React.ReactNode, element: HTMLElement) {
const root = TabularWidget.roots.get(element) || createRoot(element)
if (!TabularWidget.roots.has(element)) {
TabularWidget.roots.set(element, root)
}
root.render(children)
}
toDOM(view: EditorView) {
const element = document.createElement('div')
element.classList.add('ol-cm-tabular')
if (this.tableNode) {
element.classList.add('ol-cm-environment-table')
}
ReactDOM.render(
this.renderInDOMContainer(
<Tabular
view={view}
tabularNode={this.tabularNode}
@@ -46,7 +56,7 @@ export class TabularWidget extends WidgetType {
}
updateDOM(element: HTMLElement, view: EditorView): boolean {
ReactDOM.render(
this.renderInDOMContainer(
<Tabular
view={view}
tabularNode={this.tabularNode}
@@ -68,6 +78,10 @@ export class TabularWidget extends WidgetType {
}
destroy(element: HTMLElement) {
ReactDOM.unmountComponentAtNode(element)
const root = TabularWidget.roots.get(element)
if (root) {
TabularWidget.roots.delete(element)
root.unmount()
}
}
}

Some files were not shown because too many files have changed in this diff Show More