From 696c2f29b5545bc1427fc7afae80d1e6b415fdad Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 17 Sep 2018 16:59:27 +0200 Subject: [PATCH 1/4] implements better positioning between the editor and the preview in the SplitEditor (particularly with lot of images) --- browser/components/CodeEditor.js | 14 +- browser/components/MarkdownSplitEditor.js | 190 ++++++++++++++++++---- 2 files changed, 167 insertions(+), 37 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 304171ba..cddea042 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -101,7 +101,11 @@ export default class CodeEditor extends React.Component { } handleEditorActivity () { - if (!this.textEditorInterface.transaction) { + if (this.props.onCursorActivity) { + this.props.onCursorActivity(this.editor) + } + + if (this.props.enableTableEditor && !this.textEditorInterface.transaction) { this.updateTableEditorState() } } @@ -230,6 +234,7 @@ export default class CodeEditor extends React.Component { eventEmitter.emit('code:init') this.editor.on('scroll', this.scrollHandler) + this.editor.on('cursorActivity', this.editorActivityHandler) const editorTheme = document.getElementById('editorTheme') editorTheme.addEventListener('load', this.loadStyleHandler) @@ -289,7 +294,6 @@ export default class CodeEditor extends React.Component { }) if (this.props.enableTableEditor) { - this.editor.on('cursorActivity', 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) eventEmitter.off('top:search', this.searchHandler) this.editor.off('scroll', this.scrollHandler) + this.editor.off('cursorActivity', this.editorActivityHandler) + const editorTheme = document.getElementById('editorTheme') editorTheme.removeEventListener('load', this.loadStyleHandler) eventEmitter.off('code:format-table', this.formatTable) + + if (this.props.enableTableEditor) { + this.editor.off('changes', this.editorActivityHandler) + } } componentDidUpdate (prevProps, prevState) { diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index ddc9d7e0..088a3d9e 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -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 () { this.value = this.refs.code.value 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 - + handleEditorScroll (e) { 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') + const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document') + const codeDoc = _.get(this, 'refs.code.editor.doc') + + const from = codeDoc.cm.coordsChar({left: 0, top: 0}).line + 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 { - srcTop = _.get(previewDoc, 'body.scrollTop') - srcHeight = _.get(previewDoc, 'body.scrollHeight') - targetTop = _.get(codeDoc, 'scrollTop') - targetHeight = _.get(codeDoc, 'height') + const line = from + Math.floor((to - from) / 3) + + 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 + + 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 - const framerate = 1000 / 60 - const frames = 20 - const refractory = frames * framerate + this.scrollTo(previewTop, top, y => _.set(previewDoc, 'body.scrollTop', y)) + } + } - 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 - 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) + const srcTop = _.get(previewDoc, 'body.scrollTop') + const editorTop = _.get(codeDoc, 'scrollTop') + + let top + if (srcTop === 0) { + top = 0 + } else { + const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3) + const previewTop = srcTop + delta + + const blocks = [] + 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 () { const {config, value, storageKey, noteKey} = this.props const storage = findStorage(storageKey) @@ -162,7 +281,8 @@ class MarkdownSplitEditor extends React.Component { storageKey={storageKey} noteKey={noteKey} onChange={this.handleOnChange.bind(this)} - onScroll={this.handleScroll.bind(this)} + onScroll={this.handleEditorScroll.bind(this)} + onCursorActivity={this.handleCursorActivity.bind(this)} />
this.handleMouseDown(e)} >
@@ -186,7 +306,7 @@ class MarkdownSplitEditor extends React.Component { tabInde='0' value={value} onCheckboxClick={(e) => this.handleCheckboxClick(e)} - onScroll={this.handleScroll.bind(this)} + onScroll={this.handlePreviewScroll.bind(this)} showCopyNotification={config.ui.showCopyNotification} storagePath={storage.path} noteKey={noteKey} From e76bc72667af9cd3c8c576671b80914bcfe0edf9 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 24 Dec 2018 11:25:41 +0100 Subject: [PATCH 2/4] - `data-line` attributes might not be directly under the `body` - support checkbox preference `When scrolling, synchronize preview with editor` --- browser/components/MarkdownPreview.js | 4 +--- browser/components/MarkdownSplitEditor.js | 14 ++++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 68484b04..0d4576ea 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -813,9 +813,7 @@ export default class MarkdownPreview extends React.Component { } 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++) { let block = blocks[index] diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index e00bffef..9da6bf30 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -13,13 +13,19 @@ class MarkdownSplitEditor extends React.Component { this.value = props.value this.focus = () => this.refs.code.focus() this.reload = () => this.refs.code.reload() - this.userScroll = true + this.userScroll = props.config.preview.scrollSync this.state = { isSliderFocused: false, codeEditorWidthInPercent: 50 } } + componentDidUpdate (prevProps) { + if (this.props.config.preview.scrollSync !== prevProps.config.preview.scrollSync) { + this.userScroll = this.props.config.preview.scrollSync + } + } + handleCursorActivity (editor) { if (this.userScroll) { const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document') @@ -31,7 +37,7 @@ class MarkdownSplitEditor extends React.Component { top = 0 } else { const blocks = [] - for (const block of previewDoc.querySelectorAll('body>[data-line]')) { + for (const block of previewDoc.querySelectorAll('body [data-line]')) { const l = parseInt(block.getAttribute('data-line')) blocks.push({ @@ -87,7 +93,7 @@ class MarkdownSplitEditor extends React.Component { const line = from + Math.floor((to - from) / 3) const blocks = [] - for (const block of previewDoc.querySelectorAll('body>[data-line]')) { + for (const block of previewDoc.querySelectorAll('body [data-line]')) { const l = parseInt(block.getAttribute('data-line')) blocks.push({ @@ -127,7 +133,7 @@ class MarkdownSplitEditor extends React.Component { const previewTop = srcTop + delta const blocks = [] - for (const block of previewDoc.querySelectorAll('body>[data-line]')) { + for (const block of previewDoc.querySelectorAll('body [data-line]')) { const top = block.offsetTop blocks.push({ From 9b6a61a91cf57304eeccafca9788f6cb07cf119e Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Fri, 15 May 2020 02:57:00 +0200 Subject: [PATCH 3/4] fix cursor sync --- browser/components/CodeEditor.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index eb3e8c52..d5b654ac 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -158,6 +158,10 @@ export default class CodeEditor extends React.Component { } handleEditorActivity() { + if (this.props.onCursorActivity) { + this.props.onCursorActivity(this.editor) + } + if (!this.textEditorInterface.transaction) { this.updateTableEditorState() } From 81ac3d1748a6b2b03fe6922a2ee365d90f25f486 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 16 May 2020 14:19:22 +0200 Subject: [PATCH 4/4] fix scrolling with only one big paragraph --- browser/components/MarkdownSplitEditor.js | 85 +++++++++++++---------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 4f5c3f7f..8242e51b 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -43,8 +43,9 @@ class MarkdownSplitEditor extends React.Component { if (line === 0) { top = 0 } else { + const blockElements = previewDoc.querySelectorAll('body [data-line]') const blocks = [] - for (const block of previewDoc.querySelectorAll('body [data-line]')) { + for (const block of blockElements) { const l = parseInt(block.getAttribute('data-line')) blocks.push({ @@ -57,25 +58,27 @@ class MarkdownSplitEditor extends React.Component { } } - const i = blocks.length - 1 - if (i > 0) { - const ratio = - (blocks[i].top - blocks[i - 1].top) / - (blocks[i].line - blocks[i - 1].line) + if (blocks.length === 1) { + const block = blockElements[blockElements.length - 1] - 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 + blocks.push({ + line: editor.doc.size, + top: block.offsetTop + block.offsetHeight + }) } + + const i = blocks.length - 1 + + 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 } this.scrollTo(previewTop, top, y => @@ -118,8 +121,9 @@ class MarkdownSplitEditor extends React.Component { } else { const line = from + Math.floor((to - from) / 3) + const blockElements = previewDoc.querySelectorAll('body [data-line]') const blocks = [] - for (const block of previewDoc.querySelectorAll('body [data-line]')) { + for (const block of blockElements) { const l = parseInt(block.getAttribute('data-line')) blocks.push({ @@ -132,6 +136,15 @@ class MarkdownSplitEditor extends React.Component { } } + if (blocks.length === 1) { + const block = blockElements[blockElements.length - 1] + + blocks.push({ + line: codeDoc.size, + top: block.offsetTop + block.offsetHeight + }) + } + const i = blocks.length - 1 const ratio = @@ -166,8 +179,9 @@ class MarkdownSplitEditor extends React.Component { const delta = Math.floor(_.get(previewDoc, 'body.clientHeight') / 3) const previewTop = srcTop + delta + const blockElements = previewDoc.querySelectorAll('body [data-line]') const blocks = [] - for (const block of previewDoc.querySelectorAll('body [data-line]')) { + for (const block of blockElements) { const top = block.offsetTop blocks.push({ @@ -180,23 +194,24 @@ class MarkdownSplitEditor extends React.Component { } } - 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') + if (blocks.length === 1) { + const block = blockElements[blockElements.length - 1] - 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 + blocks.push({ + line: codeDoc.size, + top: block.offsetTop + block.offsetHeight + }) } + + const i = blocks.length - 1 + + 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 } this.scrollTo(editorTop, top, y => codeDoc.cm.scrollTo(0, y))