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:
Philip Molares 2020-08-06 13:43:48 +02:00 committed by GitHub
parent dbce0181a4
commit fc2e2bd592
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1758 additions and 382 deletions

View file

@ -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"

View file

@ -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",

View file

@ -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 {

View file

@ -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>
) )
} }

View file

@ -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
}

View 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
}

View file

@ -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

View file

@ -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\`\`\``)
}

View file

@ -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/):

View file

@ -0,0 +1,8 @@
import 'codemirror'
declare module 'codemirror' {
// noinspection JSUnusedGlobalSymbols
interface EditorConfiguration {
fullScreen?: boolean;
}
}

View file

@ -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"