@@ -11,9 +11,14 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
11
11
import iconv from 'iconv-lite'
12
12
import crypto from 'crypto'
13
13
import consts from 'browser/lib/consts'
14
+ import styles from '../components/CodeEditor.styl'
14
15
import fs from 'fs'
15
- const { ipcRenderer } = require ( 'electron' )
16
+ const { ipcRenderer, remote } = require ( 'electron' )
16
17
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
18
+ const spellcheck = require ( 'browser/lib/spellcheck' )
19
+ const buildEditorContextMenu = require ( 'browser/lib/contextMenuBuilder' )
20
+ import TurndownService from 'turndown'
21
+ import { gfm } from 'turndown-plugin-gfm'
17
22
18
23
CodeMirror . modeURL = '../node_modules/codemirror/mode/%N/%N.js'
19
24
@@ -28,7 +33,7 @@ export default class CodeEditor extends React.Component {
28
33
leading : false ,
29
34
trailing : true
30
35
} )
31
- this . changeHandler = e => this . handleChange ( e )
36
+ this . changeHandler = ( editor , changeObject ) => this . handleChange ( editor , changeObject )
32
37
this . focusHandler = ( ) => {
33
38
ipcRenderer . send ( 'editor:focused' , true )
34
39
}
@@ -57,9 +62,18 @@ export default class CodeEditor extends React.Component {
57
62
}
58
63
this . searchHandler = ( e , msg ) => this . handleSearch ( msg )
59
64
this . searchState = null
65
+ this . scrollToLineHandeler = this . scrollToLine . bind ( this )
60
66
61
67
this . formatTable = ( ) => this . handleFormatTable ( )
68
+ this . contextMenuHandler = function ( editor , event ) {
69
+ const menu = buildEditorContextMenu ( editor , event )
70
+ if ( menu != null ) {
71
+ setTimeout ( ( ) => menu . popup ( remote . getCurrentWindow ( ) ) , 30 )
72
+ }
73
+ }
62
74
this . editorActivityHandler = ( ) => this . handleEditorActivity ( )
75
+
76
+ this . turndownService = new TurndownService ( )
63
77
}
64
78
65
79
handleSearch ( msg ) {
@@ -125,6 +139,7 @@ export default class CodeEditor extends React.Component {
125
139
componentDidMount ( ) {
126
140
const { rulers, enableRulers } = this . props
127
141
const expandSnippet = this . expandSnippet . bind ( this )
142
+ eventEmitter . on ( 'line:jump' , this . scrollToLineHandeler )
128
143
129
144
const defaultSnippet = [
130
145
{
@@ -226,6 +241,7 @@ export default class CodeEditor extends React.Component {
226
241
this . editor . on ( 'blur' , this . blurHandler )
227
242
this . editor . on ( 'change' , this . changeHandler )
228
243
this . editor . on ( 'paste' , this . pasteHandler )
244
+ this . editor . on ( 'contextmenu' , this . contextMenuHandler )
229
245
eventEmitter . on ( 'top:search' , this . searchHandler )
230
246
231
247
eventEmitter . emit ( 'code:init' )
@@ -242,6 +258,10 @@ export default class CodeEditor extends React.Component {
242
258
243
259
this . textEditorInterface = new TextEditorInterface ( this . editor )
244
260
this . tableEditor = new TableEditor ( this . textEditorInterface )
261
+ if ( this . props . spellCheck ) {
262
+ this . editor . addPanel ( this . createSpellCheckPanel ( ) , { position : 'bottom' } )
263
+ }
264
+
245
265
eventEmitter . on ( 'code:format-table' , this . formatTable )
246
266
247
267
this . tableEditorOptions = options ( {
@@ -311,22 +331,28 @@ export default class CodeEditor extends React.Component {
311
331
const snippetLines = snippets [ i ] . content . split ( '\n' )
312
332
let cursorLineNumber = 0
313
333
let cursorLinePosition = 0
334
+
335
+ let cursorIndex
314
336
for ( let j = 0 ; j < snippetLines . length ; j ++ ) {
315
- const cursorIndex = snippetLines [ j ] . indexOf ( templateCursorString )
337
+ cursorIndex = snippetLines [ j ] . indexOf ( templateCursorString )
338
+
316
339
if ( cursorIndex !== - 1 ) {
317
340
cursorLineNumber = j
318
341
cursorLinePosition = cursorIndex
319
- cm . replaceRange (
320
- snippets [ i ] . content . replace ( templateCursorString , '' ) ,
321
- wordBeforeCursor . range . from ,
322
- wordBeforeCursor . range . to
323
- )
324
- cm . setCursor ( {
325
- line : cursor . line + cursorLineNumber ,
326
- ch : cursorLinePosition
327
- } )
342
+
343
+ break
328
344
}
329
345
}
346
+
347
+ cm . replaceRange (
348
+ snippets [ i ] . content . replace ( templateCursorString , '' ) ,
349
+ wordBeforeCursor . range . from ,
350
+ wordBeforeCursor . range . to
351
+ )
352
+ cm . setCursor ( {
353
+ line : cursor . line + cursorLineNumber ,
354
+ ch : cursorLinePosition + cursor . ch - wordBeforeCursor . text . length
355
+ } )
330
356
} else {
331
357
cm . replaceRange (
332
358
snippets [ i ] . content ,
@@ -383,9 +409,11 @@ export default class CodeEditor extends React.Component {
383
409
this . editor . off ( 'paste' , this . pasteHandler )
384
410
eventEmitter . off ( 'top:search' , this . searchHandler )
385
411
this . editor . off ( 'scroll' , this . scrollHandler )
412
+ this . editor . off ( 'contextmenu' , this . contextMenuHandler )
386
413
const editorTheme = document . getElementById ( 'editorTheme' )
387
414
editorTheme . removeEventListener ( 'load' , this . loadStyleHandler )
388
415
416
+ spellcheck . setLanguage ( null , spellcheck . SPELLCHECK_DISABLED )
389
417
eventEmitter . off ( 'code:format-table' , this . formatTable )
390
418
}
391
419
@@ -453,6 +481,16 @@ export default class CodeEditor extends React.Component {
453
481
needRefresh = true
454
482
}
455
483
484
+ if ( prevProps . spellCheck !== this . props . spellCheck ) {
485
+ if ( this . props . spellCheck === false ) {
486
+ spellcheck . setLanguage ( this . editor , spellcheck . SPELLCHECK_DISABLED )
487
+ let elem = document . getElementById ( 'editor-bottom-panel' )
488
+ elem . parentNode . removeChild ( elem )
489
+ } else {
490
+ this . editor . addPanel ( this . createSpellCheckPanel ( ) , { position : 'bottom' } )
491
+ }
492
+ }
493
+
456
494
if ( needRefresh ) {
457
495
this . editor . refresh ( )
458
496
}
@@ -466,16 +504,23 @@ export default class CodeEditor extends React.Component {
466
504
CodeMirror . autoLoadMode ( this . editor , syntax . mode )
467
505
}
468
506
469
- handleChange ( e ) {
470
- this . value = this . editor . getValue ( )
507
+ handleChange ( editor , changeObject ) {
508
+ spellcheck . handleChange ( editor , changeObject )
509
+ this . value = editor . getValue ( )
471
510
if ( this . props . onChange ) {
472
- this . props . onChange ( e )
511
+ this . props . onChange ( editor )
473
512
}
474
513
}
475
514
476
515
moveCursorTo ( row , col ) { }
477
516
478
- scrollToLine ( num ) { }
517
+ scrollToLine ( event , num ) {
518
+ const cursor = {
519
+ line : num ,
520
+ ch : 1
521
+ }
522
+ this . editor . setCursor ( cursor )
523
+ }
479
524
480
525
focus ( ) {
481
526
this . editor . focus ( )
@@ -538,7 +583,11 @@ export default class CodeEditor extends React.Component {
538
583
)
539
584
return prevChar === '](' && nextChar === ')'
540
585
}
541
- if ( dataTransferItem . type . match ( 'image' ) ) {
586
+
587
+ const pastedHtml = clipboardData . getData ( 'text/html' )
588
+ if ( pastedHtml !== '' ) {
589
+ this . handlePasteHtml ( e , editor , pastedHtml )
590
+ } else if ( dataTransferItem . type . match ( 'image' ) ) {
542
591
attachmentManagement . handlePastImageEvent (
543
592
this ,
544
593
storageKey ,
@@ -608,6 +657,12 @@ export default class CodeEditor extends React.Component {
608
657
} )
609
658
}
610
659
660
+ handlePasteHtml ( e , editor , pastedHtml ) {
661
+ e . preventDefault ( )
662
+ const markdown = this . turndownService . turndown ( pastedHtml )
663
+ editor . replaceSelection ( markdown )
664
+ }
665
+
611
666
mapNormalResponse ( response , pastedTxt ) {
612
667
return this . decodeResponse ( response ) . then ( body => {
613
668
return new Promise ( ( resolve , reject ) => {
@@ -690,6 +745,25 @@ export default class CodeEditor extends React.Component {
690
745
/>
691
746
)
692
747
}
748
+
749
+ createSpellCheckPanel ( ) {
750
+ const panel = document . createElement ( 'div' )
751
+ panel . className = 'panel bottom'
752
+ panel . id = 'editor-bottom-panel'
753
+ const dropdown = document . createElement ( 'select' )
754
+ dropdown . title = 'Spellcheck'
755
+ dropdown . className = styles [ 'spellcheck-select' ]
756
+ dropdown . addEventListener ( 'change' , ( e ) => spellcheck . setLanguage ( this . editor , dropdown . value ) )
757
+ const options = spellcheck . getAvailableDictionaries ( )
758
+ for ( const op of options ) {
759
+ const option = document . createElement ( 'option' )
760
+ option . value = op . value
761
+ option . innerHTML = op . label
762
+ dropdown . appendChild ( option )
763
+ }
764
+ panel . appendChild ( dropdown )
765
+ return panel
766
+ }
693
767
}
694
768
695
769
CodeEditor . propTypes = {
@@ -700,7 +774,8 @@ CodeEditor.propTypes = {
700
774
className : PropTypes . string ,
701
775
onBlur : PropTypes . func ,
702
776
onChange : PropTypes . func ,
703
- readOnly : PropTypes . bool
777
+ readOnly : PropTypes . bool ,
778
+ spellCheck : PropTypes . bool
704
779
}
705
780
706
781
CodeEditor . defaultProps = {
@@ -710,5 +785,6 @@ CodeEditor.defaultProps = {
710
785
fontSize : 14 ,
711
786
fontFamily : 'Monaco, Consolas' ,
712
787
indentSize : 4 ,
713
- indentType : 'space'
788
+ indentType : 'space' ,
789
+ spellCheck : false
714
790
}
0 commit comments