1/*  DokuWiki MoaiEditor Scroll_to.js file
2    Version : 0.5b (2026-05-08)
3    Author  : MoaiTools <info@moaitools.org>
4    License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */
5
6/* This class watches for changes lines in the textarea contents.
7
8   It detects deleted and inserted lines. An edited or replaced line is interpreted
9   as a deletion folowed by an insertion.
10
11   Handling textarea input events and toolbar button clicks might not be sufficient
12   to detect changes. Javascript can change the text without generating any events,
13   so unknown plugins might bypass this detection.
14
15   This is why we also check regularly if the text has changed even if no user input
16   event has been detected. This process is not very cpu intensive even when the page
17   is huge (it takes less than 1 ms on a 400 Kb page, which is larger than the largest
18   english wikipedia page).
19
20   We have a timer class to make these background checks more often if the user has
21   interacted recently and less often otherwise (to minimize CPU usage when there is
22   no interaction).
23*/
24MoaiEditor.WatchText = class {
25
26    constructor(outer) {
27
28        // Arguments
29        this.outer = outer;
30
31        // Objects
32        this.timer = new MoaiEditor.Timer();
33
34        // Intervals
35        this.interval = setInterval(this.onInterval.bind(this), 200);
36    }
37    // ────────────────────────────────────
38    onInterval() {
39
40        // Return if the parent is disabled
41        if (!this.outer.enabled)
42            return;
43
44        // Return if not enough time has elapsed
45        if (!this.timer.due())
46            return;
47
48        // Return if the editor is not enabled and ready
49        if (!moaiEditor.layoutReady)
50            return;
51
52        // Check for text change
53        this.checkTextChange();
54
55        // Optional debug actions
56        this.debug();
57
58    }
59    // ────────────────────────────────────
60    onInput() {
61        this.timer.resetPeriod();
62        this.timer.restart();
63        this.checkTextChange();
64    }
65    // ────────────────────────────────────
66    getTextLines() {
67        this.string = moaiEditor.layout.textarea.value;
68        return this.string.split("\n");
69    }
70    // ────────────────────────────────────
71    checkTextChange() {
72        // Init
73        var i;
74        const start = Date.now();
75        var previous = this.lines;
76        var current = this.getTextLines();
77
78        // Get shift (delta)
79        const shift = current.length - previous.length;
80
81        // Walk lines form top to bottom and detect the first change
82        for (i=0; i<previous.length; i++) {
83            if (current.length <= i)
84                break;
85            if (previous[i] != current[i])
86                break;
87        }
88        // Return if the text is the same
89        if (i == previous.length  &&  shift == 0) {
90            return null;
91        }
92        // First changed line (top to bottom)
93        const i0 = i;
94
95        // Walk lines form bottom to top and detect the first change
96        for (var k=0; k<previous.length; k++) {
97            i = previous.length - k - 1;
98            var j = current.length - k - 1;
99            if (j < 0)
100                break;
101            if (previous[i] != current[j])
102                break;
103        }
104        // First changed line (bottom to top)
105        const i1 = i;
106
107        // Calc some values
108        var removed = Math.max(0, i1 - i0 + 1);
109        if (previous.length == 0) {
110            removed = 0;
111        }
112        var inserted = removed + shift;
113        if (inserted < 0) {
114            removed -= inserted;
115            inserted = 0;
116        }
117        // Pack the result
118        const elapsed = Date.now() - start;
119        const data = { num:{keepfirst:i0, remove:removed, insert:inserted}, index:{keeplast: (i0+removed)}, i0:i0, i1:i1, shift:shift, elapsed:elapsed };
120
121        // Store current lines
122        this.lines = current;
123
124        // Start checking more frequently for changes
125        this.timer.resetPeriod();
126
127        // Enable "This page is asking you to confirm that you want to leave" popup from: lib/scripts/edit.js
128        window.textChanged = true;
129
130        // Style the Commit and Cancel buttons
131        moaiEditor.buttons.styleExitButtons();
132
133        // Inform parent object of the changes
134        this.outer.onTextChanged(data);
135    }
136    // ────────────────────────────────────
137    debug() {
138        // Placeholder
139    }
140}; // End Class
141
142// ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆
143// ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆
144
145MoaiEditor.Timer = class {
146
147    constructor () {
148        this.period = 200;
149        this.minPeriod = 200;
150        this.maxPeriod = 2000;
151        this.inc = 50;
152        this.last = Date.now();
153    }
154    restart () {
155        this.last = Date.now();
156        this.period += this.inc;
157        this.period = Math.min(this.period, this.maxPeriod);
158    }
159    due () {
160        this.debug();
161        const elapsed = Date.now() - this.last;
162        if (elapsed > this.period) {
163            this.restart();
164            return true;
165        }
166        return false;
167    }
168    set (period) {
169        this.period = Math.round(period);
170    }
171    setMin (period) {
172        this.minPeriod = Math.round(period);
173    }
174    setMax (period) {
175        this.minPeriod = Math.round(period);
176    }
177    setInc (inc) {
178        this.inc = inc;
179    }
180    resetPeriod () {
181        this.period = this.minPeriod;
182    }
183    debug () {
184        const line = document.querySelector("#moai__debug div:nth-child(1)");
185        if (!line) return;
186        const elapsed = '<span style="opacity:0.2">'+(Date.now()-this.last).toString().padStart(4," ")+'</span>';
187        const text = "TIMER: "+elapsed+" period:"+this.period+" min:"+this.minPeriod+" max:"+this.maxPeriod;
188        line.innerHTML = text;
189        line.title = 'Texarea ';
190    }
191}; // End Class
192
193
194