1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

Choosing which characters to match and explode

This commit is contained in:
Guilherme Silva
2018-11-08 13:18:13 +00:00
parent 59d31c9a18
commit ab65fb7a5c
7 changed files with 516 additions and 540 deletions

View File

@@ -1,50 +1,42 @@
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 React from 'react'
import _ from 'lodash'
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')
const {ipcRenderer} = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
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'
const buildCMRulers = (rulers, enableRulers) =>
(enableRulers ? rulers.map(ruler => ({
column: ruler
})) : [])
(enableRulers ? rulers.map(ruler => ({column: ruler})) : [])
export default class CodeEditor extends React.Component {
constructor(props) {
super(props)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
leading: false,
trailing: true
})
this.changeHandler = e => this.handleChange(e)
this.focusHandler = () => {
this.scrollHandler =
_.debounce(
this.handleScroll.bind(this), 100,
{leading: false, trailing: true}) this.changeHandler = e =>
this.handleChange(e) this.focusHandler =
() => {
ipcRenderer.send('editor:focused', true)
}
this.blurHandler = (editor, e) => {
} this.blurHandler =
(editor, e) => {
ipcRenderer.send('editor:focused', false)
if (e == null) return null
let el = e.relatedTarget
@@ -56,33 +48,31 @@ export default class CodeEditor extends React.Component {
}
this.props.onBlur != null && this.props.onBlur(e)
const {
storageKey,
noteKey
} = this.props
const {storageKey, noteKey} = this.props
attachmentManagement.deleteAttachmentsNotPresentInNote(
this.editor.getValue(),
storageKey,
noteKey
)
}
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
this.loadStyleHandler = e => {
this.editor.getValue(), storageKey, noteKey)
} this.pasteHandler = (editor, e) =>
this.handlePaste(editor, e) this.loadStyleHandler =
e => {
this.editor.refresh()
}
this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null
this.scrollToLineHandeler = this.scrollToLine.bind(this)
} this.searchHandler = (e, msg) =>
this.handleSearch(msg) this.searchState =
null this.scrollToLineHandeler =
this.scrollToLine
.bind(this)
this.formatTable = () => this.handleFormatTable()
this.editorActivityHandler = () => this.handleEditorActivity()
this.formatTable = () =>
this.handleFormatTable() this
.editorActivityHandler = () =>
this.handleEditorActivity()
}
handleSearch(msg) {
const cm = this.editor
const component = this
if (component.searchState) cm.removeOverlay(component.searchState)
if (component.searchState)
cm.removeOverlay(component.searchState)
if (msg.length < 3) return
cm.operation(function() {
@@ -92,8 +82,7 @@ export default class CodeEditor extends React.Component {
function makeOverlay(query, style) {
query = new RegExp(
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
'gi'
)
'gi')
return {
token: function(stream) {
query.lastIndex = stream.pos
@@ -101,9 +90,11 @@ export default class CodeEditor extends React.Component {
if (match && match.index === stream.pos) {
stream.pos += match[0].length || 1
return style
} else if (match) {
}
else if (match) {
stream.pos = match.index
} else {
}
else {
stream.skipToEnd()
}
}
@@ -113,9 +104,7 @@ export default class CodeEditor extends React.Component {
}
handleFormatTable() {
this.tableEditor.formatAll(options({
textWidthOptions: {}
}))
this.tableEditor.formatAll(options({textWidthOptions: {}}))
}
handleEditorActivity() {
@@ -128,23 +117,23 @@ export default class CodeEditor extends React.Component {
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
if (active) {
if (this.extraKeysMode !== 'editor') {
this.extraKeysMode = 'editor'
this.editor.setOption('extraKeys', this.editorKeyMap)
this.extraKeysMode =
'editor' this.editor.setOption('extraKeys', this.editorKeyMap)
}
} else {
}
else {
if (this.extraKeysMode !== 'default') {
this.extraKeysMode = 'default'
this.editor.setOption('extraKeys', this.defaultKeyMap)
this.tableEditor.resetSmartCursor()
this.extraKeysMode =
'default' this.editor
.setOption(
'extraKeys',
this.defaultKeyMap) this.tableEditor.resetSmartCursor()
}
}
}
componentDidMount() {
const {
rulers,
enableRulers
} = this.props
const {rulers, enableRulers} = this.props
const expandSnippet = this.expandSnippet.bind(this)
eventEmitter.on('line:jump', this.scrollToLineHandeler)
@@ -152,41 +141,36 @@ export default class CodeEditor extends React.Component {
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
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.'
}]
if (!fs.existsSync(consts.SNIPPET_FILE)) {
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.'
}] if (!fs.existsSync(consts.SNIPPET_FILE)) {
fs.writeFileSync(
consts.SNIPPET_FILE,
JSON.stringify(defaultSnippet, null, 4),
'utf8'
)
consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
}
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
this.defaultKeyMap =
CodeMirror
.normalizeKeyMap({
Tab: function(cm) {
const cursor = cm.getCursor()
const line = cm.getLine(cursor.line)
const cursorPosition = cursor.ch
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')
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
cm.execCommand('goLineStart')
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
cm.execCommand('goLineEnd')
} else if (
!charBeforeCursor.match(/\t|\s|\r|\n/) &&
cursor.ch > 1
) {
else {cm.execCommand('insertSoftTab')} cm.execCommand(
'goLineEnd')
}
else if (
!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
// text expansion on tab key if the char before is alphabet
const snippets = JSON.parse(
fs.readFileSync(consts.SNIPPET_FILE, 'utf8')
)
const snippets =
JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
if (expandSnippet(line, cursor, cm, snippets) === false) {
if (tabs) {
cm.execCommand('insertTab')
@@ -194,7 +178,8 @@ export default class CodeEditor extends React.Component {
cm.execCommand('insertSoftTab')
}
}
} else {
}
else {
if (tabs) {
cm.execCommand('insertTab')
} else {
@@ -215,8 +200,8 @@ export default class CodeEditor extends React.Component {
}
})
this.value = this.props.value
this.editor = CodeMirror(this.refs.root, {
this.value = this.props.value this.editor =
CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value,
lineNumbers: this.props.displayLineNumbers,
@@ -231,30 +216,24 @@ export default class CodeEditor extends React.Component {
dragDrop: false,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: (this.props.enableBracketMatching ? {
pairs: '()[]{}\'\'""$$**``ll',
triples: '```"""\'\'\'',
explode: '[]{}``$$',
autoCloseBrackets: {
pairs: this.props.matchingPairs,
triples: this.props.matchingTriples,
explode: this.props.explodingPairs,
override: true
} : {
pairs: '',
triples: '',
explode: '',
override: true
}),
},
extraKeys: this.defaultKeyMap
})
this.setMode(this.props.mode)
this.editor.on('focus', this.focusHandler)
this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler)
this.editor.on('paste', this.pasteHandler)
this.editor.on('focus', this.focusHandler) this.editor
.on('blur', this.blurHandler) this.editor
.on('change', this.changeHandler) this.editor.on(
'paste', this.pasteHandler)
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')
editorTheme.addEventListener('load', this.loadStyleHandler)
@@ -263,17 +242,19 @@ export default class CodeEditor extends React.Component {
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
CodeMirror.Vim.map('ZZ', ':q', 'normal')
CodeMirror.Vim
.map('ZZ', ':q', 'normal')
this.textEditorInterface = new TextEditorInterface(this.editor)
this.tableEditor = new TableEditor(this.textEditorInterface)
eventEmitter.on('code:format-table', this.formatTable)
this.textEditorInterface =
new TextEditorInterface(this.editor) this.tableEditor =
new TableEditor(this.textEditorInterface)
eventEmitter
.on('code:format-table', this.formatTable)
this.tableEditorOptions = options({
smartCursor: true
})
this.tableEditorOptions = options({smartCursor: true})
this.editorKeyMap = CodeMirror.normalizeKeyMap({
this.editorKeyMap =
CodeMirror.normalizeKeyMap({
'Tab': () => {
this.tableEditor.nextCell(this.tableEditorOptions)
},
@@ -290,28 +271,36 @@ export default class CodeEditor extends React.Component {
this.tableEditor.escape(this.tableEditorOptions)
},
'Shift-Ctrl-Left': () => {
this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
this.tableEditor.alignColumn(
Alignment.LEFT, this.tableEditorOptions)
},
'Shift-Cmd-Left': () => {
this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions)
this.tableEditor.alignColumn(
Alignment.LEFT, this.tableEditorOptions)
},
'Shift-Ctrl-Right': () => {
this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
this.tableEditor.alignColumn(
Alignment.RIGHT, this.tableEditorOptions)
},
'Shift-Cmd-Right': () => {
this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions)
this.tableEditor.alignColumn(
Alignment.RIGHT, this.tableEditorOptions)
},
'Shift-Ctrl-Up': () => {
this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
this.tableEditor.alignColumn(
Alignment.CENTER, this.tableEditorOptions)
},
'Shift-Cmd-Up': () => {
this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions)
this.tableEditor.alignColumn(
Alignment.CENTER, this.tableEditorOptions)
},
'Shift-Ctrl-Down': () => {
this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions)
this.tableEditor.alignColumn(
Alignment.NONE, this.tableEditorOptions)
},
'Shift-Cmd-Down': () => {
this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions)
this.tableEditor.alignColumn(
Alignment.NONE, this.tableEditorOptions)
},
'Ctrl-Left': () => {
this.tableEditor.moveFocus(0, -1, this.tableEditorOptions)
@@ -388,21 +377,16 @@ export default class CodeEditor extends React.Component {
})
if (this.props.enableTableEditor) {
this.editor.on('cursorActivity', this.editorActivityHandler)
this.editor.on('changes', this.editorActivityHandler)
this.editor.on('cursorActivity', 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) {
const wordBeforeCursor = this.getWordBeforeCursor(
line,
cursor.line,
cursor.ch
)
const wordBeforeCursor =
this.getWordBeforeCursor(line, cursor.line, cursor.ch)
const templateCursorString = ':{}'
for (let i = 0; i < snippets.length; i++) {
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
@@ -417,9 +401,7 @@ export default class CodeEditor extends React.Component {
cursorLinePosition = cursorIndex
cm.replaceRange(
snippets[i].content.replace(templateCursorString, ''),
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
wordBeforeCursor.range.from, wordBeforeCursor.range.to)
cm.setCursor({
line: cursor.line + cursorLineNumber,
ch: cursorLinePosition
@@ -428,10 +410,8 @@ export default class CodeEditor extends React.Component {
}
} else {
cm.replaceRange(
snippets[i].content,
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
snippets[i].content, wordBeforeCursor.range.from,
wordBeforeCursor.range.to)
}
return true
}
@@ -446,7 +426,8 @@ export default class CodeEditor extends React.Component {
const emptyChars = /\t|\s|\r|\n/
// 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 chars
// the safeStop is there to stop user to expand words that longer than 20
// chars
const safeStop = 20
while (cursorPosition > 0) {
@@ -454,25 +435,17 @@ export default class CodeEditor extends React.Component {
// if char is not an empty char
if (!emptyChars.test(currentChar)) {
wordBeforeCursor = currentChar + wordBeforeCursor
} else if (wordBeforeCursor.length >= safeStop) {
throw new Error('Your snippet trigger is too long !')
} else {
break
}
cursorPosition--
else if (wordBeforeCursor.length >= safeStop) {
throw new Error('Your snippet trigger is too long !')
}
else {break} cursorPosition--
}
return {
text: wordBeforeCursor,
range: {
from: {
line: lineNumber,
ch: originCursorPosition
},
to: {
line: lineNumber,
ch: cursorPosition
}
text: wordBeforeCursor, range: {
from: {line: lineNumber, ch: originCursorPosition},
to: {line: lineNumber, ch: cursorPosition}
}
}
}
@@ -482,12 +455,12 @@ export default class CodeEditor extends React.Component {
}
componentWillUnmount() {
this.editor.off('focus', this.focusHandler)
this.editor.off('blur', this.blurHandler)
this.editor.off('change', this.changeHandler)
this.editor.off('paste', this.pasteHandler)
eventEmitter.off('top:search', this.searchHandler)
this.editor.off('scroll', this.scrollHandler)
this.editor.off('focus', this.focusHandler) this.editor
.off('blur', this.blurHandler) this.editor
.off('change', this.changeHandler) this.editor.off(
'paste', this.pasteHandler)
eventEmitter.off('top:search', this.searchHandler) this.editor.off(
'scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler)
@@ -496,10 +469,7 @@ export default class CodeEditor extends React.Component {
componentDidUpdate(prevProps, prevState) {
let needRefresh = false
const {
rulers,
enableRulers
} = this.props
const {rulers, enableRulers} = this.props
if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode)
}
@@ -517,16 +487,14 @@ export default class CodeEditor extends React.Component {
needRefresh = true
}
if (
prevProps.enableRulers !== enableRulers ||
prevProps.rulers !== rulers
) {
if (prevProps.enableRulers !== enableRulers ||
prevProps.rulers !== rulers) {
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
}
if (prevProps.indentSize !== this.props.indentSize) {
this.editor.setOption('indentUnit', this.props.indentSize)
this.editor.setOption('tabSize', this.props.indentSize)
this.editor.setOption('indentUnit', this.props.indentSize) this.editor
.setOption('tabSize', this.props.indentSize)
}
if (prevProps.indentType !== this.props.indentType) {
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
@@ -540,38 +508,33 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
}
if (prevProps.enableBracketMatching !== this.props.enableBracketMatching) {
const bracketObject = (this.props.enableBracketMatching ? {
pairs: '()[]{}\'\'""$$**``ll',
triples: '```"""\'\'\'',
explode: '[]{}``$$',
if (prevProps.matchingPairs !== this.props.matchingPairs ||
prevProps.matchingTriples !== this.props.matchingTriples ||
prevProps.explodingPairs !== this.props.explodingPairs) {
const bracketObject = {
pairs: this.props.matchingPairs,
triples: this.props.matchingTriples,
explode: this.props.explodingPairs,
override: true
} : {
pairs: '',
triples: '',
explode: '',
override: true
});
}
this.editor.setOption('autoCloseBrackets', bracketObject)
}
if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
if (this.props.enableTableEditor) {
this.editor.on('cursorActivity', this.editorActivityHandler)
this.editor.on('changes', this.editorActivityHandler)
this.editor.on('cursorActivity', this.editorActivityHandler) this.editor
.on('changes', this.editorActivityHandler)
} else {
this.editor.off('cursorActivity', this.editorActivityHandler)
this.editor.off('changes', this.editorActivityHandler)
this.editor.off('cursorActivity', this.editorActivityHandler) this
.editor.off('changes', this.editorActivityHandler)
}
this.extraKeysMode = 'default'
this.editor.setOption('extraKeys', this.defaultKeyMap)
this.extraKeysMode =
'default' this.editor.setOption('extraKeys', this.defaultKeyMap)
}
if (this.state.clientWidth !== this.refs.root.clientWidth) {
this.setState({
clientWidth: this.refs.root.clientWidth
})
this.setState({clientWidth: this.refs.root.clientWidth})
needRefresh = true
}
@@ -583,7 +546,9 @@ export default class CodeEditor extends React.Component {
setMode(mode) {
let syntax = CodeMirror.findModeByName(convertModeName(mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
if (syntax == null) syntax =
CodeMirror
.findModeByName('Plain Text')
this.editor.setOption('mode', syntax.mime)
CodeMirror.autoLoadMode(this.editor, syntax.mode)
@@ -599,11 +564,7 @@ export default class CodeEditor extends React.Component {
moveCursorTo(row, col) {}
scrollToLine(event, num) {
const cursor = {
line: num,
ch: 1
}
this.editor.setCursor(cursor)
const cursor = { line: num, ch: 1 } this.editor.setCursor(cursor)
}
focus() {
@@ -616,32 +577,22 @@ export default class CodeEditor extends React.Component {
reload() {
// Change event shouldn't be fired when switch note
this.editor.off('change', this.changeHandler)
this.value = this.props.value
this.editor.setValue(this.props.value)
this.editor.clearHistory()
this.editor.on('change', this.changeHandler)
this.editor.refresh()
this.editor.off('change', this.changeHandler) this.value =
this.props.value this.editor.setValue(this.props.value) this.editor
.clearHistory() this.editor.on('change', this.changeHandler) this
.editor.refresh()
}
setValue(value) {
const cursor = this.editor.getCursor()
this.editor.setValue(value)
this.editor.setCursor(cursor)
const cursor = this.editor.getCursor() this.editor.setValue(value) this
.editor.setCursor(cursor)
}
handleDropImage(dropEvent) {
dropEvent.preventDefault()
const {
storageKey,
noteKey
} = this.props
const {storageKey, noteKey} = this.props
attachmentManagement.handleAttachmentDrop(
this,
storageKey,
noteKey,
dropEvent
)
this, storageKey, noteKey, dropEvent)
}
insertAttachmentMd(imageMd) {
@@ -650,59 +601,43 @@ export default class CodeEditor extends React.Component {
handlePaste(editor, e) {
const clipboardData = e.clipboardData
const {
storageKey,
noteKey
} = this.props
const dataTransferItem = clipboardData.items[0]
const pastedTxt = clipboardData.getData('text')
const isURL = str => {
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
const {storageKey, noteKey} = this.props
const dataTransferItem = clipboardData.items[0] const pastedTxt =
clipboardData.getData('text')
const isURL =
str => {
const matcher =
/^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
return matcher.test(str)
}
const isInLinkTag = editor => {
} const isInLinkTag =
editor => {
const startCursor = editor.getCursor('start')
const prevChar = editor.getRange({
line: startCursor.line,
ch: startCursor.ch - 2
}, {
line: startCursor.line,
ch: startCursor.ch
})
const prevChar = editor.getRange(
{line: startCursor.line, ch: startCursor.ch - 2},
{line: startCursor.line, ch: startCursor.ch})
const endCursor = editor.getCursor('end')
const nextChar = editor.getRange({
line: endCursor.line,
ch: endCursor.ch
}, {
line: endCursor.line,
ch: endCursor.ch + 1
})
const nextChar = editor.getRange(
{line: endCursor.line, ch: endCursor.ch},
{line: endCursor.line, ch: endCursor.ch + 1})
return prevChar === '](' && nextChar === ')'
}
const pastedHtml = clipboardData.getData('text/html')
if (pastedHtml !== '') {
this.handlePasteHtml(e, editor, pastedHtml)
} else if (dataTransferItem.type.match('image')) {
}
else if (dataTransferItem.type.match('image')) {
attachmentManagement.handlePastImageEvent(
this,
storageKey,
noteKey,
dataTransferItem
)
} else if (
this.props.fetchUrlTitle &&
isURL(pastedTxt) &&
!isInLinkTag(editor)
) {
this, storageKey, noteKey, dataTransferItem)
}
else if (
this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(e, editor, pastedTxt)
}
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
attachmentManagement
.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
.then(modifiedText => {
this.editor.replaceSelection(modifiedText)
})
.then(modifiedText => {this.editor.replaceSelection(modifiedText)})
e.preventDefault()
}
}
@@ -718,39 +653,36 @@ export default class CodeEditor extends React.Component {
const taggedUrl = `<${pastedTxt}>`
editor.replaceSelection(taggedUrl)
const isImageReponse = response => {
const isImageReponse =
response => {
return (
response.headers.has('content-type') &&
response.headers.get('content-type').match(/^image\/.+$/)
)
}
const replaceTaggedUrl = replacement => {
response.headers.get('content-type').match(/^image\/.+$/))
} const replaceTaggedUrl =
replacement => {
const value = editor.getValue()
const cursor = editor.getCursor()
const newValue = value.replace(taggedUrl, replacement)
const newCursor = Object.assign({}, cursor, {
ch: cursor.ch + newValue.length - value.length
})
const newCursor = Object.assign(
{}, cursor, {ch: cursor.ch + newValue.length - value.length})
editor.setValue(newValue)
editor.setCursor(newCursor)
}
fetch(pastedTxt, {
method: 'get'
})
fetch(pastedTxt, {method: 'get'})
.then(response => {
if (isImageReponse(response)) {
return this.mapImageResponse(response, pastedTxt)
return this.mapImageResponse(
response, pastedTxt)
} else {
return this.mapNormalResponse(response, pastedTxt)
return this.mapNormalResponse(
response, pastedTxt)
}
})
.then(replacement => {
replaceTaggedUrl(replacement)
})
.catch(e => {
replaceTaggedUrl(pastedTxt)
})
.then(
replacement => {
replaceTaggedUrl(replacement)})
.catch(e => {replaceTaggedUrl(pastedTxt)})
}
handlePasteHtml(e, editor, pastedHtml) {
@@ -760,23 +692,21 @@ export default class CodeEditor extends React.Component {
}
mapNormalResponse(response, pastedTxt) {
return this.decodeResponse(response).then(body => {
return new Promise((resolve, reject) => {
return this.decodeResponse(response).then(
body => {return new Promise((resolve, reject) => {
try {
const parsedBody = new window.DOMParser().parseFromString(
body,
'text/html'
)
const escapePipe = (str) => {
const parsedBody =
new window.DOMParser().parseFromString(body, 'text/html')
const escapePipe =
(str) => {
return str.replace('|', '\\|')
}
const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})`
} const linkWithTitle =
`[${escapePipe(parsedBody.title)}](${pastedTxt})`
resolve(linkWithTitle)
} catch (e) {
reject(e)
}
})
})
})})
}
mapImageResponse(response, pastedTxt) {
@@ -797,37 +727,28 @@ export default class CodeEditor extends React.Component {
const _charset = headers.has('content-type') ?
this.extractContentTypeCharset(headers.get('content-type')) :
undefined
return response.arrayBuffer().then(buff => {
return new Promise((resolve, reject) => {
return response.arrayBuffer().then(
buff => {return new Promise((resolve, reject) => {
try {
const charset = _charset !== undefined &&
iconv.encodingExists(_charset) ?
const charset =
_charset !== undefined && iconv.encodingExists(_charset) ?
_charset :
'utf-8'
resolve(iconv.decode(new Buffer(buff), charset).toString())
} catch (e) {
reject(e)
}
})
})
})})
}
extractContentTypeCharset(contentType) {
return contentType
.split(';')
.filter(str => {
return str.trim().toLowerCase().startsWith('charset')
})
.map(str => {
return str.replace(/['"]/g, '').split('=')[1]
})[0]
return contentType.split(';')
.filter(str => {return str.trim().toLowerCase().startsWith('charset')})
.map(str => {return str.replace(/['"]/g, '').split('=')[1]})[0]
}
render() {
const {
className,
fontSize
} = this.props
const {className, fontSize} = this.props
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
const width = this.props.width
return ( <
@@ -837,16 +758,10 @@ export default class CodeEditor extends React.Component {
ref = 'root'
tabIndex = '-1'
style = {
{
fontFamily,
fontSize: fontSize,
width: width
}
}
onDrop = {
{ fontFamily, fontSize: fontSize, width: width }
} onDrop = {
e => this.handleDropImage(e)
}
/>
} />
)
}
}

View File

@@ -269,6 +269,9 @@ class MarkdownEditor extends React.Component {
rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers}
enableBracketMatching={config.editor.enableBracketMatching}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey}
noteKey={noteKey}

View File

@@ -159,6 +159,9 @@ class MarkdownSplitEditor extends React.Component {
fontSize={editorFontSize}
displayLineNumbers={config.editor.displayLineNumbers}
enableBracketMatching={config.editor.enableBracketMatching}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers}

View File

@@ -700,6 +700,9 @@ class SnippetNoteDetail extends React.Component {
indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers}
enableBracketMatching={config.editor.enableBracketMatching}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}

View File

@@ -45,6 +45,9 @@ export const DEFAULT_CONFIG = {
rulers: [80, 120],
displayLineNumbers: true,
enableBracketMatching: true,
matchingPairs:'()[]{}\'\'""$$**``',
matchingTriples:'```"""\'\'\'',
explodingPairs:'[]{}``$$',
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE'
scrollPastEnd: false,

View File

@@ -137,6 +137,9 @@ class SnippetTab extends React.Component {
rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers}
enableBracketMatching={config.editor.enableBracketMatching}
matchingPairs={config.editor.matchingPairs}
matchingTriples={config.editor.matchingTriples}
explodingPairs={config.editor.explodingPairs}
scrollPastEnd={config.editor.scrollPastEnd}
onRef={ref => { this.snippetEditor = ref }} />
</div>

View File

@@ -95,7 +95,10 @@ class UiTab extends React.Component {
enableTableEditor: this.refs.enableTableEditor.checked,
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
frontMatterTitleField: this.refs.frontMatterTitleField.value,
enableBracketMatching: this.refs.enableBracketMatching.checked
enableBracketMatching: this.refs.enableBracketMatching.checked,
matchingPairs: this.refs.matchingPairs.value,
matchingTriples: this.refs.matchingTriples.value,
explodingPairs: this.refs.explodingPairs.value
},
preview: {
fontSize: this.refs.previewFontSize.value,
@@ -551,6 +554,48 @@ class UiTab extends React.Component {
</label>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Matching character pairs')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
value={this.state.config.editor.matchingPairs}
ref='matchingPairs'
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Matching character triples')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
value={this.state.config.editor.matchingTriples}
ref='matchingTriples'
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Exploding character pairs')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
value={this.state.config.editor.explodingPairs}
ref='explodingPairs'
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-header2'>{i18n.__('Preview')}</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
@@ -578,6 +623,7 @@ class UiTab extends React.Component {
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Code Block Theme')}</div>
<div styleName='group-section-control'>