first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsAutoCloseBrackets() {
const { t } = useTranslation()
const { autoPairDelimiters, setAutoPairDelimiters } =
useProjectSettingsContext()
return (
<SettingsMenuSelect
onChange={setAutoPairDelimiters}
value={autoPairDelimiters}
options={[
{
value: true,
label: t('on'),
},
{
value: false,
label: t('off'),
},
]}
label={t('auto_close_brackets')}
name="autoPairDelimiters"
/>
)
}

View File

@@ -0,0 +1,27 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsAutoComplete() {
const { t } = useTranslation()
const { autoComplete, setAutoComplete } = useProjectSettingsContext()
return (
<SettingsMenuSelect
onChange={setAutoComplete}
value={autoComplete}
options={[
{
value: true,
label: t('on'),
},
{
value: false,
label: t('off'),
},
]}
label={t('auto_complete')}
name="autoComplete"
/>
)
}

View File

@@ -0,0 +1,39 @@
import { useTranslation } from 'react-i18next'
import type { ProjectCompiler } from '../../../../../../types/project-settings'
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsCompiler() {
const { t } = useTranslation()
const { write } = usePermissionsContext()
const { compiler, setCompiler } = useProjectSettingsContext()
return (
<SettingsMenuSelect<ProjectCompiler>
onChange={setCompiler}
value={compiler}
disabled={!write}
options={[
{
value: 'pdflatex',
label: 'pdfLaTeX',
},
{
value: 'latex',
label: 'LaTeX',
},
{
value: 'xelatex',
label: 'XeLaTeX',
},
{
value: 'lualatex',
label: 'LuaLaTeX',
},
]}
label={t('compiler')}
name="compiler"
/>
)
}

View File

@@ -0,0 +1,30 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import DictionaryModal from '../../../dictionary/components/dictionary-modal'
import OLButton from '@/features/ui/components/ol/ol-button'
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
export default function SettingsDictionary() {
const { t } = useTranslation()
const [showModal, setShowModal] = useState(false)
return (
<OLFormGroup className="left-menu-setting">
<OLFormLabel htmlFor="dictionary-settings">{t('dictionary')}</OLFormLabel>
<OLButton
id="dictionary-settings"
variant="secondary"
size="sm"
onClick={() => setShowModal(true)}
>
{t('edit')}
</OLButton>
<DictionaryModal
show={showModal}
handleHide={() => setShowModal(false)}
/>
</OLFormGroup>
)
}

View File

@@ -0,0 +1,48 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { isValidTeXFile } from '../../../../main/is-valid-tex-file'
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
import { useFileTreeData } from '@/shared/context/file-tree-data-context'
export default function SettingsDocument() {
const { t } = useTranslation()
const { write } = usePermissionsContext()
const { docs } = useFileTreeData()
const { rootDocId, setRootDocId } = useProjectSettingsContext()
const validDocsOptions = useMemo(() => {
const filteredDocs =
docs?.filter(
doc => isValidTeXFile(doc.doc.name) || rootDocId === doc.doc.id
) ?? []
const mappedDocs: Array<Option> = filteredDocs.map(doc => ({
value: doc.doc.id,
label: doc.path,
}))
if (!rootDocId) {
mappedDocs.unshift({
value: '',
label: 'None',
disabled: true,
})
}
return mappedDocs
}, [docs, rootDocId])
return (
<SettingsMenuSelect
onChange={setRootDocId}
value={rootDocId ?? ''}
disabled={!write}
options={validDocsOptions}
label={t('main_document')}
name="rootDocId"
/>
)
}

View File

@@ -0,0 +1,45 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
export default function SettingsEditorTheme() {
const { t } = useTranslation()
const editorThemes = getMeta('ol-editorThemes')
const legacyEditorThemes = getMeta('ol-legacyEditorThemes')
const { editorTheme, setEditorTheme } = useProjectSettingsContext()
const options = useMemo(() => {
const editorThemeOptions: Array<Option> =
editorThemes?.map(theme => ({
value: theme,
label: theme.replace(/_/g, ' '),
})) ?? []
const dividerOption: Option = {
value: '-',
label: '—————————————————',
disabled: true,
}
const legacyEditorThemeOptions: Array<Option> =
legacyEditorThemes?.map(theme => ({
value: theme,
label: theme.replace(/_/g, ' ') + ' (Legacy)',
})) ?? []
return [...editorThemeOptions, dividerOption, ...legacyEditorThemeOptions]
}, [editorThemes, legacyEditorThemes])
return (
<SettingsMenuSelect
onChange={setEditorTheme}
value={editorTheme}
options={options}
label={t('editor_theme')}
name="editorTheme"
/>
)
}

View File

@@ -0,0 +1,47 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import BetaBadge from '@/shared/components/beta-badge'
import { FontFamily } from '@/shared/utils/styles'
export default function SettingsFontFamily() {
const { t } = useTranslation()
const { fontFamily, setFontFamily } = useProjectSettingsContext()
return (
<div className="left-menu-setting-position">
<SettingsMenuSelect<FontFamily>
onChange={setFontFamily}
value={fontFamily}
options={[
{
value: 'monaco',
label: 'Monaco / Menlo / Consolas',
},
{
value: 'lucida',
label: 'Lucida / Source Code Pro',
},
{
value: 'opendyslexicmono',
label: 'OpenDyslexic Mono',
},
]}
label={t('font_family')}
name="fontFamily"
/>
<BetaBadge
phase="release"
link={{
href: 'https://docs.google.com/forms/d/e/1FAIpQLScOt_IHTrcaM_uitP9dgCo_r4dl4cy9Ry6LhYYcwTN4qDTDUg/viewform',
className: 'left-menu-setting-icon',
}}
tooltip={{
id: 'font-family-tooltip',
text: `${t('new_font_open_dyslexic')} ${t('click_to_give_feedback')}`,
placement: 'right',
}}
/>
</div>
)
}

View File

@@ -0,0 +1,25 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
const sizes = [10, 11, 12, 13, 14, 16, 18, 20, 22, 24]
const options: Option<number>[] = sizes.map(size => ({
value: size,
label: `${size}px`,
}))
export default function SettingsFontSize() {
const { t } = useTranslation()
const { fontSize, setFontSize } = useProjectSettingsContext()
return (
<SettingsMenuSelect
onChange={setFontSize}
value={fontSize}
options={options}
label={t('font_size')}
name="fontSize"
/>
)
}

View File

@@ -0,0 +1,42 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import { usePermissionsContext } from '@/features/ide-react/context/permissions-context'
export default function SettingsImageName() {
const { t } = useTranslation()
const { imageName, setImageName } = useProjectSettingsContext()
const { write } = usePermissionsContext()
const allowedImageNames = useMemo(
() => getMeta('ol-allowedImageNames') || [],
[]
)
const options: Array<Option> = useMemo(
() =>
allowedImageNames.map(({ imageName, imageDesc }) => ({
value: imageName,
label: imageDesc,
})),
[allowedImageNames]
)
if (allowedImageNames.length === 0) {
return null
}
return (
<SettingsMenuSelect
onChange={setImageName}
value={imageName}
disabled={!write}
options={options}
label={t('tex_live_version')}
name="imageName"
/>
)
}

View File

@@ -0,0 +1,32 @@
import { useTranslation } from 'react-i18next'
import type { Keybindings } from '../../../../../../types/user-settings'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsKeybindings() {
const { t } = useTranslation()
const { mode, setMode } = useProjectSettingsContext()
return (
<SettingsMenuSelect<Keybindings>
onChange={setMode}
value={mode}
options={[
{
value: 'default',
label: 'None',
},
{
value: 'vim',
label: 'Vim',
},
{
value: 'emacs',
label: 'Emacs',
},
]}
label={t('keybindings')}
name="mode"
/>
)
}

View File

@@ -0,0 +1,32 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import { LineHeight } from '@/shared/utils/styles'
export default function SettingsLineHeight() {
const { t } = useTranslation()
const { lineHeight, setLineHeight } = useProjectSettingsContext()
return (
<SettingsMenuSelect<LineHeight>
onChange={setLineHeight}
value={lineHeight}
options={[
{
value: 'compact',
label: t('compact'),
},
{
value: 'normal',
label: t('normal'),
},
{
value: 'wide',
label: t('wide'),
},
]}
label={t('line_height')}
name="lineHeight"
/>
)
}

View File

@@ -0,0 +1,27 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsMathPreview() {
const { t } = useTranslation()
const { mathPreview, setMathPreview } = useProjectSettingsContext()
return (
<SettingsMenuSelect
onChange={setMathPreview}
value={mathPreview}
options={[
{
value: true,
label: t('on'),
},
{
value: false,
label: t('off'),
},
]}
label={t('equation_preview')}
name="mathPreview"
/>
)
}

View File

@@ -0,0 +1,125 @@
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
import { ChangeEventHandler, useCallback, useEffect, useRef } from 'react'
import { Spinner } from 'react-bootstrap-5'
import { useEditorLeftMenuContext } from '@/features/editor-left-menu/components/editor-left-menu-context'
type PossibleValue = string | number | boolean
export type Option<T extends PossibleValue = string> = {
value: T
label: string
ariaHidden?: 'true' | 'false'
disabled?: boolean
}
export type Optgroup<T extends PossibleValue = string> = {
label: string
options: Array<Option<T>>
}
type SettingsMenuSelectProps<T extends PossibleValue = string> = {
label: string
name: string
options: Array<Option<T>>
optgroup?: Optgroup<T>
loading?: boolean
onChange: (val: T) => void
value?: T
disabled?: boolean
}
export default function SettingsMenuSelect<T extends PossibleValue = string>({
label,
name,
options,
optgroup,
loading,
onChange,
value,
disabled = false,
}: SettingsMenuSelectProps<T>) {
const handleChange: ChangeEventHandler<HTMLSelectElement> = useCallback(
event => {
const selectedValue = event.target.value
let onChangeValue: PossibleValue = selectedValue
if (typeof value === 'boolean') {
onChangeValue = selectedValue === 'true'
} else if (typeof value === 'number') {
onChangeValue = parseInt(selectedValue, 10)
}
onChange(onChangeValue as T)
},
[onChange, value]
)
const { settingToFocus } = useEditorLeftMenuContext()
const selectRef = useRef<HTMLSelectElement | null>(null)
useEffect(() => {
if (settingToFocus === name && selectRef.current) {
selectRef.current.scrollIntoView({
block: 'center',
behavior: 'smooth',
})
selectRef.current.focus()
}
// clear the focus setting
window.dispatchEvent(
new CustomEvent('ui.focus-setting', { detail: undefined })
)
}, [name, settingToFocus])
return (
<OLFormGroup
controlId={`settings-menu-${name}`}
className="left-menu-setting"
>
<OLFormLabel>{label}</OLFormLabel>
{loading ? (
<p className="mb-0">
<Spinner
animation="border"
aria-hidden="true"
size="sm"
role="status"
/>
</p>
) : (
<OLFormSelect
size="sm"
onChange={handleChange}
value={value?.toString()}
disabled={disabled}
ref={selectRef}
>
{options.map(option => (
<option
key={`${name}-${option.value}`}
value={option.value.toString()}
aria-hidden={option.ariaHidden}
disabled={option.disabled}
>
{option.label}
</option>
))}
{optgroup ? (
<optgroup label={optgroup.label}>
{optgroup.options.map(option => (
<option
value={option.value.toString()}
key={option.value.toString()}
>
{option.label}
</option>
))}
</optgroup>
) : null}
</OLFormSelect>
)}
</OLFormGroup>
)
}

View File

@@ -0,0 +1,42 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useLayoutContext } from '../../../../shared/context/layout-context'
import getMeta from '../../../../utils/meta'
import SettingsMenuSelect, { Option } from './settings-menu-select'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import type { OverallThemeMeta } from '../../../../../../types/project-settings'
import { isIEEEBranded } from '@/utils/is-ieee-branded'
import { OverallTheme } from '@/shared/utils/styles'
export default function SettingsOverallTheme() {
const { t } = useTranslation()
const overallThemes = getMeta('ol-overallThemes') as
| OverallThemeMeta[]
| undefined
const { loadingStyleSheet } = useLayoutContext()
const { overallTheme, setOverallTheme } = useProjectSettingsContext()
const options: Array<Option<OverallTheme>> = useMemo(
() =>
overallThemes?.map(({ name, val }) => ({
value: val,
label: name,
})) ?? [],
[overallThemes]
)
if (!overallThemes || isIEEEBranded()) {
return null
}
return (
<SettingsMenuSelect<OverallTheme>
onChange={setOverallTheme}
value={overallTheme}
options={options}
loading={loadingStyleSheet}
label={t('overall_theme')}
name="overallTheme"
/>
)
}

View File

@@ -0,0 +1,28 @@
import { useTranslation } from 'react-i18next'
import type { PdfViewer } from '../../../../../../types/user-settings'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsPdfViewer() {
const { t } = useTranslation()
const { pdfViewer, setPdfViewer } = useProjectSettingsContext()
return (
<SettingsMenuSelect<PdfViewer>
onChange={setPdfViewer}
value={pdfViewer}
options={[
{
value: 'pdfjs',
label: t('overleaf'),
},
{
value: 'native',
label: t('browser'),
},
]}
label={t('pdf_viewer')}
name="pdfViewer"
/>
)
}

View File

@@ -0,0 +1,42 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Optgroup } from './settings-menu-select'
import { useEditorContext } from '@/shared/context/editor-context'
import { supportsWebAssembly } from '@/utils/wasm'
export default function SettingsSpellCheckLanguage() {
const { t } = useTranslation()
const { spellCheckLanguage, setSpellCheckLanguage } =
useProjectSettingsContext()
const { permissionsLevel } = useEditorContext()
const optgroup: Optgroup = useMemo(() => {
const options = (getMeta('ol-languages') ?? [])
// only include spell-check languages that are available in the client
.filter(language => language.dic !== undefined)
return {
label: 'Language',
options: options.map(language => ({
value: language.code,
label: language.name,
})),
}
}, [])
return (
<SettingsMenuSelect
onChange={setSpellCheckLanguage}
value={supportsWebAssembly() ? spellCheckLanguage : ''}
options={[{ value: '', label: t('off') }]}
optgroup={optgroup}
label={t('spell_check')}
name="spellCheckLanguage"
disabled={permissionsLevel === 'readOnly' || !supportsWebAssembly()}
/>
)
}

View File

@@ -0,0 +1,27 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsSyntaxValidation() {
const { t } = useTranslation()
const { syntaxValidation, setSyntaxValidation } = useProjectSettingsContext()
return (
<SettingsMenuSelect<boolean>
onChange={setSyntaxValidation}
value={syntaxValidation}
options={[
{
value: true,
label: t('on'),
},
{
value: false,
label: t('off'),
},
]}
label={t('syntax_validation')}
name="syntaxValidation"
/>
)
}