mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 01:36:22 +00:00
Fixing flickering in both cases
This commit is contained in:
@@ -14,16 +14,27 @@ const { ipcRenderer } = require('electron')
|
|||||||
|
|
||||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const defaultEditorFontFamily = [
|
||||||
|
'Monaco',
|
||||||
|
'Menlo',
|
||||||
|
'Ubuntu Mono',
|
||||||
|
'Consolas',
|
||||||
|
'source-code-pro',
|
||||||
|
'monospace'
|
||||||
|
]
|
||||||
const buildCMRulers = (rulers, enableRulers) =>
|
const buildCMRulers = (rulers, enableRulers) =>
|
||||||
enableRulers ? rulers.map(ruler => ({column: ruler})) : []
|
(enableRulers ? rulers.map(ruler => ({ column: ruler })) : [])
|
||||||
|
|
||||||
export default class CodeEditor extends React.Component {
|
export default class CodeEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
this.state = { isReady: false }
|
||||||
this.changeHandler = (e) => this.handleChange(e)
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
|
||||||
|
leading: false,
|
||||||
|
trailing: true
|
||||||
|
})
|
||||||
|
this.changeHandler = e => this.handleChange(e)
|
||||||
this.focusHandler = () => {
|
this.focusHandler = () => {
|
||||||
ipcRenderer.send('editor:focused', true)
|
ipcRenderer.send('editor:focused', true)
|
||||||
}
|
}
|
||||||
@@ -39,11 +50,15 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
this.props.onBlur != null && this.props.onBlur(e)
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
|
|
||||||
const {storageKey, noteKey} = this.props
|
const { storageKey, noteKey } = this.props
|
||||||
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
|
attachmentManagement.deleteAttachmentsNotPresentInNote(
|
||||||
|
this.editor.getValue(),
|
||||||
|
storageKey,
|
||||||
|
noteKey
|
||||||
|
)
|
||||||
}
|
}
|
||||||
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||||
this.loadStyleHandler = (e) => {
|
this.loadStyleHandler = e => {
|
||||||
this.editor.refresh()
|
this.editor.refresh()
|
||||||
}
|
}
|
||||||
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
||||||
@@ -62,7 +77,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
cm.addOverlay(component.searchState)
|
cm.addOverlay(component.searchState)
|
||||||
|
|
||||||
function makeOverlay (query, style) {
|
function makeOverlay (query, style) {
|
||||||
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi')
|
query = new RegExp(
|
||||||
|
query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'),
|
||||||
|
'gi'
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
token: function (stream) {
|
token: function (stream) {
|
||||||
query.lastIndex = stream.pos
|
query.lastIndex = stream.pos
|
||||||
@@ -94,7 +112,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
||||||
fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
|
fs.writeFileSync(
|
||||||
|
consts.SNIPPET_FILE,
|
||||||
|
JSON.stringify(defaultSnippet, null, 4),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
@@ -131,9 +153,14 @@ export default class CodeEditor extends React.Component {
|
|||||||
cm.execCommand('insertSoftTab')
|
cm.execCommand('insertSoftTab')
|
||||||
}
|
}
|
||||||
cm.execCommand('goLineEnd')
|
cm.execCommand('goLineEnd')
|
||||||
} else if (!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
|
} else if (
|
||||||
|
!charBeforeCursor.match(/\t|\s|\r|\n/) &&
|
||||||
|
cursor.ch > 1
|
||||||
|
) {
|
||||||
// text expansion on tab key if the char before is alphabet
|
// text expansion on tab key if the char before is alphabet
|
||||||
const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
|
const snippets = JSON.parse(
|
||||||
|
fs.readFileSync(consts.SNIPPET_FILE, 'utf8')
|
||||||
|
)
|
||||||
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
cm.execCommand('insertTab')
|
cm.execCommand('insertTab')
|
||||||
@@ -154,7 +181,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
},
|
},
|
||||||
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
||||||
'Ctrl-C': (cm) => {
|
'Ctrl-C': cm => {
|
||||||
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
||||||
document.execCommand('copy')
|
document.execCommand('copy')
|
||||||
}
|
}
|
||||||
@@ -182,10 +209,15 @@ export default class CodeEditor extends React.Component {
|
|||||||
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
||||||
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
||||||
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||||
|
this.setState({ isReady: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
expandSnippet (line, cursor, cm, snippets) {
|
expandSnippet (line, cursor, cm, snippets) {
|
||||||
const wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch)
|
const wordBeforeCursor = this.getWordBeforeCursor(
|
||||||
|
line,
|
||||||
|
cursor.line,
|
||||||
|
cursor.ch
|
||||||
|
)
|
||||||
const templateCursorString = ':{}'
|
const templateCursorString = ':{}'
|
||||||
for (let i = 0; i < snippets.length; i++) {
|
for (let i = 0; i < snippets.length; i++) {
|
||||||
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
||||||
@@ -203,7 +235,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
wordBeforeCursor.range.from,
|
wordBeforeCursor.range.from,
|
||||||
wordBeforeCursor.range.to
|
wordBeforeCursor.range.to
|
||||||
)
|
)
|
||||||
cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition })
|
cm.setCursor({
|
||||||
|
line: cursor.line + cursorLineNumber,
|
||||||
|
ch: cursorLinePosition
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -245,8 +280,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
return {
|
return {
|
||||||
text: wordBeforeCursor,
|
text: wordBeforeCursor,
|
||||||
range: {
|
range: {
|
||||||
from: {line: lineNumber, ch: originCursorPosition},
|
from: { line: lineNumber, ch: originCursorPosition },
|
||||||
to: {line: lineNumber, ch: cursorPosition}
|
to: { line: lineNumber, ch: cursorPosition }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,7 +303,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate (prevProps, prevState) {
|
componentDidUpdate (prevProps, prevState) {
|
||||||
let needRefresh = false
|
let needRefresh = false
|
||||||
const {rulers, enableRulers} = this.props
|
const { rulers, enableRulers } = this.props
|
||||||
if (prevProps.mode !== this.props.mode) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
this.setMode(this.props.mode)
|
this.setMode(this.props.mode)
|
||||||
}
|
}
|
||||||
@@ -286,7 +321,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
needRefresh = true
|
needRefresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.enableRulers !== enableRulers || prevProps.rulers !== rulers) {
|
if (
|
||||||
|
prevProps.enableRulers !== enableRulers ||
|
||||||
|
prevProps.rulers !== rulers
|
||||||
|
) {
|
||||||
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
|
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,11 +364,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
moveCursorTo (row, col) {
|
moveCursorTo (row, col) {}
|
||||||
}
|
|
||||||
|
|
||||||
scrollToLine (num) {
|
scrollToLine (num) {}
|
||||||
}
|
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
this.editor.focus()
|
this.editor.focus()
|
||||||
@@ -358,8 +394,13 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
handleDropImage (dropEvent) {
|
handleDropImage (dropEvent) {
|
||||||
dropEvent.preventDefault()
|
dropEvent.preventDefault()
|
||||||
const {storageKey, noteKey} = this.props
|
const { storageKey, noteKey } = this.props
|
||||||
attachmentManagement.handleAttachmentDrop(this, storageKey, noteKey, dropEvent)
|
attachmentManagement.handleAttachmentDrop(
|
||||||
|
this,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
dropEvent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
insertAttachmentMd (imageMd) {
|
insertAttachmentMd (imageMd) {
|
||||||
@@ -368,34 +409,44 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
handlePaste (editor, e) {
|
handlePaste (editor, e) {
|
||||||
const clipboardData = e.clipboardData
|
const clipboardData = e.clipboardData
|
||||||
const {storageKey, noteKey} = this.props
|
const { storageKey, noteKey } = this.props
|
||||||
const dataTransferItem = clipboardData.items[0]
|
const dataTransferItem = clipboardData.items[0]
|
||||||
const pastedTxt = clipboardData.getData('text')
|
const pastedTxt = clipboardData.getData('text')
|
||||||
const isURL = (str) => {
|
const isURL = str => {
|
||||||
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
||||||
return matcher.test(str)
|
return matcher.test(str)
|
||||||
}
|
}
|
||||||
const isInLinkTag = (editor) => {
|
const isInLinkTag = editor => {
|
||||||
const startCursor = editor.getCursor('start')
|
const startCursor = editor.getCursor('start')
|
||||||
const prevChar = editor.getRange(
|
const prevChar = editor.getRange(
|
||||||
{line: startCursor.line, ch: startCursor.ch - 2},
|
{ line: startCursor.line, ch: startCursor.ch - 2 },
|
||||||
{line: startCursor.line, ch: startCursor.ch}
|
{ line: startCursor.line, ch: startCursor.ch }
|
||||||
)
|
)
|
||||||
const endCursor = editor.getCursor('end')
|
const endCursor = editor.getCursor('end')
|
||||||
const nextChar = editor.getRange(
|
const nextChar = editor.getRange(
|
||||||
{line: endCursor.line, ch: endCursor.ch},
|
{ line: endCursor.line, ch: endCursor.ch },
|
||||||
{line: endCursor.line, ch: endCursor.ch + 1}
|
{ line: endCursor.line, ch: endCursor.ch + 1 }
|
||||||
)
|
)
|
||||||
return prevChar === '](' && nextChar === ')'
|
return prevChar === '](' && nextChar === ')'
|
||||||
}
|
}
|
||||||
if (dataTransferItem.type.match('image')) {
|
if (dataTransferItem.type.match('image')) {
|
||||||
attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem)
|
attachmentManagement.handlePastImageEvent(
|
||||||
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
this,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
dataTransferItem
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
this.props.fetchUrlTitle &&
|
||||||
|
isURL(pastedTxt) &&
|
||||||
|
!isInLinkTag(editor)
|
||||||
|
) {
|
||||||
this.handlePasteUrl(e, editor, pastedTxt)
|
this.handlePasteUrl(e, editor, pastedTxt)
|
||||||
}
|
}
|
||||||
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
||||||
attachmentManagement.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
attachmentManagement
|
||||||
.then((modifiedText) => {
|
.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
||||||
|
.then(modifiedText => {
|
||||||
this.editor.replaceSelection(modifiedText)
|
this.editor.replaceSelection(modifiedText)
|
||||||
})
|
})
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -413,39 +464,49 @@ export default class CodeEditor extends React.Component {
|
|||||||
const taggedUrl = `<${pastedTxt}>`
|
const taggedUrl = `<${pastedTxt}>`
|
||||||
editor.replaceSelection(taggedUrl)
|
editor.replaceSelection(taggedUrl)
|
||||||
|
|
||||||
const isImageReponse = (response) => {
|
const isImageReponse = response => {
|
||||||
return response.headers.has('content-type') &&
|
return (
|
||||||
|
response.headers.has('content-type') &&
|
||||||
response.headers.get('content-type').match(/^image\/.+$/)
|
response.headers.get('content-type').match(/^image\/.+$/)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const replaceTaggedUrl = (replacement) => {
|
const replaceTaggedUrl = replacement => {
|
||||||
const value = editor.getValue()
|
const value = editor.getValue()
|
||||||
const cursor = editor.getCursor()
|
const cursor = editor.getCursor()
|
||||||
const newValue = value.replace(taggedUrl, replacement)
|
const newValue = value.replace(taggedUrl, replacement)
|
||||||
const newCursor = Object.assign({}, cursor, { ch: cursor.ch + newValue.length - value.length })
|
const newCursor = Object.assign({}, cursor, {
|
||||||
|
ch: cursor.ch + newValue.length - value.length
|
||||||
|
})
|
||||||
editor.setValue(newValue)
|
editor.setValue(newValue)
|
||||||
editor.setCursor(newCursor)
|
editor.setCursor(newCursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(pastedTxt, {
|
fetch(pastedTxt, {
|
||||||
method: 'get'
|
method: 'get'
|
||||||
}).then((response) => {
|
|
||||||
if (isImageReponse(response)) {
|
|
||||||
return this.mapImageResponse(response, pastedTxt)
|
|
||||||
} else {
|
|
||||||
return this.mapNormalResponse(response, pastedTxt)
|
|
||||||
}
|
|
||||||
}).then((replacement) => {
|
|
||||||
replaceTaggedUrl(replacement)
|
|
||||||
}).catch((e) => {
|
|
||||||
replaceTaggedUrl(pastedTxt)
|
|
||||||
})
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (isImageReponse(response)) {
|
||||||
|
return this.mapImageResponse(response, pastedTxt)
|
||||||
|
} else {
|
||||||
|
return this.mapNormalResponse(response, pastedTxt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(replacement => {
|
||||||
|
replaceTaggedUrl(replacement)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
replaceTaggedUrl(pastedTxt)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mapNormalResponse (response, pastedTxt) {
|
mapNormalResponse (response, pastedTxt) {
|
||||||
return this.decodeResponse(response).then((body) => {
|
return this.decodeResponse(response).then(body => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const parsedBody = (new window.DOMParser()).parseFromString(body, 'text/html')
|
const parsedBody = new window.DOMParser().parseFromString(
|
||||||
|
body,
|
||||||
|
'text/html'
|
||||||
|
)
|
||||||
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
|
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
|
||||||
resolve(linkWithTitle)
|
resolve(linkWithTitle)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -473,10 +534,13 @@ export default class CodeEditor extends React.Component {
|
|||||||
const _charset = headers.has('content-type')
|
const _charset = headers.has('content-type')
|
||||||
? this.extractContentTypeCharset(headers.get('content-type'))
|
? this.extractContentTypeCharset(headers.get('content-type'))
|
||||||
: undefined
|
: undefined
|
||||||
return response.arrayBuffer().then((buff) => {
|
return response.arrayBuffer().then(buff => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const charset = _charset !== undefined && iconv.encodingExists(_charset) ? _charset : 'utf-8'
|
const charset = _charset !== undefined &&
|
||||||
|
iconv.encodingExists(_charset)
|
||||||
|
? _charset
|
||||||
|
: 'utf-8'
|
||||||
resolve(iconv.decode(new Buffer(buff), charset).toString())
|
resolve(iconv.decode(new Buffer(buff), charset).toString())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e)
|
reject(e)
|
||||||
@@ -486,15 +550,18 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extractContentTypeCharset (contentType) {
|
extractContentTypeCharset (contentType) {
|
||||||
return contentType.split(';').filter((str) => {
|
return contentType
|
||||||
return str.trim().toLowerCase().startsWith('charset')
|
.split(';')
|
||||||
}).map((str) => {
|
.filter(str => {
|
||||||
return str.replace(/['"]/g, '').split('=')[1]
|
return str.trim().toLowerCase().startsWith('charset')
|
||||||
})[0]
|
})
|
||||||
|
.map(str => {
|
||||||
|
return str.replace(/['"]/g, '').split('=')[1]
|
||||||
|
})[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {className, fontSize} = this.props
|
const { className, fontSize } = this.props
|
||||||
let fontFamily = this.props.fontFamily
|
let fontFamily = this.props.fontFamily
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||||
? [fontFamily].concat(defaultEditorFontFamily)
|
? [fontFamily].concat(defaultEditorFontFamily)
|
||||||
@@ -502,18 +569,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
const width = this.props.width
|
const width = this.props.width
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className == null
|
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
|
||||||
? 'CodeEditor'
|
|
||||||
: `CodeEditor ${className}`
|
|
||||||
}
|
|
||||||
ref='root'
|
ref='root'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
style={{
|
style={{
|
||||||
fontFamily: fontFamily.join(', '),
|
fontFamily: fontFamily.join(', '),
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
width: width
|
width: width,
|
||||||
|
opacity: this.state.isReady ? '1' : '0'
|
||||||
}}
|
}}
|
||||||
onDrop={(e) => this.handleDropImage(e)}
|
onDrop={e => this.handleDropImage(e)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,15 +24,24 @@ const path = require('path')
|
|||||||
const dialog = remote.dialog
|
const dialog = remote.dialog
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||||
const appPath = 'file://' + (process.env.NODE_ENV === 'production'
|
const appPath =
|
||||||
? app.getAppPath()
|
'file://' +
|
||||||
: path.resolve())
|
(process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve())
|
||||||
const CSS_FILES = [
|
const CSS_FILES = [
|
||||||
`${appPath}/node_modules/katex/dist/katex.min.css`,
|
`${appPath}/node_modules/katex/dist/katex.min.css`,
|
||||||
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
||||||
]
|
]
|
||||||
|
|
||||||
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) {
|
function buildStyle (
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
) {
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
@@ -137,17 +146,29 @@ if (!OSX) {
|
|||||||
defaultFontFamily.unshift('Microsoft YaHei')
|
defaultFontFamily.unshift('Microsoft YaHei')
|
||||||
defaultFontFamily.unshift('meiryo')
|
defaultFontFamily.unshift('meiryo')
|
||||||
}
|
}
|
||||||
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const defaultCodeBlockFontFamily = [
|
||||||
|
'Monaco',
|
||||||
|
'Menlo',
|
||||||
|
'Ubuntu Mono',
|
||||||
|
'Consolas',
|
||||||
|
'source-code-pro',
|
||||||
|
'monospace'
|
||||||
|
]
|
||||||
export default class MarkdownPreview extends React.Component {
|
export default class MarkdownPreview extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
this.state = {
|
||||||
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
isReady: false
|
||||||
this.mouseDownHandler = (e) => this.handleMouseDown(e)
|
}
|
||||||
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
this.contextMenuHandler = e => this.handleContextMenu(e)
|
||||||
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
|
this.mouseDownHandler = e => this.handleMouseDown(e)
|
||||||
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
this.mouseUpHandler = e => this.handleMouseUp(e)
|
||||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
this.DoubleClickHandler = e => this.handleDoubleClick(e)
|
||||||
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
|
||||||
|
leading: false,
|
||||||
|
trailing: true
|
||||||
|
})
|
||||||
|
this.checkboxClickHandler = e => this.handleCheckboxClick(e)
|
||||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||||
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
||||||
@@ -214,31 +235,56 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
handleSaveAsHtml () {
|
handleSaveAsHtml () {
|
||||||
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
||||||
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
|
const {
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
} = this.getStyleParams()
|
||||||
|
|
||||||
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
|
const inlineStyles = buildStyle(
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
)
|
||||||
let body = this.markdown.render(escapeHtmlCharacters(noteContent))
|
let body = this.markdown.render(escapeHtmlCharacters(noteContent))
|
||||||
|
|
||||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
||||||
|
noteContent,
|
||||||
|
this.props.storagePath
|
||||||
|
)
|
||||||
|
|
||||||
files.forEach((file) => {
|
files.forEach(file => {
|
||||||
file = file.replace('file://', '')
|
file = file.replace('file://', '')
|
||||||
exportTasks.push({
|
exportTasks.push({
|
||||||
src: file,
|
src: file,
|
||||||
dst: 'css'
|
dst: 'css'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
attachmentsAbsolutePaths.forEach((attachment) => {
|
attachmentsAbsolutePaths.forEach(attachment => {
|
||||||
exportTasks.push({
|
exportTasks.push({
|
||||||
src: attachment,
|
src: attachment,
|
||||||
dst: attachmentManagement.DESTINATION_FOLDER
|
dst: attachmentManagement.DESTINATION_FOLDER
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey)
|
body = attachmentManagement.removeStorageAndNoteReferences(
|
||||||
|
body,
|
||||||
|
this.props.noteKey
|
||||||
|
)
|
||||||
|
|
||||||
let styles = ''
|
let styles = ''
|
||||||
files.forEach((file) => {
|
files.forEach(file => {
|
||||||
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -260,42 +306,51 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
exportAsDocument (fileType, contentFormatter) {
|
exportAsDocument (fileType, contentFormatter) {
|
||||||
const options = {
|
const options = {
|
||||||
filters: [
|
filters: [{ name: 'Documents', extensions: [fileType] }],
|
||||||
{name: 'Documents', extensions: [fileType]}
|
|
||||||
],
|
|
||||||
properties: ['openFile', 'createDirectory']
|
properties: ['openFile', 'createDirectory']
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.showSaveDialog(remote.getCurrentWindow(), options,
|
dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => {
|
||||||
(filename) => {
|
if (filename) {
|
||||||
if (filename) {
|
const content = this.props.value
|
||||||
const content = this.props.value
|
const storage = this.props.storagePath
|
||||||
const storage = this.props.storagePath
|
|
||||||
|
|
||||||
exportNote(storage, content, filename, contentFormatter)
|
exportNote(storage, content, filename, contentFormatter)
|
||||||
.then((res) => {
|
.then(res => {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {type: 'info', message: `Exported to ${filename}`})
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
}).catch((err) => {
|
type: 'info',
|
||||||
dialog.showErrorBox('Export error', err ? err.message || err : 'Unexpected error during export')
|
message: `Exported to ${filename}`
|
||||||
throw err
|
})
|
||||||
})
|
})
|
||||||
}
|
.catch(err => {
|
||||||
})
|
dialog.showErrorBox(
|
||||||
|
'Export error',
|
||||||
|
err ? err.message || err : 'Unexpected error during export'
|
||||||
|
)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fixDecodedURI (node) {
|
fixDecodedURI (node) {
|
||||||
if (node && node.children.length === 1 && typeof node.children[0] === 'string') {
|
if (
|
||||||
|
node &&
|
||||||
|
node.children.length === 1 &&
|
||||||
|
typeof node.children[0] === 'string'
|
||||||
|
) {
|
||||||
const { innerText, href } = node
|
const { innerText, href } = node
|
||||||
|
|
||||||
node.innerText = mdurl.decode(href) === innerText
|
node.innerText = mdurl.decode(href) === innerText ? href : innerText
|
||||||
? href
|
|
||||||
: innerText
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||||
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
this.refs.root.contentWindow.document.body.addEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
this.contextMenuHandler
|
||||||
|
)
|
||||||
|
|
||||||
let styles = `
|
let styles = `
|
||||||
<style id='style'></style>
|
<style id='style'></style>
|
||||||
@@ -303,7 +358,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
`
|
`
|
||||||
|
|
||||||
CSS_FILES.forEach((file) => {
|
CSS_FILES.forEach(file => {
|
||||||
styles += `<link rel="stylesheet" href="${file}">`
|
styles += `<link rel="stylesheet" href="${file}">`
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -311,26 +366,66 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
|
'mousedown',
|
||||||
this.refs.root.contentWindow.document.addEventListener('dblclick', this.DoubleClickHandler)
|
this.mouseDownHandler
|
||||||
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
|
)
|
||||||
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
this.refs.root.contentWindow.document.addEventListener('scroll', this.scrollHandler)
|
'mouseup',
|
||||||
|
this.mouseUpHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
|
'dblclick',
|
||||||
|
this.DoubleClickHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
|
'drop',
|
||||||
|
this.preventImageDroppedHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
|
'dragover',
|
||||||
|
this.preventImageDroppedHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener(
|
||||||
|
'scroll',
|
||||||
|
this.scrollHandler
|
||||||
|
)
|
||||||
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||||
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
||||||
eventEmitter.on('print', this.printHandler)
|
eventEmitter.on('print', this.printHandler)
|
||||||
|
setTimeout(() => this.setState({ isReady: true }))
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
|
this.refs.root.contentWindow.document.body.removeEventListener(
|
||||||
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
|
'contextmenu',
|
||||||
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
|
this.contextMenuHandler
|
||||||
this.refs.root.contentWindow.document.removeEventListener('dblclick', this.DoubleClickHandler)
|
)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
|
'mousedown',
|
||||||
this.refs.root.contentWindow.document.removeEventListener('scroll', this.scrollHandler)
|
this.mouseDownHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'mouseup',
|
||||||
|
this.mouseUpHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'dblclick',
|
||||||
|
this.DoubleClickHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'drop',
|
||||||
|
this.preventImageDroppedHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'dragover',
|
||||||
|
this.preventImageDroppedHandler
|
||||||
|
)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener(
|
||||||
|
'scroll',
|
||||||
|
this.scrollHandler
|
||||||
|
)
|
||||||
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||||
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
||||||
@@ -339,14 +434,17 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||||
if (prevProps.smartQuotes !== this.props.smartQuotes ||
|
if (
|
||||||
prevProps.sanitize !== this.props.sanitize ||
|
prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||||
prevProps.smartArrows !== this.props.smartArrows ||
|
prevProps.sanitize !== this.props.sanitize ||
|
||||||
prevProps.breaks !== this.props.breaks) {
|
prevProps.smartArrows !== this.props.smartArrows ||
|
||||||
|
prevProps.breaks !== this.props.breaks
|
||||||
|
) {
|
||||||
this.initMarkdown()
|
this.initMarkdown()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
if (prevProps.fontFamily !== this.props.fontFamily ||
|
if (
|
||||||
|
prevProps.fontFamily !== this.props.fontFamily ||
|
||||||
prevProps.fontSize !== this.props.fontSize ||
|
prevProps.fontSize !== this.props.fontSize ||
|
||||||
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
||||||
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
||||||
@@ -355,34 +453,82 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
prevProps.theme !== this.props.theme ||
|
prevProps.theme !== this.props.theme ||
|
||||||
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
||||||
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
||||||
prevProps.customCSS !== this.props.customCSS) {
|
prevProps.customCSS !== this.props.customCSS
|
||||||
|
) {
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleParams () {
|
getStyleParams () {
|
||||||
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = this.props
|
const {
|
||||||
|
fontSize,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
} = this.props
|
||||||
let { fontFamily, codeBlockFontFamily } = this.props
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
? fontFamily
|
||||||
: defaultFontFamily
|
.split(',')
|
||||||
codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
.map(fontName => fontName.trim())
|
||||||
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
.concat(defaultFontFamily)
|
||||||
: defaultCodeBlockFontFamily
|
: defaultFontFamily
|
||||||
|
codeBlockFontFamily = _.isString(codeBlockFontFamily) &&
|
||||||
|
codeBlockFontFamily.trim().length > 0
|
||||||
|
? codeBlockFontFamily
|
||||||
|
.split(',')
|
||||||
|
.map(fontName => fontName.trim())
|
||||||
|
.concat(defaultCodeBlockFontFamily)
|
||||||
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS}
|
return {
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyle () {
|
applyStyle () {
|
||||||
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
|
const {
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
codeBlockTheme,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
} = this.getStyleParams()
|
||||||
|
|
||||||
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
|
this.getWindow().document.getElementById(
|
||||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
|
'codeTheme'
|
||||||
|
).href = this.GetCodeThemeLink(codeBlockTheme)
|
||||||
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle(
|
||||||
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
|
codeBlockFontFamily,
|
||||||
|
lineNumber,
|
||||||
|
scrollPastEnd,
|
||||||
|
theme,
|
||||||
|
allowCustomCSS,
|
||||||
|
customCSS
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
GetCodeThemeLink (theme) {
|
GetCodeThemeLink (theme) {
|
||||||
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
|
theme = consts.THEMES.some(_theme => _theme === theme) &&
|
||||||
|
theme !== 'default'
|
||||||
? theme
|
? theme
|
||||||
: 'elegant'
|
: 'elegant'
|
||||||
return theme.startsWith('solarized')
|
return theme.startsWith('solarized')
|
||||||
@@ -391,71 +537,103 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rewriteIframe () {
|
rewriteIframe () {
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
_.forEach(
|
||||||
el.removeEventListener('click', this.checkboxClickHandler)
|
this.refs.root.contentWindow.document.querySelectorAll(
|
||||||
})
|
'input[type="checkbox"]'
|
||||||
|
),
|
||||||
|
el => {
|
||||||
|
el.removeEventListener('click', this.checkboxClickHandler)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
_.forEach(
|
||||||
el.removeEventListener('click', this.linkClickHandler)
|
this.refs.root.contentWindow.document.querySelectorAll('a'),
|
||||||
})
|
el => {
|
||||||
|
el.removeEventListener('click', this.linkClickHandler)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const { theme, indentSize, showCopyNotification, storagePath, noteKey } = this.props
|
const {
|
||||||
|
theme,
|
||||||
|
indentSize,
|
||||||
|
showCopyNotification,
|
||||||
|
storagePath,
|
||||||
|
noteKey
|
||||||
|
} = this.props
|
||||||
let { value, codeBlockTheme } = this.props
|
let { value, codeBlockTheme } = this.props
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
|
|
||||||
const codeBlocks = value.match(/(```)(.|[\n])*?(```)/g)
|
const codeBlocks = value.match(/(```)(.|[\n])*?(```)/g)
|
||||||
if (codeBlocks !== null) {
|
if (codeBlocks !== null) {
|
||||||
codeBlocks.forEach((codeBlock) => {
|
codeBlocks.forEach(codeBlock => {
|
||||||
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
|
value = value.replace(
|
||||||
|
codeBlock,
|
||||||
|
htmlTextHelper.encodeEntities(codeBlock)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let renderedHTML = this.markdown.render(value)
|
let renderedHTML = this.markdown.render(value)
|
||||||
attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
|
attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey)
|
||||||
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
|
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(
|
||||||
|
renderedHTML,
|
||||||
|
storagePath
|
||||||
|
)
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
_.forEach(
|
||||||
el.addEventListener('click', this.checkboxClickHandler)
|
this.refs.root.contentWindow.document.querySelectorAll(
|
||||||
})
|
'input[type="checkbox"]'
|
||||||
|
),
|
||||||
|
el => {
|
||||||
|
el.addEventListener('click', this.checkboxClickHandler)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
_.forEach(
|
||||||
this.fixDecodedURI(el)
|
this.refs.root.contentWindow.document.querySelectorAll('a'),
|
||||||
el.addEventListener('click', this.linkClickHandler)
|
el => {
|
||||||
})
|
this.fixDecodedURI(el)
|
||||||
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
codeBlockTheme = consts.THEMES.some(_theme => _theme === codeBlockTheme)
|
||||||
? codeBlockTheme
|
? codeBlockTheme
|
||||||
: 'default'
|
: 'default'
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
|
_.forEach(
|
||||||
let syntax = CodeMirror.findModeByName(convertModeName(el.className))
|
this.refs.root.contentWindow.document.querySelectorAll('.code code'),
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
el => {
|
||||||
CodeMirror.requireMode(syntax.mode, () => {
|
let syntax = CodeMirror.findModeByName(convertModeName(el.className))
|
||||||
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
const copyIcon = document.createElement('i')
|
CodeMirror.requireMode(syntax.mode, () => {
|
||||||
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
copyIcon.onclick = (e) => {
|
const copyIcon = document.createElement('i')
|
||||||
copy(content)
|
copyIcon.innerHTML =
|
||||||
if (showCopyNotification) {
|
'<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
||||||
this.notify('Saved to Clipboard!', {
|
copyIcon.onclick = e => {
|
||||||
body: 'Paste it wherever you want!',
|
copy(content)
|
||||||
silent: true
|
if (showCopyNotification) {
|
||||||
})
|
this.notify('Saved to Clipboard!', {
|
||||||
|
body: 'Paste it wherever you want!',
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
el.parentNode.appendChild(copyIcon)
|
||||||
el.parentNode.appendChild(copyIcon)
|
el.innerHTML = ''
|
||||||
el.innerHTML = ''
|
if (codeBlockTheme.indexOf('solarized') === 0) {
|
||||||
if (codeBlockTheme.indexOf('solarized') === 0) {
|
const [refThema, color] = codeBlockTheme.split(' ')
|
||||||
const [refThema, color] = codeBlockTheme.split(' ')
|
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
|
||||||
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
|
} else {
|
||||||
} else {
|
el.parentNode.className += ` cm-s-${codeBlockTheme}`
|
||||||
el.parentNode.className += ` cm-s-${codeBlockTheme}`
|
}
|
||||||
}
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
CodeMirror.runMode(content, syntax.mime, el, {
|
tabSize: indentSize
|
||||||
tabSize: indentSize
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
const opts = {}
|
const opts = {}
|
||||||
// if (this.props.theme === 'dark') {
|
// if (this.props.theme === 'dark') {
|
||||||
// opts['font-color'] = '#DDD'
|
// opts['font-color'] = '#DDD'
|
||||||
@@ -463,37 +641,47 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
// opts['element-color'] = '#DDD'
|
// opts['element-color'] = '#DDD'
|
||||||
// opts['fill'] = '#3A404C'
|
// opts['fill'] = '#3A404C'
|
||||||
// }
|
// }
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => {
|
_.forEach(
|
||||||
Raphael.setWindow(this.getWindow())
|
this.refs.root.contentWindow.document.querySelectorAll('.flowchart'),
|
||||||
try {
|
el => {
|
||||||
const diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
Raphael.setWindow(this.getWindow())
|
||||||
el.innerHTML = ''
|
try {
|
||||||
diagram.drawSVG(el, opts)
|
const diagram = flowchart.parse(
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
el.addEventListener('click', this.linkClickHandler)
|
)
|
||||||
})
|
el.innerHTML = ''
|
||||||
} catch (e) {
|
diagram.drawSVG(el, opts)
|
||||||
console.error(e)
|
_.forEach(el.querySelectorAll('a'), el => {
|
||||||
el.className = 'flowchart-error'
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
el.innerHTML = 'Flowchart parse error: ' + e.message
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
el.className = 'flowchart-error'
|
||||||
|
el.innerHTML = 'Flowchart parse error: ' + e.message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => {
|
_.forEach(
|
||||||
Raphael.setWindow(this.getWindow())
|
this.refs.root.contentWindow.document.querySelectorAll('.sequence'),
|
||||||
try {
|
el => {
|
||||||
const diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
Raphael.setWindow(this.getWindow())
|
||||||
el.innerHTML = ''
|
try {
|
||||||
diagram.drawSVG(el, {theme: 'simple'})
|
const diagram = SequenceDiagram.parse(
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
el.addEventListener('click', this.linkClickHandler)
|
)
|
||||||
})
|
el.innerHTML = ''
|
||||||
} catch (e) {
|
diagram.drawSVG(el, { theme: 'simple' })
|
||||||
console.error(e)
|
_.forEach(el.querySelectorAll('a'), el => {
|
||||||
el.className = 'sequence-error'
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
el.className = 'sequence-error'
|
||||||
|
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
@@ -505,7 +693,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollTo (targetRow) {
|
scrollTo (targetRow) {
|
||||||
const blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
const blocks = this.getWindow().document.querySelectorAll(
|
||||||
|
'body>[data-line]'
|
||||||
|
)
|
||||||
|
|
||||||
for (let index = 0; index < blocks.length; index++) {
|
for (let index = 0; index < blocks.length; index++) {
|
||||||
let block = blocks[index]
|
let block = blocks[index]
|
||||||
@@ -525,7 +715,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
notify (title, options) {
|
notify (title, options) {
|
||||||
if (global.process.platform === 'win32') {
|
if (global.process.platform === 'win32') {
|
||||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
options.icon = path.join(
|
||||||
|
'file://',
|
||||||
|
global.__dirname,
|
||||||
|
'../../resources/app.png'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return new window.Notification(title, options)
|
return new window.Notification(title, options)
|
||||||
}
|
}
|
||||||
@@ -540,7 +734,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
const regexNoteInternalLink = /main.html#(.+)/
|
const regexNoteInternalLink = /main.html#(.+)/
|
||||||
if (regexNoteInternalLink.test(linkHash)) {
|
if (regexNoteInternalLink.test(linkHash)) {
|
||||||
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
|
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
|
||||||
const targetElement = this.refs.root.contentWindow.document.getElementById(targetId)
|
const targetElement = this.refs.root.contentWindow.document.getElementById(
|
||||||
|
targetId
|
||||||
|
)
|
||||||
|
|
||||||
if (targetElement != null) {
|
if (targetElement != null) {
|
||||||
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
||||||
@@ -574,11 +770,15 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
render () {
|
render () {
|
||||||
const { className, style, tabIndex } = this.props
|
const { className, style, tabIndex } = this.props
|
||||||
return (
|
return (
|
||||||
<iframe className={className != null
|
<iframe
|
||||||
? 'MarkdownPreview ' + className
|
className={
|
||||||
: 'MarkdownPreview'
|
className != null ? 'MarkdownPreview ' + className : 'MarkdownPreview'
|
||||||
|
}
|
||||||
|
style={
|
||||||
|
this.state.isReady
|
||||||
|
? Object.assign(style, { opacity: '1' })
|
||||||
|
: Object.assign(style, { opacity: '0' })
|
||||||
}
|
}
|
||||||
style={style}
|
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
ref='root'
|
ref='root'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,14 +26,12 @@ const TagElement = ({ tagName }) => (
|
|||||||
* @param {Array|null} tags
|
* @param {Array|null} tags
|
||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const TagElementList = (tags) => {
|
const TagElementList = tags => {
|
||||||
if (!isArray(tags)) {
|
if (!isArray(tags)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagElements = tags.map(tag => (
|
const tagElements = tags.map(tag => TagElement({ tagName: tag }))
|
||||||
TagElement({tagName: tag})
|
|
||||||
))
|
|
||||||
|
|
||||||
return tagElements
|
return tagElements
|
||||||
}
|
}
|
||||||
@@ -59,10 +57,8 @@ const NoteItem = ({
|
|||||||
folderName,
|
folderName,
|
||||||
viewType
|
viewType
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName={isActive
|
<div
|
||||||
? 'item--active'
|
styleName={isActive ? 'item--active' : 'item'}
|
||||||
: 'item'
|
|
||||||
}
|
|
||||||
key={note.key}
|
key={note.key}
|
||||||
onClick={e => handleNoteClick(e, note.key)}
|
onClick={e => handleNoteClick(e, note.key)}
|
||||||
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||||
@@ -72,42 +68,54 @@ const NoteItem = ({
|
|||||||
<div styleName='item-wrapper'>
|
<div styleName='item-wrapper'>
|
||||||
{note.type === 'SNIPPET_NOTE'
|
{note.type === 'SNIPPET_NOTE'
|
||||||
? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
|
? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
|
||||||
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
|
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />}
|
||||||
}
|
|
||||||
<div styleName='item-title'>
|
<div styleName='item-title'>
|
||||||
{note.title.trim().length > 0
|
{note.title.trim().length > 0
|
||||||
? note.title
|
? note.title
|
||||||
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
|
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
|
{['ALL', 'STORAGE'].includes(viewType) &&
|
||||||
<div styleName='item-middle-time'>{dateDisplay}</div>
|
<div styleName='item-middle'>
|
||||||
<div styleName='item-middle-app-meta'>
|
<div styleName='item-middle-time'>{dateDisplay}</div>
|
||||||
<div title={viewType === 'ALL' ? storageName : viewType === 'STORAGE' ? folderName : null} styleName='item-middle-app-meta-label'>
|
<div styleName='item-middle-app-meta'>
|
||||||
{viewType === 'ALL' && storageName}
|
<div
|
||||||
{viewType === 'STORAGE' && folderName}
|
title={
|
||||||
|
viewType === 'ALL'
|
||||||
|
? storageName
|
||||||
|
: viewType === 'STORAGE' ? folderName : null
|
||||||
|
}
|
||||||
|
styleName='item-middle-app-meta-label'
|
||||||
|
>
|
||||||
|
{viewType === 'ALL' && storageName}
|
||||||
|
{viewType === 'STORAGE' && folderName}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>}
|
||||||
</div>}
|
|
||||||
|
|
||||||
<div styleName='item-bottom'>
|
<div styleName='item-bottom'>
|
||||||
<div styleName='item-bottom-tagList'>
|
<div styleName='item-bottom-tagList'>
|
||||||
{note.tags.length > 0
|
{note.tags.length > 0
|
||||||
? TagElementList(note.tags)
|
? TagElementList(note.tags)
|
||||||
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>{i18n.__('No tags')}</span>
|
: <span
|
||||||
}
|
style={{ fontStyle: 'italic', opacity: 0.5 }}
|
||||||
|
styleName='item-bottom-tagList-empty'
|
||||||
|
>
|
||||||
|
{i18n.__('No tags')}
|
||||||
|
</span>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{note.isStarred
|
{note.isStarred
|
||||||
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
|
? <img
|
||||||
}
|
styleName='item-star'
|
||||||
|
src='../resources/icon/icon-starred.svg'
|
||||||
|
/>
|
||||||
|
: ''}
|
||||||
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
||||||
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
|
? <i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||||
}
|
: ''}
|
||||||
{note.type === 'MARKDOWN_NOTE'
|
{note.type === 'MARKDOWN_NOTE'
|
||||||
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||||
: ''
|
: ''}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -54,9 +54,8 @@ const StorageItem = ({
|
|||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
>
|
>
|
||||||
{!isFolded && (
|
{!isFolded &&
|
||||||
<DraggableIcon className={styles['folderList-item-reorder']} />
|
<DraggableIcon className={styles['folderList-item-reorder']} />}
|
||||||
)}
|
|
||||||
<span
|
<span
|
||||||
styleName={
|
styleName={
|
||||||
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
|
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
|
||||||
@@ -72,12 +71,10 @@ const StorageItem = ({
|
|||||||
: folderName}
|
: folderName}
|
||||||
</span>
|
</span>
|
||||||
{!isFolded &&
|
{!isFolded &&
|
||||||
_.isNumber(noteCount) && (
|
_.isNumber(noteCount) &&
|
||||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
<span styleName='folderList-item-noteCount'>{noteCount}</span>}
|
||||||
)}
|
{isFolded &&
|
||||||
{isFolded && (
|
<span styleName='folderList-item-tooltip'>{folderName}</span>}
|
||||||
<span styleName='folderList-item-tooltip'>{folderName}</span>
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const electron = require('electron')
|
|||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
|
|
||||||
class Main extends React.Component {
|
class Main extends React.Component {
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -60,10 +59,10 @@ class Main extends React.Component {
|
|||||||
name: 'My Storage',
|
name: 'My Storage',
|
||||||
path: path.join(remote.app.getPath('home'), 'Boostnote')
|
path: path.join(remote.app.getPath('home'), 'Boostnote')
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
if (data.storage.folders[0] != null) {
|
if (data.storage.folders[0] != null) {
|
||||||
return data
|
return data
|
||||||
} else {
|
} else {
|
||||||
@@ -72,7 +71,7 @@ class Main extends React.Component {
|
|||||||
color: '#1278BD',
|
color: '#1278BD',
|
||||||
name: 'Default'
|
name: 'Default'
|
||||||
})
|
})
|
||||||
.then((_data) => {
|
.then(_data => {
|
||||||
return {
|
return {
|
||||||
storage: _data.storage,
|
storage: _data.storage,
|
||||||
notes: data.notes
|
notes: data.notes
|
||||||
@@ -80,7 +79,7 @@ class Main extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'ADD_STORAGE',
|
type: 'ADD_STORAGE',
|
||||||
@@ -98,16 +97,16 @@ class Main extends React.Component {
|
|||||||
{
|
{
|
||||||
name: 'example.html',
|
name: 'example.html',
|
||||||
mode: 'html',
|
mode: 'html',
|
||||||
content: '<html>\n<body>\n<h1 id=\'hello\'>Enjoy Boostnote!</h1>\n</body>\n</html>'
|
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'example.js',
|
name: 'example.js',
|
||||||
mode: 'javascript',
|
mode: 'javascript',
|
||||||
content: 'var boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)'
|
content: "var boostnote = document.getElementById('enjoy').innerHTML\n\nconsole.log(boostnote)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then(note => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
@@ -120,7 +119,7 @@ class Main extends React.Component {
|
|||||||
title: 'Welcome to Boostnote!',
|
title: 'Welcome to Boostnote!',
|
||||||
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then(note => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
@@ -131,10 +130,10 @@ class Main extends React.Component {
|
|||||||
.then(defaultMarkdownNote)
|
.then(defaultMarkdownNote)
|
||||||
.then(() => data.storage)
|
.then(() => data.storage)
|
||||||
})
|
})
|
||||||
.then((storage) => {
|
.then(storage => {
|
||||||
hashHistory.push('/storages/' + storage.key)
|
hashHistory.push('/storages/' + storage.key)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -142,12 +141,7 @@ class Main extends React.Component {
|
|||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
const supportedThemes = [
|
const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai']
|
||||||
'dark',
|
|
||||||
'white',
|
|
||||||
'solarized-dark',
|
|
||||||
'monokai'
|
|
||||||
]
|
|
||||||
|
|
||||||
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
||||||
document.body.setAttribute('data-theme', config.ui.theme)
|
document.body.setAttribute('data-theme', config.ui.theme)
|
||||||
@@ -162,19 +156,18 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
applyShortcuts()
|
applyShortcuts()
|
||||||
// Reload all data
|
// Reload all data
|
||||||
dataApi.init()
|
dataApi.init().then(data => {
|
||||||
.then((data) => {
|
dispatch({
|
||||||
dispatch({
|
type: 'INIT_ALL',
|
||||||
type: 'INIT_ALL',
|
storages: data.storages,
|
||||||
storages: data.storages,
|
notes: data.notes
|
||||||
notes: data.notes
|
|
||||||
})
|
|
||||||
|
|
||||||
if (data.storages.length < 1) {
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (data.storages.length < 1) {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,34 +192,40 @@ class Main extends React.Component {
|
|||||||
handleMouseUp (e) {
|
handleMouseUp (e) {
|
||||||
// Change width of NoteList component.
|
// Change width of NoteList component.
|
||||||
if (this.state.isRightSliderFocused) {
|
if (this.state.isRightSliderFocused) {
|
||||||
this.setState({
|
this.setState(
|
||||||
isRightSliderFocused: false
|
{
|
||||||
}, () => {
|
isRightSliderFocused: false
|
||||||
const { dispatch } = this.props
|
},
|
||||||
const newListWidth = this.state.listWidth
|
() => {
|
||||||
// TODO: ConfigManager should dispatch itself.
|
const { dispatch } = this.props
|
||||||
ConfigManager.set({listWidth: newListWidth})
|
const newListWidth = this.state.listWidth
|
||||||
dispatch({
|
// TODO: ConfigManager should dispatch itself.
|
||||||
type: 'SET_LIST_WIDTH',
|
ConfigManager.set({ listWidth: newListWidth })
|
||||||
listWidth: newListWidth
|
dispatch({
|
||||||
})
|
type: 'SET_LIST_WIDTH',
|
||||||
})
|
listWidth: newListWidth
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change width of SideNav component.
|
// Change width of SideNav component.
|
||||||
if (this.state.isLeftSliderFocused) {
|
if (this.state.isLeftSliderFocused) {
|
||||||
this.setState({
|
this.setState(
|
||||||
isLeftSliderFocused: false
|
{
|
||||||
}, () => {
|
isLeftSliderFocused: false
|
||||||
const { dispatch } = this.props
|
},
|
||||||
const navWidth = this.state.navWidth
|
() => {
|
||||||
// TODO: ConfigManager should dispatch itself.
|
const { dispatch } = this.props
|
||||||
ConfigManager.set({ navWidth })
|
const navWidth = this.state.navWidth
|
||||||
dispatch({
|
// TODO: ConfigManager should dispatch itself.
|
||||||
type: 'SET_NAV_WIDTH',
|
ConfigManager.set({ navWidth })
|
||||||
navWidth
|
dispatch({
|
||||||
})
|
type: 'SET_NAV_WIDTH',
|
||||||
})
|
navWidth
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,8 +270,8 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hideLeftLists (noteDetail, noteList, mainBody) {
|
hideLeftLists (noteDetail, noteList, mainBody) {
|
||||||
this.setState({noteDetailWidth: noteDetail.style.left})
|
this.setState({ noteDetailWidth: noteDetail.style.left })
|
||||||
this.setState({mainBodyWidth: mainBody.style.left})
|
this.setState({ mainBodyWidth: mainBody.style.left })
|
||||||
noteDetail.style.left = '0px'
|
noteDetail.style.left = '0px'
|
||||||
mainBody.style.left = '0px'
|
mainBody.style.left = '0px'
|
||||||
noteList.style.display = 'none'
|
noteList.style.display = 'none'
|
||||||
@@ -294,33 +293,36 @@ class Main extends React.Component {
|
|||||||
<div
|
<div
|
||||||
className='Main'
|
className='Main'
|
||||||
styleName='root'
|
styleName='root'
|
||||||
onMouseMove={(e) => this.handleMouseMove(e)}
|
onMouseMove={e => this.handleMouseMove(e)}
|
||||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
>
|
>
|
||||||
<SideNav
|
<SideNav
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, ['dispatch', 'data', 'config', 'location'])}
|
||||||
'dispatch',
|
|
||||||
'data',
|
|
||||||
'config',
|
|
||||||
'location'
|
|
||||||
])}
|
|
||||||
width={this.state.navWidth}
|
width={this.state.navWidth}
|
||||||
/>
|
/>
|
||||||
{!config.isSideNavFolded &&
|
{!config.isSideNavFolded &&
|
||||||
<div styleName={this.state.isLeftSliderFocused ? 'slider--active' : 'slider'}
|
<div
|
||||||
style={{left: this.state.navWidth}}
|
styleName={
|
||||||
onMouseDown={(e) => this.handleLeftSlideMouseDown(e)}
|
this.state.isLeftSliderFocused ? 'slider--active' : 'slider'
|
||||||
|
}
|
||||||
|
style={{ left: this.state.navWidth }}
|
||||||
|
onMouseDown={e => this.handleLeftSlideMouseDown(e)}
|
||||||
draggable='false'
|
draggable='false'
|
||||||
>
|
>
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
</div>
|
</div>}
|
||||||
}
|
<div
|
||||||
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||||
id='main-body'
|
id='main-body'
|
||||||
ref='body'
|
ref='body'
|
||||||
style={{left: config.isSideNavFolded ? foldedNavigationWidth : this.state.navWidth}}
|
style={{
|
||||||
|
left: config.isSideNavFolded
|
||||||
|
? foldedNavigationWidth
|
||||||
|
: this.state.navWidth
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<TopBar style={{width: this.state.listWidth}}
|
<TopBar
|
||||||
|
style={{ width: this.state.listWidth }}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'config',
|
'config',
|
||||||
@@ -329,7 +331,8 @@ class Main extends React.Component {
|
|||||||
'location'
|
'location'
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
<NoteList style={{width: this.state.listWidth}}
|
<NoteList
|
||||||
|
style={{ width: this.state.listWidth }}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'data',
|
'data',
|
||||||
@@ -338,15 +341,20 @@ class Main extends React.Component {
|
|||||||
'location'
|
'location'
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
<div styleName={this.state.isRightSliderFocused ? 'slider-right--active' : 'slider-right'}
|
<div
|
||||||
style={{left: this.state.listWidth - 1}}
|
styleName={
|
||||||
onMouseDown={(e) => this.handleRightSlideMouseDown(e)}
|
this.state.isRightSliderFocused
|
||||||
|
? 'slider-right--active'
|
||||||
|
: 'slider-right'
|
||||||
|
}
|
||||||
|
style={{ left: this.state.listWidth - 1 }}
|
||||||
|
onMouseDown={e => this.handleRightSlideMouseDown(e)}
|
||||||
draggable='false'
|
draggable='false'
|
||||||
>
|
>
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
</div>
|
</div>
|
||||||
<Detail
|
<Detail
|
||||||
style={{left: this.state.listWidth}}
|
style={{ left: this.state.listWidth }}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'data',
|
'data',
|
||||||
@@ -374,4 +382,4 @@ Main.propTypes = {
|
|||||||
data: PropTypes.shape({}).isRequired
|
data: PropTypes.shape({}).isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((x) => x)(CSSModules(Main, styles))
|
export default connect(x => x)(CSSModules(Main, styles))
|
||||||
|
|||||||
118
lib/main.html
118
lib/main.html
@@ -1,72 +1,83 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
|
<link rel="stylesheet" href="../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
|
||||||
<link rel="shortcut icon" href="../resources/favicon.ico">
|
<link rel="shortcut icon" href="../resources/favicon.ico">
|
||||||
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
|
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
|
||||||
|
<link rel="stylesheet" href="../node_modules/katex/dist/katex.min.css">
|
||||||
<link rel="stylesheet" href="../node_modules/codemirror/addon/dialog/dialog.css">
|
<link rel="stylesheet" href="../node_modules/codemirror/addon/dialog/dialog.css">
|
||||||
<title>Boostnote</title>
|
<title>Boostnote</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'OpenSans';
|
font-family: 'OpenSans';
|
||||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||||
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
@font-face {
|
|
||||||
font-family: 'Lato';
|
@font-face {
|
||||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
font-family: 'Lato';
|
||||||
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||||
font-style: normal;
|
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||||
font-weight: normal;
|
font-style: normal;
|
||||||
text-rendering: optimizeLegibility;
|
font-weight: normal;
|
||||||
}
|
text-rendering: optimizeLegibility;
|
||||||
#loadingCover{
|
}
|
||||||
background-color: #f4f4f4;
|
|
||||||
position: absolute;
|
#loadingCover {
|
||||||
top: 0;
|
background-color: #f4f4f4;
|
||||||
bottom: 0;
|
position: absolute;
|
||||||
left: 0;
|
top: 0;
|
||||||
right: 0;
|
bottom: 0;
|
||||||
box-sizing: border-box;
|
left: 0;
|
||||||
padding: 65px 0;
|
right: 0;
|
||||||
font-family: sans-serif;
|
box-sizing: border-box;
|
||||||
}
|
padding: 65px 0;
|
||||||
#loadingCover img{
|
font-family: sans-serif;
|
||||||
display: block;
|
}
|
||||||
margin: 75px auto 5px;
|
|
||||||
width: 160px;
|
#loadingCover img {
|
||||||
height: 160px;
|
display: block;
|
||||||
}
|
margin: 75px auto 5px;
|
||||||
#loadingCover .message{
|
width: 160px;
|
||||||
font-size: 30px;
|
height: 160px;
|
||||||
text-align: center;
|
}
|
||||||
line-height: 1.6;
|
|
||||||
font-weight: 100;
|
#loadingCover .message {
|
||||||
color: #888;
|
font-size: 30px;
|
||||||
}
|
text-align: center;
|
||||||
.CodeEditor {
|
line-height: 1.6;
|
||||||
opacity: 1 !important;
|
font-weight: 100;
|
||||||
pointer-events: auto !important;
|
color: #888;
|
||||||
}
|
}
|
||||||
.CodeMirror-ruler {
|
|
||||||
border-left-color: rgba(142, 142, 142, 0.5);
|
.CodeEditor {
|
||||||
mix-blend-mode: difference;
|
opacity: 1 !important;
|
||||||
}
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-ruler {
|
||||||
|
border-left-color: rgba(142, 142, 142, 0.5);
|
||||||
|
mix-blend-mode: difference;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="loadingCover">
|
<div id="loadingCover">
|
||||||
<img src="../resources/app.png">
|
<img src="../resources/app.png">
|
||||||
<div class='message'><i class="fa fa-spinner fa-spin" spin></i></div>
|
<div class='message'>
|
||||||
|
<i class="fa fa-spinner fa-spin" spin></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="content"></div>
|
<div id="content"></div>
|
||||||
@@ -130,4 +141,5 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user