Tearing down of old Editor (Left settings menu) (#31357)
* removing one class * removing the ide redesign class * moving error logs files from ide-redesign to orignal directory * moving the final file of error logs * removing the left settings menu files * deleting left-menu-mask.tsx and related css styling * deleting editor-left-menu-body.tsx * deleting download-menu.tsx and related css and test and story files * deleting actions-menu.tsx and test and story files * deleting help-menu.tsx and test and story files * deleting sync-menu.tsx and test and story files * deleting settings-menu.tsx file * deleting download-pdf.tsx * deleting download-source.tsx * deleting actions-copy-project.tsx * deleting actions-word-count.tsx ans tests * deleting help-contact-us.tsx * deleting help-documentation.tsx and related files * deleting help-show-hotkeys.tsx and related files * deleting settings-auto-close-brackets.tsx * deleting settings-auto-complete * settings-compiler * settings-dictionary * deleting setting-menu files and test files * styles:fix * make cleanup_unused_locales * removing some extra css and adding some comments * npm run extract-translations * adding settings-menu-select.tsx back * adding back settings-overall-theme.tsx * format:fix * removing the settings-overall-theme.tsx and related test file * deleting settings-menu-select and adding option type in use-editor-theme-option-group * removing css * deleting files and styling related to LeftMenuButton * removing the related left editor menu files * removing the paths * Revert "removing the related left editor menu files" This reverts commit 78ffbfff88cfd3ceb19946ac45a1ed6a790388f4. * adding back the overall-theme-settings.tsx and removing the tests related to removed file * adding back the tests with new component * make cleanup_unused_locales * extract-translations * deleting the actions-manage-template.tsx file GitOrigin-RevId: 75bcbef81740cea0452eca62f2ce52b7f10acd08
This commit is contained in:
@@ -648,8 +648,6 @@
|
||||
"folder_location": "",
|
||||
"folder_name": "",
|
||||
"following_paths_conflict": "",
|
||||
"font_family": "",
|
||||
"font_size": "",
|
||||
"footer_about_us": "",
|
||||
"footer_contact_us": "",
|
||||
"footnotes": "",
|
||||
@@ -1011,7 +1009,6 @@
|
||||
"limited_to_n_collaborators_per_project": "",
|
||||
"limited_to_n_collaborators_per_project_plural": "",
|
||||
"line": "",
|
||||
"line_height": "",
|
||||
"line_width_is_the_width_of_the_line_in_the_current_environment": "",
|
||||
"link": "",
|
||||
"link_account": "",
|
||||
@@ -1152,7 +1149,6 @@
|
||||
"new_compile_domain_notice": "",
|
||||
"new_compiles_in_this_project_will_automatically_use_the_newest_version": "",
|
||||
"new_create_tables_and_equations": "",
|
||||
"new_editor_look": "",
|
||||
"new_error_logs_make_it_easier_to_find_whats_wrong": "",
|
||||
"new_file": "",
|
||||
"new_folder": "",
|
||||
@@ -1326,7 +1322,6 @@
|
||||
"please_ask_the_project_owner_to_upgrade_to_track_changes": "",
|
||||
"please_change_primary_to_remove": "",
|
||||
"please_compile_pdf_before_download": "",
|
||||
"please_compile_pdf_before_word_count": "",
|
||||
"please_confirm_primary_email_or_edit": "",
|
||||
"please_confirm_secondary_email_or_edit": "",
|
||||
"please_confirm_your_email_before_making_it_default": "",
|
||||
@@ -1678,7 +1673,6 @@
|
||||
"show_document_preamble": "",
|
||||
"show_equation_preview": "",
|
||||
"show_file_tree": "",
|
||||
"show_hotkeys": "",
|
||||
"show_in_code": "",
|
||||
"show_in_pdf": "",
|
||||
"show_less": "",
|
||||
@@ -1723,10 +1717,8 @@
|
||||
"sort_by": "",
|
||||
"sort_by_x": "",
|
||||
"sort_projects": "",
|
||||
"source": "",
|
||||
"speak": "",
|
||||
"speech_input_not_available": "",
|
||||
"spell_check": "",
|
||||
"spellcheck": "",
|
||||
"spellcheck_language": "",
|
||||
"split_view": "",
|
||||
@@ -2243,7 +2235,6 @@
|
||||
"wide": "",
|
||||
"will_lose_edit_access_on_date": "",
|
||||
"with_premium_subscription_you_also_get": "",
|
||||
"word_count": "",
|
||||
"word_count_lower": "",
|
||||
"words": "",
|
||||
"work_in_vim_or_emacs_emulation_mode": "",
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import EditorCloneProjectModalWrapper from '../../clone-project-modal/components/editor-clone-project-modal-wrapper'
|
||||
import LeftMenuButton from './left-menu-button'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import useOpenProject from '@/shared/hooks/use-open-project'
|
||||
|
||||
export default function ActionsCopyProject() {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const openProject = useOpenProject()
|
||||
|
||||
const handleShowModal = useCallback(() => {
|
||||
eventTracking.sendMB('left-menu-copy')
|
||||
setShowModal(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<LeftMenuButton onClick={handleShowModal} icon="file_copy">
|
||||
{t('copy_project')}
|
||||
</LeftMenuButton>
|
||||
<EditorCloneProjectModalWrapper
|
||||
show={showModal}
|
||||
handleHide={() => setShowModal(false)}
|
||||
openProject={openProject}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { ElementType } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
||||
import ActionsCopyProject from './actions-copy-project'
|
||||
import ActionsWordCount from './actions-word-count'
|
||||
|
||||
const components = importOverleafModules('editorLeftMenuManageTemplate') as {
|
||||
import: { default: ElementType }
|
||||
path: string
|
||||
}[]
|
||||
|
||||
export default function ActionsMenu() {
|
||||
const { t } = useTranslation()
|
||||
const anonymous = getMeta('ol-anonymous')
|
||||
|
||||
if (anonymous) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4>{t('actions')}</h4>
|
||||
<ul className="list-unstyled nav">
|
||||
<li>
|
||||
<ActionsCopyProject />
|
||||
</li>
|
||||
{components.map(({ import: { default: Component }, path }) => (
|
||||
<li key={path}>
|
||||
<Component />
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<ActionsWordCount />
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
import WordCountModal from '../../word-count-modal/components/word-count-modal'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { WordCountButton } from '@/features/word-count-modal/components/word-count-button'
|
||||
|
||||
export default function ActionsWordCount() {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
const handleShowModal = useCallback(() => {
|
||||
eventTracking.sendMB('left-menu-count')
|
||||
setShowModal(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<WordCountButton handleShowModal={handleShowModal} />
|
||||
<WordCountModal show={showModal} handleHide={() => setShowModal(false)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import DownloadPDF from './download-pdf'
|
||||
import DownloadSource from './download-source'
|
||||
|
||||
export default function DownloadMenu() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4 className="mt-0">{t('download')}</h4>
|
||||
<ul className="list-unstyled nav nav-downloads text-center">
|
||||
<li>
|
||||
<DownloadSource />
|
||||
</li>
|
||||
<li>
|
||||
<DownloadPDF />
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import { isSmallDevice } from '../../../infrastructure/event-tracking'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
|
||||
export default function DownloadPDF() {
|
||||
const { t } = useTranslation()
|
||||
const { pdfDownloadUrl, pdfUrl } = useCompileContext()
|
||||
const { projectId } = useProjectContext()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
|
||||
function sendDownloadEvent() {
|
||||
sendEvent('download-pdf-button-click', {
|
||||
projectId,
|
||||
location: 'left-menu',
|
||||
isSmallDevice,
|
||||
})
|
||||
}
|
||||
|
||||
if (pdfUrl) {
|
||||
return (
|
||||
<a
|
||||
href={pdfDownloadUrl || pdfUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={sendDownloadEvent}
|
||||
>
|
||||
<MaterialIcon type="picture_as_pdf" size="2x" />
|
||||
<br />
|
||||
PDF
|
||||
</a>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<OLTooltip
|
||||
id="disabled-pdf-download"
|
||||
description={t('please_compile_pdf_before_download')}
|
||||
overlayProps={{ placement: 'bottom' }}
|
||||
>
|
||||
<div className="link-disabled">
|
||||
<MaterialIcon type="picture_as_pdf" size="2x" />
|
||||
<br />
|
||||
PDF
|
||||
</div>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { isSmallDevice } from '../../../infrastructure/event-tracking'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
|
||||
export default function DownloadSource() {
|
||||
const { t } = useTranslation()
|
||||
const { projectId } = useProjectContext()
|
||||
|
||||
function sendDownloadEvent() {
|
||||
eventTracking.sendMB('download-zip-button-click', {
|
||||
projectId,
|
||||
location: 'left-menu',
|
||||
isSmallDevice,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`/project/${projectId}/download/zip`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={sendDownloadEvent}
|
||||
>
|
||||
<MaterialIcon type="folder_zip" size="2x" />
|
||||
<br />
|
||||
{t('source')}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import DownloadMenu from './download-menu'
|
||||
import ActionsMenu from './actions-menu'
|
||||
import HelpMenu from './help-menu'
|
||||
import SyncMenu from './sync-menu'
|
||||
import SettingsMenu from './settings-menu'
|
||||
|
||||
export default function EditorLeftMenuBody() {
|
||||
return (
|
||||
<>
|
||||
<DownloadMenu />
|
||||
<ActionsMenu />
|
||||
<SyncMenu />
|
||||
<SettingsMenu />
|
||||
<HelpMenu />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { useLayoutContext } from '../../../shared/context/layout-context'
|
||||
import LeftMenuMask from './left-menu-mask'
|
||||
import classNames from 'classnames'
|
||||
import { lazy, memo, Suspense } from 'react'
|
||||
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
|
||||
import { Offcanvas } from 'react-bootstrap'
|
||||
import { EditorLeftMenuProvider } from './editor-left-menu-context'
|
||||
import withErrorBoundary from '@/infrastructure/error-boundary'
|
||||
import OLNotification from '@/shared/components/ol/ol-notification'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const EditorLeftMenuBody = lazy(() => import('./editor-left-menu-body'))
|
||||
|
||||
const LazyEditorLeftMenuWithErrorBoundary = withErrorBoundary(
|
||||
() => (
|
||||
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
|
||||
<EditorLeftMenuBody />
|
||||
</Suspense>
|
||||
),
|
||||
() => {
|
||||
const { t } = useTranslation()
|
||||
return <OLNotification type="error" content={t('something_went_wrong')} />
|
||||
}
|
||||
)
|
||||
|
||||
function EditorLeftMenu() {
|
||||
const { leftMenuShown, setLeftMenuShown } = useLayoutContext()
|
||||
|
||||
const closeLeftMenu = () => {
|
||||
setLeftMenuShown(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<EditorLeftMenuProvider>
|
||||
<Offcanvas
|
||||
show={leftMenuShown}
|
||||
onHide={closeLeftMenu}
|
||||
backdropClassName="left-menu-modal-backdrop"
|
||||
id="left-menu-offcanvas"
|
||||
>
|
||||
<Offcanvas.Body
|
||||
className={classNames('full-size', 'left-menu', {
|
||||
shown: leftMenuShown,
|
||||
})}
|
||||
id="left-menu"
|
||||
data-testid="left-menu"
|
||||
>
|
||||
<LazyEditorLeftMenuWithErrorBoundary />
|
||||
</Offcanvas.Body>
|
||||
</Offcanvas>
|
||||
{leftMenuShown && <LeftMenuMask />}
|
||||
</EditorLeftMenuProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(EditorLeftMenu)
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCallback } from 'react'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { useContactUsModal } from '../../../shared/hooks/use-contact-us-modal'
|
||||
import LeftMenuButton from './left-menu-button'
|
||||
|
||||
export default function HelpContactUs() {
|
||||
const { modal, showModal } = useContactUsModal()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const showModalWithAnalytics = useCallback(() => {
|
||||
eventTracking.sendMB('left-menu-contact')
|
||||
showModal()
|
||||
}, [showModal])
|
||||
|
||||
return (
|
||||
<>
|
||||
<LeftMenuButton onClick={showModalWithAnalytics} icon="contact_support">
|
||||
{t('contact_us')}
|
||||
</LeftMenuButton>
|
||||
{modal}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import LeftMenuButton from './left-menu-button'
|
||||
|
||||
export default function HelpDocumentation() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<LeftMenuButton type="link" href="/learn" icon="book_4">
|
||||
{t('documentation')}
|
||||
</LeftMenuButton>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import HelpContactUs from './help-contact-us'
|
||||
import HelpDocumentation from './help-documentation'
|
||||
import HelpShowHotkeys from './help-show-hotkeys'
|
||||
|
||||
export default function HelpMenu() {
|
||||
const { t } = useTranslation()
|
||||
const showSupport = getMeta('ol-showSupport')
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4>{t('help')}</h4>
|
||||
<ul className="list-unstyled nav">
|
||||
<li>
|
||||
<HelpShowHotkeys />
|
||||
</li>
|
||||
{showSupport ? (
|
||||
<>
|
||||
<li>
|
||||
<HelpDocumentation />
|
||||
</li>
|
||||
<li>
|
||||
<HelpContactUs />
|
||||
</li>
|
||||
</>
|
||||
) : null}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
import { useProjectContext } from '../../../shared/context/project-context'
|
||||
import HotkeysModal from '../../hotkeys-modal/components/hotkeys-modal'
|
||||
import LeftMenuButton from './left-menu-button'
|
||||
import { isMac } from '@/shared/utils/os'
|
||||
|
||||
export default function HelpShowHotkeys() {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const { features } = useProjectContext()
|
||||
|
||||
const showModalWithAnalytics = useCallback(() => {
|
||||
eventTracking.sendMB('left-menu-hotkeys')
|
||||
setShowModal(true)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<LeftMenuButton onClick={showModalWithAnalytics} icon="keyboard">
|
||||
{t('show_hotkeys')}
|
||||
</LeftMenuButton>
|
||||
<HotkeysModal
|
||||
show={showModal}
|
||||
handleHide={() => setShowModal(false)}
|
||||
isMac={isMac}
|
||||
trackChangesVisible={features?.trackChangesVisible}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
|
||||
type Props = {
|
||||
onClick?: () => void
|
||||
icon?: string
|
||||
svgIcon?: React.ReactElement | null
|
||||
disabled?: boolean
|
||||
disabledAccesibilityText?: string
|
||||
type?: 'button' | 'link'
|
||||
href?: string
|
||||
translate?: React.HTMLAttributes<HTMLElement>['translate']
|
||||
}
|
||||
|
||||
function LeftMenuButtonIcon({
|
||||
svgIcon,
|
||||
icon,
|
||||
}: {
|
||||
svgIcon?: React.ReactElement | null
|
||||
icon?: string
|
||||
}) {
|
||||
if (svgIcon) {
|
||||
return <div className="material-symbols">{svgIcon}</div>
|
||||
} else if (icon) {
|
||||
return <MaterialIcon type={icon} />
|
||||
} else return null
|
||||
}
|
||||
|
||||
export default function LeftMenuButton({
|
||||
children,
|
||||
svgIcon,
|
||||
onClick,
|
||||
icon,
|
||||
disabled = false,
|
||||
disabledAccesibilityText,
|
||||
type = 'button',
|
||||
href,
|
||||
translate,
|
||||
}: PropsWithChildren<Props>) {
|
||||
if (disabled) {
|
||||
return (
|
||||
<div className="left-menu-button link-disabled">
|
||||
<LeftMenuButtonIcon svgIcon={svgIcon} icon={icon} />
|
||||
<span translate={translate}>{children}</span>
|
||||
{disabledAccesibilityText ? (
|
||||
<span className="visually-hidden">{disabledAccesibilityText}</span>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'button') {
|
||||
return (
|
||||
<button onClick={onClick} className="left-menu-button">
|
||||
<LeftMenuButtonIcon svgIcon={svgIcon} icon={icon} />
|
||||
<span translate={translate}>{children}</span>
|
||||
</button>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="left-menu-button"
|
||||
>
|
||||
<LeftMenuButtonIcon svgIcon={svgIcon} icon={icon} />
|
||||
<span translate={translate}>{children}</span>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { memo, useEffect, useRef, useState } from 'react'
|
||||
import { useLayoutContext } from '../../../shared/context/layout-context'
|
||||
import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
|
||||
export default memo(function LeftMenuMask() {
|
||||
const { setLeftMenuShown } = useLayoutContext()
|
||||
const { userSettings } = useUserSettingsContext()
|
||||
const { editorTheme, editorLightTheme, editorDarkTheme, overallTheme } =
|
||||
userSettings
|
||||
const [original] = useState({
|
||||
editorTheme,
|
||||
overallTheme,
|
||||
editorLightTheme,
|
||||
editorDarkTheme,
|
||||
})
|
||||
const maskRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (maskRef.current) {
|
||||
if (
|
||||
editorTheme !== original.editorTheme ||
|
||||
editorLightTheme !== original.editorLightTheme ||
|
||||
editorDarkTheme !== original.editorDarkTheme ||
|
||||
overallTheme !== original.overallTheme
|
||||
) {
|
||||
maskRef.current.style.opacity = '0'
|
||||
}
|
||||
}
|
||||
}, [editorTheme, editorLightTheme, editorDarkTheme, overallTheme, original])
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
<div
|
||||
id="left-menu-mask"
|
||||
ref={maskRef}
|
||||
onClick={() => setLeftMenuShown(false)}
|
||||
/>
|
||||
)
|
||||
})
|
||||
@@ -1,64 +0,0 @@
|
||||
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'
|
||||
import SettingsMathPreview from './settings/settings-math-preview'
|
||||
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
||||
import { ElementType } from 'react'
|
||||
import OLForm from '@/shared/components/ol/ol-form'
|
||||
import SettingsNewEditor from './settings/settings-new-editor'
|
||||
|
||||
const moduleSettings: Array<{
|
||||
import: { default: ElementType }
|
||||
path: string
|
||||
}> = importOverleafModules('settingsEntries')
|
||||
|
||||
export default function SettingsMenu() {
|
||||
const { t } = useTranslation()
|
||||
const anonymous = getMeta('ol-anonymous')
|
||||
|
||||
if (anonymous) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4>{t('settings')}</h4>
|
||||
<OLForm id="left-menu-setting" className="settings">
|
||||
<SettingsCompiler />
|
||||
<SettingsImageName />
|
||||
<SettingsDocument />
|
||||
<SettingsSpellCheckLanguage />
|
||||
<SettingsDictionary />
|
||||
{moduleSettings.map(({ import: { default: Component }, path }) => (
|
||||
<Component key={path} />
|
||||
))}
|
||||
<SettingsAutoComplete />
|
||||
<SettingsAutoCloseBrackets />
|
||||
<SettingsSyntaxValidation />
|
||||
<SettingsMathPreview />
|
||||
<SettingsOverallTheme />
|
||||
<SettingsEditorTheme />
|
||||
<SettingsKeybindings />
|
||||
<SettingsFontSize />
|
||||
<SettingsFontFamily />
|
||||
<SettingsLineHeight />
|
||||
<SettingsPdfViewer />
|
||||
<SettingsNewEditor />
|
||||
</OLForm>
|
||||
</>
|
||||
)
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
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'
|
||||
import { useSetCompilationSettingWithEvent } from '../../hooks/use-set-compilation-setting'
|
||||
|
||||
export default function SettingsCompiler() {
|
||||
const { t } = useTranslation()
|
||||
const { write } = usePermissionsContext()
|
||||
const { compiler, setCompiler } = useProjectSettingsContext()
|
||||
const changeCompiler = useSetCompilationSettingWithEvent(
|
||||
'compiler',
|
||||
setCompiler
|
||||
)
|
||||
|
||||
return (
|
||||
<SettingsMenuSelect<ProjectCompiler>
|
||||
onChange={changeCompiler}
|
||||
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"
|
||||
translateOptions="no"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import DictionaryModal from '../../../dictionary/components/dictionary-modal'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import OLFormGroup from '@/shared/components/ol/ol-form-group'
|
||||
import OLFormLabel from '@/shared/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>
|
||||
)
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
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'
|
||||
import { useSetCompilationSettingWithEvent } from '../../hooks/use-set-compilation-setting'
|
||||
|
||||
export default function SettingsDocument() {
|
||||
const { t } = useTranslation()
|
||||
const { write } = usePermissionsContext()
|
||||
const { docs } = useFileTreeData()
|
||||
const { rootDocId, setRootDocId } = useProjectSettingsContext()
|
||||
const changeRootDoc = useSetCompilationSettingWithEvent(
|
||||
'root-doc-id',
|
||||
setRootDocId,
|
||||
{ omitValueInEvent: true }
|
||||
)
|
||||
|
||||
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={changeRootDoc}
|
||||
value={rootDocId ?? ''}
|
||||
disabled={!write}
|
||||
options={validDocsOptions}
|
||||
label={t('main_document')}
|
||||
name="rootDocId"
|
||||
translateOptions="no"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useProjectSettingsContext } from '../../context/project-settings-context'
|
||||
import SettingsMenuSelect from './settings-menu-select'
|
||||
import { useEditorThemesOptionGroups } from '../../hooks/use-editor-theme-option-groups'
|
||||
import { isIEEEBranded } from '@/utils/is-ieee-branded'
|
||||
|
||||
export default function SettingsEditorTheme() {
|
||||
const { t } = useTranslation()
|
||||
const optGroups = useEditorThemesOptionGroups()
|
||||
const {
|
||||
editorTheme,
|
||||
setEditorTheme,
|
||||
editorLightTheme,
|
||||
editorDarkTheme,
|
||||
setEditorLightTheme,
|
||||
setEditorDarkTheme,
|
||||
overallTheme,
|
||||
} = useProjectSettingsContext()
|
||||
|
||||
if (overallTheme === 'system' && !isIEEEBranded()) {
|
||||
return (
|
||||
<>
|
||||
<SettingsMenuSelect
|
||||
onChange={setEditorLightTheme}
|
||||
value={editorLightTheme}
|
||||
optgroups={optGroups}
|
||||
label={t('editor_theme_light')}
|
||||
name="editorLightTheme"
|
||||
translateOptions="no"
|
||||
/>
|
||||
<SettingsMenuSelect
|
||||
onChange={setEditorDarkTheme}
|
||||
value={editorDarkTheme}
|
||||
optgroups={optGroups}
|
||||
label={t('editor_theme_dark')}
|
||||
name="editorDarkTheme"
|
||||
translateOptions="no"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsMenuSelect
|
||||
onChange={setEditorTheme}
|
||||
value={editorTheme}
|
||||
optgroups={optGroups}
|
||||
label={t('editor_theme')}
|
||||
name="editorTheme"
|
||||
translateOptions="no"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useProjectSettingsContext } from '../../context/project-settings-context'
|
||||
import SettingsMenuSelect from './settings-menu-select'
|
||||
import { FontFamily } from '@/shared/utils/styles'
|
||||
|
||||
export default function SettingsFontFamily() {
|
||||
const { t } = useTranslation()
|
||||
const { fontFamily, setFontFamily } = useProjectSettingsContext()
|
||||
|
||||
return (
|
||||
<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"
|
||||
translateOptions="no"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
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'
|
||||
import { useSetCompilationSettingWithEvent } from '../../hooks/use-set-compilation-setting'
|
||||
|
||||
export default function SettingsImageName() {
|
||||
const { t } = useTranslation()
|
||||
const { imageName, setImageName } = useProjectSettingsContext()
|
||||
const { write } = usePermissionsContext()
|
||||
const changeImageName = useSetCompilationSettingWithEvent(
|
||||
'image-name',
|
||||
setImageName
|
||||
)
|
||||
|
||||
const allowedImageNames = useMemo(() => getMeta('ol-imageNames') || [], [])
|
||||
|
||||
const options: Array<Option> = useMemo(
|
||||
() =>
|
||||
allowedImageNames
|
||||
// filter out images that aren't allowed, unless thats the current image the project is on
|
||||
.filter(image => image.allowed || image.imageName === imageName)
|
||||
.map(({ imageName, imageDesc }) => ({
|
||||
value: imageName,
|
||||
label: imageDesc,
|
||||
})),
|
||||
[allowedImageNames, imageName]
|
||||
)
|
||||
|
||||
if (allowedImageNames.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsMenuSelect
|
||||
onChange={changeImageName}
|
||||
value={imageName}
|
||||
disabled={!write}
|
||||
options={options}
|
||||
label={t('tex_live_version')}
|
||||
name="imageName"
|
||||
translateOptions="no"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
import OLFormGroup from '@/shared/components/ol/ol-form-group'
|
||||
import OLFormLabel from '@/shared/components/ol/ol-form-label'
|
||||
import OLFormSelect from '@/shared/components/ol/ol-form-select'
|
||||
import { ChangeEventHandler, useCallback, useEffect, useRef } from 'react'
|
||||
import { useEditorLeftMenuContext } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
|
||||
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>>
|
||||
optgroups?: Array<Optgroup<T>>
|
||||
loading?: boolean
|
||||
onChange: (val: T) => void
|
||||
value?: T
|
||||
disabled?: boolean
|
||||
translateOptions?: 'yes' | 'no'
|
||||
}
|
||||
|
||||
export default function SettingsMenuSelect<T extends PossibleValue = string>({
|
||||
label,
|
||||
name,
|
||||
options,
|
||||
optgroups,
|
||||
loading,
|
||||
onChange,
|
||||
value,
|
||||
disabled = false,
|
||||
translateOptions,
|
||||
}: 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 ? (
|
||||
<OLSpinner size="sm" />
|
||||
) : (
|
||||
<OLFormSelect
|
||||
size="sm"
|
||||
onChange={handleChange}
|
||||
value={value?.toString()}
|
||||
disabled={disabled}
|
||||
ref={selectRef}
|
||||
translate={translateOptions}
|
||||
>
|
||||
{options?.map(option => (
|
||||
<option
|
||||
key={`${name}-${option.value}`}
|
||||
value={option.value.toString()}
|
||||
aria-hidden={option.ariaHidden}
|
||||
disabled={option.disabled}
|
||||
>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
{optgroups?.map(optgroup => (
|
||||
<optgroup label={optgroup.label} key={optgroup.label}>
|
||||
{optgroup.options.map(option => (
|
||||
<option
|
||||
value={option.value.toString()}
|
||||
key={option.value.toString()}
|
||||
>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</OLFormSelect>
|
||||
)}
|
||||
</OLFormGroup>
|
||||
)
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SettingsMenuSelect from './settings-menu-select'
|
||||
import { useSwitchEnableNewEditorState } from '@/features/ide-redesign/hooks/use-switch-enable-new-editor-state'
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
canUseNewEditor,
|
||||
useIsNewEditorEnabled,
|
||||
} from '@/features/ide-redesign/utils/new-editor-utils'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
|
||||
export default function SettingsNewEditor() {
|
||||
const { t } = useTranslation()
|
||||
const { setEditorRedesignStatus } = useSwitchEnableNewEditorState()
|
||||
const { setLeftMenuShown } = useLayoutContext()
|
||||
const enabled = useIsNewEditorEnabled()
|
||||
const show = canUseNewEditor()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
|
||||
const onChange = useCallback(
|
||||
(newValue: boolean) => {
|
||||
sendEvent('switch-to-new-editor', {
|
||||
location: 'left-menu',
|
||||
})
|
||||
setEditorRedesignStatus(newValue).then(() => setLeftMenuShown(false))
|
||||
},
|
||||
[setEditorRedesignStatus, setLeftMenuShown, sendEvent]
|
||||
)
|
||||
|
||||
if (!show) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsMenuSelect
|
||||
onChange={onChange}
|
||||
value={enabled}
|
||||
options={[
|
||||
{
|
||||
value: true,
|
||||
label: t('on'),
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: t('off'),
|
||||
},
|
||||
]}
|
||||
label={t('new_editor_look')}
|
||||
name="new-editor-setting"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
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"
|
||||
translateOptions="no"
|
||||
/>
|
||||
)
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
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 { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
||||
import { supportsWebAssembly } from '@/utils/wasm'
|
||||
|
||||
export default function SettingsSpellCheckLanguage() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { spellCheckLanguage, setSpellCheckLanguage } =
|
||||
useProjectSettingsContext()
|
||||
const { permissionsLevel } = useIdeReactContext()
|
||||
|
||||
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') }]}
|
||||
optgroups={[optgroup]}
|
||||
label={t('spell_check')}
|
||||
name="spellCheckLanguage"
|
||||
disabled={permissionsLevel === 'readOnly' || !supportsWebAssembly()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
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"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { ElementType } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
||||
import getMeta from '../../../utils/meta'
|
||||
|
||||
const components = importOverleafModules('editorLeftMenuSync') as {
|
||||
import: { default: ElementType }
|
||||
path: string
|
||||
}[]
|
||||
|
||||
export default function SyncMenu() {
|
||||
const { t } = useTranslation()
|
||||
const anonymous = getMeta('ol-anonymous')
|
||||
const gitBridgeEnabled = getMeta('ol-gitBridgeEnabled')
|
||||
|
||||
if (anonymous) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (components.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// This flag can only be false in CE and Server Pro. In this case we skip rendering the
|
||||
// entire sync section, since Dropbox and GitHub are never available in SP
|
||||
if (!gitBridgeEnabled) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4>{t('sync')}</h4>
|
||||
<ul className="list-unstyled nav">
|
||||
{components.map(({ import: { default: Component }, path }) => (
|
||||
<li key={path}>
|
||||
<Component />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
+9
-1
@@ -1,6 +1,5 @@
|
||||
import getMeta from '@/utils/meta'
|
||||
import { useMemo } from 'react'
|
||||
import { Option } from '../components/settings/settings-menu-select'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const overrides = new Map([['overleaf', 'overleaf light']])
|
||||
@@ -8,6 +7,15 @@ function getThemeName(theme: string): string {
|
||||
return (overrides.get(theme) ?? theme).replace(/_/g, ' ')
|
||||
}
|
||||
|
||||
type PossibleValue = string | number | boolean
|
||||
|
||||
type Option<T extends PossibleValue = string> = {
|
||||
value: T
|
||||
label: string
|
||||
ariaHidden?: 'true' | 'false'
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function useEditorThemesOptionGroups() {
|
||||
const editorThemes = getMeta('ol-editorThemes')
|
||||
const legacyEditorThemes = getMeta('ol-legacyEditorThemes')
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||
import LeftMenuButton from '@/features/editor-left-menu/components/left-menu-button'
|
||||
import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
||||
import { memo } from 'react'
|
||||
|
||||
export const WordCountButton = memo<{
|
||||
handleShowModal: () => void
|
||||
}>(function WordCountButton({ handleShowModal }) {
|
||||
const { pdfUrl } = useCompileContext()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const enabled = pdfUrl || isSplitTestEnabled('word-count-client')
|
||||
|
||||
if (!enabled) {
|
||||
return (
|
||||
<OLTooltip
|
||||
id="disabled-word-count"
|
||||
description={t('please_compile_pdf_before_word_count')}
|
||||
overlayProps={{
|
||||
placement: 'top',
|
||||
}}
|
||||
>
|
||||
{/* OverlayTrigger won't fire unless the child is a non-react html element (e.g div, span) */}
|
||||
<div>
|
||||
<LeftMenuButton
|
||||
icon="match_case"
|
||||
disabled
|
||||
disabledAccesibilityText={t('please_compile_pdf_before_word_count')}
|
||||
>
|
||||
{t('word_count')}
|
||||
</LeftMenuButton>
|
||||
</div>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<LeftMenuButton onClick={handleShowModal} icon="match_case">
|
||||
{t('word_count')}
|
||||
</LeftMenuButton>
|
||||
)
|
||||
})
|
||||
@@ -1,57 +0,0 @@
|
||||
import ActionsMenu from '../../js/features/editor-left-menu/components/actions-menu'
|
||||
import { ScopeDecorator } from '../decorators/scope'
|
||||
import { mockCompile, mockCompileError } from '../fixtures/compile'
|
||||
import { document, mockDocument } from '../fixtures/document'
|
||||
import useFetchMock from '../hooks/use-fetch-mock'
|
||||
import { useScope } from '../hooks/use-scope'
|
||||
|
||||
export default {
|
||||
title: 'Editor / Left Menu / Actions Menu',
|
||||
component: ActionsMenu,
|
||||
decorators: [
|
||||
(Story: any) => ScopeDecorator(Story, { mockCompileOnLoad: false }),
|
||||
],
|
||||
}
|
||||
|
||||
export const NotCompiled = () => {
|
||||
window.metaAttributesCache.set('ol-anonymous', false)
|
||||
|
||||
useFetchMock(fetchMock => {
|
||||
mockCompileError(fetchMock, 'failure')
|
||||
})
|
||||
|
||||
return (
|
||||
<div id="left-menu" className="shown">
|
||||
<ActionsMenu />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CompileSuccess = () => {
|
||||
window.metaAttributesCache.set('ol-anonymous', false)
|
||||
|
||||
useScope({
|
||||
editor: {
|
||||
sharejs_doc: mockDocument(document.tex),
|
||||
},
|
||||
})
|
||||
|
||||
useFetchMock(fetchMock => {
|
||||
mockCompile(fetchMock)
|
||||
fetchMock.get('express:/project/:projectId/wordcount', {
|
||||
texcount: {
|
||||
encode: 'ascii',
|
||||
textWords: 10,
|
||||
headers: 11,
|
||||
mathInline: 12,
|
||||
mathDisplay: 13,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div id="left-menu" className="shown">
|
||||
<ActionsMenu />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import DownloadMenu from '../../js/features/editor-left-menu/components/download-menu'
|
||||
import { ScopeDecorator } from '../decorators/scope'
|
||||
import { mockCompile, mockCompileError } from '../fixtures/compile'
|
||||
import { document, mockDocument } from '../fixtures/document'
|
||||
import useFetchMock from '../hooks/use-fetch-mock'
|
||||
import { useScope } from '../hooks/use-scope'
|
||||
|
||||
export default {
|
||||
title: 'Editor / Left Menu / Download Menu',
|
||||
component: DownloadMenu,
|
||||
decorators: [
|
||||
(Story: any) => ScopeDecorator(Story, { mockCompileOnLoad: false }),
|
||||
],
|
||||
}
|
||||
|
||||
export const NotCompiled = () => {
|
||||
useFetchMock(fetchMock => {
|
||||
mockCompileError(fetchMock, 'failure')
|
||||
})
|
||||
|
||||
return (
|
||||
<div id="left-menu" className="shown">
|
||||
<DownloadMenu />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CompileSuccess = () => {
|
||||
useScope({
|
||||
editor: {
|
||||
sharejs_doc: mockDocument(document.tex),
|
||||
},
|
||||
})
|
||||
|
||||
useFetchMock(fetchMock => {
|
||||
mockCompile(fetchMock)
|
||||
})
|
||||
|
||||
return (
|
||||
<div id="left-menu" className="shown">
|
||||
<DownloadMenu />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import HelpMenu from '../../js/features/editor-left-menu/components/help-menu'
|
||||
import { ScopeDecorator } from '../decorators/scope'
|
||||
|
||||
export default {
|
||||
title: 'Editor / Left Menu / Help Menu',
|
||||
component: HelpMenu,
|
||||
decorators: [ScopeDecorator],
|
||||
}
|
||||
|
||||
export const ShowSupport = () => {
|
||||
window.metaAttributesCache.set('ol-showSupport', true)
|
||||
window.metaAttributesCache.set('ol-user', {
|
||||
email: 'sherlock@holmes.co.uk',
|
||||
first_name: 'Sherlock',
|
||||
last_name: 'Holmes',
|
||||
})
|
||||
|
||||
return (
|
||||
<div id="left-menu" className="shown">
|
||||
<HelpMenu />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const HideSupport = () => {
|
||||
window.metaAttributesCache.set('ol-showSupport', false)
|
||||
|
||||
return (
|
||||
<div id="left-menu" className="shown">
|
||||
<HelpMenu />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import SyncMenu from '../../js/features/editor-left-menu/components/sync-menu'
|
||||
import { ScopeDecorator } from '../decorators/scope'
|
||||
import { useScope } from '../hooks/use-scope'
|
||||
|
||||
export default {
|
||||
title: 'Editor / Left Menu / Sync Menu',
|
||||
component: SyncMenu,
|
||||
decorators: [ScopeDecorator],
|
||||
}
|
||||
|
||||
export const WriteAccess = () => {
|
||||
window.metaAttributesCache.set('ol-anonymous', false)
|
||||
window.metaAttributesCache.set('ol-gitBridgeEnabled', true)
|
||||
useScope({
|
||||
permissionsLevel: 'owner',
|
||||
})
|
||||
|
||||
return (
|
||||
<div id="left-menu" className="shown">
|
||||
<SyncMenu />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ReadOnlyAccess = () => {
|
||||
window.metaAttributesCache.set('ol-anonymous', false)
|
||||
window.metaAttributesCache.set('ol-gitBridgeEnabled', true)
|
||||
useScope({
|
||||
permissionsLevel: 'readOnly',
|
||||
})
|
||||
|
||||
return (
|
||||
<div id="left-menu" className="shown">
|
||||
<SyncMenu />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -6,175 +6,6 @@
|
||||
--left-menu-form-select-border: var(--border-disabled);
|
||||
}
|
||||
|
||||
.left-menu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--bg-light-secondary);
|
||||
z-index: 100;
|
||||
overflow: hidden auto;
|
||||
transition: left ease-in-out 0.5s;
|
||||
font-size: var(--font-size-02);
|
||||
width: 340px;
|
||||
|
||||
&.shown {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-family: $font-family-sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: var(--font-size-03);
|
||||
margin: var(--spacing-05) 0;
|
||||
padding-bottom: var(--spacing-03);
|
||||
color: var(--content-secondary);
|
||||
border-bottom: 1px solid var(--border-primary-dark);
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul.nav {
|
||||
.left-menu-button {
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-03);
|
||||
font-weight: 700;
|
||||
color: var(--link-ui);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
|
||||
.material-symbols {
|
||||
margin-right: var(--spacing-04);
|
||||
color: var(--neutral-70);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--bg-info-01);
|
||||
color: var(--white);
|
||||
|
||||
.material-symbols {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background-color: var(--bg-info-01);
|
||||
color: var(--white);
|
||||
|
||||
.material-symbols {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
.material-symbols {
|
||||
color: var(--neutral-70);
|
||||
}
|
||||
|
||||
padding: var(--spacing-03);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.link-disabled {
|
||||
color: var(--content-disabled);
|
||||
}
|
||||
}
|
||||
|
||||
> ul.nav:last-child {
|
||||
margin-bottom: var(--spacing-05);
|
||||
}
|
||||
|
||||
ul.nav-downloads {
|
||||
li {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
|
||||
a {
|
||||
color: var(--content-secondary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.material-symbols {
|
||||
margin: var(--spacing-03) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form.settings {
|
||||
label {
|
||||
font-weight: normal;
|
||||
color: var(--content-secondary);
|
||||
flex: 1 0 50%;
|
||||
margin-bottom: 0;
|
||||
padding-right: var(--spacing-03);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
width: 50%;
|
||||
margin: var(--spacing-04) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.left-menu-setting {
|
||||
padding: 0 var(--spacing-02);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid rgb(0 0 0 / 7%);
|
||||
margin-bottom: 0;
|
||||
height: 43px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: calc(var(--spacing-04) * -1);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
background-color: var(--bg-info-01);
|
||||
|
||||
label {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
.form-select {
|
||||
border: 1px solid var(--left-menu-form-select-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#left-menu-mask {
|
||||
opacity: 0.4;
|
||||
background-color: #999;
|
||||
z-index: 99;
|
||||
transition: opacity 0.5s;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.left-menu-modal-backdrop {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.loading-spinner-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -830,8 +830,6 @@
|
||||
"folder_name": "Folder name",
|
||||
"folders": "Folders",
|
||||
"following_paths_conflict": "The following files and folders conflict with the same path",
|
||||
"font_family": "Font Family",
|
||||
"font_size": "Font Size",
|
||||
"footer_about_us": "About us",
|
||||
"footer_contact_us": "Contact us",
|
||||
"footnotes": "Footnotes",
|
||||
@@ -1298,7 +1296,6 @@
|
||||
"limited_document_history": "Limited document history",
|
||||
"limited_to_n_collaborators_per_project": "Limited to __count__ collaborator per project",
|
||||
"limited_to_n_collaborators_per_project_plural": "Limited to __count__ collaborators per project",
|
||||
"line_height": "Line Height",
|
||||
"line_width_is_the_width_of_the_line_in_the_current_environment": "Line width is the width of the line in the current environment. e.g. a full page width in single-column layout or half a page width in a two-column layout.",
|
||||
"link": "Link",
|
||||
"link_account": "Link Account",
|
||||
@@ -1487,7 +1484,6 @@
|
||||
"new_compile_domain_notice": "Something might be blocking your browser from accessing Overleaf’s PDF download location, <0>__compilesUserContentDomain__</0>. This could be caused by network blocking or a strict browser plugin rule. Please follow our <1>troubleshooting guide</1>.",
|
||||
"new_compiles_in_this_project_will_automatically_use_the_newest_version": "New compiles in this project will automatically use the newest version. <0>Learn how to change compiler settings</0>",
|
||||
"new_create_tables_and_equations": "NEW! <sparkle/> Create tables and equations in seconds",
|
||||
"new_editor_look": "New editor look",
|
||||
"new_error_logs_make_it_easier_to_find_whats_wrong": "New error logs make it easier to find what’s wrong and fix your document, so you can get compiling again.",
|
||||
"new_file": "New file",
|
||||
"new_folder": "New folder",
|
||||
@@ -1727,7 +1723,6 @@
|
||||
"please_ask_the_project_owner_to_upgrade_to_track_changes": "Please ask the project owner to upgrade to use track changes",
|
||||
"please_change_primary_to_remove": "Please change your primary email in order to remove",
|
||||
"please_compile_pdf_before_download": "Please compile your project before downloading the PDF",
|
||||
"please_compile_pdf_before_word_count": "Please compile your project before performing a word count",
|
||||
"please_confirm_email": "Please confirm your email __emailAddress__ by clicking on the link in the confirmation email ",
|
||||
"please_confirm_primary_email_or_edit": "Please confirm your primary email address __emailAddress__. To edit it, go to <0>Account settings</0>.",
|
||||
"please_confirm_secondary_email_or_edit": "Please confirm your secondary email address __emailAddress__. To edit it, go to <0>Account settings</0>.",
|
||||
@@ -2152,7 +2147,6 @@
|
||||
"show_document_preamble": "Show document preamble",
|
||||
"show_equation_preview": "Show equation preview",
|
||||
"show_file_tree": "Show file tree",
|
||||
"show_hotkeys": "Show Hotkeys",
|
||||
"show_in_code": "Show in code",
|
||||
"show_in_pdf": "Show in PDF",
|
||||
"show_less": "show less",
|
||||
@@ -2212,7 +2206,6 @@
|
||||
"source": "Source",
|
||||
"speak": "Speak",
|
||||
"speech_input_not_available": "Speech input is not yet available in this browser",
|
||||
"spell_check": "Spell check",
|
||||
"spellcheck": "Spellcheck",
|
||||
"spellcheck_language": "Spellcheck language",
|
||||
"split_view": "Split view",
|
||||
|
||||
@@ -1,900 +0,0 @@
|
||||
import EditorLeftMenu from '../../../../frontend/js/features/editor-left-menu/components/editor-left-menu'
|
||||
import {
|
||||
ImageName,
|
||||
OverallThemeMeta,
|
||||
SpellCheckLanguage,
|
||||
} from '../../../../types/project-settings'
|
||||
import {
|
||||
EditorProviders,
|
||||
makeProjectProvider,
|
||||
} from '../../helpers/editor-providers'
|
||||
import { mockScope } from './scope'
|
||||
import { Folder } from '../../../../types/folder'
|
||||
import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { mockProject } from '../../features/source-editor/helpers/mock-project'
|
||||
import { UserId } from '../../../../types/user'
|
||||
|
||||
describe('<EditorLeftMenu />', function () {
|
||||
beforeEach(function () {
|
||||
cy.viewport(800, 800)
|
||||
cy.interceptCompile()
|
||||
})
|
||||
|
||||
describe('for non-anonymous users', function () {
|
||||
const overallThemes: OverallThemeMeta[] = [
|
||||
{
|
||||
name: 'Overall Theme 1',
|
||||
val: '',
|
||||
path: 'https://overleaf.com/overalltheme-1.css',
|
||||
},
|
||||
{
|
||||
name: 'Overall Theme 2',
|
||||
val: 'light-',
|
||||
path: 'https://overleaf.com/overalltheme-2.css',
|
||||
},
|
||||
]
|
||||
|
||||
const ImageNames: ImageName[] = [
|
||||
{
|
||||
imageDesc: 'Image 1',
|
||||
imageName: 'img-1',
|
||||
allowed: true,
|
||||
},
|
||||
{
|
||||
imageDesc: 'Image 2',
|
||||
imageName: 'img-2',
|
||||
allowed: true,
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-overallThemes', overallThemes)
|
||||
window.metaAttributesCache.set('ol-imageNames', ImageNames)
|
||||
window.metaAttributesCache.set('ol-anonymous', false)
|
||||
window.metaAttributesCache.set('ol-gitBridgeEnabled', true)
|
||||
window.metaAttributesCache.set('ol-showSupport', true)
|
||||
Object.assign(getMeta('ol-ExposedSettings'), { ieeeBrandId: 123 })
|
||||
window.metaAttributesCache.set('ol-user', {
|
||||
email: 'sherlock@holmes.co.uk',
|
||||
first_name: 'Sherlock',
|
||||
last_name: 'Holmes',
|
||||
})
|
||||
})
|
||||
|
||||
it('render full menu', function () {
|
||||
const scope = mockScope()
|
||||
const project = mockProject()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
providers={{ ProjectProvider: makeProjectProvider(project) }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
// Download Menu
|
||||
cy.findByRole('heading', { name: 'Download' })
|
||||
cy.findByRole('link', { name: 'Source' })
|
||||
cy.findByRole('link', { name: 'PDF' })
|
||||
|
||||
// Actions Menu
|
||||
cy.findByRole('heading', { name: 'Actions' })
|
||||
cy.findByRole('button', { name: 'Copy project' })
|
||||
cy.findByRole('button', { name: 'Word Count' })
|
||||
|
||||
// Sync Menu
|
||||
cy.findByRole('heading', { name: 'Sync' })
|
||||
cy.findByRole('button', { name: 'Dropbox' })
|
||||
cy.findByRole('button', { name: 'Git' })
|
||||
cy.findByRole('button', { name: 'GitHub' })
|
||||
|
||||
// Settings Menu
|
||||
cy.findByRole('heading', { name: 'Settings' })
|
||||
cy.findByLabelText('Compiler')
|
||||
cy.findByLabelText('TeX Live version')
|
||||
cy.findByLabelText('Main document')
|
||||
cy.findByLabelText('Spell check')
|
||||
cy.findByLabelText('Auto-complete')
|
||||
cy.findByLabelText('Auto-close brackets')
|
||||
cy.findByLabelText('Code check')
|
||||
cy.findByLabelText('Editor theme')
|
||||
cy.findByLabelText('Overall theme')
|
||||
cy.findByLabelText('Keybindings')
|
||||
cy.findByLabelText('Font Size')
|
||||
cy.findByLabelText('Font Family')
|
||||
cy.findByLabelText('Line Height')
|
||||
cy.findByLabelText('PDF Viewer')
|
||||
|
||||
// Help Menu
|
||||
cy.findByRole('heading', { name: 'Help' })
|
||||
cy.findByRole('button', { name: 'Show Hotkeys' })
|
||||
cy.findByRole('link', { name: 'Documentation' })
|
||||
cy.findByRole('button', { name: 'Contact us' })
|
||||
})
|
||||
|
||||
describe('download menu', function () {
|
||||
it('have a correct source & pdf download url', function () {
|
||||
const scope = mockScope()
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('link', { name: 'Source' }).should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'/project/project123/download/zip'
|
||||
)
|
||||
|
||||
cy.findByRole('link', { name: 'PDF' })
|
||||
.should('have.attr', 'href')
|
||||
.and('match', /\/download\/project\/project123\/build/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('actions menu', function () {
|
||||
it('shows copy project modal correctly', function () {
|
||||
cy.intercept('POST', '/project/*/clone', {
|
||||
body: {
|
||||
project_id: 'new_project_id',
|
||||
},
|
||||
})
|
||||
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Copy project' }).click()
|
||||
cy.findByRole('heading', { name: 'Copy project' })
|
||||
|
||||
// try closing & re-opening the modal with different methods
|
||||
cy.findByRole('button', { name: 'Close dialog' }).click()
|
||||
cy.findByRole('button', { name: 'Copy project' }).click()
|
||||
cy.findByRole('button', { name: 'Cancel' }).click()
|
||||
cy.findByRole('button', { name: 'Copy project' }).click()
|
||||
|
||||
cy.findByLabelText(/New name/i).focus()
|
||||
cy.findByLabelText(/New name/i).clear()
|
||||
cy.findByLabelText(/New name/i).type('Project Renamed')
|
||||
cy.get('#clone-project-form-name[value="Project Renamed"')
|
||||
})
|
||||
|
||||
it('shows word count modal correctly', function () {
|
||||
cy.intercept('GET', '/project/*/wordcount*', {
|
||||
texcount: {
|
||||
encode: 'ascii',
|
||||
textWords: 781,
|
||||
headWords: 66,
|
||||
outside: 11,
|
||||
headers: 41,
|
||||
elements: 2,
|
||||
mathInline: 6,
|
||||
mathDisplay: 1,
|
||||
errors: 0,
|
||||
},
|
||||
}).as('wordCount')
|
||||
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Word Count' }).click()
|
||||
|
||||
cy.wait('@wordCount')
|
||||
cy.findByText('Total Words:')
|
||||
cy.findByText('781')
|
||||
cy.findByText('Headers:')
|
||||
cy.findByText('41')
|
||||
cy.findByText('Math Inline:')
|
||||
cy.findByText('6')
|
||||
cy.findByText('Math Display:')
|
||||
cy.findByText('1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sync menu', function () {
|
||||
it('shows dropbox modal correctly', function () {
|
||||
cy.intercept('GET', '/dropbox/status', {
|
||||
registered: true,
|
||||
})
|
||||
|
||||
const scope = mockScope({
|
||||
user: {
|
||||
features: {
|
||||
dropbox: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
projectOwner={{
|
||||
_id: '123' as UserId,
|
||||
email: 'owner@example.com',
|
||||
first_name: 'Test',
|
||||
last_name: 'Owner',
|
||||
privileges: 'owner',
|
||||
signUpDate: new Date('2025-07-07').toISOString(),
|
||||
}}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Dropbox' }).click()
|
||||
cy.findByText('Dropbox Sync')
|
||||
})
|
||||
|
||||
it('shows git modal correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
projectOwner={{
|
||||
_id: '123' as UserId,
|
||||
email: 'owner@example.com',
|
||||
first_name: 'Test',
|
||||
last_name: 'Owner',
|
||||
privileges: 'owner',
|
||||
signUpDate: new Date('2025-07-07').toISOString(),
|
||||
}}
|
||||
projectFeatures={
|
||||
{
|
||||
gitBridge: true,
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Git' }).click()
|
||||
cy.findByText('Clone with Git')
|
||||
cy.findByText(/clone your project by using the link below/)
|
||||
})
|
||||
|
||||
it('shows git modal paywall correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
projectOwner={{
|
||||
_id: '123' as UserId,
|
||||
email: 'owner@example.com',
|
||||
first_name: 'Test',
|
||||
last_name: 'Owner',
|
||||
privileges: 'owner',
|
||||
signUpDate: new Date('2025-07-07').toISOString(),
|
||||
}}
|
||||
projectFeatures={
|
||||
{
|
||||
gitBridge: false,
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Git' }).click()
|
||||
cy.findByText('Collaborate online and offline, using your own workflow')
|
||||
})
|
||||
|
||||
it('shows github modal correctly', function () {
|
||||
cy.intercept('GET', '/user/github-sync/status', {
|
||||
available: false,
|
||||
enabled: false,
|
||||
}).as('user-status')
|
||||
|
||||
cy.intercept('GET', '/project/*/github-sync/status', {
|
||||
enabled: false,
|
||||
}).as('project-status')
|
||||
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.wait('@compile')
|
||||
cy.findByRole('button', { name: 'GitHub' }).click()
|
||||
cy.findByText('GitHub Sync')
|
||||
|
||||
cy.wait(['@user-status', '@project-status'])
|
||||
cy.findByText('Push to GitHub, pull to Overleaf')
|
||||
})
|
||||
|
||||
it('hides the entire sync section when git bridge is disabled', function () {
|
||||
window.metaAttributesCache.set('ol-gitBridgeEnabled', false)
|
||||
|
||||
cy.findByRole('button', { name: 'Dropbox' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'Git' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'GitHub' }).should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
describe('settings menu', function () {
|
||||
it('shows compiler menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-compiler option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq([
|
||||
'pdflatex',
|
||||
'latex',
|
||||
'xelatex',
|
||||
'lualatex',
|
||||
])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq([
|
||||
'pdfLaTeX',
|
||||
'LaTeX',
|
||||
'XeLaTeX',
|
||||
'LuaLaTeX',
|
||||
])
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows texlive version menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-imageName option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['img-1', 'img-2'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(['Image 1', 'Image 2'])
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows document menu correctly', function () {
|
||||
const rootFolder: Folder = {
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{
|
||||
_id: 'id1',
|
||||
name: 'main.tex',
|
||||
},
|
||||
{
|
||||
_id: 'id2',
|
||||
name: 'main2.tex',
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
}
|
||||
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
scope={scope}
|
||||
rootFolder={[rootFolder as any]}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const docs = docsInFolder(rootFolder)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-rootDocId option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(docs.map(doc => doc.doc.id))
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(docs.map(doc => doc.path))
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows spellcheck menu correctly', function () {
|
||||
const languages: SpellCheckLanguage[] = [
|
||||
{
|
||||
name: 'Lang 1',
|
||||
code: 'lang-1',
|
||||
dic: 'lang_1',
|
||||
},
|
||||
{
|
||||
name: 'Lang 2',
|
||||
code: 'lang-2',
|
||||
dic: 'lang_2',
|
||||
},
|
||||
]
|
||||
|
||||
window.metaAttributesCache.set('ol-languages', languages)
|
||||
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>(
|
||||
'#settings-menu-spellCheckLanguage option'
|
||||
).then(options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['', 'lang-1', 'lang-2'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(['Off', 'Lang 1', 'Lang 2'])
|
||||
})
|
||||
})
|
||||
|
||||
it('shows dictionary modal correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get('label[for="dictionary-settings"] ~ button').click()
|
||||
cy.findByText('Edit Dictionary')
|
||||
cy.findByText('Your custom dictionary is empty.')
|
||||
})
|
||||
|
||||
it('shows auto-complete menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-autoComplete option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['true', 'false'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(['On', 'Off'])
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows auto-close brackets menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>(
|
||||
'#settings-menu-autoPairDelimiters option'
|
||||
).then(options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['true', 'false'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(['On', 'Off'])
|
||||
})
|
||||
})
|
||||
|
||||
it('shows code check menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>(
|
||||
'#settings-menu-syntaxValidation option'
|
||||
).then(options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['true', 'false'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(['On', 'Off'])
|
||||
})
|
||||
})
|
||||
|
||||
it('shows editor theme menu correctly', function () {
|
||||
const editorThemes = [
|
||||
{ name: 'editortheme-1', dark: false },
|
||||
{ name: 'editortheme-2', dark: false },
|
||||
{ name: 'editortheme-3', dark: false },
|
||||
]
|
||||
|
||||
const legacyEditorThemes = [
|
||||
{ name: 'legacytheme-1', dark: false },
|
||||
{ name: 'legacytheme-2', dark: false },
|
||||
{ name: 'legacytheme-3', dark: false },
|
||||
]
|
||||
|
||||
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
||||
window.metaAttributesCache.set(
|
||||
'ol-legacyEditorThemes',
|
||||
legacyEditorThemes
|
||||
)
|
||||
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-editorTheme option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq([
|
||||
'editortheme-1',
|
||||
'editortheme-2',
|
||||
'editortheme-3',
|
||||
'legacytheme-1',
|
||||
'legacytheme-2',
|
||||
'legacytheme-3',
|
||||
])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq([
|
||||
'editortheme-1',
|
||||
'editortheme-2',
|
||||
'editortheme-3',
|
||||
'legacytheme-1 (Legacy)',
|
||||
'legacytheme-2 (Legacy)',
|
||||
'legacytheme-3 (Legacy)',
|
||||
])
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows overall theme menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-overallTheme option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['', 'light-'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(['Overall Theme 1', 'Overall Theme 2'])
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows keybindings menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-mode option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['default', 'vim', 'emacs'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(['None', 'Vim', 'Emacs'])
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows font size menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-fontSize option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq([
|
||||
'10',
|
||||
'11',
|
||||
'12',
|
||||
'13',
|
||||
'14',
|
||||
'16',
|
||||
'18',
|
||||
'20',
|
||||
'22',
|
||||
'24',
|
||||
])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq([
|
||||
'10px',
|
||||
'11px',
|
||||
'12px',
|
||||
'13px',
|
||||
'14px',
|
||||
'16px',
|
||||
'18px',
|
||||
'20px',
|
||||
'22px',
|
||||
'24px',
|
||||
])
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows font family menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-fontFamily option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['monaco', 'lucida', 'opendyslexicmono'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq([
|
||||
'Monaco / Menlo / Consolas',
|
||||
'Lucida / Source Code Pro',
|
||||
'OpenDyslexic Mono',
|
||||
])
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows line height menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-lineHeight option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['compact', 'normal', 'wide'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(['Compact', 'Normal', 'Wide'])
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('shows pdf viewer menu correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get<HTMLOptionElement>('#settings-menu-pdfViewer option').then(
|
||||
options => {
|
||||
const values = [...options].map(o => o.value)
|
||||
expect(values).to.deep.eq(['pdfjs', 'native'])
|
||||
|
||||
const texts = [...options].map(o => o.text)
|
||||
expect(texts).to.deep.eq(['Overleaf', 'Browser'])
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('help menu', function () {
|
||||
it('shows hotkeys modal correctly', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Show Hotkeys' }).click()
|
||||
cy.findByText('Hotkeys')
|
||||
})
|
||||
|
||||
it('shows correct url for documentation', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('link', { name: 'Documentation' }).should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'/learn'
|
||||
)
|
||||
})
|
||||
|
||||
it('shows correct contact us modal', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
scope={scope}
|
||||
layoutContext={{ leftMenuShown: true }}
|
||||
>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.findByRole('button', { name: 'Contact us' }).click()
|
||||
cy.findByText('Affected project URL (Optional)')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('for anonymous users', function () {
|
||||
it('render minimal menu', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
window.metaAttributesCache.set('ol-anonymous', true)
|
||||
Object.assign(getMeta('ol-ExposedSettings'), { ieeeBrandId: 123 })
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders scope={scope} layoutContext={{ leftMenuShown: true }}>
|
||||
<EditorLeftMenu />
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
// Download Menu
|
||||
cy.findByRole('heading', { name: 'Download' })
|
||||
cy.findByRole('link', { name: 'Source' })
|
||||
cy.findByRole('link', { name: 'PDF' })
|
||||
|
||||
// Actions Menu
|
||||
cy.findByRole('heading', { name: 'Actions' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'Copy project' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'Word Count' }).should('not.exist')
|
||||
|
||||
// Sync Menu
|
||||
cy.findByRole('heading', { name: 'Sync' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'Dropbox' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'Git' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'GitHub' }).should('not.exist')
|
||||
|
||||
// Settings Menu
|
||||
cy.findByRole('heading', { name: 'Settings' }).should('not.exist')
|
||||
cy.findByLabelText('Compiler').should('not.exist')
|
||||
cy.findByLabelText('TeX Live version').should('not.exist')
|
||||
cy.findByLabelText('Main document').should('not.exist')
|
||||
cy.findByLabelText('Spell check').should('not.exist')
|
||||
cy.findByLabelText('Auto-complete').should('not.exist')
|
||||
cy.findByLabelText('Auto-close brackets').should('not.exist')
|
||||
cy.findByLabelText('Code check').should('not.exist')
|
||||
cy.findByLabelText('Editor theme').should('not.exist')
|
||||
cy.findByLabelText('Overall theme').should('not.exist')
|
||||
cy.findByLabelText('Keybindings').should('not.exist')
|
||||
cy.findByLabelText('Font Size').should('not.exist')
|
||||
cy.findByLabelText('Font Family').should('not.exist')
|
||||
cy.findByLabelText('Line Height').should('not.exist')
|
||||
cy.findByLabelText('PDF Viewer').should('not.exist')
|
||||
|
||||
// Help Menu
|
||||
cy.findByRole('heading', { name: 'Help' })
|
||||
cy.findByRole('button', { name: 'Show Hotkeys' })
|
||||
cy.findByRole('button', { name: 'Documentation' }).should('not.exist')
|
||||
cy.findByRole('link', { name: 'Contact us' }).should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
import { MainDocument } from '../../../../types/project-settings'
|
||||
import { PdfViewer } from '../../../../types/user-settings'
|
||||
|
||||
type Scope = {
|
||||
settings?: {
|
||||
syntaxValidation?: boolean
|
||||
pdfViewer?: PdfViewer
|
||||
}
|
||||
editor?: {
|
||||
sharejs_doc?: {
|
||||
doc_id?: string
|
||||
getSnapshot?: () => string
|
||||
}
|
||||
}
|
||||
ui?: {
|
||||
view?: 'editor' | 'history' | 'file' | 'pdf'
|
||||
pdfLayout?: 'flat' | 'sideBySide' | 'split'
|
||||
leftMenuShown?: boolean
|
||||
}
|
||||
project?: {
|
||||
members?: any[]
|
||||
owner: {
|
||||
_id: string
|
||||
}
|
||||
features?: {
|
||||
gitBridge?: boolean
|
||||
}
|
||||
}
|
||||
user?: {
|
||||
features?: {
|
||||
dropbox: boolean
|
||||
}
|
||||
}
|
||||
docs?: MainDocument[]
|
||||
}
|
||||
|
||||
export const mockScope = (scope?: Scope) => ({
|
||||
settings: {
|
||||
syntaxValidation: false,
|
||||
pdfViewer: 'pdfjs',
|
||||
},
|
||||
editor: {
|
||||
sharejs_doc: {
|
||||
doc_id: 'test-doc',
|
||||
getSnapshot: () => 'some doc content',
|
||||
},
|
||||
},
|
||||
...scope,
|
||||
})
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import sinon from 'sinon'
|
||||
import { expect } from 'chai'
|
||||
import ActionsCopyProject from '../../../../../frontend/js/features/editor-left-menu/components/actions-copy-project'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
import { location } from '@/shared/components/location'
|
||||
|
||||
describe('<ActionsCopyProject />', function () {
|
||||
beforeEach(function () {
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationWrapperSandbox.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct modal when clicked', async function () {
|
||||
renderWithEditorContext(<ActionsCopyProject />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Copy project' }))
|
||||
|
||||
screen.getByLabelText(/New name/i)
|
||||
})
|
||||
|
||||
it('loads the project page when submitted', async function () {
|
||||
fetchMock.post('express:/project/:id/clone', {
|
||||
status: 200,
|
||||
body: {
|
||||
project_id: 'new-project',
|
||||
},
|
||||
})
|
||||
|
||||
renderWithEditorContext(<ActionsCopyProject />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Copy project' }))
|
||||
|
||||
const input = screen.getByLabelText(/New name/i)
|
||||
fireEvent.change(input, { target: { value: 'New project' } })
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Copy' })
|
||||
button.click()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(button.textContent).to.equal('Copying…')
|
||||
})
|
||||
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
expect(assignStub).to.have.been.calledOnceWith('/project/new-project')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,86 +0,0 @@
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import ActionsMenu from '../../../../../frontend/js/features/editor-left-menu/components/actions-menu'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
|
||||
describe('<ActionsMenu />', function () {
|
||||
beforeEach(function () {
|
||||
fetchMock.post('express:/project/:projectId/compile', {
|
||||
status: 'success',
|
||||
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
|
||||
outputFiles: [
|
||||
{
|
||||
path: 'output.pdf',
|
||||
build: 'build-123',
|
||||
url: '/build/build-123/output.pdf',
|
||||
type: 'pdf',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu for non-anonymous users', async function () {
|
||||
window.metaAttributesCache.set('ol-anonymous', false)
|
||||
|
||||
renderWithEditorContext(<ActionsMenu />, {
|
||||
projectId: '123abc',
|
||||
mockCompileOnLoad: true,
|
||||
scope: {
|
||||
editor: {
|
||||
sharejs_doc: {
|
||||
doc_id: 'test-doc',
|
||||
getSnapshot: () => 'some doc content',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
screen.getByText('Actions')
|
||||
screen.getByRole('button', {
|
||||
name: 'Copy project',
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
screen.getByRole('button', {
|
||||
name: 'Word Count',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('does not show anything for anonymous users', async function () {
|
||||
window.metaAttributesCache.set('ol-anonymous', true)
|
||||
|
||||
renderWithEditorContext(<ActionsMenu />, {
|
||||
projectId: '123abc',
|
||||
mockCompileOnLoad: true,
|
||||
scope: {
|
||||
editor: {
|
||||
sharejs_doc: {
|
||||
doc_id: 'test-doc',
|
||||
getSnapshot: () => 'some doc content',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(screen.queryByText('Actions')).to.equal(null)
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: 'Copy project',
|
||||
})
|
||||
).to.equal(null)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: 'Word Count',
|
||||
})
|
||||
).to.equal(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import ActionsWordCount from '../../../../../frontend/js/features/editor-left-menu/components/actions-word-count'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
|
||||
describe('<ActionsWordCount />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct modal when clicked after document is compiled', async function () {
|
||||
const compileEndpoint = 'express:/project/:projectId/compile'
|
||||
const wordcountEndpoint = 'express:/project/:projectId/wordcount'
|
||||
|
||||
fetchMock.post(compileEndpoint, {
|
||||
status: 'success',
|
||||
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
|
||||
outputFiles: [
|
||||
{
|
||||
path: 'output.pdf',
|
||||
build: 'build-123',
|
||||
url: '/build/build-123/output.pdf',
|
||||
type: 'pdf',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
fetchMock.get(wordcountEndpoint, {
|
||||
texcount: {
|
||||
encode: 'ascii',
|
||||
textWords: 0,
|
||||
headers: 0,
|
||||
mathInline: 0,
|
||||
mathDisplay: 0,
|
||||
},
|
||||
})
|
||||
|
||||
renderWithEditorContext(<ActionsWordCount />, {
|
||||
projectId: '123abc',
|
||||
mockCompileOnLoad: true,
|
||||
scope: {
|
||||
editor: {
|
||||
sharejs_doc: {
|
||||
doc_id: 'test-doc',
|
||||
getSnapshot: () => 'some doc content',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// when loading, we don't render the "Word Count" as button yet
|
||||
expect(screen.queryByRole('button', { name: 'Word Count' })).to.equal(null)
|
||||
|
||||
await waitFor(
|
||||
() => expect(fetchMock.callHistory.called(compileEndpoint)).to.be.true
|
||||
)
|
||||
|
||||
const button = await screen.findByRole('button', { name: 'Word Count' })
|
||||
button.click()
|
||||
|
||||
await waitFor(
|
||||
() => expect(fetchMock.callHistory.called(wordcountEndpoint)).to.be.true
|
||||
)
|
||||
})
|
||||
})
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import DownloadMenu from '../../../../../frontend/js/features/editor-left-menu/components/download-menu'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
|
||||
describe('<DownloadMenu />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows download links with correct url', async function () {
|
||||
fetchMock.post('express:/project/:projectId/compile', {
|
||||
clsiServerId: 'foo',
|
||||
compileGroup: 'priority',
|
||||
status: 'success',
|
||||
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
|
||||
outputFiles: [
|
||||
{
|
||||
path: 'output.pdf',
|
||||
build: 'build-123',
|
||||
url: '/build/build-123/output.pdf',
|
||||
type: 'pdf',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
renderWithEditorContext(<DownloadMenu />, {
|
||||
projectId: '123abc',
|
||||
mockCompileOnLoad: true,
|
||||
scope: {
|
||||
editor: {
|
||||
sharejs_doc: {
|
||||
doc_id: 'test-doc',
|
||||
getSnapshot: () => 'some doc content',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const sourceLink = screen.getByRole('link', {
|
||||
name: 'Source',
|
||||
})
|
||||
|
||||
expect(sourceLink.getAttribute('href')).to.equal(
|
||||
'/project/123abc/download/zip'
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
const pdfLink = screen.getByRole('link', {
|
||||
name: 'PDF',
|
||||
})
|
||||
|
||||
expect(pdfLink.getAttribute('href')).to.equal(
|
||||
'/download/project/123abc/build/build-123/output/output.pdf?compileGroup=priority&clsiserverid=foo&popupDownload=true'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import { screen, fireEvent, within } from '@testing-library/react'
|
||||
import HelpContactUs from '../../../../../frontend/js/features/editor-left-menu/components/help-contact-us'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
describe('<HelpContactUs />', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-user', {
|
||||
email: 'sherlock@holmes.co.uk',
|
||||
first_name: 'Sherlock',
|
||||
last_name: 'Holmes',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('open contact us modal when clicked', function () {
|
||||
renderWithEditorContext(<HelpContactUs />)
|
||||
|
||||
expect(screen.queryByRole('dialog')).to.equal(null)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Contact us' }))
|
||||
const modal = screen.getAllByRole('dialog')[0]
|
||||
within(modal).getAllByText('Get in touch')
|
||||
within(modal).getByText('Subject')
|
||||
})
|
||||
})
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import { screen, render } from '@testing-library/react'
|
||||
import HelpDocumentation from '../../../../../frontend/js/features/editor-left-menu/components/help-documentation'
|
||||
|
||||
describe('<HelpDocumentation />', function () {
|
||||
it('has correct href attribute', function () {
|
||||
render(<HelpDocumentation />)
|
||||
|
||||
const link = screen.getByRole('link', { name: 'Documentation' })
|
||||
expect(link.getAttribute('href')).to.equal('/learn')
|
||||
})
|
||||
})
|
||||
@@ -1,39 +0,0 @@
|
||||
import { screen } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import HelpMenu from '../../../../../frontend/js/features/editor-left-menu/components/help-menu'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
|
||||
describe('<HelpMenu />', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-user', {
|
||||
email: 'sherlock@holmes.co.uk',
|
||||
first_name: 'Sherlock',
|
||||
last_name: 'Holmes',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu if `showSupport` is `true`', function () {
|
||||
window.metaAttributesCache.set('ol-showSupport', true)
|
||||
|
||||
renderWithEditorContext(<HelpMenu />)
|
||||
|
||||
screen.getByRole('button', { name: 'Show Hotkeys' })
|
||||
screen.getByRole('button', { name: 'Contact us' })
|
||||
screen.getByRole('link', { name: 'Documentation' })
|
||||
})
|
||||
|
||||
it('shows correct menu if `showSupport` is `false`', function () {
|
||||
window.metaAttributesCache.set('ol-showSupport', false)
|
||||
|
||||
renderWithEditorContext(<HelpMenu />)
|
||||
|
||||
screen.getByRole('button', { name: 'Show Hotkeys' })
|
||||
expect(screen.queryByRole('button', { name: 'Contact us' })).to.equal(null)
|
||||
expect(screen.queryByRole('link', { name: 'Documentation' })).to.equal(null)
|
||||
})
|
||||
})
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import { screen, fireEvent, within } from '@testing-library/react'
|
||||
import HelpShowHotkeys from '../../../../../frontend/js/features/editor-left-menu/components/help-show-hotkeys'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
describe('<HelpShowHotkeys />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('open hotkeys modal when clicked', function () {
|
||||
renderWithEditorContext(<HelpShowHotkeys />)
|
||||
|
||||
expect(screen.queryByRole('dialog')).to.equal(null)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Show Hotkeys' }))
|
||||
const modal = screen.getAllByRole('dialog')[0]
|
||||
within(modal).getByText('Common')
|
||||
})
|
||||
})
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsAutoCloseBrackets from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-auto-close-brackets'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
describe('<SettingsAutoCloseBrackets />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsAutoCloseBrackets />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Auto-close brackets')
|
||||
|
||||
const optionOn = within(select).getByText('On')
|
||||
expect(optionOn.getAttribute('value')).to.equal('true')
|
||||
|
||||
const optionOff = within(select).getByText('Off')
|
||||
expect(optionOff.getAttribute('value')).to.equal('false')
|
||||
})
|
||||
})
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsAutoComplete from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-auto-complete'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
describe('<SettingsAutoComplete />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsAutoComplete />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Auto-complete')
|
||||
|
||||
const optionOn = within(select).getByText('On')
|
||||
expect(optionOn.getAttribute('value')).to.equal('true')
|
||||
|
||||
const optionOff = within(select).getByText('Off')
|
||||
expect(optionOff.getAttribute('value')).to.equal('false')
|
||||
})
|
||||
})
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsCompiler from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-compiler'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
describe('<SettingsCompiler />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsCompiler />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Compiler')
|
||||
|
||||
const optionPdfLaTeX = within(select).getByText('pdfLaTeX')
|
||||
expect(optionPdfLaTeX.getAttribute('value')).to.equal('pdflatex')
|
||||
|
||||
const optionLaTeX = within(select).getByText('LaTeX')
|
||||
expect(optionLaTeX.getAttribute('value')).to.equal('latex')
|
||||
|
||||
const optionXeLaTeX = within(select).getByText('XeLaTeX')
|
||||
expect(optionXeLaTeX.getAttribute('value')).to.equal('xelatex')
|
||||
|
||||
const optionLuaLaTeX = within(select).getByText('LuaLaTeX')
|
||||
expect(optionLuaLaTeX.getAttribute('value')).to.equal('lualatex')
|
||||
})
|
||||
})
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
import { fireEvent, screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import SettingsDictionary from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-dictionary'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
describe('<SettingsDictionary />', function () {
|
||||
it('open dictionary modal', function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsDictionary />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
screen.getByText('Dictionary')
|
||||
|
||||
const button = screen.getByText('Edit')
|
||||
fireEvent.click(button)
|
||||
|
||||
const modal = screen.getByTestId('dictionary-modal')
|
||||
|
||||
within(modal).getByRole('heading', { name: 'Edit Dictionary' })
|
||||
within(modal).getByText('Your custom dictionary is empty.')
|
||||
|
||||
const closeButton = within(modal).getByRole('button', {
|
||||
name: 'Close dialog',
|
||||
})
|
||||
fireEvent.click(closeButton)
|
||||
expect(screen.getByTestId('dictionary-modal')).to.not.be.null
|
||||
})
|
||||
})
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsDocument from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-document'
|
||||
import { Folder } from '../../../../../../types/folder'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
describe('<SettingsDocument />', function () {
|
||||
const rootFolder: Folder = {
|
||||
_id: 'root-folder-id',
|
||||
name: 'rootFolder',
|
||||
docs: [
|
||||
{
|
||||
_id: '123abc',
|
||||
name: 'main.tex',
|
||||
},
|
||||
],
|
||||
fileRefs: [],
|
||||
folders: [],
|
||||
}
|
||||
|
||||
let originalSettings: typeof window.metaAttributesCache
|
||||
|
||||
beforeEach(function () {
|
||||
originalSettings = window.metaAttributesCache.get('ol-ExposedSettings')
|
||||
window.metaAttributesCache.set('ol-ExposedSettings', {
|
||||
validRootDocExtensions: ['tex'],
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
window.metaAttributesCache.set('ol-ExposedSettings', originalSettings)
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders rootFolder={[rootFolder as any]}>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsDocument />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Main document')
|
||||
|
||||
const optionOn = within(select).getByText('main.tex')
|
||||
expect(optionOn.getAttribute('value')).to.equal('123abc')
|
||||
})
|
||||
})
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsEditorTheme from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-editor-theme'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
const MOCK_IEEE_BRAND_ID = 123
|
||||
|
||||
describe('<SettingsEditorTheme />', function () {
|
||||
const editorThemes = [
|
||||
{ name: 'editortheme-1', dark: false },
|
||||
{ name: 'editortheme-2', dark: false },
|
||||
{ name: 'editortheme-3', dark: false },
|
||||
]
|
||||
|
||||
const legacyEditorThemes = [
|
||||
{ name: 'legacytheme-1', dark: false },
|
||||
{ name: 'legacytheme-2', dark: false },
|
||||
{ name: 'legacytheme-3', dark: false },
|
||||
]
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
||||
window.metaAttributesCache.set('ol-legacyEditorThemes', legacyEditorThemes)
|
||||
window.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: undefined,
|
||||
})
|
||||
window.metaAttributesCache.get('ol-ExposedSettings').ieeeBrandId =
|
||||
MOCK_IEEE_BRAND_ID
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
function checkSelect(select: HTMLElement) {
|
||||
for (const theme of editorThemes) {
|
||||
const option = within(select).getByText(theme.name.replace(/_/g, ' '))
|
||||
expect(option.getAttribute('value')).to.equal(theme.name)
|
||||
}
|
||||
|
||||
for (const theme of legacyEditorThemes) {
|
||||
const option = within(select).getByText(
|
||||
theme.name.replace(/_/g, ' ') + ' (Legacy)'
|
||||
)
|
||||
expect(option.getAttribute('value')).to.equal(theme.name)
|
||||
}
|
||||
}
|
||||
|
||||
describe('with default theme', function () {
|
||||
beforeEach(function () {
|
||||
render(
|
||||
<EditorProviders userSettings={{ overallTheme: '' }}>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsEditorTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
const select = screen.getByLabelText('Editor theme')
|
||||
expect(select).to.exist
|
||||
checkSelect(select)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with system theme', function () {
|
||||
beforeEach(function () {
|
||||
render(
|
||||
<EditorProviders userSettings={{ overallTheme: 'system' }}>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsEditorTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
const select = screen.queryByLabelText('Editor theme')
|
||||
expect(select).to.not.exist
|
||||
const lightSelect = screen.getByLabelText('Light editor theme')
|
||||
expect(lightSelect).to.exist
|
||||
checkSelect(lightSelect)
|
||||
const darkSelect = screen.getByLabelText('Dark editor theme')
|
||||
expect(darkSelect).to.exist
|
||||
checkSelect(darkSelect)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with IEEE branding', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: MOCK_IEEE_BRAND_ID,
|
||||
})
|
||||
render(
|
||||
<EditorProviders userSettings={{ overallTheme: 'system' }}>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsEditorTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('ignores the system theme and shows single selection', async function () {
|
||||
const select = screen.getByLabelText('Editor theme')
|
||||
expect(select).to.exist
|
||||
checkSelect(select)
|
||||
})
|
||||
})
|
||||
})
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsFontFamily from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-font-family'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
describe('<SettingsFontFamily />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsFontFamily />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Font Family')
|
||||
|
||||
const optionMonaco = within(select).getByText('Monaco / Menlo / Consolas')
|
||||
expect(optionMonaco.getAttribute('value')).to.equal('monaco')
|
||||
|
||||
const optionLucida = within(select).getByText('Lucida / Source Code Pro')
|
||||
expect(optionLucida.getAttribute('value')).to.equal('lucida')
|
||||
|
||||
const optionOpenDyslexicMono = within(select).getByText('OpenDyslexic Mono')
|
||||
expect(optionOpenDyslexicMono.getAttribute('value')).to.equal(
|
||||
'opendyslexicmono'
|
||||
)
|
||||
})
|
||||
})
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsFontSize from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-font-size'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
describe('<SettingsFontSize />', function () {
|
||||
const sizes = ['10', '11', '12', '13', '14', '16', '18', '20', '22', '24']
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsFontSize />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Font Size')
|
||||
|
||||
for (const size of sizes) {
|
||||
const option = within(select).getByText(`${size}px`)
|
||||
expect(option.getAttribute('value')).to.equal(size)
|
||||
}
|
||||
})
|
||||
})
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsImageName from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-image-name'
|
||||
import type { ImageName } from '../../../../../../types/project-settings'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
describe('<SettingsImageName />', function () {
|
||||
const imageNames: ImageName[] = [
|
||||
{
|
||||
imageDesc: 'Image 1',
|
||||
imageName: 'img-1',
|
||||
allowed: true,
|
||||
},
|
||||
{
|
||||
imageDesc: 'Image 2',
|
||||
imageName: 'img-2',
|
||||
allowed: true,
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-imageNames', imageNames)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsImageName />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('TeX Live version')
|
||||
|
||||
for (const { imageName, imageDesc } of imageNames) {
|
||||
const option = within(select).getByText(imageDesc)
|
||||
expect(option.getAttribute('value')).to.equal(imageName)
|
||||
}
|
||||
})
|
||||
})
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsKeybindings from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-keybindings'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
|
||||
describe('<SettingsKeybindings />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsKeybindings />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Keybindings')
|
||||
|
||||
const optionNone = within(select).getByText('None')
|
||||
expect(optionNone.getAttribute('value')).to.equal('default')
|
||||
|
||||
const optionVim = within(select).getByText('Vim')
|
||||
expect(optionVim.getAttribute('value')).to.equal('vim')
|
||||
|
||||
const optionEmacs = within(select).getByText('Emacs')
|
||||
expect(optionEmacs.getAttribute('value')).to.equal('emacs')
|
||||
})
|
||||
})
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsLineHeight from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-line-height'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
|
||||
describe('<SettingsLineHeight />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsLineHeight />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Line Height')
|
||||
|
||||
const optionCompact = within(select).getByText('Compact')
|
||||
expect(optionCompact.getAttribute('value')).to.equal('compact')
|
||||
|
||||
const optionNormal = within(select).getByText('Normal')
|
||||
expect(optionNormal.getAttribute('value')).to.equal('normal')
|
||||
|
||||
const optionWide = within(select).getByText('Wide')
|
||||
expect(optionWide.getAttribute('value')).to.equal('wide')
|
||||
})
|
||||
})
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsMathPreview from '@/features/editor-left-menu/components/settings/settings-math-preview'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
|
||||
describe('<SettingsMathPreview />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsMathPreview />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Equation preview')
|
||||
|
||||
const optionOn = within(select).getByText('On')
|
||||
expect(optionOn.getAttribute('value')).to.equal('true')
|
||||
|
||||
const optionOff = within(select).getByText('Off')
|
||||
expect(optionOff.getAttribute('value')).to.equal('false')
|
||||
})
|
||||
})
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsOverallTheme from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-overall-theme'
|
||||
import type { OverallThemeMeta } from '../../../../../../types/project-settings'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
|
||||
const IEEE_BRAND_ID = 1234
|
||||
const OTHER_BRAND_ID = 2234
|
||||
|
||||
describe('<SettingsOverallTheme />', function () {
|
||||
const overallThemes: OverallThemeMeta[] = [
|
||||
{
|
||||
name: 'Overall Theme 1',
|
||||
val: '',
|
||||
path: 'https://overleaf.com/overalltheme-1.css',
|
||||
},
|
||||
{
|
||||
name: 'Overall Theme 2',
|
||||
val: 'light-',
|
||||
path: 'https://overleaf.com/overalltheme-2.css',
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-overallThemes', overallThemes)
|
||||
Object.assign(getMeta('ol-ExposedSettings'), {
|
||||
ieeeBrandId: IEEE_BRAND_ID,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsOverallTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Overall theme')
|
||||
|
||||
for (const theme of overallThemes) {
|
||||
const option = within(select).getByText(theme.name)
|
||||
expect(option.getAttribute('value')).to.equal(theme.val)
|
||||
}
|
||||
})
|
||||
describe('Branded Project', function () {
|
||||
it('should hide overall theme picker for IEEE branded projects', function () {
|
||||
window.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: IEEE_BRAND_ID,
|
||||
})
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsOverallTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
const select = screen.queryByText('Overall theme')
|
||||
expect(select).to.not.exist
|
||||
})
|
||||
|
||||
it('should show overall theme picker for branded projects that are not IEEE', function () {
|
||||
window.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: OTHER_BRAND_ID,
|
||||
})
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsOverallTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
const select = screen.getByLabelText('Overall theme')
|
||||
expect(select).to.exist
|
||||
})
|
||||
|
||||
it('should show overall theme picker for non branded projects', function () {
|
||||
window.metaAttributesCache.set('ol-brandVariation', undefined)
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsOverallTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
const select = screen.getByLabelText('Overall theme')
|
||||
expect(select).to.exist
|
||||
})
|
||||
})
|
||||
})
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsPdfViewer from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-pdf-viewer'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
|
||||
describe('<SettingsPdfViewer />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsPdfViewer />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('PDF Viewer')
|
||||
|
||||
const optionOverleaf = within(select).getByText('Overleaf')
|
||||
expect(optionOverleaf.getAttribute('value')).to.equal('pdfjs')
|
||||
|
||||
const optionBrowser = within(select).getByText('Browser')
|
||||
expect(optionBrowser.getAttribute('value')).to.equal('native')
|
||||
})
|
||||
})
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsSpellCheckLanguage from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-spell-check-language'
|
||||
import type { SpellCheckLanguage } from '../../../../../../types/project-settings'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
|
||||
describe('<SettingsSpellCheckLanguage />', function () {
|
||||
const languages: SpellCheckLanguage[] = [
|
||||
{
|
||||
name: 'Lang 1',
|
||||
code: 'lang-1',
|
||||
dic: 'lang_1',
|
||||
},
|
||||
{
|
||||
name: 'Lang 2',
|
||||
code: 'lang-2',
|
||||
dic: 'lang_2',
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-languages', languages)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsSpellCheckLanguage />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Spell check')
|
||||
|
||||
const optionEmpty = within(select).getByText('Off')
|
||||
expect(optionEmpty.getAttribute('value')).to.equal('')
|
||||
|
||||
for (const language of languages) {
|
||||
const option = within(select).getByText(language.name)
|
||||
expect(option.getAttribute('value')).to.equal(language.code)
|
||||
}
|
||||
})
|
||||
})
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsSyntaxValidation from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-syntax-validation'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
|
||||
describe('<SettingsSyntaxValidation />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsSyntaxValidation />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Code check')
|
||||
|
||||
const optionOn = within(select).getByText('On')
|
||||
expect(optionOn.getAttribute('value')).to.equal('true')
|
||||
|
||||
const optionOff = within(select).getByText('Off')
|
||||
expect(optionOff.getAttribute('value')).to.equal('false')
|
||||
})
|
||||
})
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
import { screen, within, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import SettingsOverallTheme from '../../../../../frontend/js/features/editor-left-menu/components/settings/settings-overall-theme'
|
||||
import type { OverallThemeMeta } from '../../../../../types/project-settings'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
@@ -76,7 +75,7 @@ describe('<OverallThemeSetting />', function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<SettingsModalProvider>
|
||||
<SettingsOverallTheme />
|
||||
<OverallThemeSetting />
|
||||
</SettingsModalProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
@@ -91,7 +90,7 @@ describe('<OverallThemeSetting />', function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<SettingsModalProvider>
|
||||
<SettingsOverallTheme />
|
||||
<OverallThemeSetting />
|
||||
</SettingsModalProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
@@ -104,7 +103,7 @@ describe('<OverallThemeSetting />', function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<SettingsModalProvider>
|
||||
<SettingsOverallTheme />
|
||||
<OverallThemeSetting />
|
||||
</SettingsModalProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user