Mercurial
comparison .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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:78edf6b517a0 |
---|---|
1 document.addEventListener( "DOMContentLoaded", function( event ) { | |
2 | |
3 function _( str ) { | |
4 return __( str, "menu.mod.php" ); | |
5 } | |
6 | |
7 function select3( selects ) { | |
8 selects.forEach( function( select ) { | |
9 // именно keyup чтобы не пересекалось с input | |
10 select.querySelector( "input" ).addEventListener( "keyup", function( e ) { | |
11 api( { fn: "get_search_pages_list", search: this.value }, function( r ) { | |
12 if ( r.html ) { | |
13 select.querySelector( ".list-search" ).innerHTML = r.html; | |
14 select.querySelectorAll( ".list-search li" ).forEach( function( li ) { | |
15 li.addEventListener( "click", select3_li ); | |
16 } ); | |
17 } | |
18 } ); | |
19 } ); | |
20 // prevent close dropdown when click on input | |
21 select.querySelector( "input" ).addEventListener( "click", function( e ) { | |
22 e.stopPropagation(); | |
23 } ); | |
24 // show/hide dropdown list | |
25 select.querySelector( ".field-select" ).addEventListener( "click", function ( event ) { | |
26 event.stopPropagation(); | |
27 this.parentElement.classList.toggle( "open" ); | |
28 let list = event.currentTarget.nextElementSibling; | |
29 | |
30 // это можно убрать если переставить стили на родителя | |
31 list.classList.toggle( "open" ); | |
32 | |
33 if ( list.classList.contains( "open" ) ) { | |
34 let input = select.querySelector( "input" ); | |
35 input.focus(); | |
36 input.dispatchEvent( new Event( "keyup" ) ); | |
37 } | |
38 } ); | |
39 } ); | |
40 } | |
41 | |
42 // click selected item | |
43 function select3_li( e ) { | |
44 let id = this.getAttribute( "data-id" ); | |
45 let url = this.getAttribute( "data-url" ); | |
46 let title = this.innerText; | |
47 let input_title = this.closest( ".select-grid" ).querySelector( ".field-select" ); | |
48 let old_id = input_title.getAttribute( "data-old-id" ); | |
49 input_title.querySelector( ".value" ).innerText = title; | |
50 input_title.setAttribute( "data-id", id ); | |
51 // поменять url в поле ввода | |
52 let input_url = this.closest( ".menu-prop" ).querySelector( "input[name='url']" ); | |
53 input_url.value = url; | |
54 // поменять заголовок и ссылку вверху | |
55 let a = this.closest( ".item" ).querySelector( ":scope > a" ); | |
56 a.setAttribute( "href", cms.base_path + url ); | |
57 let replaced_title = this.closest( ".menu-prop" ).querySelector( `input[name="title"]` ).value; | |
58 if ( replaced_title ) title = replaced_title; | |
59 a.innerText = title; | |
60 if ( id === "0" ) { | |
61 input_url.removeAttribute( "disabled" ); | |
62 } else { | |
63 input_url.setAttribute( "disabled", true ); | |
64 } | |
65 // remove li except data-id=0 | |
66 Array.from( this.parentElement.children ).forEach( function( li ) { | |
67 if ( li.getAttribute( "data-id" ) !== "0" ) { | |
68 li.remove(); | |
69 } | |
70 } ); | |
71 // close dropdown list | |
72 input_title.click(); | |
73 // изменение | |
74 if ( old_id != id ) { | |
75 input_title.dispatchEvent( new Event( "input" ) ); | |
76 } | |
77 } | |
78 | |
79 api( { fn: "get_menu_items" }, set_menu_items ); | |
80 | |
81 function set_menu_items( r ) { | |
82 | |
83 document.querySelector( "#menu .menu-grid" ).innerHTML = r.list; | |
84 | |
85 // set parents for each menu item | |
86 document.querySelectorAll( "#menu [data-parent]" ).forEach( function( el ) { | |
87 el.nextElementSibling.insertAdjacentHTML( "beforeend", r.parents ); | |
88 let pid = el.getAttribute( "data-parent" ); | |
89 let parent = el.nextElementSibling.querySelector( `[value="${pid}"]` ).innerText.trim(); | |
90 el.querySelector( ".value" ).innerText = parent; | |
91 // remove self | |
92 let self = el.closest( "[data-item]" ).getAttribute( "data-item" ); | |
93 self = el.nextElementSibling.querySelector( `[value="${self}"]` ); | |
94 if ( self ) { | |
95 self.remove(); | |
96 } | |
97 } ); | |
98 | |
99 // selects | |
100 let selects = document.querySelectorAll( "#menu .select-grid" ); | |
101 select3( selects ); | |
102 | |
103 // Select | |
104 document.querySelectorAll( "#menu .area-select-grid .field-select-menu, #menu .parent-select-grid .field-select" ).forEach( function( select ) { | |
105 select.addEventListener( "click", function( e ) { | |
106 e.stopPropagation(); | |
107 this.parentElement.classList.toggle( "open" ); | |
108 | |
109 // это можно убрать если переставить стили на родителя | |
110 select.nextElementSibling.classList.toggle( "open" ); | |
111 } ); | |
112 } ); | |
113 // Option for menu | |
114 document.querySelectorAll( "#menu .area-select-grid .field-options .option" ).forEach( function( option ) { | |
115 option.addEventListener( "click", function( e ) { | |
116 let input = this.closest( ".area-select-grid" ).querySelector( ".field-select-menu" ); | |
117 let old_val = input.getAttribute( "data-menu-area" ); | |
118 let new_val = this.getAttribute( "value" ); | |
119 if ( old_val != new_val ) { | |
120 input.querySelector( ".value" ).innerText = this.innerText; | |
121 input.setAttribute( "data-menu-area", new_val ); | |
122 // Событие изменения | |
123 let event = new Event( "input" ); | |
124 input.dispatchEvent( event ); | |
125 } | |
126 //e.stopPropagation(); убираем чтобы закрылось автоматически | |
127 } ); | |
128 } ); | |
129 // Option for item | |
130 document.querySelectorAll( "#menu .parent-select-grid .field-options .option" ).forEach( function( option ) { | |
131 option.addEventListener( "click", parent_li_click ); | |
132 } ); | |
133 function parent_li_click( e ) { | |
134 let input = this.closest( ".parent-select-grid" ).querySelector( ".field-select" ); | |
135 let old_val = input.getAttribute( "data-parent" ); | |
136 let new_val = this.getAttribute( "value" ); | |
137 if ( old_val != new_val ) { | |
138 input.querySelector( ".value" ).innerText = this.innerText.trim(); | |
139 input.setAttribute( "data-parent", new_val ); | |
140 // Событие изменения | |
141 let event = new Event( "input" ); | |
142 input.dispatchEvent( event ); | |
143 } | |
144 //e.stopPropagation(); убираем чтобы закрылось автоматически | |
145 } | |
146 | |
147 // Toggle Menu Properties | |
148 let prop_buttons = document.querySelectorAll( "#menu .menu-buttons .prop" ); | |
149 prop_buttons.forEach( function( button ) { | |
150 button.addEventListener( "click", function( e ) { | |
151 e.stopPropagation(); | |
152 this.closest( "[data-item]" ).classList.toggle( "open" ); | |
153 } ); | |
154 } ); | |
155 | |
156 // Отлавливать изменения полей в меню чтобы показать Сохранить | |
157 document.querySelectorAll( "#menu .menu" ).forEach( function( menu ) { | |
158 menu.querySelectorAll( "input, .field-select-menu" ).forEach( function( input ) { | |
159 input.addEventListener( "input", function( event ) { | |
160 let menu = this.closest( ".menu" ); | |
161 menu.classList.add( "changed" ); | |
162 } ); | |
163 } ); | |
164 } ); | |
165 | |
166 // Отлавливать изменения полей в пунктах чтобы показать Сохранить | |
167 document.querySelectorAll( "#menu .item" ).forEach( function( item ) { | |
168 item.querySelectorAll( "input, .field-select" ).forEach( function( input ) { | |
169 input.addEventListener( "input", function( event ) { | |
170 let item = this.closest( ".item" ); | |
171 item.classList.add( "changed" ); | |
172 } ); | |
173 } ); | |
174 } ); | |
175 | |
176 // Save Properties | |
177 document.querySelectorAll( "#menu .menu-buttons .save" ).forEach( function( button ) { | |
178 button.addEventListener( "click", function( e ) { | |
179 e.stopPropagation(); | |
180 | |
181 let item = this.closest( "[data-item]" ); | |
182 let mid = item.getAttribute( "data-item" ); | |
183 | |
184 let area = item.querySelector( "[data-menu-area]" ); | |
185 if ( area ) { | |
186 area = area.getAttribute( "data-menu-area" ); | |
187 } | |
188 | |
189 let tag_title = item.querySelector( "[name='tag_title']" ); | |
190 if ( tag_title ) { | |
191 tag_title = tag_title.value; | |
192 } | |
193 | |
194 let url = item.querySelector( "[name='url']" ); | |
195 if ( url ) { | |
196 url = url.value; | |
197 } | |
198 | |
199 let id = item.querySelector( "[name='id']" ); | |
200 if ( id ) { | |
201 id = id.getAttribute( "data-id" ); | |
202 } | |
203 | |
204 let pid_el = item.querySelector( "[data-parent]" ); | |
205 let pid,old_pid; | |
206 if ( pid_el ) { | |
207 pid = pid_el.getAttribute( "data-parent" ); | |
208 old_pid = pid_el.getAttribute( "data-old-parent" ); | |
209 } | |
210 | |
211 let target = item.querySelector( "[name='targetblank']" ); | |
212 if ( target ) { | |
213 target = target.checked; | |
214 } | |
215 | |
216 let old_sort_el = item.querySelector( "[name='sort']" ); | |
217 let old_sort = old_sort_el.getAttribute( "data-sort" ); | |
218 | |
219 let data = { | |
220 fn: "save_menu_item", | |
221 mid: mid, | |
222 title: item.querySelector( "[name='title']" ).value, | |
223 tag_title: tag_title, | |
224 url: url, | |
225 id: id, | |
226 pid: pid, | |
227 old_pid: old_pid, | |
228 classes: item.querySelector( "[name='classes']" ).value, | |
229 sort: item.querySelector( "[name='sort']" ).value, | |
230 area: area, | |
231 target: target | |
232 } | |
233 document.querySelectorAll( `#menu [data-item]` ).forEach( function( el ) { | |
234 el.classList.remove( "last-edited" ); | |
235 } ); | |
236 api( data, function( r ) { | |
237 if ( r.ok == "false" ) { | |
238 notify( r.info_text, r.info_class, r.info_time ); | |
239 } | |
240 if ( r.ok == "true" ) { | |
241 item.classList.remove( "changed" ); | |
242 let item_with_childs = item.parentElement; | |
243 if ( r.list ) { | |
244 set_menu_items( r ); | |
245 } else if ( old_sort != data.sort ) { | |
246 // пересортировка имеет смысл если не было замены всего списка | |
247 // обновить сортировку для сравнения | |
248 old_sort_el.setAttribute( "data-sort", data.sort ); | |
249 let grid; | |
250 if ( data.area === null ) { | |
251 grid = item.closest( ".items-grid" ); | |
252 } else { | |
253 grid = item.closest( ".menu-grid" ); | |
254 } | |
255 let before = null; | |
256 for ( let n = 0; n < grid.children.length; n++ ) { | |
257 let node = grid.children[n]; | |
258 if ( node != item_with_childs ) { | |
259 let sort = node.querySelector( '[data-item] [name="sort"]' ).value; | |
260 if ( +data.sort < +sort ) { | |
261 before = node; | |
262 break; | |
263 } | |
264 } | |
265 }; | |
266 grid.insertBefore( item_with_childs, before ); | |
267 } | |
268 | |
269 // Замена title | |
270 let title; | |
271 if ( data.title != "" ) | |
272 title = data.title; | |
273 else | |
274 title = item.querySelector( ".select-grid .value" ).innerText; | |
275 item.querySelector( ":scope > a" ).innerText = title; | |
276 | |
277 // обновить родителей у подпунктов | |
278 // поскольку мог измениться title | |
279 // и только у строго дочерних, которых может и не быть | |
280 let childs_grid = item.nextElementSibling; | |
281 if ( childs_grid ) { | |
282 let childs_selector = childs_grid.querySelectorAll( ":scope > div > .item .parent-select-grid" ); | |
283 | |
284 if ( childs_selector ) childs_selector.forEach( function( grid ) { | |
285 let input = grid.querySelector( "[data-parent]" ); | |
286 let pid = input.getAttribute( "data-parent" ); | |
287 let options = grid.querySelector( ":scope > .field-options" ); | |
288 options.innerHTML = r.parents; | |
289 options.querySelectorAll( ".option" ).forEach( function( option ) { | |
290 option.addEventListener( "click", parent_li_click ); | |
291 } ); | |
292 // тайтл вычисляется выше | |
293 input.querySelector( ".value" ).innerText = title; | |
294 } ); | |
295 } | |
296 | |
297 // Last Edited Marker | |
298 setTimeout( function() { | |
299 document.querySelector( `#menu [data-item="${data.mid}"]` ).classList.add( "last-edited" ); | |
300 }, 200 ); | |
301 } | |
302 } ); | |
303 } ); | |
304 } ); | |
305 | |
306 // Delete Menu or Item | |
307 document.querySelectorAll( "#menu .menu-buttons .del" ).forEach( function( button ) { | |
308 button.addEventListener( "click", function( e ) { | |
309 e.stopPropagation(); | |
310 clear_highlite(); | |
311 let item = button.closest( ".item,.menu" ).parentNode; | |
312 item.classList.add( "will-be-deleted" ); | |
313 setTimeout( function() { | |
314 if ( ! confirm( __( "confirm_delete", "menu.mod.php" ) ) ) { | |
315 item.classList.remove( "will-be-deleted" ); | |
316 return; | |
317 } | |
318 let data = { | |
319 fn: "del_menu_item", | |
320 mid: button.closest( "[data-item]" ).getAttribute( "data-item" ) | |
321 }; | |
322 api( data, function( r ) { | |
323 if ( r.info_text ) { | |
324 notify( r.info_text, r.info_class, r.info_time ); | |
325 if ( r.info_class == "info-success" ) { | |
326 set_menu_items( r ); | |
327 } | |
328 } | |
329 } ); | |
330 }, 20 ); | |
331 } ); | |
332 } ); | |
333 | |
334 // Create Item | |
335 document.querySelectorAll( "#menu .main-main .append" ).forEach( function( button ) { | |
336 button.addEventListener( "click", modMenuCreate ); | |
337 } ); | |
338 | |
339 // Линия к родителю | |
340 let items = document.querySelectorAll( "#menu .item" ); | |
341 items.forEach( function( el ) { | |
342 el.addEventListener( "click", function( e ) { | |
343 // Если опускание мыши было не на нас, то игнорим | |
344 if ( mouse_down_el != this ) return; | |
345 mouse_down_el = null; | |
346 let highlited = this.classList.contains( "highlite-children" ); | |
347 clear_highlite(); | |
348 // Назначить классы | |
349 if ( ! highlited ) { | |
350 this.classList.add( "highlite-children" ); | |
351 this.closest( ".items-grid" ).classList.add( "highlite-parent" );; | |
352 } | |
353 } ); | |
354 } ); | |
355 // Опускание мыши | |
356 // Возможна ситуация когда нажатие мыши было на кнопке Свойства, | |
357 // а отпускание на плашке. В этом случае может появиться линия к родителю. | |
358 // Поэтому нужно записывать что опускание было на плашке или панельке кнопок. | |
359 let mouse_down_el; // элемент на котором было опускание мыши | |
360 items.forEach( function( el ) { | |
361 el.addEventListener( "mousedown", function( e ) { | |
362 // чтобы проигнорировать клики по дочерним | |
363 if ( e.target == this ) | |
364 mouse_down_el = this; | |
365 } ); | |
366 // див с кнопками можно тыкать | |
367 let mb = el.querySelector( ".menu-buttons" ); | |
368 if ( mb ) mb.addEventListener( "mousedown", function( e ) { | |
369 // чтобы проигнорировать клики по дочерним | |
370 if ( e.target == this ) | |
371 mouse_down_el = this.parentElement; | |
372 } ); | |
373 } ); | |
374 // Сбросить классы | |
375 function clear_highlite() { | |
376 document.querySelectorAll( "#menu .items-grid.highlite-parent" ).forEach( function( el ) { | |
377 el.classList.remove( "highlite-parent" ); | |
378 } ); | |
379 document.querySelectorAll( "#menu .item.highlite-children" ).forEach( function( el ) { | |
380 el.classList.remove( "highlite-children" ); | |
381 } ); | |
382 } | |
383 | |
384 } | |
385 | |
386 // Select | |
387 // Закрытие выпадающих списков при кликах вне их, а так же по ним | |
388 document.body.addEventListener( "click", function( e ) { | |
389 // это можно убрать если переставить стили на родителя | |
390 document.querySelectorAll( "#menu .field-options" ).forEach( function( list ) { | |
391 list.classList.remove( "open" ); | |
392 } ); | |
393 | |
394 document.querySelectorAll( "#menu .area-select-grid, #menu .select-grid, #menu .parent-select-grid" ).forEach( function( list ) { | |
395 list.classList.remove( "open" ); | |
396 } ); | |
397 } ); | |
398 | |
399 // Создание меню или пункта меню | |
400 function modMenuCreate( e ) { | |
401 e.stopPropagation(); | |
402 let pid = this.closest( "[data-item]" ).getAttribute( "data-item" ); | |
403 api( { fn : "create_menu_item", pid : pid }, function( r ) { | |
404 if ( r.info_text ) { | |
405 notify( r.info_text, r.info_class, r.info_time ); | |
406 if ( r.info_class == "info-success" ) { | |
407 set_menu_items( r ); | |
408 let item = document.querySelector( `#menu [data-item="${r.mid}"]` ); | |
409 item.classList.add( "open" ); | |
410 item.classList.add( "last-edited" ); | |
411 // Докрутить к новому элементу | |
412 item.scrollIntoView( { behavior: "smooth", block: "center" } ); | |
413 } | |
414 } | |
415 } ); | |
416 } | |
417 | |
418 // Create Menu | |
419 document.querySelector( "#menu .main-footer .create" ).addEventListener( "click", modMenuCreate ); | |
420 | |
421 // update page used in menu | |
422 document.body.addEventListener( "update_menu", function( e ) { | |
423 api( { fn: "get_menu_items" }, set_menu_items ); | |
424 } ); | |
425 | |
426 } ); |