Skip to content

Commit e4238f9

Browse files
committed
Merged branch dev into master
2 parents d73b567 + 9cd6d6d commit e4238f9

8 files changed

Lines changed: 167 additions & 49 deletions

File tree

browser/components/CodeEditor.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ export default class CodeEditor extends React.Component {
219219
session.on('change', this.changeHandler)
220220
}
221221

222+
setValue (value) {
223+
let session = this.editor.getSession()
224+
session.setValue(value)
225+
this.value = value
226+
}
227+
222228
render () {
223229
let { className, fontFamily } = this.props
224230
fontFamily = _.isString(fontFamily) && fontFamily.length > 0

browser/components/MarkdownEditor.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,29 @@ class MarkdownEditor extends React.Component {
7373
}
7474
}
7575

76+
handleCheckboxClick (e) {
77+
e.preventDefault()
78+
e.stopPropagation()
79+
let idMatch = /checkbox-([0-9]+)/
80+
let checkedMatch = /\[x\]/i
81+
let uncheckedMatch = /\[ \]/
82+
if (idMatch.test(e.target.getAttribute('id'))) {
83+
let lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
84+
let lines = this.refs.code.value
85+
.split('\n')
86+
87+
let targetLine = lines[lineIndex]
88+
89+
if (targetLine.match(checkedMatch)) {
90+
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
91+
}
92+
if (targetLine.match(uncheckedMatch)) {
93+
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
94+
}
95+
this.refs.code.setValue(lines.join('\n'))
96+
}
97+
}
98+
7699
focus () {
77100
if (this.state.status === 'PREVIEW') {
78101
this.setState({
@@ -135,6 +158,7 @@ class MarkdownEditor extends React.Component {
135158
value={value}
136159
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
137160
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
161+
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
138162
/>
139163
</div>
140164
)

browser/components/MarkdownPreview.js

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ import hljsTheme from 'browser/lib/hljsThemes'
55

66
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
77
const { shell } = require('electron')
8-
const goExternal = function (e) {
9-
e.preventDefault()
10-
e.stopPropagation()
11-
shell.openExternal(e.target.href)
12-
}
138

149
const OSX = global.process.platform === 'darwin'
1510

@@ -27,17 +22,48 @@ export default class MarkdownPreview extends React.Component {
2722
this.contextMenuHandler = (e) => this.handleContextMenu(e)
2823
this.mouseDownHandler = (e) => this.handleMouseDown(e)
2924
this.mouseUpHandler = (e) => this.handleMouseUp(e)
25+
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
26+
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
27+
}
28+
29+
handlePreviewAnchorClick (e) {
30+
e.preventDefault()
31+
e.stopPropagation()
32+
33+
let href = e.target.getAttribute('href')
34+
if (_.isString(href) && href.match(/^#/)) {
35+
let targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
36+
if (targetElement != null) {
37+
this.getWindow().scrollTo(0, targetElement.offsetTop)
38+
}
39+
} else {
40+
shell.openExternal(e.target.href)
41+
}
42+
}
43+
44+
handleCheckboxClick (e) {
45+
this.props.onCheckboxClick(e)
3046
}
3147

3248
handleContextMenu (e) {
3349
this.props.onContextMenu(e)
3450
}
3551

3652
handleMouseDown (e) {
53+
if (e.target != null) {
54+
switch (e.target.tagName) {
55+
case 'A':
56+
case 'INPUT':
57+
return null
58+
}
59+
}
3760
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
3861
}
3962

4063
handleMouseUp (e) {
64+
if (e.target != null && e.target.tagName === 'A') {
65+
return null
66+
}
4167
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
4268
}
4369

@@ -68,7 +94,10 @@ export default class MarkdownPreview extends React.Component {
6894

6995
rewriteIframe () {
7096
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
71-
el.removeEventListener('click', goExternal)
97+
el.removeEventListener('click', this.anchorClickHandler)
98+
})
99+
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
100+
el.removeEventListener('click', this.checkboxClickHandler)
72101
})
73102

74103
let { value, fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme } = this.props
@@ -111,7 +140,10 @@ export default class MarkdownPreview extends React.Component {
111140
this.refs.root.contentWindow.document.body.innerHTML = markdown(value)
112141

113142
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
114-
el.addEventListener('mousedown', goExternal)
143+
el.addEventListener('click', this.anchorClickHandler)
144+
})
145+
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
146+
el.addEventListener('click', this.checkboxClickHandler)
115147
})
116148
}
117149

@@ -124,14 +156,14 @@ export default class MarkdownPreview extends React.Component {
124156
}
125157

126158
scrollTo (targetRow) {
127-
let lineAnchors = this.getWindow().document.querySelectorAll('a.lineAnchor')
159+
let blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
128160

129-
for (let index = 0; index < lineAnchors.length; index++) {
130-
let lineAnchor = lineAnchors[index]
131-
let row = parseInt(lineAnchor.getAttribute('data-key'))
161+
for (let index = 0; index < blocks.length; index++) {
162+
let block = blocks[index]
163+
let row = parseInt(block.getAttribute('data-line'))
132164
if (row > targetRow) {
133-
let targetAnchor = lineAnchors[index - 1]
134-
this.getWindow().scrollTo(0, targetAnchor.offsetTop)
165+
let targetAnchor = blocks[index - 1]
166+
targetAnchor != null && this.getWindow().scrollTo(0, targetAnchor.offsetTop)
135167
break
136168
}
137169
}
@@ -147,6 +179,7 @@ export default class MarkdownPreview extends React.Component {
147179
style={style}
148180
tabIndex={tabIndex}
149181
ref='root'
182+
onClick={(e) => this.handleClick(e)}
150183
/>
151184
)
152185
}

browser/components/markdown.styl

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ body
6868
padding 5px
6969
margin -5px
7070
border-radius 5px
71+
li
72+
label.taskListItem
73+
margin-left -2em
74+
background-color white
7175
div.math-rendered
7276
text-align center
7377
.math-failed
@@ -102,12 +106,6 @@ a
102106
background-color alpha(#FFC95C, 0.3)
103107
&:visited
104108
color brandColor
105-
&.lineAnchor
106-
padding 0
107-
margin 0
108-
display block
109-
font-size 0
110-
height 0
111109
hr
112110
border-top none
113111
border-bottom solid 1px borderColor
@@ -147,9 +145,6 @@ h6
147145
line-height 1.4em
148146
margin 1em 0 1em
149147
color #777
150-
151-
*:not(a.lineAnchor) + p, *:not(a.lineAnchor) + blockquote, *:not(a.lineAnchor) + ul, *:not(a.lineAnchor) + ol, *:not(a.lineAnchor) + pre
152-
margin-top 1em
153148
p
154149
line-height 1.6em
155150
margin 0 0 1em
@@ -195,8 +190,6 @@ code
195190
font-size 0.85em
196191
text-decoration none
197192
margin-right 2px
198-
*:not(a.lineAnchor) + code
199-
margin-left 2px
200193
pre
201194
padding 0.5em !important
202195
border solid 1px alpha(borderColor, 0.5)

browser/lib/markdown.js

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import markdownit from 'markdown-it'
22
import emoji from 'markdown-it-emoji'
33
import math from '@rokt33r/markdown-it-math'
44
import hljs from 'highlight.js'
5+
import _ from 'lodash'
56

67
const katex = window.katex
78

@@ -59,21 +60,77 @@ md.use(math, {
5960
return output
6061
}
6162
})
62-
md.use(require('markdown-it-checkbox'))
63+
md.use(require('markdown-it-footnote'))
64+
// Override task item
65+
md.block.ruler.at('paragraph', function (state, startLine/*, endLine*/) {
66+
let content, terminate, i, l, token
67+
let nextLine = startLine + 1
68+
let terminatorRules = state.md.block.ruler.getRules('paragraph')
69+
let endLine = state.lineMax
6370

64-
let originalRenderToken = md.renderer.renderToken
65-
md.renderer.renderToken = function renderToken (tokens, idx, options) {
66-
let token = tokens[idx]
71+
// jump line-by-line until empty one or EOF
72+
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
73+
// this would be a code block normally, but after paragraph
74+
// it's considered a lazy continuation regardless of what's there
75+
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
6776

68-
let result = originalRenderToken.call(md.renderer, tokens, idx, options)
69-
if (token.map != null) {
70-
return result + '<a class=\'lineAnchor\' data-key=\'' + token.map[0] + '\'></a>'
77+
// quirk for blockquotes, this line should already be checked by that rule
78+
if (state.sCount[nextLine] < 0) { continue }
79+
80+
// Some tags can terminate paragraph without empty line.
81+
terminate = false
82+
for (i = 0, l = terminatorRules.length; i < l; i++) {
83+
if (terminatorRules[i](state, nextLine, endLine, true)) {
84+
terminate = true
85+
break
86+
}
87+
}
88+
if (terminate) { break }
89+
}
90+
91+
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
92+
93+
state.line = nextLine
94+
95+
token = state.push('paragraph_open', 'p', 1)
96+
token.map = [ startLine, state.line ]
97+
98+
if (state.parentType === 'list') {
99+
let match = content.match(/\[( |x)\] ?(.+)/i)
100+
if (match) {
101+
content = `<label class='taskListItem' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${match[2]}</label>`
102+
}
71103
}
104+
105+
token = state.push('inline', '', 0)
106+
token.content = content
107+
token.map = [ startLine, state.line ]
108+
token.children = []
109+
110+
token = state.push('paragraph_close', 'p', -1)
111+
112+
return true
113+
})
114+
115+
// Add line number attribute for scrolling
116+
let originalRender = md.renderer.render
117+
md.renderer.render = function render (tokens, options, env) {
118+
tokens.forEach((token) => {
119+
switch (token.type) {
120+
case 'heading_open':
121+
case 'paragraph_open':
122+
case 'blockquote_open':
123+
case 'table_open':
124+
token.attrPush(['data-line', token.map[0]])
125+
}
126+
})
127+
let result = originalRender.call(md.renderer, tokens, options, env)
72128
return result
73129
}
130+
window.md = md
74131

75132
export default function markdown (content) {
76-
if (content == null) content = ''
133+
if (!_.isString(content)) content = ''
77134

78-
return md.render(content.toString())
135+
return md.render(content)
79136
}

browser/main/Detail/MarkdownNoteDetail.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,17 @@ class MarkdownNoteDetail extends React.Component {
8989
}
9090

9191
save () {
92-
let { note, dispatch } = this.props
93-
94-
dispatch({
95-
type: 'UPDATE_NOTE',
96-
note: this.state.note
97-
})
92+
clearTimeout(this.saveQueue)
93+
this.saveQueue = setTimeout(() => {
94+
let { note, dispatch } = this.props
95+
dispatch({
96+
type: 'UPDATE_NOTE',
97+
note: this.state.note
98+
})
9899

99-
dataApi
100-
.updateNote(note.storage, note.folder, note.key, this.state.note)
100+
dataApi
101+
.updateNote(note.storage, note.folder, note.key, this.state.note)
102+
}, 1000)
101103
}
102104

103105
handleFolderChange (e) {

browser/main/Detail/SnippetNoteDetail.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,17 @@ class SnippetNoteDetail extends React.Component {
9999
}
100100

101101
save () {
102-
let { note, dispatch } = this.props
103-
104-
dispatch({
105-
type: 'UPDATE_NOTE',
106-
note: this.state.note
107-
})
102+
clearTimeout(this.saveQueue)
103+
this.saveQueue = setTimeout(() => {
104+
let { note, dispatch } = this.props
105+
dispatch({
106+
type: 'UPDATE_NOTE',
107+
note: this.state.note
108+
})
108109

109-
dataApi
110-
.updateNote(note.storage, note.folder, note.key, this.state.note)
110+
dataApi
111+
.updateNote(note.storage, note.folder, note.key, this.state.note)
112+
}, 1000)
111113
}
112114

113115
handleFolderChange (e) {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"markdown-it": "^6.0.1",
4949
"markdown-it-checkbox": "^1.1.0",
5050
"markdown-it-emoji": "^1.1.1",
51+
"markdown-it-footnote": "^3.0.0",
5152
"md5": "^2.0.0",
5253
"moment": "^2.10.3",
5354
"sander": "^0.5.1",

0 commit comments

Comments
 (0)