mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-03-25 14:12:28 +00:00
Added markdown-file import (#645)
* Added markdown-file import * Reset file input after read, don't add unnecessary blank lines * Add cypress-file-upload dependency * Add cypress tests for md file importing * Added CHANGELOG entry
This commit is contained in:
parent
c1d4ac1014
commit
729ad652b3
11 changed files with 113 additions and 7 deletions
|
@ -44,6 +44,7 @@
|
|||
- All images can be clicked to show them in full screen.
|
||||
- Code blocks have a 'Copy code to clipboard' button.
|
||||
- Code blocks with 'vega-lite' as language are rendered as [vega-lite diagrams](https://vega.github.io/vega-lite/examples/).
|
||||
- Markdown files can be imported into an existing note directly from the editor.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
2
cypress/fixtures/import.md
Normal file
2
cypress/fixtures/import.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Some short import test file
|
||||
:)
|
42
cypress/integration/import.spec.ts
Normal file
42
cypress/integration/import.spec.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
describe('Import markdown file', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/n/test')
|
||||
cy.get('.btn.active.btn-outline-secondary > i.fa-columns')
|
||||
.should('exist')
|
||||
cy.get('.CodeMirror textarea')
|
||||
.type('{ctrl}a', { force: true })
|
||||
.type('{backspace}')
|
||||
})
|
||||
|
||||
it('import on blank note', () => {
|
||||
cy.get('button#editor-menu-import')
|
||||
.click()
|
||||
cy.get('.import-md-file')
|
||||
.click()
|
||||
cy.get('div[aria-labelledby="editor-menu-import"] > input[type=file]')
|
||||
.attachFile('import.md')
|
||||
cy.get('.CodeMirror-code > div:nth-of-type(1) > .CodeMirror-line > span > span')
|
||||
.should('have.text', '# Some short import test file')
|
||||
cy.get('.CodeMirror-code > div:nth-of-type(2) > .CodeMirror-line > span > span')
|
||||
.should('have.text', ':)')
|
||||
})
|
||||
|
||||
it('import on note with content', () => {
|
||||
cy.get('.CodeMirror textarea')
|
||||
.type('test\nabc', { force: true })
|
||||
cy.get('button#editor-menu-import')
|
||||
.click()
|
||||
cy.get('.import-md-file')
|
||||
.click()
|
||||
cy.get('div[aria-labelledby="editor-menu-import"] > input[type=file]')
|
||||
.attachFile('import.md')
|
||||
cy.get('.CodeMirror-code > div:nth-of-type(1) > .CodeMirror-line > span > span')
|
||||
.should('have.text', 'test')
|
||||
cy.get('.CodeMirror-code > div:nth-of-type(2) > .CodeMirror-line > span > span')
|
||||
.should('have.text', 'abc')
|
||||
cy.get('.CodeMirror-code > div:nth-of-type(3) > .CodeMirror-line > span > span')
|
||||
.should('have.text', '# Some short import test file')
|
||||
cy.get('.CodeMirror-code > div:nth-of-type(4) > .CodeMirror-line > span > span')
|
||||
.should('have.text', ':)')
|
||||
})
|
||||
})
|
|
@ -14,6 +14,7 @@
|
|||
// ***********************************************************
|
||||
|
||||
import 'cypress-commands'
|
||||
import 'cypress-file-upload'
|
||||
import './checkLinks'
|
||||
import './config'
|
||||
import './login'
|
||||
|
|
|
@ -159,6 +159,7 @@
|
|||
"cross-env": "7.0.2",
|
||||
"cypress": "5.3.0",
|
||||
"cypress-commands": "1.1.0",
|
||||
"cypress-file-upload": "^4.1.1",
|
||||
"eslint-plugin-chai-friendly": "0.6.0",
|
||||
"eslint-plugin-cypress": "2.11.2",
|
||||
"http-server": "0.12.3",
|
||||
|
|
|
@ -301,7 +301,8 @@
|
|||
"pdf": "PDF export is unavailable."
|
||||
},
|
||||
"import": {
|
||||
"clipboard": "Clipboard"
|
||||
"clipboard": "Clipboard",
|
||||
"file": "Markdown file"
|
||||
},
|
||||
"modal": {
|
||||
"snippetImport": {
|
||||
|
|
|
@ -6,16 +6,15 @@ import { ConnectionIndicator } from './connection-indicator/connection-indicator
|
|||
import { DocumentInfoButton } from './document-info/document-info-button'
|
||||
import { EditorMenu } from './menus/editor-menu'
|
||||
import { ExportMenu } from './menus/export-menu'
|
||||
import { ImportMenu } from './menus/import-menu'
|
||||
import { ImportMenu, ImportProps } from './menus/import-menu'
|
||||
import { PermissionButton } from './permissions/permission-button'
|
||||
import { RevisionButton } from './revisions/revision-button'
|
||||
|
||||
export interface DocumentBarProps {
|
||||
title: string
|
||||
noteContent: string
|
||||
}
|
||||
|
||||
export const DocumentBar: React.FC<DocumentBarProps> = ({ title, noteContent }) => {
|
||||
export const DocumentBar: React.FC<DocumentBarProps & ImportProps> = ({ title, noteContent, updateNoteContent }) => {
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
|
@ -28,7 +27,7 @@ export const DocumentBar: React.FC<DocumentBarProps> = ({ title, noteContent })
|
|||
<PermissionButton/>
|
||||
</div>
|
||||
<div className="ml-auto navbar-nav">
|
||||
<ImportMenu/>
|
||||
<ImportMenu updateNoteContent={updateNoteContent} noteContent={noteContent}/>
|
||||
<ExportMenu title={title} noteContent={noteContent}/>
|
||||
<EditorMenu noteTitle={title}/>
|
||||
<ConnectionIndicator/>
|
||||
|
|
45
src/components/editor/document-bar/import/import-file.tsx
Normal file
45
src/components/editor/document-bar/import/import-file.tsx
Normal file
|
@ -0,0 +1,45 @@
|
|||
import React, { Fragment, useCallback, useRef } from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { ImportProps } from '../menus/import-menu'
|
||||
|
||||
export const ImportFile: React.FC<ImportProps> = ({ noteContent, updateNoteContent }) => {
|
||||
const fileInputReference = useRef<HTMLInputElement>(null)
|
||||
const doImport = useCallback(() => {
|
||||
const fileInput = fileInputReference.current
|
||||
if (!fileInput) {
|
||||
return
|
||||
}
|
||||
fileInput.addEventListener('change', () => {
|
||||
if (!fileInput.files || fileInput.files.length < 1) {
|
||||
return
|
||||
}
|
||||
const file = fileInput.files[0]
|
||||
const fileReader = new FileReader()
|
||||
fileReader.addEventListener('load', () => {
|
||||
const newContent = fileReader.result as string
|
||||
if (noteContent.length === 0) {
|
||||
updateNoteContent(newContent)
|
||||
} else {
|
||||
updateNoteContent(noteContent + '\n' + newContent)
|
||||
}
|
||||
})
|
||||
fileReader.addEventListener('loadend', () => {
|
||||
fileInput.value = ''
|
||||
})
|
||||
fileReader.readAsText(file)
|
||||
})
|
||||
fileInput.click()
|
||||
}, [fileInputReference, noteContent, updateNoteContent])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<input type='file' ref={fileInputReference} className='d-none' accept='.md, text/markdown, text/plain'/>
|
||||
<Dropdown.Item className='small import-md-file' onClick={doImport}>
|
||||
<ForkAwesomeIcon icon='file-text-o' className={'mx-2'}/>
|
||||
<Trans i18nKey='editor.import.file'/>
|
||||
</Dropdown.Item>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -2,8 +2,14 @@ import React from 'react'
|
|||
import { Dropdown } from 'react-bootstrap'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { ImportFile } from '../import/import-file'
|
||||
|
||||
export const ImportMenu: React.FC = () => {
|
||||
export interface ImportProps {
|
||||
noteContent: string
|
||||
updateNoteContent: (content: string) => void
|
||||
}
|
||||
|
||||
export const ImportMenu: React.FC<ImportProps> = ({ updateNoteContent, noteContent }) => {
|
||||
return (
|
||||
<Dropdown className='small mx-1' alignRight={true}>
|
||||
<Dropdown.Toggle variant='light' size='sm' id='editor-menu-import' className=''>
|
||||
|
@ -27,6 +33,7 @@ export const ImportMenu: React.FC = () => {
|
|||
<ForkAwesomeIcon icon='clipboard' className={'mx-2'}/>
|
||||
<Trans i18nKey='editor.import.clipboard'/>
|
||||
</Dropdown.Item>
|
||||
<ImportFile updateNoteContent={updateNoteContent} noteContent={noteContent}/>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
)
|
||||
|
|
|
@ -115,7 +115,7 @@ export const Editor: React.FC = () => {
|
|||
<DocumentTitle title={documentTitle}/>
|
||||
<div className={'d-flex flex-column vh-100'}>
|
||||
<AppBar/>
|
||||
<DocumentBar title={documentTitle} noteContent={markdownContent}/>
|
||||
<DocumentBar title={documentTitle} noteContent={markdownContent} updateNoteContent={(newContent) => setMarkdownContent(newContent)}/>
|
||||
<Splitter
|
||||
showLeft={editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH}
|
||||
left={
|
||||
|
|
|
@ -4802,6 +4802,13 @@ cypress-commands@1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/cypress-commands/-/cypress-commands-1.1.0.tgz#9248190168783deb8ab27ae7c722e3e01d172c97"
|
||||
integrity sha512-Q8Jr25pHJQFXwln6Hp8O+Hgs8Z506Y2wA9F1Te2cTajjc5L9gtt9WPOcw1Ogh+OgyqaMHF+uq31vdfImRTio5Q==
|
||||
|
||||
cypress-file-upload@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-4.1.1.tgz#952713c8104ab7008de99c65bd63f74b244fe4df"
|
||||
integrity sha512-tX6UhuJ63rNgjdzxglpX+ZYf/bM6PDhFMtt1qCBljLtAgdearqyfD1AHqyh59rOHCjfM+bf6FA3o9b/mdaX6pw==
|
||||
dependencies:
|
||||
mime "^2.4.4"
|
||||
|
||||
cypress@5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.3.0.tgz#91122219ae66ab910058970dbf36619ab0fbde6c"
|
||||
|
|
Loading…
Reference in a new issue