mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-07 07:42:39 +00:00
Merge pull request #2162 from G-Ambatte/fixBrewJump
Brew & Source Navigation
This commit is contained in:
@@ -188,7 +188,7 @@ const BrewRenderer = createClass({
|
|||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
<Frame initialContent={this.state.initialContent}
|
<Frame id='BrewRenderer' initialContent={this.state.initialContent}
|
||||||
style={{ width: '100%', height: '100%', visibility: this.state.visibility }}
|
style={{ width: '100%', height: '100%', visibility: this.state.visibility }}
|
||||||
contentDidMount={this.frameDidMount}>
|
contentDidMount={this.frameDidMount}>
|
||||||
<div className={'brewRenderer'}
|
<div className={'brewRenderer'}
|
||||||
|
|||||||
@@ -61,8 +61,14 @@ const Editor = createClass({
|
|||||||
window.removeEventListener('resize', this.updateEditorSize);
|
window.removeEventListener('resize', this.updateEditorSize);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate : function() {
|
componentDidUpdate : function(prevProps, prevState, snapshot) {
|
||||||
this.highlightCustomMarkdown();
|
this.highlightCustomMarkdown();
|
||||||
|
if(prevProps.moveBrew !== this.props.moveBrew) {
|
||||||
|
this.brewJump();
|
||||||
|
};
|
||||||
|
if(prevProps.moveSource !== this.props.moveSource) {
|
||||||
|
this.sourceJump();
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
updateEditorSize : function() {
|
updateEditorSize : function() {
|
||||||
@@ -90,15 +96,20 @@ const Editor = createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleViewChange : function(newView){
|
handleViewChange : function(newView){
|
||||||
|
this.props.setMoveArrows(newView === 'text');
|
||||||
this.setState({
|
this.setState({
|
||||||
view : newView
|
view : newView
|
||||||
}, this.updateEditorSize); //TODO: not sure if updateeditorsize needed
|
}, this.updateEditorSize); //TODO: not sure if updateeditorsize needed
|
||||||
},
|
},
|
||||||
|
|
||||||
getCurrentPage : function(){
|
getCurrentPage : function(){
|
||||||
const lines = this.props.brew.text.split('\n').slice(0, this.cursorPosition.line + 1);
|
const lines = this.props.brew.text.split('\n').slice(0, this.refs.codeEditor.getCursorPosition().line + 1);
|
||||||
return _.reduce(lines, (r, line)=>{
|
return _.reduce(lines, (r, line)=>{
|
||||||
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;
|
return r;
|
||||||
}, 1);
|
}, 1);
|
||||||
},
|
},
|
||||||
@@ -120,6 +131,7 @@ const Editor = createClass({
|
|||||||
//reset custom line styles
|
//reset custom line styles
|
||||||
codeMirror.removeLineClass(lineNumber, 'background', 'pageLine');
|
codeMirror.removeLineClass(lineNumber, 'background', 'pageLine');
|
||||||
codeMirror.removeLineClass(lineNumber, 'text');
|
codeMirror.removeLineClass(lineNumber, 'text');
|
||||||
|
codeMirror.removeLineClass(lineNumber, 'wrap', 'sourceMoveFlash');
|
||||||
|
|
||||||
// Styling for \page breaks
|
// Styling for \page breaks
|
||||||
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
|
if((this.props.renderer == 'legacy' && line.includes('\\page')) ||
|
||||||
@@ -174,9 +186,76 @@ const Editor = createClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
brewJump : function(){
|
brewJump : function(targetPage=this.getCurrentPage()){
|
||||||
const currentPage = this.getCurrentPage();
|
if(!window) return;
|
||||||
window.location.hash = `p${currentPage}`;
|
// 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
|
//Called when there are changes to the editor's dimensions
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"codemirror/addon/fold/foldcode.js",
|
"codemirror/addon/fold/foldcode.js",
|
||||||
"codemirror/addon/fold/foldgutter.js",
|
"codemirror/addon/fold/foldgutter.js",
|
||||||
"codemirror/addon/fold/xml-fold.js",
|
"codemirror/addon/fold/xml-fold.js",
|
||||||
|
"codemirror/addon/scroll/scrollpastend.js",
|
||||||
"codemirror/addon/search/search.js",
|
"codemirror/addon/search/search.js",
|
||||||
"codemirror/addon/search/searchcursor.js",
|
"codemirror/addon/search/searchcursor.js",
|
||||||
"codemirror/addon/search/jump-to-line.js",
|
"codemirror/addon/search/jump-to-line.js",
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ if(typeof navigator !== 'undefined'){
|
|||||||
// require('codemirror/addon/edit/trailingspace.js');
|
// require('codemirror/addon/edit/trailingspace.js');
|
||||||
//Active line highlighting
|
//Active line highlighting
|
||||||
// require('codemirror/addon/selection/active-line.js');
|
// require('codemirror/addon/selection/active-line.js');
|
||||||
|
//Scroll past last line
|
||||||
|
require('codemirror/addon/scroll/scrollpastend.js');
|
||||||
//Auto-closing
|
//Auto-closing
|
||||||
//XML code folding is a requirement of the auto-closing tag feature and is not enabled
|
//XML code folding is a requirement of the auto-closing tag feature and is not enabled
|
||||||
require('codemirror/addon/fold/xml-fold.js');
|
require('codemirror/addon/fold/xml-fold.js');
|
||||||
@@ -98,6 +100,7 @@ const CodeEditor = createClass({
|
|||||||
indentWithTabs : true,
|
indentWithTabs : true,
|
||||||
tabSize : 2,
|
tabSize : 2,
|
||||||
historyEventDelay : 250,
|
historyEventDelay : 250,
|
||||||
|
scrollPastEnd : true,
|
||||||
extraKeys : {
|
extraKeys : {
|
||||||
'Ctrl-B' : this.makeBold,
|
'Ctrl-B' : this.makeBold,
|
||||||
'Cmd-B' : this.makeBold,
|
'Cmd-B' : this.makeBold,
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
@import (less) 'codemirror/addon/search/matchesonscrollbar.css';
|
@import (less) 'codemirror/addon/search/matchesonscrollbar.css';
|
||||||
@import (less) 'codemirror/addon/dialog/dialog.css';
|
@import (less) 'codemirror/addon/dialog/dialog.css';
|
||||||
|
|
||||||
|
@keyframes sourceMoveAnimation {
|
||||||
|
50% {background-color: red; color: white;}
|
||||||
|
100% {background-color: unset; color: unset;}
|
||||||
|
}
|
||||||
|
|
||||||
.codeEditor{
|
.codeEditor{
|
||||||
.CodeMirror-foldmarker {
|
.CodeMirror-foldmarker {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
@@ -10,6 +15,11 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sourceMoveFlash .CodeMirror-line{
|
||||||
|
animation-name: sourceMoveAnimation;
|
||||||
|
animation-duration: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
//.cm-tab {
|
//.cm-tab {
|
||||||
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAQAAACOs/baAAAARUlEQVR4nGJgIAG8JkXxUAcCtDWemcGR1lY4MvgzCEKY7jSBjgxBDAG09UEQzAe0AMwMHrSOAwEGRtpaMIwAAAAA//8DAG4ID9EKs6YqAAAAAElFTkSuQmCC) no-repeat right;
|
// 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;
|
// background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAQAgMAAABW5NbuAAAACVBMVEVHcEwAAAAAAAAWawmTAAAAA3RSTlMAPBJ6PMxpAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAFUlEQVQI12NgwACcCQysASAEZGAAACMuAX06aCQUAAAAAElFTkSuQmCC) no-repeat right;
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ const SplitPane = createClass({
|
|||||||
return {
|
return {
|
||||||
currentDividerPos : null,
|
currentDividerPos : null,
|
||||||
windowWidth : 0,
|
windowWidth : 0,
|
||||||
isDragging : false
|
isDragging : false,
|
||||||
|
moveSource : false,
|
||||||
|
moveBrew : false,
|
||||||
|
showMoveArrows : true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -29,6 +32,11 @@ const SplitPane = createClass({
|
|||||||
userSetDividerPos : dividerPos,
|
userSetDividerPos : dividerPos,
|
||||||
windowWidth : window.innerWidth
|
windowWidth : window.innerWidth
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
currentDividerPos : window.innerWidth / 2,
|
||||||
|
userSetDividerPos : window.innerWidth / 2
|
||||||
|
});
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', this.handleWindowResize);
|
window.addEventListener('resize', this.handleWindowResize);
|
||||||
},
|
},
|
||||||
@@ -83,20 +91,58 @@ const SplitPane = createClass({
|
|||||||
window.getSelection().removeAllRanges();
|
window.getSelection().removeAllRanges();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
setMoveArrows : function(newState) {
|
||||||
|
if(this.state.showMoveArrows != newState){
|
||||||
|
this.setState({
|
||||||
|
showMoveArrows : newState
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMoveArrows : function(){
|
||||||
|
if(this.state.showMoveArrows) {
|
||||||
|
return <>
|
||||||
|
<div className='arrow left'
|
||||||
|
style={{ left: this.state.currentDividerPos-4 }}
|
||||||
|
onClick={()=>this.setState({ moveSource: !this.state.moveSource })} >
|
||||||
|
<i className='fas fa-arrow-left' />
|
||||||
|
</div>
|
||||||
|
<div className='arrow right'
|
||||||
|
style={{ left: this.state.currentDividerPos-4 }}
|
||||||
|
onClick={()=>this.setState({ moveBrew: !this.state.moveBrew })} >
|
||||||
|
<i className='fas fa-arrow-right' />
|
||||||
|
</div>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
renderDivider : function(){
|
renderDivider : function(){
|
||||||
return <div className='divider' onMouseDown={this.handleDown} >
|
return <>
|
||||||
<div className='dots'>
|
{this.renderMoveArrows()}
|
||||||
<i className='fas fa-circle' />
|
<div className='divider' onMouseDown={this.handleDown} >
|
||||||
<i className='fas fa-circle' />
|
<div className='dots'>
|
||||||
<i className='fas fa-circle' />
|
<i className='fas fa-circle' />
|
||||||
|
<i className='fas fa-circle' />
|
||||||
|
<i className='fas fa-circle' />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</>;
|
||||||
},
|
},
|
||||||
|
|
||||||
render : function(){
|
render : function(){
|
||||||
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
return <div className='splitPane' onMouseMove={this.handleMove} onMouseUp={this.handleUp}>
|
||||||
<Pane ref='pane1' width={this.state.currentDividerPos}>{this.props.children[0]}</Pane>
|
<Pane
|
||||||
|
ref='pane1'
|
||||||
|
width={this.state.currentDividerPos}
|
||||||
|
>
|
||||||
|
{React.cloneElement(this.props.children[0], {
|
||||||
|
moveBrew : this.state.moveBrew,
|
||||||
|
moveSource : this.state.moveSource,
|
||||||
|
setMoveArrows : this.setMoveArrows
|
||||||
|
})}
|
||||||
|
</Pane>
|
||||||
{this.renderDivider()}
|
{this.renderDivider()}
|
||||||
<Pane ref='pane2' isDragging={this.state.isDragging}>{this.props.children[1]}</Pane>
|
<Pane ref='pane2' isDragging={this.state.isDragging}>{this.props.children[1]}</Pane>
|
||||||
</div>;
|
</div>;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
.divider{
|
.divider{
|
||||||
display : table;
|
display : table;
|
||||||
height : 100%;
|
height : 100%;
|
||||||
width : 12px;
|
width : 15px;
|
||||||
cursor : ew-resize;
|
cursor : ew-resize;
|
||||||
background-color : #bbb;
|
background-color : #bbb;
|
||||||
text-align : center;
|
text-align : center;
|
||||||
@@ -32,4 +32,28 @@
|
|||||||
background-color: #999;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user