Skip to content

Commit 03b1adc

Browse files
committed
Merge master branch into this branch
2 parents c15cc2e + aecf2eb commit 03b1adc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1288
-701
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
language: node_js
22
node_js:
3-
- 7
3+
- 8
44
script:
55
- npm run lint && npm run test
66
- yarn jest
7-
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
7+
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@6.4 && grunt pre-build; fi'
88
after_success:
99
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
1010
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d

browser/components/CodeEditor.js

Lines changed: 94 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,17 @@ import {
1414
import TextEditorInterface from 'browser/lib/TextEditorInterface'
1515
import eventEmitter from 'browser/main/lib/eventEmitter'
1616
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'
1919
import styles from '../components/CodeEditor.styl'
20-
import fs from 'fs'
2120
const { ipcRenderer, remote, clipboard } = require('electron')
2221
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
2322
const spellcheck = require('browser/lib/spellcheck')
2423
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
2524
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'
2928
import markdownlint from 'markdownlint'
3029

3130
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -39,85 +38,6 @@ function translateHotkey (hotkey) {
3938
return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl')
4039
}
4140

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-
12141
const validatorOfMarkdown = (text, updateLinting) => {
12242
const lintOptions = {
12343
'strings': {
@@ -261,7 +181,8 @@ export default class CodeEditor extends React.Component {
261181

262182
updateDefaultKeyMap () {
263183
const { hotkey } = this.props
264-
const expandSnippet = this.expandSnippet.bind(this)
184+
const self = this
185+
const expandSnippet = snippetManager.expandSnippet
265186

266187
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
267188
Tab: function (cm) {
@@ -285,10 +206,12 @@ export default class CodeEditor extends React.Component {
285206
cursor.ch > 1
286207
) {
287208
// 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
290213
)
291-
if (expandSnippet(line, cursor, cm, snippets) === false) {
214+
if (expandSnippet(wordBeforeCursor, cursor, cm) === false) {
292215
if (tabs) {
293216
cm.execCommand('insertTab')
294217
} else {
@@ -310,6 +233,26 @@ export default class CodeEditor extends React.Component {
310233
'Cmd-T': function (cm) {
311234
// Do nothing
312235
},
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+
},
313256
Enter: 'boostNewLineAndIndentContinueMarkdownList',
314257
'Ctrl-C': cm => {
315258
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
@@ -343,22 +286,7 @@ export default class CodeEditor extends React.Component {
343286
const { rulers, enableRulers } = this.props
344287
eventEmitter.on('line:jump', this.scrollToLineHandeler)
345288

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()
362290
this.updateDefaultKeyMap()
363291

364292
const checkMarkdownNoteIsOpening = this.props.mode === 'Boost Flavored Markdown'
@@ -558,61 +486,12 @@ export default class CodeEditor extends React.Component {
558486
this.initialHighlighting()
559487
}
560488

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-
610489
getWordBeforeCursor (line, lineNumber, cursorPosition) {
611490
let wordBeforeCursor = ''
612491
const originCursorPosition = cursorPosition
613492
const emptyChars = /\t|\s|\r|\n/
614493

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
616495
// the safeStop is there to stop user to expand words that longer than 20 chars
617496
const safeStop = 20
618497

@@ -622,7 +501,7 @@ export default class CodeEditor extends React.Component {
622501
if (!emptyChars.test(currentChar)) {
623502
wordBeforeCursor = currentChar + wordBeforeCursor
624503
} 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 !')
626505
} else {
627506
break
628507
}
@@ -776,6 +655,34 @@ export default class CodeEditor extends React.Component {
776655
handleChange (editor, changeObject) {
777656
spellcheck.handleChange(editor, changeObject)
778657

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+
779686
this.updateHighlight(editor, changeObject)
780687

781688
this.value = editor.getValue()
@@ -784,15 +691,21 @@ export default class CodeEditor extends React.Component {
784691
}
785692
}
786693

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+
787700
incrementLines (start, linesAdded, linesRemoved, editor) {
788-
let highlightedLines = editor.options.linesHighlighted
701+
const highlightedLines = editor.options.linesHighlighted
789702

790703
const totalHighlightedLines = highlightedLines.length
791704

792-
let offset = linesAdded - linesRemoved
705+
const offset = linesAdded - linesRemoved
793706

794707
// Store new items to be added as we're changing the lines
795-
let newLines = []
708+
const newLines = []
796709

797710
let i = totalHighlightedLines
798711

@@ -873,6 +786,9 @@ export default class CodeEditor extends React.Component {
873786
ch: 1
874787
}
875788
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)
876792
}
877793

878794
focus () {
@@ -988,6 +904,8 @@ export default class CodeEditor extends React.Component {
988904

989905
if (isInFencedCodeBlock(editor)) {
990906
this.handlePasteText(editor, pastedTxt)
907+
} else if (fetchUrlTitle && isMarkdownTitleURL(pastedTxt) && !isInLinkTag(editor)) {
908+
this.handlePasteUrl(editor, pastedTxt)
991909
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
992910
this.handlePasteUrl(editor, pastedTxt)
993911
} else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
@@ -1029,7 +947,17 @@ export default class CodeEditor extends React.Component {
1029947
}
1030948

1031949
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+
1033961
editor.replaceSelection(taggedUrl)
1034962

1035963
const isImageReponse = response => {
@@ -1041,22 +969,23 @@ export default class CodeEditor extends React.Component {
1041969
const replaceTaggedUrl = replacement => {
1042970
const value = editor.getValue()
1043971
const cursor = editor.getCursor()
1044-
const newValue = value.replace(taggedUrl, replacement)
972+
const newValue = value.replace(taggedUrl, titleMark + replacement)
1045973
const newCursor = Object.assign({}, cursor, {
1046-
ch: cursor.ch + newValue.length - value.length
974+
ch: cursor.ch + newValue.length - (value.length - titleMark.length)
1047975
})
976+
1048977
editor.setValue(newValue)
1049978
editor.setCursor(newCursor)
1050979
}
1051980

1052-
fetch(pastedTxt, {
981+
fetch(urlToFetch, {
1053982
method: 'get'
1054983
})
1055984
.then(response => {
1056985
if (isImageReponse(response)) {
1057-
return this.mapImageResponse(response, pastedTxt)
986+
return this.mapImageResponse(response, urlToFetch)
1058987
} else {
1059-
return this.mapNormalResponse(response, pastedTxt)
988+
return this.mapNormalResponse(response, urlToFetch)
1060989
}
1061990
})
1062991
.then(replacement => {

0 commit comments

Comments
 (0)