@@ -17,9 +17,7 @@ import crypto from 'crypto'
17
17
import consts from 'browser/lib/consts'
18
18
import styles from '../components/CodeEditor.styl'
19
19
import fs from 'fs'
20
-
21
- const { ipcRenderer, remote } = require ( 'electron' )
22
-
20
+ const { ipcRenderer, remote, clipboard } = require ( 'electron' )
23
21
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
24
22
const spellcheck = require ( 'browser/lib/spellcheck' )
25
23
const buildEditorContextMenu = require ( 'browser/lib/contextMenuBuilder' )
@@ -35,6 +33,10 @@ const buildCMRulers = (rulers, enableRulers) =>
35
33
column : ruler
36
34
} ) ) : [ ] )
37
35
36
+ function translateHotkey ( hotkey ) {
37
+ 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' )
38
+ }
39
+
38
40
export default class CodeEditor extends React . Component {
39
41
constructor ( props ) {
40
42
super ( props )
@@ -69,7 +71,11 @@ export default class CodeEditor extends React.Component {
69
71
noteKey
70
72
)
71
73
}
72
- this . pasteHandler = ( editor , e ) => this . handlePaste ( editor , e )
74
+ this . pasteHandler = ( editor , e ) => {
75
+ e . preventDefault ( )
76
+
77
+ this . handlePaste ( editor , false )
78
+ }
73
79
this . loadStyleHandler = e => {
74
80
this . editor . refresh ( )
75
81
}
@@ -139,40 +145,9 @@ export default class CodeEditor extends React.Component {
139
145
}
140
146
}
141
147
142
- updateTableEditorState ( ) {
143
- const active = this . tableEditor . cursorIsInTable ( this . tableEditorOptions )
144
- if ( active ) {
145
- if ( this . extraKeysMode !== 'editor' ) {
146
- this . extraKeysMode = 'editor'
147
- this . editor . setOption ( 'extraKeys' , this . editorKeyMap )
148
- }
149
- } else {
150
- if ( this . extraKeysMode !== 'default' ) {
151
- this . extraKeysMode = 'default'
152
- this . editor . setOption ( 'extraKeys' , this . defaultKeyMap )
153
- this . tableEditor . resetSmartCursor ( )
154
- }
155
- }
156
- }
157
-
158
- componentDidMount ( ) {
159
- const { rulers, enableRulers, switchPreview } = this . props
148
+ updateDefaultKeyMap ( ) {
149
+ const { hotkey } = this . props
160
150
const expandSnippet = this . expandSnippet . bind ( this )
161
- eventEmitter . on ( 'line:jump' , this . scrollToLineHandeler )
162
-
163
- const defaultSnippet = [ {
164
- id : crypto . randomBytes ( 16 ) . toString ( 'hex' ) ,
165
- name : 'Dummy text' ,
166
- prefix : [ 'lorem' , 'ipsum' ] ,
167
- 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.'
168
- } ]
169
- if ( ! fs . existsSync ( consts . SNIPPET_FILE ) ) {
170
- fs . writeFileSync (
171
- consts . SNIPPET_FILE ,
172
- JSON . stringify ( defaultSnippet , null , 4 ) ,
173
- 'utf8'
174
- )
175
- }
176
151
177
152
this . defaultKeyMap = CodeMirror . normalizeKeyMap ( {
178
153
Tab : function ( cm ) {
@@ -224,8 +199,50 @@ export default class CodeEditor extends React.Component {
224
199
document . execCommand ( 'copy' )
225
200
}
226
201
return CodeMirror . Pass
202
+ } ,
203
+ [ translateHotkey ( hotkey . pasteSmartly ) ] : cm => {
204
+ this . handlePaste ( cm , true )
227
205
}
228
206
} )
207
+ }
208
+
209
+ updateTableEditorState ( ) {
210
+ const active = this . tableEditor . cursorIsInTable ( this . tableEditorOptions )
211
+ if ( active ) {
212
+ if ( this . extraKeysMode !== 'editor' ) {
213
+ this . extraKeysMode = 'editor'
214
+ this . editor . setOption ( 'extraKeys' , this . editorKeyMap )
215
+ }
216
+ } else {
217
+ if ( this . extraKeysMode !== 'default' ) {
218
+ this . extraKeysMode = 'default'
219
+ this . editor . setOption ( 'extraKeys' , this . defaultKeyMap )
220
+ this . tableEditor . resetSmartCursor ( )
221
+ }
222
+ }
223
+ }
224
+
225
+ componentDidMount ( ) {
226
+ const { rulers, enableRulers } = this . props
227
+ eventEmitter . on ( 'line:jump' , this . scrollToLineHandeler )
228
+
229
+ const defaultSnippet = [
230
+ {
231
+ id : crypto . randomBytes ( 16 ) . toString ( 'hex' ) ,
232
+ name : 'Dummy text' ,
233
+ prefix : [ 'lorem' , 'ipsum' ] ,
234
+ 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.'
235
+ }
236
+ ]
237
+ if ( ! fs . existsSync ( consts . SNIPPET_FILE ) ) {
238
+ fs . writeFileSync (
239
+ consts . SNIPPET_FILE ,
240
+ JSON . stringify ( defaultSnippet , null , 4 ) ,
241
+ 'utf8'
242
+ )
243
+ }
244
+
245
+ this . updateDefaultKeyMap ( )
229
246
230
247
this . value = this . props . value
231
248
this . editor = CodeMirror ( this . refs . root , {
@@ -258,7 +275,7 @@ export default class CodeEditor extends React.Component {
258
275
this . editor . on ( 'blur' , this . blurHandler )
259
276
this . editor . on ( 'change' , this . changeHandler )
260
277
this . editor . on ( 'paste' , this . pasteHandler )
261
- if ( switchPreview !== 'RIGHTCLICK' ) {
278
+ if ( this . props . switchPreview !== 'RIGHTCLICK' ) {
262
279
this . editor . on ( 'contextmenu' , this . contextMenuHandler )
263
280
}
264
281
eventEmitter . on ( 'top:search' , this . searchHandler )
@@ -587,6 +604,14 @@ export default class CodeEditor extends React.Component {
587
604
this . editor . setOption ( 'extraKeys' , this . defaultKeyMap )
588
605
}
589
606
607
+ if ( prevProps . hotkey !== this . props . hotkey ) {
608
+ this . updateDefaultKeyMap ( )
609
+
610
+ if ( this . extraKeysMode === 'default' ) {
611
+ this . editor . setOption ( 'extraKeys' , this . defaultKeyMap )
612
+ }
613
+ }
614
+
590
615
if ( this . state . clientWidth !== this . refs . root . clientWidth ) {
591
616
this . setState ( {
592
617
clientWidth : this . refs . root . clientWidth
@@ -678,18 +703,15 @@ export default class CodeEditor extends React.Component {
678
703
this . editor . replaceSelection ( imageMd )
679
704
}
680
705
681
- handlePaste ( editor , e ) {
682
- const clipboardData = e . clipboardData
683
- const {
684
- storageKey,
685
- noteKey
686
- } = this . props
687
- const dataTransferItem = clipboardData . items [ 0 ]
688
- const pastedTxt = clipboardData . getData ( 'text' )
706
+
707
+ handlePaste ( editor , forceSmartPaste ) {
708
+ const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this . props
709
+
689
710
const isURL = str => {
690
711
const matcher = / ^ (?: \w + : ) ? \/ \/ ( [ ^ \s \. ] + \. \S { 2 } | l o c a l h o s t [ \: ? \d ] * ) \S * $ /
691
712
return matcher . test ( str )
692
713
}
714
+
693
715
const isInLinkTag = editor => {
694
716
const startCursor = editor . getCursor ( 'start' )
695
717
const prevChar = editor . getRange ( {
@@ -710,30 +732,73 @@ export default class CodeEditor extends React.Component {
710
732
return prevChar === '](' && nextChar === ')'
711
733
}
712
734
713
- const pastedHtml = clipboardData . getData ( 'text/html' )
714
- if ( pastedHtml !== '' ) {
715
- this . handlePasteHtml ( e , editor , pastedHtml )
716
- } else if ( dataTransferItem . type . match ( 'image' ) ) {
717
- attachmentManagement . handlePastImageEvent (
718
- this ,
719
- storageKey ,
720
- noteKey ,
721
- dataTransferItem
722
- )
723
- } else if (
724
- this . props . fetchUrlTitle &&
725
- isURL ( pastedTxt ) &&
726
- ! isInLinkTag ( editor )
727
- ) {
728
- this . handlePasteUrl ( e , editor , pastedTxt )
735
+ const isInFencedCodeBlock = editor => {
736
+ const cursor = editor . getCursor ( )
737
+
738
+ let token = editor . getTokenAt ( cursor )
739
+ if ( token . state . fencedState ) {
740
+ return true
741
+ }
742
+
743
+ let line = line = cursor . line - 1
744
+ while ( line >= 0 ) {
745
+ token = editor . getTokenAt ( {
746
+ ch : 3 ,
747
+ line
748
+ } )
749
+
750
+ if ( token . start === token . end ) {
751
+ -- line
752
+ } else if ( token . type === 'comment' ) {
753
+ if ( line > 0 ) {
754
+ token = editor . getTokenAt ( {
755
+ ch : 3 ,
756
+ line : line - 1
757
+ } )
758
+
759
+ return token . type !== 'comment'
760
+ } else {
761
+ return true
762
+ }
763
+ } else {
764
+ return false
765
+ }
766
+ }
767
+
768
+ return false
729
769
}
730
- if ( attachmentManagement . isAttachmentLink ( pastedTxt ) ) {
770
+
771
+ const pastedTxt = clipboard . readText ( )
772
+
773
+ if ( isInFencedCodeBlock ( editor ) ) {
774
+ this . handlePasteText ( editor , pastedTxt )
775
+ } else if ( fetchUrlTitle && isURL ( pastedTxt ) && ! isInLinkTag ( editor ) ) {
776
+ this . handlePasteUrl ( editor , pastedTxt )
777
+ } else if ( enableSmartPaste || forceSmartPaste ) {
778
+ const image = clipboard . readImage ( )
779
+ if ( ! image . isEmpty ( ) ) {
780
+ attachmentManagement . handlePastNativeImage (
781
+ this ,
782
+ storageKey ,
783
+ noteKey ,
784
+ image
785
+ )
786
+ } else {
787
+ const pastedHtml = clipboard . readHTML ( )
788
+ if ( pastedHtml . length > 0 ) {
789
+ this . handlePasteHtml ( editor , pastedHtml )
790
+ } else {
791
+ this . handlePasteText ( editor , pastedTxt )
792
+ }
793
+ }
794
+ } else if ( attachmentManagement . isAttachmentLink ( pastedTxt ) ) {
731
795
attachmentManagement
732
796
. handleAttachmentLinkPaste ( storageKey , noteKey , pastedTxt )
733
797
. then ( modifiedText => {
734
798
this . editor . replaceSelection ( modifiedText )
735
799
} )
736
- e . preventDefault ( )
800
+ } else {
801
+ this . handlePasteText ( editor , pastedTxt )
737
802
}
738
803
}
739
804
@@ -743,8 +808,7 @@ export default class CodeEditor extends React.Component {
743
808
}
744
809
}
745
810
746
- handlePasteUrl ( e , editor , pastedTxt ) {
747
- e . preventDefault ( )
811
+ handlePasteUrl ( editor , pastedTxt ) {
748
812
const taggedUrl = `<${ pastedTxt } >`
749
813
editor . replaceSelection ( taggedUrl )
750
814
@@ -783,12 +847,15 @@ export default class CodeEditor extends React.Component {
783
847
} )
784
848
}
785
849
786
- handlePasteHtml ( e , editor , pastedHtml ) {
787
- e . preventDefault ( )
850
+ handlePasteHtml ( editor , pastedHtml ) {
788
851
const markdown = this . turndownService . turndown ( pastedHtml )
789
852
editor . replaceSelection ( markdown )
790
853
}
791
854
855
+ handlePasteText ( editor , pastedTxt ) {
856
+ editor . replaceSelection ( pastedTxt )
857
+ }
858
+
792
859
mapNormalResponse ( response , pastedTxt ) {
793
860
return this . decodeResponse ( response ) . then ( body => {
794
861
return new Promise ( ( resolve , reject ) => {
0 commit comments