mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #20740 from overleaf/rd-ide-offcanvas
[web] Implement the editor's left menu in Offcanvas GitOrigin-RevId: 999e995d664b1dc958f56643f05e95b8aa2d6290
This commit is contained in:
parent
7a26d46d7c
commit
f8efc3e2ae
40 changed files with 865 additions and 239 deletions
|
@ -29,4 +29,12 @@
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
font-feature-settings: 'liga';
|
font-feature-settings: 'liga';
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
|
&.size-2x {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.rotate-180 {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -1,12 +1,20 @@
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Alert, Button, Modal } from 'react-bootstrap'
|
|
||||||
import Icon from '../../../shared/components/icon'
|
|
||||||
import Tooltip from '../../../shared/components/tooltip'
|
|
||||||
import useAsync from '../../../shared/hooks/use-async'
|
import useAsync from '../../../shared/hooks/use-async'
|
||||||
import { postJSON } from '../../../infrastructure/fetch-json'
|
import { postJSON } from '../../../infrastructure/fetch-json'
|
||||||
import ignoredWords from '../ignored-words'
|
import ignoredWords from '../ignored-words'
|
||||||
import { debugConsole } from '@/utils/debugging'
|
import { debugConsole } from '@/utils/debugging'
|
||||||
|
import {
|
||||||
|
OLModalBody,
|
||||||
|
OLModalFooter,
|
||||||
|
OLModalHeader,
|
||||||
|
OLModalTitle,
|
||||||
|
} from '@/features/ui/components/ol/ol-modal'
|
||||||
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import OLIconButton from '@/features/ui/components/ol/ol-icon-button'
|
||||||
|
import { bsVersion } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
type DictionaryModalContentProps = {
|
type DictionaryModalContentProps = {
|
||||||
handleHide: () => void
|
handleHide: () => void
|
||||||
|
@ -42,13 +50,16 @@ export default function DictionaryModalContent({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal.Header closeButton>
|
<OLModalHeader closeButton>
|
||||||
<Modal.Title>{t('edit_dictionary')}</Modal.Title>
|
<OLModalTitle>{t('edit_dictionary')}</OLModalTitle>
|
||||||
</Modal.Header>
|
</OLModalHeader>
|
||||||
|
|
||||||
<Modal.Body>
|
<OLModalBody>
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<Alert bsStyle="danger">{t('generic_something_went_wrong')}</Alert>
|
<OLNotification
|
||||||
|
type="error"
|
||||||
|
content={t('generic_something_went_wrong')}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{learnedWords?.size > 0 ? (
|
{learnedWords?.size > 0 ? (
|
||||||
|
@ -56,22 +67,25 @@ export default function DictionaryModalContent({
|
||||||
{[...learnedWords].sort(wordsSortFunction).map(learnedWord => (
|
{[...learnedWords].sort(wordsSortFunction).map(learnedWord => (
|
||||||
<li key={learnedWord} className="dictionary-entry">
|
<li key={learnedWord} className="dictionary-entry">
|
||||||
<span className="dictionary-entry-name">{learnedWord}</span>
|
<span className="dictionary-entry-name">{learnedWord}</span>
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id={`tooltip-remove-learned-word-${learnedWord}`}
|
id={`tooltip-remove-learned-word-${learnedWord}`}
|
||||||
description={t('edit_dictionary_remove')}
|
description={t('edit_dictionary_remove')}
|
||||||
overlayProps={{ delay: 0 }}
|
overlayProps={{ delay: 0 }}
|
||||||
>
|
>
|
||||||
<Button
|
<OLIconButton
|
||||||
bsStyle="danger"
|
variant="danger"
|
||||||
bsSize="xs"
|
size="sm"
|
||||||
onClick={() => handleRemove(learnedWord)}
|
onClick={() => handleRemove(learnedWord)}
|
||||||
>
|
bs3Props={{ bsSize: 'xsmall' }}
|
||||||
<Icon
|
icon={
|
||||||
type="trash-o"
|
bsVersion({
|
||||||
accessibilityLabel={t('edit_dictionary_remove')}
|
bs5: 'delete',
|
||||||
/>
|
bs3: 'trash-o',
|
||||||
</Button>
|
}) as string
|
||||||
</Tooltip>
|
}
|
||||||
|
accessibilityLabel={t('edit_dictionary_remove')}
|
||||||
|
/>
|
||||||
|
</OLTooltip>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -80,13 +94,13 @@ export default function DictionaryModalContent({
|
||||||
<i>{t('edit_dictionary_empty')}</i>
|
<i>{t('edit_dictionary_empty')}</i>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</Modal.Body>
|
</OLModalBody>
|
||||||
|
|
||||||
<Modal.Footer>
|
<OLModalFooter>
|
||||||
<Button bsStyle={null} className="btn-secondary" onClick={handleHide}>
|
<OLButton variant="secondary" onClick={handleHide}>
|
||||||
{t('close')}
|
{t('close')}
|
||||||
</Button>
|
</OLButton>
|
||||||
</Modal.Footer>
|
</OLModalFooter>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import DictionaryModalContent from './dictionary-modal-content'
|
import DictionaryModalContent from './dictionary-modal-content'
|
||||||
import AccessibleModal from '../../../shared/components/accessible-modal'
|
|
||||||
import withErrorBoundary from '../../../infrastructure/error-boundary'
|
import withErrorBoundary from '../../../infrastructure/error-boundary'
|
||||||
|
import OLModal from '@/features/ui/components/ol/ol-modal'
|
||||||
|
|
||||||
type DictionaryModalProps = {
|
type DictionaryModalProps = {
|
||||||
show?: boolean
|
show?: boolean
|
||||||
|
@ -10,15 +10,15 @@ type DictionaryModalProps = {
|
||||||
|
|
||||||
function DictionaryModal({ show, handleHide }: DictionaryModalProps) {
|
function DictionaryModal({ show, handleHide }: DictionaryModalProps) {
|
||||||
return (
|
return (
|
||||||
<AccessibleModal
|
<OLModal
|
||||||
animation
|
animation
|
||||||
show={show}
|
show={show}
|
||||||
onHide={handleHide}
|
onHide={handleHide}
|
||||||
id="dictionary-modal"
|
id="dictionary-modal"
|
||||||
bsSize="small"
|
size="sm"
|
||||||
>
|
>
|
||||||
<DictionaryModalContent handleHide={handleHide} />
|
<DictionaryModalContent handleHide={handleHide} />
|
||||||
</AccessibleModal>
|
</OLModal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import EditorCloneProjectModalWrapper from '../../clone-project-modal/components
|
||||||
import LeftMenuButton from './left-menu-button'
|
import LeftMenuButton from './left-menu-button'
|
||||||
import { useLocation } from '../../../shared/hooks/use-location'
|
import { useLocation } from '../../../shared/hooks/use-location'
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||||
|
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
type ProjectCopyResponse = {
|
type ProjectCopyResponse = {
|
||||||
project_id: string
|
project_id: string
|
||||||
|
@ -30,10 +31,10 @@ export default function ActionsCopyProject() {
|
||||||
<>
|
<>
|
||||||
<LeftMenuButton
|
<LeftMenuButton
|
||||||
onClick={handleShowModal}
|
onClick={handleShowModal}
|
||||||
icon={{
|
icon={bsVersionIcon({
|
||||||
type: 'copy',
|
bs5: { type: 'file_copy' },
|
||||||
fw: true,
|
bs3: { type: 'copy', fw: true },
|
||||||
}}
|
})}
|
||||||
>
|
>
|
||||||
{t('copy_project')}
|
{t('copy_project')}
|
||||||
</LeftMenuButton>
|
</LeftMenuButton>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { useState, useCallback } from 'react'
|
import { useState, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Tooltip from '../../../shared/components/tooltip'
|
|
||||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||||
import WordCountModal from '../../word-count-modal/components/word-count-modal'
|
import WordCountModal from '../../word-count-modal/components/word-count-modal'
|
||||||
import LeftMenuButton from './left-menu-button'
|
import LeftMenuButton from './left-menu-button'
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||||
|
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
|
||||||
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
|
||||||
export default function ActionsWordCount() {
|
export default function ActionsWordCount() {
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
@ -21,15 +22,15 @@ export default function ActionsWordCount() {
|
||||||
{pdfUrl ? (
|
{pdfUrl ? (
|
||||||
<LeftMenuButton
|
<LeftMenuButton
|
||||||
onClick={handleShowModal}
|
onClick={handleShowModal}
|
||||||
icon={{
|
icon={bsVersionIcon({
|
||||||
type: 'eye',
|
bs5: { type: 'match_case' },
|
||||||
fw: true,
|
bs3: { type: 'eye', fw: true },
|
||||||
}}
|
})}
|
||||||
>
|
>
|
||||||
{t('word_count')}
|
{t('word_count')}
|
||||||
</LeftMenuButton>
|
</LeftMenuButton>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="disabled-word-count"
|
id="disabled-word-count"
|
||||||
description={t('please_compile_pdf_before_word_count')}
|
description={t('please_compile_pdf_before_word_count')}
|
||||||
overlayProps={{
|
overlayProps={{
|
||||||
|
@ -39,10 +40,10 @@ export default function ActionsWordCount() {
|
||||||
{/* OverlayTrigger won't fire unless the child is a non-react html element (e.g div, span) */}
|
{/* OverlayTrigger won't fire unless the child is a non-react html element (e.g div, span) */}
|
||||||
<div>
|
<div>
|
||||||
<LeftMenuButton
|
<LeftMenuButton
|
||||||
icon={{
|
icon={bsVersionIcon({
|
||||||
type: 'eye',
|
bs5: { type: 'match_case' },
|
||||||
fw: true,
|
bs3: { type: 'eye', fw: true },
|
||||||
}}
|
})}
|
||||||
disabled
|
disabled
|
||||||
disabledAccesibilityText={t(
|
disabledAccesibilityText={t(
|
||||||
'please_compile_pdf_before_word_count'
|
'please_compile_pdf_before_word_count'
|
||||||
|
@ -51,7 +52,7 @@ export default function ActionsWordCount() {
|
||||||
{t('word_count')}
|
{t('word_count')}
|
||||||
</LeftMenuButton>
|
</LeftMenuButton>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</OLTooltip>
|
||||||
)}
|
)}
|
||||||
<WordCountModal show={showModal} handleHide={() => setShowModal(false)} />
|
<WordCountModal show={showModal} handleHide={() => setShowModal(false)} />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -2,9 +2,11 @@ import { useTranslation } from 'react-i18next'
|
||||||
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
|
||||||
import { useProjectContext } from '../../../shared/context/project-context'
|
import { useProjectContext } from '../../../shared/context/project-context'
|
||||||
import Icon from '../../../shared/components/icon'
|
import Icon from '../../../shared/components/icon'
|
||||||
import Tooltip from '../../../shared/components/tooltip'
|
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||||
import { isSmallDevice } from '../../../infrastructure/event-tracking'
|
import { isSmallDevice } from '../../../infrastructure/event-tracking'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||||
|
|
||||||
export default function DownloadPDF() {
|
export default function DownloadPDF() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
@ -27,24 +29,30 @@ export default function DownloadPDF() {
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
onClick={sendDownloadEvent}
|
onClick={sendDownloadEvent}
|
||||||
>
|
>
|
||||||
<Icon type="file-pdf-o" modifier="2x" />
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="file-pdf-o" modifier="2x" />}
|
||||||
|
bs5={<MaterialIcon type="picture_as_pdf" size="2x" />}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
PDF
|
PDF
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<OLTooltip
|
||||||
id="disabled-pdf-download"
|
id="disabled-pdf-download"
|
||||||
description={t('please_compile_pdf_before_download')}
|
description={t('please_compile_pdf_before_download')}
|
||||||
overlayProps={{ placement: 'bottom' }}
|
overlayProps={{ placement: 'bottom' }}
|
||||||
>
|
>
|
||||||
<div className="link-disabled">
|
<div className="link-disabled">
|
||||||
<Icon type="file-pdf-o" modifier="2x" />
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="file-pdf-o" modifier="2x" />}
|
||||||
|
bs5={<MaterialIcon type="picture_as_pdf" size="2x" />}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
PDF
|
PDF
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</OLTooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { useProjectContext } from '../../../shared/context/project-context'
|
||||||
import Icon from '../../../shared/components/icon'
|
import Icon from '../../../shared/components/icon'
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||||
import { isSmallDevice } from '../../../infrastructure/event-tracking'
|
import { isSmallDevice } from '../../../infrastructure/event-tracking'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
|
||||||
export default function DownloadSource() {
|
export default function DownloadSource() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
@ -23,7 +25,10 @@ export default function DownloadSource() {
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
onClick={sendDownloadEvent}
|
onClick={sendDownloadEvent}
|
||||||
>
|
>
|
||||||
<Icon type="file-archive-o" modifier="2x" />
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="file-archive-o" modifier="2x" />}
|
||||||
|
bs5={<MaterialIcon type="folder_zip" size="2x" />}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
{t('source')}
|
{t('source')}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -5,35 +5,63 @@ import { Modal } from 'react-bootstrap'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { lazy, memo, Suspense } from 'react'
|
import { lazy, memo, Suspense } from 'react'
|
||||||
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
|
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import { Offcanvas } from 'react-bootstrap-5'
|
||||||
const EditorLeftMenuBody = lazy(() => import('./editor-left-menu-body'))
|
const EditorLeftMenuBody = lazy(() => import('./editor-left-menu-body'))
|
||||||
|
|
||||||
function EditorLeftMenu() {
|
function EditorLeftMenu() {
|
||||||
const { leftMenuShown, setLeftMenuShown } = useLayoutContext()
|
const { leftMenuShown, setLeftMenuShown } = useLayoutContext()
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeLeftMenu = () => {
|
||||||
setLeftMenuShown(false)
|
setLeftMenuShown(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<BootstrapVersionSwitcher
|
||||||
<AccessibleModal
|
bs3={
|
||||||
backdropClassName="left-menu-modal-backdrop"
|
<>
|
||||||
keyboard
|
<AccessibleModal
|
||||||
onHide={closeModal}
|
backdropClassName="left-menu-modal-backdrop"
|
||||||
id="left-menu-modal"
|
keyboard
|
||||||
show={leftMenuShown}
|
onHide={closeLeftMenu}
|
||||||
>
|
id="left-menu-modal"
|
||||||
<Modal.Body
|
show={leftMenuShown}
|
||||||
className={classNames('full-size', { shown: leftMenuShown })}
|
>
|
||||||
id="left-menu"
|
<Modal.Body
|
||||||
>
|
className={classNames('full-size', { shown: leftMenuShown })}
|
||||||
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
|
id="left-menu"
|
||||||
<EditorLeftMenuBody />
|
>
|
||||||
</Suspense>
|
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
|
||||||
</Modal.Body>
|
<EditorLeftMenuBody />
|
||||||
</AccessibleModal>
|
</Suspense>
|
||||||
{leftMenuShown && <LeftMenuMask />}
|
</Modal.Body>
|
||||||
</>
|
</AccessibleModal>
|
||||||
|
{leftMenuShown && <LeftMenuMask />}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
|
||||||
|
<EditorLeftMenuBody />
|
||||||
|
</Suspense>
|
||||||
|
</Offcanvas.Body>
|
||||||
|
</Offcanvas>
|
||||||
|
{leftMenuShown && <LeftMenuMask />}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useCallback } from 'react'
|
||||||
import * as eventTracking from '../../../infrastructure/event-tracking'
|
import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||||
import { useContactUsModal } from '../../../shared/hooks/use-contact-us-modal'
|
import { useContactUsModal } from '../../../shared/hooks/use-contact-us-modal'
|
||||||
import LeftMenuButton from './left-menu-button'
|
import LeftMenuButton from './left-menu-button'
|
||||||
|
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
export default function HelpContactUs() {
|
export default function HelpContactUs() {
|
||||||
const { modal, showModal } = useContactUsModal()
|
const { modal, showModal } = useContactUsModal()
|
||||||
|
@ -17,10 +18,10 @@ export default function HelpContactUs() {
|
||||||
<>
|
<>
|
||||||
<LeftMenuButton
|
<LeftMenuButton
|
||||||
onClick={showModalWithAnalytics}
|
onClick={showModalWithAnalytics}
|
||||||
icon={{
|
icon={bsVersionIcon({
|
||||||
type: 'question',
|
bs5: { type: 'contact_support' },
|
||||||
fw: true,
|
bs3: { type: 'question', fw: true },
|
||||||
}}
|
})}
|
||||||
>
|
>
|
||||||
{t('contact_us')}
|
{t('contact_us')}
|
||||||
</LeftMenuButton>
|
</LeftMenuButton>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import LeftMenuButton from './left-menu-button'
|
import LeftMenuButton from './left-menu-button'
|
||||||
|
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
export default function HelpDocumentation() {
|
export default function HelpDocumentation() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
@ -9,10 +10,10 @@ export default function HelpDocumentation() {
|
||||||
<LeftMenuButton
|
<LeftMenuButton
|
||||||
type="link"
|
type="link"
|
||||||
href="/learn"
|
href="/learn"
|
||||||
icon={{
|
icon={bsVersionIcon({
|
||||||
type: 'book',
|
bs5: { type: 'book_4' },
|
||||||
fw: true,
|
bs3: { type: 'book', fw: true },
|
||||||
}}
|
})}
|
||||||
>
|
>
|
||||||
{t('documentation')}
|
{t('documentation')}
|
||||||
</LeftMenuButton>
|
</LeftMenuButton>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||||
import { useProjectContext } from '../../../shared/context/project-context'
|
import { useProjectContext } from '../../../shared/context/project-context'
|
||||||
import HotkeysModal from '../../hotkeys-modal/components/hotkeys-modal'
|
import HotkeysModal from '../../hotkeys-modal/components/hotkeys-modal'
|
||||||
import LeftMenuButton from './left-menu-button'
|
import LeftMenuButton from './left-menu-button'
|
||||||
|
import { bsVersionIcon } from '@/features/utils/bootstrap-5'
|
||||||
|
|
||||||
export default function HelpShowHotkeys() {
|
export default function HelpShowHotkeys() {
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
@ -20,10 +21,10 @@ export default function HelpShowHotkeys() {
|
||||||
<>
|
<>
|
||||||
<LeftMenuButton
|
<LeftMenuButton
|
||||||
onClick={showModalWithAnalytics}
|
onClick={showModalWithAnalytics}
|
||||||
icon={{
|
icon={bsVersionIcon({
|
||||||
type: 'keyboard-o',
|
bs5: { type: 'keyboard' },
|
||||||
fw: true,
|
bs3: { type: 'keyboard-o', fw: true },
|
||||||
}}
|
})}
|
||||||
>
|
>
|
||||||
{t('show_hotkeys')}
|
{t('show_hotkeys')}
|
||||||
</LeftMenuButton>
|
</LeftMenuButton>
|
||||||
|
|
|
@ -1,20 +1,43 @@
|
||||||
import { PropsWithChildren } from 'react'
|
import { PropsWithChildren } from 'react'
|
||||||
import Icon from '../../../shared/components/icon'
|
import Icon from '../../../shared/components/icon'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import MaterialIcon from '@/shared/components/material-icon'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
icon: {
|
icon?: {
|
||||||
type: string
|
type: string
|
||||||
fw?: boolean
|
fw?: boolean
|
||||||
}
|
}
|
||||||
|
svgIcon?: React.ReactElement | null
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
disabledAccesibilityText?: string
|
disabledAccesibilityText?: string
|
||||||
type?: 'button' | 'link'
|
type?: 'button' | 'link'
|
||||||
href?: string
|
href?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LeftMenuButtonIcon({
|
||||||
|
svgIcon,
|
||||||
|
icon,
|
||||||
|
}: {
|
||||||
|
svgIcon?: React.ReactElement | null
|
||||||
|
icon?: { type: string; fw?: boolean }
|
||||||
|
}) {
|
||||||
|
if (svgIcon) {
|
||||||
|
return <div className="material-symbols">{svgIcon}</div>
|
||||||
|
} else if (icon) {
|
||||||
|
return (
|
||||||
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type={icon.type} fw={icon.fw ?? false} />}
|
||||||
|
bs5={<MaterialIcon type={icon.type} />}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} else return null
|
||||||
|
}
|
||||||
|
|
||||||
export default function LeftMenuButton({
|
export default function LeftMenuButton({
|
||||||
children,
|
children,
|
||||||
|
svgIcon,
|
||||||
onClick,
|
onClick,
|
||||||
icon,
|
icon,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
@ -25,7 +48,7 @@ export default function LeftMenuButton({
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return (
|
return (
|
||||||
<div className="left-menu-button link-disabled">
|
<div className="left-menu-button link-disabled">
|
||||||
<Icon type={icon.type} fw={icon.fw} />
|
<LeftMenuButtonIcon svgIcon={svgIcon} icon={icon} />
|
||||||
<span>{children}</span>
|
<span>{children}</span>
|
||||||
{disabledAccesibilityText ? (
|
{disabledAccesibilityText ? (
|
||||||
<span className="sr-only">{disabledAccesibilityText}</span>
|
<span className="sr-only">{disabledAccesibilityText}</span>
|
||||||
|
@ -37,7 +60,7 @@ export default function LeftMenuButton({
|
||||||
if (type === 'button') {
|
if (type === 'button') {
|
||||||
return (
|
return (
|
||||||
<button onClick={onClick} className="left-menu-button">
|
<button onClick={onClick} className="left-menu-button">
|
||||||
<Icon type={icon.type} fw={icon.fw} />
|
<LeftMenuButtonIcon svgIcon={svgIcon} icon={icon} />
|
||||||
<span>{children}</span>
|
<span>{children}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
@ -49,7 +72,7 @@ export default function LeftMenuButton({
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="left-menu-button"
|
className="left-menu-button"
|
||||||
>
|
>
|
||||||
<Icon type={icon.type} fw={icon.fw} />
|
<LeftMenuButtonIcon svgIcon={svgIcon} icon={icon} />
|
||||||
<span>{children}</span>
|
<span>{children}</span>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Form } from 'react-bootstrap'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import getMeta from '../../../utils/meta'
|
import getMeta from '../../../utils/meta'
|
||||||
import SettingsAutoCloseBrackets from './settings/settings-auto-close-brackets'
|
import SettingsAutoCloseBrackets from './settings/settings-auto-close-brackets'
|
||||||
|
@ -20,6 +19,7 @@ import SettingsMathPreview from './settings/settings-math-preview'
|
||||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||||
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
||||||
import { ElementType } from 'react'
|
import { ElementType } from 'react'
|
||||||
|
import OLForm from '@/features/ui/components/ol/ol-form'
|
||||||
|
|
||||||
const moduleSettings: Array<{
|
const moduleSettings: Array<{
|
||||||
import: { default: ElementType }
|
import: { default: ElementType }
|
||||||
|
@ -38,7 +38,7 @@ export default function SettingsMenu() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h4>{t('settings')}</h4>
|
<h4>{t('settings')}</h4>
|
||||||
<Form className="settings">
|
<OLForm id="left-menu-setting" className="settings">
|
||||||
<SettingsCompiler />
|
<SettingsCompiler />
|
||||||
<SettingsImageName />
|
<SettingsImageName />
|
||||||
<SettingsDocument />
|
<SettingsDocument />
|
||||||
|
@ -58,7 +58,7 @@ export default function SettingsMenu() {
|
||||||
<SettingsFontFamily />
|
<SettingsFontFamily />
|
||||||
<SettingsLineHeight />
|
<SettingsLineHeight />
|
||||||
<SettingsPdfViewer />
|
<SettingsPdfViewer />
|
||||||
</Form>
|
</OLForm>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,31 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import DictionaryModal from '../../../dictionary/components/dictionary-modal'
|
import DictionaryModal from '../../../dictionary/components/dictionary-modal'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||||
|
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
||||||
|
|
||||||
export default function SettingsDictionary() {
|
export default function SettingsDictionary() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-group left-menu-setting">
|
<OLFormGroup className="left-menu-setting">
|
||||||
<label htmlFor="dictionary">{t('dictionary')}</label>
|
<OLFormLabel htmlFor="dictionary-settings">{t('dictionary')}</OLFormLabel>
|
||||||
<Button
|
<OLButton
|
||||||
className="btn-secondary"
|
id="dictionary-settings"
|
||||||
bsSize="xs"
|
variant="secondary"
|
||||||
bsStyle={null}
|
size="sm"
|
||||||
onClick={() => setShowModal(true)}
|
onClick={() => setShowModal(true)}
|
||||||
|
bs3Props={{ bsSize: 'xsmall' }}
|
||||||
>
|
>
|
||||||
{t('edit')}
|
{t('edit')}
|
||||||
</Button>
|
</OLButton>
|
||||||
|
|
||||||
<DictionaryModal
|
<DictionaryModal
|
||||||
show={showModal}
|
show={showModal}
|
||||||
handleHide={() => setShowModal(false)}
|
handleHide={() => setShowModal(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</OLFormGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||||
|
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
||||||
|
import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
|
||||||
import { ChangeEventHandler, useCallback } from 'react'
|
import { ChangeEventHandler, useCallback } from 'react'
|
||||||
|
import { Spinner } from 'react-bootstrap-5'
|
||||||
|
|
||||||
type PossibleValue = string | number | boolean
|
type PossibleValue = string | number | boolean
|
||||||
|
|
||||||
|
@ -50,16 +55,32 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-group left-menu-setting">
|
<OLFormGroup
|
||||||
<label htmlFor={`settings-menu-${name}`}>{label}</label>
|
controlId={`settings-menu-${name}`}
|
||||||
|
className="left-menu-setting"
|
||||||
|
>
|
||||||
|
<OLFormLabel>{label}</OLFormLabel>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="loading pull-right">
|
<BootstrapVersionSwitcher
|
||||||
<i className="fa fa-fw fa-spin fa-refresh" />
|
bs3={
|
||||||
</p>
|
<p className="loading pull-right">
|
||||||
|
<i className="fa fa-fw fa-spin fa-refresh" />
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
bs5={
|
||||||
|
<p className="mb-0">
|
||||||
|
<Spinner
|
||||||
|
animation="border"
|
||||||
|
aria-hidden="true"
|
||||||
|
size="sm"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<select
|
<OLFormSelect
|
||||||
id={`settings-menu-${name}`}
|
size="sm"
|
||||||
className="form-control"
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
value={value?.toString()}
|
value={value?.toString()}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -86,8 +107,8 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
|
||||||
))}
|
))}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
) : null}
|
) : null}
|
||||||
</select>
|
</OLFormSelect>
|
||||||
)}
|
)}
|
||||||
</div>
|
</OLFormGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
import { Button, Modal, Row, Col } from 'react-bootstrap'
|
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import AccessibleModal from '../../../shared/components/accessible-modal'
|
|
||||||
import HotkeysModalBottomText from './hotkeys-modal-bottom-text'
|
import HotkeysModalBottomText from './hotkeys-modal-bottom-text'
|
||||||
|
import OLModal, {
|
||||||
|
OLModalBody,
|
||||||
|
OLModalFooter,
|
||||||
|
OLModalHeader,
|
||||||
|
OLModalTitle,
|
||||||
|
} from '@/features/ui/components/ol/ol-modal'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||||
|
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||||
|
|
||||||
export default function HotkeysModal({
|
export default function HotkeysModal({
|
||||||
animation = true,
|
animation = true,
|
||||||
|
@ -16,21 +23,16 @@ export default function HotkeysModal({
|
||||||
const ctrl = isMac ? 'Cmd' : 'Ctrl'
|
const ctrl = isMac ? 'Cmd' : 'Ctrl'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleModal
|
<OLModal size="lg" onHide={handleHide} show={show} animation={animation}>
|
||||||
bsSize="large"
|
<OLModalHeader closeButton>
|
||||||
onHide={handleHide}
|
<OLModalTitle>{t('hotkeys')}</OLModalTitle>
|
||||||
show={show}
|
</OLModalHeader>
|
||||||
animation={animation}
|
|
||||||
>
|
|
||||||
<Modal.Header closeButton>
|
|
||||||
<Modal.Title>{t('hotkeys')}</Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
|
|
||||||
<Modal.Body className="hotkeys-modal">
|
<OLModalBody className="hotkeys-modal">
|
||||||
<h3>{t('common')}</h3>
|
<h3>{t('common')}</h3>
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + F`}
|
combination={`${ctrl} + F`}
|
||||||
description={t('hotkey_find_and_replace')}
|
description={t('hotkey_find_and_replace')}
|
||||||
|
@ -39,48 +41,48 @@ export default function HotkeysModal({
|
||||||
combination={`${ctrl} + Enter`}
|
combination={`${ctrl} + Enter`}
|
||||||
description={t('hotkey_compile')}
|
description={t('hotkey_compile')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + Z`}
|
combination={`${ctrl} + Z`}
|
||||||
description={t('hotkey_undo')}
|
description={t('hotkey_undo')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + Y`}
|
combination={`${ctrl} + Y`}
|
||||||
description={t('hotkey_redo')}
|
description={t('hotkey_redo')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
|
|
||||||
<h3>{t('navigation')}</h3>
|
<h3>{t('navigation')}</h3>
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + Home`}
|
combination={`${ctrl} + Home`}
|
||||||
description={t('hotkey_beginning_of_document')}
|
description={t('hotkey_beginning_of_document')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + End`}
|
combination={`${ctrl} + End`}
|
||||||
description={t('hotkey_end_of_document')}
|
description={t('hotkey_end_of_document')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + Shift + L`}
|
combination={`${ctrl} + Shift + L`}
|
||||||
description={t('hotkey_go_to_line')}
|
description={t('hotkey_go_to_line')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
|
|
||||||
<h3>{t('editing')}</h3>
|
<h3>{t('editing')}</h3>
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + /`}
|
combination={`${ctrl} + /`}
|
||||||
description={t('hotkey_toggle_comment')}
|
description={t('hotkey_toggle_comment')}
|
||||||
|
@ -93,9 +95,9 @@ export default function HotkeysModal({
|
||||||
combination={`${ctrl} + A`}
|
combination={`${ctrl} + A`}
|
||||||
description={t('hotkey_select_all')}
|
description={t('hotkey_select_all')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
|
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination="Ctrl + U"
|
combination="Ctrl + U"
|
||||||
description={t('hotkey_to_uppercase')}
|
description={t('hotkey_to_uppercase')}
|
||||||
|
@ -108,9 +110,9 @@ export default function HotkeysModal({
|
||||||
combination="Tab"
|
combination="Tab"
|
||||||
description={t('hotkey_indent_selection')}
|
description={t('hotkey_indent_selection')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
|
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + B`}
|
combination={`${ctrl} + B`}
|
||||||
description={t('hotkey_bold_text')}
|
description={t('hotkey_bold_text')}
|
||||||
|
@ -119,31 +121,31 @@ export default function HotkeysModal({
|
||||||
combination={`${ctrl} + I`}
|
combination={`${ctrl} + I`}
|
||||||
description={t('hotkey_italic_text')}
|
description={t('hotkey_italic_text')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
|
|
||||||
<h3>{t('autocomplete')}</h3>
|
<h3>{t('autocomplete')}</h3>
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination="Ctrl + Space"
|
combination="Ctrl + Space"
|
||||||
description={t('hotkey_autocomplete_menu')}
|
description={t('hotkey_autocomplete_menu')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination="Up / Down"
|
combination="Up / Down"
|
||||||
description={t('hotkey_select_candidate')}
|
description={t('hotkey_select_candidate')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination="Enter / Tab"
|
combination="Enter / Tab"
|
||||||
description={t('hotkey_insert_candidate')}
|
description={t('hotkey_insert_candidate')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
<Trans
|
<Trans
|
||||||
|
@ -152,50 +154,50 @@ export default function HotkeysModal({
|
||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`Ctrl + Space `}
|
combination={`Ctrl + Space `}
|
||||||
description={t('hotkey_search_references')}
|
description={t('hotkey_search_references')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
|
|
||||||
{trackChangesVisible && (
|
{trackChangesVisible && (
|
||||||
<>
|
<>
|
||||||
<h3>{t('review')}</h3>
|
<h3>{t('review')}</h3>
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + J`}
|
combination={`${ctrl} + J`}
|
||||||
description={t('hotkey_toggle_review_panel')}
|
description={t('hotkey_toggle_review_panel')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + Shift + A`}
|
combination={`${ctrl} + Shift + A`}
|
||||||
description={t('hotkey_toggle_track_changes')}
|
description={t('hotkey_toggle_track_changes')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<Hotkey
|
<Hotkey
|
||||||
combination={`${ctrl} + Shift + C`}
|
combination={`${ctrl} + Shift + C`}
|
||||||
description={t('hotkey_add_a_comment')}
|
description={t('hotkey_add_a_comment')}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<HotkeysModalBottomText />
|
<HotkeysModalBottomText />
|
||||||
</Modal.Body>
|
</OLModalBody>
|
||||||
|
|
||||||
<Modal.Footer>
|
<OLModalFooter>
|
||||||
<Button bsStyle={null} className="btn-secondary" onClick={handleHide}>
|
<OLButton variant="secondary" onClick={handleHide}>
|
||||||
{t('close')}
|
{t('close')}
|
||||||
</Button>
|
</OLButton>
|
||||||
</Modal.Footer>
|
</OLModalFooter>
|
||||||
</AccessibleModal>
|
</OLModal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ export function bs3ButtonProps(props: ButtonProps) {
|
||||||
disabled: props.isLoading || props.disabled,
|
disabled: props.isLoading || props.disabled,
|
||||||
form: props.form,
|
form: props.form,
|
||||||
href: props.href,
|
href: props.href,
|
||||||
|
id: props.id,
|
||||||
target: props.target,
|
target: props.target,
|
||||||
rel: props.rel,
|
rel: props.rel,
|
||||||
onClick: props.onClick,
|
onClick: props.onClick,
|
||||||
|
|
|
@ -9,6 +9,7 @@ export type ButtonProps = {
|
||||||
form?: string
|
form?: string
|
||||||
leadingIcon?: string | React.ReactNode
|
leadingIcon?: string | React.ReactNode
|
||||||
href?: string
|
href?: string
|
||||||
|
id?: string
|
||||||
target?: string
|
target?: string
|
||||||
rel?: string
|
rel?: string
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
|
|
|
@ -12,6 +12,16 @@ export function bsVersion({ bs5, bs3 }: { bs5?: unknown; bs3?: unknown }) {
|
||||||
return isBootstrap5() ? bs5 : bs3
|
return isBootstrap5() ? bs5 : bs3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const bsVersionIcon = ({
|
||||||
|
bs5,
|
||||||
|
bs3,
|
||||||
|
}: {
|
||||||
|
bs5?: { type: string }
|
||||||
|
bs3?: { type: string; fw?: boolean }
|
||||||
|
}) => {
|
||||||
|
return isBootstrap5() ? bs5 : bs3
|
||||||
|
}
|
||||||
|
|
||||||
// get all `aria-*` and `data-*` attributes
|
// get all `aria-*` and `data-*` attributes
|
||||||
export const getAriaAndDataProps = (obj: Record<string, unknown>) => {
|
export const getAriaAndDataProps = (obj: Record<string, unknown>) => {
|
||||||
return Object.entries(obj).reduce(
|
return Object.entries(obj).reduce(
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Alert, Button, Modal, Row, Col, Grid } from 'react-bootstrap'
|
|
||||||
import { useIdeContext } from '../../../shared/context/ide-context'
|
import { useIdeContext } from '../../../shared/context/ide-context'
|
||||||
import { useProjectContext } from '../../../shared/context/project-context'
|
import { useProjectContext } from '../../../shared/context/project-context'
|
||||||
import { useWordCount } from '../hooks/use-word-count'
|
import { useWordCount } from '../hooks/use-word-count'
|
||||||
import Icon from '../../../shared/components/icon'
|
import {
|
||||||
|
OLModalBody,
|
||||||
|
OLModalFooter,
|
||||||
|
OLModalHeader,
|
||||||
|
OLModalTitle,
|
||||||
|
} from '@/features/ui/components/ol/ol-modal'
|
||||||
|
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||||
|
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||||
|
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||||
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||||
|
import Icon from '@/shared/components/icon'
|
||||||
|
import { Spinner } from 'react-bootstrap-5'
|
||||||
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
|
|
||||||
// NOTE: this component is only mounted when the modal is open
|
// NOTE: this component is only mounted when the modal is open
|
||||||
export default function WordCountModalContent({ handleHide }) {
|
export default function WordCountModalContent({ handleHide }) {
|
||||||
|
@ -15,71 +26,87 @@ export default function WordCountModalContent({ handleHide }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal.Header closeButton>
|
<OLModalHeader closeButton>
|
||||||
<Modal.Title>{t('word_count')}</Modal.Title>
|
<OLModalTitle>{t('word_count')}</OLModalTitle>
|
||||||
</Modal.Header>
|
</OLModalHeader>
|
||||||
|
|
||||||
<Modal.Body>
|
<OLModalBody>
|
||||||
{loading && !error && (
|
{loading && !error && (
|
||||||
<div className="loading">
|
<div className="loading">
|
||||||
<Icon type="refresh" spin fw />
|
<BootstrapVersionSwitcher
|
||||||
|
bs3={<Icon type="refresh" spin fw />}
|
||||||
|
bs5={
|
||||||
|
<Spinner
|
||||||
|
animation="border"
|
||||||
|
aria-hidden="true"
|
||||||
|
size="sm"
|
||||||
|
role="status"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{t('loading')}…
|
{t('loading')}…
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<Alert bsStyle="danger">{t('generic_something_went_wrong')}</Alert>
|
<OLNotification
|
||||||
|
type="error"
|
||||||
|
content={t('generic_something_went_wrong')}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{data && (
|
{data && (
|
||||||
<Grid fluid>
|
<div className="container-fluid">
|
||||||
{data.messages && (
|
{data.messages && (
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={12}>
|
<OLCol xs={12}>
|
||||||
<Alert bsStyle="danger">
|
<OLNotification
|
||||||
<p style={{ whiteSpace: 'pre-wrap' }}>{data.messages}</p>
|
type="error"
|
||||||
</Alert>
|
content={
|
||||||
</Col>
|
<p style={{ whiteSpace: 'pre-wrap' }}>{data.messages}</p>
|
||||||
</Row>
|
}
|
||||||
|
/>
|
||||||
|
</OLCol>
|
||||||
|
</OLRow>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<div className="pull-right">{t('total_words')}:</div>
|
<div className="pull-right">{t('total_words')}:</div>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={6}>{data.textWords}</Col>
|
<OLCol xs={6}>{data.textWords}</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<div className="pull-right">{t('headers')}:</div>
|
<div className="pull-right">{t('headers')}:</div>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={6}>{data.headers}</Col>
|
<OLCol xs={6}>{data.headers}</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<div className="pull-right">{t('math_inline')}:</div>
|
<div className="pull-right">{t('math_inline')}:</div>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={6}>{data.mathInline}</Col>
|
<OLCol xs={6}>{data.mathInline}</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
|
|
||||||
<Row>
|
<OLRow>
|
||||||
<Col xs={4}>
|
<OLCol xs={4}>
|
||||||
<div className="pull-right">{t('math_display')}:</div>
|
<div className="pull-right">{t('math_display')}:</div>
|
||||||
</Col>
|
</OLCol>
|
||||||
<Col xs={6}>{data.mathDisplay}</Col>
|
<OLCol xs={6}>{data.mathDisplay}</OLCol>
|
||||||
</Row>
|
</OLRow>
|
||||||
</Grid>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Modal.Body>
|
</OLModalBody>
|
||||||
|
|
||||||
<Modal.Footer>
|
<OLModalFooter>
|
||||||
<Button bsStyle={null} className="btn-secondary" onClick={handleHide}>
|
<OLButton variant="secondary" onClick={handleHide}>
|
||||||
{t('close')}
|
{t('close')}
|
||||||
</Button>
|
</OLButton>
|
||||||
</Modal.Footer>
|
</OLModalFooter>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import WordCountModalContent from './word-count-modal-content'
|
import WordCountModalContent from './word-count-modal-content'
|
||||||
import AccessibleModal from '../../../shared/components/accessible-modal'
|
|
||||||
import withErrorBoundary from '../../../infrastructure/error-boundary'
|
import withErrorBoundary from '../../../infrastructure/error-boundary'
|
||||||
|
import OLModal from '@/features/ui/components/ol/ol-modal'
|
||||||
|
|
||||||
const WordCountModal = React.memo(function WordCountModal({
|
const WordCountModal = React.memo(function WordCountModal({
|
||||||
show,
|
show,
|
||||||
handleHide,
|
handleHide,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<AccessibleModal
|
<OLModal animation show={show} onHide={handleHide} id="word-count-modal">
|
||||||
animation
|
|
||||||
show={show}
|
|
||||||
onHide={handleHide}
|
|
||||||
id="word-count-modal"
|
|
||||||
>
|
|
||||||
<WordCountModalContent handleHide={handleHide} />
|
<WordCountModalContent handleHide={handleHide} />
|
||||||
</AccessibleModal>
|
</OLModal>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,18 @@ import Icon from './icon'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||||
import { Spinner } from 'react-bootstrap-5'
|
import { Spinner } from 'react-bootstrap-5'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
function LoadingSpinner({
|
function LoadingSpinner({
|
||||||
|
align,
|
||||||
delay = 0,
|
delay = 0,
|
||||||
loadingText,
|
loadingText,
|
||||||
|
size,
|
||||||
}: {
|
}: {
|
||||||
|
align?: 'left' | 'center'
|
||||||
delay?: 0 | 500 // 500 is our standard delay
|
delay?: 0 | 500 // 500 is our standard delay
|
||||||
loadingText?: string
|
loadingText?: string
|
||||||
|
size?: 'sm'
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
@ -29,6 +34,9 @@ function LoadingSpinner({
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const alignmentClass =
|
||||||
|
align === 'left' ? 'align-items-start' : 'align-items-center'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BootstrapVersionSwitcher
|
<BootstrapVersionSwitcher
|
||||||
bs3={
|
bs3={
|
||||||
|
@ -39,12 +47,13 @@ function LoadingSpinner({
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
bs5={
|
bs5={
|
||||||
<div className="text-center mt-4">
|
<div className={classNames(`d-flex ${alignmentClass}`)}>
|
||||||
<Spinner
|
<Spinner
|
||||||
animation="border"
|
animation="border"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
role="status"
|
role="status"
|
||||||
className="align-middle"
|
className="align-self-center"
|
||||||
|
size={size}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loadingText || t('loading')}…
|
{loadingText || t('loading')}…
|
||||||
|
|
|
@ -6,6 +6,7 @@ type IconProps = React.ComponentProps<'i'> & {
|
||||||
type: string
|
type: string
|
||||||
accessibilityLabel?: string
|
accessibilityLabel?: string
|
||||||
modifier?: string
|
modifier?: string
|
||||||
|
size?: '2x'
|
||||||
}
|
}
|
||||||
|
|
||||||
function MaterialIcon({
|
function MaterialIcon({
|
||||||
|
@ -13,9 +14,12 @@ function MaterialIcon({
|
||||||
className,
|
className,
|
||||||
accessibilityLabel,
|
accessibilityLabel,
|
||||||
modifier,
|
modifier,
|
||||||
|
size,
|
||||||
...rest
|
...rest
|
||||||
}: IconProps) {
|
}: IconProps) {
|
||||||
const iconClassName = classNames('material-symbols', className, modifier)
|
const iconClassName = classNames('material-symbols', className, modifier, {
|
||||||
|
[`size-${size}`]: size,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
46
services/web/frontend/js/shared/svgs/dropbox-logo-black.tsx
Normal file
46
services/web/frontend/js/shared/svgs/dropbox-logo-black.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
function DropboxlLogoBlack() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clipPath="url(#clip0_5804_191)">
|
||||||
|
<path
|
||||||
|
d="M5.41651 2.16667L0.833374 5.08661L5.41651 8.00655L10.0004 5.08661L5.41651 2.16667Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M14.5836 2.16667L10.0005 5.08661L14.5836 8.00655L19.1668 5.08661L14.5836 2.16667Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0.833374 10.9265L5.41651 13.8464L10.0004 10.9265L5.41651 8.00655L0.833374 10.9265Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M14.5836 8.00655L10.0005 10.9265L14.5836 13.8464L19.1668 10.9265L14.5836 8.00655Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.4165 14.8198L10.0004 17.7397L14.5836 14.8198L10.0004 11.8998L5.4165 14.8198Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_5804_191">
|
||||||
|
<rect
|
||||||
|
width="18.3333"
|
||||||
|
height="15.573"
|
||||||
|
fill="white"
|
||||||
|
transform="translate(0.833374 2.16667)"
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DropboxlLogoBlack
|
30
services/web/frontend/js/shared/svgs/git-fork.tsx
Normal file
30
services/web/frontend/js/shared/svgs/git-fork.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
function GitFork() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clipPath="url(#clip0_5804_193)">
|
||||||
|
<path
|
||||||
|
d="M18.6495 9.20689L10.795 1.35338C10.3431 0.901045 9.60947 0.901045 9.15698 1.35338L7.52609 2.98443L9.59482 5.05317C10.0758 4.89079 10.627 4.99966 11.0102 5.38292C11.3953 5.76856 11.5034 6.32451 11.337 6.80705L13.3309 8.80094C13.8133 8.63471 14.3698 8.74211 14.755 9.12798C15.2935 9.66633 15.2935 10.5387 14.755 11.0772C14.2164 11.6159 13.3441 11.6159 12.8052 11.0772C12.4002 10.672 12.3001 10.077 12.5052 9.5781L10.6457 7.71858L10.6455 12.6119C10.7768 12.6769 10.9007 12.7636 11.0101 12.8726C11.5485 13.4109 11.5485 14.2831 11.0101 14.8222C10.4716 15.3605 9.59883 15.3605 9.06088 14.8222C8.52245 14.2831 8.52245 13.4109 9.06088 12.8726C9.19395 12.7397 9.34792 12.6392 9.51221 12.5718V7.63301C9.34784 7.56591 9.19405 7.46612 9.06079 7.33222C8.65295 6.9247 8.55472 6.32612 8.76388 5.82529L6.72452 3.78561L1.33936 9.17061C0.88688 9.62329 0.88688 10.3569 1.33936 10.8092L9.19336 18.6628C9.64554 19.1151 10.3789 19.1151 10.8317 18.6628L18.6494 10.8463C19.1017 10.3938 19.1017 9.65999 18.6494 9.20756"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_5804_193">
|
||||||
|
<rect
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
fill="white"
|
||||||
|
transform="translate(1 1)"
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GitFork
|
48
services/web/frontend/js/shared/svgs/github-logo-black.tsx
Normal file
48
services/web/frontend/js/shared/svgs/github-logo-black.tsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
function GithubLogoBlack() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M9.8325 2C5.23119 2 1.5 5.73036 1.5 10.3325C1.5 14.0139 3.88752 17.1373 7.19827 18.239C7.6147 18.3162 7.76759 18.0583 7.76759 17.8382C7.76759 17.6396 7.75986 16.9831 7.75627 16.2869C5.43816 16.7909 4.94902 15.3037 4.94902 15.3037C4.56998 14.3408 4.02384 14.0845 4.02384 14.0845C3.26783 13.5674 4.08083 13.578 4.08083 13.578C4.91756 13.6368 5.35813 14.4368 5.35813 14.4368C6.10131 15.7105 7.30742 15.3422 7.7829 15.1293C7.85783 14.5909 8.07363 14.2232 8.31193 14.0151C6.46117 13.8046 4.51561 13.0899 4.51561 9.89716C4.51561 8.98758 4.84111 8.24413 5.37414 7.6606C5.28763 7.45073 5.00242 6.60324 5.455 5.4555C5.455 5.4555 6.15471 5.23169 7.74689 6.30975C8.41155 6.12513 9.12437 6.03254 9.8325 6.02923C10.5406 6.03254 11.254 6.12513 11.9199 6.30975C13.5103 5.23169 14.209 5.4555 14.209 5.4555C14.6626 6.60324 14.3774 7.45073 14.2909 7.6606C14.8251 8.24413 15.1483 8.98758 15.1483 9.89716C15.1483 13.0977 13.199 13.8022 11.3435 14.0085C11.6424 14.2671 11.9087 14.7742 11.9087 15.5516C11.9087 16.6665 11.8992 17.5638 11.8992 17.8382C11.8992 18.0601 12.0491 18.3198 12.4714 18.2381C15.7805 17.135 18.165 14.0128 18.165 10.3325C18.165 5.73036 14.4344 2 9.8325 2Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4.65591 13.9635C4.63756 14.0051 4.57243 14.0175 4.51309 13.9891C4.45252 13.9617 4.41871 13.9053 4.43831 13.8638C4.45625 13.8213 4.52137 13.8094 4.58181 13.8378C4.64225 13.865 4.67674 13.922 4.65591 13.9635Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M4.99353 14.34C4.95379 14.3769 4.87597 14.3598 4.8234 14.3015C4.76875 14.2434 4.75868 14.1656 4.79897 14.1284C4.83995 14.0915 4.91529 14.1088 4.9698 14.1669C5.02444 14.2256 5.03506 14.3026 4.99353 14.34Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.32207 14.8199C5.27102 14.8555 5.1874 14.8222 5.13579 14.7481C5.08474 14.6739 5.08474 14.585 5.13703 14.5494C5.18864 14.5138 5.27102 14.546 5.32331 14.6194C5.37423 14.6947 5.37423 14.7836 5.32207 14.8199Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.77213 15.2835C5.72646 15.3339 5.62918 15.3204 5.55798 15.2515C5.48512 15.1845 5.46484 15.0891 5.51051 15.0388C5.55688 14.9883 5.65471 15.0025 5.72646 15.0706C5.79876 15.1377 5.8207 15.2337 5.77213 15.2835Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.3931 15.5528C6.37282 15.618 6.27927 15.6475 6.18489 15.6198C6.09064 15.5913 6.02897 15.5149 6.04801 15.449C6.0676 15.3833 6.16171 15.3524 6.25678 15.3821C6.35088 15.4105 6.4127 15.4864 6.3931 15.5528Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.075 15.6026C7.07735 15.6713 6.99732 15.7283 6.89824 15.7296C6.79862 15.7318 6.7179 15.6762 6.7168 15.6086C6.7168 15.5392 6.79517 15.4829 6.89466 15.4811C6.99373 15.4791 7.075 15.5345 7.075 15.6026Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.70952 15.4947C7.72138 15.5616 7.65253 15.6305 7.55415 15.6488C7.45742 15.6666 7.36787 15.6251 7.35559 15.5587C7.34358 15.49 7.41354 15.4211 7.51027 15.4033C7.60879 15.3862 7.69696 15.4265 7.70952 15.4947Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GithubLogoBlack
|
|
@ -247,12 +247,8 @@ $dropdown-item-padding-x: var(--spacing-04);
|
||||||
$dropdown-item-padding-y: var(--spacing-05);
|
$dropdown-item-padding-y: var(--spacing-05);
|
||||||
$dropdown-header-color: var(--content-secondary);
|
$dropdown-header-color: var(--content-secondary);
|
||||||
|
|
||||||
// List group
|
// Offcanvas
|
||||||
$list-group-color: var(--content-secondary);
|
$offcanvas-horizontal-width: 320px;
|
||||||
$list-group-border-width: 0;
|
$offcanvas-padding-x: 12.5px;
|
||||||
$list-group-border-radius: (--border-radius-base);
|
$offcanvas-padding-y: 12.5px;
|
||||||
$list-group-item-padding-y: var(--spacing-04);
|
$offcanvas-border-width: 0;
|
||||||
$list-group-item-padding-x: var(--spacing-05);
|
|
||||||
$list-group-hover-bg: var(--bg-light-secondary);
|
|
||||||
$list-group-disabled-color: var(--content-disabled);
|
|
||||||
$list-group-disabled-bg: var(--bg-light-primary);
|
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
@import 'bootstrap-5/scss/close';
|
@import 'bootstrap-5/scss/close';
|
||||||
@import 'bootstrap-5/scss/nav';
|
@import 'bootstrap-5/scss/nav';
|
||||||
@import 'bootstrap-5/scss/navbar';
|
@import 'bootstrap-5/scss/navbar';
|
||||||
|
@import 'bootstrap-5/scss/offcanvas';
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
@import 'bootstrap-5/scss/helpers';
|
@import 'bootstrap-5/scss/helpers';
|
||||||
|
|
|
@ -54,7 +54,3 @@ hr {
|
||||||
.row-spaced-extra-large {
|
.row-spaced-extra-large {
|
||||||
margin-top: calc(var(--line-height-03) * 4);
|
margin-top: calc(var(--line-height-03) * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rotate-180 {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
|
@ -78,6 +78,10 @@ samp {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: var(--content-disabled) !important;
|
||||||
|
}
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
@include media-breakpoint-up(lg) {
|
||||||
.text-center-only-desktop {
|
.text-center-only-desktop {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
@import 'beta-badges';
|
@import 'beta-badges';
|
||||||
@import 'list-group';
|
@import 'list-group';
|
||||||
@import 'select';
|
@import 'select';
|
||||||
|
@import 'dictionary';
|
||||||
@import 'link';
|
@import 'link';
|
||||||
@import 'pagination';
|
@import 'pagination';
|
||||||
@import 'loading-spinner';
|
@import 'loading-spinner';
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
#dictionary-modal {
|
||||||
|
.modal-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dictionary-entries-list {
|
||||||
|
overflow-y: scroll;
|
||||||
|
max-height: calc(100vh - 225px);
|
||||||
|
margin: 0;
|
||||||
|
padding: var(--spacing-04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dictionary-entry {
|
||||||
|
word-break: break-all;
|
||||||
|
display: flex;
|
||||||
|
padding: var(--spacing-04);
|
||||||
|
border-bottom: solid 1px var(--border-divider);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dictionary-entry-name {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-right: var(--spacing-02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dictionary-empty-body {
|
||||||
|
padding: var(--spacing-07);
|
||||||
|
}
|
|
@ -6,12 +6,14 @@
|
||||||
@import 'editor/ide';
|
@import 'editor/ide';
|
||||||
@import 'editor/toolbar';
|
@import 'editor/toolbar';
|
||||||
@import 'editor/online-users';
|
@import 'editor/online-users';
|
||||||
@import 'editor/review-panel';
|
@import 'editor/hotkeys';
|
||||||
|
@import 'editor/left-menu';
|
||||||
@import 'editor/loading-screen';
|
@import 'editor/loading-screen';
|
||||||
@import 'editor/outline';
|
@import 'editor/outline';
|
||||||
@import 'editor/file-tree';
|
@import 'editor/file-tree';
|
||||||
@import 'editor/file-view';
|
@import 'editor/file-view';
|
||||||
@import 'editor/figure-modal';
|
@import 'editor/figure-modal';
|
||||||
|
@import 'editor/review-panel';
|
||||||
@import 'editor/chat';
|
@import 'editor/chat';
|
||||||
@import 'subscription';
|
@import 'subscription';
|
||||||
@import 'editor/pdf';
|
@import 'editor/pdf';
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
.sales-contact-form-left-column {
|
.sales-contact-form-left-column {
|
||||||
.sales-contact-form-heading-title {
|
.sales-contact-form-heading-title {
|
||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
font-family: var(-font-family-san-serif);
|
font-family: var(--font-family-san-serif);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.333;
|
line-height: 1.333;
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
.hotkeys-modal {
|
||||||
|
font-size: var(--font-size-02);
|
||||||
|
|
||||||
|
h3:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotkey {
|
||||||
|
margin-bottom: var(--spacing-05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combination {
|
||||||
|
padding: var(--spacing-02) var(--spacing-04);
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: var(--bg-dark-tertiary);
|
||||||
|
color: var(--white);
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: var(--spacing-03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotkeys-modal-bottom-text {
|
||||||
|
background-color: var(--bg-light-secondary);
|
||||||
|
padding: var(--spacing-04);
|
||||||
|
border-radius: var(--border-radius-base);
|
||||||
|
}
|
||||||
|
}
|
|
@ -193,3 +193,31 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.teaser-title {
|
||||||
|
margin-top: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teaser-refresh-label {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teaser-img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: var(--spacing-03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.teaser-video-container {
|
||||||
|
margin: calc(var(--spacing-07) * -1) calc(var(--spacing-07) * -1)
|
||||||
|
var(--spacing-02) calc(var(--spacing-07) * -1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teaser-video {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-bottom: 1px solid var(--border-divider);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,247 @@
|
||||||
|
:root {
|
||||||
|
--left-menu-form-select-border: var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include theme('light') {
|
||||||
|
--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: 320px;
|
||||||
|
|
||||||
|
&.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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
> a {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
padding: var(--spacing-04) var(--spacing-06);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: var(--bg-info-01);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled state sets text to gray and nukes hover/tab effects
|
||||||
|
&.disabled > a {
|
||||||
|
color: var(--content-disabled);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: var(--content-disabled);
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open dropdowns
|
||||||
|
.open > a {
|
||||||
|
&,
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--bg-info-01);
|
||||||
|
border-color: var(--link-ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-size-loading-spinner-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reference-manager-search-group {
|
||||||
|
width: '100%';
|
||||||
|
border-radius: 0;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -486,7 +486,7 @@ describe('<EditorLeftMenu />', function () {
|
||||||
</EditorProviders>
|
</EditorProviders>
|
||||||
)
|
)
|
||||||
|
|
||||||
cy.get('label[for="dictionary"] ~ button').click()
|
cy.get('label[for="dictionary-settings"] ~ button').click()
|
||||||
cy.findByText('Edit Dictionary')
|
cy.findByText('Edit Dictionary')
|
||||||
cy.findByText('Your custom dictionary is empty.')
|
cy.findByText('Your custom dictionary is empty.')
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe('<SettingsDictionary />', function () {
|
||||||
|
|
||||||
screen.getByText('Dictionary')
|
screen.getByText('Dictionary')
|
||||||
|
|
||||||
const button = screen.getByRole('button', { name: 'Edit' })
|
const button = screen.getByText('Edit')
|
||||||
fireEvent.click(button)
|
fireEvent.click(button)
|
||||||
|
|
||||||
const modal = screen.getAllByRole('dialog')[0]
|
const modal = screen.getAllByRole('dialog')[0]
|
||||||
|
|
Loading…
Reference in a new issue