分页: 9 / 9
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 16:11
由 ejsoon
gemini thinking
代码: 全选
為了解決你的需求,我們需要在函式的一開始**紀錄進入函式前的 `isSingleWordMode` 狀態**(例如使用 `wasSingleWordMode` 變數)。
在原本的程式碼中,只要判定為單字模式,就會無條件執行 `activeEditKeys.clear()`,這會導致之前產生的編輯行(edit-row)全部被清空。為了讓它和詞語模式一樣可以「保留並累加」編輯行,我們只需要改為:**只有在「從詞語模式切換到單字模式」時才清空,如果是「連續輸入單字」則不清空 `activeEditKeys**`。
以下是修改後的完整 `handleEditSubmit` 函式:
```javascript
function handleEditSubmit() {
let val = document.getElementById('codeInput').value.trim();
if (!val) return;
// 【新增】記錄執行前的單字模式狀態,用來判斷是否為「再次執行單字模式」
let wasSingleWordMode = isSingleWordMode;
// Validation profile for the input string
let isEnglishCode = /^[a-zA-Z]+$/.test(val);
let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;
// Check input constraints if the editor interface is already operational
if (document.getElementById('editArea').style.display === 'block') {
if (isSingleWordMode && isPhraseInput) {
alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
return;
}
if (!isSingleWordMode && isSingleWordInput) {
alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
return;
}
}
document.getElementById('codeInput').value = '';
selectedEditItem = null;
justMovedItem = null;
pendingAction = null;
updateActionButtonsUI();
// Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
isSingleWordMode = false;
singleWordActiveCode = '';
currentTargetWord = '';
if (isEnglishCode && val[0].toLowerCase() !== 'z') {
isSingleWordMode = true;
singleWordActiveCode = val.toLowerCase().substring(0, 4);
} else if (!isEnglishCode && val.length === 1) {
isSingleWordMode = true;
currentTargetWord = val; // Set the target word to highlight the Chinese char
// Reverse lookup code from charDefCodeMap
for (let [code, chars] of charDefCodeMap) {
if (chars.includes(val)) {
singleWordActiveCode = code;
break;
}
}
}
if (isSingleWordMode) {
// 【修改】如果原本就已經是單字模式,就不清空 activeEditKeys,以保留當前的 edit-row
if (!wasSingleWordMode) {
activeEditKeys.clear(); // 只有從詞語模式切換過來時才清空
}
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
// 【修改】比照詞語模式,若已有歷史快照則不重複覆蓋,確保 Undo 機制正常
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
} else {
// Original Phrase Logic
if (isEnglishCode) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
// Check input pattern profile
if (/^[a-zA-Z]+$/.test(val)) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
if (!lines.includes(val)) {
// CRITICAL FIX: Capture baseline state *before* pushing the new word
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
saveStateForUndo(`Added new word: ${val}`);
lines.push(val);
processContent(JSON.stringify(lines));
}
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);
}
}
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);
// Standard initialization fallback block
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
}
// Keep input area visible allowing repeated inputs
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
// Toggle action buttons visibility based on single word vs phrase mode
if (isSingleWordMode) {
document.getElementById('btnMoveEnd').style.display = 'none';
document.getElementById('btnDelete').style.display = 'none';
} else {
document.getElementById('btnMoveEnd').style.display = 'inline-block';
document.getElementById('btnDelete').style.display = 'inline-block';
}
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
renderEditArea();
}
```
### 主要修改點說明:
1. **狀態攔截**:在函式頂端加入 `let wasSingleWordMode = isSingleWordMode;`,用來捕捉本次輸入前的模式。
2. **條件式清空**:將 `if (isSingleWordMode)` 區塊內的 `activeEditKeys.clear();` 包裹在 `if (!wasSingleWordMode)` 判斷中。這樣一來,若前一次已經是單字模式,連續輸入新單字時,舊有的編碼按鈕與 row 就不會被洗掉,而是會像詞語模式一樣疊加保留。
3. **快照保護**:同步將 `originalSnapshotState = getSystemState();` 改為安全守衛寫法 `if (!originalSnapshotState ...)`,避免連續輸入時把最初的備份狀態給覆蓋掉了。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 16:16
由 ejsoon
把所有注釋裡的中文全部翻譯成英文,同時,不存在「詞語模式跳到單字模式」的情況,當處於一種模式時,不允許跳到另一種模式。
代码: 全选
Here is the updated `handleEditSubmit` function. All comments have been translated into English, and the logic handles mode persistence correctly.
Since mode switching is strictly disallowed once active, the check `if (!wasSingleWordMode)` will only evaluate to `true` during the very first initialization of the single-word mode session (when `editArea` is not yet open), ensuring a clean slate. On all subsequent sequential inputs within the same mode, it will safely skip clearing the keys, preserving your edit rows just like phrase mode does.
```javascript
function handleEditSubmit() {
let val = document.getElementById('codeInput').value.trim();
if (!val) return;
// Record the single word mode state before execution to check for repeated inputs
let wasSingleWordMode = isSingleWordMode;
// Validation profile for the input string
let isEnglishCode = /^[a-zA-Z]+$/.test(val);
let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;
// Check input constraints if the editor interface is already operational
if (document.getElementById('editArea').style.display === 'block') {
if (isSingleWordMode && isPhraseInput) {
alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
return;
}
if (!isSingleWordMode && isSingleWordInput) {
alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
return;
}
}
document.getElementById('codeInput').value = '';
selectedEditItem = null;
justMovedItem = null;
pendingAction = null;
updateActionButtonsUI();
// Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
isSingleWordMode = false;
singleWordActiveCode = '';
currentTargetWord = '';
if (isEnglishCode && val[0].toLowerCase() !== 'z') {
isSingleWordMode = true;
singleWordActiveCode = val.toLowerCase().substring(0, 4);
} else if (!isEnglishCode && val.length === 1) {
isSingleWordMode = true;
currentTargetWord = val; // Set the target word to highlight the Chinese char
// Reverse lookup code from charDefCodeMap
for (let [code, chars] of charDefCodeMap) {
if (chars.includes(val)) {
singleWordActiveCode = code;
break;
}
}
}
if (isSingleWordMode) {
// If already in single word mode, do not clear activeEditKeys to retain current edit rows
if (!wasSingleWordMode) {
activeEditKeys.clear(); // Only clear during initial session setup
}
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
// Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
} else {
// Original Phrase Logic
if (isEnglishCode) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
// Check input pattern profile
if (/^[a-zA-Z]+$/.test(val)) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
if (!lines.includes(val)) {
// CRITICAL FIX: Capture baseline state *before* pushing the new word
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
saveStateForUndo(`Added new word: ${val}`);
lines.push(val);
processContent(JSON.stringify(lines));
}
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);
}
}
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);
// Standard initialization fallback block
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
}
// Keep input area visible allowing repeated inputs
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
// Toggle action buttons visibility based on single word vs phrase mode
if (isSingleWordMode) {
document.getElementById('btnMoveEnd').style.display = 'none';
document.getElementById('btnDelete').style.display = 'none';
} else {
document.getElementById('btnMoveEnd').style.display = 'inline-block';
document.getElementById('btnDelete').style.display = 'inline-block';
}
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
renderEditArea();
}
```
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 16:35
由 ejsoon
domchar5763.html
在單字模式下,undo,redo,點擊log-item,都無效。查找原因並修改代碼。
需要明確給出所要修改的地方以及要替換的代碼,不用給出所有的代碼。所有的注釋都要使用英文。
代码: 全选
```
function handleEditSubmit() {
let val = document.getElementById('codeInput').value.trim();
if (!val) return;
// Record the single word mode state before execution to check for repeated inputs
let wasSingleWordMode = isSingleWordMode;
// Validation profile for the input string
let isEnglishCode = /^[a-zA-Z]+$/.test(val);
let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;
// Check input constraints if the editor interface is already operational
if (document.getElementById('editArea').style.display === 'block') {
if (isSingleWordMode && isPhraseInput) {
alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
return;
}
if (!isSingleWordMode && isSingleWordInput) {
alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
return;
}
}
document.getElementById('codeInput').value = '';
selectedEditItem = null;
justMovedItem = null;
pendingAction = null;
updateActionButtonsUI();
// Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
isSingleWordMode = false;
singleWordActiveCode = '';
currentTargetWord = '';
if (isEnglishCode && val[0].toLowerCase() !== 'z') {
isSingleWordMode = true;
singleWordActiveCode = val.toLowerCase().substring(0, 4);
} else if (!isEnglishCode && val.length === 1) {
isSingleWordMode = true;
currentTargetWord = val; // Set the target word to highlight the Chinese char
// Reverse lookup code from charDefCodeMap
for (let [code, chars] of charDefCodeMap) {
if (chars.includes(val)) {
singleWordActiveCode = code;
break;
}
}
}
if (isSingleWordMode) {
// If already in single word mode, do not clear activeEditKeys to retain current edit rows
if (!wasSingleWordMode) {
activeEditKeys.clear(); // Only clear during initial session setup
}
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
// Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
} else {
// Original Phrase Logic
if (isEnglishCode) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
// Check input pattern profile
if (/^[a-zA-Z]+$/.test(val)) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
if (!lines.includes(val)) {
// CRITICAL FIX: Capture baseline state *before* pushing the new word
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
saveStateForUndo(`Added new word: ${val}`);
lines.push(val);
processContent(JSON.stringify(lines));
}
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);
}
}
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);
// Standard initialization fallback block
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
}
// Keep input area visible allowing repeated inputs
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
// Toggle action buttons visibility based on single word vs phrase mode
if (isSingleWordMode) {
document.getElementById('btnMoveEnd').style.display = 'none';
document.getElementById('btnDelete').style.display = 'none';
} else {
document.getElementById('btnMoveEnd').style.display = 'inline-block';
document.getElementById('btnDelete').style.display = 'inline-block';
}
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
renderEditArea();
}
// Helper to restore state and refresh UI
function applyState(targetState) {
if (isSingleWordMode) {
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
} else {
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
}
function handleUndo() {
if (currentIndex < 0) return; // Allow undoing down to the initial state
let oldState = getSystemState();
currentIndex--; // Move pointer back
let targetState;
if (currentIndex === -1) {
// Apply the BEFORE state of the first action (Initial State)
targetState = historyStack[0];
} else {
// Apply the AFTER state of the action we stepped back to
targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
}
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function handleRedo() {
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
currentIndex++; // Move pointer forward
// Apply the AFTER state of the redone action
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function jumpToLog(index) {
if (index < 0 || index >= historyStack.length || index === currentIndex) return;
let oldState = getSystemState();
currentIndex = index; // Jump directly to the selected state's index
// Apply the AFTER state of the selected action log
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function saveStateForUndo(description = 'Manual Edit') {
// Truncate the history stack if the pointer is not at the end
if (currentIndex < historyStack.length - 1) {
historyStack = historyStack.slice(0, currentIndex + 1);
}
let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let charDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
charDefMapCopy.set(k, [...v]);
}
// Push the 'before' state
historyStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: description,
afterState: null // Will be populated after the current action completes
});
// Always point to the latest state after saving
currentIndex = historyStack.length - 1;
renderActionLogs();
// Asynchronously capture the state AFTER the synchronous action finishes
let capturedIndex = currentIndex;
setTimeout(() => {
if (historyStack[capturedIndex]) {
let afterLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let afterCharDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
afterCharDefMapCopy.set(k, [...v]);
}
historyStack[capturedIndex].afterState = {
lines: afterLines,
filecontent: filecontent,
charDefCodeMap: afterCharDefMapCopy
};
}
}, 0);
}
```
在單字模式下,undo,redo,點擊log-item,都無效。查找原因並修改代碼。
需要明確給出所要修改的地方以及要替換的代碼,不用給出所有的代碼。所有的注釋都要使用英文。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 16:37
由 ejsoon
gemini thinking
代码: 全选
### 原因分析
在單字模式(`isSingleWordMode === true`)下,`undo`、`redo` 和點擊 `log-item` 失效的核心原因在於 **`applyState` 函數的邏輯缺失**:
1. **未復原數據狀態**:當 `isSingleWordMode` 為 true 時,`applyState` 只更新了 `activeEditKeys`,卻完全跳過了實際數據(`filecontent` 與記錄單字編碼映射的 `charDefCodeMap`)的復原。
2. **未刷新 UI 渲染**:單字模式下的修改直接作用於字元映射,狀態回溯後必須重新呼叫 `renderEditArea()`,否則畫面上的編輯列表不會同步更新。
---
### 修改方案
請將原本的 `applyState` 函數替換為以下代碼。此修改確保了在單字模式下能正確還原 `filecontent`、深拷貝(Deep Copy)還原 `charDefCodeMap`,並即時刷新編輯區界面。
#### 尋找並替換 `applyState` 函數:
```javascript
// Helper to restore state and refresh UI
function applyState(targetState) {
if (isSingleWordMode) {
// Restore core data state for single word mode
filecontent = targetState.filecontent;
if (targetState.charDefCodeMap) {
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
}
// Re-populate active edit keys configuration
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
// Crucial: Refresh the edit workspace UI to reflect the restored state
renderEditArea();
} else {
// Original Phrase Logic
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
}
```
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 16:55
由 ejsoon
domchar5764.html
我認為問題已解決,就是花了太多時間。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 20:31
由 ejsoon
當前的代碼,如果一開始輸入的是一個新詞,則log中會出現一個item,點擊undo時會回到沒加新詞的狀態。如果一開始輸入的是一個已有的詞,則log中不會出現item。
現在要改成,如果一開始輸入的是一個新詞,則log中會出現一個item,點擊undo時不會回到沒加新詞的狀態,也就是說historyStack至少存在一項,currentIndex最小只能是0而不能是-1。如果一開始輸入的是一個已有的詞,則log中會出現第一個item,它的內容是「begin with searching ...」。點擊undo時回到第一個item就不能再回退。注意只限於一開始時輸入已有的詞會在log中出現,即在editArea不存在時。
代码: 全选
```
function handleEditSubmit() {
let val = document.getElementById('codeInput').value.trim();
if (!val) return;
// Record the single word mode state before execution to check for repeated inputs
let wasSingleWordMode = isSingleWordMode;
// Validation profile for the input string
let isEnglishCode = /^[a-zA-Z]+$/.test(val);
let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;
// Check input constraints if the editor interface is already operational
if (document.getElementById('editArea').style.display === 'block') {
if (isSingleWordMode && isPhraseInput) {
alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
return;
}
if (!isSingleWordMode && isSingleWordInput) {
alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
return;
}
}
document.getElementById('codeInput').value = '';
selectedEditItem = null;
justMovedItem = null;
pendingAction = null;
updateActionButtonsUI();
// Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
isSingleWordMode = false;
singleWordActiveCode = '';
currentTargetWord = '';
if (isEnglishCode && val[0].toLowerCase() !== 'z') {
isSingleWordMode = true;
singleWordActiveCode = val.toLowerCase().substring(0, 4);
} else if (!isEnglishCode && val.length === 1) {
isSingleWordMode = true;
currentTargetWord = val; // Set the target word to highlight the Chinese char
// Reverse lookup code from charDefCodeMap
for (let [code, chars] of charDefCodeMap) {
if (chars.includes(val)) {
singleWordActiveCode = code;
break;
}
}
}
if (isSingleWordMode) {
// If already in single word mode, do not clear activeEditKeys to retain current edit rows
if (!wasSingleWordMode) {
activeEditKeys.clear(); // Only clear during initial session setup
}
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
// Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
} else {
// Original Phrase Logic
if (isEnglishCode) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
// Check input pattern profile
if (/^[a-zA-Z]+$/.test(val)) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
if (!lines.includes(val)) {
// CRITICAL FIX: Capture baseline state *before* pushing the new word
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
saveStateForUndo(`Added new word: ${val}`);
lines.push(val);
processContent(JSON.stringify(lines));
}
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);
}
}
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);
// Standard initialization fallback block
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
}
// Keep input area visible allowing repeated inputs
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
// Toggle action buttons visibility based on single word vs phrase mode
if (isSingleWordMode) {
document.getElementById('btnMoveEnd').style.display = 'none';
document.getElementById('btnDelete').style.display = 'none';
} else {
document.getElementById('btnMoveEnd').style.display = 'inline-block';
document.getElementById('btnDelete').style.display = 'inline-block';
}
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
renderEditArea();
}
// Helper to restore state and refresh UI
function applyState(targetState) {
if (isSingleWordMode) {
// Restore core data state for single word mode
filecontent = targetState.filecontent;
if (targetState.charDefCodeMap) {
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
}
// Re-populate active edit keys configuration
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
// Crucial: Refresh the edit workspace UI to reflect the restored state
renderEditArea();
} else {
// Original Phrase Logic
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
}
function handleUndo() {
if (currentIndex < 0) return; // Allow undoing down to the initial state
let oldState = getSystemState();
currentIndex--; // Move pointer back
let targetState;
if (currentIndex === -1) {
// Apply the BEFORE state of the first action (Initial State)
targetState = historyStack[0];
} else {
// Apply the AFTER state of the action we stepped back to
targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
}
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function handleRedo() {
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
currentIndex++; // Move pointer forward
// Apply the AFTER state of the redone action
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function jumpToLog(index) {
if (index < 0 || index >= historyStack.length || index === currentIndex) return;
let oldState = getSystemState();
currentIndex = index; // Jump directly to the selected state's index
// Apply the AFTER state of the selected action log
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function saveStateForUndo(description = 'Manual Edit') {
// Truncate the history stack if the pointer is not at the end
if (currentIndex < historyStack.length - 1) {
historyStack = historyStack.slice(0, currentIndex + 1);
}
let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let charDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
charDefMapCopy.set(k, [...v]);
}
// Push the 'before' state
historyStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: description,
afterState: null // Will be populated after the current action completes
});
// Always point to the latest state after saving
currentIndex = historyStack.length - 1;
renderActionLogs();
// Asynchronously capture the state AFTER the synchronous action finishes
let capturedIndex = currentIndex;
setTimeout(() => {
if (historyStack[capturedIndex]) {
let afterLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let afterCharDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
afterCharDefMapCopy.set(k, [...v]);
}
historyStack[capturedIndex].afterState = {
lines: afterLines,
filecontent: filecontent,
charDefCodeMap: afterCharDefMapCopy
};
}
}, 0);
}
```
當前的代碼,如果一開始輸入的是一個新詞,則log中會出現一個item,點擊undo時會回到沒加新詞的狀態。如果一開始輸入的是一個已有的詞,則log中不會出現item。
現在要改成,如果一開始輸入的是一個新詞,則log中會出現一個item,點擊undo時不會回到沒加新詞的狀態,也就是說historyStack至少存在一項,currentIndex最小只能是0而不能是-1。如果一開始輸入的是一個已有的詞,則log中會出現第一個item,它的內容是「begin with searching ...」。點擊undo時回到第一個item就不能再回退。注意只限於一開始時輸入已有的詞會在log中出現,即在editArea不存在時。
所有的注釋都要使用英文。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 20:37
由 ejsoon
gemini 3.1pro
代码: 全选
Here is the modified code to meet your requirements.
I have adjusted the logic so that any initial input (when `editArea` is not yet visible) will register as the foundational action in the history log. If it's a new word, it logs as "Added new word: ..."; if it's an existing word, it logs as "begin with searching ...". I also updated the `handleUndo` function to establish `0` as the absolute minimum `currentIndex`, ensuring the user cannot back out of the very first state (the initial new word or the initial search). All comments are provided in English.
```javascript
function handleEditSubmit() {
let val = document.getElementById('codeInput').value.trim();
if (!val) return;
// Check if the editor interface is hidden, indicating this is the initial input
let isInitialInput = document.getElementById('editArea').style.display !== 'block';
// Record the single word mode state before execution to check for repeated inputs
let wasSingleWordMode = isSingleWordMode;
// Validation profile for the input string
let isEnglishCode = /^[a-zA-Z]+$/.test(val);
let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;
// Check input constraints if the editor interface is already operational
if (!isInitialInput) {
if (isSingleWordMode && isPhraseInput) {
alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
return;
}
if (!isSingleWordMode && isSingleWordInput) {
alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
return;
}
}
// Determine if a new word is about to be added to the dictionary
let willAddNewWord = false;
if (!isEnglishCode && val.length > 1) {
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
if (!lines.includes(val)) {
willAddNewWord = true;
}
}
// If this is the initial input and it's an existing word/code, log the starting action
if (isInitialInput && !willAddNewWord) {
saveStateForUndo(`begin with searching ${val}`);
}
document.getElementById('codeInput').value = '';
selectedEditItem = null;
justMovedItem = null;
pendingAction = null;
updateActionButtonsUI();
// Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
isSingleWordMode = false;
singleWordActiveCode = '';
currentTargetWord = '';
if (isEnglishCode && val[0].toLowerCase() !== 'z') {
isSingleWordMode = true;
singleWordActiveCode = val.toLowerCase().substring(0, 4);
} else if (!isEnglishCode && val.length === 1) {
isSingleWordMode = true;
currentTargetWord = val; // Set the target word to highlight the Chinese char
// Reverse lookup code from charDefCodeMap
for (let [code, chars] of charDefCodeMap) {
if (chars.includes(val)) {
singleWordActiveCode = code;
break;
}
}
}
if (isSingleWordMode) {
// If already in single word mode, do not clear activeEditKeys to retain current edit rows
if (!wasSingleWordMode) {
activeEditKeys.clear(); // Only clear during initial session setup
}
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
// Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
} else {
// Original Phrase Logic
if (isEnglishCode) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
// Check input pattern profile
if (/^[a-zA-Z]+$/.test(val)) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
if (!lines.includes(val)) {
// CRITICAL FIX: Capture baseline state *before* pushing the new word
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
saveStateForUndo(`Added new word: ${val}`);
lines.push(val);
processContent(JSON.stringify(lines));
}
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);
}
}
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);
// Standard initialization fallback block
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
}
// Keep input area visible allowing repeated inputs
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
// Toggle action buttons visibility based on single word vs phrase mode
if (isSingleWordMode) {
document.getElementById('btnMoveEnd').style.display = 'none';
document.getElementById('btnDelete').style.display = 'none';
} else {
document.getElementById('btnMoveEnd').style.display = 'inline-block';
document.getElementById('btnDelete').style.display = 'inline-block';
}
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
renderEditArea();
}
// Helper to restore state and refresh UI
function applyState(targetState) {
if (isSingleWordMode) {
// Restore core data state for single word mode
filecontent = targetState.filecontent;
if (targetState.charDefCodeMap) {
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
}
// Re-populate active edit keys configuration
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
// Crucial: Refresh the edit workspace UI to reflect the restored state
renderEditArea();
} else {
// Original Phrase Logic
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
}
function handleUndo() {
// Prevent undoing past the initial logged action (index 0).
// The user can no longer revert to the unlogged state (-1).
if (currentIndex <= 0) return;
let oldState = getSystemState();
currentIndex--; // Move pointer back
// Apply the AFTER state of the action we stepped back to
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function handleRedo() {
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
currentIndex++; // Move pointer forward
// Apply the AFTER state of the redone action
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function jumpToLog(index) {
if (index < 0 || index >= historyStack.length || index === currentIndex) return;
let oldState = getSystemState();
currentIndex = index; // Jump directly to the selected state's index
// Apply the AFTER state of the selected action log
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function saveStateForUndo(description = 'Manual Edit') {
// Truncate the history stack if the pointer is not at the end
if (currentIndex < historyStack.length - 1) {
historyStack = historyStack.slice(0, currentIndex + 1);
}
let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let charDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
charDefMapCopy.set(k, [...v]);
}
// Push the 'before' state
historyStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: description,
afterState: null // Will be populated after the current action completes
});
// Always point to the latest state after saving
currentIndex = historyStack.length - 1;
renderActionLogs();
// Asynchronously capture the state AFTER the synchronous action finishes
let capturedIndex = currentIndex;
setTimeout(() => {
if (historyStack[capturedIndex]) {
let afterLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let afterCharDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
afterCharDefMapCopy.set(k, [...v]);
}
historyStack[capturedIndex].afterState = {
lines: afterLines,
filecontent: filecontent,
charDefCodeMap: afterCharDefMapCopy
};
}
}, 0);
}
```
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 20:55
由 ejsoon
當我在單字模式中作了改動,點擊cancel或finish之後,如果再次進入詞語模式,對比欄全部變為空,這是不對的,程式應在上一次cancal或finish後,清空對比欄的數據,不能影響到下一次的進程。
代码: 全选
```javascript
function handleEditSubmit() {
let val = document.getElementById('codeInput').value.trim();
if (!val) return;
// Check if the editor interface is hidden, indicating this is the initial input
let isInitialInput = document.getElementById('editArea').style.display !== 'block';
// Record the single word mode state before execution to check for repeated inputs
let wasSingleWordMode = isSingleWordMode;
// Validation profile for the input string
let isEnglishCode = /^[a-zA-Z]+$/.test(val);
let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;
// Check input constraints if the editor interface is already operational
if (!isInitialInput) {
if (isSingleWordMode && isPhraseInput) {
alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
return;
}
if (!isSingleWordMode && isSingleWordInput) {
alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
return;
}
}
// Determine if a new word is about to be added to the dictionary
let willAddNewWord = false;
if (!isEnglishCode && val.length > 1) {
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
if (!lines.includes(val)) {
willAddNewWord = true;
}
}
// If this is the initial input and it's an existing word/code, log the starting action
if (isInitialInput && !willAddNewWord) {
saveStateForUndo(`begin with searching ${val}`);
}
document.getElementById('codeInput').value = '';
selectedEditItem = null;
justMovedItem = null;
pendingAction = null;
updateActionButtonsUI();
// Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
isSingleWordMode = false;
singleWordActiveCode = '';
currentTargetWord = '';
if (isEnglishCode && val[0].toLowerCase() !== 'z') {
isSingleWordMode = true;
singleWordActiveCode = val.toLowerCase().substring(0, 4);
} else if (!isEnglishCode && val.length === 1) {
isSingleWordMode = true;
currentTargetWord = val; // Set the target word to highlight the Chinese char
// Reverse lookup code from charDefCodeMap
for (let [code, chars] of charDefCodeMap) {
if (chars.includes(val)) {
singleWordActiveCode = code;
break;
}
}
}
if (isSingleWordMode) {
// If already in single word mode, do not clear activeEditKeys to retain current edit rows
if (!wasSingleWordMode) {
activeEditKeys.clear(); // Only clear during initial session setup
}
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
// Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
} else {
// Original Phrase Logic
if (isEnglishCode) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
// Check input pattern profile
if (/^[a-zA-Z]+$/.test(val)) {
if (val[0].toLowerCase() !== 'z') val = 'z' + val;
val = val.substring(0, 4).toLowerCase();
currentEditCode = val;
currentTargetWord = '';
} else {
currentTargetWord = val;
let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
if (!lines.includes(val)) {
// CRITICAL FIX: Capture baseline state *before* pushing the new word
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
saveStateForUndo(`Added new word: ${val}`);
lines.push(val);
processContent(JSON.stringify(lines));
}
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);
}
}
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);
// Standard initialization fallback block
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
}
// Keep input area visible allowing repeated inputs
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
// Toggle action buttons visibility based on single word vs phrase mode
if (isSingleWordMode) {
document.getElementById('btnMoveEnd').style.display = 'none';
document.getElementById('btnDelete').style.display = 'none';
} else {
document.getElementById('btnMoveEnd').style.display = 'inline-block';
document.getElementById('btnDelete').style.display = 'inline-block';
}
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'block';
renderEditArea();
}
// Helper to restore state and refresh UI
function applyState(targetState) {
if (isSingleWordMode) {
// Restore core data state for single word mode
filecontent = targetState.filecontent;
if (targetState.charDefCodeMap) {
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
}
// Re-populate active edit keys configuration
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
// Crucial: Refresh the edit workspace UI to reflect the restored state
renderEditArea();
} else {
// Original Phrase Logic
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
}
function handleUndo() {
// Prevent undoing past the initial logged action (index 0).
// The user can no longer revert to the unlogged state (-1).
if (currentIndex <= 0) return;
let oldState = getSystemState();
currentIndex--; // Move pointer back
// Apply the AFTER state of the action we stepped back to
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function handleRedo() {
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
currentIndex++; // Move pointer forward
// Apply the AFTER state of the redone action
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function jumpToLog(index) {
if (index < 0 || index >= historyStack.length || index === currentIndex) return;
let oldState = getSystemState();
currentIndex = index; // Jump directly to the selected state's index
// Apply the AFTER state of the selected action log
let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function saveStateForUndo(description = 'Manual Edit') {
// Truncate the history stack if the pointer is not at the end
if (currentIndex < historyStack.length - 1) {
historyStack = historyStack.slice(0, currentIndex + 1);
}
let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let charDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
charDefMapCopy.set(k, [...v]);
}
// Push the 'before' state
historyStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: description,
afterState: null // Will be populated after the current action completes
});
// Always point to the latest state after saving
currentIndex = historyStack.length - 1;
renderActionLogs();
// Asynchronously capture the state AFTER the synchronous action finishes
let capturedIndex = currentIndex;
setTimeout(() => {
if (historyStack[capturedIndex]) {
let afterLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let afterCharDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
afterCharDefMapCopy.set(k, [...v]);
}
historyStack[capturedIndex].afterState = {
lines: afterLines,
filecontent: filecontent,
charDefCodeMap: afterCharDefMapCopy
};
}
}, 0);
}
function handleCancelEdit() {
// Revert to initial state before any edits in this session were made
if (historyStack.length > 0) {
applyState(historyStack[0]);
}
// Close the edit interface
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'none';
document.getElementById('codeInput').value = '';
// Reset tracking variables without updating originalSnapshotState
currentEditCode = '';
currentTargetWord = '';
selectedEditItem = null;
justMovedItem = null;
activeEditKeys.clear();
changedWords.clear();
pendingAction = null;
historyStack = [];
currentIndex = -1;
renderActionLogs();
updateActionButtonsUI();
}
function handleEditCloseOnly() {
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'none';
document.getElementById('codeInput').value = '';
originalSnapshotState = getSystemState();
currentEditCode = '';
currentTargetWord = '';
selectedEditItem = null;
justMovedItem = null;
activeEditKeys.clear();
changedWords.clear();
pendingAction = null;
historyStack = [];
currentIndex = -1;
renderActionLogs(); // Visually empty out the log UI on close
updateActionButtonsUI();
}
```
當我在單字模式中作了改動,點擊cancel或finish之後,如果再次進入詞語模式,對比欄全部變為空,這是不對的,程式應在上一次cancal或finish後,清空對比欄的數據,不能影響到下一次的進程。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 21:03
由 ejsoon
代码: 全选
完全理解你的困擾。狀態殘留導致下一次操作的對比欄位失效,這在切換不同模式時確實是個常見且讓人頭痛的邏輯漏洞。
問題的根源在於你的 **`originalSnapshotState`(原始快照狀態)沒有被正確釋放**。
在 `handleEditSubmit` 中,程式依賴以下邏輯來擷取當前進程的基準點:
```javascript
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
```
但在目前的 `handleCancelEdit` 與 `handleEditCloseOnly` 函數中,`originalSnapshotState` 要麼被原封不動地保留,要麼被更新成當前狀態,**卻從未被清空**。這導致你下一次進入「詞語模式」時,程式誤以為基準點已經存在,從而跳過了擷取新基準點的步驟,最終導致對比欄抓不到正確的差異數據而變為空。
以下是針對這兩個函數的修正程式碼。
### 修正方案
你需要將 `originalSnapshotState` 在退出編輯時強制設為 `null`,強迫下一次的 `handleEditSubmit` 重新抓取乾淨的基準點。
**1. 修正 `handleCancelEdit**`
```javascript
function handleCancelEdit() {
// Revert to initial state before any edits in this session were made
if (historyStack.length > 0) {
applyState(historyStack[0]);
}
// Close the edit interface
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'none';
document.getElementById('codeInput').value = '';
// 【關鍵修正】:徹底清空快照狀態,避免干擾下一次進程
originalSnapshotState = null;
// Reset other tracking variables
currentEditCode = '';
currentTargetWord = '';
selectedEditItem = null;
justMovedItem = null;
activeEditKeys.clear();
changedWords.clear();
pendingAction = null;
historyStack = [];
currentIndex = -1;
renderActionLogs();
updateActionButtonsUI();
}
```
**2. 修正 `handleEditCloseOnly**`
```javascript
function handleEditCloseOnly() {
document.getElementById('inputArea').style.display = 'block';
document.getElementById('editArea').style.display = 'none';
document.getElementById('codeInput').value = '';
// 【關鍵修正】:這裡原本是 originalSnapshotState = getSystemState();
// 應改為 null,讓下一次重新進入時再透過 handleEditSubmit 抓取最新的基準點
originalSnapshotState = null;
currentEditCode = '';
currentTargetWord = '';
selectedEditItem = null;
justMovedItem = null;
activeEditKeys.clear();
changedWords.clear();
pendingAction = null;
historyStack = [];
currentIndex = -1;
renderActionLogs(); // Visually empty out the log UI on close
updateActionButtonsUI();
}
```
### 為什麼這樣改有效?
* **Cancel (取消)**:放棄所有更改並恢復到歷史紀錄的第一筆。此時清空 `originalSnapshotState`、`activeEditKeys` 與 `changedWords`,確保系統狀態完全重置。
* **Finish / Close (完成並關閉)**:保留現有的更改並退出編輯區。將 `originalSnapshotState` 設為 `null` 可以確保下次使用者再次觸發 `handleEditSubmit` 輸入新代碼時,程式能精準地把「那一刻」當作新的比較基準,對比欄就能正常顯示該次進程的改動了。
gemini 3.1 pro
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 21:08
由 ejsoon
domchar5765.html
修正了一個極其嚴重的問題。