diff .cms/js/menu.js @ 0:78edf6b517a0 draft

24.10
author Coffee CMS <info@coffee-cms.ru>
date Fri, 11 Oct 2024 22:40:23 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.cms/js/menu.js	Fri Oct 11 22:40:23 2024 +0000
@@ -0,0 +1,426 @@
+document.addEventListener( "DOMContentLoaded", function( event ) {
+
+    function _( str ) {
+        return __( str, "menu.mod.php" );
+    }
+
+    function select3( selects ) {
+        selects.forEach( function( select ) {
+            // именно keyup чтобы не пересекалось с input
+            select.querySelector( "input" ).addEventListener( "keyup", function( e ) {
+                api( { fn: "get_search_pages_list", search: this.value }, function( r ) {
+                    if ( r.html ) {
+                        select.querySelector( ".list-search" ).innerHTML = r.html;
+                        select.querySelectorAll( ".list-search li" ).forEach( function( li ) {
+                            li.addEventListener( "click", select3_li );
+                        } );
+                    }
+                } );
+            } );
+            // prevent close dropdown when click on input
+            select.querySelector( "input" ).addEventListener( "click", function( e ) {
+                e.stopPropagation();
+            } );
+            // show/hide dropdown list
+            select.querySelector( ".field-select" ).addEventListener( "click", function ( event ) {
+                event.stopPropagation();
+                this.parentElement.classList.toggle( "open" );
+                let list = event.currentTarget.nextElementSibling;
+
+                // это можно убрать если переставить стили на родителя
+                list.classList.toggle( "open" );
+                
+                if ( list.classList.contains( "open" ) ) {
+                    let input = select.querySelector( "input" );
+                    input.focus();
+                    input.dispatchEvent( new Event( "keyup" ) );
+                }
+            } );
+        } );
+    }
+
+    // click selected item
+    function select3_li( e ) {
+        let id    = this.getAttribute( "data-id" );
+        let url   = this.getAttribute( "data-url" );
+        let title = this.innerText;
+        let input_title = this.closest( ".select-grid" ).querySelector( ".field-select" );
+        let old_id = input_title.getAttribute( "data-old-id" );
+        input_title.querySelector( ".value" ).innerText = title;
+        input_title.setAttribute( "data-id", id );
+        // поменять url в поле ввода
+        let input_url = this.closest( ".menu-prop" ).querySelector( "input[name='url']" );
+        input_url.value = url;
+        // поменять заголовок и ссылку вверху
+        let a = this.closest( ".item" ).querySelector( ":scope > a" );
+        a.setAttribute( "href", cms.base_path + url );
+        let replaced_title = this.closest( ".menu-prop" ).querySelector( `input[name="title"]` ).value;
+        if ( replaced_title ) title = replaced_title;
+        a.innerText = title;
+        if ( id === "0" ) {
+            input_url.removeAttribute( "disabled" );
+        } else {
+            input_url.setAttribute( "disabled", true );
+        }
+        // remove li except data-id=0
+        Array.from( this.parentElement.children ).forEach( function( li ) {
+            if ( li.getAttribute( "data-id" ) !== "0" ) {
+                li.remove();
+            }
+        } );
+        // close dropdown list
+        input_title.click();
+        // изменение
+        if ( old_id != id ) {
+            input_title.dispatchEvent( new Event( "input" ) );
+        }
+    }
+
+    api( { fn: "get_menu_items" }, set_menu_items );
+
+    function set_menu_items( r ) {
+
+        document.querySelector( "#menu .menu-grid" ).innerHTML = r.list;
+
+        // set parents for each menu item
+        document.querySelectorAll( "#menu [data-parent]" ).forEach( function( el ) {
+            el.nextElementSibling.insertAdjacentHTML( "beforeend", r.parents );
+            let pid = el.getAttribute( "data-parent" );
+            let parent = el.nextElementSibling.querySelector( `[value="${pid}"]` ).innerText.trim();
+            el.querySelector( ".value" ).innerText = parent;
+            // remove self
+            let self   = el.closest( "[data-item]" ).getAttribute( "data-item" );
+            self = el.nextElementSibling.querySelector( `[value="${self}"]` );
+            if ( self ) {
+                self.remove();
+            }
+        } );
+
+        // selects
+        let selects = document.querySelectorAll( "#menu .select-grid" );
+        select3( selects );
+
+        // Select
+        document.querySelectorAll( "#menu .area-select-grid .field-select-menu, #menu .parent-select-grid .field-select" ).forEach( function( select ) {
+            select.addEventListener( "click", function( e ) {
+                e.stopPropagation();
+                this.parentElement.classList.toggle( "open" );
+
+                // это можно убрать если переставить стили на родителя
+                select.nextElementSibling.classList.toggle( "open" );
+            } );
+        } );
+        // Option for menu
+        document.querySelectorAll( "#menu .area-select-grid .field-options .option" ).forEach( function( option ) {
+            option.addEventListener( "click", function( e ) {
+                let input = this.closest( ".area-select-grid" ).querySelector( ".field-select-menu" );
+                let old_val = input.getAttribute( "data-menu-area" );
+                let new_val = this.getAttribute( "value" );
+                if ( old_val != new_val ) {
+                    input.querySelector( ".value" ).innerText = this.innerText;
+                    input.setAttribute( "data-menu-area", new_val );
+                    // Событие изменения
+                    let event = new Event( "input" );
+                    input.dispatchEvent( event );
+                }
+                //e.stopPropagation(); убираем чтобы закрылось автоматически
+            } );
+        } );
+        // Option for item
+        document.querySelectorAll( "#menu .parent-select-grid .field-options .option" ).forEach( function( option ) {
+            option.addEventListener( "click", parent_li_click );
+        } );
+        function parent_li_click( e ) {
+            let input = this.closest( ".parent-select-grid" ).querySelector( ".field-select" );
+            let old_val = input.getAttribute( "data-parent" );
+            let new_val = this.getAttribute( "value" );
+            if ( old_val != new_val ) {
+                input.querySelector( ".value" ).innerText = this.innerText.trim();
+                input.setAttribute( "data-parent", new_val );
+                // Событие изменения
+                let event = new Event( "input" );
+                input.dispatchEvent( event );
+            }
+            //e.stopPropagation(); убираем чтобы закрылось автоматически
+        }
+
+        // Toggle Menu Properties
+        let prop_buttons = document.querySelectorAll( "#menu .menu-buttons .prop" );
+        prop_buttons.forEach( function( button ) {
+            button.addEventListener( "click", function( e ) {
+                e.stopPropagation();
+                this.closest( "[data-item]" ).classList.toggle( "open" );
+            } );
+        } );
+
+        // Отлавливать изменения полей в меню чтобы показать Сохранить
+        document.querySelectorAll( "#menu .menu" ).forEach( function( menu ) {
+            menu.querySelectorAll( "input, .field-select-menu" ).forEach( function( input ) {
+                input.addEventListener( "input", function( event ) {
+                    let menu = this.closest( ".menu" );
+                    menu.classList.add( "changed" );
+                } );
+            } );
+        } );
+
+        // Отлавливать изменения полей в пунктах чтобы показать Сохранить
+        document.querySelectorAll( "#menu .item" ).forEach( function( item ) {
+            item.querySelectorAll( "input, .field-select" ).forEach( function( input ) {
+                input.addEventListener( "input", function( event ) {
+                    let item = this.closest( ".item" );
+                    item.classList.add( "changed" );
+                } );
+            } );
+        } );
+
+        // Save Properties
+        document.querySelectorAll( "#menu .menu-buttons .save" ).forEach( function( button ) {
+            button.addEventListener( "click", function( e ) {
+                e.stopPropagation();
+
+                let item = this.closest( "[data-item]" );
+                let mid  = item.getAttribute( "data-item" );
+
+                let area = item.querySelector( "[data-menu-area]" );
+                if ( area ) {
+                    area = area.getAttribute( "data-menu-area" );
+                }
+
+                let tag_title = item.querySelector( "[name='tag_title']" );
+                if ( tag_title ) {
+                    tag_title = tag_title.value;
+                }
+
+                let url = item.querySelector( "[name='url']" );
+                if ( url ) {
+                    url = url.value;
+                }
+
+                let id = item.querySelector( "[name='id']" );
+                if ( id ) {
+                    id = id.getAttribute( "data-id" );
+                }
+
+                let pid_el = item.querySelector( "[data-parent]" );
+                let pid,old_pid;
+                if ( pid_el ) {
+                    pid = pid_el.getAttribute( "data-parent" );
+                    old_pid = pid_el.getAttribute( "data-old-parent" );
+                }
+
+                let target = item.querySelector( "[name='targetblank']" );
+                if ( target ) {
+                    target = target.checked;
+                }
+
+                let old_sort_el = item.querySelector( "[name='sort']" );
+                let old_sort = old_sort_el.getAttribute( "data-sort" );
+
+                let data = {
+                    fn:        "save_menu_item",
+                    mid:       mid,
+                    title:     item.querySelector( "[name='title']" ).value,
+                    tag_title: tag_title,
+                    url:       url,
+                    id:        id,
+                    pid:       pid,
+                    old_pid:   old_pid,
+                    classes:   item.querySelector( "[name='classes']" ).value,
+                    sort:      item.querySelector( "[name='sort']" ).value,
+                    area:      area,
+                    target:    target
+                }
+                document.querySelectorAll( `#menu [data-item]` ).forEach( function( el ) {
+                    el.classList.remove( "last-edited" );
+                } );
+                api( data, function( r ) {
+                    if ( r.ok == "false" ) {
+                        notify( r.info_text, r.info_class, r.info_time );
+                    }
+                    if ( r.ok == "true" ) {
+                        item.classList.remove( "changed" );
+                        let item_with_childs = item.parentElement;
+                        if ( r.list ) {
+                            set_menu_items( r );
+                        } else if ( old_sort != data.sort ) {
+                        // пересортировка имеет смысл если не было замены всего списка
+                            // обновить сортировку для сравнения
+                            old_sort_el.setAttribute( "data-sort", data.sort );
+                            let grid;
+                            if ( data.area === null ) {
+                                grid = item.closest( ".items-grid" );
+                            } else {
+                                grid = item.closest( ".menu-grid" );
+                            }
+                            let before = null;
+                            for ( let n = 0; n < grid.children.length; n++ ) {
+                                let node = grid.children[n];
+                                if ( node != item_with_childs ) {
+                                    let sort = node.querySelector( '[data-item] [name="sort"]' ).value;
+                                    if ( +data.sort < +sort ) {
+                                        before = node;
+                                        break;
+                                    }
+                                }
+                            };
+                            grid.insertBefore( item_with_childs, before );
+                        }
+                        
+                        // Замена title
+                        let title;
+                        if ( data.title != "" )
+                            title = data.title;
+                        else
+                            title = item.querySelector( ".select-grid .value" ).innerText;
+                        item.querySelector( ":scope > a" ).innerText = title;
+                        
+                        // обновить родителей у подпунктов
+                        // поскольку мог измениться title
+                        // и только у строго дочерних, которых может и не быть
+                        let childs_grid = item.nextElementSibling;
+                        if ( childs_grid ) {
+                            let childs_selector = childs_grid.querySelectorAll( ":scope > div > .item .parent-select-grid" );
+                            
+                            if ( childs_selector ) childs_selector.forEach( function( grid ) {
+                                let input = grid.querySelector( "[data-parent]" );
+                                let pid = input.getAttribute( "data-parent" );
+                                let options = grid.querySelector( ":scope > .field-options" );
+                                options.innerHTML = r.parents;
+                                options.querySelectorAll( ".option" ).forEach( function( option ) {
+                                    option.addEventListener( "click", parent_li_click );
+                                } );
+                                // тайтл вычисляется выше
+                                input.querySelector( ".value" ).innerText = title;
+                            } );
+                        }
+                        
+                        // Last Edited Marker
+                        setTimeout( function() {
+                            document.querySelector( `#menu [data-item="${data.mid}"]` ).classList.add( "last-edited" );
+                        }, 200 );
+                    }
+                } );
+            } );
+        } );
+
+        // Delete Menu or Item
+        document.querySelectorAll( "#menu .menu-buttons .del" ).forEach( function( button ) {
+            button.addEventListener( "click", function( e ) {
+                e.stopPropagation();
+                clear_highlite();
+                let item = button.closest( ".item,.menu" ).parentNode;
+                item.classList.add( "will-be-deleted" );
+                setTimeout( function() {
+                if ( ! confirm( __( "confirm_delete", "menu.mod.php" ) ) ) {
+                    item.classList.remove( "will-be-deleted" );
+                    return;
+                }
+                let data = {
+                    fn: "del_menu_item",
+                    mid: button.closest( "[data-item]" ).getAttribute( "data-item" )
+                };
+                api( data, function( r ) {
+                    if ( r.info_text ) {
+                        notify( r.info_text, r.info_class, r.info_time );
+                        if ( r.info_class == "info-success" ) {
+                            set_menu_items( r );
+                        }
+                    }
+                } );
+                }, 20 );
+            } );
+        } );
+
+        // Create Item
+        document.querySelectorAll( "#menu .main-main .append" ).forEach( function( button ) {
+            button.addEventListener( "click", modMenuCreate );
+        } );
+
+        // Линия к родителю
+        let items = document.querySelectorAll( "#menu .item" );
+        items.forEach( function( el ) {
+            el.addEventListener( "click", function( e ) {
+                // Если опускание мыши было не на нас, то игнорим
+                if ( mouse_down_el != this ) return;
+                mouse_down_el = null;
+                let highlited = this.classList.contains( "highlite-children" );
+                clear_highlite();
+                // Назначить классы
+                if ( ! highlited ) {
+                    this.classList.add( "highlite-children" );
+                    this.closest( ".items-grid" ).classList.add( "highlite-parent" );;
+                }
+            } );
+        } );
+        // Опускание мыши
+        // Возможна ситуация когда нажатие мыши было на кнопке Свойства,
+        // а отпускание на плашке. В этом случае может появиться линия к родителю.
+        // Поэтому нужно записывать что опускание было на плашке или панельке кнопок.
+        let mouse_down_el; // элемент на котором было опускание мыши
+        items.forEach( function( el ) {
+            el.addEventListener( "mousedown", function( e ) {
+                // чтобы проигнорировать клики по дочерним
+                if ( e.target == this )
+                    mouse_down_el = this;
+            } );
+            // див с кнопками можно тыкать
+            let mb = el.querySelector( ".menu-buttons" );
+            if ( mb ) mb.addEventListener( "mousedown", function( e ) {
+                // чтобы проигнорировать клики по дочерним
+                if ( e.target == this )
+                    mouse_down_el = this.parentElement;
+            } );
+        } );
+        // Сбросить классы
+        function clear_highlite() {
+            document.querySelectorAll( "#menu .items-grid.highlite-parent" ).forEach( function( el ) {
+                el.classList.remove( "highlite-parent" );
+            } );
+            document.querySelectorAll( "#menu .item.highlite-children" ).forEach( function( el ) {
+                el.classList.remove( "highlite-children" );
+            } );
+        }
+
+    }
+
+    // Select
+    // Закрытие выпадающих списков при кликах вне их, а так же по ним
+    document.body.addEventListener( "click", function( e ) {
+        // это можно убрать если переставить стили на родителя
+        document.querySelectorAll( "#menu .field-options" ).forEach( function( list ) {
+            list.classList.remove( "open" );
+        } );
+        
+        document.querySelectorAll( "#menu .area-select-grid, #menu .select-grid, #menu .parent-select-grid" ).forEach( function( list ) {
+            list.classList.remove( "open" );
+        } );
+    } );
+
+    // Создание меню или пункта меню
+    function modMenuCreate( e ) {
+        e.stopPropagation();
+        let pid = this.closest( "[data-item]" ).getAttribute( "data-item" );
+        api( { fn : "create_menu_item", pid : pid }, function( r ) {
+            if ( r.info_text ) {
+                notify( r.info_text, r.info_class, r.info_time );
+                if ( r.info_class == "info-success" ) {
+                    set_menu_items( r );
+                    let item = document.querySelector( `#menu [data-item="${r.mid}"]` );
+                    item.classList.add( "open" );
+                    item.classList.add( "last-edited" );
+                    // Докрутить к новому элементу
+                    item.scrollIntoView( { behavior: "smooth", block: "center" } );
+                }
+            }
+        } );
+    }
+    
+    // Create Menu
+    document.querySelector( "#menu .main-footer .create" ).addEventListener( "click", modMenuCreate );
+    
+    // update page used in menu
+    document.body.addEventListener( "update_menu", function( e ) {
+        api( { fn: "get_menu_items" }, set_menu_items );
+    } );
+
+} );