@@ -13,7 +13,7 @@ import crypto from 'crypto'
13
13
import consts from 'browser/lib/consts'
14
14
import styles from '../components/CodeEditor.styl'
15
15
import fs from 'fs'
16
- const { ipcRenderer, remote } = require ( 'electron' )
16
+ const { ipcRenderer, remote, clipboard } = require ( 'electron' )
17
17
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
18
18
const spellcheck = require ( 'browser/lib/spellcheck' )
19
19
const buildEditorContextMenu = require ( 'browser/lib/contextMenuBuilder' )
@@ -25,6 +25,10 @@ CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
25
25
const buildCMRulers = ( rulers , enableRulers ) =>
26
26
( enableRulers ? rulers . map ( ruler => ( { column : ruler } ) ) : [ ] )
27
27
28
+ function translateHotkey ( hotkey ) {
29
+ return hotkey . replace ( / \s * \+ \s * / g, '-' ) . replace ( / C o m m a n d / g, 'Cmd' ) . replace ( / C o n t r o l / g, 'Ctrl' )
30
+ }
31
+
28
32
export default class CodeEditor extends React . Component {
29
33
constructor ( props ) {
30
34
super ( props )
@@ -56,7 +60,11 @@ export default class CodeEditor extends React.Component {
56
60
noteKey
57
61
)
58
62
}
59
- this . pasteHandler = ( editor , e ) => this . handlePaste ( editor , e )
63
+ this . pasteHandler = ( editor , e ) => {
64
+ e . preventDefault ( )
65
+
66
+ this . handlePaste ( editor , false )
67
+ }
60
68
this . loadStyleHandler = e => {
61
69
this . editor . refresh ( )
62
70
}
@@ -124,42 +132,9 @@ export default class CodeEditor extends React.Component {
124
132
}
125
133
}
126
134
127
- updateTableEditorState ( ) {
128
- const active = this . tableEditor . cursorIsInTable ( this . tableEditorOptions )
129
- if ( active ) {
130
- if ( this . extraKeysMode !== 'editor' ) {
131
- this . extraKeysMode = 'editor'
132
- this . editor . setOption ( 'extraKeys' , this . editorKeyMap )
133
- }
134
- } else {
135
- if ( this . extraKeysMode !== 'default' ) {
136
- this . extraKeysMode = 'default'
137
- this . editor . setOption ( 'extraKeys' , this . defaultKeyMap )
138
- this . tableEditor . resetSmartCursor ( )
139
- }
140
- }
141
- }
142
-
143
- componentDidMount ( ) {
144
- const { rulers, enableRulers, switchPreview } = this . props
135
+ updateDefaultKeyMap ( ) {
136
+ const { hotkey } = this . props
145
137
const expandSnippet = this . expandSnippet . bind ( this )
146
- eventEmitter . on ( 'line:jump' , this . scrollToLineHandeler )
147
-
148
- const defaultSnippet = [
149
- {
150
- id : crypto . randomBytes ( 16 ) . toString ( 'hex' ) ,
151
- name : 'Dummy text' ,
152
- prefix : [ 'lorem' , 'ipsum' ] ,
153
- 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.'
154
- }
155
- ]
156
- if ( ! fs . existsSync ( consts . SNIPPET_FILE ) ) {
157
- fs . writeFileSync (
158
- consts . SNIPPET_FILE ,
159
- JSON . stringify ( defaultSnippet , null , 4 ) ,
160
- 'utf8'
161
- )
162
- }
163
138
164
139
this . defaultKeyMap = CodeMirror . normalizeKeyMap ( {
165
140
Tab : function ( cm ) {
@@ -211,8 +186,50 @@ export default class CodeEditor extends React.Component {
211
186
document . execCommand ( 'copy' )
212
187
}
213
188
return CodeMirror . Pass
189
+ } ,
190
+ [ translateHotkey ( hotkey . pasteSmartly ) ] : cm => {
191
+ this . handlePaste ( cm , true )
214
192
}
215
193
} )
194
+ }
195
+
196
+ updateTableEditorState ( ) {
197
+ const active = this . tableEditor . cursorIsInTable ( this . tableEditorOptions )
198
+ if ( active ) {
199
+ if ( this . extraKeysMode !== 'editor' ) {
200
+ this . extraKeysMode = 'editor'
201
+ this . editor . setOption ( 'extraKeys' , this . editorKeyMap )
202
+ }
203
+ } else {
204
+ if ( this . extraKeysMode !== 'default' ) {
205
+ this . extraKeysMode = 'default'
206
+ this . editor . setOption ( 'extraKeys' , this . defaultKeyMap )
207
+ this . tableEditor . resetSmartCursor ( )
208
+ }
209
+ }
210
+ }
211
+
212
+ componentDidMount ( ) {
213
+ const { rulers, enableRulers } = this . props
214
+ eventEmitter . on ( 'line:jump' , this . scrollToLineHandeler )
215
+
216
+ const defaultSnippet = [
217
+ {
218
+ id : crypto . randomBytes ( 16 ) . toString ( 'hex' ) ,
219
+ name : 'Dummy text' ,
220
+ prefix : [ 'lorem' , 'ipsum' ] ,
221
+ 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.'
222
+ }
223
+ ]
224
+ if ( ! fs . existsSync ( consts . SNIPPET_FILE ) ) {
225
+ fs . writeFileSync (
226
+ consts . SNIPPET_FILE ,
227
+ JSON . stringify ( defaultSnippet , null , 4 ) ,
228
+ 'utf8'
229
+ )
230
+ }
231
+
232
+ this . updateDefaultKeyMap ( )
216
233
217
234
this . value = this . props . value
218
235
this . editor = CodeMirror ( this . refs . root , {
@@ -245,7 +262,7 @@ export default class CodeEditor extends React.Component {
245
262
this . editor . on ( 'blur' , this . blurHandler )
246
263
this . editor . on ( 'change' , this . changeHandler )
247
264
this . editor . on ( 'paste' , this . pasteHandler )
248
- if ( switchPreview !== 'RIGHTCLICK' ) {
265
+ if ( this . props . switchPreview !== 'RIGHTCLICK' ) {
249
266
this . editor . on ( 'contextmenu' , this . contextMenuHandler )
250
267
}
251
268
eventEmitter . on ( 'top:search' , this . searchHandler )
@@ -479,6 +496,14 @@ export default class CodeEditor extends React.Component {
479
496
this . editor . setOption ( 'extraKeys' , this . defaultKeyMap )
480
497
}
481
498
499
+ if ( prevProps . hotkey !== this . props . hotkey ) {
500
+ this . updateDefaultKeyMap ( )
501
+
502
+ if ( this . extraKeysMode === 'default' ) {
503
+ this . editor . setOption ( 'extraKeys' , this . defaultKeyMap )
504
+ }
505
+ }
506
+
482
507
if ( this . state . clientWidth !== this . refs . root . clientWidth ) {
483
508
this . setState ( {
484
509
clientWidth : this . refs . root . clientWidth
@@ -567,15 +592,14 @@ export default class CodeEditor extends React.Component {
567
592
this . editor . replaceSelection ( imageMd )
568
593
}
569
594
570
- handlePaste ( editor , e ) {
571
- const clipboardData = e . clipboardData
572
- const { storageKey, noteKey } = this . props
573
- const dataTransferItem = clipboardData . items [ 0 ]
574
- const pastedTxt = clipboardData . getData ( 'text' )
595
+ handlePaste ( editor , forceSmartPaste ) {
596
+ const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this . props
597
+
575
598
const isURL = str => {
576
599
const matcher = / ^ (?: \w + : ) ? \/ \/ ( [ ^ \s \. ] + \. \S { 2 } | l o c a l h o s t [ \: ? \d ] * ) \S * $ /
577
600
return matcher . test ( str )
578
601
}
602
+
579
603
const isInLinkTag = editor => {
580
604
const startCursor = editor . getCursor ( 'start' )
581
605
const prevChar = editor . getRange (
@@ -590,30 +614,73 @@ export default class CodeEditor extends React.Component {
590
614
return prevChar === '](' && nextChar === ')'
591
615
}
592
616
593
- const pastedHtml = clipboardData . getData ( 'text/html' )
594
- if ( pastedHtml !== '' ) {
595
- this . handlePasteHtml ( e , editor , pastedHtml )
596
- } else if ( dataTransferItem . type . match ( 'image' ) ) {
597
- attachmentManagement . handlePastImageEvent (
598
- this ,
599
- storageKey ,
600
- noteKey ,
601
- dataTransferItem
602
- )
603
- } else if (
604
- this . props . fetchUrlTitle &&
605
- isURL ( pastedTxt ) &&
606
- ! isInLinkTag ( editor )
607
- ) {
608
- this . handlePasteUrl ( e , editor , pastedTxt )
617
+ const isInFencedCodeBlock = editor => {
618
+ const cursor = editor . getCursor ( )
619
+
620
+ let token = editor . getTokenAt ( cursor )
621
+ if ( token . state . fencedState ) {
622
+ return true
623
+ }
624
+
625
+ let line = line = cursor . line - 1
626
+ while ( line >= 0 ) {
627
+ token = editor . getTokenAt ( {
628
+ ch : 3 ,
629
+ line
630
+ } )
631
+
632
+ if ( token . start === token . end ) {
633
+ -- line
634
+ } else if ( token . type === 'comment' ) {
635
+ if ( line > 0 ) {
636
+ token = editor . getTokenAt ( {
637
+ ch : 3 ,
638
+ line : line - 1
639
+ } )
640
+
641
+ return token . type !== 'comment'
642
+ } else {
643
+ return true
644
+ }
645
+ } else {
646
+ return false
647
+ }
648
+ }
649
+
650
+ return false
609
651
}
610
- if ( attachmentManagement . isAttachmentLink ( pastedTxt ) ) {
652
+
653
+ const pastedTxt = clipboard . readText ( )
654
+
655
+ if ( isInFencedCodeBlock ( editor ) ) {
656
+ this . handlePasteText ( editor , pastedTxt )
657
+ } else if ( fetchUrlTitle && isURL ( pastedTxt ) && ! isInLinkTag ( editor ) ) {
658
+ this . handlePasteUrl ( editor , pastedTxt )
659
+ } else if ( enableSmartPaste || forceSmartPaste ) {
660
+ const image = clipboard . readImage ( )
661
+ if ( ! image . isEmpty ( ) ) {
662
+ attachmentManagement . handlePastNativeImage (
663
+ this ,
664
+ storageKey ,
665
+ noteKey ,
666
+ image
667
+ )
668
+ } else {
669
+ const pastedHtml = clipboard . readHTML ( )
670
+ if ( pastedHtml . length > 0 ) {
671
+ this . handlePasteHtml ( editor , pastedHtml )
672
+ } else {
673
+ this . handlePasteText ( editor , pastedTxt )
674
+ }
675
+ }
676
+ } else if ( attachmentManagement . isAttachmentLink ( pastedTxt ) ) {
611
677
attachmentManagement
612
678
. handleAttachmentLinkPaste ( storageKey , noteKey , pastedTxt )
613
679
. then ( modifiedText => {
614
680
this . editor . replaceSelection ( modifiedText )
615
681
} )
616
- e . preventDefault ( )
682
+ } else {
683
+ this . handlePasteText ( editor , pastedTxt )
617
684
}
618
685
}
619
686
@@ -623,8 +690,7 @@ export default class CodeEditor extends React.Component {
623
690
}
624
691
}
625
692
626
- handlePasteUrl ( e , editor , pastedTxt ) {
627
- e . preventDefault ( )
693
+ handlePasteUrl ( editor , pastedTxt ) {
628
694
const taggedUrl = `<${ pastedTxt } >`
629
695
editor . replaceSelection ( taggedUrl )
630
696
@@ -663,12 +729,15 @@ export default class CodeEditor extends React.Component {
663
729
} )
664
730
}
665
731
666
- handlePasteHtml ( e , editor , pastedHtml ) {
667
- e . preventDefault ( )
732
+ handlePasteHtml ( editor , pastedHtml ) {
668
733
const markdown = this . turndownService . turndown ( pastedHtml )
669
734
editor . replaceSelection ( markdown )
670
735
}
671
736
737
+ handlePasteText ( editor , pastedTxt ) {
738
+ editor . replaceSelection ( pastedTxt )
739
+ }
740
+
672
741
mapNormalResponse ( response , pastedTxt ) {
673
742
return this . decodeResponse ( response ) . then ( body => {
674
743
return new Promise ( ( resolve , reject ) => {
0 commit comments