diff --git a/components/admin/AdminNavPrimaryBranch.vue b/components/admin/AdminNavPrimaryBranch.vue new file mode 100644 index 0000000..5ccb101 --- /dev/null +++ b/components/admin/AdminNavPrimaryBranch.vue @@ -0,0 +1,160 @@ + + + diff --git a/components/site/LeftSidebar.vue b/components/site/LeftSidebar.vue index 52f8a4d..e8430da 100644 --- a/components/site/LeftSidebar.vue +++ b/components/site/LeftSidebar.vue @@ -18,6 +18,120 @@ const { data: navigation } = await useFetch('/api/navigation', { footer: [] }) }) + +const STORAGE_KEY = 'sori-primary-nav-expanded' + +/** + * 트리에서 하위가 있는 노드 id를 모은다. + * @param {Array} list - 노드 목록 + * @returns {string[]} id 목록 + */ +const collectBranchIds = (list) => { + const out = [] + for (const node of list || []) { + if (node.children?.length) { + out.push(String(node.id)) + out.push(...collectBranchIds(node.children)) + } + } + return out +} + +/** + * localStorage에서 펼침 상태를 읽는다. + * @returns {string[]|null} id 배열 + */ +const readStoredExpanded = () => { + if (typeof window === 'undefined') { + return null + } + try { + const raw = window.localStorage.getItem(STORAGE_KEY) + if (!raw) { + return null + } + const parsed = JSON.parse(raw) + return Array.isArray(parsed) ? parsed.map(String) : null + } catch { + return null + } +} + +/** + * 펼침 상태를 localStorage에 저장한다. + * @param {Set} set - id 집합 + * @returns {void} + */ +const persistExpanded = (set) => { + if (typeof window === 'undefined') { + return + } + window.localStorage.setItem(STORAGE_KEY, JSON.stringify([...set])) +} + +const primaryNavExpandedSet = ref(new Set()) + +/** + * 트리 구조에 맞게 펼침 집합을 맞춘다. + * @param {Array} nodes - primary 트리 + * @param {boolean} useStorage - 최초·복원 시 저장값 반영 + * @returns {void} + */ +const syncPrimaryNavExpanded = (nodes, useStorage = false) => { + const allBranch = new Set(collectBranchIds(nodes)) + if (useStorage) { + const stored = readStoredExpanded() + if (stored && stored.length) { + const next = new Set() + for (const id of stored) { + if (allBranch.has(id)) { + next.add(id) + } + } + primaryNavExpandedSet.value = next.size ? next : allBranch + return + } + } + const next = new Set() + for (const id of primaryNavExpandedSet.value) { + if (allBranch.has(id)) { + next.add(id) + } + } + primaryNavExpandedSet.value = next.size ? next : allBranch +} + +/** + * 상단 네비 폴더 펼침 토글 + * @param {string} id - 노드 id + * @returns {void} + */ +const togglePrimaryNavBranch = (id) => { + const key = String(id) + const next = new Set(primaryNavExpandedSet.value) + if (next.has(key)) { + next.delete(key) + } else { + next.add(key) + } + primaryNavExpandedSet.value = next + persistExpanded(next) +} + +provide('sidebarPrimaryNavExpandedSet', primaryNavExpandedSet) +provide('sidebarPrimaryNavToggle', togglePrimaryNavBranch) + +watch( + () => navigation.value?.primary, + (nodes) => { + syncPrimaryNavExpanded(nodes || [], false) + }, + { deep: true } +) + +onMounted(() => { + syncPrimaryNavExpanded(navigation.value?.primary || [], true) +})