mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-29 06:15:29 -04:00
Upgrade to CodeMirror 6 (#1787)
Upgrade to CodeMirror 6 Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
1a09bfa5f1
commit
6a6f6105b9
103 changed files with 1906 additions and 2615 deletions
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -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'
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue