Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion browser/components/CodeEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -352,6 +356,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)
Expand Down Expand Up @@ -489,7 +494,6 @@ export default class CodeEditor extends React.Component {
})

if (this.props.enableTableEditor) {
this.editor.on('cursorActivity', this.editorActivityHandler)
this.editor.on('changes', this.editorActivityHandler)
}

Expand Down Expand Up @@ -548,12 +552,18 @@ 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)
this.editor.off('contextmenu', this.contextMenuHandler)

const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler)

spellcheck.setLanguage(null, spellcheck.SPELLCHECK_DISABLED)
eventEmitter.off('code:format-table', this.formatTable)

if (this.props.enableTableEditor) {
this.editor.off('changes', this.editorActivityHandler)
}
}

componentDidUpdate(prevProps, prevState) {
Expand Down
2 changes: 1 addition & 1 deletion browser/components/MarkdownEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class MarkdownEditor extends React.Component {
},
() => {
this.previewRef.current.focus()
this.previewRef.current.scrollToRow(cursorPosition.line)
this.previewRef.current.scrollToLine(cursorPosition.line)
}
)
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
Expand Down
11 changes: 6 additions & 5 deletions browser/components/MarkdownPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -1145,17 +1145,18 @@ class MarkdownPreview extends React.Component {

/**
* @public
* @param {Number} targetRow
* @param {Number} targetLine
*/
scrollToRow(targetRow) {
scrollToLine(targetLine) {
const blocks = this.getWindow().document.querySelectorAll(
'body>[data-line]'
'body [data-line]'
)

for (let index = 0; index < blocks.length; index++) {
let block = blocks[index]
const row = parseInt(block.getAttribute('data-line'))
if (row > targetRow || index === blocks.length - 1) {
const line = parseInt(block.getAttribute('data-line'))

if (line > targetLine || index === blocks.length - 1) {
block = blocks[index - 1]
block != null && this.scrollTo(0, block.offsetTop)
break
Expand Down
262 changes: 212 additions & 50 deletions browser/components/MarkdownSplitEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,80 @@ 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,
codeEditorHeightInPercent: 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'
)
const previewTop = _.get(previewDoc, 'body.scrollTop')

const line = editor.doc.getCursor().line
let top
if (line === 0) {
top = 0
} else {
const blockElements = previewDoc.querySelectorAll('body [data-line]')
const blocks = []
for (const block of blockElements) {
const l = parseInt(block.getAttribute('data-line'))

blocks.push({
line: l,
top: block.offsetTop
})

if (l > line) {
break
}
}

if (blocks.length === 1) {
const block = blockElements[blockElements.length - 1]

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 =>
_.set(previewDoc, 'body.scrollTop', y)
)
}
}

setValue(value) {
this.refs.code.setValue(value)
}
Expand All @@ -30,59 +96,125 @@ class MarkdownSplitEditor extends React.Component {
this.props.onChange(e)
}

handleScroll(e) {
if (!this.props.config.preview.scrollSync) return
handleEditorScroll(e) {
if (this.userScroll) {
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 {
const line = from + Math.floor((to - from) / 3)

const previewDoc = _.get(
this,
'refs.preview.refs.root.contentWindow.document'
)
const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight
const blockElements = previewDoc.querySelectorAll('body [data-line]')
const blocks = []
for (const block of blockElements) {
const l = parseInt(block.getAttribute('data-line'))

blocks.push({
line: l,
top: block.offsetTop
})

if (l > line) {
break
}
}

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 =
(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)
}

this.scrollTo(previewTop, top, y =>
_.set(previewDoc, 'body.scrollTop', y)
)
}
}

handlePreviewScroll(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 srcTop = _.get(previewDoc, 'body.scrollTop')
const editorTop = _.get(codeDoc, 'scrollTop')

let top
if (srcTop === 0) {
top = 0
} else {
srcTop = _.get(previewDoc, 'body.scrollTop')
srcHeight = _.get(previewDoc, 'body.scrollHeight')
targetTop = _.get(codeDoc, 'scrollTop')
targetHeight = _.get(codeDoc, 'height')
}
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 blockElements) {
const top = block.offsetTop

blocks.push({
line: parseInt(block.getAttribute('data-line')),
top
})

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)
if (top > previewTop) {
break
}
}

if (blocks.length === 1) {
const block = blockElements[blockElements.length - 1]

blocks.push({
line: codeDoc.size,
top: block.offsetTop + block.offsetHeight
})
}
frame++
}, framerate)

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))
}
}

Expand Down Expand Up @@ -168,6 +300,35 @@ 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,
Expand Down Expand Up @@ -273,7 +434,8 @@ class MarkdownSplitEditor extends React.Component {
noteKey={noteKey}
linesHighlighted={linesHighlighted}
onChange={e => this.handleOnChange(e)}
onScroll={this.handleScroll.bind(this)}
onScroll={e => this.handleEditorScroll(e)}
onCursorActivity={e => this.handleCursorActivity(e)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
Expand Down Expand Up @@ -309,7 +471,7 @@ class MarkdownSplitEditor extends React.Component {
tabInde='0'
value={value}
onCheckboxClick={e => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)}
onScroll={e => this.handlePreviewScroll(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
noteKey={noteKey}
Expand Down
1 change: 0 additions & 1 deletion browser/components/render/MermaidRender.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ function render(element, content, theme, enableHTMLLabel) {

el.setAttribute('ratio', ratio)
el.setAttribute('height', el.parentNode.clientWidth / ratio)
console.log(el)
}
})
} catch (e) {
Expand Down