@@ -14,18 +14,17 @@ import {
14
14
import TextEditorInterface from 'browser/lib/TextEditorInterface'
15
15
import eventEmitter from 'browser/main/lib/eventEmitter'
16
16
import iconv from 'iconv-lite'
17
- import crypto from 'crypto'
18
- import consts from 'browser/lib/consts '
17
+
18
+ import { isMarkdownTitleURL } from 'browser/lib/utils '
19
19
import styles from '../components/CodeEditor.styl'
20
- import fs from 'fs'
21
20
const { ipcRenderer, remote, clipboard } = require ( 'electron' )
22
21
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
23
22
const spellcheck = require ( 'browser/lib/spellcheck' )
24
23
const buildEditorContextMenu = require ( 'browser/lib/contextMenuBuilder' )
25
24
import TurndownService from 'turndown'
26
- import {
27
- gfm
28
- } from 'turndown-plugin-gfm '
25
+ import { languageMaps } from '../lib/CMLanguageList'
26
+ import snippetManager from '../lib/SnippetManager'
27
+ import { generateInEditor , tocExistsInEditor } from 'browser/lib/markdown-toc-generator '
29
28
import markdownlint from 'markdownlint'
30
29
31
30
CodeMirror . modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -39,85 +38,6 @@ function translateHotkey (hotkey) {
39
38
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' )
40
39
}
41
40
42
- const languageMaps = {
43
- brainfuck : 'Brainfuck' ,
44
- cpp : 'C++' ,
45
- cs : 'C#' ,
46
- clojure : 'Clojure' ,
47
- 'clojure-repl' : 'ClojureScript' ,
48
- cmake : 'CMake' ,
49
- coffeescript : 'CoffeeScript' ,
50
- crystal : 'Crystal' ,
51
- css : 'CSS' ,
52
- d : 'D' ,
53
- dart : 'Dart' ,
54
- delphi : 'Pascal' ,
55
- diff : 'Diff' ,
56
- django : 'Django' ,
57
- dockerfile : 'Dockerfile' ,
58
- ebnf : 'EBNF' ,
59
- elm : 'Elm' ,
60
- erlang : 'Erlang' ,
61
- 'erlang-repl' : 'Erlang' ,
62
- fortran : 'Fortran' ,
63
- fsharp : 'F#' ,
64
- gherkin : 'Gherkin' ,
65
- go : 'Go' ,
66
- groovy : 'Groovy' ,
67
- haml : 'HAML' ,
68
- haskell : 'Haskell' ,
69
- haxe : 'Haxe' ,
70
- http : 'HTTP' ,
71
- ini : 'toml' ,
72
- java : 'Java' ,
73
- javascript : 'JavaScript' ,
74
- json : 'JSON' ,
75
- julia : 'Julia' ,
76
- kotlin : 'Kotlin' ,
77
- less : 'LESS' ,
78
- livescript : 'LiveScript' ,
79
- lua : 'Lua' ,
80
- markdown : 'Markdown' ,
81
- mathematica : 'Mathematica' ,
82
- nginx : 'Nginx' ,
83
- nsis : 'NSIS' ,
84
- objectivec : 'Objective-C' ,
85
- ocaml : 'Ocaml' ,
86
- perl : 'Perl' ,
87
- php : 'PHP' ,
88
- powershell : 'PowerShell' ,
89
- properties : 'Properties files' ,
90
- protobuf : 'ProtoBuf' ,
91
- python : 'Python' ,
92
- puppet : 'Puppet' ,
93
- q : 'Q' ,
94
- r : 'R' ,
95
- ruby : 'Ruby' ,
96
- rust : 'Rust' ,
97
- sas : 'SAS' ,
98
- scala : 'Scala' ,
99
- scheme : 'Scheme' ,
100
- scss : 'SCSS' ,
101
- shell : 'Shell' ,
102
- smalltalk : 'Smalltalk' ,
103
- sml : 'SML' ,
104
- sql : 'SQL' ,
105
- stylus : 'Stylus' ,
106
- swift : 'Swift' ,
107
- tcl : 'Tcl' ,
108
- tex : 'LaTex' ,
109
- typescript : 'TypeScript' ,
110
- twig : 'Twig' ,
111
- vbnet : 'VB.NET' ,
112
- vbscript : 'VBScript' ,
113
- verilog : 'Verilog' ,
114
- vhdl : 'VHDL' ,
115
- xml : 'HTML' ,
116
- xquery : 'XQuery' ,
117
- yaml : 'YAML' ,
118
- elixir : 'Elixir'
119
- }
120
-
121
41
const validatorOfMarkdown = ( text , updateLinting ) => {
122
42
const lintOptions = {
123
43
'strings' : {
@@ -261,7 +181,8 @@ export default class CodeEditor extends React.Component {
261
181
262
182
updateDefaultKeyMap ( ) {
263
183
const { hotkey } = this . props
264
- const expandSnippet = this . expandSnippet . bind ( this )
184
+ const self = this
185
+ const expandSnippet = snippetManager . expandSnippet
265
186
266
187
this . defaultKeyMap = CodeMirror . normalizeKeyMap ( {
267
188
Tab : function ( cm ) {
@@ -285,10 +206,12 @@ export default class CodeEditor extends React.Component {
285
206
cursor . ch > 1
286
207
) {
287
208
// text expansion on tab key if the char before is alphabet
288
- const snippets = JSON . parse (
289
- fs . readFileSync ( consts . SNIPPET_FILE , 'utf8' )
209
+ const wordBeforeCursor = self . getWordBeforeCursor (
210
+ line ,
211
+ cursor . line ,
212
+ cursor . ch
290
213
)
291
- if ( expandSnippet ( line , cursor , cm , snippets ) === false ) {
214
+ if ( expandSnippet ( wordBeforeCursor , cursor , cm ) === false ) {
292
215
if ( tabs ) {
293
216
cm . execCommand ( 'insertTab' )
294
217
} else {
@@ -310,6 +233,26 @@ export default class CodeEditor extends React.Component {
310
233
'Cmd-T' : function ( cm ) {
311
234
// Do nothing
312
235
} ,
236
+ 'Ctrl-/' : function ( cm ) {
237
+ if ( global . process . platform === 'darwin' ) { return }
238
+ const dateNow = new Date ( )
239
+ cm . replaceSelection ( dateNow . toLocaleDateString ( ) )
240
+ } ,
241
+ 'Cmd-/' : function ( cm ) {
242
+ if ( global . process . platform !== 'darwin' ) { return }
243
+ const dateNow = new Date ( )
244
+ cm . replaceSelection ( dateNow . toLocaleDateString ( ) )
245
+ } ,
246
+ 'Shift-Ctrl-/' : function ( cm ) {
247
+ if ( global . process . platform === 'darwin' ) { return }
248
+ const dateNow = new Date ( )
249
+ cm . replaceSelection ( dateNow . toLocaleString ( ) )
250
+ } ,
251
+ 'Shift-Cmd-/' : function ( cm ) {
252
+ if ( global . process . platform !== 'darwin' ) { return }
253
+ const dateNow = new Date ( )
254
+ cm . replaceSelection ( dateNow . toLocaleString ( ) )
255
+ } ,
313
256
Enter : 'boostNewLineAndIndentContinueMarkdownList' ,
314
257
'Ctrl-C' : cm => {
315
258
if ( cm . getOption ( 'keyMap' ) . substr ( 0 , 3 ) === 'vim' ) {
@@ -343,22 +286,7 @@ export default class CodeEditor extends React.Component {
343
286
const { rulers, enableRulers } = this . props
344
287
eventEmitter . on ( 'line:jump' , this . scrollToLineHandeler )
345
288
346
- const defaultSnippet = [
347
- {
348
- id : crypto . randomBytes ( 16 ) . toString ( 'hex' ) ,
349
- name : 'Dummy text' ,
350
- prefix : [ 'lorem' , 'ipsum' ] ,
351
- 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.'
352
- }
353
- ]
354
- if ( ! fs . existsSync ( consts . SNIPPET_FILE ) ) {
355
- fs . writeFileSync (
356
- consts . SNIPPET_FILE ,
357
- JSON . stringify ( defaultSnippet , null , 4 ) ,
358
- 'utf8'
359
- )
360
- }
361
-
289
+ snippetManager . init ( )
362
290
this . updateDefaultKeyMap ( )
363
291
364
292
const checkMarkdownNoteIsOpening = this . props . mode === 'Boost Flavored Markdown'
@@ -558,61 +486,12 @@ export default class CodeEditor extends React.Component {
558
486
this . initialHighlighting ( )
559
487
}
560
488
561
- expandSnippet ( line , cursor , cm , snippets ) {
562
- const wordBeforeCursor = this . getWordBeforeCursor (
563
- line ,
564
- cursor . line ,
565
- cursor . ch
566
- )
567
- const templateCursorString = ':{}'
568
- for ( let i = 0 ; i < snippets . length ; i ++ ) {
569
- if ( snippets [ i ] . prefix . indexOf ( wordBeforeCursor . text ) !== - 1 ) {
570
- if ( snippets [ i ] . content . indexOf ( templateCursorString ) !== - 1 ) {
571
- const snippetLines = snippets [ i ] . content . split ( '\n' )
572
- let cursorLineNumber = 0
573
- let cursorLinePosition = 0
574
-
575
- let cursorIndex
576
- for ( let j = 0 ; j < snippetLines . length ; j ++ ) {
577
- cursorIndex = snippetLines [ j ] . indexOf ( templateCursorString )
578
-
579
- if ( cursorIndex !== - 1 ) {
580
- cursorLineNumber = j
581
- cursorLinePosition = cursorIndex
582
-
583
- break
584
- }
585
- }
586
-
587
- cm . replaceRange (
588
- snippets [ i ] . content . replace ( templateCursorString , '' ) ,
589
- wordBeforeCursor . range . from ,
590
- wordBeforeCursor . range . to
591
- )
592
- cm . setCursor ( {
593
- line : cursor . line + cursorLineNumber ,
594
- ch : cursorLinePosition + cursor . ch - wordBeforeCursor . text . length
595
- } )
596
- } else {
597
- cm . replaceRange (
598
- snippets [ i ] . content ,
599
- wordBeforeCursor . range . from ,
600
- wordBeforeCursor . range . to
601
- )
602
- }
603
- return true
604
- }
605
- }
606
-
607
- return false
608
- }
609
-
610
489
getWordBeforeCursor ( line , lineNumber , cursorPosition ) {
611
490
let wordBeforeCursor = ''
612
491
const originCursorPosition = cursorPosition
613
492
const emptyChars = / \t | \s | \r | \n /
614
493
615
- // to prevent the word to expand is long that will crash the whole app
494
+ // to prevent the word is long that will crash the whole app
616
495
// the safeStop is there to stop user to expand words that longer than 20 chars
617
496
const safeStop = 20
618
497
@@ -622,7 +501,7 @@ export default class CodeEditor extends React.Component {
622
501
if ( ! emptyChars . test ( currentChar ) ) {
623
502
wordBeforeCursor = currentChar + wordBeforeCursor
624
503
} else if ( wordBeforeCursor . length >= safeStop ) {
625
- throw new Error ( 'Your snippet trigger is too long !' )
504
+ throw new Error ( 'Stopped after 20 loops for safety reason !' )
626
505
} else {
627
506
break
628
507
}
@@ -776,6 +655,34 @@ export default class CodeEditor extends React.Component {
776
655
handleChange ( editor , changeObject ) {
777
656
spellcheck . handleChange ( editor , changeObject )
778
657
658
+ // The current note contains an toc. We'll check for changes on headlines.
659
+ // origin is undefined when markdownTocGenerator replace the old tod
660
+ if ( tocExistsInEditor ( editor ) && changeObject . origin !== undefined ) {
661
+ let requireTocUpdate
662
+
663
+ // Check if one of the changed lines contains a headline
664
+ for ( let line = 0 ; line < changeObject . text . length ; line ++ ) {
665
+ if ( this . linePossibleContainsHeadline ( editor . getLine ( changeObject . from . line + line ) ) ) {
666
+ requireTocUpdate = true
667
+ break
668
+ }
669
+ }
670
+
671
+ if ( ! requireTocUpdate ) {
672
+ // Check if one of the removed lines contains a headline
673
+ for ( let line = 0 ; line < changeObject . removed . length ; line ++ ) {
674
+ if ( this . linePossibleContainsHeadline ( changeObject . removed [ line ] ) ) {
675
+ requireTocUpdate = true
676
+ break
677
+ }
678
+ }
679
+ }
680
+
681
+ if ( requireTocUpdate ) {
682
+ generateInEditor ( editor )
683
+ }
684
+ }
685
+
779
686
this . updateHighlight ( editor , changeObject )
780
687
781
688
this . value = editor . getValue ( )
@@ -784,15 +691,21 @@ export default class CodeEditor extends React.Component {
784
691
}
785
692
}
786
693
694
+ linePossibleContainsHeadline ( currentLine ) {
695
+ // We can't check if the line start with # because when some write text before
696
+ // the # we also need to update the toc
697
+ return currentLine . includes ( '# ' )
698
+ }
699
+
787
700
incrementLines ( start , linesAdded , linesRemoved , editor ) {
788
- let highlightedLines = editor . options . linesHighlighted
701
+ const highlightedLines = editor . options . linesHighlighted
789
702
790
703
const totalHighlightedLines = highlightedLines . length
791
704
792
- let offset = linesAdded - linesRemoved
705
+ const offset = linesAdded - linesRemoved
793
706
794
707
// Store new items to be added as we're changing the lines
795
- let newLines = [ ]
708
+ const newLines = [ ]
796
709
797
710
let i = totalHighlightedLines
798
711
@@ -873,6 +786,9 @@ export default class CodeEditor extends React.Component {
873
786
ch : 1
874
787
}
875
788
this . editor . setCursor ( cursor )
789
+ const top = this . editor . charCoords ( { line : num , ch : 0 } , 'local' ) . top
790
+ const middleHeight = this . editor . getScrollerElement ( ) . offsetHeight / 2
791
+ this . editor . scrollTo ( null , top - middleHeight - 5 )
876
792
}
877
793
878
794
focus ( ) {
@@ -988,6 +904,8 @@ export default class CodeEditor extends React.Component {
988
904
989
905
if ( isInFencedCodeBlock ( editor ) ) {
990
906
this . handlePasteText ( editor , pastedTxt )
907
+ } else if ( fetchUrlTitle && isMarkdownTitleURL ( pastedTxt ) && ! isInLinkTag ( editor ) ) {
908
+ this . handlePasteUrl ( editor , pastedTxt )
991
909
} else if ( fetchUrlTitle && isURL ( pastedTxt ) && ! isInLinkTag ( editor ) ) {
992
910
this . handlePasteUrl ( editor , pastedTxt )
993
911
} else if ( attachmentManagement . isAttachmentLink ( pastedTxt ) ) {
@@ -1029,7 +947,17 @@ export default class CodeEditor extends React.Component {
1029
947
}
1030
948
1031
949
handlePasteUrl ( editor , pastedTxt ) {
1032
- const taggedUrl = `<${ pastedTxt } >`
950
+ let taggedUrl = `<${ pastedTxt } >`
951
+ let urlToFetch = pastedTxt
952
+ let titleMark = ''
953
+
954
+ if ( isMarkdownTitleURL ( pastedTxt ) ) {
955
+ const pastedTxtSplitted = pastedTxt . split ( ' ' )
956
+ titleMark = `${ pastedTxtSplitted [ 0 ] } `
957
+ urlToFetch = pastedTxtSplitted [ 1 ]
958
+ taggedUrl = `<${ urlToFetch } >`
959
+ }
960
+
1033
961
editor . replaceSelection ( taggedUrl )
1034
962
1035
963
const isImageReponse = response => {
@@ -1041,22 +969,23 @@ export default class CodeEditor extends React.Component {
1041
969
const replaceTaggedUrl = replacement => {
1042
970
const value = editor . getValue ( )
1043
971
const cursor = editor . getCursor ( )
1044
- const newValue = value . replace ( taggedUrl , replacement )
972
+ const newValue = value . replace ( taggedUrl , titleMark + replacement )
1045
973
const newCursor = Object . assign ( { } , cursor , {
1046
- ch : cursor . ch + newValue . length - value . length
974
+ ch : cursor . ch + newValue . length - ( value . length - titleMark . length )
1047
975
} )
976
+
1048
977
editor . setValue ( newValue )
1049
978
editor . setCursor ( newCursor )
1050
979
}
1051
980
1052
- fetch ( pastedTxt , {
981
+ fetch ( urlToFetch , {
1053
982
method : 'get'
1054
983
} )
1055
984
. then ( response => {
1056
985
if ( isImageReponse ( response ) ) {
1057
- return this . mapImageResponse ( response , pastedTxt )
986
+ return this . mapImageResponse ( response , urlToFetch )
1058
987
} else {
1059
- return this . mapNormalResponse ( response , pastedTxt )
988
+ return this . mapNormalResponse ( response , urlToFetch )
1060
989
}
1061
990
} )
1062
991
. then ( replacement => {
0 commit comments