mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-05-09 20:38:40 +00:00
safe jumps 2
This commit is contained in:
@@ -118,16 +118,45 @@ const CodeEditor = forwardRef(
|
|||||||
const docsRef = useRef({});
|
const docsRef = useRef({});
|
||||||
const prevTabRef = useRef(tab);
|
const prevTabRef = useRef(tab);
|
||||||
|
|
||||||
|
// page map
|
||||||
|
const pageBreaksRef = useRef([]);
|
||||||
|
|
||||||
|
const recomputePages = (doc)=>{
|
||||||
|
const pages = [0];
|
||||||
|
const text = doc.toString();
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (const line of text.split('\n')) {
|
||||||
|
if(/^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m.test(line)) {
|
||||||
|
pages.push(offset);
|
||||||
|
}
|
||||||
|
offset += line.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageBreaksRef.current = pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findPageFromPos = (pos)=>{
|
||||||
|
const pages = pageBreaksRef.current;
|
||||||
|
let page = 1;
|
||||||
|
|
||||||
|
for (let i = 1; i < pages.length; i++) {
|
||||||
|
if(pos >= pages[i]) page = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return page;
|
||||||
|
};
|
||||||
|
|
||||||
const createExtensions = ({ onChange, language, editorTheme })=>{
|
const createExtensions = ({ onChange, language, editorTheme })=>{
|
||||||
const setEventListeners = EditorView.updateListener.of((update)=>{
|
const setEventListeners = EditorView.updateListener.of((update)=>{
|
||||||
if(update.docChanged) {
|
if(update.docChanged) {
|
||||||
|
recomputePages(update.state.doc); // CHANGED (added)
|
||||||
onChange(update.state.doc.toString());
|
onChange(update.state.doc.toString());
|
||||||
}
|
}
|
||||||
if(update.selectionSet) {
|
if(update.selectionSet) {
|
||||||
const pos = update.state.selection.main.head;
|
const pos = update.state.selection.main.head;
|
||||||
const line = update.state.doc.lineAt(pos).number;
|
const page = findPageFromPos(pos);
|
||||||
|
onCursorChange(page);
|
||||||
onCursorChange(line);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -184,6 +213,8 @@ const CodeEditor = forwardRef(
|
|||||||
extensions : createExtensions({ onChange, language, editorTheme }),
|
extensions : createExtensions({ onChange, language, editorTheme }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
recomputePages(state.doc);
|
||||||
|
|
||||||
viewRef.current = new EditorView({
|
viewRef.current = new EditorView({
|
||||||
state,
|
state,
|
||||||
parent : editorRef.current,
|
parent : editorRef.current,
|
||||||
@@ -198,14 +229,12 @@ const CodeEditor = forwardRef(
|
|||||||
|
|
||||||
ticking = true;
|
ticking = true;
|
||||||
requestAnimationFrame(()=>{
|
requestAnimationFrame(()=>{
|
||||||
const view = viewRef.current;
|
|
||||||
if(!view?.scrollDOM) return;
|
|
||||||
|
|
||||||
const top = view.scrollDOM.scrollTop;
|
const top = view.scrollDOM.scrollTop;
|
||||||
const block = view.lineBlockAtHeight(top);
|
const block = view.lineBlockAtHeight(top);
|
||||||
const line = view.state.doc.lineAt(block.from).number;
|
|
||||||
|
|
||||||
onViewChange(line);
|
const page = findPageFromPos(block.from); // CHANGED
|
||||||
|
onViewChange(page);
|
||||||
|
|
||||||
ticking = false;
|
ticking = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -320,14 +349,24 @@ const CodeEditor = forwardRef(
|
|||||||
viewRef.current.scrollDOM.scrollTo({ top: y });
|
viewRef.current.scrollDOM.scrollTo({ top: y });
|
||||||
},
|
},
|
||||||
|
|
||||||
getLineTop : (lineNumber)=>{
|
scrollToPage : (pageNumber, smooth = true)=>{
|
||||||
|
const view = viewRef.current;
|
||||||
|
if(!view) return;
|
||||||
|
|
||||||
|
const pos = pageBreaksRef.current[pageNumber - 1] ?? 0;
|
||||||
|
|
||||||
|
view.dispatch({
|
||||||
|
effects : EditorView.scrollIntoView(pos, { y: 'start' })
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
getPagePos : (pageNumber)=>{
|
||||||
const view = viewRef.current;
|
const view = viewRef.current;
|
||||||
if(!view) return 0;
|
if(!view) return 0;
|
||||||
|
|
||||||
const line = view.state.doc.line(lineNumber);
|
const pos = pageBreaksRef.current[pageNumber - 1] ?? 0;
|
||||||
return view.coordsAtPos(line.from)?.top ?? 0;
|
return pos;
|
||||||
},
|
},
|
||||||
|
|
||||||
setCursorToLine : (lineNumber)=>{
|
setCursorToLine : (lineNumber)=>{
|
||||||
const view = viewRef.current;
|
const view = viewRef.current;
|
||||||
const line = view.state.doc.line(lineNumber);
|
const line = view.state.doc.line(lineNumber);
|
||||||
|
|||||||
@@ -141,18 +141,12 @@ const Editor = createReactClass({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCurrentCursorPage : function(lineNumber) {
|
updateCurrentCursorPage : function(pageNumber) {
|
||||||
const lines = this.props.brew.text.split('\n').slice(0, lineNumber);
|
this.props.onCursorPageChange(pageNumber);
|
||||||
const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
|
|
||||||
const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1);
|
|
||||||
this.props.onCursorPageChange(currentPage);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCurrentViewPage : function(topLine) {
|
updateCurrentViewPage : function(pageNumber) {
|
||||||
const lines = this.props.brew.text.split('\n').slice(0, topLine);
|
this.props.onViewPageChange(pageNumber);
|
||||||
const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
|
|
||||||
const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1);
|
|
||||||
this.props.onViewPageChange(currentPage);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleInject : function(injectText){
|
handleInject : function(injectText){
|
||||||
@@ -214,43 +208,14 @@ const Editor = createReactClass({
|
|||||||
if(!this.isText() || isJumping)
|
if(!this.isText() || isJumping)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const textSplit = this.props.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/;
|
|
||||||
const textString = this.props.brew.text.split(textSplit).slice(0, targetPage-1).join(textSplit);
|
|
||||||
const targetLine = textString.match('\n') ? textString.split('\n').length : 1;
|
|
||||||
|
|
||||||
const editor = this.codeEditor.current;
|
const editor = this.codeEditor.current;
|
||||||
|
if(!editor) return;
|
||||||
|
|
||||||
let currentY = editor.getScrollTop();
|
editor.scrollToPage(targetPage);
|
||||||
const targetY = editor.getLineTop(targetLine);
|
const pos = editor.getPagePos(targetPage);
|
||||||
|
editor.setCursorToPos?.(pos);
|
||||||
let scrollingTimeout;
|
|
||||||
const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times
|
|
||||||
clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs
|
|
||||||
scrollingTimeout = setTimeout(()=>{
|
|
||||||
isJumping = false;
|
|
||||||
}, 150); // If 150 ms pass without a scroll event, assume scrolling is done
|
|
||||||
};
|
|
||||||
|
|
||||||
isJumping = true;
|
|
||||||
checkIfScrollComplete();
|
|
||||||
|
|
||||||
if(smooth) {
|
|
||||||
//Scroll 1/10 of the way every 10ms until 1px off.
|
|
||||||
const incrementalScroll = setInterval(()=>{
|
|
||||||
currentY += (targetY - currentY) / 10;
|
|
||||||
editor.scrollToY(currentY);
|
|
||||||
|
|
||||||
if(Math.abs(targetY - currentY) < 1) {
|
|
||||||
editor.scrollToY(targetY);
|
|
||||||
editor.setCursorToLine(targetLine);
|
|
||||||
clearInterval(incrementalScroll);
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
} else {
|
|
||||||
editor.scrollToY(targetY);
|
|
||||||
editor.setCursorToLine(targetLine);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
//Called when there are changes to the editor's dimensions
|
//Called when there are changes to the editor's dimensions
|
||||||
update : function(){},
|
update : function(){},
|
||||||
|
|
||||||
@@ -276,8 +241,8 @@ const Editor = createReactClass({
|
|||||||
view={this.state.view}
|
view={this.state.view}
|
||||||
value={this.props.brew.text}
|
value={this.props.brew.text}
|
||||||
onChange={this.props.onBrewChange('text')}
|
onChange={this.props.onBrewChange('text')}
|
||||||
onCursorChange={(line)=>this.updateCurrentCursorPage(line)}
|
onCursorChange={(page)=>this.updateCurrentCursorPage(page)}
|
||||||
onViewChange={(line)=>this.updateCurrentViewPage(line)}
|
onViewChange={(page)=>this.updateCurrentViewPage(page)}
|
||||||
editorTheme={this.state.editorTheme}
|
editorTheme={this.state.editorTheme}
|
||||||
renderer={this.props.brew.renderer}
|
renderer={this.props.brew.renderer}
|
||||||
rerenderParent={this.rerenderParent}
|
rerenderParent={this.rerenderParent}
|
||||||
@@ -348,9 +313,9 @@ const Editor = createReactClass({
|
|||||||
return this.codeEditor.current?.undo();
|
return this.codeEditor.current?.undo();
|
||||||
},
|
},
|
||||||
|
|
||||||
foldCode: function() {
|
foldCode: function() {
|
||||||
return this.codeEditor.current?.foldAll();
|
return this.codeEditor.current?.foldAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
unfoldCode : function() {
|
unfoldCode : function() {
|
||||||
return this.codeEditor.current?.unfoldAll();
|
return this.codeEditor.current?.unfoldAll();
|
||||||
|
|||||||
Reference in New Issue
Block a user