diff --git a/script.js b/script.js index 64785ae..4efa218 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,5 @@ const STORAGE_KEY = "ecosystem-panel-preferences"; +const COLLAPSE_STORAGE_KEY = "ecosystem-panel-collapse-state"; // Меняй навигацию здесь: разделы -> подразделы -> элементы. // url можно заменить на реальные адреса, tags помогают поиску. @@ -141,6 +142,7 @@ const defaultPreferences = { const availableViews = ["cards", "list", "compact-cards", "compact-list"]; const preferences = { ...defaultPreferences, ...readPreferences() }; +const collapseState = readCollapseState(); const catalog = document.querySelector("[data-catalog-root]"); const navTree = document.querySelector("[data-nav-tree]"); const searchInput = document.querySelector("[data-search-input]"); @@ -172,6 +174,9 @@ navTree.addEventListener("click", (event) => { focusCatalogTarget(button.dataset.focusTarget); }); +navTree.addEventListener("toggle", handleDetailsToggle, true); +catalog.addEventListener("toggle", handleDetailsToggle, true); + settingsToggle.addEventListener("click", () => { const isOpen = settingsPanel.classList.toggle("is-open"); settingsOverlay.classList.toggle("is-open", isOpen); @@ -235,11 +240,12 @@ function createItem(number, title, type, tags = [], url = "#") { function renderCatalog() { const query = normalize(searchInput.value); + const forceOpen = Boolean(query); const fragment = document.createDocumentFragment(); let visibleItems = 0; ecosystemSections.forEach((section) => { - const sectionElement = createSectionElement(section); + const sectionElement = createSectionElement(section, forceOpen); let visibleGroups = 0; let sectionVisibleItems = 0; @@ -252,7 +258,7 @@ function renderCatalog() { visibleGroups += 1; sectionVisibleItems += filteredItems.length; - sectionElement.querySelector(".section-groups").append(createGroupElement(group, filteredItems)); + sectionElement.querySelector(".section-groups").append(createGroupElement(group, filteredItems, forceOpen)); }); if (visibleGroups) { @@ -273,12 +279,13 @@ function renderCatalog() { } -function createSectionElement(section) { +function createSectionElement(section, forceOpen = false) { const sectionElement = document.createElement("details"); sectionElement.className = "catalog-section"; sectionElement.id = getSectionId(section); + sectionElement.dataset.collapseKey = sectionElement.id; sectionElement.tabIndex = -1; - sectionElement.open = true; + sectionElement.open = forceOpen || getSavedOpenState(sectionElement.dataset.collapseKey); sectionElement.innerHTML = `
@@ -296,12 +303,13 @@ function createSectionElement(section) { return sectionElement; } -function createGroupElement(group, items) { +function createGroupElement(group, items, forceOpen = false) { const groupElement = document.createElement("details"); groupElement.className = "catalog-group"; groupElement.id = getGroupId(group); + groupElement.dataset.collapseKey = groupElement.id; groupElement.tabIndex = -1; - groupElement.open = true; + groupElement.open = forceOpen || getSavedOpenState(groupElement.dataset.collapseKey); const cards = items.map(createCardElement); groupElement.innerHTML = ` @@ -384,7 +392,8 @@ function renderNavigationTree() { ecosystemSections.forEach((section) => { const sectionNode = document.createElement("details"); sectionNode.className = "tree-section"; - sectionNode.open = true; + sectionNode.dataset.collapseKey = getTreeSectionId(section); + sectionNode.open = getSavedOpenState(sectionNode.dataset.collapseKey); const sectionSummary = document.createElement("summary"); sectionSummary.innerHTML = ` @@ -398,7 +407,8 @@ function renderNavigationTree() { section.groups.forEach((group) => { const groupNode = document.createElement("details"); groupNode.className = "tree-group"; - groupNode.open = true; + groupNode.dataset.collapseKey = getTreeGroupId(group); + groupNode.open = getSavedOpenState(groupNode.dataset.collapseKey); const groupSummary = document.createElement("summary"); groupSummary.innerHTML = ` @@ -446,12 +456,34 @@ function focusCatalogTarget(targetId) { target.closest(".catalog-section")?.setAttribute("open", ""); target.closest(".catalog-group")?.setAttribute("open", ""); + persistOpenedAncestor(target.closest(".catalog-section")); + persistOpenedAncestor(target.closest(".catalog-group")); target.scrollIntoView({ behavior: "smooth", block: "center" }); target.focus({ preventScroll: true }); target.classList.add("is-focused"); window.setTimeout(() => target.classList.remove("is-focused"), 1200); } +function handleDetailsToggle(event) { + const details = event.target; + + if (!(details instanceof HTMLDetailsElement) || !details.dataset.collapseKey) { + return; + } + + collapseState[details.dataset.collapseKey] = details.open; + saveCollapseState(); +} + +function persistOpenedAncestor(details) { + if (!details?.dataset.collapseKey) { + return; + } + + collapseState[details.dataset.collapseKey] = true; + saveCollapseState(); +} + function attachCardInteractions() { document.querySelectorAll(".nav-card").forEach((card) => { let leaveTimer; @@ -525,6 +557,14 @@ function getGroupId(group) { return `group-${group.id}`; } +function getTreeSectionId(section) { + return `tree-${getSectionId(section)}`; +} + +function getTreeGroupId(group) { + return `tree-${getGroupId(group)}`; +} + function getItemId(item) { return `item-${item.number.replaceAll(".", "-")}`; } @@ -545,6 +585,22 @@ function savePreferences(nextPreferences) { localStorage.setItem(STORAGE_KEY, JSON.stringify(nextPreferences)); } +function getSavedOpenState(key) { + return collapseState[key] ?? true; +} + +function readCollapseState() { + try { + return JSON.parse(localStorage.getItem(COLLAPSE_STORAGE_KEY)) || {}; + } catch { + return {}; + } +} + +function saveCollapseState() { + localStorage.setItem(COLLAPSE_STORAGE_KEY, JSON.stringify(collapseState)); +} + function closeSettingsPanel() { settingsPanel.classList.remove("is-open"); settingsOverlay.classList.remove("is-open");