diff --git a/client/homebrew/brewRenderer/brewRenderer.jsx b/client/homebrew/brewRenderer/brewRenderer.jsx
index 2da7123cc..787423528 100644
--- a/client/homebrew/brewRenderer/brewRenderer.jsx
+++ b/client/homebrew/brewRenderer/brewRenderer.jsx
@@ -188,7 +188,7 @@ const BrewRenderer = createClass({
: null}
-
{
- if(line.indexOf('\\page') !== -1) r++;
+ if(
+ (this.props.renderer == 'legacy' && line.indexOf('\\page') !== -1)
+ ||
+ (this.props.renderer == 'V3' && line.match(/^\\page$/))
+ ) r++;
return r;
}, 1);
},
@@ -120,6 +131,7 @@ const Editor = createClass({
//reset custom line styles
codeMirror.removeLineClass(lineNumber, 'background', 'pageLine');
codeMirror.removeLineClass(lineNumber, 'text');
+ codeMirror.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash');
// Styling for \page breaks
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
@@ -174,9 +186,76 @@ const Editor = createClass({
}
},
- brewJump : function(){
- const currentPage = this.getCurrentPage();
- window.location.hash = `p${currentPage}`;
+ brewJump : function(targetPage=this.getCurrentPage()){
+ if(!window) return;
+ // 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;
+ const interimPos = targetPos >= 0 ? -30 : 30;
+
+ 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);
+
+ // const hashPage = (page != 1) ? `p${page}` : '';
+ // window.location.hash = hashPage;
+ },
+
+ sourceJump : function(targetLine=null){
+ if(this.isText()) {
+ if(targetLine == null) {
+ targetLine = 0;
+
+ const pageCollection = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('page');
+ const brewRendererHeight = window.frames['BrewRenderer'].contentDocument.getElementsByClassName('brewRenderer').item(0).getBoundingClientRect().height;
+
+ let currentPage = 1;
+ for (const page of pageCollection) {
+ if(page.getBoundingClientRect().bottom > (brewRendererHeight / 2)) {
+ currentPage = parseInt(page.id.slice(1)) || 1;
+ break;
+ }
+ }
+
+ const textSplit = this.props.renderer == 'V3' ? /^\\page$/gm : /\\page/;
+ const textString = this.props.brew.text.split(textSplit).slice(0, currentPage-1).join(textSplit);
+ const textPosition = textString.length;
+ const lineCount = textString.match('\n') ? textString.slice(0, textPosition).split('\n').length : 0;
+
+ targetLine = lineCount - 1; //Scroll to `\page`, which is one line back.
+
+ let currentY = this.refs.codeEditor.codeMirror.getScrollInfo().top;
+ let targetY = this.refs.codeEditor.codeMirror.heightAtLine(targetLine, 'local', true);
+
+ //Scroll 1/10 of the way every 10ms until 1px off.
+ const incrementalScroll = setInterval(()=>{
+ currentY += (targetY - currentY) / 10;
+ this.refs.codeEditor.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.refs.codeEditor.codeMirror.heightAtLine(targetLine, 'local', true);
+
+ // End when close enough
+ if(Math.abs(targetY - currentY) < 1) {
+ this.refs.codeEditor.codeMirror.scrollTo(null, targetY); // Scroll any remaining difference
+ this.refs.codeEditor.setCursorPosition({ line: targetLine + 1, ch: 0 });
+ this.refs.codeEditor.codeMirror.addLineClass(targetLine + 1, 'wrap', 'sourceMoveFlash');
+ clearInterval(incrementalScroll);
+ }
+ }, 10);
+ }
+ }
},
//Called when there are changes to the editor's dimensions
diff --git a/scripts/project.json b/scripts/project.json
index 07acdda48..5a0289ad0 100644
--- a/scripts/project.json
+++ b/scripts/project.json
@@ -15,6 +15,7 @@
"codemirror/addon/fold/foldcode.js",
"codemirror/addon/fold/foldgutter.js",
"codemirror/addon/fold/xml-fold.js",
+ "codemirror/addon/scroll/scrollpastend.js",
"codemirror/addon/search/search.js",
"codemirror/addon/search/searchcursor.js",
"codemirror/addon/search/jump-to-line.js",
diff --git a/shared/naturalcrit/codeEditor/codeEditor.jsx b/shared/naturalcrit/codeEditor/codeEditor.jsx
index 42076ed76..6340a58fe 100644
--- a/shared/naturalcrit/codeEditor/codeEditor.jsx
+++ b/shared/naturalcrit/codeEditor/codeEditor.jsx
@@ -30,6 +30,8 @@ if(typeof navigator !== 'undefined'){
// require('codemirror/addon/edit/trailingspace.js');
//Active line highlighting
// require('codemirror/addon/selection/active-line.js');
+ //Scroll past last line
+ require('codemirror/addon/scroll/scrollpastend.js');
//Auto-closing
//XML code folding is a requirement of the auto-closing tag feature and is not enabled
require('codemirror/addon/fold/xml-fold.js');
@@ -98,6 +100,7 @@ const CodeEditor = createClass({
indentWithTabs : true,
tabSize : 2,
historyEventDelay : 250,
+ scrollPastEnd : true,
extraKeys : {
'Ctrl-B' : this.makeBold,
'Cmd-B' : this.makeBold,
diff --git a/shared/naturalcrit/codeEditor/codeEditor.less b/shared/naturalcrit/codeEditor/codeEditor.less
index bf36293ed..c929e0d21 100644
--- a/shared/naturalcrit/codeEditor/codeEditor.less
+++ b/shared/naturalcrit/codeEditor/codeEditor.less
@@ -3,6 +3,11 @@
@import (less) 'codemirror/addon/search/matchesonscrollbar.css';
@import (less) 'codemirror/addon/dialog/dialog.css';
+@keyframes sourceMoveAnimation {
+ 50% {background-color: red; color: white;}
+ 100% {background-color: unset; color: unset;}
+}
+
.codeEditor{
.CodeMirror-foldmarker {
font-family: inherit;
@@ -10,6 +15,11 @@
font-weight: 600;
}
+ .sourceMoveFlash .CodeMirror-line{
+ animation-name: sourceMoveAnimation;
+ animation-duration: 0.4s;
+ }
+
//.cm-tab {
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
//}
@@ -19,4 +29,4 @@
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right;
// }
//}
-}
\ No newline at end of file
+}
diff --git a/shared/naturalcrit/splitPane/splitPane.jsx b/shared/naturalcrit/splitPane/splitPane.jsx
index 4d138e30b..f7ad4bed4 100644
--- a/shared/naturalcrit/splitPane/splitPane.jsx
+++ b/shared/naturalcrit/splitPane/splitPane.jsx
@@ -17,7 +17,10 @@ const SplitPane = createClass({
return {
currentDividerPos : null,
windowWidth : 0,
- isDragging : false
+ isDragging : false,
+ moveSource : false,
+ moveBrew : false,
+ showMoveArrows : true
};
},
@@ -29,6 +32,11 @@ const SplitPane = createClass({
userSetDividerPos : dividerPos,
windowWidth : window.innerWidth
});
+ } else {
+ this.setState({
+ currentDividerPos : window.innerWidth / 2,
+ userSetDividerPos : window.innerWidth / 2
+ });
}
window.addEventListener('resize', this.handleWindowResize);
},
@@ -83,20 +91,58 @@ const SplitPane = createClass({
window.getSelection().removeAllRanges();
}
},
-*/
+ */
+
+ setMoveArrows : function(newState) {
+ if(this.state.showMoveArrows != newState){
+ this.setState({
+ showMoveArrows : newState
+ });
+ }
+ },
+
+ renderMoveArrows : function(){
+ if(this.state.showMoveArrows) {
+ return <>
+
this.setState({ moveSource: !this.state.moveSource })} >
+
+
+
this.setState({ moveBrew: !this.state.moveBrew })} >
+
+
+ >;
+ }
+ },
+
renderDivider : function(){
- return
-
-
-
-
+ return <>
+ {this.renderMoveArrows()}
+
-
;
+ >;
},
render : function(){
return
-
{this.props.children[0]}
+
+ {React.cloneElement(this.props.children[0], {
+ moveBrew : this.state.moveBrew,
+ moveSource : this.state.moveSource,
+ setMoveArrows : this.setMoveArrows
+ })}
+
{this.renderDivider()}
{this.props.children[1]}
;
diff --git a/shared/naturalcrit/splitPane/splitPane.less b/shared/naturalcrit/splitPane/splitPane.less
index 0e9095193..700d383d5 100644
--- a/shared/naturalcrit/splitPane/splitPane.less
+++ b/shared/naturalcrit/splitPane/splitPane.less
@@ -13,7 +13,7 @@
.divider{
display : table;
height : 100%;
- width : 12px;
+ width : 15px;
cursor : ew-resize;
background-color : #bbb;
text-align : center;
@@ -32,4 +32,28 @@
background-color: #999;
}
}
+ .arrow{
+ position : absolute;
+ width : 25px;
+ height : 25px;
+ border : 2px solid #bbb;
+ border-radius : 15px;
+ text-align : center;
+ font-size : 1.2em;
+ cursor : pointer;
+ background-color : #ddd;
+ z-index : 999;
+ box-shadow : 0 4px 5px #0000007f;
+ &.left{
+ .tooltipLeft('Jump to location in Editor');
+ top : 30px;
+ }
+ &.right{
+ .tooltipRight('Jump to location in Preview');
+ top : 60px;
+ }
+ &:hover{
+ background-color: #666;
+ }
+ }
}