Show warning if note is longer than configured maximum length (#534)

* Add maximum document length config option

* Show remaining characters in tooltip of status-bar length-info

* Remove unnecessary checkDocumentLength function

* Add max-length warning

* Update translation wording

* Set dialog to medium size

* Add coloring to status-bar length info

* Improve wording in warning modal

* Add cypress e2e tests

I included the cypress-commands package and set the language level to ES6 to allow easier testing e.g. of element attributes.

* Changed way how the modal-advice was styled and positioned

* Show warning modal only on first length exceeding

* Improved length tooltip by adding messages when exceeding or reaching limit
This commit is contained in:
Erik Michelson 2020-09-05 16:36:46 +02:00 committed by GitHub
parent 14dfb5f315
commit 79469c5ddc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 151 additions and 19 deletions

View file

@ -0,0 +1,26 @@
import React from 'react'
import { Button, Modal } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { CommonModal } from '../../common/modals/common-modal'
export interface MaxLengthWarningModalProps {
show: boolean
onHide: () => void
maxLength: number
}
export const MaxLengthWarningModal: React.FC<MaxLengthWarningModalProps> = ({ show, onHide, maxLength }) => {
useTranslation()
return (
<CommonModal show={show} onHide={onHide} titleI18nKey={'editor.error.limitReached.title'} closeButton={true}>
<Modal.Body className={'limit-warning'}>
<Trans i18nKey={'editor.error.limitReached.description'} values={{ maxLength }} />
<strong className='mt-2 d-block'><Trans i18nKey={'editor.error.limitReached.advice'}/></strong>
</Modal.Body>
<Modal.Footer>
<Button onClick={onHide}><Trans i18nKey={'common.close'}/></Button>
</Modal.Footer>
</CommonModal>
)
}

View file

@ -23,6 +23,9 @@ import 'codemirror/mode/gfm/gfm'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { ApplicationState } from '../../../redux'
import { MaxLengthWarningModal } from '../editor-modals/max-length-warning-modal'
import { ScrollProps, ScrollState } from '../scroll/scroll-props'
import { allHinters, findWordAtCursor } from './autocompletion'
import './editor-pane.scss'
@ -52,6 +55,9 @@ const onChange = (editor: Editor) => {
export const EditorPane: React.FC<EditorPaneProps & ScrollProps> = ({ onContentChange, content, scrollState, onScroll, onMakeScrollSource }) => {
const { t } = useTranslation()
const maxLength = useSelector((state: ApplicationState) => state.config.maxDocumentLength)
const [showMaxLengthWarning, setShowMaxLengthWarning] = useState(false)
const maxLengthWarningAlreadyShown = useRef(false)
const [editor, setEditor] = useState<Editor>()
const [statusBarInfo, setStatusBarInfo] = useState<StatusBarInfo>(defaultState)
const [editorPreferences, setEditorPreferences] = useState<EditorConfiguration>({
@ -99,15 +105,24 @@ export const EditorPane: React.FC<EditorPaneProps & ScrollProps> = ({ onContentC
}, [editor, scrollState])
const onBeforeChange = useCallback((editor: Editor, data: EditorChange, value: string) => {
if (value.length > maxLength && !maxLengthWarningAlreadyShown.current) {
setShowMaxLengthWarning(true)
maxLengthWarningAlreadyShown.current = true
}
if (value.length <= maxLength) {
maxLengthWarningAlreadyShown.current = false
}
onContentChange(value)
}, [onContentChange])
}, [onContentChange, maxLength, maxLengthWarningAlreadyShown])
const onEditorDidMount = useCallback(mountedEditor => {
setStatusBarInfo(createStatusInfo(mountedEditor))
setStatusBarInfo(createStatusInfo(mountedEditor, maxLength))
setEditor(mountedEditor)
}, [])
}, [maxLength])
const onCursorActivity = useCallback((editorWithActivity) => {
setStatusBarInfo(createStatusInfo(editorWithActivity))
}, [])
setStatusBarInfo(createStatusInfo(editorWithActivity, maxLength))
}, [maxLength])
const codeMirrorOptions: EditorConfiguration = useMemo<EditorConfiguration>(() => ({
...editorPreferences,
mode: 'gfm',
@ -140,6 +155,7 @@ export const EditorPane: React.FC<EditorPaneProps & ScrollProps> = ({ onContentC
return (
<div className={'d-flex flex-column h-100'} onMouseEnter={onMakeScrollSource}>
<MaxLengthWarningModal show={showMaxLengthWarning} onHide={() => setShowMaxLengthWarning(false)} maxLength={maxLength}/>
<ToolBar
editor={editor}
onPreferencesChange={config => setEditorPreferences(config)}

View file

@ -1,5 +1,5 @@
import { Editor, Position } from 'codemirror'
import React from 'react'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { ShowIf } from '../../../common/show-if/show-if'
import './status-bar.scss'
@ -10,6 +10,7 @@ export interface StatusBarInfo {
selectedLines: number
linesInDocument: number
charactersInDocument: number
remainingCharacters: number
}
export const defaultState: StatusBarInfo = {
@ -17,20 +18,32 @@ export const defaultState: StatusBarInfo = {
selectedColumns: 0,
selectedLines: 0,
linesInDocument: 0,
charactersInDocument: 0
charactersInDocument: 0,
remainingCharacters: 0
}
export const createStatusInfo = (editor: Editor): StatusBarInfo => ({
export const createStatusInfo = (editor: Editor, maxDocumentLength: number): StatusBarInfo => ({
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
})
export const StatusBar: React.FC<StatusBarInfo> = ({ position, selectedColumns, selectedLines, charactersInDocument, linesInDocument }) => {
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])
return (
<div className="d-flex flex-row status-bar px-2">
<div>
@ -46,7 +59,13 @@ export const StatusBar: React.FC<StatusBarInfo> = ({ position, selectedColumns,
</div>
<div className="ml-auto">
<span>{t('editor.statusBar.lines', { lines: linesInDocument })}</span>
<span title={t('editor.statusBar.lengthTooltip')}>&nbsp;&nbsp;{t('editor.statusBar.length', { length: charactersInDocument })}</span>
&nbsp;&nbsp;
<span
title={getLengthTooltip}
className={remainingCharacters <= 0 ? 'text-danger' : remainingCharacters <= 100 ? 'text-warning' : ''}
>
{t('editor.statusBar.length', { length: charactersInDocument })}
</span>
</div>
</div>
)