@@ -14,6 +14,8 @@ 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
+
18
+ import { isMarkdownTitleURL } from 'browser/lib/utils'
17
19
import styles from '../components/CodeEditor.styl'
18
20
const { ipcRenderer, remote, clipboard } = require ( 'electron' )
19
21
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
@@ -22,6 +24,8 @@ const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
22
24
import TurndownService from 'turndown'
23
25
import { languageMaps } from '../lib/CMLanguageList'
24
26
import snippetManager from '../lib/SnippetManager'
27
+ import { generateInEditor , tocExistsInEditor } from 'browser/lib/markdown-toc-generator'
28
+ import markdownlint from 'markdownlint'
25
29
26
30
CodeMirror . modeURL = '../node_modules/codemirror/mode/%N/%N.js'
27
31
@@ -34,6 +38,38 @@ function translateHotkey (hotkey) {
34
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' )
35
39
}
36
40
41
+ const validatorOfMarkdown = ( text , updateLinting ) => {
42
+ const lintOptions = {
43
+ 'strings' : {
44
+ 'content' : text
45
+ }
46
+ }
47
+
48
+ return markdownlint ( lintOptions , ( err , result ) => {
49
+ if ( ! err ) {
50
+ const foundIssues = [ ]
51
+ result . content . map ( item => {
52
+ let ruleNames = ''
53
+ item . ruleNames . map ( ( ruleName , index ) => {
54
+ ruleNames += ruleName
55
+ if ( index === item . ruleNames . length - 1 ) {
56
+ ruleNames += ': '
57
+ } else {
58
+ ruleNames += '/'
59
+ }
60
+ } )
61
+ foundIssues . push ( {
62
+ from : CodeMirror . Pos ( item . lineNumber , 0 ) ,
63
+ to : CodeMirror . Pos ( item . lineNumber , 1 ) ,
64
+ message : ruleNames + item . ruleDescription ,
65
+ severity : 'warning'
66
+ } )
67
+ } )
68
+ updateLinting ( foundIssues )
69
+ }
70
+ } )
71
+ }
72
+
37
73
export default class CodeEditor extends React . Component {
38
74
constructor ( props ) {
39
75
super ( props )
@@ -197,6 +233,26 @@ export default class CodeEditor extends React.Component {
197
233
'Cmd-T' : function ( cm ) {
198
234
// Do nothing
199
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
+ } ,
200
256
Enter : 'boostNewLineAndIndentContinueMarkdownList' ,
201
257
'Ctrl-C' : cm => {
202
258
if ( cm . getOption ( 'keyMap' ) . substr ( 0 , 3 ) === 'vim' ) {
@@ -233,6 +289,7 @@ export default class CodeEditor extends React.Component {
233
289
snippetManager . init ( )
234
290
this . updateDefaultKeyMap ( )
235
291
292
+ const checkMarkdownNoteIsOpening = this . props . mode === 'Boost Flavored Markdown'
236
293
this . value = this . props . value
237
294
this . editor = CodeMirror ( this . refs . root , {
238
295
rulers : buildCMRulers ( rulers , enableRulers ) ,
@@ -249,7 +306,11 @@ export default class CodeEditor extends React.Component {
249
306
inputStyle : 'textarea' ,
250
307
dragDrop : false ,
251
308
foldGutter : true ,
252
- gutters : [ 'CodeMirror-linenumbers' , 'CodeMirror-foldgutter' ] ,
309
+ lint : checkMarkdownNoteIsOpening ? {
310
+ 'getAnnotations' : validatorOfMarkdown ,
311
+ 'async' : true
312
+ } : false ,
313
+ gutters : [ 'CodeMirror-linenumbers' , 'CodeMirror-foldgutter' , 'CodeMirror-lint-markers' ] ,
253
314
autoCloseBrackets : {
254
315
pairs : this . props . matchingPairs ,
255
316
triples : this . props . matchingTriples ,
@@ -594,6 +655,34 @@ export default class CodeEditor extends React.Component {
594
655
handleChange ( editor , changeObject ) {
595
656
spellcheck . handleChange ( editor , changeObject )
596
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
+
597
686
this . updateHighlight ( editor , changeObject )
598
687
599
688
this . value = editor . getValue ( )
@@ -602,6 +691,12 @@ export default class CodeEditor extends React.Component {
602
691
}
603
692
}
604
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
+
605
700
incrementLines ( start , linesAdded , linesRemoved , editor ) {
606
701
const highlightedLines = editor . options . linesHighlighted
607
702
@@ -809,6 +904,8 @@ export default class CodeEditor extends React.Component {
809
904
810
905
if ( isInFencedCodeBlock ( editor ) ) {
811
906
this . handlePasteText ( editor , pastedTxt )
907
+ } else if ( fetchUrlTitle && isMarkdownTitleURL ( pastedTxt ) && ! isInLinkTag ( editor ) ) {
908
+ this . handlePasteUrl ( editor , pastedTxt )
812
909
} else if ( fetchUrlTitle && isURL ( pastedTxt ) && ! isInLinkTag ( editor ) ) {
813
910
this . handlePasteUrl ( editor , pastedTxt )
814
911
} else if ( attachmentManagement . isAttachmentLink ( pastedTxt ) ) {
@@ -850,7 +947,17 @@ export default class CodeEditor extends React.Component {
850
947
}
851
948
852
949
handlePasteUrl ( editor , pastedTxt ) {
853
- 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
+
854
961
editor . replaceSelection ( taggedUrl )
855
962
856
963
const isImageReponse = response => {
@@ -862,22 +969,23 @@ export default class CodeEditor extends React.Component {
862
969
const replaceTaggedUrl = replacement => {
863
970
const value = editor . getValue ( )
864
971
const cursor = editor . getCursor ( )
865
- const newValue = value . replace ( taggedUrl , replacement )
972
+ const newValue = value . replace ( taggedUrl , titleMark + replacement )
866
973
const newCursor = Object . assign ( { } , cursor , {
867
- ch : cursor . ch + newValue . length - value . length
974
+ ch : cursor . ch + newValue . length - ( value . length - titleMark . length )
868
975
} )
976
+
869
977
editor . setValue ( newValue )
870
978
editor . setCursor ( newCursor )
871
979
}
872
980
873
- fetch ( pastedTxt , {
981
+ fetch ( urlToFetch , {
874
982
method : 'get'
875
983
} )
876
984
. then ( response => {
877
985
if ( isImageReponse ( response ) ) {
878
- return this . mapImageResponse ( response , pastedTxt )
986
+ return this . mapImageResponse ( response , urlToFetch )
879
987
} else {
880
- return this . mapNormalResponse ( response , pastedTxt )
988
+ return this . mapNormalResponse ( response , urlToFetch )
881
989
}
882
990
} )
883
991
. then ( replacement => {
0 commit comments