mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 02:06:29 +00:00
Merged branch dev into master
This commit is contained in:
@@ -219,6 +219,12 @@ export default class CodeEditor extends React.Component {
|
|||||||
session.on('change', this.changeHandler)
|
session.on('change', this.changeHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setValue (value) {
|
||||||
|
let session = this.editor.getSession()
|
||||||
|
session.setValue(value)
|
||||||
|
this.value = value
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, fontFamily } = this.props
|
let { className, fontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||||
|
|||||||
@@ -73,6 +73,29 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCheckboxClick (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
let idMatch = /checkbox-([0-9]+)/
|
||||||
|
let checkedMatch = /\[x\]/i
|
||||||
|
let uncheckedMatch = /\[ \]/
|
||||||
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
|
let lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
|
let lines = this.refs.code.value
|
||||||
|
.split('\n')
|
||||||
|
|
||||||
|
let targetLine = lines[lineIndex]
|
||||||
|
|
||||||
|
if (targetLine.match(checkedMatch)) {
|
||||||
|
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
||||||
|
}
|
||||||
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
|
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
||||||
|
}
|
||||||
|
this.refs.code.setValue(lines.join('\n'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
if (this.state.status === 'PREVIEW') {
|
if (this.state.status === 'PREVIEW') {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -135,6 +158,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
value={value}
|
value={value}
|
||||||
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
||||||
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
||||||
|
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,11 +5,6 @@ import hljsTheme from 'browser/lib/hljsThemes'
|
|||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||||
const { shell } = require('electron')
|
const { shell } = require('electron')
|
||||||
const goExternal = function (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
shell.openExternal(e.target.href)
|
|
||||||
}
|
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -27,6 +22,27 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
||||||
this.mouseDownHandler = (e) => this.handleMouseDown(e)
|
this.mouseDownHandler = (e) => this.handleMouseDown(e)
|
||||||
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
||||||
|
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
|
||||||
|
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePreviewAnchorClick (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
let href = e.target.getAttribute('href')
|
||||||
|
if (_.isString(href) && href.match(/^#/)) {
|
||||||
|
let targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
|
||||||
|
if (targetElement != null) {
|
||||||
|
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shell.openExternal(e.target.href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCheckboxClick (e) {
|
||||||
|
this.props.onCheckboxClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu (e) {
|
||||||
@@ -34,10 +50,20 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown (e) {
|
||||||
|
if (e.target != null) {
|
||||||
|
switch (e.target.tagName) {
|
||||||
|
case 'A':
|
||||||
|
case 'INPUT':
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
|
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp (e) {
|
||||||
|
if (e.target != null && e.target.tagName === 'A') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
|
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +94,10 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
rewriteIframe () {
|
rewriteIframe () {
|
||||||
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
el.removeEventListener('click', goExternal)
|
el.removeEventListener('click', this.anchorClickHandler)
|
||||||
|
})
|
||||||
|
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
|
el.removeEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
let { value, fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme } = this.props
|
let { value, fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme } = this.props
|
||||||
@@ -111,7 +140,10 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.refs.root.contentWindow.document.body.innerHTML = markdown(value)
|
this.refs.root.contentWindow.document.body.innerHTML = markdown(value)
|
||||||
|
|
||||||
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
el.addEventListener('mousedown', goExternal)
|
el.addEventListener('click', this.anchorClickHandler)
|
||||||
|
})
|
||||||
|
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
|
el.addEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,14 +156,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollTo (targetRow) {
|
scrollTo (targetRow) {
|
||||||
let lineAnchors = this.getWindow().document.querySelectorAll('a.lineAnchor')
|
let blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
||||||
|
|
||||||
for (let index = 0; index < lineAnchors.length; index++) {
|
for (let index = 0; index < blocks.length; index++) {
|
||||||
let lineAnchor = lineAnchors[index]
|
let block = blocks[index]
|
||||||
let row = parseInt(lineAnchor.getAttribute('data-key'))
|
let row = parseInt(block.getAttribute('data-line'))
|
||||||
if (row > targetRow) {
|
if (row > targetRow) {
|
||||||
let targetAnchor = lineAnchors[index - 1]
|
let targetAnchor = blocks[index - 1]
|
||||||
this.getWindow().scrollTo(0, targetAnchor.offsetTop)
|
targetAnchor != null && this.getWindow().scrollTo(0, targetAnchor.offsetTop)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +179,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
style={style}
|
style={style}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
ref='root'
|
ref='root'
|
||||||
|
onClick={(e) => this.handleClick(e)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ body
|
|||||||
padding 5px
|
padding 5px
|
||||||
margin -5px
|
margin -5px
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
|
li
|
||||||
|
label.taskListItem
|
||||||
|
margin-left -2em
|
||||||
|
background-color white
|
||||||
div.math-rendered
|
div.math-rendered
|
||||||
text-align center
|
text-align center
|
||||||
.math-failed
|
.math-failed
|
||||||
@@ -102,12 +106,6 @@ a
|
|||||||
background-color alpha(#FFC95C, 0.3)
|
background-color alpha(#FFC95C, 0.3)
|
||||||
&:visited
|
&:visited
|
||||||
color brandColor
|
color brandColor
|
||||||
&.lineAnchor
|
|
||||||
padding 0
|
|
||||||
margin 0
|
|
||||||
display block
|
|
||||||
font-size 0
|
|
||||||
height 0
|
|
||||||
hr
|
hr
|
||||||
border-top none
|
border-top none
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
@@ -147,9 +145,6 @@ h6
|
|||||||
line-height 1.4em
|
line-height 1.4em
|
||||||
margin 1em 0 1em
|
margin 1em 0 1em
|
||||||
color #777
|
color #777
|
||||||
|
|
||||||
*:not(a.lineAnchor) + p, *:not(a.lineAnchor) + blockquote, *:not(a.lineAnchor) + ul, *:not(a.lineAnchor) + ol, *:not(a.lineAnchor) + pre
|
|
||||||
margin-top 1em
|
|
||||||
p
|
p
|
||||||
line-height 1.6em
|
line-height 1.6em
|
||||||
margin 0 0 1em
|
margin 0 0 1em
|
||||||
@@ -195,8 +190,6 @@ code
|
|||||||
font-size 0.85em
|
font-size 0.85em
|
||||||
text-decoration none
|
text-decoration none
|
||||||
margin-right 2px
|
margin-right 2px
|
||||||
*:not(a.lineAnchor) + code
|
|
||||||
margin-left 2px
|
|
||||||
pre
|
pre
|
||||||
padding 0.5em !important
|
padding 0.5em !important
|
||||||
border solid 1px alpha(borderColor, 0.5)
|
border solid 1px alpha(borderColor, 0.5)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import markdownit from 'markdown-it'
|
|||||||
import emoji from 'markdown-it-emoji'
|
import emoji from 'markdown-it-emoji'
|
||||||
import math from '@rokt33r/markdown-it-math'
|
import math from '@rokt33r/markdown-it-math'
|
||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const katex = window.katex
|
const katex = window.katex
|
||||||
|
|
||||||
@@ -59,21 +60,77 @@ md.use(math, {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
md.use(require('markdown-it-checkbox'))
|
md.use(require('markdown-it-footnote'))
|
||||||
|
// Override task item
|
||||||
|
md.block.ruler.at('paragraph', function (state, startLine/*, endLine*/) {
|
||||||
|
let content, terminate, i, l, token
|
||||||
|
let nextLine = startLine + 1
|
||||||
|
let terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||||
|
let endLine = state.lineMax
|
||||||
|
|
||||||
let originalRenderToken = md.renderer.renderToken
|
// jump line-by-line until empty one or EOF
|
||||||
md.renderer.renderToken = function renderToken (tokens, idx, options) {
|
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||||
let token = tokens[idx]
|
// this would be a code block normally, but after paragraph
|
||||||
|
// it's considered a lazy continuation regardless of what's there
|
||||||
|
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
|
||||||
|
|
||||||
let result = originalRenderToken.call(md.renderer, tokens, idx, options)
|
// quirk for blockquotes, this line should already be checked by that rule
|
||||||
if (token.map != null) {
|
if (state.sCount[nextLine] < 0) { continue }
|
||||||
return result + '<a class=\'lineAnchor\' data-key=\'' + token.map[0] + '\'></a>'
|
|
||||||
|
// Some tags can terminate paragraph without empty line.
|
||||||
|
terminate = false
|
||||||
|
for (i = 0, l = terminatorRules.length; i < l; i++) {
|
||||||
|
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||||
|
terminate = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (terminate) { break }
|
||||||
|
}
|
||||||
|
|
||||||
|
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
|
||||||
|
|
||||||
|
state.line = nextLine
|
||||||
|
|
||||||
|
token = state.push('paragraph_open', 'p', 1)
|
||||||
|
token.map = [ startLine, state.line ]
|
||||||
|
|
||||||
|
if (state.parentType === 'list') {
|
||||||
|
let match = content.match(/\[( |x)\] ?(.+)/i)
|
||||||
|
if (match) {
|
||||||
|
content = `<label class='taskListItem' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${match[2]}</label>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token = state.push('inline', '', 0)
|
||||||
|
token.content = content
|
||||||
|
token.map = [ startLine, state.line ]
|
||||||
|
token.children = []
|
||||||
|
|
||||||
|
token = state.push('paragraph_close', 'p', -1)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add line number attribute for scrolling
|
||||||
|
let originalRender = md.renderer.render
|
||||||
|
md.renderer.render = function render (tokens, options, env) {
|
||||||
|
tokens.forEach((token) => {
|
||||||
|
switch (token.type) {
|
||||||
|
case 'heading_open':
|
||||||
|
case 'paragraph_open':
|
||||||
|
case 'blockquote_open':
|
||||||
|
case 'table_open':
|
||||||
|
token.attrPush(['data-line', token.map[0]])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let result = originalRender.call(md.renderer, tokens, options, env)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
window.md = md
|
||||||
|
|
||||||
export default function markdown (content) {
|
export default function markdown (content) {
|
||||||
if (content == null) content = ''
|
if (!_.isString(content)) content = ''
|
||||||
|
|
||||||
return md.render(content.toString())
|
return md.render(content)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,8 +89,9 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save () {
|
save () {
|
||||||
|
clearTimeout(this.saveQueue)
|
||||||
|
this.saveQueue = setTimeout(() => {
|
||||||
let { note, dispatch } = this.props
|
let { note, dispatch } = this.props
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: this.state.note
|
note: this.state.note
|
||||||
@@ -98,6 +99,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.updateNote(note.storage, note.folder, note.key, this.state.note)
|
.updateNote(note.storage, note.folder, note.key, this.state.note)
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange (e) {
|
||||||
|
|||||||
@@ -99,8 +99,9 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save () {
|
save () {
|
||||||
|
clearTimeout(this.saveQueue)
|
||||||
|
this.saveQueue = setTimeout(() => {
|
||||||
let { note, dispatch } = this.props
|
let { note, dispatch } = this.props
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: this.state.note
|
note: this.state.note
|
||||||
@@ -108,6 +109,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.updateNote(note.storage, note.folder, note.key, this.state.note)
|
.updateNote(note.storage, note.folder, note.key, this.state.note)
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange (e) {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"markdown-it": "^6.0.1",
|
"markdown-it": "^6.0.1",
|
||||||
"markdown-it-checkbox": "^1.1.0",
|
"markdown-it-checkbox": "^1.1.0",
|
||||||
"markdown-it-emoji": "^1.1.1",
|
"markdown-it-emoji": "^1.1.1",
|
||||||
|
"markdown-it-footnote": "^3.0.0",
|
||||||
"md5": "^2.0.0",
|
"md5": "^2.0.0",
|
||||||
"moment": "^2.10.3",
|
"moment": "^2.10.3",
|
||||||
"sander": "^0.5.1",
|
"sander": "^0.5.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user