Upgrade to CodeMirror 6 (#1787)

Upgrade to CodeMirror 6

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-02-13 12:14:01 +01:00 committed by GitHub
parent 1a09bfa5f1
commit 6a6f6105b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
103 changed files with 1906 additions and 2615 deletions

View file

@ -1,47 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { EditorConfiguration } from 'codemirror'
import type { ChangeEvent } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
import type { EditorPreferenceProperty } from './editor-preference-property'
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
export interface EditorPreferenceBooleanProps {
property: EditorPreferenceProperty
}
export const EditorPreferenceBooleanProperty: React.FC<EditorPreferenceBooleanProps> = ({ property }) => {
const preference = useApplicationState((state) => state.editorConfig.preferences[property]?.toString() ?? '')
const { t } = useTranslation()
const selectItem = useCallback(
(event: ChangeEvent<HTMLSelectElement>) => {
const selectedItem: boolean = event.target.value === 'true'
mergeEditorPreferences({
[property]: selectedItem
} as EditorConfiguration)
},
[property]
)
const i18nPrefix = `editor.modal.preferences.${property}`
return (
<EditorPreferenceInput
onChange={selectItem}
property={property}
type={EditorPreferenceInputType.SELECT}
value={preference}>
<option value={'true'}>{t(`${i18nPrefix}.on`)}</option>
<option value={'false'}>{t(`${i18nPrefix}.off`)}</option>
</EditorPreferenceInput>
)
}

View file

@ -1,48 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react'
import { Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
export enum EditorPreferenceInputType {
SELECT,
BOOLEAN,
NUMBER
}
export interface EditorPreferenceInputProps {
property: string
type: EditorPreferenceInputType
onChange: React.ChangeEventHandler<HTMLSelectElement>
value?: string | number | string[]
}
export const EditorPreferenceInput: React.FC<EditorPreferenceInputProps> = ({
property,
type,
onChange,
value,
children
}) => {
useTranslation()
return (
<Form.Group controlId={`editor-pref-${property}`}>
<Form.Label>
<Trans
i18nKey={`editor.modal.preferences.${property}${type === EditorPreferenceInputType.NUMBER ? '' : '.label'}`}
/>
</Form.Label>
<Form.Control
as={type === EditorPreferenceInputType.NUMBER ? 'input' : 'select'}
size='sm'
value={value}
onChange={onChange}
type={type === EditorPreferenceInputType.NUMBER ? 'number' : ''}>
{children}
</Form.Control>
</Form.Group>
)
}

View file

@ -1,31 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ChangeEvent } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { setEditorLigatures } from '../../../../../redux/editor/methods'
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
export const EditorPreferenceLigaturesSelect: React.FC = () => {
const ligaturesEnabled = useApplicationState((state) => Boolean(state.editorConfig.ligatures).toString())
const saveLigatures = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
const ligaturesActivated: boolean = event.target.value === 'true'
setEditorLigatures(ligaturesActivated)
}, [])
const { t } = useTranslation()
return (
<EditorPreferenceInput
onChange={saveLigatures}
value={ligaturesEnabled}
property={'ligatures'}
type={EditorPreferenceInputType.BOOLEAN}>
<option value='true'>{t(`common.yes`)}</option>
<option value='false'>{t(`common.no`)}</option>
</EditorPreferenceInput>
)
}

View file

@ -1,41 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { EditorConfiguration } from 'codemirror'
import type { ChangeEvent } from 'react'
import React, { useCallback } from 'react'
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
import type { EditorPreferenceProperty } from './editor-preference-property'
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
export interface EditorPreferenceNumberProps {
property: EditorPreferenceProperty
}
export const EditorPreferenceNumberProperty: React.FC<EditorPreferenceNumberProps> = ({ property }) => {
const preference = useApplicationState((state) => state.editorConfig.preferences[property]?.toString() ?? '')
const selectItem = useCallback(
(event: ChangeEvent<HTMLSelectElement>) => {
const selectedItem: number = Number.parseInt(event.target.value)
mergeEditorPreferences({
[property]: selectedItem
} as EditorConfiguration)
},
[property]
)
return (
<EditorPreferenceInput
onChange={selectItem}
property={property}
type={EditorPreferenceInputType.NUMBER}
value={preference}
/>
)
}

View file

@ -1,13 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export enum EditorPreferenceProperty {
KEYMAP = 'keyMap',
THEME = 'theme',
INDENT_WITH_TABS = 'indentWithTabs',
INDENT_UNIT = 'indentUnit',
SPELL_CHECK = 'spellcheck'
}

View file

@ -1,55 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { EditorConfiguration } from 'codemirror'
import type { ChangeEvent } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
import type { EditorPreferenceProperty } from './editor-preference-property'
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
export interface EditorPreferenceSelectPropertyProps {
property: EditorPreferenceProperty
selections: string[]
}
export const EditorPreferenceSelectProperty: React.FC<EditorPreferenceSelectPropertyProps> = ({
property,
selections
}) => {
const preference = useApplicationState((state) => state.editorConfig.preferences[property]?.toString() ?? '')
const { t } = useTranslation()
const selectItem = useCallback(
(event: ChangeEvent<HTMLSelectElement>) => {
const selectedItem: string = event.target.value
mergeEditorPreferences({
[property]: selectedItem
} as EditorConfiguration)
},
[property]
)
const i18nPrefix = `editor.modal.preferences.${property}`
return (
<EditorPreferenceInput
onChange={selectItem}
property={property}
type={EditorPreferenceInputType.SELECT}
value={preference}>
{selections.map((selection) => (
<option key={selection} value={selection}>
{t(`${i18nPrefix}.${selection}`)}
</option>
))}
</EditorPreferenceInput>
)
}

View file

@ -1,31 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ChangeEvent } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
import { setEditorSmartPaste } from '../../../../../redux/editor/methods'
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
export const EditorPreferenceSmartPasteSelect: React.FC = () => {
const smartPasteEnabled = useApplicationState((state) => Boolean(state.editorConfig.smartPaste).toString())
const saveSmartPaste = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
const smartPasteActivated: boolean = event.target.value === 'true'
setEditorSmartPaste(smartPasteActivated)
}, [])
const { t } = useTranslation()
return (
<EditorPreferenceInput
onChange={saveSmartPaste}
value={smartPasteEnabled}
property={'smartPaste'}
type={EditorPreferenceInputType.BOOLEAN}>
<option value='true'>{t(`common.yes`)}</option>
<option value='false'>{t(`common.no`)}</option>
</EditorPreferenceInput>
)
}

View file

@ -1,80 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { Fragment, useState } from 'react'
import { Button, Form, ListGroup } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
import { CommonModal } from '../../../../common/modals/common-modal'
import { ShowIf } from '../../../../common/show-if/show-if'
import { EditorPreferenceBooleanProperty } from './editor-preference-boolean-property'
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
import { EditorPreferenceLigaturesSelect } from './editor-preference-ligatures-select'
import { EditorPreferenceNumberProperty } from './editor-preference-number-property'
import { EditorPreferenceProperty } from './editor-preference-property'
import { EditorPreferenceSelectProperty } from './editor-preference-select-property'
import { EditorPreferenceSmartPasteSelect } from './editor-preference-smart-paste-select'
import { useApplicationState } from '../../../../../hooks/common/use-application-state'
export const EditorPreferences: React.FC = () => {
const { t } = useTranslation()
const [showModal, setShowModal] = useState(false)
const indentWithTabs = useApplicationState((state) => state.editorConfig.preferences.indentWithTabs ?? false)
return (
<Fragment>
<Button variant='light' onClick={() => setShowModal(true)} title={t('editor.editorToolbar.preferences')}>
<ForkAwesomeIcon icon='wrench' />
</Button>
<CommonModal
show={showModal}
onHide={() => setShowModal(false)}
title={'editor.modal.preferences.title'}
showCloseButton={true}
titleIcon={'wrench'}>
<Form>
<ListGroup>
<ListGroup.Item>
<EditorPreferenceSelectProperty
property={EditorPreferenceProperty.THEME}
selections={['one-dark', 'neat']}
/>
</ListGroup.Item>
<ListGroup.Item>
<EditorPreferenceSelectProperty
property={EditorPreferenceProperty.KEYMAP}
selections={['sublime', 'emacs', 'vim']}
/>
</ListGroup.Item>
<ListGroup.Item>
<EditorPreferenceBooleanProperty property={EditorPreferenceProperty.INDENT_WITH_TABS} />
</ListGroup.Item>
<ShowIf condition={!indentWithTabs}>
<ListGroup.Item>
<EditorPreferenceNumberProperty property={EditorPreferenceProperty.INDENT_UNIT} />
</ListGroup.Item>
</ShowIf>
<ListGroup.Item>
<EditorPreferenceLigaturesSelect />
</ListGroup.Item>
<ListGroup.Item>
<EditorPreferenceSmartPasteSelect />
</ListGroup.Item>
<ListGroup.Item>
<EditorPreferenceInput
onChange={() => alert('This feature is not yet implemented.')}
property={EditorPreferenceProperty.SPELL_CHECK}
type={EditorPreferenceInputType.SELECT}>
<option value='off'>Off</option>
<option value='en'>English</option>
</EditorPreferenceInput>
</ListGroup.Item>
</ListGroup>
</Form>
</CommonModal>
</Fragment>
)
}

View file

@ -6,7 +6,6 @@
import React from 'react'
import { ButtonGroup, ButtonToolbar } from 'react-bootstrap'
import { EditorPreferences } from './editor-preferences/editor-preferences'
import { EmojiPickerButton } from './emoji-picker/emoji-picker-button'
import { TablePickerButton } from './table-picker/table-picker-button'
import styles from './tool-bar.module.scss'
@ -46,9 +45,6 @@ export const ToolBar: React.FC = () => {
<ToolbarButton icon={'comment'} formatType={FormatType.COMMENT} />
<EmojiPickerButton />
</ButtonGroup>
<ButtonGroup className={'mx-1 flex-wrap'}>
<EditorPreferences />
</ButtonGroup>
</ButtonToolbar>
)
}

View file

@ -13,20 +13,19 @@ import { Mock } from 'ts-mockery'
describe('Check whether cursor is in codefence', () => {
const getGlobalStateMocked = jest.spyOn(storeModule, 'getGlobalState')
const mockRedux = (content: string, line: number): void => {
const mockRedux = (content: string, from: number): void => {
const contentLines = content.split('\n')
getGlobalStateMocked.mockImplementation(() =>
Mock.from<ApplicationState>({
noteDetails: {
...initialState,
selection: {
from: {
line: line,
character: 0
}
from
},
markdownContentLines: contentLines,
markdownContent: content
markdownContent: {
plain: content,
lines: contentLines
}
}
})
)
@ -46,22 +45,22 @@ describe('Check whether cursor is in codefence', () => {
})
it('returns true with one open codefence directly above', () => {
mockRedux('```\n', 1)
mockRedux('```\n', 4)
expect(isCursorInCodeFence()).toBe(true)
})
it('returns true with one open codefence and empty lines above', () => {
mockRedux('```\n\n\n', 3)
mockRedux('```\n\n\n', 5)
expect(isCursorInCodeFence()).toBe(true)
})
it('returns false with one completed codefence above', () => {
mockRedux('```\n\n```\n', 3)
mockRedux('```\n\n```\n', 8)
expect(isCursorInCodeFence()).toBe(false)
})
it('returns true with one completed and one open codefence above', () => {
mockRedux('```\n\n```\n\n```\n\n', 6)
mockRedux('```\n\n```\n\n```\n\n', 13)
expect(isCursorInCodeFence()).toBe(true)
})
})

View file

@ -10,10 +10,8 @@ import { getGlobalState } from '../../../../../redux'
* Checks if the start of the current {@link CursorSelection cursor selection} is in a code fence.
*/
export const isCursorInCodeFence = (): boolean => {
const lines = getGlobalState().noteDetails.markdownContentLines.slice(
0,
getGlobalState().noteDetails.selection.from.line
)
const noteDetails = getGlobalState().noteDetails
const lines = noteDetails.markdownContent.plain.slice(0, noteDetails.selection.from).split('\n')
return countCodeFenceLinesUntilIndex(lines) % 2 === 1
}

View file

@ -22,21 +22,20 @@ export interface PasteEvent {
}
/**
* Checks if the given {@link PasteEvent paste event} contains a text formatted table
* and inserts it into the markdown content.
* This happens only if smart paste was activated.
* Checks if the given {@link DataTransfer clipboard data} contains a text formatted table
* and inserts it into the markdown content. This happens only if smart paste is activated.
*
* @param event The {@link PasteEvent} from the browser
* @param clipboardData The {@link DataTransfer} from the paste event
* @return {@code true} if the event was processed. {@code false} otherwise
*/
export const handleTablePaste = (event: PasteEvent): boolean => {
export const handleTablePaste = (clipboardData: DataTransfer): boolean => {
if (!getGlobalState().editorConfig.smartPaste || isCursorInCodeFence()) {
return false
}
return Optional.ofNullable(event.clipboardData.getData('text'))
.filter((pasteText) => !!pasteText && isTable(pasteText))
.map((pasteText) => convertClipboardTableToMarkdown(pasteText))
return Optional.ofNullable(clipboardData.getData('text'))
.filter(isTable)
.map(convertClipboardTableToMarkdown)
.map((markdownTable) => {
replaceSelection(markdownTable)
return true
@ -47,12 +46,12 @@ export const handleTablePaste = (event: PasteEvent): boolean => {
/**
* Checks if the given {@link PasteEvent paste event} contains files and uploads them.
*
* @param event The {@link PasteEvent} from the browser
* @param clipboardData The {@link DataTransfer} from the paste event
* @return {@code true} if the event was processed. {@code false} otherwise
*/
export const handleFilePaste = (event: PasteEvent): boolean => {
return Optional.ofNullable(event.clipboardData.files)
.filter((files) => !!files && files.length > 0)
export const handleFilePaste = (clipboardData: DataTransfer): boolean => {
return Optional.of(clipboardData.files)
.filter((files) => files.length > 0)
.map((files) => {
handleUpload(files[0])
return true