diff --git a/services/web/app/views/project/editor/meta.pug b/services/web/app/views/project/editor/meta.pug
index a4559fa7b7..034bc7ba2d 100644
--- a/services/web/app/views/project/editor/meta.pug
+++ b/services/web/app/views/project/editor/meta.pug
@@ -26,6 +26,11 @@ meta(name="ol-galileoEnabled" data-type="string" content=galileoEnabled)
meta(name="ol-galileoPromptWords" data-type="string" content=galileoPromptWords)
meta(name="ol-galileoFeatures" data-type="json" content=galileoFeatures)
meta(name="ol-detachRole" data-type="string" content=detachRole)
+meta(name="ol-allowedImageNames" data-type="json" content=allowedImageNames)
+meta(name="ol-languages" data-type="json" content=languages)
+meta(name="ol-dictionaryEditorEnabled" data-type="boolean" content=dictionaryEditorEnabled)
+meta(name="ol-editorThemes" data-type="json" content=editorThemes)
+meta(name="ol-legacyEditorThemes" data-type="json" content=legacyEditorThemes)
meta(name="ol-showUpgradePrompt" data-type="boolean" content=showUpgradePrompt)
meta(name="ol-useOpenTelemetry" data-type="boolean" content=useOpenTelemetry)
meta(name="ol-showSupport", data-type="boolean" content=showSupport)
diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 1a059fe8a2..0f39ad8af0 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -36,7 +36,9 @@
"ask_proj_owner_to_upgrade_for_git_bridge": "",
"ask_proj_owner_to_upgrade_for_longer_compiles": "",
"ask_proj_owner_to_upgrade_for_references_search": "",
+ "auto_close_brackets": "",
"auto_compile": "",
+ "auto_complete": "",
"autocompile_disabled": "",
"autocompile_disabled_reason": "",
"autocomplete": "",
@@ -47,6 +49,7 @@
"beta_program_not_participating": "",
"blank_project": "",
"blocked_filename": "",
+ "browser": "",
"can_edit": "",
"can_link_institution_email_acct_to_institution_acct": "",
"can_link_your_institution_acct_2": "",
@@ -88,11 +91,13 @@
"commit": "",
"common": "",
"commons_plan_tooltip": "",
+ "compact": "",
"compile_error_entry_description": "",
"compile_error_handling": "",
"compile_larger_projects": "",
"compile_mode": "",
"compile_terminated_by_user": "",
+ "compiler": "",
"compiling": "",
"confirm": "",
"confirm_affiliation": "",
@@ -131,6 +136,7 @@
"department": "",
"descending": "",
"description": "",
+ "dictionary": "",
"did_you_know_institution_providing_professional": "",
"disable_stop_on_first_error": "",
"dismiss": "",
@@ -162,6 +168,7 @@
"duplicate_file": "",
"duplicate_projects": "",
"easily_manage_your_project_files_everywhere": "",
+ "edit": "",
"edit_dictionary": "",
"edit_dictionary_empty": "",
"edit_dictionary_remove": "",
@@ -169,6 +176,7 @@
"editing": "",
"editor_and_pdf": "",
"editor_only_hide_pdf": "",
+ "editor_theme": "",
"email": "",
"email_or_password_wrong_try_again": "",
"emails_and_affiliations_explanation": "",
@@ -198,6 +206,8 @@
"first_name": "",
"fold_line": "",
"following_paths_conflict": "",
+ "font_family": "",
+ "font_size": "",
"free_accounts_have_timeout_upgrade_to_increase": "",
"free_plan_label": "",
"free_plan_tooltip": "",
@@ -323,6 +333,7 @@
"is_email_affiliated": "",
"join_project": "",
"joining": "",
+ "keybindings": "",
"labs_program_already_participating": "",
"labs_program_benefits": "<0>0>",
"labs_program_not_participating": "",
@@ -339,6 +350,7 @@
"leave_projects": "",
"let_us_know": "",
"limited_offer": "",
+ "line_height": "",
"link": "",
"link_account": "",
"link_accounts": "",
@@ -368,6 +380,7 @@
"login_with_service": "",
"logs_and_output_files": "",
"looks_like_youre_at": "",
+ "main_document": "",
"main_file_not_found": "",
"make_a_copy": "",
"make_email_primary_description": "",
@@ -433,6 +446,8 @@
"or": "",
"other_logs_and_files": "",
"other_output_files": "",
+ "overall_theme": "",
+ "overleaf": "",
"overleaf_labs": "",
"owned_by_x": "",
"owner": "",
@@ -448,6 +463,7 @@
"pdf_only_hide_editor": "",
"pdf_preview_error": "",
"pdf_rendering_error": "",
+ "pdf_viewer": "",
"pdf_viewer_error": "",
"plan_tooltip": "",
"please_change_primary_to_remove": "",
@@ -579,6 +595,7 @@
"session_error": "",
"session_expired_redirecting_to_login": "",
"sessions": "",
+ "settings": "",
"share": "",
"share_project": "",
"share_with_your_collabs": "",
@@ -605,6 +622,7 @@
"sort_by": "",
"sort_by_x": "",
"source": "",
+ "spell_check": "",
"sso_link_error": "",
"start_by_adding_your_email": "",
"start_free_trial": "",
@@ -624,6 +642,7 @@
"sync_project_to_github_explanation": "",
"sync_to_dropbox": "",
"sync_to_github": "",
+ "syntax_validation": "",
"tab_connecting": "",
"tab_no_longer_connected": "",
"tag_name_cannot_exceed_characters": "",
@@ -634,6 +653,7 @@
"template_approved_by_publisher": "",
"templates": "",
"terminated": "",
+ "tex_live_version": "",
"thank_you_exclamation": "",
"thanks_settings_updated": "",
"this_action_cannot_be_undone": "",
@@ -709,6 +729,7 @@
"we_cant_find_any_sections_or_subsections_in_this_file": "",
"we_logged_you_in": "",
"welcome_to_sl": "",
+ "wide": "",
"with_premium_subscription_you_also_get": "",
"word_count": "",
"work_offline": "",
diff --git a/services/web/frontend/js/features/editor-left-menu/components/editor-left-menu.tsx b/services/web/frontend/js/features/editor-left-menu/components/editor-left-menu.tsx
index f64676445c..3e00fd058d 100644
--- a/services/web/frontend/js/features/editor-left-menu/components/editor-left-menu.tsx
+++ b/services/web/frontend/js/features/editor-left-menu/components/editor-left-menu.tsx
@@ -4,6 +4,7 @@ import HelpMenu from './help-menu'
import { useLayoutContext } from '../../../shared/context/layout-context'
import classNames from 'classnames'
import SyncMenu from './sync-menu'
+import SettingsMenu from './settings-menu'
export default function EditorLeftMenu() {
const { leftMenuShown, setLeftMenuShown } = useLayoutContext()
@@ -17,6 +18,7 @@ export default function EditorLeftMenu() {
+
{leftMenuShown ? (
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings-menu-dropdown.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings-menu-dropdown.tsx
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings-menu.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings-menu.tsx
new file mode 100644
index 0000000000..5504419e2f
--- /dev/null
+++ b/services/web/frontend/js/features/editor-left-menu/components/settings-menu.tsx
@@ -0,0 +1,50 @@
+import { Form } from 'react-bootstrap'
+import { useTranslation } from 'react-i18next'
+import getMeta from '../../../utils/meta'
+import SettingsAutoCloseBrackets from './settings/settings-auto-close-brackets'
+import SettingsAutoComplete from './settings/settings-auto-complete'
+import SettingsCompiler from './settings/settings-compiler'
+import SettingsDictionary from './settings/settings-dictionary'
+import SettingsDocument from './settings/settings-document'
+import SettingsEditorTheme from './settings/settings-editor-theme'
+import SettingsFontFamily from './settings/settings-font-family'
+import SettingsFontSize from './settings/settings-font-size'
+import SettingsImageName from './settings/settings-image-name'
+import SettingsKeybindings from './settings/settings-keybindings'
+import SettingsLineHeight from './settings/settings-line-height'
+import SettingsOverallTheme from './settings/settings-overall-theme'
+import SettingsPdfViewer from './settings/settings-pdf-viewer'
+import SettingsSpellCheckLanguage from './settings/settings-spell-check-language'
+import SettingsSyntaxValidation from './settings/settings-syntax-validation'
+
+export default function SettingsMenu() {
+ const { t } = useTranslation()
+ const anonymous = getMeta('ol-anonymous') as boolean | undefined
+
+ if (anonymous === true || anonymous === undefined) {
+ return null
+ }
+
+ return (
+ <>
+
{t('settings')}
+
+ >
+ )
+}
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-auto-close-brackets.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-auto-close-brackets.tsx
new file mode 100644
index 0000000000..5bc2903fb5
--- /dev/null
+++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-auto-close-brackets.tsx
@@ -0,0 +1,23 @@
+import { useTranslation } from 'react-i18next'
+import SettingsMenuSelect from './settings-menu-select'
+
+export default function SettingsAutoCloseBrackets() {
+ const { t } = useTranslation()
+
+ return (
+
+ )
+}
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-auto-complete.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-auto-complete.tsx
new file mode 100644
index 0000000000..eeca5b6658
--- /dev/null
+++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-auto-complete.tsx
@@ -0,0 +1,23 @@
+import { useTranslation } from 'react-i18next'
+import SettingsMenuSelect from './settings-menu-select'
+
+export default function SettingsAutoComplete() {
+ const { t } = useTranslation()
+
+ return (
+
+ )
+}
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-compiler.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-compiler.tsx
new file mode 100644
index 0000000000..bf7307a146
--- /dev/null
+++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-compiler.tsx
@@ -0,0 +1,37 @@
+import { useTranslation } from 'react-i18next'
+import { useEditorContext } from '../../../../shared/context/editor-context'
+import SettingsMenuSelect from './settings-menu-select'
+
+export default function SettingsCompiler() {
+ const { t } = useTranslation()
+ const { permissionsLevel } = useEditorContext()
+
+ if (permissionsLevel === 'readOnly') {
+ return null
+ }
+
+ return (
+
+ )
+}
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-dictionary.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-dictionary.tsx
new file mode 100644
index 0000000000..3cbe57f1f3
--- /dev/null
+++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-dictionary.tsx
@@ -0,0 +1,31 @@
+import { useState } from 'react'
+import { Button } from 'react-bootstrap'
+import { useTranslation } from 'react-i18next'
+import getMeta from '../../../../utils/meta'
+import DictionaryModal from '../../../dictionary/components/dictionary-modal'
+
+export default function SettingsDictionary() {
+ const { t } = useTranslation()
+ const [showModal, setShowModal] = useState(false)
+ const dictionaryEditorEnabled = getMeta(
+ 'ol-dictionaryEditorEnabled'
+ ) as boolean
+
+ if (!dictionaryEditorEnabled) {
+ return null
+ }
+
+ return (
+
+
+
+
+ setShowModal(false)}
+ />
+
+ )
+}
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-document.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-document.tsx
new file mode 100644
index 0000000000..76227fc5bb
--- /dev/null
+++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-document.tsx
@@ -0,0 +1,53 @@
+import { useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
+import isValidTeXFile from '../../../../main/is-valid-tex-file'
+import { useEditorContext } from '../../../../shared/context/editor-context'
+import { useProjectContext } from '../../../../shared/context/project-context'
+import useScopeValue from '../../../../shared/hooks/use-scope-value'
+import SettingsMenuSelect from './settings-menu-select'
+import type { Option } from './settings-menu-select'
+
+type Doc = {
+ doc: {
+ name: string
+ id: string
+ type: string
+ selected: boolean
+ }
+ path: string
+}
+
+export default function SettingsDocument() {
+ const { t } = useTranslation()
+
+ const { permissionsLevel } = useEditorContext()
+
+ const { rootDocId } = useProjectContext()
+ const [docs] = useScopeValue('docs')
+
+ const validDocsOptions = useMemo(() => {
+ const filteredDocs =
+ docs?.filter(
+ doc => isValidTeXFile(doc.doc.name) || rootDocId === doc.doc.id
+ ) ?? []
+
+ const mappedDocs: Array = useMemo(
+ () =>
+ overallThemes?.map(({ name, val }) => ({
+ value: val,
+ label: name,
+ })) ?? [],
+ [overallThemes]
+ )
+
+ // TODO: check for IEEE brand by:
+ // - const brandVariation = getMeta('ol-brandVariation') as any[]
+ // - settings.overleaf != null && !isIEEE(brandVariation)
+ if (!overallThemes) {
+ return null
+ }
+
+ return (
+
+ )
+}
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-pdf-viewer.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-pdf-viewer.tsx
new file mode 100644
index 0000000000..bc31ccfee4
--- /dev/null
+++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-pdf-viewer.tsx
@@ -0,0 +1,23 @@
+import { useTranslation } from 'react-i18next'
+import SettingsMenuSelect from './settings-menu-select'
+
+export default function SettingsPdfViewer() {
+ const { t } = useTranslation()
+
+ return (
+
+ )
+}
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-spell-check-language.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-spell-check-language.tsx
new file mode 100644
index 0000000000..7ea6b76939
--- /dev/null
+++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-spell-check-language.tsx
@@ -0,0 +1,36 @@
+import { useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
+import getMeta from '../../../../utils/meta'
+import SettingsMenuSelect from './settings-menu-select'
+import type { Optgroup } from './settings-menu-select'
+
+type Language = {
+ name: string
+ code: string
+}
+
+export default function SettingsSpellCheckLanguage() {
+ const { t } = useTranslation()
+ const languages = getMeta('ol-languages') as Language[] | undefined
+
+ const optgroup: Optgroup = useMemo(
+ () => ({
+ label: 'Language',
+ options:
+ languages?.map(language => ({
+ value: language.code,
+ label: language.name,
+ })) ?? [],
+ }),
+ [languages]
+ )
+
+ return (
+
+ )
+}
diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-syntax-validation.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-syntax-validation.tsx
new file mode 100644
index 0000000000..0a58460cf5
--- /dev/null
+++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-syntax-validation.tsx
@@ -0,0 +1,23 @@
+import { useTranslation } from 'react-i18next'
+import SettingsMenuSelect from './settings-menu-select'
+
+export default function SettingsSyntaxValidation() {
+ const { t } = useTranslation()
+
+ return (
+
+ )
+}
diff --git a/services/web/frontend/js/shared/context/layout-context.js b/services/web/frontend/js/shared/context/layout-context.js
index d2289781ae..9edf8febcb 100644
--- a/services/web/frontend/js/shared/context/layout-context.js
+++ b/services/web/frontend/js/shared/context/layout-context.js
@@ -84,6 +84,11 @@ export function LayoutProvider({ children }) {
// whether to display the editor and preview side-by-side or full-width ("flat")
const [pdfLayout, setPdfLayout] = useScopeValue('ui.pdfLayout')
+ // whether stylesheet on theme is loading
+ const [loadingStyleSheet, setLoadingStyleSheet] = useScopeValue(
+ 'ui.loadingStyleSheet'
+ )
+
// switch to either side-by-side or flat (full-width) layout
const switchLayout = useCallback(() => {
setPdfLayout(layout => {
@@ -157,10 +162,12 @@ export function LayoutProvider({ children }) {
pdfLayout,
pdfPreviewOpen,
reviewPanelOpen,
+ loadingStyleSheet,
setChatIsOpen,
setLeftMenuShown,
setPdfLayout,
setReviewPanelOpen,
+ setLoadingStyleSheet,
setView,
switchLayout,
view,
@@ -176,10 +183,12 @@ export function LayoutProvider({ children }) {
pdfLayout,
pdfPreviewOpen,
reviewPanelOpen,
+ loadingStyleSheet,
setChatIsOpen,
setLeftMenuShown,
setPdfLayout,
setReviewPanelOpen,
+ setLoadingStyleSheet,
setView,
switchLayout,
view,
diff --git a/services/web/frontend/stylesheets/app/editor/left-menu.less b/services/web/frontend/stylesheets/app/editor/left-menu.less
index 978c9223f8..5bae91a13a 100644
--- a/services/web/frontend/stylesheets/app/editor/left-menu.less
+++ b/services/web/frontend/stylesheets/app/editor/left-menu.less
@@ -145,6 +145,42 @@
}
}
}
+
+ .left-menu-setting {
+ padding: 0 9px;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ justify-content: flex-end;
+ border-bottom: solid 1px rgba(0, 0, 0, 0.07);
+ margin-bottom: 0;
+
+ &:first-child {
+ margin-top: -9px;
+ }
+
+ &:last-child {
+ border-bottom: 0;
+ }
+
+ &:hover {
+ background-color: @link-color;
+
+ label {
+ color: @white;
+ }
+ }
+
+ select.form-control {
+ height: 23px;
+ padding: 1px @padding-xs;
+ font-size: inherit;
+
+ &:hover {
+ background-color: @ol-blue-gray-1;
+ }
+ }
+ }
}
#left-menu-mask {