1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

Merge branch 'master' of https://github.com/BoostIO/Boostnote into all-notes-storage-labels

This commit is contained in:
pfftdammitchris
2018-02-22 16:16:43 -08:00
12 changed files with 210 additions and 34 deletions

View File

@@ -7,6 +7,7 @@ import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage' import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import fs from 'fs' import fs from 'fs'
import eventEmitter from 'browser/main/lib/eventEmitter'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -47,6 +48,40 @@ export default class CodeEditor extends React.Component {
this.loadStyleHandler = (e) => { this.loadStyleHandler = (e) => {
this.editor.refresh() this.editor.refresh()
} }
this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null
}
handleSearch (msg) {
const cm = this.editor
const component = this
if (component.searchState) cm.removeOverlay(component.searchState)
if (msg.length < 3) return
cm.operation(function () {
component.searchState = makeOverlay(msg, 'searching')
cm.addOverlay(component.searchState)
function makeOverlay (query, style) {
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi')
return {
token: function (stream) {
query.lastIndex = stream.pos
var match = query.exec(stream.string)
if (match && match.index === stream.pos) {
stream.pos += match[0].length || 1
return style
} else if (match) {
stream.pos = match.index
} else {
stream.skipToEnd()
}
}
}
}
})
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
} }
componentDidMount () { componentDidMount () {
@@ -107,6 +142,10 @@ export default class CodeEditor extends React.Component {
this.editor.on('blur', this.blurHandler) this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler) this.editor.on('change', this.changeHandler)
this.editor.on('paste', this.pasteHandler) this.editor.on('paste', this.pasteHandler)
eventEmitter.on('top:search', this.searchHandler)
eventEmitter.emit('code:init')
this.editor.on('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme') const editorTheme = document.getElementById('editorTheme')
editorTheme.addEventListener('load', this.loadStyleHandler) editorTheme.addEventListener('load', this.loadStyleHandler)
@@ -126,6 +165,8 @@ export default class CodeEditor extends React.Component {
this.editor.off('blur', this.blurHandler) this.editor.off('blur', this.blurHandler)
this.editor.off('change', this.changeHandler) this.editor.off('change', this.changeHandler)
this.editor.off('paste', this.pasteHandler) this.editor.off('paste', this.pasteHandler)
eventEmitter.off('top:search', this.searchHandler)
this.editor.off('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme') const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler) editorTheme.removeEventListener('load', this.loadStyleHandler)
} }
@@ -231,11 +272,16 @@ export default class CodeEditor extends React.Component {
} }
handlePaste (editor, e) { handlePaste (editor, e) {
const dataTransferItem = e.clipboardData.items[0] const clipboardData = e.clipboardData
if (!dataTransferItem.type.match('image')) return const dataTransferItem = clipboardData.items[0]
const pastedTxt = clipboardData.getData('text')
const isURL = (str) => {
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
return matcher.test(str)
}
if (dataTransferItem.type.match('image')) {
const blob = dataTransferItem.getAsFile() const blob = dataTransferItem.getAsFile()
const reader = new window.FileReader() const reader = new FileReader()
let base64data let base64data
reader.readAsDataURL(blob) reader.readAsDataURL(blob)
@@ -252,6 +298,41 @@ export default class CodeEditor extends React.Component {
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})` const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd) this.insertImageMd(imageMd)
} }
} else if (this.props.fetchUrlTitle && isURL(pastedTxt)) {
this.handlePasteUrl(e, editor, pastedTxt)
}
}
handleScroll (e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handlePasteUrl (e, editor, pastedTxt) {
e.preventDefault()
const taggedUrl = `<${pastedTxt}>`
editor.replaceSelection(taggedUrl)
fetch(pastedTxt, {
method: 'get'
}).then((response) => {
return (response.text())
}).then((response) => {
const parsedResponse = (new window.DOMParser()).parseFromString(response, 'text/html')
const value = editor.getValue()
const cursor = editor.getCursor()
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})`
const newValue = value.replace(taggedUrl, LinkWithTitle)
editor.setValue(newValue)
editor.setCursor(cursor)
}).catch((e) => {
const value = editor.getValue()
const newValue = value.replace(taggedUrl, pastedTxt)
const cursor = editor.getCursor()
editor.setValue(newValue)
editor.setCursor(cursor)
})
} }
render () { render () {

View File

@@ -261,6 +261,7 @@ class MarkdownEditor extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey} storageKey={storageKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)} onBlur={(e) => this.handleBlur(e)}
/> />

View File

@@ -121,6 +121,7 @@ export default class MarkdownPreview extends React.Component {
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.DoubleClickHandler = (e) => this.handleDoubleClick(e) this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e) this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e) this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
this.saveAsTextHandler = () => this.handleSaveAsText() this.saveAsTextHandler = () => this.handleSaveAsText()
@@ -151,6 +152,12 @@ export default class MarkdownPreview extends React.Component {
this.props.onCheckboxClick(e) this.props.onCheckboxClick(e)
} }
handleScroll (e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handleContextMenu (e) { handleContextMenu (e) {
this.props.onContextMenu(e) this.props.onContextMenu(e)
} }
@@ -279,6 +286,7 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.addEventListener('dblclick', this.DoubleClickHandler) this.refs.root.contentWindow.document.addEventListener('dblclick', this.DoubleClickHandler)
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler) this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.addEventListener('dragover', 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)
@@ -292,6 +300,7 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.removeEventListener('dblclick', this.DoubleClickHandler) this.refs.root.contentWindow.document.removeEventListener('dblclick', this.DoubleClickHandler)
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler) this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.removeEventListener('dragover', 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)

View File

@@ -2,6 +2,7 @@ import React from 'react'
import CodeEditor from 'browser/components/CodeEditor' import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview' import MarkdownPreview from 'browser/components/MarkdownPreview'
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import _ from 'lodash'
import styles from './MarkdownSplitEditor.styl' import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
@@ -12,6 +13,7 @@ class MarkdownSplitEditor extends React.Component {
this.value = props.value this.value = props.value
this.focus = () => this.refs.code.focus() this.focus = () => this.refs.code.focus()
this.reload = () => this.refs.code.reload() this.reload = () => this.refs.code.reload()
this.userScroll = true
} }
handleOnChange () { handleOnChange () {
@@ -19,6 +21,49 @@ class MarkdownSplitEditor extends React.Component {
this.props.onChange() this.props.onChange()
} }
handleScroll (e) {
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight
if (this.userScroll) {
if (e.doc) {
srcTop = _.get(e, 'doc.scrollTop')
srcHeight = _.get(e, 'doc.height')
targetTop = _.get(previewDoc, 'body.scrollTop')
targetHeight = _.get(previewDoc, 'body.scrollHeight')
} else {
srcTop = _.get(previewDoc, 'body.scrollTop')
srcHeight = _.get(previewDoc, 'body.scrollHeight')
targetTop = _.get(codeDoc, 'scrollTop')
targetHeight = _.get(codeDoc, 'height')
}
const distance = (targetHeight * srcTop / srcHeight) - targetTop
const framerate = 1000 / 60
const frames = 20
const refractory = frames * framerate
this.userScroll = false
let frame = 0
let scrollPos, time
const timer = setInterval(() => {
time = frame / frames
scrollPos = time < 0.5
? 2 * time * time // ease in
: -1 + (4 - 2 * time) * time // ease out
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
if (frame >= frames) {
clearInterval(timer)
setTimeout(() => { this.userScroll = true }, refractory)
}
frame++
}, framerate)
}
}
handleCheckboxClick (e) { handleCheckboxClick (e) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@@ -66,8 +111,10 @@ class MarkdownSplitEditor extends React.Component {
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
storageKey={storageKey} storageKey={storageKey}
onChange={this.handleOnChange.bind(this)} onChange={this.handleOnChange.bind(this)}
onScroll={this.handleScroll.bind(this)}
/> />
<MarkdownPreview <MarkdownPreview
style={previewStyle} style={previewStyle}
@@ -84,6 +131,7 @@ class MarkdownSplitEditor extends React.Component {
tabInde='0' tabInde='0'
value={value} value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)} onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification} showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path} storagePath={storage.path}
/> />

View File

@@ -220,6 +220,7 @@ pre
background-color white background-color white
&.CodeMirror &.CodeMirror
height initial height initial
flex-wrap wrap
&>code &>code
flex 1 flex 1
overflow-x auto overflow-x auto
@@ -229,6 +230,13 @@ pre
padding 0 padding 0
border none border none
border-radius 0 border-radius 0
&>span.filename
width 100%
border-radius: 5px 0px 0px 0px
margin -8px 100% 8px -8px
padding 0px 6px
background-color #777;
color white
&>span.lineNumber &>span.lineNumber
display none display none
font-size 1em font-size 1em

View File

View File

@@ -9,10 +9,11 @@ import {lastFindInArray} from './utils'
const katex = window.katex const katex = window.katex
const config = ConfigManager.get() const config = ConfigManager.get()
function createGutter (str) { function createGutter (str, firstLineNumber) {
const lc = (str.match(/\n/g) || []).length if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
const lines = [] const lines = []
for (let i = 1; i <= lc; i++) { for (let i = firstLineNumber; i <= lastLineNumber; i++) {
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>') lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
} }
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>' return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
@@ -25,15 +26,22 @@ var md = markdownit({
xhtmlOut: true, xhtmlOut: true,
breaks: true, breaks: true,
highlight: function (str, lang) { highlight: function (str, lang) {
if (lang === 'flowchart') { const delimiter = ':'
const langInfo = lang.split(delimiter)
const langType = langInfo[0]
const fileName = langInfo[1] || ''
const firstLineNumber = parseInt(langInfo[2], 10)
if (langType === 'flowchart') {
return `<pre class="flowchart">${str}</pre>` return `<pre class="flowchart">${str}</pre>`
} }
if (lang === 'sequence') { if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>` return `<pre class="sequence">${str}</pre>`
} }
return '<pre class="code">' + return '<pre class="code">' +
createGutter(str) + '<span class="filename">' + fileName + '</span>' +
'<code class="' + lang + '">' + createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
str + str +
'</code></pre>' '</code></pre>'
} }

View File

@@ -68,11 +68,8 @@ class MarkdownNoteDetail extends React.Component {
} }
componentWillUnmount () { componentWillUnmount () {
if (this.saveQueue != null) this.saveNow()
}
componentDidUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton) ee.off('topbar:togglelockbutton', this.toggleLockButton)
if (this.saveQueue != null) this.saveNow()
} }
handleUpdateTag () { handleUpdateTag () {

View File

@@ -568,6 +568,7 @@ class SnippetNoteDetail extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index} ref={'code-' + index}
/> />

View File

@@ -22,14 +22,18 @@ class TopBar extends React.Component {
this.focusSearchHandler = () => { this.focusSearchHandler = () => {
this.handleOnSearchFocus() this.handleOnSearchFocus()
} }
this.codeInitHandler = this.handleCodeInit.bind(this)
} }
componentDidMount () { componentDidMount () {
ee.on('top:focus-search', this.focusSearchHandler) ee.on('top:focus-search', this.focusSearchHandler)
ee.on('code:init', this.codeInitHandler)
} }
componentWillUnmount () { componentWillUnmount () {
ee.off('top:focus-search', this.focusSearchHandler) ee.off('top:focus-search', this.focusSearchHandler)
ee.off('code:init', this.codeInitHandler)
} }
handleKeyDown (e) { handleKeyDown (e) {
@@ -73,14 +77,16 @@ class TopBar extends React.Component {
handleSearchChange (e) { handleSearchChange (e) {
const { router } = this.context const { router } = this.context
const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) { if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push('/searched') router.push('/searched')
} else { } else {
e.preventDefault() e.preventDefault()
} }
this.setState({ this.setState({
search: this.refs.searchInput.value search: keyword
}) })
ee.emit('top:search', keyword)
} }
handleSearchFocus (e) { handleSearchFocus (e) {
@@ -115,6 +121,10 @@ class TopBar extends React.Component {
} }
} }
handleCodeInit () {
ee.emit('top:search', this.refs.searchInput.value)
}
render () { render () {
const { config, style, location } = this.props const { config, style, location } = this.props
return ( return (

View File

@@ -36,7 +36,8 @@ export const DEFAULT_CONFIG = {
displayLineNumbers: true, displayLineNumbers: true,
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false, scrollPastEnd: false,
type: 'SPLIT' type: 'SPLIT',
fetchUrlTitle: true
}, },
preview: { preview: {
fontSize: '14', fontSize: '14',

View File

@@ -76,7 +76,8 @@ class UiTab extends React.Component {
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked, displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
switchPreview: this.refs.editorSwitchPreview.value, switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value, keyMap: this.refs.editorKeyMap.value,
scrollPastEnd: this.refs.scrollPastEnd.checked scrollPastEnd: this.refs.scrollPastEnd.checked,
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
}, },
preview: { preview: {
fontSize: this.refs.previewFontSize.value, fontSize: this.refs.previewFontSize.value,
@@ -328,6 +329,17 @@ class UiTab extends React.Component {
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.fetchUrlTitle}
ref='editorFetchUrlTitle'
type='checkbox'
/>&nbsp;
Bring in web page title when pasting URL on editor
</label>
</div>
<div styleName='group-header2'>Preview</div> <div styleName='group-header2'>Preview</div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>