1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-16 00:14:34 +02:00

Desktop: Fixes #6074: Scroll jumps when typing if heavy scripts or many large elements are used (#6383)

This commit is contained in:
Kenichi Kobayashi
2022-04-10 19:31:17 +09:00
committed by GitHub
parent e02422070e
commit f6e21e0180
7 changed files with 35 additions and 11 deletions

View File

@ -242,9 +242,9 @@ describe('MdToHtml', function() {
{
const input = '# Head\nFruits\n- Apple\n';
const result = await mdToHtml.render(input, null, { bodyOnly: true, mapsToLine: true });
expect(result.html.trim()).toBe('<h1 id="head" class="maps-to-line" source-line="0">Head</h1>\n' +
'<p class="maps-to-line" source-line="1">Fruits</p>\n' +
'<ul>\n<li class="maps-to-line" source-line="2">Apple</li>\n</ul>'
expect(result.html.trim()).toBe('<h1 id="head" class="maps-to-line" source-line="0" source-line-end="1">Head</h1>\n' +
'<p class="maps-to-line" source-line="1" source-line-end="2">Fruits</p>\n' +
'<ul>\n<li class="maps-to-line" source-line="2" source-line-end="3">Apple</li>\n</ul>'
);
}
}));

View File

@ -649,6 +649,11 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
// undefined. Maybe due to the error boundary that unmount components.
// Since we can't do much about it we just print an error.
if (webviewRef.current && webviewRef.current.wrappedInstance) {
// To keep consistency among CodeMirror's editing and scroll percents
// of Editor and Viewer.
const percent = getLineScrollPercent();
setEditorPercentScroll(percent);
options.percent = percent;
webviewRef.current.wrappedInstance.send('setHtml', renderedBody.html, options);
} else {
console.error('Trying to set HTML on an undefined webview ref');

View File

@ -100,6 +100,7 @@ export interface EditorProps {
function Editor(props: EditorProps, ref: any) {
const [editor, setEditor] = useState(null);
const editorParent = useRef(null);
const lastEditTime = useRef(NaN);
// Codemirror plugins add new commands to codemirror (or change it's behavior)
// This command adds the smartListIndent function which will be bound to tab
@ -120,6 +121,7 @@ function Editor(props: EditorProps, ref: any) {
const editor_change = useCallback((cm: any, change: any) => {
if (props.onChange && change.origin !== 'setValue') {
props.onChange(cm.getValue());
lastEditTime.current = Date.now();
}
}, [props.onChange]);
@ -154,7 +156,8 @@ function Editor(props: EditorProps, ref: any) {
}, [props.onResize]);
const editor_update = useCallback((cm: any) => {
props.onUpdate(cm);
const edited = Date.now() - lastEditTime.current <= 100;
props.onUpdate(cm, edited);
}, [props.onUpdate]);
useEffect(() => {

View File

@ -17,7 +17,7 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
const now = Date.now();
if (now >= ignoreNextEditorScrollTime_.current) ignoreNextEditorScrollEventCount_.current = 0;
if (ignoreNextEditorScrollEventCount_.current < 10) { // for safety
ignoreNextEditorScrollTime_.current = now + 200;
ignoreNextEditorScrollTime_.current = now + 1000;
ignoreNextEditorScrollEventCount_.current += 1;
}
};
@ -157,8 +157,9 @@ export default function useScrollHandler(editorRef: any, webviewRef: any, onScro
// When heights of lines are updated in CodeMirror, 'update' events are raised.
// If such an update event is raised, scroll position should be restored.
// See https://github.com/laurent22/joplin/issues/5981
const editor_update = useCallback((cm) => {
const editor_update = useCallback((cm: any, edited: boolean) => {
if (isCodeMirrorReady(cm)) {
if (edited) return;
const linesHeight = cm.heightAtLine(cm.lineCount()) - cm.heightAtLine(0);
if (lastLinesHeight_.current !== linesHeight) {
// To avoid cancelling intentional scroll position changes,

View File

@ -116,7 +116,7 @@
const now = Date.now();
if (now >= ignoreNextScrollTime_) ignoreNextScrollEventCount_ = 0;
if (ignoreNextScrollEventCount_ < 10) { // for safety
ignoreNextScrollTime_ = now + 200;
ignoreNextScrollTime_ = now + 1000;
ignoreNextScrollEventCount_ += 1;
}
};
@ -293,7 +293,7 @@
return;
}
}
if (!heightChanged) return;
if (!heightChanged && cause !== 'dom-changed') return;
const restoreAndRefresh = () => {
scrollmap.refresh();
restorePercentScroll();
@ -337,7 +337,11 @@
contentElement.innerHTML = html;
scrollmap.create(event.options.markupLineCount);
if (typeof event.options.percent !== 'number') {
restorePercentScroll(); // First, a quick treatment is applied.
} else {
setPercentScroll(event.options.percent);
}
addPluginAssets(event.options.pluginAssets);

View File

@ -45,7 +45,8 @@ scrollmap.get_ = () => {
// Each map entry is total-ordered.
let last = 0;
for (let i = 0; i < elems.length; i++) {
const top = elems[i].getBoundingClientRect().top - offset;
const rect = elems[i].getBoundingClientRect();
const top = rect.top - offset;
const line = Number(elems[i].getAttribute('source-line'));
const percent = Math.max(0, Math.min(1, top / height));
if (map.line[last] < line && map.percent[last] < percent) {
@ -53,12 +54,20 @@ scrollmap.get_ = () => {
map.percent.push(percent);
last += 1;
}
const bottom = rect.bottom - offset;
const lineEnd = Number(elems[i].getAttribute('source-line-end'));
const percentEnd = Math.max(0, Math.min(1, bottom / height));
if (map.line[last] < lineEnd && map.percent[last] < percentEnd) {
map.line.push(lineEnd);
map.percent.push(percentEnd);
last += 1;
}
}
const lineCount = scrollmap.lineCount_;
if (lineCount) {
map.lineCount = lineCount;
} else {
if (map.lineCount <= map.line[last]) map.lineCount = map.line[last] + 1;
if (map.lineCount < map.line[last]) map.lineCount = map.line[last];
}
if (map.percent[last] < 1) {
map.line.push(lineCount || 1e10);

View File

@ -22,8 +22,10 @@ export default {
markdownIt.renderer.rules[key] = (tokens: any[], idx: number, options: any, env: any, self: any) => {
if (!!tokens[idx].map && tokens[idx].level <= allowedLevel) {
const line = tokens[idx].map[0];
const lineEnd = tokens[idx].map[1];
tokens[idx].attrJoin('class', 'maps-to-line');
tokens[idx].attrSet('source-line', `${line}`);
tokens[idx].attrSet('source-line-end', `${lineEnd}`);
}
if (precedentRule) {
return precedentRule(tokens, idx, options, env, self);