Skip to content

Commit 6ff03bb

Browse files
author
hooklife
authored
Merge branch 'master' into perfect-zh-CN
2 parents 50bce48 + 9fac6bc commit 6ff03bb

Some content is hidden

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

67 files changed

+2632
-1750
lines changed

browser/components/CodeEditor.js

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
77
import convertModeName from 'browser/lib/convertModeName'
88
import eventEmitter from 'browser/main/lib/eventEmitter'
99
import iconv from 'iconv-lite'
10-
10+
import crypto from 'crypto'
11+
import consts from 'browser/lib/consts'
12+
import fs from 'fs'
1113
const { ipcRenderer } = require('electron')
1214

1315
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -81,8 +83,21 @@ export default class CodeEditor extends React.Component {
8183

8284
componentDidMount () {
8385
const { rulers, enableRulers } = this.props
84-
this.value = this.props.value
86+
const expandSnippet = this.expandSnippet.bind(this)
87+
88+
const defaultSnippet = [
89+
{
90+
id: crypto.randomBytes(16).toString('hex'),
91+
name: 'Dummy text',
92+
prefix: ['lorem', 'ipsum'],
93+
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.'
94+
}
95+
]
96+
if (!fs.existsSync(consts.SNIPPET_FILE)) {
97+
fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
98+
}
8599

100+
this.value = this.props.value
86101
this.editor = CodeMirror(this.refs.root, {
87102
rulers: buildCMRulers(rulers, enableRulers),
88103
value: this.props.value,
@@ -103,6 +118,8 @@ export default class CodeEditor extends React.Component {
103118
Tab: function (cm) {
104119
const cursor = cm.getCursor()
105120
const line = cm.getLine(cursor.line)
121+
const cursorPosition = cursor.ch
122+
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
106123
if (cm.somethingSelected()) cm.indentSelection('add')
107124
else {
108125
const tabs = cm.getOption('indentWithTabs')
@@ -114,6 +131,16 @@ export default class CodeEditor extends React.Component {
114131
cm.execCommand('insertSoftTab')
115132
}
116133
cm.execCommand('goLineEnd')
134+
} else if (!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
135+
// text expansion on tab key if the char before is alphabet
136+
const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
137+
if (expandSnippet(line, cursor, cm, snippets) === false) {
138+
if (tabs) {
139+
cm.execCommand('insertTab')
140+
} else {
141+
cm.execCommand('insertSoftTab')
142+
}
143+
}
117144
} else {
118145
if (tabs) {
119146
cm.execCommand('insertTab')
@@ -157,6 +184,73 @@ export default class CodeEditor extends React.Component {
157184
CodeMirror.Vim.map('ZZ', ':q', 'normal')
158185
}
159186

187+
expandSnippet (line, cursor, cm, snippets) {
188+
const wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch)
189+
const templateCursorString = ':{}'
190+
for (let i = 0; i < snippets.length; i++) {
191+
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
192+
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
193+
const snippetLines = snippets[i].content.split('\n')
194+
let cursorLineNumber = 0
195+
let cursorLinePosition = 0
196+
for (let j = 0; j < snippetLines.length; j++) {
197+
const cursorIndex = snippetLines[j].indexOf(templateCursorString)
198+
if (cursorIndex !== -1) {
199+
cursorLineNumber = j
200+
cursorLinePosition = cursorIndex
201+
cm.replaceRange(
202+
snippets[i].content.replace(templateCursorString, ''),
203+
wordBeforeCursor.range.from,
204+
wordBeforeCursor.range.to
205+
)
206+
cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition })
207+
}
208+
}
209+
} else {
210+
cm.replaceRange(
211+
snippets[i].content,
212+
wordBeforeCursor.range.from,
213+
wordBeforeCursor.range.to
214+
)
215+
}
216+
return true
217+
}
218+
}
219+
220+
return false
221+
}
222+
223+
getWordBeforeCursor (line, lineNumber, cursorPosition) {
224+
let wordBeforeCursor = ''
225+
const originCursorPosition = cursorPosition
226+
const emptyChars = /\t|\s|\r|\n/
227+
228+
// to prevent the word to expand is long that will crash the whole app
229+
// the safeStop is there to stop user to expand words that longer than 20 chars
230+
const safeStop = 20
231+
232+
while (cursorPosition > 0) {
233+
const currentChar = line.substr(cursorPosition - 1, 1)
234+
// if char is not an empty char
235+
if (!emptyChars.test(currentChar)) {
236+
wordBeforeCursor = currentChar + wordBeforeCursor
237+
} else if (wordBeforeCursor.length >= safeStop) {
238+
throw new Error('Your snippet trigger is too long !')
239+
} else {
240+
break
241+
}
242+
cursorPosition--
243+
}
244+
245+
return {
246+
text: wordBeforeCursor,
247+
range: {
248+
from: {line: lineNumber, ch: originCursorPosition},
249+
to: {line: lineNumber, ch: cursorPosition}
250+
}
251+
}
252+
}
253+
160254
quitEditor () {
161255
document.querySelector('textarea').blur()
162256
}
@@ -322,8 +416,9 @@ export default class CodeEditor extends React.Component {
322416
const cursor = editor.getCursor()
323417
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})`
324418
const newValue = value.replace(taggedUrl, LinkWithTitle)
419+
const newCursor = Object.assign({}, cursor, { ch: cursor.ch + newValue.length - value.length })
325420
editor.setValue(newValue)
326-
editor.setCursor(cursor)
421+
editor.setCursor(newCursor)
327422
}).catch((e) => {
328423
const value = editor.getValue()
329424
const newValue = value.replace(taggedUrl, pastedTxt)

browser/components/MarkdownEditor.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ class MarkdownEditor extends React.Component {
283283
indentSize={editorIndentSize}
284284
scrollPastEnd={config.preview.scrollPastEnd}
285285
smartQuotes={config.preview.smartQuotes}
286+
smartArrows={config.previw.smartArrows}
286287
breaks={config.preview.breaks}
287288
sanitize={config.preview.sanitize}
288289
ref='preview'
@@ -296,6 +297,8 @@ class MarkdownEditor extends React.Component {
296297
showCopyNotification={config.ui.showCopyNotification}
297298
storagePath={storage.path}
298299
noteKey={noteKey}
300+
customCSS={config.preview.customCSS}
301+
allowCustomCSS={config.preview.allowCustomCSS}
299302
/>
300303
</div>
301304
)

browser/components/MarkdownPreview.js

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const CSS_FILES = [
3232
`${appPath}/node_modules/codemirror/lib/codemirror.css`
3333
]
3434

35-
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) {
35+
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) {
3636
return `
3737
@font-face {
3838
font-family: 'Lato';
@@ -52,7 +52,19 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scro
5252
font-weight: 700;
5353
text-rendering: optimizeLegibility;
5454
}
55+
@font-face {
56+
font-family: 'Material Icons';
57+
font-style: normal;
58+
font-weight: 400;
59+
src: local('Material Icons'),
60+
local('MaterialIcons-Regular'),
61+
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff2') format('woff2'),
62+
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
63+
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
64+
}
65+
${allowCustomCSS ? customCSS : ''}
5566
${markdownStyle}
67+
5668
body {
5769
font-family: '${fontFamily.join("','")}';
5870
font-size: ${fontSize}px;
@@ -132,7 +144,6 @@ export default class MarkdownPreview extends React.Component {
132144
this.mouseUpHandler = (e) => this.handleMouseUp(e)
133145
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
134146
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
135-
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
136147
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
137148
this.saveAsTextHandler = () => this.handleSaveAsText()
138149
this.saveAsMdHandler = () => this.handleSaveAsMd()
@@ -153,22 +164,6 @@ export default class MarkdownPreview extends React.Component {
153164
})
154165
}
155166

156-
handlePreviewAnchorClick (e) {
157-
e.preventDefault()
158-
e.stopPropagation()
159-
160-
const anchor = e.target.closest('a')
161-
const href = anchor.getAttribute('href')
162-
if (_.isString(href) && href.match(/^#/)) {
163-
const targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
164-
if (targetElement != null) {
165-
this.getWindow().scrollTo(0, targetElement.offsetTop)
166-
}
167-
} else {
168-
shell.openExternal(href)
169-
}
170-
}
171-
172167
handleCheckboxClick (e) {
173168
this.props.onCheckboxClick(e)
174169
}
@@ -216,9 +211,9 @@ export default class MarkdownPreview extends React.Component {
216211

217212
handleSaveAsHtml () {
218213
this.exportAsDocument('html', (noteContent, exportTasks) => {
219-
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams()
214+
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
220215

221-
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme)
216+
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
222217
let body = this.markdown.render(escapeHtmlCharacters(noteContent))
223218

224219
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
@@ -343,6 +338,7 @@ export default class MarkdownPreview extends React.Component {
343338
if (prevProps.value !== this.props.value) this.rewriteIframe()
344339
if (prevProps.smartQuotes !== this.props.smartQuotes ||
345340
prevProps.sanitize !== this.props.sanitize ||
341+
prevProps.smartArrows !== this.props.smartArrows ||
346342
prevProps.breaks !== this.props.breaks) {
347343
this.initMarkdown()
348344
this.rewriteIframe()
@@ -354,14 +350,16 @@ export default class MarkdownPreview extends React.Component {
354350
prevProps.lineNumber !== this.props.lineNumber ||
355351
prevProps.showCopyNotification !== this.props.showCopyNotification ||
356352
prevProps.theme !== this.props.theme ||
357-
prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
353+
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
354+
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
355+
prevProps.customCSS !== this.props.customCSS) {
358356
this.applyStyle()
359357
this.rewriteIframe()
360358
}
361359
}
362360

363361
getStyleParams () {
364-
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme } = this.props
362+
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = this.props
365363
let { fontFamily, codeBlockFontFamily } = this.props
366364
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
367365
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
@@ -370,14 +368,14 @@ export default class MarkdownPreview extends React.Component {
370368
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
371369
: defaultCodeBlockFontFamily
372370

373-
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme}
371+
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS}
374372
}
375373

376374
applyStyle () {
377-
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams()
375+
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
378376

379377
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
380-
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme)
378+
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
381379
}
382380

383381
GetCodeThemeLink (theme) {
@@ -390,9 +388,6 @@ export default class MarkdownPreview extends React.Component {
390388
}
391389

392390
rewriteIframe () {
393-
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
394-
el.removeEventListener('click', this.anchorClickHandler)
395-
})
396391
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
397392
el.removeEventListener('click', this.checkboxClickHandler)
398393
})
@@ -401,7 +396,7 @@ export default class MarkdownPreview extends React.Component {
401396
el.removeEventListener('click', this.linkClickHandler)
402397
})
403398

404-
const { theme, indentSize, showCopyNotification, storagePath } = this.props
399+
const { theme, indentSize, showCopyNotification, storagePath, noteKey } = this.props
405400
let { value, codeBlockTheme } = this.props
406401

407402
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
@@ -413,18 +408,15 @@ export default class MarkdownPreview extends React.Component {
413408
})
414409
}
415410
let renderedHTML = this.markdown.render(value)
411+
attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
416412
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
417413

418-
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
419-
this.fixDecodedURI(el)
420-
el.addEventListener('click', this.anchorClickHandler)
421-
})
422-
423414
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
424415
el.addEventListener('click', this.checkboxClickHandler)
425416
})
426417

427418
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
419+
this.fixDecodedURI(el)
428420
el.addEventListener('click', this.linkClickHandler)
429421
})
430422

@@ -475,7 +467,7 @@ export default class MarkdownPreview extends React.Component {
475467
el.innerHTML = ''
476468
diagram.drawSVG(el, opts)
477469
_.forEach(el.querySelectorAll('a'), (el) => {
478-
el.addEventListener('click', this.anchorClickHandler)
470+
el.addEventListener('click', this.linkClickHandler)
479471
})
480472
} catch (e) {
481473
console.error(e)
@@ -491,7 +483,7 @@ export default class MarkdownPreview extends React.Component {
491483
el.innerHTML = ''
492484
diagram.drawSVG(el, {theme: 'simple'})
493485
_.forEach(el.querySelectorAll('a'), (el) => {
494-
el.addEventListener('click', this.anchorClickHandler)
486+
el.addEventListener('click', this.linkClickHandler)
495487
})
496488
} catch (e) {
497489
console.error(e)
@@ -598,10 +590,12 @@ MarkdownPreview.propTypes = {
598590
onDoubleClick: PropTypes.func,
599591
onMouseUp: PropTypes.func,
600592
onMouseDown: PropTypes.func,
593+
onContextMenu: PropTypes.func,
601594
className: PropTypes.string,
602595
value: PropTypes.string,
603596
showCopyNotification: PropTypes.bool,
604597
storagePath: PropTypes.string,
605598
smartQuotes: PropTypes.bool,
599+
smartArrows: PropTypes.bool,
606600
breaks: PropTypes.bool
607601
}

browser/components/MarkdownSplitEditor.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ class MarkdownSplitEditor extends React.Component {
131131
lineNumber={config.preview.lineNumber}
132132
scrollPastEnd={config.preview.scrollPastEnd}
133133
smartQuotes={config.preview.smartQuotes}
134+
smartArrows={config.preview.smartArrows}
134135
breaks={config.preview.breaks}
135136
sanitize={config.preview.sanitize}
136137
ref='preview'
@@ -141,6 +142,8 @@ class MarkdownSplitEditor extends React.Component {
141142
showCopyNotification={config.ui.showCopyNotification}
142143
storagePath={storage.path}
143144
noteKey={noteKey}
145+
customCSS={config.preview.customCSS}
146+
allowCustomCSS={config.preview.allowCustomCSS}
144147
/>
145148
</div>
146149
)

browser/components/SideNavFilter.styl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
.iconWrap
1919
width 20px
2020
text-align center
21-
21+
2222
.counters
2323
float right
2424
color $ui-inactive-text-color
@@ -68,10 +68,9 @@
6868
.menu-button-label
6969
position fixed
7070
display inline-block
71-
height 32px
71+
height 36px
7272
left 44px
7373
padding 0 10px
74-
margin-top -8px
7574
margin-left 0
7675
overflow ellipsis
7776
z-index 10

browser/components/StorageItem.styl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@
5858
opacity 0
5959
border-top-right-radius 2px
6060
border-bottom-right-radius 2px
61-
height 26px
62-
line-height 26px
61+
height 34px
62+
line-height 32px
6363

6464
.folderList-item:hover, .folderList-item--active:hover
6565
.folderList-item-tooltip

0 commit comments

Comments
 (0)