mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-05 17:14:40 -04:00
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:
parent
14dfb5f315
commit
79469c5ddc
14 changed files with 151 additions and 19 deletions
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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)}
|
||||
|
|
|
@ -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')}> – {t('editor.statusBar.length', { length: charactersInDocument })}</span>
|
||||
–
|
||||
<span
|
||||
title={getLengthTooltip}
|
||||
className={remainingCharacters <= 0 ? 'text-danger' : remainingCharacters <= 100 ? 'text-warning' : ''}
|
||||
>
|
||||
{t('editor.statusBar.length', { length: charactersInDocument })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue