mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-20 02:05:21 -04:00
Restructure the max-length-warning and the status bar (#1654)
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
7182f22e52
commit
4023acc9d6
10 changed files with 291 additions and 92 deletions
|
@ -5,14 +5,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Editor, EditorChange } from 'codemirror'
|
import type { Editor, EditorChange } from 'codemirror'
|
||||||
import React, { useCallback, useRef, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
|
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
|
||||||
import { MaxLengthWarningModal } from '../editor-modals/max-length-warning-modal'
|
|
||||||
import type { ScrollProps } from '../synced-scroll/scroll-props'
|
import type { ScrollProps } from '../synced-scroll/scroll-props'
|
||||||
import { allHinters, findWordAtCursor } from './autocompletion'
|
import { allHinters, findWordAtCursor } from './autocompletion'
|
||||||
import './editor-pane.scss'
|
import './editor-pane.scss'
|
||||||
import type { StatusBarInfo } from './status-bar/status-bar'
|
import { StatusBar } from './status-bar/status-bar'
|
||||||
import { createStatusInfo, defaultState, StatusBar } from './status-bar/status-bar'
|
|
||||||
import { ToolBar } from './tool-bar/tool-bar'
|
import { ToolBar } from './tool-bar/tool-bar'
|
||||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
import './codemirror-imports'
|
import './codemirror-imports'
|
||||||
|
@ -23,6 +21,8 @@ import { useOnEditorPasteCallback } from './hooks/use-on-editor-paste-callback'
|
||||||
import { useOnEditorFileDrop } from './hooks/use-on-editor-file-drop'
|
import { useOnEditorFileDrop } from './hooks/use-on-editor-file-drop'
|
||||||
import { useOnEditorScroll } from './hooks/use-on-editor-scroll'
|
import { useOnEditorScroll } from './hooks/use-on-editor-scroll'
|
||||||
import { useApplyScrollState } from './hooks/use-apply-scroll-state'
|
import { useApplyScrollState } from './hooks/use-apply-scroll-state'
|
||||||
|
import { MaxLengthWarning } from './max-length-warning/max-length-warning'
|
||||||
|
import { useCreateStatusBarInfo } from './hooks/use-create-status-bar-info'
|
||||||
|
|
||||||
const onChange = (editor: Editor) => {
|
const onChange = (editor: Editor) => {
|
||||||
for (const hinter of allHinters) {
|
for (const hinter of allHinters) {
|
||||||
|
@ -41,53 +41,34 @@ const onChange = (editor: Editor) => {
|
||||||
|
|
||||||
export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMakeScrollSource }) => {
|
export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMakeScrollSource }) => {
|
||||||
const markdownContent = useNoteMarkdownContent()
|
const markdownContent = useNoteMarkdownContent()
|
||||||
const maxLength = useApplicationState((state) => state.config.maxDocumentLength)
|
|
||||||
const [showMaxLengthWarning, setShowMaxLengthWarning] = useState(false)
|
|
||||||
const maxLengthWarningAlreadyShown = useRef(false)
|
|
||||||
const [editor, setEditor] = useState<Editor>()
|
const [editor, setEditor] = useState<Editor>()
|
||||||
const [statusBarInfo, setStatusBarInfo] = useState<StatusBarInfo>(defaultState)
|
|
||||||
const ligaturesEnabled = useApplicationState((state) => state.editorConfig.ligatures)
|
const ligaturesEnabled = useApplicationState((state) => state.editorConfig.ligatures)
|
||||||
|
|
||||||
const onPaste = useOnEditorPasteCallback()
|
const onPaste = useOnEditorPasteCallback()
|
||||||
const onEditorScroll = useOnEditorScroll(onScroll)
|
const onEditorScroll = useOnEditorScroll(onScroll)
|
||||||
useApplyScrollState(editor, scrollState)
|
useApplyScrollState(editor, scrollState)
|
||||||
|
|
||||||
const onBeforeChange = useCallback(
|
const onBeforeChange = useCallback((editor: Editor, data: EditorChange, value: string) => {
|
||||||
(editor: Editor, data: EditorChange, value: string) => {
|
setNoteContent(value)
|
||||||
if (value.length > maxLength && !maxLengthWarningAlreadyShown.current) {
|
}, [])
|
||||||
setShowMaxLengthWarning(true)
|
|
||||||
maxLengthWarningAlreadyShown.current = true
|
const [statusBarInfo, updateStatusBarInfo] = useCreateStatusBarInfo()
|
||||||
}
|
|
||||||
if (value.length <= maxLength) {
|
|
||||||
maxLengthWarningAlreadyShown.current = false
|
|
||||||
}
|
|
||||||
setNoteContent(value)
|
|
||||||
},
|
|
||||||
[maxLength]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onEditorDidMount = useCallback(
|
const onEditorDidMount = useCallback(
|
||||||
(mountedEditor: Editor) => {
|
(mountedEditor: Editor) => {
|
||||||
setStatusBarInfo(createStatusInfo(mountedEditor, maxLength))
|
updateStatusBarInfo(mountedEditor)
|
||||||
setEditor(mountedEditor)
|
setEditor(mountedEditor)
|
||||||
},
|
},
|
||||||
[maxLength]
|
[updateStatusBarInfo]
|
||||||
)
|
|
||||||
|
|
||||||
const onCursorActivity = useCallback(
|
|
||||||
(editorWithActivity: Editor) => {
|
|
||||||
setStatusBarInfo(createStatusInfo(editorWithActivity, maxLength))
|
|
||||||
},
|
|
||||||
[maxLength]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const onDrop = useOnEditorFileDrop()
|
const onDrop = useOnEditorFileDrop()
|
||||||
const onMaxLengthHide = useCallback(() => setShowMaxLengthWarning(false), [])
|
|
||||||
const codeMirrorOptions = useCodeMirrorOptions()
|
const codeMirrorOptions = useCodeMirrorOptions()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'d-flex flex-column h-100 position-relative'} onMouseEnter={onMakeScrollSource}>
|
<div className={'d-flex flex-column h-100 position-relative'} onMouseEnter={onMakeScrollSource}>
|
||||||
<MaxLengthWarningModal show={showMaxLengthWarning} onHide={onMaxLengthHide} maxLength={maxLength} />
|
<MaxLengthWarning />
|
||||||
<ToolBar editor={editor} />
|
<ToolBar editor={editor} />
|
||||||
<ControlledCodeMirror
|
<ControlledCodeMirror
|
||||||
className={`overflow-hidden w-100 flex-fill ${ligaturesEnabled ? '' : 'no-ligatures'}`}
|
className={`overflow-hidden w-100 flex-fill ${ligaturesEnabled ? '' : 'no-ligatures'}`}
|
||||||
|
@ -96,12 +77,12 @@ export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMak
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onPaste={onPaste}
|
onPaste={onPaste}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
onCursorActivity={onCursorActivity}
|
onCursorActivity={updateStatusBarInfo}
|
||||||
editorDidMount={onEditorDidMount}
|
editorDidMount={onEditorDidMount}
|
||||||
onBeforeChange={onBeforeChange}
|
onBeforeChange={onBeforeChange}
|
||||||
onScroll={onEditorScroll}
|
onScroll={onEditorScroll}
|
||||||
/>
|
/>
|
||||||
<StatusBar {...statusBarInfo} />
|
<StatusBar statusBarInfo={statusBarInfo} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { StatusBarInfo } from '../status-bar/status-bar'
|
||||||
|
import { defaultState } from '../status-bar/status-bar'
|
||||||
|
import type { Editor } from 'codemirror'
|
||||||
|
import { useCallback, useState } from 'react'
|
||||||
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a {@link StatusBarInfo} object and a function that can update this object using a {@link CodeMirror code mirror instance}.
|
||||||
|
*/
|
||||||
|
export const useCreateStatusBarInfo = (): [
|
||||||
|
statusBarInfo: StatusBarInfo,
|
||||||
|
updateStatusBarInfo: (editor: Editor) => void
|
||||||
|
] => {
|
||||||
|
const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength)
|
||||||
|
const [statusBarInfo, setStatusBarInfo] = useState(defaultState)
|
||||||
|
|
||||||
|
const updateStatusBarInfo = useCallback(
|
||||||
|
(editor: Editor): void => {
|
||||||
|
setStatusBarInfo({
|
||||||
|
position: editor.getCursor(),
|
||||||
|
charactersInDocument: editor.getValue().length,
|
||||||
|
remainingCharacters: maxDocumentLength - editor.getValue().length,
|
||||||
|
linesInDocument: editor.lineCount(),
|
||||||
|
selectedColumns: editor.getSelection().length,
|
||||||
|
selectedLines: editor.getSelection().split('\n').length
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[maxDocumentLength]
|
||||||
|
)
|
||||||
|
|
||||||
|
return [statusBarInfo, updateStatusBarInfo]
|
||||||
|
}
|
|
@ -7,16 +7,20 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Button, Modal } from 'react-bootstrap'
|
import { Button, Modal } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import type { ModalVisibilityProps } from '../../common/modals/common-modal'
|
import type { ModalVisibilityProps } from '../../../common/modals/common-modal'
|
||||||
import { CommonModal } from '../../common/modals/common-modal'
|
import { CommonModal } from '../../../common/modals/common-modal'
|
||||||
import { cypressId } from '../../../utils/cypress-attribute'
|
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||||
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
export interface MaxLengthWarningModalProps extends ModalVisibilityProps {
|
/**
|
||||||
maxLength: number
|
* Shows a modal that informs the user that the document is too long.
|
||||||
}
|
*
|
||||||
|
* @param show is {@code true} if the modal should be shown
|
||||||
export const MaxLengthWarningModal: React.FC<MaxLengthWarningModalProps> = ({ show, onHide, maxLength }) => {
|
* @param onHide gets called if the modal was closed
|
||||||
|
*/
|
||||||
|
export const MaxLengthWarningModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonModal
|
<CommonModal
|
||||||
|
@ -26,7 +30,7 @@ export const MaxLengthWarningModal: React.FC<MaxLengthWarningModalProps> = ({ sh
|
||||||
title={'editor.error.limitReached.title'}
|
title={'editor.error.limitReached.title'}
|
||||||
showCloseButton={true}>
|
showCloseButton={true}>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Trans i18nKey={'editor.error.limitReached.description'} values={{ maxLength }} />
|
<Trans i18nKey={'editor.error.limitReached.description'} values={{ maxDocumentLength }} />
|
||||||
<strong className='mt-2 d-block'>
|
<strong className='mt-2 d-block'>
|
||||||
<Trans i18nKey={'editor.error.limitReached.advice'} />
|
<Trans i18nKey={'editor.error.limitReached.advice'} />
|
||||||
</strong>
|
</strong>
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import { MaxLengthWarningModal } from './max-length-warning-modal'
|
||||||
|
import { useNoteMarkdownContent } from '../../../../hooks/common/use-note-markdown-content'
|
||||||
|
import { useApplicationState } from '../../../../hooks/common/use-application-state'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watches the length of the document and shows a warning modal to the user if the document length exceeds the configured value.
|
||||||
|
*/
|
||||||
|
export const MaxLengthWarning: React.FC = () => {
|
||||||
|
const [showMaxLengthWarningModal, setShowMaxLengthWarningModal] = useState(false)
|
||||||
|
const maxLengthWarningAlreadyShown = useRef(false)
|
||||||
|
const maxDocumentLength = useApplicationState((state) => state.config.maxDocumentLength)
|
||||||
|
const hideWarning = useCallback(() => setShowMaxLengthWarningModal(false), [])
|
||||||
|
const markdownContent = useNoteMarkdownContent()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (markdownContent.length > maxDocumentLength && !maxLengthWarningAlreadyShown.current) {
|
||||||
|
setShowMaxLengthWarningModal(true)
|
||||||
|
maxLengthWarningAlreadyShown.current = true
|
||||||
|
}
|
||||||
|
if (markdownContent.length <= maxDocumentLength) {
|
||||||
|
maxLengthWarningAlreadyShown.current = false
|
||||||
|
}
|
||||||
|
}, [markdownContent, maxDocumentLength])
|
||||||
|
|
||||||
|
return <MaxLengthWarningModal show={showMaxLengthWarningModal} onHide={hideWarning} />
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import { Trans } from 'react-i18next'
|
||||||
|
import type { Position } from 'codemirror'
|
||||||
|
|
||||||
|
export interface CursorPositionInfoProps {
|
||||||
|
cursorPosition: Position
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a translated text that shows the given cursor position.
|
||||||
|
*
|
||||||
|
* @param cursorPosition The cursor position that should be included
|
||||||
|
*/
|
||||||
|
export const CursorPositionInfo: React.FC<CursorPositionInfoProps> = ({ cursorPosition }) => {
|
||||||
|
const translationOptions = useMemo(
|
||||||
|
() => ({
|
||||||
|
line: cursorPosition.line + 1,
|
||||||
|
columns: cursorPosition.ch + 1
|
||||||
|
}),
|
||||||
|
[cursorPosition.ch, cursorPosition.line]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Trans i18nKey={'editor.statusBar.cursor'} values={translationOptions} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export interface LinesInDocumentInfoProps {
|
||||||
|
numberOfLinesInDocument: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a translated text that shows the number of lines in the document.
|
||||||
|
*
|
||||||
|
* @param linesInDocument The number of lines in the document
|
||||||
|
*/
|
||||||
|
export const NumberOfLinesInDocumentInfo: React.FC<LinesInDocumentInfoProps> = ({ numberOfLinesInDocument }) => {
|
||||||
|
useTranslation()
|
||||||
|
|
||||||
|
const translationOptions = useMemo(() => ({ lines: numberOfLinesInDocument }), [numberOfLinesInDocument])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Trans i18nKey={'editor.statusBar.lines'} values={translationOptions} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export interface LengthInfoProps {
|
||||||
|
remainingCharacters: number
|
||||||
|
charactersInDocument: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a translated text that shows the number of remaining characters.
|
||||||
|
*
|
||||||
|
* @param remainingCharacters The number of characters that are still available in this document
|
||||||
|
* @param charactersInDocument The total number of characters in the document
|
||||||
|
*/
|
||||||
|
export const RemainingCharactersInfo: React.FC<LengthInfoProps> = ({ remainingCharacters, charactersInDocument }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const remainingCharactersClass = useMemo(() => {
|
||||||
|
if (remainingCharacters <= 0) {
|
||||||
|
return 'text-danger'
|
||||||
|
} else if (remainingCharacters <= 100) {
|
||||||
|
return 'text-warning'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}, [remainingCharacters])
|
||||||
|
|
||||||
|
const lengthTooltip = useMemo(() => {
|
||||||
|
if (remainingCharacters === 0) {
|
||||||
|
return t('editor.statusBar.lengthTooltip.maximumReached')
|
||||||
|
} else if (remainingCharacters < 0) {
|
||||||
|
return t('editor.statusBar.lengthTooltip.exceeded', { exceeded: -remainingCharacters })
|
||||||
|
} else {
|
||||||
|
return t('editor.statusBar.lengthTooltip.remaining', { remaining: remainingCharacters })
|
||||||
|
}
|
||||||
|
}, [remainingCharacters, t])
|
||||||
|
|
||||||
|
const translationOptions = useMemo(() => ({ length: charactersInDocument }), [charactersInDocument])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span {...cypressId('remainingCharacters')} title={lengthTooltip} className={remainingCharactersClass}>
|
||||||
|
<Trans i18nKey={'editor.statusBar.length'} values={translationOptions} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo } from 'react'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export interface SelectionInfoProps {
|
||||||
|
count: number
|
||||||
|
translationKey: 'column' | 'line'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a translated text that shows the number of selected columns or lines.
|
||||||
|
*
|
||||||
|
* @param count The number that should be included in the text
|
||||||
|
* @param translationKey Defines if the text for selected columns or lines should be used
|
||||||
|
*/
|
||||||
|
export const SelectionInfo: React.FC<SelectionInfoProps> = ({ count, translationKey }) => {
|
||||||
|
useTranslation()
|
||||||
|
|
||||||
|
const countTranslationOptions = useMemo(() => ({ count: count }), [count])
|
||||||
|
|
||||||
|
console.log(count, translationKey)
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Trans i18nKey={`editor.statusBar.selection.${translationKey}`} values={countTranslationOptions} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Fragment } from 'react'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a dash without breaking spaces around.
|
||||||
|
*/
|
||||||
|
export const SeparatorDash: React.FC = () => {
|
||||||
|
return <Fragment> – </Fragment>
|
||||||
|
}
|
|
@ -4,12 +4,15 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Editor, Position } from 'codemirror'
|
import type { Position } from 'codemirror'
|
||||||
import React, { useMemo } from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
|
||||||
import './status-bar.scss'
|
import './status-bar.scss'
|
||||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
import { RemainingCharactersInfo } from './remaining-characters-info'
|
||||||
|
import { NumberOfLinesInDocumentInfo } from './number-of-lines-in-document-info'
|
||||||
|
import { CursorPositionInfo } from './cursor-position-info'
|
||||||
|
import { SelectionInfo } from './selection-info'
|
||||||
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
|
import { SeparatorDash } from './separator-dash'
|
||||||
|
|
||||||
export interface StatusBarInfo {
|
export interface StatusBarInfo {
|
||||||
position: Position
|
position: Position
|
||||||
|
@ -29,57 +32,36 @@ export const defaultState: StatusBarInfo = {
|
||||||
remainingCharacters: 0
|
remainingCharacters: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createStatusInfo = (editor: Editor, maxDocumentLength: number): StatusBarInfo => ({
|
export interface StatusBarProps {
|
||||||
position: editor.getCursor(),
|
statusBarInfo: StatusBarInfo
|
||||||
charactersInDocument: editor.getValue().length,
|
}
|
||||||
remainingCharacters: maxDocumentLength - editor.getValue().length,
|
|
||||||
linesInDocument: editor.lineCount(),
|
|
||||||
selectedColumns: editor.getSelection().length,
|
|
||||||
selectedLines: editor.getSelection().split('\n').length
|
|
||||||
})
|
|
||||||
|
|
||||||
export const StatusBar: React.FC<StatusBarInfo> = ({
|
|
||||||
position,
|
|
||||||
selectedColumns,
|
|
||||||
selectedLines,
|
|
||||||
charactersInDocument,
|
|
||||||
linesInDocument,
|
|
||||||
remainingCharacters
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const getLengthTooltip = useMemo(() => {
|
|
||||||
if (remainingCharacters === 0) {
|
|
||||||
return t('editor.statusBar.lengthTooltip.maximumReached')
|
|
||||||
}
|
|
||||||
if (remainingCharacters < 0) {
|
|
||||||
return t('editor.statusBar.lengthTooltip.exceeded', { exceeded: -remainingCharacters })
|
|
||||||
}
|
|
||||||
return t('editor.statusBar.lengthTooltip.remaining', { remaining: remainingCharacters })
|
|
||||||
}, [remainingCharacters, t])
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows additional information about the document length and the current selection.
|
||||||
|
*
|
||||||
|
* @param statusBarInfo The information to show
|
||||||
|
*/
|
||||||
|
export const StatusBar: React.FC<StatusBarProps> = ({ statusBarInfo }) => {
|
||||||
return (
|
return (
|
||||||
<div className='d-flex flex-row status-bar px-2'>
|
<div className='d-flex flex-row status-bar px-2'>
|
||||||
<div>
|
<div>
|
||||||
<span>{t('editor.statusBar.cursor', { line: position.line + 1, columns: position.ch + 1 })}</span>
|
<CursorPositionInfo cursorPosition={statusBarInfo.position} />
|
||||||
<ShowIf condition={selectedColumns !== 0 && selectedLines !== 0}>
|
<ShowIf condition={statusBarInfo.selectedLines === 1 && statusBarInfo.selectedColumns > 0}>
|
||||||
<ShowIf condition={selectedLines === 1}>
|
<SeparatorDash />
|
||||||
<span> – {t('editor.statusBar.selection.column', { count: selectedColumns })}</span>
|
<SelectionInfo count={statusBarInfo.selectedColumns} translationKey={'column'} />
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
<ShowIf condition={selectedLines > 1}>
|
<ShowIf condition={statusBarInfo.selectedLines > 1}>
|
||||||
<span> – {t('editor.statusBar.selection.line', { count: selectedLines })}</span>
|
<SeparatorDash />
|
||||||
</ShowIf>
|
<SelectionInfo count={statusBarInfo.selectedLines} translationKey={'line'} />
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
</div>
|
</div>
|
||||||
<div className='ml-auto'>
|
<div className='ml-auto'>
|
||||||
<span>{t('editor.statusBar.lines', { lines: linesInDocument })}</span>
|
<NumberOfLinesInDocumentInfo numberOfLinesInDocument={statusBarInfo.linesInDocument} />
|
||||||
–
|
<SeparatorDash />
|
||||||
<span
|
<RemainingCharactersInfo
|
||||||
{...cypressId('remainingCharacters')}
|
remainingCharacters={statusBarInfo.remainingCharacters}
|
||||||
title={getLengthTooltip}
|
charactersInDocument={statusBarInfo.charactersInDocument}
|
||||||
className={remainingCharacters <= 0 ? 'text-danger' : remainingCharacters <= 100 ? 'text-warning' : ''}>
|
/>
|
||||||
{t('editor.statusBar.length', { length: charactersInDocument })}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue