From d53f90efff48e96aae466906be2a446882cd515b Mon Sep 17 00:00:00 2001 From: swpa Date: Fri, 22 May 2026 18:34:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B0=87=20God-Mode=20=E7=9A=84?= =?UTF-8?q?=E5=94=AF=E8=AE=80=E9=A0=90=E8=A6=BD=E8=AE=8A=E6=9B=B4=E6=88=90?= =?UTF-8?q?=E5=B9=B3=E5=9D=A6=E5=8C=96=20(Flatten)=20=E8=A8=AD=E8=A8=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/edit-mode.js | 150 ++++++++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 55 deletions(-) diff --git a/static/edit-mode.js b/static/edit-mode.js index 31640a4..9966c3f 100644 --- a/static/edit-mode.js +++ b/static/edit-mode.js @@ -24,7 +24,7 @@ function escapeHTML(str) { export const SESSION_USER_ID = crypto.randomUUID ? crypto.randomUUID() : 'user-' + Math.random().toString(36).substr(2, 9); const ACTIVE_HEARTBEATS = {}; -// 💡 追蹤當前編輯狀態 +// 💡 追蹤當前編輯狀態 export let currentEditPath = null; export let currentEditElementId = null; export let currentEditIsSuccess = false; @@ -400,10 +400,17 @@ function showCliPreviewModal(cliScript) { document.getElementById('btn-side-cancel').style.display = 'inline-block'; document.getElementById('btn-side-confirm').style.display = 'inline-block'; document.getElementById('btn-side-close').style.display = 'none'; + document.getElementById('btn-side-close').textContent = '關閉'; document.getElementById('side-cli-textarea').style.display = 'block'; document.getElementById('side-execution-result').style.display = 'none'; - document.getElementById('side-cli-textarea').value = cliScript; + const textarea = document.getElementById('side-cli-textarea'); + textarea.value = cliScript; + + // 👇 確保正常模式可以編輯,並明確恢復深色主題 + textarea.readOnly = false; + textarea.style.backgroundColor = '#1e1e1e'; // 恢復成標準的深色背景 (您可以依喜好微調顏色碼) + textarea.style.color = '#ecf0f1'; // 確保字體是清晰的淺灰色 // 🌟 修改:遍歷所有樹狀圖容器進行縮放 leftPanes.forEach(pane => pane.style.width = 'calc(50% - 11px)'); @@ -631,81 +638,114 @@ function transformNoToActive(elementId, newCommand) { // ============================================================================ // 💻 開發者工具:預覽節點完整指令 (唯讀 Debug 模式) // ============================================================================ -export function previewNodeCLI(btnElement, rootPath) { +export async function previewNodeCLI(btnElement, rootPath) { // 1. 定位當前點擊的容器 let container = btnElement.closest('details'); - if (!container) { - const leafNode = btnElement.closest('.tree-leaf-node'); - if (leafNode) container = leafNode.querySelector('.leaf-container'); + if (container) { + const elementId = container.id.replace('details-', ''); + // 確保延遲渲染的內容已經載入 (Lazy Load) + if (container.dataset.loaded !== 'true') { + await window.lazyLoadFolder(elementId, false); + } } - if (!container) return; + const targetNode = container || btnElement.closest('.tree-leaf-node'); + if (!targetNode) return; - let cliOutput = `! =========================================\n`; - cliOutput += `! 🔍 [Debug] 節點預覽: ${rootPath}\n`; - cliOutput += `! =========================================\n`; - cliOutput += `${rootPath.replace(/::/g, ' ')}\n`; + // 2. 收集所有子節點資料 + const leafNodes = targetNode.querySelectorAll('.leaf-container'); + const interfaceGroups = {}; - // 2. 找出該節點下所有的葉節點 (包含自己) - const allLeaves = container.classList.contains('leaf-container') - ? [container] - : Array.from(container.querySelectorAll('.leaf-container')); - - if (allLeaves.length === 1 && allLeaves[0] === container) { - // 情況 A:它自己就是最底層的葉節點 - const val = container.getAttribute('data-original'); - if (val) cliOutput += ` ${val}\n`; - } else { - // 情況 B:它是一個資料夾,遞迴列出所有子項目的相對路徑與值 - allLeaves.forEach(leaf => { - const leafPath = leaf.getAttribute('data-path'); - const leafVal = leaf.getAttribute('data-original'); - - if (leafPath && leafVal !== null) { - const relativePath = leafPath.replace(rootPath, '').replace(/^::/, '').replace(/::/g, ' '); - cliOutput += ` ${relativePath} ${leafVal}\n`; + leafNodes.forEach(node => { + const path = node.getAttribute('data-path') || ""; + const val = node.getAttribute('data-original'); + if (!path) return; + + const parts = path.split('::'); + const paramName = parts.pop(); + // 移除虛擬陣列索引 [0], [1] 等,還原真實介面路徑 + let interfacePath = parts.join(' ').replace(/\s*\[\d+\]/g, ''); + + if (!interfaceGroups[interfacePath]) { + interfaceGroups[interfacePath] = []; + } + interfaceGroups[interfacePath].push({ param: paramName, val: val }); + }); + + // 補強邏輯:處理使用者直接點擊葉節點的情況 + if (Object.keys(interfaceGroups).length === 0 && targetNode.classList.contains('tree-leaf-node')) { + const singleNode = targetNode.querySelector('.leaf-container'); + if (singleNode) { + const path = singleNode.getAttribute('data-path'); + const val = singleNode.getAttribute('data-original'); + const parts = path.split('::'); + const paramName = parts.pop(); + let interfacePath = parts.join(' ').replace(/\s*\[\d+\]/g, ''); + interfaceGroups[interfacePath] = [{ param: paramName, val: val }]; + } + } + + if (Object.keys(interfaceGroups).length === 0) { + return alert("此節點下沒有任何數值可供預覽!"); + } + + // 3. 在前端組裝平坦化 CLI (模擬真實設備指令) + let cliLines = []; + cliLines.push("! --- Read-Only Configuration Preview ---"); + for (const [interfacePath, params] of Object.entries(interfaceGroups)) { + cliLines.push(`! Node: ${interfacePath || 'Global'}`); + params.forEach(({param, val}) => { + if (/^\[\d+\]$/.test(param)) { + // 陣列元素本身就是值 + if (val) { + cliLines.push(interfacePath ? `${interfacePath} ${val}` : val); + } + } else if (param === 'no') { + // 處理 no 指令 + cliLines.push(interfacePath ? `${interfacePath} no ${val}` : `no ${val}`); + } else { + // 一般鍵值對 (若 val 為空,代表是單純的 flag 指令) + let line = interfacePath ? `${interfacePath} ${param}` : param; + if (val) line += ` ${val}`; + cliLines.push(line); } }); + cliLines.push("!"); } + const finalScript = cliLines.join('\n'); + + // 4. 顯示唯讀預覽面板 + showReadOnlyCliPreviewModal(finalScript, rootPath); +} - cliOutput += `exit\n`; - - // 3. 呼叫現有的樹狀圖側邊欄 (Side Pane) 顯示 - // 🌟 修改:改用 class 統一控制所有樹狀圖容器 +// 專為 God-Mode 設計的唯讀面板 UI +function showReadOnlyCliPreviewModal(cliScript, rootPath) { const leftPanes = document.querySelectorAll('.tree-view-instance'); const resizer = document.getElementById('drag-resizer'); const rightPane = document.getElementById('side-cli-preview'); - - if (!rightPane) { - console.error("找不到側邊欄元素 'side-cli-preview'"); - return; - } - // 設定標題與樣式 - document.getElementById('side-pane-title').innerHTML = '🔍 節點指令預覽 (唯讀)'; + document.getElementById('side-pane-title').innerHTML = `🔍 [唯讀預覽] ${rootPath}`; document.getElementById('side-pane-title').style.color = '#3498db'; - - // 隱藏「確認寫入」按鈕,只顯示「取消/關閉」按鈕 + + // 隱藏執行按鈕,只保留關閉 document.getElementById('btn-side-confirm').style.display = 'none'; - document.getElementById('btn-side-cancel').style.display = 'inline-block'; - document.getElementById('btn-side-close').style.display = 'none'; - - // 顯示 Textarea 並填入內容 + document.getElementById('btn-side-cancel').style.display = 'none'; + const closeBtn = document.getElementById('btn-side-close'); + closeBtn.style.display = 'inline-block'; + closeBtn.textContent = '關閉預覽'; + const textarea = document.getElementById('side-cli-textarea'); textarea.style.display = 'block'; - textarea.value = cliOutput; - textarea.style.backgroundColor = '#1e1e1e'; // 終端機深色背景 - textarea.style.color = '#d4d4d4'; - + textarea.value = cliScript; + textarea.readOnly = true; // 強制唯讀 + textarea.style.backgroundColor = '#2c3e50'; // 改變背景色提示唯讀狀態 + document.getElementById('side-execution-result').style.display = 'none'; - - // 展開側邊欄 - // 🌟 修改:遍歷所有樹狀圖容器進行縮放 + leftPanes.forEach(pane => pane.style.width = 'calc(50% - 11px)'); rightPane.style.width = 'calc(50% - 11px)'; if (resizer) resizer.style.display = 'flex'; - rightPane.style.display = 'block'; + if (rightPane) rightPane.style.display = 'block'; - // 初始化拖拉分隔線 (如果尚未初始化) if (!window.isResizerInitialized && typeof initResizer === 'function') { initResizer(); window.isResizerInitialized = true;