1import $ from 'jquery'; 2import { htmlspecialchars } from './escape.js'; 3 4/** 5 * The category tree of the admin forms â port of getThreeChapters()/ 6 * getChapterHtml() from admin/js/generate.js. The category screen renders it 7 * with radios (one parent), the product form with checkboxes (many sections); 8 * both stitch children into their parent's html while looping levels down, so a 9 * category whose `parent` is not a JSON number lands at the root. 10 */ 11export function renderTree(list, maxLevel, inputType) { 12 const wrap = document.querySelector('.form__tree-list'); 13 if (wrap === null) { 14 return; 15 } 16 const childs = []; 17 let code = ''; 18 19 for (let lev = maxLevel; lev > 0; lev--) { 20 for (const key in list) { 21 const cat = list[key]; 22 if (cat.level != lev) { 23 continue; 24 } 25 const html = chapterHtml(cat, childs[cat.id], cat.level, inputType); 26 if (typeof cat.parent === 'number') { 27 childs[cat.parent] = typeof childs[cat.parent] !== 'undefined' ? childs[cat.parent] + html : html; 28 } else { 29 code += html; 30 } 31 } 32 } 33 wrap.innerHTML = code; 34} 35 36/** Port of getChapterHtml(cat, child, level, inputType). */ 37export function chapterHtml(cat, child, level, inputType) { 38 const childLevel = level + 1; 39 let item = typeof child !== 'undefined' 40 ? '<li class="form__tree-parent form__tree-item"><span class="form__tree-toggle"><i class="ico__select"></i></span>' 41 : '<li class="form__tree-item">'; 42 43 item += '<label class="form__tree-label">' + 44 '<input type="' + inputType + '" value="' + cat['id'] + '" data-text=\'' + htmlspecialchars(cat.chaptername) + '\' name="chapter">' + 45 '<i class="form__tree-check"></i>' + 46 '<span>' + htmlspecialchars(cat.chaptername) + '</span>' + 47 '</label>'; 48 if (typeof child !== 'undefined') { 49 item = item + '<ul class="form__tree-children form__tree-children-' + inputType + '" data-level="' + childLevel + '">' + child + '</ul>'; 50 } 51 52 return item + '</li>'; 53} 54 55/** 56 * Port of checkedTree(): every checked box becomes a tag carrying the hidden 57 * `chapters[]` field the product form submits. 58 */ 59export function checkedTree() { 60 let html = ''; 61 $('.form__tree-item').removeClass('parentActive'); 62 $('.form__tree input[type="checkbox"]:checked').each((_, input) => { 63 const level = parseInt($(input).closest('ul').data('level'), 10); 64 html += '<div class="form__tree-tag"><input type="hidden" name="chapters[]" value="' + input.value + '"><span>' + htmlspecialchars($(input).data('text')) + '</span><button type="button" data-value="' + input.value + '" class="form__tree-tag-remove form-tree-tag-group-remove"><i class="ico__close"></i></button></div>'; 65 // legacy highlights the ancestors of every checked box 66 if (level > 1) { 67 const parentli = $(input).closest('ul').parent('.form__tree-item'); 68 parentli.addClass('parentActive'); 69 if (level > 2) { 70 parentli.closest('ul').parent('.form__tree-item').addClass('parentActive'); 71 } 72 } 73 }); 74 $('.form__tree-tags').html(html).addClass('selected'); 75} 76 77/** Close every open category-tree dropdown. */ 78export function closeTreePanel() { 79 $('.form__tree').removeClass('active'); 80 $('.form__tree-item').removeClass('opened'); 81 $('.form__tree ul').removeClass('active'); 82 $('.form__tree-title').removeClass('toback'); 83 $('.form__tree-wrap').removeClass('is-open'); 84 // Drop any leftover body backdrop from older builds â it sat above 85 // .admin__wrap (isolation:isolate) and stole checkbox clicks. 86 document.getElementById('form-tree-backdrop')?.remove(); 87} 88 89/** Open one tree panel. Outside clicks are handled by bindTreeOutsideClose. */ 90export function openTreePanel(treeEl) { 91 closeTreePanel(); 92 if (!treeEl) { 93 return; 94 } 95 treeEl.classList.add('active'); 96 treeEl.closest('.form__tree-wrap')?.classList.add('is-open'); 97} 98 99let treeOutsideCloseBound = false;
100 101/** Collapse the tree when clicking anywhere outside the open popup panel. */ 102export function bindTreeOutsideClose() { 103 if (treeOutsideCloseBound) { 104 return; 105 } 106 treeOutsideCloseBound = true; 107 document.addEventListener('click', (e) => { 108 const tree = document.querySelector('.form__tree.active'); 109 if (!tree) { 110 return; 111 } 112 if (tree.contains(e.target)) { 113 return; 114 } 115 // Keep open while interacting with the field chrome (tags, toggle). 116 if (e.target.closest('.form__tree-wrap')) { 117 return; 118 } 119 closeTreePanel(); 120 }, true); 121} 122 123/** The panel behaviour shared by both forms (toggle, back, close). */ 124export function bindTreePanel(state) { 125 bindTreeOutsideClose(); 126 if (bindTreePanel.bound) { 127 return; 128 } 129 bindTreePanel.bound = true; 130 131 $(document).on('click', '.form__tree-toggle', (e) => { 132 e.preventDefault(); 133 const toggle = $(e.currentTarget); 134 toggle.closest('li').toggleClass('opened').siblings('li').removeClass('opened').find('ul').removeClass('active'); 135 toggle.siblings('.form__tree-children').toggleClass('active'); 136 state.level = toggle.siblings('.form__tree-children').data('level'); 137 $('.form__tree-title').addClass('toback'); 138 }); 139 140 $(document).on('click', '.form-tree-back', (e) => { 141 e.preventDefault(); 142 $(document).find('.form__tree-children[data-level="' + state.level + '"]') 143 .removeClass('active').closest('li').removeClass('opened'); 144 state.level--; 145 if (state.level === 1) { 146 $('.form__tree-title').removeClass('toback'); 147 } 148 }); 149 150 $(document).on('click', '.form__tree-select-toggle', (e) => { 151 e.preventDefault(); 152 e.stopPropagation(); 153 const $wrap = $(e.currentTarget).closest('.form__tree-wrap'); 154 const $tree = $wrap.find('.form__tree'); 155 if ($tree.hasClass('active')) { 156 closeTreePanel(); 157 return; 158 } 159 openTreePanel($tree.get(0)); 160 $wrap.find('.form__tree-item').removeClass('opened'); 161 $wrap.find('.form__tree ul').removeClass('active'); 162 }); 163 164 $(document).on('click', '.form-tree-close', (e) => { 165 e.preventDefault(); 166 closeTreePanel(); 167 }); 168}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.