mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-20 10:15:17 -04:00
Add codemirror keybindings and addons (#311)
* added codemirror addons - fullScreen - autorefresh added a default: - extraKeys Co-authored-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> Co-authored-by: mrdrogdrog <mr.drogdrog@gmail.com> Co-authored-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
dbce0181a4
commit
fc2e2bd592
12 changed files with 1758 additions and 382 deletions
|
@ -81,6 +81,7 @@
|
||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "5.2.0",
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "3.4.1",
|
||||||
"redux": "4.0.5",
|
"redux": "4.0.5",
|
||||||
|
"ts-mockery": "^1.2.0",
|
||||||
"typescript": "3.9.7",
|
"typescript": "3.9.7",
|
||||||
"use-media": "1.4.0",
|
"use-media": "1.4.0",
|
||||||
"use-resize-observer": "6.1.0"
|
"use-resize-observer": "6.1.0"
|
||||||
|
|
|
@ -204,7 +204,10 @@
|
||||||
"editorToolbar": {
|
"editorToolbar": {
|
||||||
"bold": "Bold",
|
"bold": "Bold",
|
||||||
"italic": "Italic",
|
"italic": "Italic",
|
||||||
|
"underline": "Underline",
|
||||||
"strikethrough": "Strikethrough",
|
"strikethrough": "Strikethrough",
|
||||||
|
"subscript": "Subscript",
|
||||||
|
"superscript": "Superscript",
|
||||||
"header": "Heading",
|
"header": "Heading",
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
"blockquote": "Blockquote",
|
"blockquote": "Blockquote",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@import '../../../../node_modules/codemirror/lib/codemirror.css';
|
@import '../../../../node_modules/codemirror/lib/codemirror.css';
|
||||||
|
@import '../../../../node_modules/codemirror/addon/display/fullscreen.css';
|
||||||
@import './one-dark.css';
|
@import './one-dark.css';
|
||||||
|
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { Editor } from 'codemirror'
|
||||||
import 'codemirror/addon/comment/comment'
|
import 'codemirror/addon/comment/comment'
|
||||||
|
import 'codemirror/addon/display/autorefresh'
|
||||||
|
import 'codemirror/addon/display/fullscreen'
|
||||||
import 'codemirror/addon/display/placeholder'
|
import 'codemirror/addon/display/placeholder'
|
||||||
import 'codemirror/addon/edit/closebrackets'
|
import 'codemirror/addon/edit/closebrackets'
|
||||||
import 'codemirror/addon/edit/closetag'
|
import 'codemirror/addon/edit/closetag'
|
||||||
|
@ -11,11 +14,11 @@ import 'codemirror/addon/search/match-highlighter'
|
||||||
import 'codemirror/addon/selection/active-line'
|
import 'codemirror/addon/selection/active-line'
|
||||||
import 'codemirror/keymap/sublime.js'
|
import 'codemirror/keymap/sublime.js'
|
||||||
import 'codemirror/mode/gfm/gfm.js'
|
import 'codemirror/mode/gfm/gfm.js'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
|
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import './editor-window.scss'
|
import './editor-window.scss'
|
||||||
import { Positions, SelectionData } from './interfaces'
|
import { defaultKeyMap } from './key-map'
|
||||||
import { ToolBar } from './tool-bar/tool-bar'
|
import { ToolBar } from './tool-bar/tool-bar'
|
||||||
|
|
||||||
export interface EditorWindowProps {
|
export interface EditorWindowProps {
|
||||||
|
@ -25,39 +28,12 @@ export interface EditorWindowProps {
|
||||||
|
|
||||||
export const EditorWindow: React.FC<EditorWindowProps> = ({ onContentChange, content }) => {
|
export const EditorWindow: React.FC<EditorWindowProps> = ({ onContentChange, content }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [positions, setPositions] = useState<Positions>({
|
const [editor, setEditor] = useState<Editor>()
|
||||||
startPosition: {
|
|
||||||
ch: 0,
|
|
||||||
line: 0
|
|
||||||
},
|
|
||||||
endPosition: {
|
|
||||||
ch: 0,
|
|
||||||
line: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSelection = useCallback((editor, data: SelectionData) => {
|
|
||||||
const { anchor, head } = data.ranges[0]
|
|
||||||
const headFirst = head.line < anchor.line || (head.line === anchor.line && head.ch < anchor.ch)
|
|
||||||
|
|
||||||
setPositions({
|
|
||||||
startPosition: {
|
|
||||||
line: headFirst ? head.line : anchor.line,
|
|
||||||
ch: headFirst ? head.ch : anchor.ch
|
|
||||||
},
|
|
||||||
endPosition: {
|
|
||||||
line: headFirst ? anchor.line : head.line,
|
|
||||||
ch: headFirst ? anchor.ch : head.ch
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'d-flex flex-column h-100'}>
|
<div className={'d-flex flex-column h-100'}>
|
||||||
<ToolBar
|
<ToolBar
|
||||||
content={content}
|
editor={editor}
|
||||||
onContentChange={onContentChange}
|
|
||||||
positions={positions}
|
|
||||||
/>
|
/>
|
||||||
<ControlledCodeMirror
|
<ControlledCodeMirror
|
||||||
className="overflow-hidden w-100 flex-fill"
|
className="overflow-hidden w-100 flex-fill"
|
||||||
|
@ -73,7 +49,6 @@ export const EditorWindow: React.FC<EditorWindowProps> = ({ onContentChange, con
|
||||||
showCursorWhenSelecting: true,
|
showCursorWhenSelecting: true,
|
||||||
highlightSelectionMatches: true,
|
highlightSelectionMatches: true,
|
||||||
indentUnit: 4,
|
indentUnit: 4,
|
||||||
// continueComments: 'Enter',
|
|
||||||
inputStyle: 'textarea',
|
inputStyle: 'textarea',
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
autoCloseBrackets: true,
|
autoCloseBrackets: true,
|
||||||
|
@ -87,18 +62,17 @@ export const EditorWindow: React.FC<EditorWindowProps> = ({ onContentChange, con
|
||||||
'authorship-gutters',
|
'authorship-gutters',
|
||||||
'CodeMirror-foldgutter'
|
'CodeMirror-foldgutter'
|
||||||
],
|
],
|
||||||
// extraKeys: this.defaultExtraKeys,
|
extraKeys: defaultKeyMap,
|
||||||
flattenSpans: true,
|
flattenSpans: true,
|
||||||
addModeClass: true,
|
addModeClass: true,
|
||||||
// autoRefresh: true,
|
autoRefresh: true,
|
||||||
// otherCursors: true
|
// otherCursors: true,
|
||||||
placeholder: t('editor.placeholder')
|
placeholder: t('editor.placeholder')
|
||||||
}}
|
}}
|
||||||
|
editorDidMount={mountedEditor => setEditor(mountedEditor)}
|
||||||
onBeforeChange={(editor, data, value) => {
|
onBeforeChange={(editor, data, value) => {
|
||||||
onContentChange(value)
|
onContentChange(value)
|
||||||
}}
|
}}
|
||||||
onSelection={onSelection}
|
/></div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import CodeMirror from 'codemirror'
|
|
||||||
|
|
||||||
export interface SelectionData {
|
|
||||||
ranges: AnchorAndHead[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AnchorAndHead {
|
|
||||||
anchor: CodeMirror.Position
|
|
||||||
head: CodeMirror.Position
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Positions {
|
|
||||||
startPosition: CodeMirror.Position
|
|
||||||
endPosition: CodeMirror.Position
|
|
||||||
}
|
|
93
src/components/editor/editor-window/key-map.ts
Normal file
93
src/components/editor/editor-window/key-map.ts
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import CodeMirror, { Editor, KeyMap, Pass } from 'codemirror'
|
||||||
|
import {
|
||||||
|
makeSelectionBold,
|
||||||
|
makeSelectionItalic,
|
||||||
|
markSelection,
|
||||||
|
strikeThroughSelection,
|
||||||
|
underlineSelection
|
||||||
|
} from './tool-bar/utils'
|
||||||
|
|
||||||
|
const isMac = navigator.platform.toLowerCase().includes('mac')
|
||||||
|
|
||||||
|
const isVim = (keyMapName?: string) => (keyMapName?.substr(0, 3) === 'vim')
|
||||||
|
|
||||||
|
const f10 = (editor: Editor): void | typeof Pass => editor.setOption('fullScreen', !editor.getOption('fullScreen'))
|
||||||
|
const esc = (editor: Editor): void | typeof Pass => {
|
||||||
|
if (editor.getOption('fullScreen') && !isVim(editor.getOption('keyMap'))) {
|
||||||
|
editor.setOption('fullScreen', false)
|
||||||
|
} else {
|
||||||
|
return CodeMirror.Pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const suppressSave = (): undefined => undefined
|
||||||
|
const tab = (editor: Editor) => {
|
||||||
|
const tab = '\t'
|
||||||
|
|
||||||
|
// contruct x length spaces
|
||||||
|
const spaces = Array((editor.getOption('indentUnit') ?? 0) + 1).join(' ')
|
||||||
|
|
||||||
|
// auto indent whole line when in list or blockquote
|
||||||
|
const cursor = editor.getCursor()
|
||||||
|
const line = editor.getLine(cursor.line)
|
||||||
|
|
||||||
|
// this regex match the following patterns
|
||||||
|
// 1. blockquote starts with "> " or ">>"
|
||||||
|
// 2. unorder list starts with *+-parseInt
|
||||||
|
// 3. order list starts with "1." or "1)"
|
||||||
|
const regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/
|
||||||
|
|
||||||
|
let match
|
||||||
|
const multiple = editor.getSelection().split('\n').length > 1 ||
|
||||||
|
editor.getSelections().length > 1
|
||||||
|
|
||||||
|
if (multiple) {
|
||||||
|
editor.execCommand('defaultTab')
|
||||||
|
} else if ((match = regex.exec(line)) !== null) {
|
||||||
|
const ch = match[1].length
|
||||||
|
const pos = {
|
||||||
|
line: cursor.line,
|
||||||
|
ch: ch
|
||||||
|
}
|
||||||
|
if (editor.getOption('indentWithTabs')) {
|
||||||
|
editor.replaceRange(tab, pos, pos, '+input')
|
||||||
|
} else {
|
||||||
|
editor.replaceRange(spaces, pos, pos, '+input')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (editor.getOption('indentWithTabs')) {
|
||||||
|
editor.execCommand('defaultTab')
|
||||||
|
} else {
|
||||||
|
editor.replaceSelection(spaces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultKeyMap: KeyMap = !isMac ? {
|
||||||
|
F10: f10,
|
||||||
|
Esc: esc,
|
||||||
|
'Ctrl-S': suppressSave,
|
||||||
|
Enter: 'newlineAndIndentContinueMarkdownList',
|
||||||
|
Tab: tab,
|
||||||
|
Home: 'goLineLeftSmart',
|
||||||
|
End: 'goLineRight',
|
||||||
|
'Ctrl-I': makeSelectionItalic,
|
||||||
|
'Ctrl-B': makeSelectionBold,
|
||||||
|
'Ctrl-U': underlineSelection,
|
||||||
|
'Ctrl-D': strikeThroughSelection,
|
||||||
|
'Ctrl-M': markSelection
|
||||||
|
} : {
|
||||||
|
F10: f10,
|
||||||
|
Esc: esc,
|
||||||
|
'Cmd-S': suppressSave,
|
||||||
|
Enter: 'newlineAndIndentContinueMarkdownList',
|
||||||
|
Tab: tab,
|
||||||
|
'Cmd-Left': 'goLineLeftSmart',
|
||||||
|
'Cmd-Right': 'goLineRight',
|
||||||
|
Home: 'goLineLeftSmart',
|
||||||
|
End: 'goLineRight',
|
||||||
|
'Cmd-I': makeSelectionItalic,
|
||||||
|
'Cmd-B': makeSelectionBold,
|
||||||
|
'Cmd-U': underlineSelection,
|
||||||
|
'Cmd-D': strikeThroughSelection,
|
||||||
|
'Cmd-M': markSelection
|
||||||
|
}
|
|
@ -1,80 +1,97 @@
|
||||||
|
import { Editor } from 'codemirror'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Button, ButtonToolbar } from 'react-bootstrap'
|
import { Button, ButtonToolbar } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { Positions } from '../interfaces'
|
|
||||||
import './tool-bar.scss'
|
import './tool-bar.scss'
|
||||||
import { addCodeFences, addHeaderLevel, addLink, addMarkup, addQuotes, createList, replaceSelection } from './utils'
|
import {
|
||||||
|
addCodeFences,
|
||||||
|
addComment,
|
||||||
|
addHeaderLevel,
|
||||||
|
addImage,
|
||||||
|
addLine,
|
||||||
|
addLink,
|
||||||
|
addList,
|
||||||
|
addOrderedList,
|
||||||
|
addQuotes,
|
||||||
|
addTable,
|
||||||
|
addTaskList,
|
||||||
|
makeSelectionBold,
|
||||||
|
makeSelectionItalic,
|
||||||
|
strikeThroughSelection,
|
||||||
|
subscriptSelection,
|
||||||
|
superscriptSelection,
|
||||||
|
underlineSelection
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
export interface ToolBarProps {
|
export interface ToolBarProps {
|
||||||
content: string
|
editor: Editor | undefined
|
||||||
onContentChange: (content: string) => void
|
|
||||||
positions: Positions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToolBar: React.FC<ToolBarProps> = ({ content, positions, onContentChange }) => {
|
export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const notImplemented = () => {
|
const notImplemented = () => {
|
||||||
alert('This feature is not yet implemented')
|
alert('This feature is not yet implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
const makeSelectionBold = () => addMarkup(content, positions.startPosition, positions.endPosition, onContentChange, '**')
|
if (!editor) {
|
||||||
const makeSelectionItalic = () => addMarkup(content, positions.startPosition, positions.endPosition, onContentChange, '*')
|
return null
|
||||||
const strikeThroughSelection = () => addMarkup(content, positions.startPosition, positions.endPosition, onContentChange, '~~')
|
}
|
||||||
|
|
||||||
const addList = () => createList(content, positions.startPosition, positions.endPosition, onContentChange, () => '-')
|
|
||||||
const addOrderedList = () => createList(content, positions.startPosition, positions.endPosition, onContentChange, j => `${j}.`)
|
|
||||||
const addTaskList = () => createList(content, positions.startPosition, positions.endPosition, onContentChange, () => '- [ ]')
|
|
||||||
|
|
||||||
const addLine = () => replaceSelection(content, positions.startPosition, positions.endPosition, onContentChange, '----')
|
|
||||||
const addComment = () => replaceSelection(content, positions.startPosition, positions.endPosition, onContentChange, '> []')
|
|
||||||
const addTable = () => replaceSelection(content, positions.startPosition, positions.endPosition, onContentChange, '| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |')
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonToolbar className='flex-nowrap bg-light'>
|
<ButtonToolbar className='flex-nowrap bg-light'>
|
||||||
<Button variant='light' onClick={makeSelectionBold} title={t('editor.editorToolbar.bold')}>
|
<Button variant='light' onClick={() => makeSelectionBold(editor)} title={t('editor.editorToolbar.bold')}>
|
||||||
<ForkAwesomeIcon icon="bold"/>
|
<ForkAwesomeIcon icon="bold"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={makeSelectionItalic} title={t('editor.editorToolbar.italic')}>
|
<Button variant='light' onClick={() => makeSelectionItalic(editor)} title={t('editor.editorToolbar.italic')}>
|
||||||
<ForkAwesomeIcon icon="italic"/>
|
<ForkAwesomeIcon icon="italic"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={strikeThroughSelection} title={t('editor.editorToolbar.strikethrough')}>
|
<Button variant='light' onClick={() => underlineSelection(editor)} title={t('editor.editorToolbar.underline')}>
|
||||||
|
<ForkAwesomeIcon icon="underline"/>
|
||||||
|
</Button>
|
||||||
|
<Button variant='light' onClick={() => strikeThroughSelection(editor)} title={t('editor.editorToolbar.strikethrough')}>
|
||||||
<ForkAwesomeIcon icon="strikethrough"/>
|
<ForkAwesomeIcon icon="strikethrough"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={() => addHeaderLevel(content, positions.startPosition, onContentChange)} title={t('editor.editorToolbar.header')}>
|
<Button variant='light' onClick={() => subscriptSelection(editor)} title={t('editor.editorToolbar.subscript')}>
|
||||||
|
<ForkAwesomeIcon icon="subscript"/>
|
||||||
|
</Button>
|
||||||
|
<Button variant='light' onClick={() => superscriptSelection(editor)} title={t('editor.editorToolbar.superscript')}>
|
||||||
|
<ForkAwesomeIcon icon="superscript"/>
|
||||||
|
</Button>
|
||||||
|
<Button variant='light' onClick={() => addHeaderLevel(editor)} title={t('editor.editorToolbar.header')}>
|
||||||
<ForkAwesomeIcon icon="header"/>
|
<ForkAwesomeIcon icon="header"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={() => addCodeFences(content, positions.startPosition, positions.endPosition, onContentChange)} title={t('editor.editorToolbar.code')}>
|
<Button variant='light' onClick={() => addCodeFences(editor)} title={t('editor.editorToolbar.code')}>
|
||||||
<ForkAwesomeIcon icon="code"/>
|
<ForkAwesomeIcon icon="code"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={() => addQuotes(content, positions.startPosition, positions.endPosition, onContentChange)} title={t('editor.editorToolbar.blockquote')}>
|
<Button variant='light' onClick={() => addQuotes(editor)} title={t('editor.editorToolbar.blockquote')}>
|
||||||
<ForkAwesomeIcon icon="quote-right"/>
|
<ForkAwesomeIcon icon="quote-right"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={addList} title={t('editor.editorToolbar.unorderedList')}>
|
<Button variant='light' onClick={() => addList(editor)} title={t('editor.editorToolbar.unorderedList')}>
|
||||||
<ForkAwesomeIcon icon="list"/>
|
<ForkAwesomeIcon icon="list"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={addOrderedList} title={t('editor.editorToolbar.orderedList')}>
|
<Button variant='light' onClick={() => addOrderedList(editor)} title={t('editor.editorToolbar.orderedList')}>
|
||||||
<ForkAwesomeIcon icon="list-ol"/>
|
<ForkAwesomeIcon icon="list-ol"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={addTaskList} title={t('editor.editorToolbar.checkList')}>
|
<Button variant='light' onClick={() => addTaskList(editor)} title={t('editor.editorToolbar.checkList')}>
|
||||||
<ForkAwesomeIcon icon="check-square"/>
|
<ForkAwesomeIcon icon="check-square"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={() => addLink(content, positions.startPosition, positions.endPosition, onContentChange)} title={t('editor.editorToolbar.link')}>
|
<Button variant='light' onClick={() => addLink(editor)} title={t('editor.editorToolbar.link')}>
|
||||||
<ForkAwesomeIcon icon="link"/>
|
<ForkAwesomeIcon icon="link"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={() => addLink(content, positions.startPosition, positions.endPosition, onContentChange, '!')} title={t('editor.editorToolbar.image')}>
|
<Button variant='light' onClick={() => addImage(editor)} title={t('editor.editorToolbar.image')}>
|
||||||
<ForkAwesomeIcon icon="picture-o"/>
|
<ForkAwesomeIcon icon="picture-o"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={notImplemented} title={t('editor.editorToolbar.uploadImage')}>
|
<Button variant='light' onClick={notImplemented} title={t('editor.editorToolbar.uploadImage')}>
|
||||||
<ForkAwesomeIcon icon="upload"/>
|
<ForkAwesomeIcon icon="upload"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={addTable} title={t('editor.editorToolbar.table')}>
|
<Button variant='light' onClick={() => addTable(editor)} title={t('editor.editorToolbar.table')}>
|
||||||
<ForkAwesomeIcon icon="table"/>
|
<ForkAwesomeIcon icon="table"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={addLine} title={t('editor.editorToolbar.line')}>
|
<Button variant='light' onClick={() => addLine(editor)} title={t('editor.editorToolbar.line')}>
|
||||||
<ForkAwesomeIcon icon="minus"/>
|
<ForkAwesomeIcon icon="minus"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='light' onClick={addComment} title={t('editor.editorToolbar.comment')}>
|
<Button variant='light' onClick={() => addComment(editor)} title={t('editor.editorToolbar.comment')}>
|
||||||
<ForkAwesomeIcon icon="comment"/>
|
<ForkAwesomeIcon icon="comment"/>
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonToolbar>
|
</ButtonToolbar>
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,104 +1,100 @@
|
||||||
import CodeMirror from 'codemirror'
|
import { Editor } from 'codemirror'
|
||||||
|
|
||||||
export const replaceSelection = (content: string, startPosition: CodeMirror.Position, endPosition: CodeMirror.Position, onContentChange: (content: string) => void, replaceText: string): void => {
|
export const makeSelectionBold = (editor: Editor): void => wrapTextWith(editor, '**')
|
||||||
const contentLines = content.split('\n')
|
export const makeSelectionItalic = (editor: Editor): void => wrapTextWith(editor, '*')
|
||||||
const replaceTextLines = replaceText.split('\n')
|
export const strikeThroughSelection = (editor: Editor): void => wrapTextWith(editor, '~~')
|
||||||
const numberOfExtraLines = replaceTextLines.length - 1 - (endPosition.line - startPosition.line)
|
export const underlineSelection = (editor: Editor): void => wrapTextWith(editor, '++')
|
||||||
const replaceTextIncludeNewline = replaceText.includes('\n')
|
export const subscriptSelection = (editor: Editor): void => wrapTextWith(editor, '~')
|
||||||
if (!replaceTextIncludeNewline) {
|
export const superscriptSelection = (editor: Editor): void => wrapTextWith(editor, '^')
|
||||||
contentLines[startPosition.line] = contentLines[startPosition.line].slice(0, startPosition.ch) + replaceText + contentLines[startPosition.line].slice(endPosition.ch)
|
export const markSelection = (editor: Editor): void => wrapTextWith(editor, '==')
|
||||||
} else {
|
|
||||||
const lastPart = contentLines[endPosition.line].slice(endPosition.ch)
|
|
||||||
contentLines.push(...contentLines.slice(endPosition.line + 1))
|
|
||||||
contentLines[startPosition.line] = contentLines[startPosition.line].slice(0, startPosition.ch) + replaceTextLines[0]
|
|
||||||
contentLines.splice(startPosition.line + 1, replaceTextLines.length - 1, ...replaceTextLines.slice(1))
|
|
||||||
contentLines[numberOfExtraLines + endPosition.line] += lastPart
|
|
||||||
}
|
|
||||||
onContentChange(contentLines.join('\n'))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const extractSelection = (content: string, startPosition: CodeMirror.Position, endPosition: CodeMirror.Position): string => {
|
export const addHeaderLevel = (editor: Editor): void => changeLines(editor, line => line.startsWith('#') ? `#${line}` : `# ${line}`)
|
||||||
if (startPosition.line === endPosition.line && startPosition.ch === endPosition.ch) {
|
export const addCodeFences = (editor: Editor): void => wrapTextWith(editor, '```\n', '\n```')
|
||||||
return ''
|
export const addQuotes = (editor: Editor): void => insertOnStartOfLines(editor, '> ')
|
||||||
}
|
|
||||||
|
|
||||||
const lines = content.split('\n')
|
export const addList = (editor: Editor): void => createList(editor, () => '- ')
|
||||||
|
export const addOrderedList = (editor: Editor): void => createList(editor, j => `${j}. `)
|
||||||
|
export const addTaskList = (editor: Editor): void => createList(editor, () => '- [ ] ')
|
||||||
|
|
||||||
if (startPosition.line === endPosition.line) {
|
export const addImage = (editor: Editor): void => addLink(editor, '!')
|
||||||
return removeLastNewLine(lines[startPosition.line].slice(startPosition.ch, endPosition.ch))
|
|
||||||
}
|
|
||||||
|
|
||||||
let multiLineSelection = lines[startPosition.line].slice(startPosition.ch) + '\n'
|
export const addLine = (editor: Editor): void => changeLines(editor, line => `${line}\n----`)
|
||||||
for (let i = startPosition.line + 1; i <= endPosition.line; i++) {
|
export const addComment = (editor: Editor): void => changeLines(editor, line => `${line}\n> []`)
|
||||||
if (i === endPosition.line) {
|
export const addTable = (editor: Editor): void => changeLines(editor, line => `${line}\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |`)
|
||||||
multiLineSelection += lines[i].slice(0, endPosition.ch)
|
|
||||||
} else {
|
|
||||||
multiLineSelection += lines[i] + '\n'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return multiLineSelection
|
|
||||||
}
|
|
||||||
|
|
||||||
export const removeLastNewLine = (selection: string): string => {
|
export const wrapTextWith = (editor: Editor, symbol: string, endSymbol?: string): void => {
|
||||||
if (selection.endsWith('\n')) {
|
if (!editor.getSelection()) {
|
||||||
selection = selection.slice(0, -1)
|
|
||||||
}
|
|
||||||
return selection
|
|
||||||
}
|
|
||||||
|
|
||||||
export const addMarkup = (content: string, startPosition: CodeMirror.Position, endPosition: CodeMirror.Position, onContentChange: (content: string) => void, markUp: string): void => {
|
|
||||||
const selection = extractSelection(content, startPosition, endPosition)
|
|
||||||
if (selection === '') {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
replaceSelection(content, startPosition, endPosition, onContentChange, `${markUp}${selection}${markUp}`)
|
const ranges = editor.listSelections()
|
||||||
}
|
for (const range of ranges) {
|
||||||
|
if (range.empty()) {
|
||||||
export const createList = (content: string, startPosition: CodeMirror.Position, endPosition: CodeMirror.Position, onContentChange: (content: string) => void, listMark: (j: number) => string): void => {
|
continue
|
||||||
const lines = content.split('\n')
|
|
||||||
let j = 1
|
|
||||||
for (let i = startPosition.line; i <= endPosition.line; i++) {
|
|
||||||
lines[i] = `${listMark(j)} ${lines[i]}`
|
|
||||||
j++
|
|
||||||
}
|
}
|
||||||
onContentChange(lines.join('\n'))
|
const from = range.from()
|
||||||
|
const to = range.to()
|
||||||
|
|
||||||
|
const selection = editor.getRange(from, to)
|
||||||
|
editor.replaceRange(symbol + selection + (endSymbol || symbol), from, to, '+input')
|
||||||
|
range.head.ch += symbol.length
|
||||||
|
range.anchor.ch += endSymbol ? endSymbol.length : symbol.length
|
||||||
|
}
|
||||||
|
editor.setSelections(ranges)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addHeaderLevel = (content: string, startPosition: CodeMirror.Position, onContentChange: (content: string) => void): void => {
|
export const insertOnStartOfLines = (editor: Editor, symbol: string): void => {
|
||||||
const lines = content.split('\n')
|
const cursor = editor.getCursor()
|
||||||
const startLine = lines[startPosition.line]
|
const ranges = editor.listSelections()
|
||||||
const isHeadingAlready = startLine.startsWith('#')
|
for (const range of ranges) {
|
||||||
lines[startPosition.line] = `#${!isHeadingAlready ? ' ' : ''}${startLine}`
|
const from = range.empty() ? { line: cursor.line, ch: 0 } : range.from()
|
||||||
onContentChange(lines.join('\n'))
|
const to = range.empty() ? { line: cursor.line, ch: editor.getLine(cursor.line).length } : range.to()
|
||||||
|
const selection = editor.getRange(from, to)
|
||||||
|
const lines = selection.split('\n')
|
||||||
|
editor.replaceRange(lines.map(line => `${symbol}${line}`).join('\n'), from, to, '+input')
|
||||||
|
}
|
||||||
|
editor.setSelections(ranges)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addLink = (content: string, startPosition: CodeMirror.Position, endPosition: CodeMirror.Position, onContentChange: (content: string) => void, prefix?: string): void => {
|
export const changeLines = (editor: Editor, replaceFunction: (line: string) => string): void => {
|
||||||
const selection = extractSelection(content, startPosition, endPosition)
|
const cursor = editor.getCursor()
|
||||||
|
const ranges = editor.listSelections()
|
||||||
|
for (const range of ranges) {
|
||||||
|
const lineNumber = range.empty() ? cursor.line : range.from().line
|
||||||
|
const line = editor.getLine(lineNumber)
|
||||||
|
editor.replaceRange(replaceFunction(line), { line: lineNumber, ch: 0 }, {
|
||||||
|
line: lineNumber,
|
||||||
|
ch: line.length
|
||||||
|
}, '+input')
|
||||||
|
}
|
||||||
|
editor.setSelections(ranges)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createList = (editor: Editor, listMark: (i: number) => string): void => {
|
||||||
|
const cursor = editor.getCursor()
|
||||||
|
const ranges = editor.listSelections()
|
||||||
|
for (const range of ranges) {
|
||||||
|
const from = range.empty() ? { line: cursor.line, ch: 0 } : range.from()
|
||||||
|
const to = range.empty() ? { line: cursor.line, ch: editor.getLine(cursor.line).length } : range.to()
|
||||||
|
|
||||||
|
const selection = editor.getRange(from, to)
|
||||||
|
const lines = selection.split('\n')
|
||||||
|
editor.replaceRange(lines.map((line, i) => `${listMark(i + 1)}${line}`).join('\n'), from, to, '+input')
|
||||||
|
}
|
||||||
|
editor.setSelections(ranges)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addLink = (editor: Editor, prefix?: string): void => {
|
||||||
|
const cursor = editor.getCursor()
|
||||||
|
const ranges = editor.listSelections()
|
||||||
|
for (const range of ranges) {
|
||||||
|
const from = range.empty() ? { line: cursor.line, ch: cursor.ch } : range.from()
|
||||||
|
const to = range.empty() ? { line: cursor.line, ch: cursor.ch } : range.to()
|
||||||
|
const selection = editor.getRange(from, to)
|
||||||
const linkRegex = /^(?:https?|ftp|mailto):/
|
const linkRegex = /^(?:https?|ftp|mailto):/
|
||||||
if (linkRegex.exec(selection)) {
|
if (linkRegex.exec(selection)) {
|
||||||
replaceSelection(content, startPosition, endPosition, onContentChange, `${prefix || ''}[](${selection})`)
|
editor.replaceRange(`${prefix || ''}[](${selection})`, from, to, '+input')
|
||||||
} else {
|
} else {
|
||||||
replaceSelection(content, startPosition, endPosition, onContentChange, `${prefix || ''}[${selection}](https://)`)
|
editor.replaceRange(`${prefix || ''}[${selection}](https://)`, from, to, '+input')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addQuotes = (content: string, startPosition: CodeMirror.Position, endPosition: CodeMirror.Position, onContentChange: (content: string) => void): void => {
|
|
||||||
const selection = extractSelection(content, startPosition, endPosition)
|
|
||||||
if (selection === '') {
|
|
||||||
replaceSelection(content, startPosition, endPosition, onContentChange, '> ')
|
|
||||||
} else if (!selection.includes('\n')) {
|
|
||||||
const lines = content.split('\n')
|
|
||||||
replaceSelection(content, startPosition, endPosition, onContentChange, '> ' + lines[startPosition.line])
|
|
||||||
} else {
|
|
||||||
const lines = content.split('\n')
|
|
||||||
for (let i = startPosition.line; i <= endPosition.line; i++) {
|
|
||||||
lines[i] = `> ${lines[i]}`
|
|
||||||
}
|
|
||||||
onContentChange(lines.join('\n'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const addCodeFences = (content: string, startPosition: CodeMirror.Position, endPosition: CodeMirror.Position, onContentChange: (content: string) => void): void => {
|
|
||||||
const selection = extractSelection(content, startPosition, endPosition)
|
|
||||||
replaceSelection(content, startPosition, endPosition, onContentChange, `\`\`\`\n${selection}\n\`\`\``)
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,6 +9,10 @@ opengraph:
|
||||||
# Embedding demo
|
# Embedding demo
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
|
## some plain text
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||||
|
|
||||||
## MathJax
|
## MathJax
|
||||||
You can render *LaTeX* mathematical expressions using **MathJax**, as on [math.stackexchange.com](https://math.stackexchange.com/):
|
You can render *LaTeX* mathematical expressions using **MathJax**, as on [math.stackexchange.com](https://math.stackexchange.com/):
|
||||||
|
|
||||||
|
|
8
src/external-types/codemirror/addon/display/fullScreen.d.ts
vendored
Normal file
8
src/external-types/codemirror/addon/display/fullScreen.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import 'codemirror'
|
||||||
|
|
||||||
|
declare module 'codemirror' {
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
interface EditorConfiguration {
|
||||||
|
fullScreen?: boolean;
|
||||||
|
}
|
||||||
|
}
|
|
@ -12254,6 +12254,11 @@ ts-loader@8.0.2:
|
||||||
micromatch "^4.0.0"
|
micromatch "^4.0.0"
|
||||||
semver "^6.0.0"
|
semver "^6.0.0"
|
||||||
|
|
||||||
|
ts-mockery@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-mockery/-/ts-mockery-1.2.0.tgz#aa76521653d729e99b3808836817f64e61a213dd"
|
||||||
|
integrity sha512-ArGPMUzO4H25KBYVTWmmE36y5bCOFAwC7XdW4CLTqYg+gQcvxJzKoj5URSc+luzwI8QdtwAkHtazBmrKepX81g==
|
||||||
|
|
||||||
ts-pnp@1.1.6:
|
ts-pnp@1.1.6:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.6.tgz#389a24396d425a0d3162e96d2b4638900fdc289a"
|
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.6.tgz#389a24396d425a0d3162e96d2b4638900fdc289a"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue