/* DokuWiki MoaiEditor Scroll_to.js file Version : 0.5b (2026-05-08) Author : MoaiTools License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */ /* Scrolls the preview (keeping it synchronized with the editor). */ MoaiEditor.ScrollSync = class { constructor(outer) { // Variables this.disabled = false; // synchronization can be disabled temporarily when other processes want to take control } // ──────────────────────────────────── init() { this.scroller = new MoaiEditor.Scroller(this); } // ──────────────────────────────────── get isEnabled() { if (this.disabled || moaiEditor.buttons.autoscroll.mode == 'off' || !moaiEditor.layoutReady) return false; return true; } // ──────────────────────────────────── // This function is called whenever the user scrolls the editor side onScroll(event) { // Exit if disabled by the user, by other process, or if the editor is not ready if (!this.isEnabled) return; // Exit if the editor scroll has not changed (to avoid interfering right after another process has done scrolling) const scroll = moaiEditor.editor.current.scroll.top; const sameScroll = Math.abs(scroll - this.lastScroll) <= 2; if (sameScroll) return; // Synchronize the scroll of the preview const target = this.calcScroll().right; if (target !== null) { this.scroller.target = target; this.scroller.engaged = true; } // Remember last scroll position this.lastScroll = scroll; } // ──────────────────────────────────── calcScroll() { // There should be at least one match var matches = moaiEditor.matches.matches; if (matches.length == 0) return null; // Get the editor side scroll position const leftScroll = moaiEditor.editor.current.scroll.top; // Get the preview container top position relative to the screen this.container = moaiEditor.preview.container; this.containerTop = this.container.getBoundingClientRect().top; // Get the scroll points above and below our current scroll position var p1, p2, match; for (var i = 0; i < matches.length; i++) { if (matches[i].scroll === null) continue; match = matches[i]; if (match.scroll.top > leftScroll) { p1 = this.getPoint(i-1, 'bottom'); p2 = this.getPoint(i, 'top'); break; } if (match.scroll.bottom > leftScroll) { p1 = this.getPoint(i, 'top'); p2 = this.getPoint(i, 'bottom'); break; } } // Exit if no scroll points where found if (match === undefined) { return null; } // Build bottom scroll point if needed if (p1 === undefined) { p1 = this.getPoint(i-1, 'bottom'); p2 = this.getPoint(i, 'top'); } // Get the normalized scroll progress between both scrollpoints on the editor side const normalized = (leftScroll - p1.left) / (p2.left - p1.left); // Compute the corresponding scroll on the preview side const right = p1.right + normalized * (p2.right - p1.right); // Debug line const scroll = {p1:p1, p2:p2, right:right}; this.debugLine(scroll); // Return return scroll; } // ──────────────────────────────────── getPoint(i, side) { var type, left, right; var linenum = side.toUpperCase(); // Build a TOP scrollpoint if needed if (i < 0) { type = 'TOP'; left = 0; right = 0; // Build a BOTTOM scrollpoint if needed } else if (i >= moaiEditor.matches.matches.length) { type = 'BOTTOM'; left = moaiEditor.editor.current.scroll.max; right = this.container.scrollHeight; // Get the scrollpoint from the match } else { const match = moaiEditor.matches.matches[i]; type = match.type, left = match.scroll[side], right = match.handle.getBoundingClientRect()[side] - this.containerTop + this.container.scrollTop; linenum = match.startline; if (side == 'bottom') linenum = match.endline; } // Pack and return return {linenum:linenum, type:type, left:left, right:right, side:side}; } // ──────────────────────────────────── debugLine(scroll) { // Exit if debugline is not enabled const line = document.querySelector("#moai__debug div:nth-child(2)"); if (!line) return; // Debug line const p1 = scroll.p1; const p2 = scroll.p2; const txt1 = "<"+scroll.p1.type+"> "+p1.side+" L"+p1.linenum; const txt2 = "<"+scroll.p2.type+"> "+p2.side+" L"+p2.linenum; const text = txt1+" ⇔ "+txt2; line.textContent = text; } }; // End Class // ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ // ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆ MoaiEditor.Scroller = class { constructor(outer) { this.outer = outer; this.i = 0; this.target = null; this._engaged = false; this.onFrame(); } get engaged() { return this._engaged; } set engaged(boolean) { this._engaged = boolean; this.lastScroll = moaiEditor.preview.scroll.top; } onFrame() { requestAnimationFrame(this.onFrame.bind(this)); //this.i += 1; if (this.i % 3 !== 0) return; // Test other framerates const dt = Date.now() - this.lastTime; this.lastTime = Date.now(); if (!this.engaged || !this.outer.isEnabled || this.target === null) return; // Disengage if the user scrolled the preview const scroll = moaiEditor.preview.scroll.top; const userScrolled = Math.abs(scroll - this.lastScroll) > 2; if (userScrolled) { this.engaged = false; return; } // Scroll the preview const distance = this.target - scroll; const increment = 1*distance*dt/160; moaiEditor.preview.scroll.top = scroll + increment; this.lastScroll = scroll + increment; } }; // End Class