mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-29 06:15:29 -04:00
Add table formatting on paste of detected table (#957)
This commit is contained in:
parent
107f0f6fa3
commit
0b4a0afa16
14 changed files with 375 additions and 27 deletions
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React, { ChangeEvent, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import { setEditorSmartPaste } from '../../../../../redux/editor/methods'
|
||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||
|
||||
export const EditorPreferenceSmartPasteSelect: React.FC = () => {
|
||||
const smartPasteEnabled = useSelector((state: ApplicationState) => 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>
|
||||
)
|
||||
}
|
|
@ -19,6 +19,7 @@ import { EditorPreferenceLigaturesSelect } from './editor-preference-ligatures-s
|
|||
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'
|
||||
|
||||
export const EditorPreferences: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
@ -57,6 +58,9 @@ export const EditorPreferences: React.FC = () => {
|
|||
<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 }
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Mock } from 'ts-mockery'
|
||||
import { Editor } from 'codemirror'
|
||||
import { isCursorInCodefence } from './codefenceDetection'
|
||||
|
||||
Mock.configure('jest')
|
||||
|
||||
const mockEditor = (content: string, line: number) => {
|
||||
const contentLines = content.split('\n')
|
||||
return Mock.of<Editor>({
|
||||
getCursor() {
|
||||
return {
|
||||
line: line,
|
||||
ch: 0
|
||||
}
|
||||
},
|
||||
getDoc() {
|
||||
return {
|
||||
getLine(ln: number) {
|
||||
return contentLines[ln] ?? ''
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
describe('Check whether cursor is in codefence', () => {
|
||||
|
||||
it('returns false for empty document', () => {
|
||||
const editor = mockEditor('', 0)
|
||||
expect(isCursorInCodefence(editor))
|
||||
.toBe(false)
|
||||
})
|
||||
|
||||
it('returns true with one open codefence directly above', () => {
|
||||
const editor = mockEditor('```\n', 1)
|
||||
expect(isCursorInCodefence(editor))
|
||||
.toBe(true)
|
||||
})
|
||||
|
||||
it('returns true with one open codefence and empty lines above', () => {
|
||||
const editor = mockEditor('```\n\n\n', 3)
|
||||
expect(isCursorInCodefence(editor))
|
||||
.toBe(true)
|
||||
})
|
||||
|
||||
it('returns false with one completed codefence above', () => {
|
||||
const editor = mockEditor('```\n\n```\n', 3)
|
||||
expect(isCursorInCodefence(editor))
|
||||
.toBe(false)
|
||||
})
|
||||
|
||||
it('returns true with one completed and one open codefence above', () => {
|
||||
const editor = mockEditor('```\n\n```\n\n```\n\n', 6)
|
||||
expect(isCursorInCodefence(editor))
|
||||
.toBe(true)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Editor } from 'codemirror'
|
||||
|
||||
export const isCursorInCodefence = (editor: Editor): boolean => {
|
||||
const currentLine = editor.getCursor().line
|
||||
let codefenceCount = 0
|
||||
for (let line = currentLine; line >= 0; --line) {
|
||||
const markdownContentLine = editor.getDoc().getLine(line)
|
||||
if (markdownContentLine.startsWith('```')) {
|
||||
codefenceCount++
|
||||
}
|
||||
}
|
||||
return codefenceCount % 2 === 1
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Editor } from 'codemirror'
|
||||
import { convertClipboardTableToMarkdown, isTable } from '../../table-extractor'
|
||||
import { handleUpload } from '../../upload-handler'
|
||||
import { insertAtCursor } from './toolbarButtonUtils'
|
||||
import { isCursorInCodefence } from './codefenceDetection'
|
||||
|
||||
type ClipboardDataFormats = 'text' | 'url' | 'text/plain' | 'text/uri-list' | 'text/html'
|
||||
|
||||
export interface PasteEvent {
|
||||
clipboardData: {
|
||||
files: FileList,
|
||||
getData: (format: ClipboardDataFormats) => string
|
||||
},
|
||||
preventDefault: () => void
|
||||
}
|
||||
|
||||
export const handleTablePaste = (event: PasteEvent, editor: Editor): boolean => {
|
||||
const pasteText = event.clipboardData.getData('text')
|
||||
if (!pasteText || isCursorInCodefence(editor) || !isTable(pasteText)) {
|
||||
return false
|
||||
}
|
||||
event.preventDefault()
|
||||
const markdownTable = convertClipboardTableToMarkdown(pasteText)
|
||||
insertAtCursor(editor, markdownTable)
|
||||
return true
|
||||
}
|
||||
|
||||
export const handleFilePaste = (event: PasteEvent, editor: Editor): boolean => {
|
||||
if (!event.clipboardData.files || event.clipboardData.files.length < 1) {
|
||||
return false
|
||||
}
|
||||
event.preventDefault()
|
||||
const files: FileList = event.clipboardData.files
|
||||
if (files && files.length >= 1) {
|
||||
handleUpload(files[0], editor)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue