view .cms/js/pages.js @ 1:1d486627aa1e draft default tip

24.10
author Coffee CMS <info@coffee-cms.ru>
date Sat, 12 Oct 2024 02:51:39 +0000
parents 78edf6b517a0
children
line wrap: on
line source

document.addEventListener( "DOMContentLoaded", function( event ) {

    function _( str ) {
        return __( str, "pages.mod.php" );
    }

    // Полностью этот скрипт отключить нельзя если включены Инструменты,
    // поэтому нужна проверка
    if ( document.querySelector( "#pages" ) ) {
        api( { fn: "get_pages_list" }, set_pages_list );
    }

    function set_pages_list( r ) {

        if ( ! document.querySelector( "#pages" ) ) return;

        if ( r.no_database ) {
            document.querySelector( "#pages .pages-grid" ).innerHTML = r.no_database;
            return;
        }

        if ( r.overloaded ) {
            let m = _( "server_overloaded_xxx" );
            m = m.replace( "xxx", r.pages.length );
            notify( m, "info-error", 5000 );
        }

        // Запоминаем результаты какого поиска мы получили
        // Это нужно чтобы не загружать снова все страницы при создании новой
        // не производить сброс поиска.
        let pages_search = document.querySelector( "#pages .page-search" );
        pages_search.setAttribute( "data-result-of", r.search );

        let grid = document.querySelector( "#pages .pages-grid" );
        let count = document.querySelector( "#pages .main-footer .count" );
        let loaded = document.querySelector( "#pages .main-footer .loaded" );
        if ( cms.clear_pages_list ) {
            cms.clear_pages_list = false;
            grid.innerHTML = "";
            loaded.value = "0";
        }

        // Всего страниц в БД
        count.innerText = r.count;

        // При удалении страниц подгружаются последние и не нужно менять смещение у пейджера
        if ( cms.dont_change_offset ) {
            cms.dont_change_offset = false;
        } else {
            loaded.setAttribute( "data-offset", r.offset );
        }

        // insert pages
        let start = Date.now();
        for ( let i = 0; i < r.pages.length; i++ ) {
            grid.insertAdjacentHTML( "beforeend", r.pages[i].html );
            loaded.value = +loaded.value + 1;
            let page = grid.querySelector( `[data-id="${r.pages[i].id}"]` );
            set_controls( page );
            if ( Date.now() - start > 1000 ) {
                let m = _( "browser_overloaded_xxx" );
                m = m.replace( "xxx", i + 1 );
                m = m.replace( "nnn", r.pages.length );
                notify( m, "info-error", 5000 );
                break;
            }
        }

        create_pager();

        // При создании страницы было обнаружено что поиск не сброшен,
        // поэтому была вызвана функция загрузки страниц с пустым поиском
        // и пришло время создать новую страницу
        if ( cms.create_page_fn ) {
            cms.create_page_fn();
            cms.create_page_fn = null;
        }

    }

    function create_pager() {
        let loaded = document.querySelector( "#pages .main-footer .loaded" );
        let offset = parseInt( loaded.getAttribute( "data-offset" ) );
        let count  = parseInt( document.querySelector( "#pages .main-footer .count" ).innerText );
        if ( count === 0 ) { count++; }
        let cookie_pp = get_cookie( "pages_pager" );
        let pages  = Math.ceil( count / cookie_pp );
        let pager  = document.querySelector( "#pages .main-footer .pager" );
        pager.innerHTML = "";
        if ( pages > 1 ) {
            for ( let i = 1; i <= pages; i++ ) {
                let p = document.createElement( "div" );
                p.innerText = i;
                p.setAttribute( "data-offset", ( i - 1 ) * cookie_pp );
                pager.appendChild( p );
            }
            pager.querySelector( `[data-offset="${offset}"]` ).classList.add( "active" );
            pager.childNodes.forEach( function( el ) {
                el.addEventListener( "click", function( e ) {
                    let offset = this.getAttribute( "data-offset" );
                    let search = document.querySelector( "#pages .page-search" ).value;
                    let data = {
                        fn: "get_pages_list",
                        offset: offset,
                        search: search
                    }
                    document.querySelector( "#pages .main-main" ).scrollTop = 0;
                    cms.clear_pages_list = true;
                    api( data, set_pages_list );
                } );
            } );
            // scroll
            pager.onmousedown = function( e ) {
                let pageX = 0;
                let pageY0 = 0;
              
                document.onmousemove = function( e ) {
                    if ( pageX !== 0 ) {
                        pager.scrollLeft = pager.scrollLeft + ( pageX - e.pageX );
                    }
                    pageX = e.pageX;
                    // fix for google chrome
                    if ( pageY0 === 0 ) {
                        pageY0 = e.pageY;
                    }
                    if ( Math.abs( pageY0 - e.pageY ) > 64 ) {
                        const event = new Event( "mouseup" );
                        pager.dispatchEvent( event );
                    }
                }
              
                // end drag
                pager.onmouseup = function() {
                    document.onmousemove = null;
                    pager.onmouseup = null;
                }
              
                // disable browser drag
                pager.ondragstart = function() {
                    return false;
                }
            }
        }
    }

    // pager counter
    let pager_counter = document.querySelector( "#pages .main-footer input" );
    if ( pager_counter ) pager_counter.addEventListener( "keydown", function( e ) {
        if ( e.keyCode === 13 ) { // keyCode work on mobile
            let p = document.querySelector( "#pages .main-footer input" ).value;
            set_cookie_expires( "pages_pager", p );
            cms.clear_pages_list = true;
            api( { fn: "get_pages_list", search: document.querySelector( "#pages .page-search" ).value }, set_pages_list );
        }
    } );

    function set_controls( selector ) {

        // Open properties
        selector.querySelectorAll( ".page-prop-btn" ).forEach( function( button ) {
            button.addEventListener( "click", function( e ) {
                let id = this.closest( "[data-id]" ).getAttribute( "data-id" );
                document.querySelector( `#pages .pages-grid [data-id="${id}"]` ).classList.toggle( "open" );
            } );
        } );

        // Save properties
        selector.querySelectorAll( ".page-prop-save-btn" ).forEach( function( button ) {
            button.addEventListener( "click", function( e ) {
                let id       = this.closest( "[data-id]" ).getAttribute( "data-id" );
                let item = document.querySelector( `#pages .pages-grid [data-id="${id}"] ` );
                let data = {
                    fn:           "save_prop",
                    id:           id,
                    title:        item.querySelector( '[name="title"]' ).value,
                    seo_title:    item.querySelector( '[name="seo_title"]' ).value,
                    url:          item.querySelector( '[name="url"]' ).value,
                    date:         item.querySelector( '[name="date"]' ).value,
                    time:         item.querySelector( '[name="time"]' ).value,
                    template:     item.querySelector( '.template-select-grid .field-select' ).getAttribute( "data-template" ),
                    old_template: item.querySelector( '.template-select-grid .field-select' ).getAttribute( "data-old-template" ),
                    description:  item.querySelector( '[name="description"]' ).value,
                    tags:         item.querySelector( '[name="tags"]' ).value,
                }
                api( data, function( r ) {
                    if ( r.ok == "false" ) { // FIXME: never fire
                        notify( r.info_text, r.info_class, 5000 );
                    }
                    if ( r.ok == "true" ) {

                        // Update Title, URL and Date
                        item.querySelector( ".page-name" ).innerHTML = r.title;
                        item.querySelector( ".page-name" ).setAttribute( "href", r.base_path + r.url );
                        update_home();
                        let url_el = item.querySelector( "[name=url]" );
                        if ( url_el.value != r.url ) {
                            notify( _( "url_changed" ), "info-error", 5000 );
                        }
                        url_el.value = r.url;
                        let date = item.querySelector( ".page-date" );
                        date.innerHTML = r.created;
                        if ( r.planned ) {
                            date.classList.add( "future" );
                        } else {
                            date.classList.remove( "future" );
                        }

                        // update old template
                        item.querySelector( '.template-select-grid .field-select' ).setAttribute( "data-old-template", data.template );

                        // edit marker
                        document.querySelectorAll( "#pages .pages-grid > div" ).forEach( function( el ) {
                            el.classList.remove( "last-edited" );
                        } );
                        setTimeout( function() {
                            item.classList.add( "last-edited" );
                        }, 200 );

                        // highlight save button
                        button.classList.add( "saved" );
                        setTimeout( function() {
                            button.classList.remove( "saved" );
                        }, 200 );

                        notify( r.info_text, r.info_class, 5000 );

                        // update event for menu
                        if ( r.update_menu == "true" ) {
                            let event = new Event( "update_menu" );
                            document.body.dispatchEvent( event );
                        }
                    }
                    
                } );
            } );
        } );

        // Pin Page
        selector.querySelectorAll( ".pin" ).forEach( function( pin ) {
            pin.addEventListener( "click", function( e ) {
                let box  = this.closest( "[data-id]" );
                let id   = box.getAttribute( "data-id" );
                let pin  = box.getAttribute( "data-pin" );
                if ( pin === "1" ) {
                    pin = "0";
                } else {
                    pin = "1";
                }
                let data = {
                    fn:  "page_pin",
                    id:  id,
                    pin: pin
                }
                api( data, function( r ) {
                    if ( r.ok == "true" ) {
                        box.setAttribute( "data-pin", pin );
                    }
                } );
            } );
        } );
        
        // Publish Page
        selector.querySelectorAll( ".published" ).forEach( function( pub ) {
            pub.addEventListener( "click", function( e ) {
                let box  = this.closest( "[data-id]" );
                let id   = box.getAttribute( "data-id" );
                let published  = box.getAttribute( "data-published" );
                if ( published === "1" ) {
                    published = "0";
                } else {
                    published = "1";
                }
                let data = {
                    fn:  "page_publish",
                    id:  id,
                    published: published
                }
                api( data, function( r ) {
                    if ( r.ok == "true" ) {
                        box.setAttribute( "data-published", published );
                        if ( published == "1" ) {
                            pub.setAttribute( "title", _( "published" ) );
                        } else {
                            pub.setAttribute( "title", _( "unpublished" ) );
                        }
                    }
                } );
            } );
        } );

        // Edit page
        selector.querySelectorAll( ".page-edit-btn" ).forEach( function( button ) {
            button.addEventListener( "click", function( e ) {
                button.classList.add( "loading" );
                let id = this.closest( "[data-id]" ).getAttribute( "data-id" );
                // get page from server
                api( { fn: "get_page", id: id }, function( r ) {
                    button.classList.remove( "loading" );
                    if ( r.result == "ok" ) {
                        let title = document.querySelector( "#pages .page-editor-title" );
                        title.innerHTML = r.page.title;
                        title.setAttribute( "href", r.base_path + r.page.url );
                        let props = document.querySelector( "#pages .page-properties" );
                        props.querySelector( "input[name='title']" ).value = r.page.title;
                        props.querySelector( "input[name='url']" ).value = r.page.url;
                        props.querySelector( "input[name='seo_title']" ).value = r.page.seo_title;
                        props.querySelector( "textarea[name='description']" ).value = r.page.description;
                        props.querySelector( "textarea[name='tags']" ).value = r.page.tags;
                        props.querySelector( "input[name='date']" ).value = r.date;
                        props.querySelector( "input[name='time']" ).value = r.time;
                        let select = props.querySelector( ".template-select-grid" );
                        let options = select.querySelector( ".field-options" );
                        options.innerHTML = r.options;
                        let tpl = select.querySelector( ".field-select" );
                        tpl.setAttribute( "data-template", r.option );
                        tpl.setAttribute( "data-old-template", r.option );
                        tpl.querySelector( ".value" ).innerText = r.option_tr;
                        document.querySelector( "#pages .page-editor > textarea" ).value = r.page.text;
                        if ( r.page.modified != null ) { // prevent delete attribute
                            document.querySelector( "#pages .page-editor > textarea" ).setAttribute( "data-modified", r.page.modified );
                        }
                        document.querySelector( "#pages .save-page-button" ).setAttribute( "data-id", r.page.id );

                        options.querySelectorAll( ".option" ).forEach( function( option ) {
                            option.addEventListener( "click", click_select_option );
                        } );
                        
                        
                        // Images
                        document.querySelector( "#pages .link-file-tag" ).innerHTML = "";
                        document.querySelector( "#pages .del-uploaded-files" ).classList.add( "disabled" );
                        document.querySelector( "#pages .mediateka-files-grid" ).innerHTML = r.flist;
                        document.querySelectorAll( "#pages .mediateka-files-grid input[type=checkbox]" ).forEach( function( checkbox ) {
                            checkbox.addEventListener( "dblclick", img_check_dblclick );
                            checkbox.addEventListener( "change", img_rechecked );
                        } );
                        document.querySelectorAll( "#pages .file-block" ).forEach( function( block ) {
                            block.addEventListener( "click", img_click );
                        } );
                        document.querySelectorAll( "#pages .mediateka-files-grid img" ).forEach( function( img ) {
                            img.addEventListener( "dblclick", img_lbox );
                        } );

                        // Show Editor
                        document.querySelector( "#pages .page-editor-bg" ).classList.remove( "hidden" );
                        document.body.classList.add( "editor" ); // for notifications

                        // Connect Editor
                        codemirror_connect( "#pages .page-editor > textarea", "cm" );

                        // restore scroll and cursor position
                        let cursor = localStorage.getItem( "cursor_page_" + id );
                        if ( cursor ) {
                            cursor = JSON.parse( cursor );
                            window.cm.scrollTo( cursor.left, cursor.top );
                            window.cm.setCursor( { line:cursor.line, ch:cursor.ch } );
                            window.cm.refresh();
                            //window.cm.scrollIntoView( { line:cursor.line, ch:cursor.ch } ); // fix glitch
                        }

                        // track changes
                        document.querySelector( "#pages .close-page-button" ).setAttribute( "data-changed", "false" );
                        document.querySelector( "#pages .page-editor-grid" ).setAttribute( "data-changed", "false" );
                        cm.on( "change", function( cm, change ) {
                            document.querySelector( "#pages .close-page-button" ).setAttribute( "data-changed", "true" );
                            document.querySelector( "#pages .page-editor-grid" ).setAttribute( "data-changed", "true" );
                        } );
                        // save scroll and cursor position
                        [ "cursorActivity", "scroll" ].forEach( function( event ) {
                            cm.on( event, function() {
                                let cursor = window.cm.getCursor();
                                let scroll = window.cm.getScrollInfo();
                                localStorage.setItem( "cursor_page_" + id, JSON.stringify( { line:cursor.line, ch:cursor.ch, left: scroll.left, top: scroll.top } ) );
                            } );
                        } );

                        // set focus to editor
                        cm.focus();

                        // Save Page Ctrl+S
                        document.documentElement.addEventListener( "keydown", CtrlS );

                        // open tags panel
                        if ( document.documentElement.offsetWidth >= 1024 ) {
                            document.querySelector( "#pages .page-editor-grid" ).classList.add( "tags-opened" );
                        }
                    }
                } );
            } );
        } );

        // Select
        selector.querySelectorAll( ".field-select" ).forEach( function( select ) {
            select.addEventListener( "click", function( e ) {
                e.stopPropagation();
                this.parentElement.classList.toggle( "open" );

                // это можно убрать если переставить стили на родителя
                select.nextElementSibling.classList.toggle( "open" );
            } );
        } );
        // Option шаблона
        // вынесено в функцию потому что динамическое изменение
        selector.querySelectorAll( ".field-options .option" ).forEach( function( option ) {
            option.addEventListener( "click", click_select_option );
        } );

        // Транслитерация URL
        selector.querySelectorAll( ".url-translit" ).forEach( function( btn ) {
            btn.addEventListener( "click", function( e ) {
                let url = selector.querySelector( "input[name='title']" ).value;
                let tr_url = url_translit( url );
                this.previousElementSibling.value = tr_url;
            } );
        } );

        // Выбор всех страниц двойным кликом
        let check = selector.querySelector( `:scope > input[type=checkbox]` );
        if ( check ) check.addEventListener( "dblclick", function( e ) {
            let stat = ! this.checked;
            document.querySelectorAll( "#pages .pages-grid > div > input[type=checkbox]" ).forEach( function( chbox ) {
                chbox.checked = stat;
            } );
        } );

    }

    // Клик по опции выбора шаблона
    function click_select_option( e ) {
        let select = this.closest( ".template-select-grid" );
        let value  = this.getAttribute( "value" );
        select_switch_to( select, value );
    }

    // 
    function select_switch_to( select, value ) {
        let field_select = select.querySelector( ".field-select" );
        let old_value = field_select.getAttribute( "data-template" );
        if ( old_value != value ) {
            let old_name = field_select.innerText;
            let field_options = select.querySelector( ".field-options" );
            let option = field_options.querySelector( `[value="${value}"]` );
            field_select.querySelector( ".value" ).innerText = option.innerText;
            field_select.setAttribute( "data-template", value );
            option.remove();
            let option_html = `<div class=option value="${old_value}">${old_name}</div>`;
            field_options.insertAdjacentHTML( "afterbegin", option_html );
            field_options.firstChild.addEventListener( "click", click_select_option );
        }
    }

    // Select for editor
    document.querySelectorAll( ".page-properties .field-select" ).forEach( function( select ) {
        select.addEventListener( "click", function( e ) {
            e.stopPropagation();
            this.parentElement.classList.toggle( "open" );
            // это можно убрать если переставить стили на родителя
            select.nextElementSibling.classList.toggle( "open" );
        } );
    } );

    // Select
    // Закрытие выпадающих списков при кликах вне их, а так же по ним
    document.body.addEventListener( "click", function( e ) {
        document.querySelectorAll( "#pages .template-select-grid" ).forEach( function( list ) {
            list.classList.remove( "open" );
        } );

        // это можно убрать если переставить стили на родителя
        document.querySelectorAll( "#pages .field-options" ).forEach( function( list ) {
            list.classList.remove( "open" );
        } );
    } );

    // Search page
    let pages_search = document.querySelector( "#pages .page-search" );
    if ( pages_search ) pages_search.addEventListener( "keydown", function( e ) {
        if ( e.keyCode === 13 ) {
            // сохраняем событие целиком чтобы ниже считать Shift, Ctrl, Alt
            cms.search_event = e;
            search_pages();
        }
    } );

    let search_btn = document.querySelector( "#pages .page-search-button" );
    if ( search_btn ) search_btn.addEventListener( "click", function( e ) {
        // клик по кнопке не дает Ctrl, Shift, Alt, запишем это
        cms.search_event = { "ctrlKey": false, "shiftKey": false, "altKey": false };
        search_pages();
        document.querySelector( "#pages .page-search" ).focus();
    } );

    function search_pages() {
        let search_string = document.querySelector( "#pages .page-search" ).value;
        let data = {
            fn: "get_pages_list",
            search: search_string,
            Ctrl: cms.search_event.ctrlKey,
            Shift: cms.search_event.shiftKey,
            Alt: cms.search_event.altKey,
        };
        cms.clear_pages_list = true;
        api( data, set_pages_list );
    }

    // Reset Search
    let reset_btn = document.querySelector( "#pages .reset" );
    if ( reset_btn ) reset_btn.addEventListener( "click", function() {
        document.querySelector( "#pages .page-search" ).value = "";
        document.querySelector( "#pages .page-search-button" ).click();
    } );

    // Delete pages
    document.querySelectorAll( "#pages .del-pages-btn" ).forEach( function( button ) {
        button.addEventListener( "click", function( e ) {
            let ids = [];
            document.querySelectorAll( "#pages .pages-grid input[type=checkbox]:checked" ).forEach( function( ch ) {
                let id = ch.closest( "[data-id]" ).getAttribute( "data-id" );
                ids.push( id );
            } );
            if ( ids.length === 0 ) {
                notify( _( "no_selected_pages" ), "info-error", 5000 );
                return;
            }
            if ( ! confirm( _( "confirm_delete_pages" ) ) ) {
                return;
            }
            let data = {
                fn: "del_pages",
                ids: ids
            };
            api( data, function( r ) {
                if ( r.info_text ) {
                    notify( r.info_text, r.info_class, 5000 );
                    if ( r.info_class == "info-success" ) {
                        data.ids.forEach( function( id ) {
                            document.querySelector( `#pages .pages-grid [data-id="${id}"]` ).remove();
                            localStorage.removeItem( "cursor_page_" + id );
                        } );
                        let count = document.querySelector( "#pages .main-footer .count" );
                        let loaded = document.querySelector( "#pages .main-footer .loaded" );
                        loaded.value = parseInt( loaded.value ) - data.ids.length;
                        count.innerText = parseInt( count.innerText ) - data.ids.length;
                        // load pages
                        let offset = +loaded.getAttribute( "data-offset" ) + document.querySelectorAll( "#pages .pages-grid > *" ).length;
                        let search = document.querySelector( "#pages .page-search" ).value;
                        let data2 = {
                            fn: "get_pages_list",
                            count: data.ids.length,
                            offset: offset,
                            search: search
                        };
                        // Не менять атрибут смещения у пейджера
                        cms.dont_change_offset = true;
                        api( data2, set_pages_list );
                    }
                }
                // update event for menu
                if ( r.update_menu == "true" ) {
                    let event = new Event( "update_menu" );
                    document.body.dispatchEvent( event );
                }
            } );
        } );
    } );

    // copy file link button
    document.querySelector( "#pages .link-file-copy-btn" ).addEventListener( "click", function( e ) {
        let img = this.previousElementSibling.innerText;
        let tmp = document.createElement( "textarea" );
        document.body.appendChild( tmp );
        tmp.value = img;
        tmp.select();
        let r = document.execCommand( "copy" );
        tmp.remove();
        if ( r ) {
            if ( img ) {
                notify( _( "copyed" ), "info-success", 5000 );
            } else {
                notify( _( "select_file" ), "info-error", 5000 );
            }
        } else {
            notify( _( "copy_error" ), "info-error", 5000 );
        }
        cm.focus();
    } );

    function CtrlS( e ) {
        // ы and і - fix for librewolf
        if ( ( e.code == "KeyS" || e.key == "ы" || e.key == "і" ) && e.ctrlKey == true ) {
            e.preventDefault(); // don't save page
            if ( window.location.hash == "#pages" ) {
                document.querySelector( "#pages .save-page-button" ).click();
            }
        }
        /*/ Ловим кнопки codemirror
        if ( e.code == "F1" ) {
            let dialog = document.querySelector( ".CodeMirror-dialog-top" );
            let html = dialog.outerHTML;
            cms.dialog = html;
            //notify( html, "info-success", 5000000 );
        }
        if ( e.code == "F2" ) {
            document.querySelector( ".CodeMirror" ).insertAdjacentHTML( "beforeend", cms.dialog );
        }
        */
    }

    // Save Page
    document.querySelectorAll( "#pages .save-page-button" ).forEach( function( button ) {
        button.addEventListener( "click", function( e ) {
            window.cm.save(); // drop changes to textarea
            let main_div = document.querySelector( "#pages" );
            let prop_div = main_div.querySelector( ".page-properties" );
            let data = {
                fn: "save_page",
                id: main_div.querySelector( ".save-page-button" ).getAttribute( "data-id" ),
                modified: main_div.querySelector( ".page-editor > textarea" ).getAttribute( "data-modified" ),
                text: main_div.querySelector( ".page-editor > textarea" ).value,
                title: prop_div.querySelector( "input[name='title']" ).value,
                url: prop_div.querySelector( "input[name='url']" ).value,
                seo_title: prop_div.querySelector( "input[name='seo_title']" ).value,
                description: prop_div.querySelector( "textarea[name='description']" ).value,
                tags: prop_div.querySelector( "textarea[name='tags']" ).value,
                date: prop_div.querySelector( "input[name='date']" ).value,
                time: prop_div.querySelector( "input[name='time']" ).value,
                template: prop_div.querySelector( ".template-select-grid .field-select" ).getAttribute( "data-template" ),
                old_template: prop_div.querySelector( ".template-select-grid .field-select" ).getAttribute( "data-old-template" ),
            }
            api( data, function( r ) {
                if ( r.ok == "true" ) {
                    // set text
                    if ( r.new_text ) {
                        window.cm.setValue( r.new_text );
                    }

                    // Update Title and URL
                    main_div.querySelector( ".page-editor-title" ).innerHTML = r.title;
                    main_div.querySelector( ".page-editor-title" ).setAttribute( "href", r.base_path + r.url );
                    prop_div.querySelector( "input[name='url']" ).value = r.url;

                    // update old template
                    prop_div.querySelector( ".template-select-grid .field-select" ).setAttribute( "data-old-template", data.template );

                    // Update item in page list
                    let item = main_div.querySelector( `.pages-grid [data-id='${data.id}']` );
                    item.querySelector( `.page-name` ).innerHTML = r.title;
                    item.querySelector( `.page-name` ).setAttribute( "href", r.base_path + r.url );
                    update_home();
                    item.querySelector( `input[name='title']` ).value = r.title;
                    item.querySelector( `input[name='url']` ).value = r.url;
                    item.querySelector( `input[name='seo_title']` ).value = data.seo_title;
                    item.querySelector( `textarea[name='description']` ).value = data.description;
                    item.querySelector( `textarea[name='tags']` ).value = data.tags;

                    // Выставить шаблон в плашке в списке страниц
                    let select = item.querySelector( `.template-select-grid` );
                    select_switch_to( select, data.template );
                    item.querySelector( `.template-select-grid .field-select` ).setAttribute( "data-old-template", data.template );

                    item.querySelector( `input[name='date']` ).value = data.date;
                    item.querySelector( `input[name='time']` ).value = data.time;
                    if ( r.planned ) {
                        item.querySelector( `.page-date` ).classList.add( "future" );
                    } else {
                        item.querySelector( `.page-date` ).classList.remove( "future" );
                    }

                    main_div.querySelector( ".page-editor > textarea" ).setAttribute( "data-modified", r.modified );
                    main_div.querySelector( ".close-page-button" ).setAttribute( "data-changed", "false" );
                    main_div.querySelector( ".page-editor-grid" ).setAttribute( "data-changed", "false" );
                    // edit marker
                    main_div.querySelectorAll( ".pages-grid > div" ).forEach( function( item ) {
                        item.classList.remove( "last-edited" );
                    } );
                    item.classList.add( "last-edited" );
                    // close editor after save
                    if ( main_div.querySelector( ".save-page-button" ).getAttribute( "data-close" ) === "true" ) {
                        main_div.querySelector( ".save-page-button" ).setAttribute( "data-close", "false" );
                        main_div.querySelector( ".close-page-button" ).click();
                    }
                    // highlight save button
                    main_div.querySelector( ".save-page-button" ).classList.add( "saved" );
                    setTimeout( function() {
                        main_div.querySelector( ".save-page-button" ).classList.remove( "saved" );
                    }, 1000 );

                    notify( r.info_text, r.info_class, r.info_time );

                    // update event for menu
                    if ( r.update_menu == "true" ) {
                        let event = new Event( "update_menu" );
                        document.body.dispatchEvent( event );
                    }
                }
                if ( r.ok == "false" ) {
                    // highlight save button
                    main_div.querySelector( ".save-page-button" ).classList.add( "error" );
                    setTimeout( function() {
                        main_div.querySelector( ".save-page-button" ).classList.remove( "error" );
                    }, 1000 );

                    notify( r.info_text, r.info_class, r.info_time );
                }
            } );
        } );
    } );

    // transliterate file name
    function __tr_file( str ) {
        let ext = str.match( /\.[^\.]+$/, "" );
        str = str.replace( /\.[^\.]+$/, "" );
        let sp = cms.tr[" "];
        cms.tr[" "] = "_";
        for ( let i in cms.tr ) {
            let re = new RegExp( i, "g" );
            str = str.replace( re, cms.tr[i] );
        }
        if ( sp === undefined ) {
            delete cms.tr[" "];
        } else {
            cms.tr[" "] = sp;
        }
        str = str.replace( /[^-A-Za-z0-9_]+/g, "" );
        if ( ext[0] ) {
            str = str + ext[0];
        }
        str = str.toLowerCase();
        return str;
    }

    // Upload files
    let upload_btn = document.querySelector( "#pages .upload-files input[type=file]" );
    if ( upload_btn ) upload_btn.addEventListener( "change", async function( event ) {
        const formData = new FormData();
        let id = document.querySelector( "#pages .save-page-button" ).getAttribute( "data-id" );
        formData.append( "id", id );
        formData.append( "fn", "upload_files" );
        let n = 0;
        for ( let i = 0; i < this.files.length; i++ ) {
            formData.append( "myfile[]", this.files[i] );
            let f = `${cms.base_path}uploads/${id}/` + __tr_file( this.files[i].name );
            let f_exists = document.querySelector( `#pages .file-block [data-src="${f}"]` );
            if ( f_exists ) { n++; }
        }
        let google_chrome_fix = this;
        if ( n )  {
            let c = confirm( _( "same_files" ) + ` - ${n} ` + _( "pc" ) + "\n" + _( "confirm_replace" ) );
            if ( ! c ) {
                google_chrome_fix.value = "";
                return c;
            }
        }
        let bar = document.querySelector( "#pages .upload-progress" );

        
        let ajax = new XMLHttpRequest();
        
        ajax.upload.addEventListener( "progress", function( event ) {
            let percent = Math.round( (event.loaded / event.total) * 100 );
            bar.style.width = percent + "%";
        }, false );

        ajax.addEventListener( "error", function( event ) {
            notify( _( "error_upload_file" ), "info-error", 3600000 );
            bar.style = "";
        }, false );
        
        ajax.addEventListener( "abort", function( event ) {
            notify( _( "error_upload_file" ), "info-error", 3600000 );
            bar.style = "";
        }, false );

        ajax.addEventListener( "load", function( event ) {
            bar.style = "";
            google_chrome_fix.value = "";
            if ( event.target.status == 413 ) {
                notify( _( "too_large" ), "info-error", 5000 );
            } else {
                let r = JSON.parse( event.target.responseText );
                if ( r.info_text ) {
                    notify( r.info_text, r.info_class, r.info_time );
                    if ( r.info_class == "info-success" ) {

                        // удалить файлы которые были обновлены
                        let tmp = document.createElement( "div" );
                        tmp.innerHTML = r.flist;
                        let imgs = tmp.querySelectorAll( "img" );
                        imgs.forEach( function( img ) {
                            let file = img.getAttribute( "data-src" );
                            let exists_file = document.querySelector( `#pages .file-block [data-src="${file}"]` );
                            if ( exists_file ) {
                                exists_file.parentElement.remove();
                            }
                        } );

                        let container = document.querySelector( "#pages .mediateka-files-grid" );
                        container.innerHTML = r.flist + container.innerHTML;
                        
                        // images checkboxes
                        container.querySelectorAll( "input[type=checkbox]" ).forEach( function( checkbox ) {
                            checkbox.addEventListener( "dblclick", img_check_dblclick );
                            checkbox.addEventListener( "change", img_rechecked );
                        } );
                        
                        // open lightbox
                        container.querySelectorAll( "img" ).forEach( function( img ) {
                            img.addEventListener( "dblclick", img_lbox );
                        } );
                        
                        // generate link
                        container.querySelectorAll( ".file-block" ).forEach( function( file_block ) {
                            file_block.addEventListener( "click", img_click );
                        } );
                        
                        // select last uploaded
                        container.querySelector( ".file-block" ).click();
                    }
                }
            }
        }, false );

        ajax.open( "POST", cms.api );
	    ajax.send( formData );
        
    } );

    // Close Editor
    document.querySelectorAll( "#pages .close-page-button" ).forEach( function( button ) {
        button.addEventListener( "click", function( e ) {

            // hide mediateka
            if ( ! document.querySelector( "#pages .page-editor-panel" ).classList.contains( "hidden" ) ) {
                document.querySelector( "#pages .open-mediateka" ).click();
            }

            // hide editor
            document.querySelector( "#pages .page-editor-bg" ).classList.add( "hidden" );
            document.body.classList.remove( "editor" );

            document.documentElement.removeEventListener( "keydown", CtrlS );
            
            // detach
            if ( window.cm !== undefined ) {
                if ( this.getAttribute( "data-changed" ) === "true" ) {
                    if ( confirm( _( "confirm_save" ) ) ) {
                        document.querySelector( "#pages .save-page-button" ).setAttribute( "data-close", "true" );
                        document.querySelector( "#pages .save-page-button" ).click();
                        return;
                    }
                }
                window.cm.toTextArea();
                window.cm = null;
            }
            
        } );
    } );

    // Create Page
    document.querySelectorAll( "#pages .add-page-btn" ).forEach( function( btn ) {
        btn.addEventListener( "click", function ( e ) {
            // Отложенный вызов на случай если нужно очистить поиск
            cms.create_page_fn = function() {
                api( { fn: "create_page" }, function( r ) {
                    if ( r.info_text ) {
                        notify( r.info_text, r.info_class, r.info_time );
                    }
                    if ( r.pages ) {
                        let grid = document.querySelector( "#pages .pages-grid" );
                        grid.insertAdjacentHTML( "afterbegin", r.pages[0].html );
                        // Подкрутить список страниц в начало
                        document.querySelector( "#pages .main-main" ).scrollTop = 0;

                        let page_box = grid.querySelector( `[data-id="${r.pages[0].id}"]` );

                        set_controls( page_box );

                        let counter = document.querySelector( "#pages .main-footer .count" );
                        counter.innerText = +counter.innerText + 1;

                        let showed_pages_el = document.querySelector( "#pages .main-footer .counters input" );
                        if ( showed_pages_el.value === get_cookie( "pages_pager" ) ) {
                            document.querySelector( "#pages .pages-grid > div:last-child" ).remove();
                        } else {
                            showed_pages_el.value = +showed_pages_el.value + 1;
                        }
                    }
                } );
            }
            // Если сейчас не результаты поиска отображены,
            // и если мы видим первый пейджер
            // то сразу выполнить создание страницы
            let empty_search = document.querySelector( "#pages .page-search" ).getAttribute( "data-result-of" ) === "";
            let offset_zero = +document.querySelector( "#pages .main-footer .counters input" ).getAttribute( "data-offset" ) === 0;
            if ( empty_search && offset_zero ) {
                cms.create_page_fn();
                cms.create_page_fn = null;
            } else {
                document.querySelector( "#pages .reset" ).click();
                // Создание страницы произведет функция set_pages_list()
            }
        } );
    } );

    // Open Properties
    document.querySelector( "#pages .open-properties" ).addEventListener( "click", function( e ) {
        document.querySelector( "#pages .page-editor-grid" ).classList.toggle( "properties" );
        document.querySelector( "#pages .page-properties" ).classList.toggle( "hidden" );
        if ( window.cm ) {
            let cursor = window.cm.getCursor();
            window.cm.scrollIntoView( { line:cursor.line, ch:cursor.ch } );
        }
        if ( document.querySelector( "#pages .page-editor-grid" ).classList.contains( "properties" ) ) {
            document.querySelector( "#pages .page-editor-grid" ).classList.remove( "mediateka" );
            document.querySelector( "#pages .page-editor-panel" ).classList.add( "hidden" );
        }
    } );
    
    // Open Mediateka
    document.querySelector( "#pages .open-mediateka" ).addEventListener( "click", function( e ) {
        document.querySelector( "#pages .page-editor-grid" ).classList.toggle( "mediateka" );
        document.querySelector( "#pages .page-editor-panel" ).classList.toggle( "hidden" );
        if ( window.cm ) {
            let cursor = window.cm.getCursor();
            window.cm.scrollIntoView( { line:cursor.line, ch:cursor.ch } );
        }
        if ( document.querySelector( "#pages .page-editor-grid" ).classList.contains( "mediateka" ) ) {
            document.querySelector( "#pages .page-editor-grid" ).classList.remove( "properties" );
            document.querySelector( "#pages .page-properties" ).classList.add( "hidden" );
        }
        cm.focus();
    } );

    // Replace Dialog Toggle
    document.querySelector( "#pages .codemirror-replace" ).addEventListener( "click", function( e ) {
        let dialog = document.querySelector( "#pages .CodeMirror-dialog" );
        if ( dialog ) {
            dialog.remove();
        } else {
            let mediateka = ! document.querySelector( "#pages .page-editor-panel" ).classList.contains( "hidden" );
            if ( mediateka && window.innerWidth < 1024 ) {
                document.querySelector( "#pages .open-mediateka" ).click();
            }
            window.cm.execCommand( "replace" );
        }
    } );

    // generate link to clicked file
    function img_click() {
        this.parentElement.querySelectorAll( ".file-block" ).forEach( function( block ) {
            block.classList.remove( "active-file" );
        } );
        this.classList.add( "active-file" );
        let i = this.querySelector( "img" );
        let t = i.getAttribute( "data-type" );
        let link = i.getAttribute( "data-src" );
        let w = i.getAttribute( "width" );
        let h = i.getAttribute( "height" );
        let e = link.replace( /.*\./, "" );
        let img = [ "webp", "tiff", "jpeg", "jpg", "png", "svg", "gif", "bmp", "ico" ];
        let mus = [ "mp3", "ogg", "m4a", "flac" ];
        let vid = [ "mp4", "mkv", "webm" ];
        let a = `<a href="${link}" target=_blank>${link}</a>`;
        let tag = this.closest( ".mediateka-grid" ).querySelector( ".link-file-tag" );
        if ( img.indexOf( e ) >= 0 ) {
            link = `&lt;img alt="" src="${a}"`;
            if ( w ) {
                link += ` width="${w}"`;
            }
            if ( h ) {
                link += ` height="${h}"`;
            }
            link += ` loading="lazy"`;
            link += "&gt;";
            tag.innerHTML = link;
        } else if ( mus.indexOf( e ) >= 0 ) {
            link = `&lt;audio src="${a}" controls>&lt;/audio>`;
            tag.innerHTML = link;
        } else if ( vid.indexOf( e ) >= 0 ) {
            link = `&lt;video src="${a}" poster="" controls preload="none">&lt;/video>`;
            tag.innerHTML = link;
        } else {
            link = `&lt;a href="${a}"&gt;TEXT&lt;/a&gt;`;
            tag.innerHTML = link;
        }
        let inner_link = tag.querySelector( "a" );
        inner_link.addEventListener( "click", file_link_click );
        cm.focus();
    }

    function file_link_click( e ) {
        e.preventDefault();
        let tmp = document.createElement( "textarea" );
        document.body.appendChild( tmp );
        tmp.value = e.target.getAttribute( "href" );
        tmp.select();
        let r = document.execCommand( "copy" );
        tmp.remove();
        if ( r ) {
            notify( _( "copyed" ), "info-success", 5000 );
        } else {
            notify( _( "copy_error" ), "info-error", 5000 );
        }
        cm.focus();
    }

    // enable or disable delete files button
    function img_rechecked() {
        let checked = document.querySelectorAll( "#pages .mediateka-files-grid input[type=checkbox]:checked" );
        if ( checked.length ) {
            document.querySelector( "#pages .del-uploaded-files" ).classList.remove( "disabled" );
        } else {
            document.querySelector( "#pages .del-uploaded-files" ).classList.add( "disabled" );
        }
    }

    // Выбор всех картинок двойным кликом
    function img_check_dblclick( e ) {
        let stat = ! this.checked;
        document.querySelectorAll( "#pages .mediateka-files-grid input[type=checkbox]" ).forEach( function( chbox ) {
            chbox.checked = stat;
        } );
        img_rechecked();
    }
    
    // view in lightbox
    function img_lbox() {
        let src = this.getAttribute( "data-src" );
        let e = src.replace( /.*\./, "" );
        let img = [ "webp", "tiff", "jpeg", "jpg", "png", "svg", "gif", "bmp", "ico" ];
        let mus = [ "mp3", "ogg", "m4a", "flac" ];
        let vid = [ "mp4", "mkv", "webm" ];
        let t;
        if ( document.querySelector( "#lbox-window" ) == null ) {
            if ( img.indexOf( e ) >= 0 ) {
                t = document.createElement( "img" );
            } else if ( mus.indexOf( e ) >= 0 ) {
                t = document.createElement( "audio" );
                t.setAttribute( "controls", true );
            } else if ( vid.indexOf( e ) >= 0 ) {
                t = document.createElement( "video" );
                t.setAttribute( "controls", true );
            }
            if ( t !== undefined ) {
                t.src = src;
                let d = document.createElement( "div" );
                d.id = "lbox-window";
                d.appendChild( t );
                document.body.appendChild( d );
                d.addEventListener( "click", function( e ) {
                    this.remove();
                } );
            }
        }
    }


    // Delete files
    document.querySelector( "#pages .del-uploaded-files" ).addEventListener( "click", function( e ) {
        if ( ! this.classList.contains( "disabled" ) ) {
            let flist = [];
            document.querySelectorAll( "#pages .mediateka-files-grid input[type=checkbox]:checked" ).forEach( function( e ) {
                let f = e.closest( ".file-block" ).querySelector( "img" ).getAttribute( "data-src" );
                flist.push( f );
            } );
            let data = {
                fn: "del_files",
                flist: flist
            };
            api( data, function( r ) {
                if ( r.info_text ) {
                    document.querySelector( "#pages .link-file-tag" ).innerHTML = "";
                    notify( r.info_text, r.info_class, r.info_time );
                    if ( r.info_class == "info-success" ) {
                        for ( let f in flist ) {
                            document.querySelector( `#pages .mediateka-files-grid img[data-src="${flist[f]}"]` ).parentElement.remove();
                        }
                        document.querySelector( "#pages .del-uploaded-files" ).classList.add( "disabled" );
                    }
                }
            } );
        }
    } );

    // prevent hide cursor when window resize
    window.addEventListener( "resize", function() {
        if ( window.cm ) {
            let cursor = window.cm.getCursor();
            window.cm.scrollIntoView( { line:cursor.line, ch:cursor.ch } );
        }
    } );

    // show/hide tags
    let tags_btn = document.querySelector( "#pages .tags-helper" );
    if ( tags_btn ) tags_btn.addEventListener( "click", function( e ) {
        document.querySelector( "#pages .page-editor-grid" ).classList.toggle( "tags-opened" );
        cm.focus();
        cm.refresh();
    } );

    // for tags
    document.querySelectorAll( "#pages .tags-grid [data-type='wrap']" ) .forEach( function( btn ) {
        btn.addEventListener( "click", function( e ) {
            let otag = this.getAttribute( "data-otag" );
            let ctag = this.getAttribute( "data-ctag" );
            let len  = this.getAttribute( "data-len" );
            let ch   = this.getAttribute( "data-ch" );
            let line = this.getAttribute( "data-line" );
            
            //wrap_selections( otag, ctag, len, line, ch );
            let cursor = cm.getCursor();
            let selections = cm.getSelections();
            let replacements = [];
            for ( let i = 0; i < selections.length; i++ ) {
                replacements[i] = otag + selections[i] + ctag;
            }
            cm.replaceSelections( replacements );
            // Прятять панельку на мобильнике
            if ( window.innerWidth < 1024 ) {
                //document.querySelector( "#pages .tags-helper" ).click();
                let grid = this.closest( ".editor-grid" );
                grid.classList.remove( "tags-opened" );
            }
            if ( selections.length < 2 ) {
                if ( line ) {
                    cursor.line += +line;
                    cursor.ch = +ch;
                } else if ( len ) {
                    cursor.ch += +len;
                }
                cm.setCursor( cursor );
            }
            cm.focus();
            cm.refresh();
        } );
    } );
    // for <a> tag
    document.querySelectorAll( "#pages .tags-grid [data-type='wrap-a']" ) .forEach( function( btn ) {
        btn.addEventListener( "click", function( e ) {
            let cursor = cm.getCursor();
            let selections = cm.getSelections();
            let replacements = [];
            
            for ( let i = 0; i < selections.length; i++ ) {
                if ( selections[i].match( /https?:\/\// ) ) {
                    replacements[i] = `<a href="${selections[i]}" target=_blank>${selections[i]}</a>`;
                } else {
                    replacements[i] = `<a href="" target=_blank>${selections[i]}</a>`;
                }
            }
            
            cm.replaceSelections( replacements );

            if ( selections.length == 1 && selections[0] === "" ) {
                cursor.ch += 9;
                cm.setCursor( cursor );
            }
            
            if ( window.innerWidth < 1024 ) {
                //document.querySelector( "#pages .tags-helper" ).click();
                let grid = this.closest( ".editor-grid" );
                grid.classList.remove( "tags-opened" );
            }

            cm.focus();
            cm.refresh();
        } );
    } );
    // for tags <ul> and <ol>
    document.querySelectorAll( "#pages .tags-grid [data-type='wrap-list']" ) .forEach( function( btn ) {
        btn.addEventListener( "click", function( e ) {
            let tag = this.getAttribute( "data-tag" );
            let selections = cm.getSelections();
            let replacements = [];

            if ( selections.length == 1 && selections[0] === "" ) {
                replacements[0] = `<${tag}>\n    <li></li>\n</${tag}>`;
            } else if ( selections.length == 1 ) {
                let lines = selections[0].split( "\n" );
                replacements[0] = `<${tag}>\n    <li>` + lines.join( "</li>\n    <li>" ) + `</li>\n</${tag}>`;
            } else {
                let n = selections.length - 1;
                for ( let i = 0; i <= n; i++ ) {
                    replacements[i] = `    <li>${selections[i]}</li>`;
                }
                replacements[0] = `<${tag}>\n` + replacements[0];
                replacements[n] = replacements[n] + `\n</${tag}>`;
            }

            cm.replaceSelections( replacements );
            
            if ( window.innerWidth < 1024 ) {
                //document.querySelector( "#pages .tags-helper" ).click();
                let grid = this.closest( ".editor-grid" );
                grid.classList.remove( "tags-opened" );
            }

            cm.focus();
            cm.refresh();
        } );
    } );

    // fix glitches codemirror
    let fix = document.querySelector( "aside a[href='#pages']" );
    if ( fix ) fix.addEventListener( "click", function( e ) {
        setTimeout( function( e ) {
            if ( window.cm ) {
                cm.refresh();
                cm.focus();
            }
        }, 50 );
    } );

    // Транслитерация URL
    let tr_url = document.querySelector( "#pages .page-editor-grid .url-translit" );
    if ( tr_url ) tr_url.addEventListener( "click", function( e ) {
        let url = document.querySelector( "#pages .page-editor-grid .page-properties input[name='title']" ).value;
        let tr_url = url_translit( url );
        this.previousElementSibling.value = tr_url;
    } );
    function url_translit( url ) {
        url = url.toLowerCase();
        for ( let i in cms.tr ) {
            let re = new RegExp( i, "g" );
            url = url.replace( re, cms.tr[i] );
        }
        url = url.replace( / +/g, "-" );
        url = url.replace( /[^-a-z0-9_]+/g, "" );
        url = url.replace( /^[-_]+|[-_]+$/g, "" );
        return url;
    }

    function update_home() {
        document.querySelectorAll( `#pages .pages-grid [data-id]` ).forEach( function( page ) {
            if ( page.querySelector( `.page-name[href="${cms.base_path}"]` ) ) {
                page.classList.add( "home" );
            } else {
                page.classList.remove( "home" );
            }
        } );
    }


    // Замена текста на всех страницах
    document.querySelectorAll( "#pages-utils .replace-btn" ).forEach( function( button ) {
        button.addEventListener( "click", function( e ) {
            let data = {
                fn: "replace_in_pages",
                table: document.querySelector( "#pages-utils input[name='table']" ).value,
                id_col: document.querySelector( "#pages-utils input[name='id_col']" ).value,
                column: document.querySelector( "#pages-utils input[name='column']" ).value,
                search_regex: document.querySelector( "#pages-utils input[name='search_regex']" ).value,
                replace: document.querySelector( "#pages-utils input[name='replace']" ).value,
            };
            if ( data.search_regex && confirm( _( "replace_in_pages_confirm" ) ) ) {
                api( data, function( r ) {
                    if ( r.info_text ) {
                        notify( r.info_text, r.info_class, r.info_time );
                    }
                } );
            }
        } );
    } );

} );