PageSourceSearch

https://latex2web.app/assets/controllers/advancedEdit_controller-xXlk4W3.js

js latex2web.app collected 2026-10-04 14:35:06 UTC 21,546 bytes, 551 lines download raw bytes

1import {Controller} from '@hotwired/stimulus';
2import Cookies from "../vendor/js.cookie/js.cookie.index.js";
3import axios from "../vendor/axios/axios.index.js";
4import LatexDOM from "../lib/latexDOM.js";
5import notifications from "../lib/notifications.js";
6import Log from "../lib/log.js";
7
8/*
9 * This is an example Stimulus controller!
10 *
11 * Any element with a data-controller="hello" attribute will cause
12 * this controller to be executed. The name "hello" comes from the filename:
13 * hello_controller.js -> "hello"
14 *
15 * Delete this file or adapt it for your use!
16 */
17export default class advancedEditor extends Controller {
18    static values = {
19        render: String,
20        revert: String,
21        save: String,
22        id: String,
23        language: String,
24        searchMode: { type: String, default: 'caseSensitive' },
25    }
26    static targets = ['search', 'replace', 'searchModeIcon'];
27
28    static searchModes = {
29        caseSensitive:   { icon: 'fa-magnifying-glass',   caseSensitive: true,  regExp: false, wholeWord: false },
30        caseInsensitive: { icon: 'fa-font',                caseSensitive: false, regExp: false, wholeWord: false },
31        regex:           { icon: 'fa-asterisk',            caseSensitive: false, regExp: true,  wholeWord: false },
32        wholeWord:       { icon: 'fa-spell-check',         caseSensitive: true,  regExp: false, wholeWord: true  },
33    };
34
35    setSearchMode(e) {
36        const mode = e.params.mode;
37        if (!advancedEditor.searchModes[mode]) return;
38        this.searchModeValue = mode;
39        const { icon } = advancedEditor.searchModes[mode];
40        this.searchModeIconTarget.className = `fa-solid ${icon}`;
41    }
42
43    _findOptions() {
44        const { caseSensitive, regExp, wholeWord } = advancedEditor.searchModes[this.searchModeValue] ?? advancedEditor.searchModes.caseSensitive;
45        return { wrap: true, caseSensitive, regExp, wholeWord };
46    }
47
48    search(e) {
49        let pattern = this.searchTarget.value;
50        let codeEditor = ace.edit("editorCode");
51        codeEditor.focus();
52        codeEditor.find(pattern, this._findOptions());
53    }
54
55    replace(e) {
56        let pattern = this.searchTarget.value;
57        let replace = this.replaceTarget.value;
58        let codeEditor = ace.edit("editorCode");
59        codeEditor.focus();
60        codeEditor.find(pattern, this._findOptions());
61        codeEditor.replace(replace);
62    }
63
64    close(e) {
65        let elementId = this.idValue;
66        let appendId = elementId+'/append';
67        let element = document.getElementById(appendId);
68        if (element !== null) {
69            element.remove();
70            Cookies.set('LaTeX2Web_Editor', 'inactive', {path: ''});
71            document.dispatchEvent(new CustomEvent('editor:deactivated'));
72        }
73        let id = this.idValue;
74        let objectType = LatexDOM.objectType(id);
75        if (LatexDOM.sectionKeywords && !LatexDOM.sectionKeywords.includes(objectType)) {
76            id = LatexDOM.closestParent(id);
77        }
78        let idArray = id.split('/');
79        let tail = idArray.pop();
80        let tailArray = tail.split('-');
81        if (tailArray[0]==='section'){
82            id+='/div';
83        }
84        element = document.getElementById(id);
85        if (element !== null) {
86            element.style.backgroundColor = 'white';
87            element.classList.remove('editing');
88            if (element.tagName === 'PRE') {
89                hljs.highlightAll();
90            }
91        }
92        if (LatexDOM.sectionKeywords && LatexDOM.sectionKeywords.includes(objectType)){
93            let escapedId = elementId.replace(/\//g, '\\/');
94            let editHeaders = document.querySelectorAll('[id="' + elementId + '"]');
95            let m = editHeaders.length;
96            var headerElement = null;
97            for (let i = 0; i < m; i++) {
98                headerElement = editHeaders[i];
99                let headerClasses = headerElement.classList;
100                if (headerClasses.contains('editing')){
101                    headerElement.classList.remove('editing');
102                } else {
103                    headerElement.remove();
104                }
105            }
106        }
107        let editingObjects = document.getElementsByClassName('editing');
108        let n = editingObjects.length;
109        for (let i = 0; i < n; i++) {
110            editingObjects[i].remove();
111        }
112        if (LatexDOM.sectionKeywords && LatexDOM.sectionKeywords.includes(objectType)){
113            sessionStorage.setItem('elementId', elementId);
114            window.location.reload();
115        }
116        if (element) {
117            element.scrollIntoView({behavior: "smooth"});
118        }
119    }
120
121    revert(e) {
122        let elementId = this.idValue;
123        let objectType = LatexDOM.objectType(elementId);
124        let destinationId = elementId;
125        if (LatexDOM.sectionKeywords && !LatexDOM.sectionKeywords.includes(objectType)) {
126            destinationId = LatexDOM.closestParent(elementId);
127        }
128        let controllerRoute = this.revertValue;
129        let language = this.languageValue;
130        var math = document.getElementById(elementId);
131        axios.post(controllerRoute, {
132            'elementId': destinationId,
133            'language': language,
134        })
135            .then(function (response) {
136                Log.log(response.data);
137                let objectType = LatexDOM.objectType(elementId);
138                if (LatexDOM.sectionKeywords && !LatexDOM.sectionKeywords.includes(objectType))
139                {
140                    math.innerHTML = response.data.html;
141                    let children = math.children;
142                    let n = children.length;
143                    for (let i = 0; i < n; i++) {
144                        let child = children[i];
145                        child.style.display = 'block';
146                    }
147                    var content = math;
148                } else {
149                    let temp = document.createElement('DIV');
150                    temp.innerHTML = response.data.html;
151                    let children = temp.children;
152                    children[0].classList.add('editing');
153                    math.replaceWith(children[0]);
154                    var content = document.getElementById(elementId+'/div');
155                    content.innerHTML = children[0].innerHTML;
156                    content.classList.add('editing');
157                }
158                hljs.highlightAll();
159                let latex = response.data.latex;
160                let codeEditor = ace.edit("editorCode");
161                codeEditor.setValue(latex,-1);
162                new Notify({
163                    status: 'success',
164                    title: 'Document reverted to saved version',
165                    customIcon: '<i class="fa-solid fa-rotate-left"></i></i>',
166                })
167                let target = document.getElementById(destinationId);
168                if (target) {
169                    target.scrollIntoView({behavior: "smooth"});
170                }
171                let promise = Promise.resolve();
172
173                function typeset(code) {
174                    promise = promise.then(() => MathJax.typesetPromise(code()))
175                        .catch((err) => console.log('Typeset failed: ' + err.message));
176                    return promise;
177                }
178
179                typeset(() => {
180                    //content.innerHTML = response.data.html;
181                    return [content];
182                });
183            })
184            .catch()
185
186    }
187
188    save(e) {
189        var elementId = this.idValue;
190        var destinationId = elementId;
191        let objectType = LatexDOM.objectType(elementId);
192        if (LatexDOM.sectionKeywords && !LatexDOM.sectionKeywords.includes(objectType)) {
193            destinationId = LatexDOM.closestParent(elementId);
194        }
195        let controllerRoute = this.saveValue;
196        let language = this.languageValue;
197        let params = e.params;
198        let action = params.action;
199        let codeEditor = ace.edit("editorCode");
200        let content = codeEditor.getValue();
201        new Notify({
202            status: 'info',
203            title: 'Starting save operation...',
204            autoclose: true,
205            customIcon: '<i class="fa-solid fa-sync fa-spin"></i>',
206        });
207        axios.post(controllerRoute, {
208            'elementId': destinationId,
209            'latex': content,
210            'language': language,
211            'action': action,
212        })
213            .then(function (response) {
214                Log.log(response.data);
215                if (response.data.reload) {
216                    if(response.data.reload === true){
217                        let id = response.data.id;
218                        let message = response.data.message;
219                        message = JSON.stringify(message);
220                        sessionStorage.setItem('messages', message);
221                        let element = document.getElementById(id);
222                        if (element !== null) {
223                            sessionStorage.setItem('elementId', id);
224                        }
225                        window.location.reload();
226                    }
227                } else {
228                    let type = LatexDOM.objectType(destinationId);
229                    let subtypes = advancedEditor.subTypes;
230                    let messages = response.data.message;
231                    if (subtypes.includes(type)) {
232                        sessionStorage.setItem('elementId', elementId);
233                        message = JSON.stringify(message);
234                        sessionStorage.setItem('messages', messages);
235                        window.location.reload();
236                    } else {
237                        notifications.notify(messages);
238                    }
239                    let target = document.getElementById(destinationId);
240                    if (target) {
241                        target.scrollIntoView({behavior: "smooth"});
242                    }
243                }
244                })
245            .catch()
246    }
247
248    render(e) {
249        let elementId = this.idValue;
250        let objectType = LatexDOM.objectType(elementId);
251        if (objectType === 'chunk'){
252            new Notify({
253                title: 'Chunks cannot be rendered',
254                text: 'You can render the chunk container instead.',
255                status: 'warning',
256            })
257            return;
258        }
259        var destinationId = elementId;
260        if (LatexDOM.sectionKeywords && !LatexDOM.sectionKeywords.includes(objectType)) {
261            destinationId = LatexDOM.closestParent(elementId);
262        }
263        var math = document.getElementById(elementId);
264        let controllerRoute = this.renderValue;
265        let language = this.languageValue;
266        let codeEditor = ace.edit("editorCode");
267        let content = codeEditor.getValue();
268        axios.post(controllerRoute, {
269            'elementId': destinationId,
270            'latex': content,
271            'language': language,
272        })
273            .then(function (response) {
274                Log.log(response.data);
275                let objectType = LatexDOM.objectType(elementId);
276                if (LatexDOM.sectionKeywords && !LatexDOM.sectionKeywords.includes(objectType)) {
277                    math.innerHTML = response.data.html;
278                    let children = math.children;
279                    let n = children.length;
280                    for (let i = 0; i < n; i++) {
281                        let child = children[i];
282                        child.style.display = 'block';
283                    }
284                    var content = math;
285                } else {
286                    let temp = document.createElement('DIV');
287                    temp.innerHTML = response.data.html;
288                    let headerId = math.id;
289                    let escapedId = headerId.replace(/\//g, '\\/');
290                    let editHeaders = document.querySelectorAll('[id="' + headerId + '"]');
291
292                    let m = editHeaders.length;
293                    let found = false;
294                    var headerElement = null;
295                    for (let i = 0; i < m; i++) {
296                        headerElement = editHeaders[i];
297                        let headerClasses = headerElement.classList;
298                        if (headerClasses.contains('editing')){
299                            found = true;
300                            break;
301                        }
302                    }
303                    if (found){
304                        math = headerElement;
305                    }
306                    let children = temp.children;
307                    math.innerHTML = children[0].innerHTML;
308                    var content = document.getElementById(elementId + '/div');
309                    content.innerHTML = children[1].innerHTML;
310                }
311                hljs.highlightAll();
312                let status = response.data.status;
313                let title = 'Render successful'
314                let icon = '<i class="fa-solid fa-pen-swirl"></i>';
315                let autoclose = true;
316                let text = '';
317                if (status !== 'success'){
318                    title = 'Render failed';
319                    autoclose = false;
320                    icon = '<i class="fa-solid fa-bomb"></i>'
321                    text = response.data.message;
322                }
323                new Notify({
324                    status: status,
325                    title: title,
326                    autoclose: autoclose,
327                    text: text,
328                    customIcon: icon,
329                })
330                let target = document.getElementById(destinationId);
331                if (target) {
332                    target.scrollIntoView({behavior: "smooth"});
333                }
334                let promise = Promise.resolve();
335
336                function typeset(code) {
337                    promise = promise.then(() => MathJax.typesetPromise(code()))
338                        .catch((err) =>
339                            new Notify({
340                                status: 'error',
341                                title: 'Typeset failed',
342                                text: err.message,
343                            }))
344                    return promise;
345                }
346
347                typeset(() => {
348                    return [content];
349                });
350            })
351            .catch(function (error) {
352                new Notify({
353                    status: 'error',
354                    title: error.response.data.message,
355                    autoclose: false,
356                    customIcon: '<i class="fa-solid fa-bomb"></i>',
357                })
358            })
359    }
360    replaceAll(e) {
361        let pattern = this.searchTarget.value;
362        let replace = this.replaceTarget.value;
363        let codeEditor = ace.edit("editorCode");
364        codeEditor.focus();
365        let selectedText = codeEditor.getSelectedText();
366        if (selectedText !== '') {
367            const opts = this._findOptions();
368            const flags = opts.caseSensitive ? 'g' : 'gi';
369            let src = opts.regExp ? pattern : pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
370            if (opts.wholeWord) src = '\\b' + src + '\\b';
371            const newText = selectedText.replace(new RegExp(src, flags), replace);
372            codeEditor.session.replace(codeEditor.selection.getRange(), newText);
373        } else {
374            codeEditor.find(pattern, this._findOptions());
375            codeEditor.replaceAll(replace);
376        }
377    }
378    markdownToLatex(e){
379        var codeEditor = ace.edit("editorCode");
380        var content = codeEditor.getCopyText();
381        var selection = '';
382        if (content !== '') {
383            selection = content;
384            codeEditor.remove();
385        } else {
386            content = codeEditor.getValue();
387        }
388        let slug = this.getSlug();
389        axios.post('/advancedEdit/markdownToLatex', {
390                'content': content,
391                'slug': slug,
392            }
393        )
394            .then((response) => {
395                let latex = response.data.latex.trim();
396                codeEditor.focus();
397                if (selection === '') {
398                    codeEditor.setValue(latex);
399                } else {
400                    codeEditor.insert(latex);
401                }
402            })
403            .catch(function (error) {
404                alert(error);
405            })
406    }
407    latexToMarkdown(e){
408        var codeEditor = ace.edit("editorCode");
409        var content = codeEditor.getCopyText();
410        if (content === '') {
411            content = codeEditor.getValue();
412        }
413        let slug = this.getSlug();
414        axios.post('/advancedEdit/latexToMarkdown', {
415                'content': content,
416                'slug': slug,
417            }
418        )
419            .then((response) => {
420                let markdown = response.data.markdown.trim();
421                navigator.clipboard.writeText(markdown);
422                new Notify({
423                    status: 'success',
424                    title: 'Code converted to markdown',
425                    text: '<pre>' + markdown + '</pre>',
426                    customIcon: '<i class="fa-regular fa-clipboard"></i>',
427                })
428            })
429            .catch(function (error) {
430                alert(error);
431            })
432    }
433    openDialog(html) {
434        // let existing = document.getElementById('advancedEditDialog');
435        // if (existing) existing.remove();
436        //
437        // let dialog = document.createElement('dialog');
438        // dialog.id = 'advancedEditDialog';
439        // dialog.className = 'rounded border-0 shadow';
440        // dialog.style.cssText = 'min-width: 50vw; max-width: 90vw; padding: 0;';
441        // dialog.innerHTML = this.aiAnswer(html);
442        // document.body.appendChild(dialog);
443        //
444        // dialog.addEventListener('click', (e) => {
445        //     if (e.target === dialog) dialog.close();
446        // });
447        let footer = document.getElementById('editorFooter');
448        if (footer){
449            footer.innerHTML = this.aiAnswer(html);
450            footer.style.display = 'block';
451            footer.querySelectorAll('pre code').forEach((block) => hljs.highlightElement(block));
452            document.getElementById('editorFooterClose').addEventListener('click', (e) => {
453                footer.style.display = 'none';
454            });
455            document.getElementById('advancedEditCopyBtn').addEventListener('click', () => {
456                let code = document.getElementById('corrected-text').innerText;
457                navigator.clipboard.writeText(code);
458                new Notify({
459                    status: 'success',
460                    title: 'Code copied to clipboard',
461                    text: '<pre>'+code+'</pre>',
462                    customIcon: '<i class="fa-solid fa-copy"></i>',
463                    autoClose: false,
464                })
465            });
466        }
467
468
469        // dialog.showModal();
470        //
471    }
472    aiAnswer (html)
473    {
474        return `
475            <div class="p-4">
476                <div id="advancedEditDialogBody" style="max-height: 70vh; overflow-y: auto;">${html}</div>
477                <div class="d-flex justify-content-end gap-2 mt-3 pt-3 border-top">
478                    <button type="button" class="btn btn-primary" id="advancedEditCopyBtn">
479                        <i class="fa-solid fa-copy"></i> Copy
480                    </button>
481                    <form method="dialog" style="margin: 0;">
482                        <button type="submit" class="btn btn-secondary" id="editorFooterClose">Close</button>
483                    </form>
484                </div>
485            </div>`
486    }
487    spelling(e) {
488        let codeEditor = ace.edit("editorCode");
489        let content = codeEditor.getCopyText();
490        if (content === ''){
491            content = codeEditor.getValue();
492        }
493        let slug = this.getSlug();
494        axios.post('/aspell', {
495                'content': content,
496                'editor': "editorCode",
497                'slug': slug,
498            }
499        )
500            .then((response) => {
501                let footer = document.getElementById('aspell-answer');
502                if (footer){
503                    footer.style.display = 'block';
504                    footer.innerHTML = response.data.response;
505                }
506            })
507            .catch(function (error) {
508                alert(error);
509            })
510    }
511    syntax(e) {
512        let codeEditor = ace.edit("editorCode");
513        let content = codeEditor.getCopyText();
514        if (content === ''){
515            content = codeEditor.getValue();
516        }
517        let slug = this.getSlug();
518        axios.post('/neuron/spelling', {
519                'content': content,
520                'slug': slug,
521            'name': 'Syntax'
522            }
523        )
524            .then((response) => {
525                this.openDialog(response.data.response);
526            })
527            .catch(function (error) {
528                alert(error);
529            })
530    }
531
532    static getCookie(name) {
533        let cookies = document.cookie.split(';');
534        let n = cookies.length;
535        for (let i = 0; i < n; i++) {
536            let cookie = cookies[i].split('=');
537            if (cookie[0].trim() === name) {
538                return cookie[1];
539            }
540        }
541        return null;
542    }
543    static subTypes = [
544        'col',
545    ]
546    getSlug(){
547        let id = this.idValue;
548        let idArray = id.split('/');
549        return idArray[0];
550    }
551}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.