fix: 解決編輯模式下含有雙引號與特殊符號導致的 HTML 破版問題
This commit is contained in:
parent
b7395a1763
commit
93eb15ac9c
|
|
@ -6,6 +6,17 @@ import {
|
||||||
} from './api.js';
|
} from './api.js';
|
||||||
import { getGlobalConnectionInfo } from './utils.js';
|
import { getGlobalConnectionInfo } from './utils.js';
|
||||||
|
|
||||||
|
// --- 安全跳脫 HTML 特殊字元,防止破版 ---
|
||||||
|
function escapeHTML(str) {
|
||||||
|
if (str === null || str === undefined) return "";
|
||||||
|
return String(str)
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 1. 全域狀態與鎖定管理 (Lock Management)
|
// 1. 全域狀態與鎖定管理 (Lock Management)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
@ -80,6 +91,10 @@ export async function startEditFolder(path, elementId) {
|
||||||
leafContainers.forEach(container => {
|
leafContainers.forEach(container => {
|
||||||
const origVal = container.getAttribute('data-original');
|
const origVal = container.getAttribute('data-original');
|
||||||
const rawPath = container.getAttribute('data-path');
|
const rawPath = container.getAttribute('data-path');
|
||||||
|
|
||||||
|
// ✅ 修改這裡:新增 safeOrigVal
|
||||||
|
const safeOrigVal = escapeHTML(origVal);
|
||||||
|
|
||||||
let cacheKey = rawPath.replace(/::/g, ' ').replace(/\s*\[\d+\]/g, '');
|
let cacheKey = rawPath.replace(/::/g, ' ').replace(/\s*\[\d+\]/g, '');
|
||||||
|
|
||||||
if (rawPath.endsWith('::no')) {
|
if (rawPath.endsWith('::no')) {
|
||||||
|
|
@ -108,12 +123,15 @@ export async function startEditFolder(path, elementId) {
|
||||||
|
|
||||||
inputHtml = `<select class="edit-input" data-path="${rawPath}" ${hintAttr} style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; height: 26px; outline: none; margin-left: 5px; background-color: white; cursor: pointer;">`;
|
inputHtml = `<select class="edit-input" data-path="${rawPath}" ${hintAttr} style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; height: 26px; outline: none; margin-left: 5px; background-color: white; cursor: pointer;">`;
|
||||||
options.forEach(opt => {
|
options.forEach(opt => {
|
||||||
|
// ✅ 修改這裡:新增 safeOpt,並替換 value 與顯示文字
|
||||||
|
const safeOpt = escapeHTML(opt);
|
||||||
const isSelected = (opt === origVal) ? 'selected' : '';
|
const isSelected = (opt === origVal) ? 'selected' : '';
|
||||||
inputHtml += `<option value="${opt}" ${isSelected}>${opt}</option>`;
|
inputHtml += `<option value="${safeOpt}" ${isSelected}>${safeOpt}</option>`;
|
||||||
});
|
});
|
||||||
inputHtml += `</select>${hintIcon}`;
|
inputHtml += `</select>${hintIcon}`;
|
||||||
} else {
|
} else {
|
||||||
inputHtml = `<input type="text" class="edit-input" data-path="${rawPath}" value="${origVal}" ${hintAttr} style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; outline: none; margin-left: 5px;">${hintIcon}`;
|
// ✅ 修改這裡:把 value="${origVal}" 改成 value="${safeOrigVal}"
|
||||||
|
inputHtml = `<input type="text" class="edit-input" data-path="${rawPath}" value="${safeOrigVal}" ${hintAttr} style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; outline: none; margin-left: 5px;">${hintIcon}`;
|
||||||
}
|
}
|
||||||
container.innerHTML = inputHtml;
|
container.innerHTML = inputHtml;
|
||||||
});
|
});
|
||||||
|
|
@ -144,6 +162,10 @@ export async function startEditLeaf(path, elementId) {
|
||||||
|
|
||||||
const container = document.getElementById(`container-${elementId}`);
|
const container = document.getElementById(`container-${elementId}`);
|
||||||
const origVal = container.getAttribute('data-original');
|
const origVal = container.getAttribute('data-original');
|
||||||
|
|
||||||
|
// ✅ 修改這裡:新增 safeOrigVal
|
||||||
|
const safeOrigVal = escapeHTML(origVal);
|
||||||
|
|
||||||
container.innerHTML = `<span style="font-size: 12px; color: #e67e22; margin-left: 5px;">⏳ 設備連線與載入選項中...</span>`;
|
container.innerHTML = `<span style="font-size: 12px; color: #e67e22; margin-left: 5px;">⏳ 設備連線與載入選項中...</span>`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -188,17 +210,21 @@ export async function startEditLeaf(path, elementId) {
|
||||||
|
|
||||||
inputHtml = `<select class="edit-input" data-path="${path}" ${hintAttr} style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; height: 26px; outline: none; margin-left: 5px; background-color: white; cursor: pointer;">`;
|
inputHtml = `<select class="edit-input" data-path="${path}" ${hintAttr} style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; height: 26px; outline: none; margin-left: 5px; background-color: white; cursor: pointer;">`;
|
||||||
options.forEach(opt => {
|
options.forEach(opt => {
|
||||||
|
// ✅ 修改這裡:新增 safeOpt
|
||||||
|
const safeOpt = escapeHTML(opt);
|
||||||
const isSelected = (opt === origVal) ? 'selected' : '';
|
const isSelected = (opt === origVal) ? 'selected' : '';
|
||||||
inputHtml += `<option value="${opt}" ${isSelected}>${opt}</option>`;
|
inputHtml += `<option value="${safeOpt}" ${isSelected}>${safeOpt}</option>`;
|
||||||
});
|
});
|
||||||
inputHtml += `</select>${hintIcon}`;
|
inputHtml += `</select>${hintIcon}`;
|
||||||
} else {
|
} else {
|
||||||
inputHtml = `<input type="text" class="edit-input" data-path="${path}" value="${origVal}" ${hintAttr} style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; outline: none; margin-left: 5px;">${hintIcon}`;
|
// ✅ 修改這裡:使用 safeOrigVal
|
||||||
|
inputHtml = `<input type="text" class="edit-input" data-path="${path}" value="${safeOrigVal}" ${hintAttr} style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; outline: none; margin-left: 5px;">${hintIcon}`;
|
||||||
}
|
}
|
||||||
container.innerHTML = inputHtml;
|
container.innerHTML = inputHtml;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("選項載入失敗", e);
|
console.warn("選項載入失敗", e);
|
||||||
container.innerHTML = `<input type="text" class="edit-input" data-path="${path}" value="${origVal}" style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; outline: none; margin-left: 5px;">`;
|
// ✅ 修改這裡:catch 區塊也要使用 safeOrigVal
|
||||||
|
container.innerHTML = `<input type="text" class="edit-input" data-path="${path}" value="${safeOrigVal}" style="padding: 2px 6px; border: 1px solid #3498db; border-radius: 3px; font-family: Courier New, monospace; font-size: 13px; width: 200px; outline: none; margin-left: 5px;">`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -220,7 +246,9 @@ export async function cancelEditFolder(path, elementId) {
|
||||||
const leafContainers = contentDiv.querySelectorAll('.leaf-container');
|
const leafContainers = contentDiv.querySelectorAll('.leaf-container');
|
||||||
leafContainers.forEach(container => {
|
leafContainers.forEach(container => {
|
||||||
const origVal = container.getAttribute('data-original');
|
const origVal = container.getAttribute('data-original');
|
||||||
container.innerHTML = `<span class="leaf-value" style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace;">${origVal}</span>`;
|
// ✅ 修改這裡:還原時也要跳脫
|
||||||
|
const safeOrigVal = escapeHTML(origVal);
|
||||||
|
container.innerHTML = `<span class="leaf-value" style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace;">${safeOrigVal}</span>`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,7 +264,9 @@ export async function cancelEditLeaf(path, elementId) {
|
||||||
|
|
||||||
const container = document.getElementById(`container-${elementId}`);
|
const container = document.getElementById(`container-${elementId}`);
|
||||||
const origVal = container.getAttribute('data-original');
|
const origVal = container.getAttribute('data-original');
|
||||||
container.innerHTML = `<span class="leaf-value" style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace;">${origVal}</span>`;
|
// ✅ 修改這裡:還原時也要跳脫
|
||||||
|
const safeOrigVal = escapeHTML(origVal);
|
||||||
|
container.innerHTML = `<span class="leaf-value" style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace;">${safeOrigVal}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
@ -508,6 +538,8 @@ function transformNoToActive(elementId, newCommand) {
|
||||||
const firstWord = parts[0];
|
const firstWord = parts[0];
|
||||||
const restCommand = parts.slice(1).join(' ');
|
const restCommand = parts.slice(1).join(' ');
|
||||||
|
|
||||||
|
const safeRestCommand = escapeHTML(restCommand);
|
||||||
|
|
||||||
const svgEnabled = `<svg width="16" height="16" viewBox="0 0 24 24" fill="#27ae60"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zM8.29 11.59L7 12.88l4 4 8-8-1.29-1.29L11 14.18z"/></svg>`;
|
const svgEnabled = `<svg width="16" height="16" viewBox="0 0 24 24" fill="#27ae60"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zM8.29 11.59L7 12.88l4 4 8-8-1.29-1.29L11 14.18z"/></svg>`;
|
||||||
const iconHtml = `<span style="margin-right: 6px; display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;">${svgEnabled}</span>`;
|
const iconHtml = `<span style="margin-right: 6px; display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;">${svgEnabled}</span>`;
|
||||||
|
|
||||||
|
|
@ -515,7 +547,7 @@ function transformNoToActive(elementId, newCommand) {
|
||||||
${iconHtml}
|
${iconHtml}
|
||||||
<b style="color: #2c3e50;">${firstWord}</b>
|
<b style="color: #2c3e50;">${firstWord}</b>
|
||||||
<span class="leaf-container" style="display: inline-flex; align-items: center; min-height: 24px;">
|
<span class="leaf-container" style="display: inline-flex; align-items: center; min-height: 24px;">
|
||||||
<span class="leaf-value" style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace; display: inline-block; transform: translateY(1.5px);">${restCommand}</span>
|
<span class="leaf-value" style="color: #16a085; margin-left: 5px; font-family: Courier New, monospace; display: inline-block; transform: translateY(1.5px);">${safeRestCommand}</span>
|
||||||
<span style="font-size: 0.8em; color: #f39c12; margin-left: 10px; border: 1px solid #f39c12; padding: 2px 6px; border-radius: 4px; background-color: #fffdf7;">(已啟用,重整後歸檔)</span>
|
<span style="font-size: 0.8em; color: #f39c12; margin-left: 10px; border: 1px solid #f39c12; padding: 2px 6px; border-radius: 4px; background-color: #fffdf7;">(已啟用,重整後歸檔)</span>
|
||||||
</span>
|
</span>
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue