代码: 全选
這套修改方案解決了重複字詞的 Bug(原因在於 `processContent` 重複執行時沒有清空 Map 容器,導致舊資料不斷疊加),並為輸入框加上了美觀的 CSS 樣式,同時確保在尚未載入檔案時隱藏整個標籤頁系統。
請將以下修改應用至您的程式碼中:
### 1. 更新 CSS 樣式 (CSS Updates)
請在 `<style>` 區塊中添加 `#codeInput` 的樣式,並確保標籤頁容器在初始狀態下隱藏:
```css
/* === UPDATED & ADDED CSS FOR CODE INPUT AND TAB SYSTEM === */
#mainTabSystem {
display: none; /* Hidden by default until file is loaded */
}
#codeInput {
padding: 8px 12px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
width: 220px;
margin-right: 6px;
}
#codeInput:focus {
border-color: #4caf50;
box-shadow: 0 0 5px rgba(76, 175, 80, 0.3);
}
.tab-container {
display: flex;
border-bottom: 1px solid #ccc;
margin-bottom: 15px;
margin-top: 20px;
}
.tab-button {
padding: 10px 20px;
cursor: pointer;
border: 1px solid transparent;
background: transparent;
font-size: 16px;
margin-bottom: -1px;
}
.tab-button.active {
border: 1px solid #ccc;
border-bottom-color: #fff;
background: #fff;
border-radius: 5px 5px 0 0;
font-weight: bold;
color: #000;
}
.tab-content {
display: none;
padding: 15px 0;
}
.tab-content.active {
display: block;
}
.stats-buttons {
display: flex;
gap: 8px;
margin-bottom: 15px;
}
.stats-btn {
padding: 8px 12px;
cursor: pointer;
border: 1px solid #ccc;
border-radius: 4px;
background: #f9f9f9;
}
.stats-btn.active {
background-color: #4caf50;
color: white;
border-color: #388e3c;
}
.edit-row {
margin-bottom: 15px;
padding: 10px;
border-bottom: 1px dashed #ccc;
background: #fafafa;
border-radius: 5px;
}
.edit-item {
display: inline-block;
padding: 6px 12px;
margin: 4px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
background: #fff;
transition: all 0.2s;
user-select: none;
}
.edit-item:hover { background: #e0e0e0; }
.edit-item.selected {
background: #fff9c4 !important;
border: 2px solid #fbc02d;
box-shadow: 0 0 5px rgba(251, 192, 45, 0.5);
}
.edit-item.mcc3 { background: #e3f2fd; border-color: #90caf9; }
.edit-item.mcc4 { background: #e8f5e9; border-color: #a5d6a7; }
.edit-item.changed { border: 2px dashed #f44336 !important; background: #ffebee; }
.edit-item.target-word { font-weight: bold; color: #d32f2f; border: 2px solid #d32f2f; }
```
### 2. 更新 HTML 結構 (HTML Body Updates)
將整個標籤頁系統包裹在 `id="mainTabSystem"` 的容器中,使其在初始未載入碼表時保持隱藏:
```html
<div id="mainTabSystem">
<div class="tab-container">
<button id="tabEdit" class="tab-button active" onclick="switchTab('edit')">Edit</button>
<button id="tabStats" class="tab-button" onclick="switchTab('stats')">Statistics</button>
</div>
<div id="contentEdit" class="tab-content active">
<div id="inputArea">
<label for="codeInput" style="font-weight: bold;">Encode Input: </label>
<input type="text" id="codeInput" placeholder="Enter code (e.g. zhy) or word">
<button id="submitCode" onclick="handleEditSubmit()">Submit</button>
<button id="resetEditBtn" onclick="handleEditReset()">Reset</button>
</div>
<div id="editArea" style="display: none;">
<div id="editRows"></div>
<button onclick="handleEditReset()" style="margin-top: 15px;">Close / Reset Edit</button>
</div>
</div>
<div id="contentStats" class="tab-content">
<div class="stats-buttons" id="statsBtnContainer">
<button class="stats-btn" style="display: none" id="mcc0update" onclick="showContent('mcc0update', this)">Update</button>
<button class="stats-btn" id="btnMcc0All" onclick="showContent('mcc0all', this)">All</button>
<button class="stats-btn" id="btnMcc1All" onclick="showContent('mcc1all', this)">High Freq</button>
<button class="stats-btn" id="btnMcc2All" onclick="showContent('mcc2all', this)">Common</button>
<button class="stats-btn" id="btnMcc3" onclick="showContent('mcc3', this)">Repeat</button>
<button class="stats-btn" id="btnMcc4" onclick="showContent('mcc4', this)">Multi</button>
</div>
<iframe
id="domifr"
style="width: 100%; height: 480px"
src="https://ejsoon.vip/wp-content/uploads/2024/05/json3.html"
frameborder="0"
></iframe>
</div>
</div>
```
### 3. JavaScript 邏輯修正與優化 (JavaScript Updates)
**3.1 修正 `resetPage` 與 `processContent` 函式:**
必須在 `processContent` 的最開頭**清空所有 Map**,以防調序或加詞重新計算時產生重複項目。同時控制 `#mainTabSystem` 的顯示與隱藏。
```javascript
function resetPage(readfile = true) {
cchardefine.clear();
ccharfirst.clear();
ccharsecond.clear();
mcc0.clear();
mcc0moog.clear();
mcc1all.clear();
mcc1.clear();
mcc1moog.clear();
mcc2all.clear();
mcc2.clear();
mcc2moog.clear();
mcc3.clear();
mcc4.clear();
mcc0string = '';
mcc1string = '';
mcc2string = '';
mcc3string = '';
mcc4string = '';
if (readfile) {
filecontent = '';
document.getElementById('cinfile').value = '';
document.getElementById('output').innerHTML = '';
document.getElementById('download').innerHTML = '';
document.getElementById('selectcinfile').classList.remove('hide');
domifr.contentWindow.ifrmUpdate('{}');
// Hide tab system when page is reset
document.getElementById('mainTabSystem').style.display = 'none';
}
mcc0update.style.display = 'none';
}
function processContent(remcc0 = '') {
// CRITICAL FIX: Clear all maps before recalculating to prevent duplicate words
mcc0.clear();
mcc0moog.clear();
mcc1all.clear();
mcc1.clear();
mcc1moog.clear();
mcc2all.clear();
mcc2.clear();
mcc2moog.clear();
mcc3.clear();
mcc4.clear();
const lines = filecontent.split('\n').map(line => line.replace(/\r$/, ''));
// Process cchardefine
let charDefStart = lines.indexOf('%chardef begin') + 1;
let charDefEnd = lines.findIndex((line, i) => i > charDefStart && line.includes('z'));
if (charDefEnd === -1) charDefEnd = lines.length;
const charDefLines = lines.slice(charDefStart, charDefEnd);
document.getElementById('output').innerHTML = `cchardefine 行數: ${charDefLines.length}<br>`;
charDefLines.forEach(line => {
const [code, char] = line.split('\t');
if (code && char) cchardefine.set(char, code);
});
// Process ccharfirst and ccharsecond
for (let [char, code] of cchardefine) {
ccharfirst.set(char, code[0]);
ccharsecond.set(char, code[1] || 'x');
}
// Process mcc0string
let xLine = -1;
for (let i = lines.length - 1; i >= 0; i--) {
if (lines[i].startsWith('x')) {
xLine = i;
break;
}
}
let charDefEndLine = lines.indexOf('%chardef end');
if (xLine === -1 || charDefEndLine === -1) return;
let mcc0Lines;
if (remcc0 == '') {
mcc0Lines = lines.slice(xLine + 1, charDefEndLine);
mcc0Lines = mcc0Lines.filter(line => {
const parts = line.split('\t');
if (parts.length > 1) {
const code = parts[0];
if (code.startsWith('z')) {
const restOfCode = code.slice(1);
if (restOfCode.includes('z')) {
return false;
}
}
}
return true;
});
mcc0Lines = mcc0Lines.map(line => {
const parts = line.split('\t');
return parts.length > 1 ? parts[1] : line;
});
mcc0Lines = mcc0Lines.filter((line, index, arr) => arr.indexOf(line) == index);
} else {
mcc0Lines = JSON.parse(remcc0);
}
mcc0string = mcc0Lines.join('\n');
document.getElementById('output').innerHTML += `mcc0string 行數: ${mcc0Lines.length}<br>`;
// Process mcc0
const mcc0Values = mcc0Lines.slice(0, 5);
mcc0.set('z', mcc0Values);
// Process mcc0moog
if (mcc0Values[1]) mcc0moog.set('zz', mcc0Values[1]);
if (mcc0Values[2]) mcc0moog.set('zzz', mcc0Values[2]);
if (mcc0Values[3]) mcc0moog.set('zzx', mcc0Values[3]);
if (mcc0Values[4]) mcc0moog.set('zxz', mcc0Values[4]);
// Process mcc1string and mcc4string
const remainingLines = mcc0Lines.slice(5);
mcc1string = remainingLines.filter(line => line.length === 2).join('\n');
mcc4string = remainingLines.filter(line => line.length >= 3).join('\n');
// Process mcc1all
mcc1string.split('\n').forEach(line => {
if (line) {
const firstChar = line[0];
const code = ccharfirst.get(firstChar);
if (code) {
const key = `z${code}`;
if (!mcc1all.has(key)) mcc1all.set(key, []);
mcc1all.get(key).push(line);
}
}
});
// Process mcc1 and mcc2string
for (let [key, values] of mcc1all) {
const top5 = values.slice(0, 5);
mcc1.set(key, top5);
}
mcc2string = mcc1string
.split('\n')
.filter(line => {
return !Array.from(mcc1.values()).flat().includes(line);
})
.join('\n');
// Process mcc1moog
for (let [key, values] of mcc1) {
if (values[1]) mcc1moog.set(key + 'z', values[1]);
if (values[2]) mcc1moog.set(key + 'zz', values[2]);
if (values[3]) mcc1moog.set(key + 'zx', values[3]);
if (values[4]) mcc1moog.set(key + 'xz', values[4]);
}
// Process mcc2all
mcc2string.split('\n').forEach(line => {
if (line && line.length >= 2) {
const firstCode = ccharfirst.get(line[0]) || '';
const secondCode = ccharfirst.get(line[1]) || '';
const key = `z${firstCode}${secondCode}`;
if (!mcc2all.has(key)) mcc2all.set(key, []);
mcc2all.get(key).push(line);
}
});
// Process mcc2 and mcc3string
for (let [key, values] of mcc2all) {
const top2 = values.slice(0, 4);
mcc2.set(key, top2);
}
mcc3string = mcc2string
.split('\n')
.filter(line => {
return !Array.from(mcc2.values()).flat().includes(line);
})
.join('\n');
// Process mcc2moog
for (let [key, values] of mcc2) {
if (values[1]) mcc2moog.set(key + 'z', values[1]);
if (values[2]) mcc2moog.set('z' + key[1] + 'z' + key[2], values[2]);
if (values[3]) mcc2moog.set('zz' + key[1] + key[2], values[3]);
}
// Process mcc3
mcc3string.split('\n').forEach(line => {
if (line && line.length >= 2) {
const firstCode = ccharfirst.get(line[0]) || '';
const secondCode = ccharfirst.get(line[1]) || '';
const thirdCode = ccharsecond.get(line[1]) || 'x';
const key = `z${firstCode}${secondCode}${thirdCode}`;
if (!mcc3.has(key)) mcc3.set(key, []);
mcc3.get(key).push(line);
}
});
// Process mcc4
mcc4string.split('\n').forEach(line => {
if (line && line.length >= 3) {
const codes = line
.slice(0, 3)
.split('')
.map(char => ccharfirst.get(char) || '');
const key = `z${codes.join('')}`;
if (!mcc4.has(key)) mcc4.set(key, []);
mcc4.get(key).push(line);
}
});
// 生成時間戳
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const min = String(now.getMinutes()).padStart(2, '0');
const sec = String(now.getSeconds()).padStart(2, '0');
const timestamp = `${yyyy}${mm}${dd}_${hh}${min}${sec}`;
// Generate download content
const newContent = [
...lines.slice(0, xLine + 1),
...generateNewSection(),
'%chardef end',
...lines.slice(charDefEndLine + 1)
]
.map(line => {
if (line.startsWith('%version')) {
return `%version ${timestamp}`;
}
return line;
})
.join('\n');
const blob = new Blob([newContent], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const size = blob.size;
document.getElementById('download').innerHTML =
`<a href="${url}" download="haha_${timestamp}.txt">下載文件 (${size} bytes)</a>`;
// Show the tab system now that data is successfully loaded
document.getElementById('mainTabSystem').style.display = 'block';
}
```
**3.2 更新互動控制邏輯(添加 Enter 鍵監聽與編輯欄邏輯):**
將這段代碼放在 `</script>` 前,它包含了全新的 `switchTab`、`handleEditSubmit`、以及與修正後 `processContent` 完美連動的排序調校機制:
```javascript
// === ADDED EDIT AREA VARIABLES AND FUNCTIONS ===
let currentEditCode = '';
let currentTargetWord = '';
let selectedEditItem = null;
let activeEditKeys = new Set();
let changedWords = new Set();
// Setup Event Listeners
document.addEventListener('DOMContentLoaded', () => {
const inputEl = document.getElementById('codeInput');
if (inputEl) {
inputEl.addEventListener('keypress', function (e) {
if (e.key === 'Enter') handleEditSubmit();
});
}
});
function switchTab(tab) {
document.getElementById('tabEdit').classList.toggle('active', tab === 'edit');
document.getElementById('tabStats').classList.toggle('active', tab === 'stats');
document.getElementById('contentEdit').classList.toggle('active', tab === 'edit');
document.getElementById('contentStats').classList.toggle('active', tab === 'stats');
}
function handleEditSubmit() {
let val = document.getElementById('codeInput').value.trim();
if (!val) return;
activeEditKeys.clear();
changedWords.clear();
selectedEditItem = null;
// Check if English input
if (/^[a-zA-Z]+$/.test(val)) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
// Chinese phrase input
currentTargetWord = val;
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
// Append if it doesn't exist
if (!lines.includes(val)) {
lines.push(val);
processContent(JSON.stringify(lines));
}
// Compute target code prefix
let code = 'z';
let c1 = ccharfirst.get(val[0]) || '';
if (val.length === 1) {
code += c1;
} else if (val.length === 2) {
let c2 = ccharfirst.get(val[1]) || '';
let c3 = ccharsecond.get(val[1]) || 'x';
code += c1 + c2 + c3;
} else {
let c2 = ccharfirst.get(val[1]) || '';
let c3 = ccharfirst.get(val[2]) || '';
code += c1 + c2 + c3;
}
currentEditCode = code.substring(0, 4);
}
// Initialize display rows depending on code length
if (currentEditCode.length >= 1) activeEditKeys.add('z');
if (currentEditCode.length >= 2) activeEditKeys.add(currentEditCode.substring(0, 2));
if (currentEditCode.length >= 3) activeEditKeys.add(currentEditCode.substring(0, 3));
if (currentEditCode.length >= 4) activeEditKeys.add(currentEditCode);
document.getElementById('inputArea').style.display = 'none';
document.getElementById('editArea').style.display = 'block';
renderEditArea();
}
function handleEditReset() {
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'none';
document.getElementById('codeInput').value = '';
currentEditCode = '';
currentTargetWord = '';
selectedEditItem = null;
activeEditKeys.clear();
changedWords.clear();
}
function renderEditArea() {
let container = document.getElementById('editRows');
container.innerHTML = '';
let sortedKeys = Array.from(activeEditKeys).sort((a, b) => {
if (a === 'z') return -1;
if (b === 'z') return 1;
if (a.length !== b.length) return a.length - b.length;
return a.localeCompare(b);
});
sortedKeys.forEach(k => {
let label = '';
let items = [];
if (k === 'z') {
label = 'z: mcc0all top 5';
(mcc0.get('z') || []).forEach(w => items.push({ w: w, cls: '' }));
} else if (k.length === 2) {
label = `${k}: mcc1all top 5`;
(mcc1all.get(k) || []).slice(0, 5).forEach(w => items.push({ w: w, cls: '' }));
} else if (k.length === 3) {
label = `${k}: mcc2all top 4`;
(mcc2all.get(k) || []).slice(0, 4).forEach(w => items.push({ w: w, cls: '' }));
} else if (k.length === 4) {
label = `${k}: all items from mcc3 and mcc4`;
(mcc3.get(k) || []).forEach(w => items.push({ w: w, cls: 'mcc3' }));
(mcc4.get(k) || []).forEach(w => items.push({ w: w, cls: 'mcc4' }));
}
if (items.length > 0) {
let rowDiv = document.createElement('div');
rowDiv.className = 'edit-row';
let titleDiv = document.createElement('div');
titleDiv.textContent = label;
titleDiv.style.fontWeight = 'bold';
titleDiv.style.marginBottom = '8px';
rowDiv.appendChild(titleDiv);
items.forEach(item => {
let span = document.createElement('span');
span.textContent = item.w;
span.className = `edit-item ${item.cls}`;
if (item.w === selectedEditItem) span.classList.add('selected');
if (item.w === currentTargetWord) span.classList.add('target-word');
if (changedWords.has(item.w)) span.classList.add('changed');
span.onclick = () => handleItemClick(item.w);
rowDiv.appendChild(span);
});
container.appendChild(rowDiv);
}
});
}
function getSystemState() {
let state = new Map();
state.set('z', mcc0.get('z') || []);
for (let [k, v] of mcc1all) state.set(k, v.slice(0, 5));
for (let [k, v] of mcc2all) state.set(k, v.slice(0, 4));
for (let [k, v] of mcc3) {
if (!state.has(k)) state.set(k, []);
state.get(k).push(...v);
}
for (let [k, v] of mcc4) {
if (!state.has(k)) state.set(k, []);
state.get(k).push(...v);
}
return state;
}
function detectAndRenderChanges(oldState, newState) {
let wordToOldPos = new Map();
for (let [k, vals] of oldState) {
vals.forEach((w, i) => wordToOldPos.set(w, `${k}-${i}`));
}
changedWords.clear();
for (let [k, vals] of newState) {
let oldVals = oldState.get(k) || [];
if (JSON.stringify(vals) !== JSON.stringify(oldVals)) {
activeEditKeys.add(k);
}
vals.forEach((w, i) => {
if (wordToOldPos.get(w) !== `${k}-${i}`) {
changedWords.add(w);
activeEditKeys.add(k);
}
});
}
renderEditArea();
}
function handleItemClick(word) {
if (!selectedEditItem) {
selectedEditItem = word;
renderEditArea();
} else {
if (selectedEditItem === word) {
selectedEditItem = null;
renderEditArea();
return;
}
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
let idxA = lines.indexOf(selectedEditItem);
let originalIdxB = lines.indexOf(word);
if (idxA > -1 && originalIdxB > -1) {
let oldState = getSystemState();
lines.splice(idxA, 1);
let idxB = lines.indexOf(word);
if (idxA > originalIdxB) {
lines.splice(idxB, 0, selectedEditItem);
} else {
lines.splice(idxB + 1, 0, selectedEditItem);
}
processContent(JSON.stringify(lines));
let newState = getSystemState();
detectAndRenderChanges(oldState, newState);
}
selectedEditItem = null;
}
}
// === END OF ADDED EDIT AREA FUNCTIONS ===
```