mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-15 02:36:36 +00:00
implements better positioning between the editor and the preview in the SplitEditor (particularly with lot of images)
This commit is contained in:
@@ -101,7 +101,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleEditorActivity () {
|
handleEditorActivity () {
|
||||||
if (!this.textEditorInterface.transaction) {
|
if (this.props.onCursorActivity) {
|
||||||
|
this.props.onCursorActivity(this.editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.enableTableEditor && !this.textEditorInterface.transaction) {
|
||||||
this.updateTableEditorState()
|
this.updateTableEditorState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,6 +234,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
eventEmitter.emit('code:init')
|
eventEmitter.emit('code:init')
|
||||||
this.editor.on('scroll', this.scrollHandler)
|
this.editor.on('scroll', this.scrollHandler)
|
||||||
|
this.editor.on('cursorActivity', this.editorActivityHandler)
|
||||||
|
|
||||||
const editorTheme = document.getElementById('editorTheme')
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.addEventListener('load', this.loadStyleHandler)
|
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||||
@@ -289,7 +294,6 @@ export default class CodeEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (this.props.enableTableEditor) {
|
if (this.props.enableTableEditor) {
|
||||||
this.editor.on('cursorActivity', this.editorActivityHandler)
|
|
||||||
this.editor.on('changes', this.editorActivityHandler)
|
this.editor.on('changes', this.editorActivityHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,10 +383,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.off('paste', this.pasteHandler)
|
this.editor.off('paste', this.pasteHandler)
|
||||||
eventEmitter.off('top:search', this.searchHandler)
|
eventEmitter.off('top:search', this.searchHandler)
|
||||||
this.editor.off('scroll', this.scrollHandler)
|
this.editor.off('scroll', this.scrollHandler)
|
||||||
|
this.editor.off('cursorActivity', this.editorActivityHandler)
|
||||||
|
|
||||||
const editorTheme = document.getElementById('editorTheme')
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||||
|
|
||||||
eventEmitter.off('code:format-table', this.formatTable)
|
eventEmitter.off('code:format-table', this.formatTable)
|
||||||
|
|
||||||
|
if (this.props.enableTableEditor) {
|
||||||
|
this.editor.off('changes', this.editorActivityHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps, prevState) {
|
componentDidUpdate (prevProps, prevState) {
|
||||||
|
|||||||
@@ -20,51 +20,144 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCursorActivity (editor) {
|
||||||
|
if (this.userScroll) {
|
||||||
|
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
|
||||||
|
const previewTop = _.get(previewDoc, 'body.scrollTop')
|
||||||
|
|
||||||
|
const line = editor.doc.getCursor().line
|
||||||
|
let top
|
||||||
|
if (line === 0) {
|
||||||
|
top = 0
|
||||||
|
} else {
|
||||||
|
const blocks = []
|
||||||
|
for (const block of previewDoc.querySelectorAll('body>[data-line]')) {
|
||||||
|
const l = parseInt(block.getAttribute('data-line'))
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
line: l,
|
||||||
|
top: block.offsetTop
|
||||||
|
})
|
||||||
|
|
||||||
|
if (l > line) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const i = blocks.length - 1
|
||||||
|
if (i > 0) {
|
||||||
|
const ratio = (blocks[i].top - blocks[i - 1].top) / (blocks[i].line - blocks[i - 1].line)
|
||||||
|
|
||||||
|
const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)
|
||||||
|
|
||||||
|
top = blocks[i - 1].top + Math.floor((line - blocks[i - 1].line) * ratio) - delta
|
||||||
|
} else {
|
||||||
|
const srcTop = _.get(editor.doc, 'scrollTop')
|
||||||
|
const srcHeight = _.get(editor.doc, 'height')
|
||||||
|
const targetHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||||
|
|
||||||
|
top = targetHeight * srcTop / srcHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollTo(previewTop, top, y => _.set(previewDoc, 'body.scrollTop', y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleOnChange () {
|
handleOnChange () {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
this.props.onChange()
|
this.props.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleEditorScroll (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 (this.userScroll) {
|
||||||
if (e.doc) {
|
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
|
||||||
srcTop = _.get(e, 'doc.scrollTop')
|
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||||
srcHeight = _.get(e, 'doc.height')
|
|
||||||
targetTop = _.get(previewDoc, 'body.scrollTop')
|
const from = codeDoc.cm.coordsChar({left: 0, top: 0}).line
|
||||||
targetHeight = _.get(previewDoc, 'body.scrollHeight')
|
const to = codeDoc.cm.coordsChar({left: 0, top: codeDoc.cm.display.lastWrapHeight * 1.125}).line
|
||||||
|
const previewTop = _.get(previewDoc, 'body.scrollTop')
|
||||||
|
|
||||||
|
let top
|
||||||
|
if (from === 0) {
|
||||||
|
top = 0
|
||||||
|
} else if (to === codeDoc.lastLine()) {
|
||||||
|
top = _.get(previewDoc, 'body.scrollHeight') - _.get(previewDoc, 'body.clientHeight')
|
||||||
} else {
|
} else {
|
||||||
srcTop = _.get(previewDoc, 'body.scrollTop')
|
const line = from + Math.floor((to - from) / 3)
|
||||||
srcHeight = _.get(previewDoc, 'body.scrollHeight')
|
|
||||||
targetTop = _.get(codeDoc, 'scrollTop')
|
const blocks = []
|
||||||
targetHeight = _.get(codeDoc, 'height')
|
for (const block of previewDoc.querySelectorAll('body>[data-line]')) {
|
||||||
|
const l = parseInt(block.getAttribute('data-line'))
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
line: l,
|
||||||
|
top: block.offsetTop
|
||||||
|
})
|
||||||
|
|
||||||
|
if (l > line) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const i = blocks.length - 1
|
||||||
|
|
||||||
|
const ratio = (blocks[i].top - blocks[i - 1].top) / (blocks[i].line - blocks[i - 1].line)
|
||||||
|
|
||||||
|
top = blocks[i - 1].top + Math.floor((line - blocks[i - 1].line) * ratio)
|
||||||
}
|
}
|
||||||
|
|
||||||
const distance = (targetHeight * srcTop / srcHeight) - targetTop
|
this.scrollTo(previewTop, top, y => _.set(previewDoc, 'body.scrollTop', y))
|
||||||
const framerate = 1000 / 60
|
}
|
||||||
const frames = 20
|
}
|
||||||
const refractory = frames * framerate
|
|
||||||
|
|
||||||
this.userScroll = false
|
handlePreviewScroll (e) {
|
||||||
|
if (this.userScroll) {
|
||||||
|
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
|
||||||
|
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||||
|
|
||||||
let frame = 0
|
const srcTop = _.get(previewDoc, 'body.scrollTop')
|
||||||
let scrollPos, time
|
const editorTop = _.get(codeDoc, 'scrollTop')
|
||||||
const timer = setInterval(() => {
|
|
||||||
time = frame / frames
|
let top
|
||||||
scrollPos = time < 0.5
|
if (srcTop === 0) {
|
||||||
? 2 * time * time // ease in
|
top = 0
|
||||||
: -1 + (4 - 2 * time) * time // ease out
|
} else {
|
||||||
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3)
|
||||||
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
|
const previewTop = srcTop + delta
|
||||||
if (frame >= frames) {
|
|
||||||
clearInterval(timer)
|
const blocks = []
|
||||||
setTimeout(() => { this.userScroll = true }, refractory)
|
for (const block of previewDoc.querySelectorAll('body>[data-line]')) {
|
||||||
|
const top = block.offsetTop
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
line: parseInt(block.getAttribute('data-line')),
|
||||||
|
top
|
||||||
|
})
|
||||||
|
|
||||||
|
if (top > previewTop) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
frame++
|
|
||||||
}, framerate)
|
const i = blocks.length - 1
|
||||||
|
if (i > 0) {
|
||||||
|
const from = codeDoc.cm.heightAtLine(blocks[i - 1].line, 'local')
|
||||||
|
const to = codeDoc.cm.heightAtLine(blocks[i].line, 'local')
|
||||||
|
|
||||||
|
const ratio = (previewTop - blocks[i - 1].top) / (blocks[i].top - blocks[i - 1].top)
|
||||||
|
|
||||||
|
top = from + Math.floor((to - from) * ratio) - delta
|
||||||
|
} else {
|
||||||
|
const srcTop = _.get(previewDoc, 'body.scrollTop')
|
||||||
|
const srcHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||||
|
const targetHeight = _.get(codeDoc, 'height')
|
||||||
|
|
||||||
|
top = targetHeight * srcTop / srcHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollTo(editorTop, top, y => codeDoc.cm.scrollTo(0, y))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +220,32 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scrollTo (from, to, scroller) {
|
||||||
|
const distance = to - from
|
||||||
|
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
|
||||||
|
|
||||||
|
scroller(from + scrollPos * distance)
|
||||||
|
|
||||||
|
if (frame >= frames) {
|
||||||
|
clearInterval(timer)
|
||||||
|
setTimeout(() => { this.userScroll = true }, refractory)
|
||||||
|
}
|
||||||
|
frame++
|
||||||
|
}, framerate)
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {config, value, storageKey, noteKey} = this.props
|
const {config, value, storageKey, noteKey} = this.props
|
||||||
const storage = findStorage(storageKey)
|
const storage = findStorage(storageKey)
|
||||||
@@ -162,7 +281,8 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
onChange={this.handleOnChange.bind(this)}
|
onChange={this.handleOnChange.bind(this)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleEditorScroll.bind(this)}
|
||||||
|
onCursorActivity={this.handleCursorActivity.bind(this)}
|
||||||
/>
|
/>
|
||||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
@@ -186,7 +306,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)}
|
onScroll={this.handlePreviewScroll.bind(this)}
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
|
|||||||
Reference in New Issue
Block a user