let sbPosition = "right"; let defaultSearchToggle = "fuzzy"; let defaultSearchOrder = "comfyui"; let current_path = null; async function getConfiguration(name,local = false) { try { if (!current_path) { res = await fetch("./sidebar/internal_settings"); settings = await res.json(); current_path = settings.current_path; } if (local || local === 'true') { return localStorage.getItem(name); } else { const response = await fetch('/sidebar/settings', { cache: "no-store", method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action : "read", parameter: name }), }); const data = await response.json(); return data["value"]; } } catch (error) { console.log(error); return null; } } function addConfiguration(name,value) { return fetch('/sidebar/settings', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action : "add", parameter: name, value: value }), }) } function updateConfiguration(name,value,local = false) { if (local) { return localStorage.setItem(name,value) } else { return fetch('/sidebar/settings', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action : "update", parameter: name, value: value }), }) } } function removeConfiguration(name) { return fetch('/sidebar/settings', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action : "delete", parameter: name }), }) } function factoryResetSettings() { return fetch('/sidebar/settings', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action : "factory_reset" }) }) } async function settingsSetup(app,$el) { /* app.ui.settings.addSetting({ id: "n-sidebar.Other.0_sidebar", name: "[Sidebar] Better ComfyUI Settings Style", type: "boolean", defaultValue: false, onChange(value) { let styleElement; if (value) { styleElement = $el("style", { id: "sb-settings-style", parent: document.head, textContent: settingsStyle }); } else { try{ document.getElementById("sb-settings-style").remove(); } catch(e) { } } }, }); */ addSBSetting( "w_section",{ id: "workflow_preview", name: "Enable Workflow Node List", defaultValue: "false", type: "boolean", local: true, info: "This is disabled by default since workflows preview can be slow.", onChange(value) { }, }); addSBSetting( "w_section",{ id: "workflow_replace", name: "Workflow Replace Popup", defaultValue: "true", type: "boolean", local: true, onChange(value) { }, }); addSBSetting( "g_section",{ id: "auto_hide", name: "Auto Show Sidebar", defaultValue: "false", type: "boolean", local: true, onChange(value) { }, }); addSBSetting( "g_section",{ id: "show_at_startup", name: "Show at Startup", defaultValue: "false", type: "boolean", local: true, info: "This setting is only considered when the sidebar is not fixed", onChange(value) { }, }); addSBSetting( "g_section",{ id: "embed_osb", name: "Embed Official Sidebar", defaultValue: "true", type: "boolean", local: true, info: "This will move the buttons of the official sidebar inside the N-Sidebar.", onChange(value) { }, }); addSBSetting( "g_section",{ id: "embed_osb_nodes", name: "Keep Node Library Button", defaultValue: "false", type: "boolean", local: true, info: "This will keep Node Library button in the N-Sidebar. Otherwise, it will be removed due to redundancy.", onChange(value) { }, }); await addSBSetting( "g_section", { id: "tree_view", name: "Node Category Tree View", defaultValue: "multi", type: "dropdown", info: "NOTE: The new view will be applied after a page refresh!", options: [ { value: "mono", label: "MONO" }, { value: "multi", label: "MULTILEVEL" } ], onChange(value) { defaultSearchOrder = value; }, }); addSBSetting( "g_section",{ id: "font", name: "Font Size", defaultValue: "13", type: "slider", attrs: { min: 5, max: 30, step: 1, }, onChange(value) { addDynamicCSSRule('.sidebarCategory, .sidebarFolder, #sidebarBookmarks', 'font-size', value + 'px'); }, }); addSBSetting( "g_section",{ id: "node", name: "Node Size", defaultValue: "10", type: "slider", attrs: { min: 0, max: 40, step: 1, }, onChange(value) { addDynamicCSSRule('.sidebar li', 'padding', value + 'px'); }, }); addSBSetting( "g_section", { id: "bartop", name: "Space Top", defaultValue: 0, type: "slider", local: true, attrs: { min: 0, max: 100, step: 1, }, async onChange(value) { addDynamicCSSRule('.sidebar', 'top', value + '%'); } }); var last_right_value = 0; var element = document.getElementById('sidebar'); addSBSetting( "g_section", { id: "barbottom", name: "Space Bottom", defaultValue: "97", type: "slider", local: true, attrs: { min: 30, max: 100, step: 1, }, async onChange(value) { try{ if (localStorage.getItem("Comfy.Settings.Comfy.UseNewMenu").replace(/"/g,'') ==='Top' || localStorage.getItem("Comfy.Settings.Comfy.UseNewMenu").replace(/"/g,'') ==='Bottom'){ addDynamicCSSRule('.sidebar', 'height', `calc(${value}% - 35px`); } else { addDynamicCSSRule('.sidebar', 'height', value + '%'); } } catch{ addDynamicCSSRule('.sidebar', 'height', value + '%'); } } }); //z-index sidebar addSBSetting( "g_section",{ id: "zindex", name: "Sidebar Z-Index", defaultValue: "2", type: "slider", attrs: { min: 2, max: 1000, step: 1, }, onChange(value) { addDynamicCSSRule('.sidebar', 'z-index', value); }, }) addSBSetting( "g_section",{ id: "noderadius", name: "Node Radius Border", defaultValue: "9", type: "slider", attrs: { min: 0, max: 20, step: 1, }, onChange(value) { addDynamicCSSRule('.sidebarCategory, .sidebarFolder, #sidebarBookmarks', 'border-radius', value + 'px'); addDynamicCSSRule('.sidebarItem', 'border-radius', value + 'px'); }, }); addSBSetting( "g_section",{ id: "blur", name: "Blur Intesity", defaultValue: "5", type: "slider", attrs: { min: 0, max: 30, step: 1, }, onChange(value) { addDynamicCSSRule('.content_sidebar', 'backdrop-filter', 'blur(' + value + 'px)'); addDynamicCSSRule('#sidebar_views', 'backdrop-filter', 'blur(' + value + 'px)'); }, }); addSBSetting( "g_section", { id: "opacity", name: "Opacity", defaultValue: "1.0", type: "slider", info: "Note: The opacity of custom-colored nodes is not applied in real-time and will take effect after the page is refreshed.", attrs: { min: 0.1, max: 1, step: 0.1, }, onChange(value) { let value_perc = (value * 100).toFixed(0); addDynamicCSSRule('.content_sidebar', 'background', 'rgb(from var(--comfy-menu-bg) r g b / ' + value_perc + '%)'); addDynamicCSSRule('#sidebar_views', 'background', 'rgb(from var(--comfy-menu-bg) r g b / ' + value_perc + '%)'); addDynamicCSSRule('#sidebar_views', 'border-left', '2px rgb(from var(--comfy-menu-bg) r g b / ' + (parseFloat(value) + 10) + '%)'); addDynamicCSSRule('.sidebarCategory, .sidebarFolder, #sidebarBookmarks', 'background-color', 'rgb(from var(--comfy-input-bg) r g b / ' + value_perc + '%)'); }, }); addSBSetting( "g_section", { id: "search_type", name: "Search Type", defaultValue: "fuzzy", type: "dropdown", info: "Select the search type:\nORIGINAL: People who use custom nodes to translate ComfyUI (e.g. Chinese, Japanese...) should use this!\nNORMAL: Search based on original node name but without fuzzy matching\nFUZZY: Search based on the original node name with fuzzy matching (Default).", options: [ { value: "original", label: "ORIGINAL" }, { value: "normal", label: "NORMAL" }, { value: "fuzzy", label: "FUZZY" }, ], onChange(value) { defaultSearchToggle = value; }, }); addSBSetting( "g_section", { id: "position", name: "Position", defaultValue: "left", type: "dropdown", options: [ { value: "left", label: "LEFT" }, { value: "right", label: "RIGHT" } ], onChange(value) { let valueChange= '35px' if (value == "right") { addDynamicCSSRule('.content_sidebar', 'float', 'right'); addDynamicCSSRule('.content_sidebar', 'border-top-left-radius', '5px'); addDynamicCSSRule('.content_sidebar', 'border-bottom-left-radius', '5px'); addDynamicCSSRule('#sidebar_views', 'float', 'right'); addDynamicCSSRule('.dragHandle', 'float', 'right'); addDynamicCSSRule('.dragHandle', 'right', '-5px'); addDynamicCSSRule('.sidebar', 'left', 'unset'); addDynamicCSSRule('.sidebar', 'right', '0'); addDynamicCSSRule('.sidebar', 'flex-direction', 'row-reverse'); addDynamicCSSRule('.sidebar-header', 'right', '52px'); addDynamicCSSRule('.sidebar-header', 'left', '-17px'); addDynamicCSSRule('#sb_scrollToTopButton', 'right', '48px'); addDynamicCSSRule('.view_button', 'border-right-width', '2px'); addDynamicCSSRule('.view_button', 'border-right-style', 'solid'); addDynamicCSSRule('.view_button', 'border-left', '0px'); //add to view_button the . left class const view_button = document.getElementsByClassName('view_button'); for (let i = 0; i < view_button.length; i++) { view_button[i].classList.remove('right'); view_button[i].classList.add('left'); } valueChange= '-35px'; } if (value == "left") { addDynamicCSSRule('.content_sidebar', 'float', 'left'); addDynamicCSSRule('.content_sidebar', 'border-top-right-radius', '5px'); addDynamicCSSRule('.content_sidebar', 'border-bottom-right-radius', '5px'); addDynamicCSSRule('#sidebar_views', 'float', 'left'); addDynamicCSSRule('.dragHandle', 'float', 'left'); addDynamicCSSRule('.sidebar', 'left', '0'); addDynamicCSSRule('.sidebar', 'right', 'unset'); addDynamicCSSRule('.sidebar', 'flex-direction', 'row'); addDynamicCSSRule('.sidebar-header', 'right', 'unset'); addDynamicCSSRule('.sidebar-header', 'left', '11px'); addDynamicCSSRule('#sb_scrollToTopButton', 'right', '26px'); addDynamicCSSRule('.view_button', 'border-left-width', '2px'); addDynamicCSSRule('.view_button', 'border-left-style', 'solid'); addDynamicCSSRule('.view_button', 'border-right', '0px'); //add to view_button the . right class const view_button = document.getElementsByClassName('view_button'); for (let i = 0; i < view_button.length; i++) { view_button[i].classList.remove('left'); view_button[i].classList.add('right'); } valueChange= '35px' } //ALPHA try{ if (localStorage.getItem("sb_embed_osb") == 'true') { if (((localStorage.getItem("Comfy.Settings.Comfy.UseNewMenu") ==='"Top"' || localStorage.getItem("Comfy.Settings.Comfy.UseNewMenu") ==='"Bottom"')) && localStorage.getItem("Comfy.Settings.Comfy.Sidebar.Location")==null) { addDynamicCSSRule('.splitter-overlay', 'margin-left', '0px'); addDynamicCSSRule('.side-bar-panel', 'min-width', 'calc('+localStorage.getItem("sidebarWidth")+'px - 35px)'); } } } catch(err){ console.log("ERROR IN ALPHA VERSION: " + err); } try{ if (localStorage.getItem("sb_embed_osb") == 'true') { if (localStorage.getItem("Comfy.Settings.Comfy.Sidebar.Location") === '"'+value+'"' ) { addDynamicCSSRule('.splitter-overlay', 'margin-left', valueChange); addDynamicCSSRule('.side-bar-panel', 'min-width', 'calc('+localStorage.getItem("sidebarWidth")+'px - 35px)'); } if (localStorage.getItem("Comfy.Settings.Comfy.Sidebar.Location") === null ) { addDynamicCSSRule('.side-bar-panel', 'min-width', 'calc('+localStorage.getItem("sidebarWidth")+'px - 35px)'); addDynamicCSSRule('.splitter-overlay', 'margin-left', '35px'); } } } catch(err){ console.log("ERROR IN ALPHA VERSION: " + err); } sbPosition = value; }, }); await addSBSetting( "g_section", { id: "order_type", name: "Nodes Order Type", defaultValue: "comfyui", type: "dropdown", info: "Select the order type:\nCOMFYUI: The order of nodes will be the same as in the original ComfyUI\nALPHABETICAL: The order of nodes will be alphabetical\nNOTE: The new order will be applied after a page refresh!", options: [ { value: "comfyui", label: "COMFYUI" }, { value: "alphabetical", label: "ALPHABETICAL" } ], onChange(value) { defaultSearchOrder = value; }, }); addSBSetting( "w_section", { id: "wf_path", name: "Workflow Paths", defaultValue: "", type: "textarea", info: "Paths must be row separated (only one per row).\nThe default ComfyUI workflows path has already been set.\nReboot ComfyUI to apply changes!", onChange(value) { NaN }, }); const switch_settings = document.getElementById("switch_settings"); const sb_exportButton = document.getElementById("sb_exportButton"); const sb_importButton = document.getElementById("sb_importButton"); const sb_shortcutButton = document.getElementById("sb_shortcutButton"); const sb_modal_backdrop = document.getElementById("sb-modal-backdrop-settings"); const sb_settingsDiv_export = document.getElementById("sb_settingsDiv_export"); const sb_settingsDiv_import = document.getElementById("sb_settingsDiv_import"); const sb_settingsDiv_shortcut = document.getElementById("sb_settingsDiv_shortcut"); const sb_settingsDiv = document.getElementById("sb_settingsDiv"); switch_settings.addEventListener('click', function() { //set sb-settingsDiv visible showSettings(); }) sb_exportButton.addEventListener('click', function() { showIESettings("export"); }) sb_importButton.addEventListener('click', function() { showIESettings("import"); }) sb_shortcutButton.addEventListener('click', function() { showIESettings("shortcut"); }) document.body.addEventListener('click', function(event) { const modalBackdrop = document.querySelector('.sb-modal-backdrop-settings'); if (event.target === modalBackdrop) { sb_settingsDiv.classList.add('sb_hidden'); sb_modal_backdrop.classList.add('sb_hidden'); } if (!sb_settingsDiv_export.classList.contains('sb_hidden')) { if ( (event.target === sb_modal_backdrop || sb_settingsDiv.contains(event.target)) && !event.target.matches('#sb_exportButton') ) { sb_settingsDiv_export.classList.add('sb_hidden'); sb_settingsDiv_import.classList.add('sb_hidden'); } } if (!sb_settingsDiv_import.classList.contains('sb_hidden')) { if ( (event.target === sb_modal_backdrop || sb_settingsDiv.contains(event.target)) && !event.target.matches('#sb_importButton') ) { sb_settingsDiv_import.classList.add('sb_hidden'); //sb_settingsDiv_import.classList.add('sb_hidden'); } } if (!sb_settingsDiv_shortcut.classList.contains('sb_hidden')) { if ( (event.target === sb_modal_backdrop || sb_settingsDiv.contains(event.target)) && !event.target.matches('#sb_shortcutButton') ) { sb_settingsDiv_shortcut.classList.add('sb_hidden'); //sb_settingsDiv_shortcut.classList.add('sb_hidden'); } } }) sb_resetButton.addEventListener('click', function() { resetSettings(); }) sb_factoryResetButton.addEventListener('click', function() { factoryReset(); }) document.getElementById('sb_runExportButton').addEventListener('click', function() { const selectedItems = []; // Verifica quali checkbox sono selezionate e aggiungile all'array document.querySelectorAll('#sb_settingsDiv_export input[type="checkbox"]').forEach((checkbox, index) => { if (checkbox.checked) { // Supponendo che l'ordine dei checkbox corrisponda ai parametri della configurazione const keys = ['sb_pinnedItems', 'sb_categoryNodeMap|sb_ColorCustomCategories', 'sb_workflowNodeMap|sb_ColorCustomWorkflows', 'sb_templateNodeMap|sb_ColorCustomTemplates','sb_w_API_key','model_templates','']; selectedItems.push(keys[index]); } }); // Effettua una chiamata POST per esportare la configurazione selezionata fetch('/sidebar/export', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ selected_items: selectedItems }), }) .then(response => response.blob()) // Riceviamo il file come blob .then(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'n-sidebar_config.zip'; // Nome del file document.body.appendChild(a); a.click(); // Avvia il download a.remove(); }) .catch(error => console.error('Error exporting config:', error)); }); document.getElementById('sb_selectFileButton').addEventListener('click', function() { document.getElementById('importFileInput').click(); }); document.getElementById('sb_runImportButton').addEventListener('click', async function() { const selectedItems = []; // Verifica quali checkbox sono selezionate e aggiungile all'array document.querySelectorAll('#sb_settingsDiv_import input[type="checkbox"]').forEach((checkbox, index) => { if (checkbox.checked) { const keys = ['sb_pinnedItems', 'sb_categoryNodeMap|sb_ColorCustomCategories', 'sb_workflowNodeMap|sb_ColorCustomWorkflows', 'sb_templateNodeMap|sb_ColorCustomTemplates','sb_w_API_key','model_templates','']; selectedItems.push(keys[index]); } }); // Ottieni il file di importazione const fileInput = document.getElementById('importFileInput'); const file = fileInput.files[0]; if (file) { const formData = new FormData(); formData.append('file', file); formData.append('selected_items', JSON.stringify(selectedItems)); try { const response = await fetch('/sidebar/import', { method: 'POST', body: formData }); if (response.ok) { alert('Import successful! You may need to reload the ComfyUI page or restart the server for the changes to take effect'); } else { alert('Failed to import settings.'); } } catch (error) { console.error('Error during import:', error); } } else { alert('Please select a file to import.'); } }); /*SHORTCUTS*/ const shortcutInputs = document.querySelectorAll('.sb-shortcut'); const saveAllButton = document.getElementById('sb_saveAllShortcuts'); const resetButton = document.getElementById('sb_resetToDefault'); const defaultShortcuts = { action1: 'Alt + X', action2: 'Alt + Z', action3: 'Alt + G' }; let shortcuts = JSON.parse(await getConfiguration('sb_shortcuts')) || defaultShortcuts; let currentShortcutKeys = { ctrl: false, alt: false, shift: false, key: null }; // Function to update the input with the current shortcut function updateShortcutInput(inputElement) { const modifiers = []; if (currentShortcutKeys.ctrl) modifiers.push('Ctrl'); if (currentShortcutKeys.meta) modifiers.push('Meta'); // Display Meta key separately if (currentShortcutKeys.alt) modifiers.push('Alt'); if (currentShortcutKeys.shift) modifiers.push('Shift'); if (modifiers.length > 0 && currentShortcutKeys.key) { inputElement.value = `${modifiers.join(' + ')} + ${currentShortcutKeys.key.toUpperCase()}`; } else { inputElement.value = ''; } } // Mostra le shortcut salvate accanto a ciascun campo function displayShortcuts() { shortcutInputs.forEach(inputElement => { const action = inputElement.dataset.action; const shortcutSpan = document.querySelector(`.current-shortcut[data-action="${action}"]`); if (shortcuts[action]) { shortcutSpan.textContent = shortcuts[action]; } else { shortcutSpan.textContent = 'Not Set'; } }); } document.addEventListener('keydown', (event) => { // Check if the active element is one of your shortcut input fields const activeElement = document.activeElement; const isShortcutInputActive = Array.from(shortcutInputs).includes(activeElement); if (isShortcutInputActive) { // Only prevent default behavior when interacting with shortcut input fields event.preventDefault(); // Separate handling for Ctrl, Alt, Shift, and Meta (Command on Mac) if (event.ctrlKey) currentShortcutKeys.ctrl = true; if (event.metaKey) currentShortcutKeys.meta = true; // Handle Meta separately if (event.altKey) currentShortcutKeys.alt = true; if (event.shiftKey) currentShortcutKeys.shift = true; // Check if the key is a letter, number, special character, or accented letter if (event.key && !['Control', 'Meta', 'Alt', 'Shift'].includes(event.key)) { currentShortcutKeys.key = event.key; } // Update the input element with the current shortcut combination updateShortcutInput(activeElement); } }); document.addEventListener('keyup', (event) => { // Reset the modifier keys when they are released if (!event.ctrlKey) currentShortcutKeys.ctrl = false; if (!event.metaKey) currentShortcutKeys.meta = false; // Reset Meta separately if (!event.altKey) currentShortcutKeys.alt = false; if (!event.shiftKey) currentShortcutKeys.shift = false; }); // Funzione per salvare tutte le shortcut function saveAllShortcuts() { shortcutInputs.forEach(inputElement => { const action = inputElement.dataset.action; const shortcut = inputElement.value; if (shortcut) { shortcuts[action] = shortcut; } }); // Salva tutte le shortcut updateConfiguration('sb_shortcuts', JSON.stringify(shortcuts)); displayShortcuts(); alert('Shortcuts saved successfully!'); } // Evento per il pulsante "Save All Shortcuts" saveAllButton.addEventListener('click', () => { saveAllShortcuts(); }); // Funzione per riportare le shortcut a quelle di default function resetToDefault() { shortcuts = { ...defaultShortcuts }; updateConfiguration('sb_shortcuts', JSON.stringify(shortcuts)); displayShortcuts(); } // Evento per il pulsante "Reset to Default" resetButton.addEventListener('click', () => { resetToDefault(); }); // Mostra le shortcut salvate all'avvio displayShortcuts(); /* END OF SHORTCUTS */ // General function to handle tab switching function createTabManager(tabs, contents) { tabs.forEach((tab, index) => { tab.addEventListener('click', () => { // Remove 'n-sb-active' class from all tabs and contents tabs.forEach(t => t.classList.remove('n-sb-active')); contents.forEach(c => c.classList.remove('n-sb-active')); // Add 'n-sb-active' class to the clicked tab and corresponding content tab.classList.add('n-sb-active'); contents[index].classList.add('n-sb-active'); }); }); } const tabSettings = document.getElementById('tabSettings'); const tabCustomize = document.getElementById('tabCustomize'); const settingsContainer = document.getElementById('settingsContainer'); const sbCustomizeContainer = document.getElementById('sbCustomizeContainer'); createTabManager([tabSettings, tabCustomize], [settingsContainer, sbCustomizeContainer]); const t1 = document.getElementById('t1'); const t2 = document.getElementById('t2'); const tabSettings2 = document.getElementById('tabSettings2'); const tabCustomize2 = document.getElementById('tabCustomize2'); createTabManager([tabSettings2, tabCustomize2], [t1, t2]); /******************************************************************************* */ function generateHash(value) { let hash = 0; for (let i = 0; i < value.length; i++) { const char = value.charCodeAt(i); hash = (hash << 5) - hash + char; hash |= 0; // Convert to 32-bit integer } return `section_${hash}`; } // Function to map default titles to IDs function getDefaultId(description) { const mapping = { "Custom Node Categories": "custom_categories", "Templates list": "custom_templates", "Workflows list": "custom_workflows" }; return mapping[description] || generateHash(description); } //const availablePanels = ["custom_categories", "custom_templates", "custom_workflows", "custom_reports", "custom_users"]; let panelsInUse = new Set(); // Keep track of panels currently in use const sectionList = document.getElementById('n-sb-section-list'); // Function to fetch sections from the server async function fetchSections() { const timestamp = Date.now(); // You can use a different timestamp or method if needed const url = `./sidebar/views?v=${timestamp}`; try { const response = await fetch(url); if (!response.ok) { throw new Error('Failed to fetch sections: ' + response.statusText); } const data = await response.json(); return data; } catch (error) { console.error('Error fetching sections:', error); return []; // Return an empty array on error } } // Function to fetch panels from the server async function fetchPanels() { const timestamp = Date.now(); // You can use a different timestamp or method if needed const url = `./sidebar/panels?v=${timestamp}`; try { const response = await fetch(url); if (!response.ok) { throw new Error('Failed to fetch panels: ' + response.statusText); } const data = await response.json(); return data.panels; // Extract the "panels" array from the response } catch (error) { console.error('Error fetching panels:', error); return []; // Return an empty array on error } } // Function to create a panel item function createPanelItem(panelName, sectionDiv) { const panelItem = document.createElement('li'); panelItem.textContent = panelName; const removeBtn = document.createElement('button'); removeBtn.textContent = "x"; removeBtn.classList.add('remove-panel'); removeBtn.addEventListener('click', () => { panelItem.remove(); panelsInUse.delete(panelName); updateAllPanelDropdowns(availablePanels); // Update all select dropdowns when a panel is removed }); panelItem.appendChild(removeBtn); sectionDiv.querySelector('.panel-list').appendChild(panelItem); } // Function to create the section editor function createSectionEditor(section, availablePanels) { const sectionDiv = document.createElement('div'); sectionDiv.classList.add('n-sb-section', 'draggable'); sectionDiv.setAttribute('draggable', true); // Use default IDs for predefined titles const sectionId = getDefaultId(section.description); sectionDiv.innerHTML = `