|
|
|
@@ -1,42 +1,50 @@
|
|
|
|
import 'codemirror-mode-elixir'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import {Alignment, options, TableEditor} from '@susisu/mte-kernel'
|
|
|
|
|
|
|
|
import consts from 'browser/lib/consts'
|
|
|
|
|
|
|
|
import convertModeName from 'browser/lib/convertModeName'
|
|
|
|
|
|
|
|
import TextEditorInterface from 'browser/lib/TextEditorInterface'
|
|
|
|
|
|
|
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
|
|
|
|
|
|
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
|
|
|
|
|
|
|
import CodeMirror from 'codemirror'
|
|
|
|
|
|
|
|
import crypto from 'crypto'
|
|
|
|
|
|
|
|
import fs from 'fs'
|
|
|
|
|
|
|
|
import iconv from 'iconv-lite'
|
|
|
|
|
|
|
|
import _ from 'lodash'
|
|
|
|
|
|
|
|
import PropTypes from 'prop-types'
|
|
|
|
import PropTypes from 'prop-types'
|
|
|
|
import React from 'react'
|
|
|
|
import React from 'react'
|
|
|
|
|
|
|
|
import _ from 'lodash'
|
|
|
|
const {ipcRenderer} = require('electron')
|
|
|
|
import CodeMirror from 'codemirror'
|
|
|
|
|
|
|
|
import 'codemirror-mode-elixir'
|
|
|
|
|
|
|
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
|
|
|
|
|
|
|
import convertModeName from 'browser/lib/convertModeName'
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
|
|
options,
|
|
|
|
|
|
|
|
TableEditor,
|
|
|
|
|
|
|
|
Alignment
|
|
|
|
|
|
|
|
} from '@susisu/mte-kernel'
|
|
|
|
|
|
|
|
import TextEditorInterface from 'browser/lib/TextEditorInterface'
|
|
|
|
|
|
|
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
|
|
|
|
|
|
|
import iconv from 'iconv-lite'
|
|
|
|
|
|
|
|
import crypto from 'crypto'
|
|
|
|
|
|
|
|
import consts from 'browser/lib/consts'
|
|
|
|
|
|
|
|
import fs from 'fs'
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
|
|
ipcRenderer
|
|
|
|
|
|
|
|
} = require('electron')
|
|
|
|
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
|
|
|
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
|
|
|
import TurndownService from 'turndown'
|
|
|
|
import TurndownService from 'turndown'
|
|
|
|
import{gfm} from 'turndown-plugin-gfm'
|
|
|
|
import {
|
|
|
|
|
|
|
|
gfm
|
|
|
|
|
|
|
|
} from 'turndown-plugin-gfm'
|
|
|
|
|
|
|
|
|
|
|
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
|
|
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
|
|
|
|
|
|
|
|
|
|
|
const buildCMRulers = (rulers, enableRulers) =>
|
|
|
|
const buildCMRulers = (rulers, enableRulers) =>
|
|
|
|
(enableRulers ? rulers.map(ruler => ({column: ruler})) : [])
|
|
|
|
(enableRulers ? rulers.map(ruler => ({
|
|
|
|
|
|
|
|
column: ruler
|
|
|
|
|
|
|
|
})) : [])
|
|
|
|
|
|
|
|
|
|
|
|
export default class CodeEditor extends React.Component {
|
|
|
|
export default class CodeEditor extends React.Component {
|
|
|
|
constructor(props) {
|
|
|
|
constructor(props) {
|
|
|
|
super(props)
|
|
|
|
super(props)
|
|
|
|
|
|
|
|
|
|
|
|
this.scrollHandler =
|
|
|
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
|
|
|
|
_.debounce(
|
|
|
|
leading: false,
|
|
|
|
this.handleScroll.bind(this), 100,
|
|
|
|
trailing: true
|
|
|
|
{leading: false, trailing: true}) this.changeHandler = e =>
|
|
|
|
})
|
|
|
|
this.handleChange(e) this.focusHandler =
|
|
|
|
this.changeHandler = e => this.handleChange(e)
|
|
|
|
() => {
|
|
|
|
this.focusHandler = () => {
|
|
|
|
ipcRenderer.send('editor:focused', true)
|
|
|
|
ipcRenderer.send('editor:focused', true)
|
|
|
|
} this.blurHandler =
|
|
|
|
}
|
|
|
|
(editor, e) => {
|
|
|
|
this.blurHandler = (editor, e) => {
|
|
|
|
ipcRenderer.send('editor:focused', false)
|
|
|
|
ipcRenderer.send('editor:focused', false)
|
|
|
|
if (e == null) return null
|
|
|
|
if (e == null) return null
|
|
|
|
let el = e.relatedTarget
|
|
|
|
let el = e.relatedTarget
|
|
|
|
@@ -48,53 +56,54 @@ export default class CodeEditor extends React.Component {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.props.onBlur != null && this.props.onBlur(e)
|
|
|
|
this.props.onBlur != null && this.props.onBlur(e)
|
|
|
|
|
|
|
|
|
|
|
|
const {storageKey, noteKey} = this.props
|
|
|
|
const {
|
|
|
|
|
|
|
|
storageKey,
|
|
|
|
|
|
|
|
noteKey
|
|
|
|
|
|
|
|
} = this.props
|
|
|
|
attachmentManagement.deleteAttachmentsNotPresentInNote(
|
|
|
|
attachmentManagement.deleteAttachmentsNotPresentInNote(
|
|
|
|
this.editor.getValue(), storageKey, noteKey)
|
|
|
|
this.editor.getValue(),
|
|
|
|
} this.pasteHandler = (editor, e) =>
|
|
|
|
storageKey,
|
|
|
|
this.handlePaste(editor, e) this.loadStyleHandler =
|
|
|
|
noteKey
|
|
|
|
e => {
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
|
|
|
|
|
|
|
this.loadStyleHandler = e => {
|
|
|
|
this.editor.refresh()
|
|
|
|
this.editor.refresh()
|
|
|
|
} this.searchHandler = (e, msg) =>
|
|
|
|
}
|
|
|
|
this.handleSearch(msg) this.searchState =
|
|
|
|
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
|
|
|
null this.scrollToLineHandeler =
|
|
|
|
this.searchState = null
|
|
|
|
this.scrollToLine
|
|
|
|
this.scrollToLineHandeler = this.scrollToLine.bind(this)
|
|
|
|
.bind(this)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.formatTable = () =>
|
|
|
|
this.formatTable = () => this.handleFormatTable()
|
|
|
|
this.handleFormatTable() this
|
|
|
|
this.editorActivityHandler = () => this.handleEditorActivity()
|
|
|
|
.editorActivityHandler = () =>
|
|
|
|
|
|
|
|
this.handleEditorActivity()
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleSearch(msg) {
|
|
|
|
handleSearch(msg) {
|
|
|
|
const cm = this.editor
|
|
|
|
const cm = this.editor
|
|
|
|
const component = this
|
|
|
|
const component = this
|
|
|
|
|
|
|
|
|
|
|
|
if (component.searchState)
|
|
|
|
if (component.searchState) cm.removeOverlay(component.searchState)
|
|
|
|
cm.removeOverlay(component.searchState)
|
|
|
|
|
|
|
|
if (msg.length < 3) return
|
|
|
|
if (msg.length < 3) return
|
|
|
|
|
|
|
|
|
|
|
|
cm.operation(function() {
|
|
|
|
cm.operation(function () {
|
|
|
|
component.searchState = makeOverlay(msg, 'searching')
|
|
|
|
component.searchState = makeOverlay(msg, 'searching')
|
|
|
|
cm.addOverlay(component.searchState)
|
|
|
|
cm.addOverlay(component.searchState)
|
|
|
|
|
|
|
|
|
|
|
|
function makeOverlay(query, style) {
|
|
|
|
function makeOverlay(query, style) {
|
|
|
|
query = new RegExp(
|
|
|
|
query = new RegExp(
|
|
|
|
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
|
|
|
|
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
|
|
|
|
'gi')
|
|
|
|
'gi'
|
|
|
|
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
token: function(stream) {
|
|
|
|
token: function (stream) {
|
|
|
|
query.lastIndex = stream.pos
|
|
|
|
query.lastIndex = stream.pos
|
|
|
|
var match = query.exec(stream.string)
|
|
|
|
var match = query.exec(stream.string)
|
|
|
|
if (match && match.index === stream.pos) {
|
|
|
|
if (match && match.index === stream.pos) {
|
|
|
|
stream.pos += match[0].length || 1
|
|
|
|
stream.pos += match[0].length || 1
|
|
|
|
return style
|
|
|
|
return style
|
|
|
|
}
|
|
|
|
} else if (match) {
|
|
|
|
else if (match) {
|
|
|
|
|
|
|
|
stream.pos = match.index
|
|
|
|
stream.pos = match.index
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
else {
|
|
|
|
|
|
|
|
stream.skipToEnd()
|
|
|
|
stream.skipToEnd()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -104,7 +113,9 @@ export default class CodeEditor extends React.Component {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleFormatTable() {
|
|
|
|
handleFormatTable() {
|
|
|
|
this.tableEditor.formatAll(options({textWidthOptions: {}}))
|
|
|
|
this.tableEditor.formatAll(options({
|
|
|
|
|
|
|
|
textWidthOptions: {}
|
|
|
|
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleEditorActivity() {
|
|
|
|
handleEditorActivity() {
|
|
|
|
@@ -117,23 +128,23 @@ export default class CodeEditor extends React.Component {
|
|
|
|
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
|
|
|
|
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
|
|
|
|
if (active) {
|
|
|
|
if (active) {
|
|
|
|
if (this.extraKeysMode !== 'editor') {
|
|
|
|
if (this.extraKeysMode !== 'editor') {
|
|
|
|
this.extraKeysMode =
|
|
|
|
this.extraKeysMode = 'editor'
|
|
|
|
'editor' this.editor.setOption('extraKeys', this.editorKeyMap)
|
|
|
|
this.editor.setOption('extraKeys', this.editorKeyMap)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
else {
|
|
|
|
|
|
|
|
if (this.extraKeysMode !== 'default') {
|
|
|
|
if (this.extraKeysMode !== 'default') {
|
|
|
|
this.extraKeysMode =
|
|
|
|
this.extraKeysMode = 'default'
|
|
|
|
'default' this.editor
|
|
|
|
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
|
|
|
.setOption(
|
|
|
|
this.tableEditor.resetSmartCursor()
|
|
|
|
'extraKeys',
|
|
|
|
|
|
|
|
this.defaultKeyMap) this.tableEditor.resetSmartCursor()
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
componentDidMount() {
|
|
|
|
const {rulers, enableRulers} = this.props
|
|
|
|
const {
|
|
|
|
|
|
|
|
rulers,
|
|
|
|
|
|
|
|
enableRulers
|
|
|
|
|
|
|
|
} = this.props
|
|
|
|
const expandSnippet = this.expandSnippet.bind(this)
|
|
|
|
const expandSnippet = this.expandSnippet.bind(this)
|
|
|
|
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
|
|
|
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
|
|
|
|
|
|
|
|
|
|
|
@@ -141,36 +152,41 @@ export default class CodeEditor extends React.Component {
|
|
|
|
id: crypto.randomBytes(16).toString('hex'),
|
|
|
|
id: crypto.randomBytes(16).toString('hex'),
|
|
|
|
name: 'Dummy text',
|
|
|
|
name: 'Dummy text',
|
|
|
|
prefix: ['lorem', 'ipsum'],
|
|
|
|
prefix: ['lorem', 'ipsum'],
|
|
|
|
content:
|
|
|
|
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
|
|
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
|
|
|
}]
|
|
|
|
}] if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
|
|
|
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
|
|
|
fs.writeFileSync(
|
|
|
|
fs.writeFileSync(
|
|
|
|
consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
|
|
|
|
consts.SNIPPET_FILE,
|
|
|
|
|
|
|
|
JSON.stringify(defaultSnippet, null, 4),
|
|
|
|
|
|
|
|
'utf8'
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.defaultKeyMap =
|
|
|
|
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
|
|
|
|
CodeMirror
|
|
|
|
Tab: function (cm) {
|
|
|
|
.normalizeKeyMap({
|
|
|
|
|
|
|
|
Tab: function(cm) {
|
|
|
|
|
|
|
|
const cursor = cm.getCursor()
|
|
|
|
const cursor = cm.getCursor()
|
|
|
|
const line = cm.getLine(cursor.line)
|
|
|
|
const line = cm.getLine(cursor.line)
|
|
|
|
const cursorPosition = cursor.ch
|
|
|
|
const cursorPosition = cursor.ch
|
|
|
|
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
|
|
|
|
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
|
|
|
|
if (cm.somethingSelected()) cm.indentSelection('add') else {
|
|
|
|
if (cm.somethingSelected()) cm.indentSelection('add')
|
|
|
|
|
|
|
|
else {
|
|
|
|
const tabs = cm.getOption('indentWithTabs')
|
|
|
|
const tabs = cm.getOption('indentWithTabs')
|
|
|
|
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
|
|
|
|
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
|
|
|
|
cm.execCommand('goLineStart')
|
|
|
|
cm.execCommand('goLineStart')
|
|
|
|
if (tabs) {
|
|
|
|
if (tabs) {
|
|
|
|
cm.execCommand('insertTab')
|
|
|
|
cm.execCommand('insertTab')
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
cm.execCommand('insertSoftTab')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {cm.execCommand('insertSoftTab')} cm.execCommand(
|
|
|
|
cm.execCommand('goLineEnd')
|
|
|
|
'goLineEnd')
|
|
|
|
} else if (
|
|
|
|
}
|
|
|
|
!charBeforeCursor.match(/\t|\s|\r|\n/) &&
|
|
|
|
else if (
|
|
|
|
cursor.ch > 1
|
|
|
|
!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
|
|
|
|
) {
|
|
|
|
// text expansion on tab key if the char before is alphabet
|
|
|
|
// text expansion on tab key if the char before is alphabet
|
|
|
|
const snippets =
|
|
|
|
const snippets = JSON.parse(
|
|
|
|
JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
|
|
|
|
fs.readFileSync(consts.SNIPPET_FILE, 'utf8')
|
|
|
|
|
|
|
|
)
|
|
|
|
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
|
|
|
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
|
|
|
if (tabs) {
|
|
|
|
if (tabs) {
|
|
|
|
cm.execCommand('insertTab')
|
|
|
|
cm.execCommand('insertTab')
|
|
|
|
@@ -178,8 +194,7 @@ export default class CodeEditor extends React.Component {
|
|
|
|
cm.execCommand('insertSoftTab')
|
|
|
|
cm.execCommand('insertSoftTab')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
else {
|
|
|
|
|
|
|
|
if (tabs) {
|
|
|
|
if (tabs) {
|
|
|
|
cm.execCommand('insertTab')
|
|
|
|
cm.execCommand('insertTab')
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
@@ -188,7 +203,7 @@ export default class CodeEditor extends React.Component {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Cmd-T': function(cm) {
|
|
|
|
'Cmd-T': function (cm) {
|
|
|
|
// Do nothing
|
|
|
|
// Do nothing
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
|
|
|
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
|
|
|
@@ -200,8 +215,8 @@ export default class CodeEditor extends React.Component {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
this.value = this.props.value this.editor =
|
|
|
|
this.value = this.props.value
|
|
|
|
CodeMirror(this.refs.root, {
|
|
|
|
this.editor = CodeMirror(this.refs.root, {
|
|
|
|
rulers: buildCMRulers(rulers, enableRulers),
|
|
|
|
rulers: buildCMRulers(rulers, enableRulers),
|
|
|
|
value: this.props.value,
|
|
|
|
value: this.props.value,
|
|
|
|
lineNumbers: this.props.displayLineNumbers,
|
|
|
|
lineNumbers: this.props.displayLineNumbers,
|
|
|
|
@@ -227,13 +242,14 @@ export default class CodeEditor extends React.Component {
|
|
|
|
|
|
|
|
|
|
|
|
this.setMode(this.props.mode)
|
|
|
|
this.setMode(this.props.mode)
|
|
|
|
|
|
|
|
|
|
|
|
this.editor.on('focus', this.focusHandler) this.editor
|
|
|
|
this.editor.on('focus', this.focusHandler)
|
|
|
|
.on('blur', this.blurHandler) this.editor
|
|
|
|
this.editor.on('blur', this.blurHandler)
|
|
|
|
.on('change', this.changeHandler) this.editor.on(
|
|
|
|
this.editor.on('change', this.changeHandler)
|
|
|
|
'paste', this.pasteHandler)
|
|
|
|
this.editor.on('paste', this.pasteHandler)
|
|
|
|
eventEmitter.on('top:search', this.searchHandler)
|
|
|
|
eventEmitter.on('top:search', this.searchHandler)
|
|
|
|
|
|
|
|
|
|
|
|
eventEmitter.emit('code:init') this.editor.on('scroll', this.scrollHandler)
|
|
|
|
eventEmitter.emit('code:init')
|
|
|
|
|
|
|
|
this.editor.on('scroll', this.scrollHandler)
|
|
|
|
|
|
|
|
|
|
|
|
const editorTheme = document.getElementById('editorTheme')
|
|
|
|
const editorTheme = document.getElementById('editorTheme')
|
|
|
|
editorTheme.addEventListener('load', this.loadStyleHandler)
|
|
|
|
editorTheme.addEventListener('load', this.loadStyleHandler)
|
|
|
|
@@ -242,19 +258,17 @@ export default class CodeEditor extends React.Component {
|
|
|
|
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
|
|
|
|
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
|
|
|
|
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
|
|
|
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
|
|
|
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
|
|
|
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
|
|
|
CodeMirror.Vim
|
|
|
|
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
|
|
|
.map('ZZ', ':q', 'normal')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.textEditorInterface =
|
|
|
|
this.textEditorInterface = new TextEditorInterface(this.editor)
|
|
|
|
new TextEditorInterface(this.editor) this.tableEditor =
|
|
|
|
this.tableEditor = new TableEditor(this.textEditorInterface)
|
|
|
|
new TableEditor(this.textEditorInterface)
|
|
|
|
eventEmitter.on('code:format-table', this.formatTable)
|
|
|
|
eventEmitter
|
|
|
|
|
|
|
|
.on('code:format-table', this.formatTable)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.tableEditorOptions = options({smartCursor: true})
|
|
|
|
this.tableEditorOptions = options({
|
|
|
|
|
|
|
|
smartCursor: true
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
this.editorKeyMap =
|
|
|
|
this.editorKeyMap = CodeMirror.normalizeKeyMap({
|
|
|
|
CodeMirror.normalizeKeyMap({
|
|
|
|
|
|
|
|
'Tab': () => {
|
|
|
|
'Tab': () => {
|
|
|
|
this.tableEditor.nextCell(this.tableEditorOptions)
|
|
|
|
this.tableEditor.nextCell(this.tableEditorOptions)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@@ -271,36 +285,28 @@ export default class CodeEditor extends React.Component {
|
|
|
|
this.tableEditor.escape(this.tableEditorOptions)
|
|
|
|
this.tableEditor.escape(this.tableEditorOptions)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Shift-Ctrl-Left': () => {
|
|
|
|
'Shift-Ctrl-Left': () => {
|
|
|
|
this.tableEditor.alignColumn(
|
|
|
|
this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
|
|
|
|
Alignment.LEFT, this.tableEditorOptions)
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Shift-Cmd-Left': () => {
|
|
|
|
'Shift-Cmd-Left': () => {
|
|
|
|
this.tableEditor.alignColumn(
|
|
|
|
this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
|
|
|
|
Alignment.LEFT, this.tableEditorOptions)
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Shift-Ctrl-Right': () => {
|
|
|
|
'Shift-Ctrl-Right': () => {
|
|
|
|
this.tableEditor.alignColumn(
|
|
|
|
this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
|
|
|
|
Alignment.RIGHT, this.tableEditorOptions)
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Shift-Cmd-Right': () => {
|
|
|
|
'Shift-Cmd-Right': () => {
|
|
|
|
this.tableEditor.alignColumn(
|
|
|
|
this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
|
|
|
|
Alignment.RIGHT, this.tableEditorOptions)
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Shift-Ctrl-Up': () => {
|
|
|
|
'Shift-Ctrl-Up': () => {
|
|
|
|
this.tableEditor.alignColumn(
|
|
|
|
this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
|
|
|
|
Alignment.CENTER, this.tableEditorOptions)
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Shift-Cmd-Up': () => {
|
|
|
|
'Shift-Cmd-Up': () => {
|
|
|
|
this.tableEditor.alignColumn(
|
|
|
|
this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
|
|
|
|
Alignment.CENTER, this.tableEditorOptions)
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Shift-Ctrl-Down': () => {
|
|
|
|
'Shift-Ctrl-Down': () => {
|
|
|
|
this.tableEditor.alignColumn(
|
|
|
|
this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions)
|
|
|
|
Alignment.NONE, this.tableEditorOptions)
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Shift-Cmd-Down': () => {
|
|
|
|
'Shift-Cmd-Down': () => {
|
|
|
|
this.tableEditor.alignColumn(
|
|
|
|
this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions)
|
|
|
|
Alignment.NONE, this.tableEditorOptions)
|
|
|
|
|
|
|
|
},
|
|
|
|
},
|
|
|
|
'Ctrl-Left': () => {
|
|
|
|
'Ctrl-Left': () => {
|
|
|
|
this.tableEditor.moveFocus(0, -1, this.tableEditorOptions)
|
|
|
|
this.tableEditor.moveFocus(0, -1, this.tableEditorOptions)
|
|
|
|
@@ -377,16 +383,21 @@ export default class CodeEditor extends React.Component {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (this.props.enableTableEditor) {
|
|
|
|
if (this.props.enableTableEditor) {
|
|
|
|
this.editor.on('cursorActivity', this.editorActivityHandler) this.editor
|
|
|
|
this.editor.on('cursorActivity', this.editorActivityHandler)
|
|
|
|
.on('changes', this.editorActivityHandler)
|
|
|
|
this.editor.on('changes', this.editorActivityHandler)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.setState({clientWidth: this.refs.root.clientWidth})
|
|
|
|
this.setState({
|
|
|
|
|
|
|
|
clientWidth: this.refs.root.clientWidth
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
expandSnippet(line, cursor, cm, snippets) {
|
|
|
|
expandSnippet(line, cursor, cm, snippets) {
|
|
|
|
const wordBeforeCursor =
|
|
|
|
const wordBeforeCursor = this.getWordBeforeCursor(
|
|
|
|
this.getWordBeforeCursor(line, cursor.line, cursor.ch)
|
|
|
|
line,
|
|
|
|
|
|
|
|
cursor.line,
|
|
|
|
|
|
|
|
cursor.ch
|
|
|
|
|
|
|
|
)
|
|
|
|
const templateCursorString = ':{}'
|
|
|
|
const templateCursorString = ':{}'
|
|
|
|
for (let i = 0; i < snippets.length; i++) {
|
|
|
|
for (let i = 0; i < snippets.length; i++) {
|
|
|
|
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
|
|
|
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
|
|
|
@@ -401,7 +412,9 @@ export default class CodeEditor extends React.Component {
|
|
|
|
cursorLinePosition = cursorIndex
|
|
|
|
cursorLinePosition = cursorIndex
|
|
|
|
cm.replaceRange(
|
|
|
|
cm.replaceRange(
|
|
|
|
snippets[i].content.replace(templateCursorString, ''),
|
|
|
|
snippets[i].content.replace(templateCursorString, ''),
|
|
|
|
wordBeforeCursor.range.from, wordBeforeCursor.range.to)
|
|
|
|
wordBeforeCursor.range.from,
|
|
|
|
|
|
|
|
wordBeforeCursor.range.to
|
|
|
|
|
|
|
|
)
|
|
|
|
cm.setCursor({
|
|
|
|
cm.setCursor({
|
|
|
|
line: cursor.line + cursorLineNumber,
|
|
|
|
line: cursor.line + cursorLineNumber,
|
|
|
|
ch: cursorLinePosition
|
|
|
|
ch: cursorLinePosition
|
|
|
|
@@ -410,8 +423,10 @@ export default class CodeEditor extends React.Component {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
cm.replaceRange(
|
|
|
|
cm.replaceRange(
|
|
|
|
snippets[i].content, wordBeforeCursor.range.from,
|
|
|
|
snippets[i].content,
|
|
|
|
wordBeforeCursor.range.to)
|
|
|
|
wordBeforeCursor.range.from,
|
|
|
|
|
|
|
|
wordBeforeCursor.range.to
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -426,8 +441,7 @@ export default class CodeEditor extends React.Component {
|
|
|
|
const emptyChars = /\t|\s|\r|\n/
|
|
|
|
const emptyChars = /\t|\s|\r|\n/
|
|
|
|
|
|
|
|
|
|
|
|
// to prevent the word to expand is long that will crash the whole app
|
|
|
|
// to prevent the word to expand is long that will crash the whole app
|
|
|
|
// the safeStop is there to stop user to expand words that longer than 20
|
|
|
|
// the safeStop is there to stop user to expand words that longer than 20 chars
|
|
|
|
// chars
|
|
|
|
|
|
|
|
const safeStop = 20
|
|
|
|
const safeStop = 20
|
|
|
|
|
|
|
|
|
|
|
|
while (cursorPosition > 0) {
|
|
|
|
while (cursorPosition > 0) {
|
|
|
|
@@ -435,17 +449,25 @@ export default class CodeEditor extends React.Component {
|
|
|
|
// if char is not an empty char
|
|
|
|
// if char is not an empty char
|
|
|
|
if (!emptyChars.test(currentChar)) {
|
|
|
|
if (!emptyChars.test(currentChar)) {
|
|
|
|
wordBeforeCursor = currentChar + wordBeforeCursor
|
|
|
|
wordBeforeCursor = currentChar + wordBeforeCursor
|
|
|
|
}
|
|
|
|
} else if (wordBeforeCursor.length >= safeStop) {
|
|
|
|
else if (wordBeforeCursor.length >= safeStop) {
|
|
|
|
|
|
|
|
throw new Error('Your snippet trigger is too long !')
|
|
|
|
throw new Error('Your snippet trigger is too long !')
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {break} cursorPosition--
|
|
|
|
cursorPosition--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
text: wordBeforeCursor, range: {
|
|
|
|
text: wordBeforeCursor,
|
|
|
|
from: {line: lineNumber, ch: originCursorPosition},
|
|
|
|
range: {
|
|
|
|
to: {line: lineNumber, ch: cursorPosition}
|
|
|
|
from: {
|
|
|
|
|
|
|
|
line: lineNumber,
|
|
|
|
|
|
|
|
ch: originCursorPosition
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
to: {
|
|
|
|
|
|
|
|
line: lineNumber,
|
|
|
|
|
|
|
|
ch: cursorPosition
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -455,12 +477,12 @@ export default class CodeEditor extends React.Component {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
componentWillUnmount() {
|
|
|
|
this.editor.off('focus', this.focusHandler) this.editor
|
|
|
|
this.editor.off('focus', this.focusHandler)
|
|
|
|
.off('blur', this.blurHandler) this.editor
|
|
|
|
this.editor.off('blur', this.blurHandler)
|
|
|
|
.off('change', this.changeHandler) this.editor.off(
|
|
|
|
this.editor.off('change', this.changeHandler)
|
|
|
|
'paste', this.pasteHandler)
|
|
|
|
this.editor.off('paste', this.pasteHandler)
|
|
|
|
eventEmitter.off('top:search', this.searchHandler) this.editor.off(
|
|
|
|
eventEmitter.off('top:search', this.searchHandler)
|
|
|
|
'scroll', this.scrollHandler)
|
|
|
|
this.editor.off('scroll', this.scrollHandler)
|
|
|
|
const editorTheme = document.getElementById('editorTheme')
|
|
|
|
const editorTheme = document.getElementById('editorTheme')
|
|
|
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
|
|
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
|
|
|
|
|
|
|
|
|
|
|
@@ -469,7 +491,10 @@ export default class CodeEditor extends React.Component {
|
|
|
|
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
|
|
let needRefresh = false
|
|
|
|
let needRefresh = false
|
|
|
|
const {rulers, enableRulers} = this.props
|
|
|
|
const {
|
|
|
|
|
|
|
|
rulers,
|
|
|
|
|
|
|
|
enableRulers
|
|
|
|
|
|
|
|
} = this.props
|
|
|
|
if (prevProps.mode !== this.props.mode) {
|
|
|
|
if (prevProps.mode !== this.props.mode) {
|
|
|
|
this.setMode(this.props.mode)
|
|
|
|
this.setMode(this.props.mode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -487,14 +512,16 @@ export default class CodeEditor extends React.Component {
|
|
|
|
needRefresh = true
|
|
|
|
needRefresh = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (prevProps.enableRulers !== enableRulers ||
|
|
|
|
if (
|
|
|
|
prevProps.rulers !== rulers) {
|
|
|
|
prevProps.enableRulers !== enableRulers ||
|
|
|
|
|
|
|
|
prevProps.rulers !== rulers
|
|
|
|
|
|
|
|
) {
|
|
|
|
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
|
|
|
|
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (prevProps.indentSize !== this.props.indentSize) {
|
|
|
|
if (prevProps.indentSize !== this.props.indentSize) {
|
|
|
|
this.editor.setOption('indentUnit', this.props.indentSize) this.editor
|
|
|
|
this.editor.setOption('indentUnit', this.props.indentSize)
|
|
|
|
.setOption('tabSize', this.props.indentSize)
|
|
|
|
this.editor.setOption('tabSize', this.props.indentSize)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (prevProps.indentType !== this.props.indentType) {
|
|
|
|
if (prevProps.indentType !== this.props.indentType) {
|
|
|
|
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
|
|
|
|
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
|
|
|
|
@@ -511,7 +538,7 @@ export default class CodeEditor extends React.Component {
|
|
|
|
if (prevProps.matchingPairs !== this.props.matchingPairs ||
|
|
|
|
if (prevProps.matchingPairs !== this.props.matchingPairs ||
|
|
|
|
prevProps.matchingTriples !== this.props.matchingTriples ||
|
|
|
|
prevProps.matchingTriples !== this.props.matchingTriples ||
|
|
|
|
prevProps.explodingPairs !== this.props.explodingPairs) {
|
|
|
|
prevProps.explodingPairs !== this.props.explodingPairs) {
|
|
|
|
const bracketObject = {
|
|
|
|
const bracketObject ={
|
|
|
|
pairs: this.props.matchingPairs,
|
|
|
|
pairs: this.props.matchingPairs,
|
|
|
|
triples: this.props.matchingTriples,
|
|
|
|
triples: this.props.matchingTriples,
|
|
|
|
explode: this.props.explodingPairs,
|
|
|
|
explode: this.props.explodingPairs,
|
|
|
|
@@ -522,19 +549,21 @@ export default class CodeEditor extends React.Component {
|
|
|
|
|
|
|
|
|
|
|
|
if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
|
|
|
|
if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
|
|
|
|
if (this.props.enableTableEditor) {
|
|
|
|
if (this.props.enableTableEditor) {
|
|
|
|
this.editor.on('cursorActivity', this.editorActivityHandler) this.editor
|
|
|
|
this.editor.on('cursorActivity', this.editorActivityHandler)
|
|
|
|
.on('changes', this.editorActivityHandler)
|
|
|
|
this.editor.on('changes', this.editorActivityHandler)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
this.editor.off('cursorActivity', this.editorActivityHandler) this
|
|
|
|
this.editor.off('cursorActivity', this.editorActivityHandler)
|
|
|
|
.editor.off('changes', this.editorActivityHandler)
|
|
|
|
this.editor.off('changes', this.editorActivityHandler)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.extraKeysMode =
|
|
|
|
this.extraKeysMode = 'default'
|
|
|
|
'default' this.editor.setOption('extraKeys', this.defaultKeyMap)
|
|
|
|
this.editor.setOption('extraKeys', this.defaultKeyMap)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (this.state.clientWidth !== this.refs.root.clientWidth) {
|
|
|
|
if (this.state.clientWidth !== this.refs.root.clientWidth) {
|
|
|
|
this.setState({clientWidth: this.refs.root.clientWidth})
|
|
|
|
this.setState({
|
|
|
|
|
|
|
|
clientWidth: this.refs.root.clientWidth
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
needRefresh = true
|
|
|
|
needRefresh = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -546,9 +575,7 @@ export default class CodeEditor extends React.Component {
|
|
|
|
|
|
|
|
|
|
|
|
setMode(mode) {
|
|
|
|
setMode(mode) {
|
|
|
|
let syntax = CodeMirror.findModeByName(convertModeName(mode))
|
|
|
|
let syntax = CodeMirror.findModeByName(convertModeName(mode))
|
|
|
|
if (syntax == null) syntax =
|
|
|
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
|
|
|
CodeMirror
|
|
|
|
|
|
|
|
.findModeByName('Plain Text')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.editor.setOption('mode', syntax.mime)
|
|
|
|
this.editor.setOption('mode', syntax.mime)
|
|
|
|
CodeMirror.autoLoadMode(this.editor, syntax.mode)
|
|
|
|
CodeMirror.autoLoadMode(this.editor, syntax.mode)
|
|
|
|
@@ -564,7 +591,11 @@ export default class CodeEditor extends React.Component {
|
|
|
|
moveCursorTo(row, col) {}
|
|
|
|
moveCursorTo(row, col) {}
|
|
|
|
|
|
|
|
|
|
|
|
scrollToLine(event, num) {
|
|
|
|
scrollToLine(event, num) {
|
|
|
|
const cursor = { line: num, ch: 1 } this.editor.setCursor(cursor)
|
|
|
|
const cursor = {
|
|
|
|
|
|
|
|
line: num,
|
|
|
|
|
|
|
|
ch: 1
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.editor.setCursor(cursor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
focus() {
|
|
|
|
focus() {
|
|
|
|
@@ -577,22 +608,32 @@ export default class CodeEditor extends React.Component {
|
|
|
|
|
|
|
|
|
|
|
|
reload() {
|
|
|
|
reload() {
|
|
|
|
// Change event shouldn't be fired when switch note
|
|
|
|
// Change event shouldn't be fired when switch note
|
|
|
|
this.editor.off('change', this.changeHandler) this.value =
|
|
|
|
this.editor.off('change', this.changeHandler)
|
|
|
|
this.props.value this.editor.setValue(this.props.value) this.editor
|
|
|
|
this.value = this.props.value
|
|
|
|
.clearHistory() this.editor.on('change', this.changeHandler) this
|
|
|
|
this.editor.setValue(this.props.value)
|
|
|
|
.editor.refresh()
|
|
|
|
this.editor.clearHistory()
|
|
|
|
|
|
|
|
this.editor.on('change', this.changeHandler)
|
|
|
|
|
|
|
|
this.editor.refresh()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setValue(value) {
|
|
|
|
setValue(value) {
|
|
|
|
const cursor = this.editor.getCursor() this.editor.setValue(value) this
|
|
|
|
const cursor = this.editor.getCursor()
|
|
|
|
.editor.setCursor(cursor)
|
|
|
|
this.editor.setValue(value)
|
|
|
|
|
|
|
|
this.editor.setCursor(cursor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleDropImage(dropEvent) {
|
|
|
|
handleDropImage(dropEvent) {
|
|
|
|
dropEvent.preventDefault()
|
|
|
|
dropEvent.preventDefault()
|
|
|
|
const {storageKey, noteKey} = this.props
|
|
|
|
const {
|
|
|
|
|
|
|
|
storageKey,
|
|
|
|
|
|
|
|
noteKey
|
|
|
|
|
|
|
|
} = this.props
|
|
|
|
attachmentManagement.handleAttachmentDrop(
|
|
|
|
attachmentManagement.handleAttachmentDrop(
|
|
|
|
this, storageKey, noteKey, dropEvent)
|
|
|
|
this,
|
|
|
|
|
|
|
|
storageKey,
|
|
|
|
|
|
|
|
noteKey,
|
|
|
|
|
|
|
|
dropEvent
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
insertAttachmentMd(imageMd) {
|
|
|
|
insertAttachmentMd(imageMd) {
|
|
|
|
@@ -601,43 +642,59 @@ export default class CodeEditor extends React.Component {
|
|
|
|
|
|
|
|
|
|
|
|
handlePaste(editor, e) {
|
|
|
|
handlePaste(editor, e) {
|
|
|
|
const clipboardData = e.clipboardData
|
|
|
|
const clipboardData = e.clipboardData
|
|
|
|
const {storageKey, noteKey} = this.props
|
|
|
|
const {
|
|
|
|
const dataTransferItem = clipboardData.items[0] const pastedTxt =
|
|
|
|
storageKey,
|
|
|
|
clipboardData.getData('text')
|
|
|
|
noteKey
|
|
|
|
const isURL =
|
|
|
|
} = this.props
|
|
|
|
str => {
|
|
|
|
const dataTransferItem = clipboardData.items[0]
|
|
|
|
const matcher =
|
|
|
|
const pastedTxt = clipboardData.getData('text')
|
|
|
|
/^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
|
|
|
const isURL = str => {
|
|
|
|
|
|
|
|
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
|
|
|
return matcher.test(str)
|
|
|
|
return matcher.test(str)
|
|
|
|
} const isInLinkTag =
|
|
|
|
}
|
|
|
|
editor => {
|
|
|
|
const isInLinkTag = editor => {
|
|
|
|
const startCursor = editor.getCursor('start')
|
|
|
|
const startCursor = editor.getCursor('start')
|
|
|
|
const prevChar = editor.getRange(
|
|
|
|
const prevChar = editor.getRange({
|
|
|
|
{line: startCursor.line, ch: startCursor.ch - 2},
|
|
|
|
line: startCursor.line,
|
|
|
|
{line: startCursor.line, ch: startCursor.ch})
|
|
|
|
ch: startCursor.ch - 2
|
|
|
|
|
|
|
|
}, {
|
|
|
|
|
|
|
|
line: startCursor.line,
|
|
|
|
|
|
|
|
ch: startCursor.ch
|
|
|
|
|
|
|
|
})
|
|
|
|
const endCursor = editor.getCursor('end')
|
|
|
|
const endCursor = editor.getCursor('end')
|
|
|
|
const nextChar = editor.getRange(
|
|
|
|
const nextChar = editor.getRange({
|
|
|
|
{line: endCursor.line, ch: endCursor.ch},
|
|
|
|
line: endCursor.line,
|
|
|
|
{line: endCursor.line, ch: endCursor.ch + 1})
|
|
|
|
ch: endCursor.ch
|
|
|
|
|
|
|
|
}, {
|
|
|
|
|
|
|
|
line: endCursor.line,
|
|
|
|
|
|
|
|
ch: endCursor.ch + 1
|
|
|
|
|
|
|
|
})
|
|
|
|
return prevChar === '](' && nextChar === ')'
|
|
|
|
return prevChar === '](' && nextChar === ')'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const pastedHtml = clipboardData.getData('text/html')
|
|
|
|
const pastedHtml = clipboardData.getData('text/html')
|
|
|
|
if (pastedHtml !== '') {
|
|
|
|
if (pastedHtml !== '') {
|
|
|
|
this.handlePasteHtml(e, editor, pastedHtml)
|
|
|
|
this.handlePasteHtml(e, editor, pastedHtml)
|
|
|
|
}
|
|
|
|
} else if (dataTransferItem.type.match('image')) {
|
|
|
|
else if (dataTransferItem.type.match('image')) {
|
|
|
|
|
|
|
|
attachmentManagement.handlePastImageEvent(
|
|
|
|
attachmentManagement.handlePastImageEvent(
|
|
|
|
this, storageKey, noteKey, dataTransferItem)
|
|
|
|
this,
|
|
|
|
}
|
|
|
|
storageKey,
|
|
|
|
else if (
|
|
|
|
noteKey,
|
|
|
|
this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
|
|
|
dataTransferItem
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
|
|
this.props.fetchUrlTitle &&
|
|
|
|
|
|
|
|
isURL(pastedTxt) &&
|
|
|
|
|
|
|
|
!isInLinkTag(editor)
|
|
|
|
|
|
|
|
) {
|
|
|
|
this.handlePasteUrl(e, editor, pastedTxt)
|
|
|
|
this.handlePasteUrl(e, editor, pastedTxt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
|
|
|
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
|
|
|
attachmentManagement
|
|
|
|
attachmentManagement
|
|
|
|
.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
|
|
|
.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
|
|
|
.then(modifiedText => {this.editor.replaceSelection(modifiedText)})
|
|
|
|
.then(modifiedText => {
|
|
|
|
|
|
|
|
this.editor.replaceSelection(modifiedText)
|
|
|
|
|
|
|
|
})
|
|
|
|
e.preventDefault()
|
|
|
|
e.preventDefault()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -653,36 +710,39 @@ export default class CodeEditor extends React.Component {
|
|
|
|
const taggedUrl = `<${pastedTxt}>`
|
|
|
|
const taggedUrl = `<${pastedTxt}>`
|
|
|
|
editor.replaceSelection(taggedUrl)
|
|
|
|
editor.replaceSelection(taggedUrl)
|
|
|
|
|
|
|
|
|
|
|
|
const isImageReponse =
|
|
|
|
const isImageReponse = response => {
|
|
|
|
response => {
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
response.headers.has('content-type') &&
|
|
|
|
response.headers.has('content-type') &&
|
|
|
|
response.headers.get('content-type').match(/^image\/.+$/))
|
|
|
|
response.headers.get('content-type').match(/^image\/.+$/)
|
|
|
|
} const replaceTaggedUrl =
|
|
|
|
)
|
|
|
|
replacement => {
|
|
|
|
}
|
|
|
|
|
|
|
|
const replaceTaggedUrl = replacement => {
|
|
|
|
const value = editor.getValue()
|
|
|
|
const value = editor.getValue()
|
|
|
|
const cursor = editor.getCursor()
|
|
|
|
const cursor = editor.getCursor()
|
|
|
|
const newValue = value.replace(taggedUrl, replacement)
|
|
|
|
const newValue = value.replace(taggedUrl, replacement)
|
|
|
|
const newCursor = Object.assign(
|
|
|
|
const newCursor = Object.assign({}, cursor, {
|
|
|
|
{}, cursor, {ch: cursor.ch + newValue.length - value.length})
|
|
|
|
ch: cursor.ch + newValue.length - value.length
|
|
|
|
|
|
|
|
})
|
|
|
|
editor.setValue(newValue)
|
|
|
|
editor.setValue(newValue)
|
|
|
|
editor.setCursor(newCursor)
|
|
|
|
editor.setCursor(newCursor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fetch(pastedTxt, {method: 'get'})
|
|
|
|
fetch(pastedTxt, {
|
|
|
|
|
|
|
|
method: 'get'
|
|
|
|
|
|
|
|
})
|
|
|
|
.then(response => {
|
|
|
|
.then(response => {
|
|
|
|
if (isImageReponse(response)) {
|
|
|
|
if (isImageReponse(response)) {
|
|
|
|
return this.mapImageResponse(
|
|
|
|
return this.mapImageResponse(response, pastedTxt)
|
|
|
|
response, pastedTxt)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return this.mapNormalResponse(
|
|
|
|
return this.mapNormalResponse(response, pastedTxt)
|
|
|
|
response, pastedTxt)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.then(
|
|
|
|
.then(replacement => {
|
|
|
|
replacement => {
|
|
|
|
replaceTaggedUrl(replacement)
|
|
|
|
replaceTaggedUrl(replacement)})
|
|
|
|
})
|
|
|
|
.catch(e => {replaceTaggedUrl(pastedTxt)})
|
|
|
|
.catch(e => {
|
|
|
|
|
|
|
|
replaceTaggedUrl(pastedTxt)
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handlePasteHtml(e, editor, pastedHtml) {
|
|
|
|
handlePasteHtml(e, editor, pastedHtml) {
|
|
|
|
@@ -692,21 +752,23 @@ export default class CodeEditor extends React.Component {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mapNormalResponse(response, pastedTxt) {
|
|
|
|
mapNormalResponse(response, pastedTxt) {
|
|
|
|
return this.decodeResponse(response).then(
|
|
|
|
return this.decodeResponse(response).then(body => {
|
|
|
|
body => {return new Promise((resolve, reject) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const parsedBody =
|
|
|
|
const parsedBody = new window.DOMParser().parseFromString(
|
|
|
|
new window.DOMParser().parseFromString(body, 'text/html')
|
|
|
|
body,
|
|
|
|
const escapePipe =
|
|
|
|
'text/html'
|
|
|
|
(str) => {
|
|
|
|
)
|
|
|
|
|
|
|
|
const escapePipe = (str) => {
|
|
|
|
return str.replace('|', '\\|')
|
|
|
|
return str.replace('|', '\\|')
|
|
|
|
} const linkWithTitle =
|
|
|
|
}
|
|
|
|
`[${escapePipe(parsedBody.title)}](${pastedTxt})`
|
|
|
|
const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})`
|
|
|
|
resolve(linkWithTitle)
|
|
|
|
resolve(linkWithTitle)
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
reject(e)
|
|
|
|
reject(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})})
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mapImageResponse(response, pastedTxt) {
|
|
|
|
mapImageResponse(response, pastedTxt) {
|
|
|
|
@@ -727,28 +789,37 @@ export default class CodeEditor extends React.Component {
|
|
|
|
const _charset = headers.has('content-type') ?
|
|
|
|
const _charset = headers.has('content-type') ?
|
|
|
|
this.extractContentTypeCharset(headers.get('content-type')) :
|
|
|
|
this.extractContentTypeCharset(headers.get('content-type')) :
|
|
|
|
undefined
|
|
|
|
undefined
|
|
|
|
return response.arrayBuffer().then(
|
|
|
|
return response.arrayBuffer().then(buff => {
|
|
|
|
buff => {return new Promise((resolve, reject) => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const charset =
|
|
|
|
const charset = _charset !== undefined &&
|
|
|
|
_charset !== undefined && iconv.encodingExists(_charset) ?
|
|
|
|
iconv.encodingExists(_charset) ?
|
|
|
|
_charset :
|
|
|
|
_charset :
|
|
|
|
'utf-8'
|
|
|
|
'utf-8'
|
|
|
|
resolve(iconv.decode(new Buffer(buff), charset).toString())
|
|
|
|
resolve(iconv.decode(new Buffer(buff), charset).toString())
|
|
|
|
} catch (e) {
|
|
|
|
} catch (e) {
|
|
|
|
reject(e)
|
|
|
|
reject(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})})
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extractContentTypeCharset(contentType) {
|
|
|
|
extractContentTypeCharset(contentType) {
|
|
|
|
return contentType.split(';')
|
|
|
|
return contentType
|
|
|
|
.filter(str => {return str.trim().toLowerCase().startsWith('charset')})
|
|
|
|
.split(';')
|
|
|
|
.map(str => {return str.replace(/['"]/g, '').split('=')[1]})[0]
|
|
|
|
.filter(str => {
|
|
|
|
|
|
|
|
return str.trim().toLowerCase().startsWith('charset')
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.map(str => {
|
|
|
|
|
|
|
|
return str.replace(/['"]/g, '').split('=')[1]
|
|
|
|
|
|
|
|
})[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
render() {
|
|
|
|
const {className, fontSize} = this.props
|
|
|
|
const {
|
|
|
|
|
|
|
|
className,
|
|
|
|
|
|
|
|
fontSize
|
|
|
|
|
|
|
|
} = this.props
|
|
|
|
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
|
|
|
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
|
|
|
const width = this.props.width
|
|
|
|
const width = this.props.width
|
|
|
|
return ( <
|
|
|
|
return ( <
|
|
|
|
@@ -758,10 +829,16 @@ export default class CodeEditor extends React.Component {
|
|
|
|
ref = 'root'
|
|
|
|
ref = 'root'
|
|
|
|
tabIndex = '-1'
|
|
|
|
tabIndex = '-1'
|
|
|
|
style = {
|
|
|
|
style = {
|
|
|
|
{ fontFamily, fontSize: fontSize, width: width }
|
|
|
|
{
|
|
|
|
} onDrop = {
|
|
|
|
fontFamily,
|
|
|
|
|
|
|
|
fontSize: fontSize,
|
|
|
|
|
|
|
|
width: width
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
onDrop = {
|
|
|
|
e => this.handleDropImage(e)
|
|
|
|
e => this.handleDropImage(e)
|
|
|
|
} />
|
|
|
|
}
|
|
|
|
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|