(
+ () => {
+ const position = localStorage.getItem(
+ `doc.position.${getCurrentDocumentId()}`
+ )
+ return position ? position.cursorPosition : null
+ }
+ )
const isMounted = useIsMounted()
const { signal } = useAbortController()
- useEffect(() => {
- const listener = event => setCursorPosition(event.detail)
- window.addEventListener('cursor:editor:update', listener)
- return () => window.removeEventListener('cursor:editor:update', listener)
- }, [])
+ const editorUpdateListener = useCallback(
+ event => setCursorPosition(event.detail),
+ []
+ )
+ useEventListener('cursor:editor:update', editorUpdateListener)
const [syncToPdfInFlight, setSyncToPdfInFlight] = useState(false)
const [syncToCodeInFlight, setSyncToCodeInFlight] = useDetachState(
@@ -216,8 +231,17 @@ function PdfSynctexControls() {
const getCurrentFilePath = useCallback(() => {
const docId = getCurrentDocumentId()
+
+ if (!docId || !rootDocId) {
+ return null
+ }
+
let path = pathInFolder(docId)
+ if (!path) {
+ return null
+ }
+
// If the root file is folder/main.tex, then synctex sees the path as folder/./main.tex
const rootDocDirname = dirname(rootDocId)
@@ -286,8 +310,10 @@ function PdfSynctexControls() {
const syncToPdf = useCallback(
cursorPosition => {
+ const file = getCurrentFilePath()
+
const params = new URLSearchParams({
- file: getCurrentFilePath(),
+ file: file ?? '',
line: cursorPosition.row + 1,
column: cursorPosition.column,
}).toString()
@@ -377,13 +403,11 @@ function PdfSynctexControls() {
'detacher'
)
- useEffect(() => {
- const listener = event => syncToCode(event.detail)
- window.addEventListener('synctex:sync-to-position', listener)
- return () => {
- window.removeEventListener('synctex:sync-to-position', listener)
- }
- }, [syncToCode])
+ const syncToPositionListener = useCallback(
+ event => syncToCode(event.detail),
+ [syncToCode]
+ )
+ useEventListener('synctex:sync-to-position', syncToPositionListener)
const [hasSingleSelectedDoc, setHasSingleSelectedDoc] = useDetachState(
'has-single-selected-doc',
@@ -458,18 +482,3 @@ function PdfSynctexControls() {
}
export default memo(PdfSynctexControls)
-
-GoToCodeButton.propTypes = {
- isDetachLayout: PropTypes.bool,
- position: PropTypes.object.isRequired,
- syncToCode: PropTypes.func.isRequired,
- syncToCodeInFlight: PropTypes.bool.isRequired,
-}
-
-GoToPdfButton.propTypes = {
- cursorPosition: PropTypes.object,
- isDetachLayout: PropTypes.bool,
- syncToPdf: PropTypes.func.isRequired,
- syncToPdfInFlight: PropTypes.bool.isRequired,
- hasSingleSelectedDoc: PropTypes.bool.isRequired,
-}
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.tsx
similarity index 76%
rename from services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.jsx
rename to services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.tsx
index 5edfef6ef4..6968faacb2 100644
--- a/services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.jsx
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-validation-issue.tsx
@@ -1,14 +1,8 @@
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
-import PropTypes from 'prop-types'
import PdfLogEntry from './pdf-log-entry'
-PdfValidationIssue.propTypes = {
- name: PropTypes.string.isRequired,
- issue: PropTypes.any,
-}
-
-function PdfValidationIssue({ issue, name }) {
+function PdfValidationIssue({ issue, name }: { issue: any; name: string }) {
const { t } = useTranslation()
switch (name) {
@@ -20,12 +14,14 @@ function PdfValidationIssue({ issue, name }) {
<>
{t('project_too_large_please_reduce')}
- {issue.resources.map(resource => (
- -
- {resource.path} — {resource.kbSize}
- kb
-
- ))}
+ {issue.resources.map(
+ (resource: { path: string; kbSize: number }) => (
+ -
+ {resource.path} — {resource.kbSize}
+ kb
+
+ )
+ )}
>
}
@@ -42,7 +38,7 @@ function PdfValidationIssue({ issue, name }) {
<>
{t('following_paths_conflict')}
- {issue.map(detail => (
+ {issue.map((detail: { path: string }) => (
- /{detail.path}
))}
diff --git a/services/web/frontend/js/features/pdf-preview/components/stop-on-first-error-prompt.jsx b/services/web/frontend/js/features/pdf-preview/components/stop-on-first-error-prompt.tsx
similarity index 100%
rename from services/web/frontend/js/features/pdf-preview/components/stop-on-first-error-prompt.jsx
rename to services/web/frontend/js/features/pdf-preview/components/stop-on-first-error-prompt.tsx
diff --git a/services/web/frontend/js/features/pdf-preview/util/types.ts b/services/web/frontend/js/features/pdf-preview/util/types.ts
new file mode 100644
index 0000000000..78e953a3aa
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/util/types.ts
@@ -0,0 +1,41 @@
+import React from 'react'
+
+export type LogEntry = {
+ raw: string
+ level: ErrorLevel
+ key: string
+ file?: string
+ column?: number
+ line?: number
+ ruleId?: string
+ message?: string
+ content?: string
+ type?: string
+ messageComponent?: React.ReactNode
+ contentDetails?: string[]
+}
+
+export type ErrorLevel =
+ | 'error'
+ | 'warning'
+ | 'info'
+ | 'typesetting'
+ | 'raw'
+ | 'success'
+
+export type SourceLocation = {
+ file?: string
+ // `line should be either a number or null (i.e. not required), but currently sometimes we get
+ // an empty string (from BibTeX errors).
+ line?: number | string | null
+ column?: number
+}
+
+export type PdfFileData = { path: string; url: string }
+type PdfFileArchiveData = { path: string; url: string; fileCount: number }
+
+export type PdfFileDataList = {
+ top: PdfFileData[]
+ other: PdfFileData[]
+ archive?: PdfFileArchiveData
+}
diff --git a/services/web/frontend/js/features/preview/components/preview-log-entry-header.jsx b/services/web/frontend/js/features/preview/components/preview-log-entry-header.tsx
similarity index 83%
rename from services/web/frontend/js/features/preview/components/preview-log-entry-header.jsx
rename to services/web/frontend/js/features/preview/components/preview-log-entry-header.tsx
index 3c0c230c8f..8a6842f4ff 100644
--- a/services/web/frontend/js/features/preview/components/preview-log-entry-header.jsx
+++ b/services/web/frontend/js/features/preview/components/preview-log-entry-header.tsx
@@ -1,6 +1,5 @@
-import PropTypes from 'prop-types'
import classNames from 'classnames'
-import { useState, useRef } from 'react'
+import { useState, useRef, MouseEventHandler } from 'react'
import { useTranslation } from 'react-i18next'
import useResizeObserver from '../hooks/use-resize-observer'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
@@ -8,6 +7,7 @@ import Icon from '../../../shared/components/icon'
import OLButton from '@/features/ui/components/ol/ol-button'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import MaterialIcon from '@/shared/components/material-icon'
+import { ErrorLevel, SourceLocation } from '@/features/pdf-preview/util/types'
function PreviewLogEntryHeader({
sourceLocation,
@@ -19,9 +19,19 @@ function PreviewLogEntryHeader({
showCloseButton = false,
onSourceLocationClick,
onClose,
+}: {
+ headerTitle: string | React.ReactNode
+ level: ErrorLevel
+ headerIcon?: React.ReactElement
+ logType?: string
+ sourceLocation?: SourceLocation
+ showSourceLocationLink?: boolean
+ showCloseButton?: boolean
+ onSourceLocationClick?: MouseEventHandler
+ onClose?: () => void
}) {
const { t } = useTranslation()
- const logLocationSpanRef = useRef()
+ const logLocationSpanRef = useRef(null)
const [locationSpanOverflown, setLocationSpanOverflown] = useState(false)
useResizeObserver(
@@ -52,7 +62,7 @@ function PreviewLogEntryHeader({
location: file + (line ? `, ${line}` : ''),
})
- function checkLocationSpanOverflow(observedElement) {
+ function checkLocationSpanOverflow(observedElement: ResizeObserverEntry) {
const spanEl = observedElement.target
const isOverflowing = spanEl.scrollWidth > spanEl.clientWidth
setLocationSpanOverflown(isOverflowing)
@@ -104,7 +114,7 @@ function PreviewLogEntryHeader({
{headerIcon}
) : null}
{headerTitleText}
- {locationSpanOverflown && locationLinkText ? (
+ {locationSpanOverflown && locationLinkText && locationLink ? (
,
+ observedData: any,
+ callback: (observedElement: ResizeObserverEntry) => void
+) {
+ const resizeObserver = useRef()
const observe = useCallback(() => {
resizeObserver.current = new ResizeObserver(function (elementsObserved) {
@@ -9,15 +13,17 @@ function useResizeObserver(observedElement, observedData, callback) {
})
}, [callback])
- function unobserve(observedCurrent) {
- resizeObserver.current.unobserve(observedCurrent)
+ function unobserve(observedCurrent: HTMLElement) {
+ if (resizeObserver.current) {
+ resizeObserver.current.unobserve(observedCurrent)
+ }
}
useLayoutEffect(() => {
if ('ResizeObserver' in window) {
const observedCurrent = observedElement && observedElement.current
if (observedCurrent) {
- observe(observedElement.current)
+ observe()
}
if (resizeObserver.current && observedCurrent) {
diff --git a/services/web/frontend/js/features/ui/components/types/button-props.ts b/services/web/frontend/js/features/ui/components/types/button-props.ts
index e98538e168..fad0c49c6a 100644
--- a/services/web/frontend/js/features/ui/components/types/button-props.ts
+++ b/services/web/frontend/js/features/ui/components/types/button-props.ts
@@ -34,4 +34,5 @@ export type ButtonProps = {
| 'premium'
| 'premium-secondary'
| 'link'
+ | 'info'
}
diff --git a/services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.jsx b/services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.tsx
similarity index 98%
rename from services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.jsx
rename to services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.tsx
index 8c26517468..dd188ea54e 100644
--- a/services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.jsx
+++ b/services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsHints.tsx
@@ -1,11 +1,16 @@
-import PropTypes from 'prop-types'
import {
packageSuggestionsForCommands,
packageSuggestionsForEnvironments,
} from './HumanReadableLogsPackageSuggestions'
import getMeta from '@/utils/meta'
-function WikiLink({ url, children }) {
+function WikiLink({
+ url,
+ children,
+}: {
+ url: string
+ children: React.ReactNode
+}) {
if (getMeta('ol-wikiEnabled')) {
return (
@@ -17,12 +22,12 @@ function WikiLink({ url, children }) {
}
}
-WikiLink.propTypes = {
- url: PropTypes.string.isRequired,
- children: PropTypes.node.isRequired,
+type LogHint = {
+ extraInfoURL?: string | null
+ formattedContent: (details?: string[]) => React.ReactNode
}
-const hints = {
+const hints: { [ruleId: string]: LogHint } = {
hint_misplaced_alignment_tab_character: {
extraInfoURL:
'https://www.overleaf.com/learn/Errors/Misplaced_alignment_tab_character_%26',
diff --git a/services/web/frontend/js/shared/context/local-compile-context.tsx b/services/web/frontend/js/shared/context/local-compile-context.tsx
index 9eaec47428..aa4e6d6378 100644
--- a/services/web/frontend/js/shared/context/local-compile-context.tsx
+++ b/services/web/frontend/js/shared/context/local-compile-context.tsx
@@ -42,6 +42,7 @@ import {
PdfScrollPosition,
usePdfScrollPosition,
} from '@/shared/hooks/use-pdf-scroll-position'
+import { PdfFileDataList } from '@/features/pdf-preview/util/types'
type PdfFile = Record
@@ -54,7 +55,7 @@ export type CompileContext = {
deliveryLatencies: Record
draft: boolean
error?: string
- fileList?: Record
+ fileList?: PdfFileDataList
hasChanges: boolean
hasShortCompileTimeout: boolean
highlights?: Record[]
@@ -208,7 +209,7 @@ export const LocalCompileProvider: FC = ({ children }) => {
const [error, setError] = useState()
// the list of files that can be downloaded
- const [fileList, setFileList] = useState>()
+ const [fileList, setFileList] = useState()
// the raw contents of the log file
const [rawLog, setRawLog] = useState()
diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts
index 8b63bbbe1f..c077bbfa00 100644
--- a/services/web/frontend/js/utils/meta.ts
+++ b/services/web/frontend/js/utils/meta.ts
@@ -126,6 +126,7 @@ export interface Meta {
'ol-inviterName': string
'ol-isExternalAuthenticationSystemUsed': boolean
'ol-isManagedAccount': boolean
+ 'ol-isPaywallChangeCompileTimeoutEnabled': boolean
'ol-isProfessional': boolean
'ol-isRegisteredViaGoogle': boolean
'ol-isRestrictedTokenMember': boolean
diff --git a/services/web/frontend/stories/pdf-log-entry.stories.tsx b/services/web/frontend/stories/pdf-log-entry.stories.tsx
index 0e4823a520..aeb735fcb3 100644
--- a/services/web/frontend/stories/pdf-log-entry.stories.tsx
+++ b/services/web/frontend/stories/pdf-log-entry.stories.tsx
@@ -7,6 +7,7 @@ import { useMeta } from './hooks/use-meta'
import { FC, ReactNode } from 'react'
import { useScope } from './hooks/use-scope'
import { EditorView } from '@codemirror/view'
+import { LogEntry } from '@/features/pdf-preview/util/types'
const fakeSourceLocation = {
file: 'file.tex',
@@ -14,7 +15,7 @@ const fakeSourceLocation = {
column: 5,
}
-const fakeLogEntry = {
+const fakeLogEntry: LogEntry = {
key: 'fake',
ruleId: 'hint_misplaced_alignment_tab_character',
message: 'Fake message',
diff --git a/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx
index d1c0a81c6f..5b4efdd650 100644
--- a/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx
+++ b/services/web/test/frontend/components/pdf-preview/pdf-logs-entries.spec.tsx
@@ -11,6 +11,7 @@ import {
} from '@/features/ide-react/context/editor-manager-context'
import { EditorView } from '@codemirror/view'
import { OpenDocuments } from '@/features/ide-react/editor/open-documents'
+import { LogEntry } from '@/features/pdf-preview/util/types'
describe('', function () {
const fakeFindEntityResult: FindResult = {
@@ -48,7 +49,7 @@ describe('', function () {
)
}
- const logEntries = [
+ const logEntries: LogEntry[] = [
{
file: 'main.tex',
line: 9,