diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx index 4b82c6bc0..b3ec038aa 100644 --- a/client/homebrew/brewRenderer/brewRenderer.jsx +++ b/client/homebrew/brewRenderer/brewRenderer.jsx @@ -19,6 +19,7 @@ const DOMPurify = require('dompurify'); const purifyConfig = { FORCE_BODY: true, SANITIZE_DOM: false }; const PAGE_HEIGHT = 1056; +let isScrolling; const INITIAL_CONTENT = dedent` @@ -87,9 +88,16 @@ const BrewRenderer = (props)=>{ const handleScroll = (e)=>{ const target = e.target; + const newPage = Math.floor(target.scrollTop / target.scrollHeight * rawPages.length); + if(newPage != state.viewablePageNumber) { + window.clearTimeout(isScrolling); + isScrolling = setTimeout(function() { + window.parent.document.dispatchEvent(new CustomEvent('renderScrolled', {})); + }, 66); + } setState((prevState)=>({ ...prevState, - viewablePageNumber : Math.floor(target.scrollTop / target.scrollHeight * rawPages.length) + viewablePageNumber : newPage })); }; diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index 3164cc585..7555a0e58 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -23,6 +23,9 @@ const DEFAULT_STYLE_TEXT = dedent` }`; let lastPage = 0; +let lockBrewJump = false; +let lockSourceJump = false; +let scrollingJump = false; const isElementCodeMirror=(element)=>{ let el = element; @@ -73,6 +76,7 @@ const Editor = createClass({ this.highlightCustomMarkdown(); window.addEventListener('resize', this.updateEditorSize); document.getElementById('BrewRenderer').addEventListener('keydown', this.handleControlKeys); + document.addEventListener('renderScrolled', this.handleBrewScroll); document.addEventListener('keydown', this.handleControlKeys); document.addEventListener('click', (e)=>{ if(isElementCodeMirror(e.target) && this.props.liveScroll ) { @@ -128,7 +132,6 @@ const Editor = createClass({ } if(!(e.ctrlKey || e.metaKey)) return; - console.log(e); // Handle CTRL-HOME and CTRL-END if(((e.keyCode == END_KEY) || (e.keyCode == HOME_KEY)) && this.props.liveScroll) this.brewJump(); @@ -140,6 +143,19 @@ const Editor = createClass({ } }, + handleBrewScroll : function() { + if(!this.props.liveScroll) return; + scrollingJump = true; + this.sourceJump(); + scrollingJump = false; + }, + + handleSourceScroll : function(e) { + if(!this.props.liveScroll) return; + scrollingJump = true; + this.brewJump(); + scrollingJump = false; + }, updateEditorSize : function() { if(this.codeEditor.current) { @@ -318,8 +334,11 @@ const Editor = createClass({ }, brewJump : function(targetPage=this.getCurrentPage()){ + if(lockBrewJump) return; if(!window) return; - // console.log(`Scroll to: p${targetPage}`); + lockSourceJump = true; + lockBrewJump = true; + //console.log(`Scroll to: p${targetPage}`); const brewRenderer = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer')[0]; const currentPos = brewRenderer.scrollTop; const targetPos = window.frames['BrewRenderer'].contentDocument.getElementById(`p${targetPage}`).getBoundingClientRect().top; @@ -328,24 +347,33 @@ const Editor = createClass({ const bounceDelay = 100; const scrollDelay = 500; - if(!this.throttleBrewMove) { - this.throttleBrewMove = _.throttle((currentPos, interimPos, targetPos)=>{ - brewRenderer.scrollTo({ top: currentPos + interimPos, behavior: 'smooth' }); - setTimeout(()=>{ - brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' }); - }, bounceDelay); - }, scrollDelay, { leading: true, trailing: false }); - }; - this.throttleBrewMove(currentPos, interimPos, targetPos); + if(scrollingJump) { + brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'instant', block: 'start' }); + } else { + if(!this.throttleBrewMove) { + this.throttleBrewMove = _.throttle((currentPos, interimPos, targetPos)=>{ + brewRenderer.scrollTo({ top: currentPos + interimPos, behavior: 'smooth' }); + setTimeout(()=>{ + brewRenderer.scrollTo({ top: currentPos + targetPos, behavior: 'smooth', block: 'start' }); + }, bounceDelay); + }, scrollDelay, { leading: true, trailing: false }); + }; + this.throttleBrewMove(currentPos, interimPos, targetPos); + } + lockSourceJump = false; + lockBrewJump = false; // const hashPage = (page != 1) ? `p${page}` : ''; // window.location.hash = hashPage; }, sourceJump : function(targetLine=null){ + if(lockSourceJump) return; if(this.isText()) { if(targetLine == null) { targetLine = 0; + lockBrewJump = true; + lockSourceJump = true; const pageCollection = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('page'); const brewRendererHeight = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer').item(0).getBoundingClientRect().height; @@ -368,23 +396,33 @@ const Editor = createClass({ let currentY = this.codeEditor.current.codeMirror.getScrollInfo().top; let targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true); - //Scroll 1/10 of the way every 10ms until 1px off. - const incrementalScroll = setInterval(()=>{ - currentY += (targetY - currentY) / 10; - this.codeEditor.current.codeMirror.scrollTo(null, currentY); + if(!scrollingJump) { + //Scroll 1/10 of the way every 10ms until 1px off. + const incrementalScroll = setInterval(()=>{ + currentY += (targetY - currentY) / 10; + this.codeEditor.current.codeMirror.scrollTo(null, currentY); - // Update target: target height is not accurate until within +-10 lines of the visible window - if(Math.abs(targetY - currentY > 100)) - targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true); + // Update target: target height is not accurate until within +-10 lines of the visible window + if(Math.abs(targetY - currentY > 100)) + targetY = this.codeEditor.current.codeMirror.heightAtLine(targetLine, 'local', true); - // End when close enough - if(Math.abs(targetY - currentY) < 1) { - this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference - this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); - this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); - clearInterval(incrementalScroll); - } - }, 10); + // End when close enough + if(Math.abs(targetY - currentY) < 1) { + this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference + this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); + this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); + clearInterval(incrementalScroll); + lockBrewJump = false; + lockSourceJump = false; + } + }, 10); + } else { + this.codeEditor.current.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference + this.codeEditor.current.setCursorPosition({ line: targetLine + 1, ch: 0 }); + this.codeEditor.current.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash'); + lockBrewJump = false; + lockSourceJump = false; + } } } }, @@ -415,6 +453,7 @@ const Editor = createClass({ view={this.state.view} value={this.props.brew.text} onChange={this.props.onTextChange} + onScroll={this.handleSourceScroll} editorTheme={this.state.editorTheme} rerenderParent={this.rerenderParent} /> ; diff --git a/shared/naturalcrit/codeEditor/codeEditor.jsx b/shared/naturalcrit/codeEditor/codeEditor.jsx index 3186e39f1..46ae64edb 100644 --- a/shared/naturalcrit/codeEditor/codeEditor.jsx +++ b/shared/naturalcrit/codeEditor/codeEditor.jsx @@ -7,6 +7,7 @@ const closeTag = require('./close-tag'); const autoCompleteEmoji = require('./autocompleteEmoji'); let CodeMirror; +let isScrolling; if(typeof window !== 'undefined'){ CodeMirror = require('codemirror'); @@ -190,6 +191,14 @@ const CodeEditor = createClass({ // Note: codeMirror passes a copy of itself in this callback. cm === this.codeMirror. Either one works. this.codeMirror.on('change', (cm)=>{this.props.onChange(cm.getValue());}); + this.codeMirror.on('scroll', (cm)=>{ + window.clearTimeout(isScrolling); + const props = this.props; + isScrolling = setTimeout(function() { + cm.setCursor({ line: cm.lineAtHeight(cm.getWrapperElement().getBoundingClientRect().top) + 1, ch: 0 }); + props.onScroll(cm.lineAtHeight(cm.getWrapperElement().getBoundingClientRect().top)); + }, 66); + }); this.updateSize(); },