分页: 8 / 9
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 10:50
由 ejsoon
改進:
分析handleUndo(),handleRedo(),jumpToLog()函式,它是如何對actionLogsList進行操作的。
現在要改為,當前操作會在log的item高亮,undo和redo都只在log切換高亮的item。點擊item也是切換高亮。
去掉reset按鈕以及handleEditResetOnly()相關代碼。
需要準確給出所要修改的地方以及所要替換的代碼,不用給出全部代碼,所有的注釋都要使用英文。
代码: 全选
改進:
分析handleUndo(),handleRedo(),jumpToLog()函式,它是如何對actionLogsList進行操作的。
現在要改為,當前操作會在log的item高亮,undo和redo都只在log切換高亮的item。點擊item也是切換高亮。
去掉reset按鈕以及handleEditResetOnly()相關代碼。
需要準確給出所要修改的地方以及所要替換的代碼,不用給出全部代碼,所有的注釋都要使用英文。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 11:01
由 ejsoon
代码: 全选
``` function handleUndo() {
if (undoStack.length === 0) return;
let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let charDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
charDefMapCopy.set(k, [...v]);
}
let lastActionDesc = undoStack[undoStack.length - 1].desc;
redoStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: `Undo: ${lastActionDesc}`
});
let prevState = undoStack.pop();
let oldState = getSystemState();
if (isSingleWordMode) {
// --- SINGLE WORD MODE UNDO BRANCH ---
charDefCodeMap = new Map();
for (let [k, v] of prevState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
} else {
// --- EXISTING PHRASE MODE BRANCH ---
filecontent = prevState.filecontent;
processContent(JSON.stringify(prevState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function handleRedo() {
if (redoStack.length === 0) return;
let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let charDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
charDefMapCopy.set(k, [...v]);
}
let nextState = redoStack.pop();
undoStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: nextState.desc
});
let oldState = getSystemState();
if (isSingleWordMode) {
// --- SINGLE WORD MODE REDO BRANCH ---
charDefCodeMap = new Map();
for (let [k, v] of nextState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
} else {
// --- EXISTING PHRASE MODE BRANCH ---
filecontent = nextState.filecontent;
processContent(JSON.stringify(nextState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function jumpToLog(index) {
if (index < 0 || index >= undoStack.length) return;
// Current operational baseline state capture
let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let charDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
charDefMapCopy.set(k, [...v]);
}
// Target state represents the snapshot *before* the clicked action was performed.
// To arrive at the state *after* the action was performed, we need to apply the state
// that immediately follows it in the timeline, or stay on current state if it's the top.
let targetState;
if (index === undoStack.length - 1) {
// Already at the latest state executed, no content update needed
return;
} else {
// The state immediately following index represents the system condition AFTER index action completed
targetState = undoStack[index + 1];
}
// Transfer chronological items into the redo stack from the trailing end down to the split boundary
for (let i = undoStack.length - 1; i > index; i--) {
redoStack.push({
lines: i === undoStack.length - 1 ? currentLines : undoStack[i + 1].lines,
filecontent: i === undoStack.length - 1 ? filecontent : undoStack[i + 1].filecontent,
charDefCodeMap: i === undoStack.length - 1 ? charDefMapCopy : undoStack[i + 1].charDefCodeMap,
desc: undoStack[i].desc
});
}
// Keep the stack history slice up to and including the targeted executed action index
undoStack = undoStack.slice(0, index + 1);
let oldState = getSystemState();
if (isSingleWordMode) {
// --- SINGLE WORD MODE STATE RESTORATION ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
if (typeof runDomumorText === 'function') {
runDomumorText();
}
} else {
// --- PHRASE MODE STATE RESTORATION ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
// Draws the visual list of history operations
function renderActionLogs() {
const container = document.getElementById('actionLogContainer');
const list = document.getElementById('actionLogsList');
if (!container || !list) return;
if (undoStack.length === 0) {
container.style.display = 'none';
return;
}
container.style.display = 'block';
list.innerHTML = '';
undoStack.forEach((log, index) => {
let logDiv = document.createElement('div');
logDiv.className = 'log-item';
logDiv.textContent = log.desc;
logDiv.onclick = () => jumpToLog(index);
list.appendChild(logDiv);
});
}
```
分析以上四個函式,現在
let undoStack = [];
let redoStack = [];
已經替換為:
let historyStack = [];
let currentIndex = -1;
現在要改為,當前操作會在log的item高亮,undo和redo都只在log切換高亮的item。點擊item也是切換高亮。
需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 11:12
由 ejsoon
gemini pro
代码: 全选
以下是針對使用單一 `historyStack` 和 `currentIndex` 的架構所需要替換的代碼部分。這種設計大大簡化了歷史記錄的切換,因為我們不再需要在兩個陣列之間搬運資料,只需移動指標並讀取對應的狀態即可。
請將您原有的四個函式替換為以下代碼。所有的註解已更改為英文。
### 1. `handleUndo`
**修改重點:** 移除操作 `undoStack` 和 `redoStack` 的推入與彈出邏輯,改為檢查並遞減 `currentIndex`,然後直接套用該索引的狀態。
```javascript
function handleUndo() {
// Check if we are already at the oldest state
if (currentIndex <= 0) return;
let oldState = getSystemState();
// Move the pointer back by one
currentIndex--;
let targetState = historyStack[currentIndex];
if (isSingleWordMode) {
// --- SINGLE WORD MODE UNDO BRANCH ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
} else {
// --- EXISTING PHRASE MODE BRANCH ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
```
### 2. `handleRedo`
**修改重點:** 同樣移除堆疊的搬運邏輯,改為檢查 `currentIndex` 是否到達頂端,遞增 `currentIndex` 並套用狀態。
```javascript
function handleRedo() {
// Check if we are already at the newest state
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
// Move the pointer forward by one
currentIndex++;
let targetState = historyStack[currentIndex];
if (isSingleWordMode) {
// --- SINGLE WORD MODE REDO BRANCH ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
} else {
// --- EXISTING PHRASE MODE BRANCH ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
```
### 3. `jumpToLog`
**修改重點:** 移除複雜的切片 (slice) 與搬移迴圈。現在只需要直接將 `currentIndex` 設為點擊的 `index`,並套用對應狀態。
```javascript
function jumpToLog(index) {
// Guard clause: prevent out of bounds or jumping to the current active state
if (index < 0 || index >= historyStack.length || index === currentIndex) return;
let oldState = getSystemState();
// Set the pointer to the clicked index
currentIndex = index;
let targetState = historyStack[currentIndex];
if (isSingleWordMode) {
// --- SINGLE WORD MODE STATE RESTORATION ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
if (typeof runDomumorText === 'function') {
runDomumorText();
}
} else {
// --- PHRASE MODE STATE RESTORATION ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
```
### 4. `renderActionLogs`
**修改重點:** 改為遍歷 `historyStack`。在建立 DOM 元素時,比對當前迴圈的 `index` 是否等於 `currentIndex`,若是,則加上高亮的 CSS class。
```javascript
// Draws the visual list of history operations
function renderActionLogs() {
const container = document.getElementById('actionLogContainer');
const list = document.getElementById('actionLogsList');
if (!container || !list) return;
if (historyStack.length === 0) {
container.style.display = 'none';
return;
}
container.style.display = 'block';
list.innerHTML = '';
historyStack.forEach((log, index) => {
let logDiv = document.createElement('div');
logDiv.className = 'log-item';
// Highlight the current state item
if (index === currentIndex) {
logDiv.classList.add('active-log'); // Make sure to define this in your CSS
}
logDiv.textContent = log.desc;
logDiv.onclick = () => jumpToLog(index);
list.appendChild(logDiv);
});
}
```
**額外提醒:** 別忘了在您的 CSS 檔案中加入 `.active-log` 類別,來定義高亮的樣式(例如改變背景顏色或文字顏色):
```css
.active-log {
background-color: #e0f7fa; /* Example highlight color */
font-weight: bold;
}
```
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 11:13
由 ejsoon
來自oneaiplus.cn的gemini 3.1 pro preview
代码: 全选
這裡為你提供改為單一 `historyStack` 和 `currentIndex` 指針機制的修改方案。
這種機制的優勢在於**不再需要在不同堆疊間搬移數據**,穿梭歷史只需改變 `currentIndex` 指針,並讀取對應狀態即可。同時我們會在 `renderActionLogs` 中根據 `currentIndex` 為當前步驟添加高亮。
以下是具體需要替換的代碼片段(所有的注釋已改為英文):
### 1. 替換 `handleUndo`
移除原本複雜的狀態備份和壓疊邏輯,改為只減少 `currentIndex` 指針並讀取狀態。
```javascript
function handleUndo() {
// Cannot undo if we are at the very beginning of the history
if (currentIndex <= 0) return;
let oldState = getSystemState();
// Move pointer backward
currentIndex--;
let targetState = historyStack[currentIndex];
if (isSingleWordMode) {
// --- SINGLE WORD MODE UNDO BRANCH ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
} else {
// --- EXISTING PHRASE MODE BRANCH ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs(); // This will visually update the highlighted log item
}
```
### 2. 替換 `handleRedo`
同樣地,改為只增加 `currentIndex` 指針並讀取狀態。
```javascript
function handleRedo() {
// Cannot redo if we are already at the newest state
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
// Move pointer forward
currentIndex++;
let targetState = historyStack[currentIndex];
if (isSingleWordMode) {
// --- SINGLE WORD MODE REDO BRANCH ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
} else {
// --- EXISTING PHRASE MODE BRANCH ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs(); // Update the highlighted log item
}
```
### 3. 替換 `jumpToLog`
移除原本為了維持 undo/redo 堆疊平衡而寫的迴圈切分邏輯,現在只需要直接將 `currentIndex` 設置為點擊的 `index`。
```javascript
function jumpToLog(index) {
// Prevent out-of-bounds or jumping to the currently active state
if (index < 0 || index >= historyStack.length || index === currentIndex) return;
let oldState = getSystemState();
// Directly set the pointer to the targeted index
currentIndex = index;
let targetState = historyStack[currentIndex];
if (isSingleWordMode) {
// --- SINGLE WORD MODE STATE RESTORATION ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
if (typeof runDomumorText === 'function') {
runDomumorText();
}
} else {
// --- PHRASE MODE STATE RESTORATION ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
```
### 4. 替換 `renderActionLogs`
改為遍歷 `historyStack`。在生成 DOM 節點時,判斷若當前項目的 `index` 等於 `currentIndex`,則為其加上高亮用的 class(例如 `active`)。
```javascript
// Draws the visual list of history operations and highlights the current state
function renderActionLogs() {
const container = document.getElementById('actionLogContainer');
const list = document.getElementById('actionLogsList');
if (!container || !list) return;
if (historyStack.length === 0) {
container.style.display = 'none';
return;
}
container.style.display = 'block';
list.innerHTML = '';
historyStack.forEach((log, index) => {
let logDiv = document.createElement('div');
logDiv.className = 'log-item';
// Highlight the currently active step
if (index === currentIndex) {
logDiv.classList.add('active'); // Add your CSS highlight styling to this class
}
logDiv.textContent = log.desc;
logDiv.onclick = () => jumpToLog(index);
list.appendChild(logDiv);
});
}
```
### 補充提醒 (CSS 部分)
為了讓高亮效果生效,你需要在你的 CSS 樣式表中加上針對 `.log-item.active` 的樣式,例如:
```css
.log-item.active {
background-color: #007bff; /* Example highlight color */
color: white;
font-weight: bold;
}
```
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 11:42
由 ejsoon
改進handleUndo(),handleRedo(),jumpToLog(),saveStateForUndo(),renderActionLogs()函式:
當點擊某個log-item時,或者當某個log-item高亮時,應是走到log-item已執行的狀態。
當從中間的某個log-item發生新的操作時,應在historyStack中間截斷,之後再在後面增加新的操作。
需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
代码: 全选
改進handleUndo(),handleRedo(),jumpToLog(),saveStateForUndo(),renderActionLogs()函式:
當點擊某個log-item時,或者當某個log-item高亮時,應是走到log-item已執行的狀態。
當從中間的某個log-item發生新的操作時,應在historyStack中間截斷,之後再在後面增加新的操作。
需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 14:15
由 ejsoon
改進handleUndo(),handleRedo(),jumpToLog(),renderActionLogs(),saveStateForUndo()函式:
當點擊某個log-item時,或者當某個log-item高亮時,應是走到當前log已執行的狀態。
當從中間的某個log-item發生新的操作時,應在historyStack中間截斷,之後再在後面增加新的操作。
需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
代码: 全选
```
function handleUndo() {
// Check if we are already at the oldest state
if (currentIndex <= 0) return;
let oldState = getSystemState();
// Move the pointer back by one
currentIndex--;
let targetState = historyStack[currentIndex];
if (isSingleWordMode) {
// --- SINGLE WORD MODE UNDO BRANCH ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
} else {
// --- EXISTING PHRASE MODE BRANCH ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function handleRedo() {
// Check if we are already at the newest state
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
// Move the pointer forward by one
currentIndex++;
let targetState = historyStack[currentIndex];
if (isSingleWordMode) {
// --- SINGLE WORD MODE REDO BRANCH ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
} else {
// --- EXISTING PHRASE MODE BRANCH ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function jumpToLog(index) {
// Guard clause: prevent out of bounds or jumping to the current active state
if (index < 0 || index >= historyStack.length || index === currentIndex) return;
let oldState = getSystemState();
// Set the pointer to the clicked index
currentIndex = index;
let targetState = historyStack[currentIndex];
if (isSingleWordMode) {
// --- SINGLE WORD MODE STATE RESTORATION ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
if (typeof runDomumorText === 'function') {
runDomumorText();
}
} else {
// --- PHRASE MODE STATE RESTORATION ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
// Draws the visual list of history operations
function renderActionLogs() {
const container = document.getElementById('actionLogContainer');
const list = document.getElementById('actionLogsList');
if (!container || !list) return;
if (historyStack.length === 0) {
container.style.display = 'none';
return;
}
container.style.display = 'block';
list.innerHTML = '';
console.log('currentIndex', currentIndex);
historyStack.forEach((log, index) => {
let logDiv = document.createElement('div');
logDiv.className = 'log-item';
// Highlight the current state item
if (index === currentIndex) {
logDiv.classList.add('active-log'); // Make sure to define this in your CSS
}
logDiv.textContent = log.desc;
logDiv.onclick = () => jumpToLog(index);
list.appendChild(logDiv);
});
}
function saveStateForUndo(description = 'Manual Edit') {
let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
let charDefMapCopy = new Map();
for (let [k, v] of charDefCodeMap.entries()) {
charDefMapCopy.set(k, [...v]);
}
historyStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: description
});
currentIndex = historyStack.length - 1;
renderActionLogs(); // Update visual logs
}
```
改進handleUndo(),handleRedo(),jumpToLog(),renderActionLogs(),saveStateForUndo()函式:
當點擊某個log-item時,或者當某個log-item高亮時,應是走到當前log已執行的狀態。
當從中間的某個log-item發生新的操作時,應在historyStack中間截斷,之後再在後面增加新的操作。
需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 14:17
由 ejsoon
gemini 3 flash preview
代码: 全选
為了實現您要求的功能(在中間操作時截斷後續歷史、統一狀態恢復邏輯、並確保當前高亮項代表已執行狀態),我建議將重複的恢復邏輯提取到一個 `applyState` 函式中,並修改 `saveStateForUndo` 的邏輯。
以下是修改後的代碼:
### 1. 新增內部輔助函式 `applyState`
為了避免在大規模修改 `handleUndo`, `handleRedo`, `jumpToLog` 時產生代碼重復,先定義此函式用於統一恢復狀態。
```javascript
// Helper function to apply a state from history
function applyState(targetState) {
if (isSingleWordMode) {
// --- SINGLE WORD MODE STATE RESTORATION ---
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
// Specific call for some external UI updates if exists
if (typeof runDomumorText === 'function') {
runDomumorText();
}
} else {
// --- PHRASE MODE STATE RESTORATION ---
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
}
```
### 2. 修改 `saveStateForUndo`
添加截斷(Truncate)邏輯:如果當前指標不在最後一個,則刪除指標之後的所有記錄。
```javascript
function saveStateForUndo(description = 'Manual Edit') {
// If we are not at the end of the stack, truncate the "future" history
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]);
}
historyStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: description
});
// Update pointer to the latest state
currentIndex = historyStack.length - 1;
renderActionLogs();
}
```
### 3. 修改 `handleUndo`, `handleRedo`, `jumpToLog`
使用 `applyState` 簡化邏輯。
```javascript
function handleUndo() {
if (currentIndex <= 0) return;
let oldState = getSystemState();
currentIndex--; // Move back
let targetState = historyStack[currentIndex];
applyState(targetState); // Apply the restored state
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function handleRedo() {
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
currentIndex++; // Move forward
let targetState = 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 to specific index
let targetState = historyStack[currentIndex];
applyState(targetState);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
```
### 4. 修改 `renderActionLogs`
優化高亮顯示,確保使用者能清晰看到當前處於歷史中的哪一步。
```javascript
function renderActionLogs() {
const container = document.getElementById('actionLogContainer');
const list = document.getElementById('actionLogsList');
if (!container || !list) return;
if (historyStack.length === 0) {
container.style.display = 'none';
return;
}
container.style.display = 'block';
list.innerHTML = '';
historyStack.forEach((log, index) => {
let logDiv = document.createElement('div');
logDiv.className = 'log-item';
// Highlight the state that is currently active
if (index === currentIndex) {
logDiv.classList.add('active-log');
// Ensure the active log is visible in the scrollable list
setTimeout(() => logDiv.scrollIntoView({ block: 'nearest', behavior: 'smooth' }), 0);
}
logDiv.textContent = `${index + 1}. ${log.desc}`;
logDiv.onclick = () => jumpToLog(index);
list.appendChild(logDiv);
});
}
```
### 主要改進說明:
1. **歷史截斷(Truncation)**:在 `saveStateForUndo` 中,使用 `historyStack.slice(0, currentIndex + 1)`。這保證了如果你 Undo 回去並執行新操作,原本中斷之後的「未來」歷史會被刪除,這符合主流軟體(如 Photoshop/VS Code)的撤銷邏輯。
2. **邏輯統一**:將所有的狀態恢復邏輯(`isSingleWordMode` 判斷、Map 的複製、內容處理)全部封裝在 `applyState` 中,減少了代碼冗餘,並解決了原本 `handleUndo` 和 `handleRedo` 中可能遺漏 `runDomumorText()` 的問題。
3. **狀態同步**:現在 `currentIndex` 嚴格對應 `historyStack` 的索引。當點擊 index 為 5 的 Log 時,系統會恢復到第 5 個操作完成時的狀態。
4. **體驗優化**:在 `renderActionLogs` 中添加了 `scrollIntoView`,當 Log 很多時,高亮項會自動滾動到視線內。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 14:29
由 ejsoon
你仍沒有實現「當index為5時的Log時,恢復到第5個操作完成時的狀態」現在是「第5個操作完成前的狀態」。只需告知所要修改的地方。
代码: 全选
為了達成「點擊 Log 即回到該操作**完成後**狀態」以及「從中間操作時**截斷後續歷史**」的需求,我們需要對 `saveStateForUndo` 增加截斷邏輯,並統一 `handleUndo`、`handleRedo` 與 `jumpToLog` 的狀態應用邏輯。
以下是修改建議:
### 1. 統一狀態恢復邏輯 (建議新增一個輔助函式或直接替換)
為了確保所有跳轉行為一致,建議將恢復狀態的邏輯封裝。
```javascript
// Add or use this logic inside Undo/Redo/Jump to ensure consistency
function applyState(targetState) {
if (isSingleWordMode) {
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
if (typeof runDomumorText === 'function') runDomumorText();
} else {
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
}
```
### 2. 修改 `saveStateForUndo` (增加截斷邏輯)
當在歷史中間進行新操作時,必須刪除 `currentIndex` 之後的所有記錄。
```javascript
function saveStateForUndo(description = 'Manual Edit') {
// --- TRUNCATE LOGIC ---
// If we are not at the end of the stack, remove all "future" states
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]);
}
historyStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: description
});
// Update pointer to the newly pushed state
currentIndex = historyStack.length - 1;
renderActionLogs();
}
```
### 3. 修改 `handleUndo`, `handleRedo`, `jumpToLog`
確保指標移動後,直接套用該索引對應的狀態。
```javascript
function handleUndo() {
if (currentIndex <= 0) return;
let oldState = getSystemState();
currentIndex--; // Move pointer back
applyState(historyStack[currentIndex]); // Apply the state at new index
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function handleRedo() {
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
currentIndex++; // Move pointer forward
applyState(historyStack[currentIndex]); // Apply the state at new index
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function jumpToLog(index) {
if (index < 0 || index >= historyStack.length || index === currentIndex) return;
let oldState = getSystemState();
currentIndex = index; // Directly jump to the clicked state
applyState(historyStack[currentIndex]);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
```
### 4. 修改 `renderActionLogs` (優化顯示)
確保當前處於活躍狀態的操作被清楚標記。
```javascript
function renderActionLogs() {
const container = document.getElementById('actionLogContainer');
const list = document.getElementById('actionLogsList');
if (!container || !list) return;
if (historyStack.length === 0) {
container.style.display = 'none';
return;
}
container.style.display = 'block';
list.innerHTML = '';
historyStack.forEach((log, index) => {
let logDiv = document.createElement('div');
logDiv.className = 'log-item' + (index === currentIndex ? ' active-log' : '');
// Display order numbers to make it clear (Optional)
logDiv.textContent = `${index + 1}. ${log.desc}`;
logDiv.onclick = () => jumpToLog(index);
list.appendChild(logDiv);
// Auto-scroll to active log
if (index === currentIndex) {
setTimeout(() => logDiv.scrollIntoView({ block: 'nearest' }), 0);
}
});
}
```
### 修改重點點評:
1. **截斷邏輯 (Truncation)**:在 `saveStateForUndo` 中使用 `slice(0, currentIndex + 1)`。這保證了如果你撤銷(Undo)了 3 次然後做了一個新動作,那原本被撤銷的 3 個動作會從歷史中永久消失,並由新動作接替。
2. **已執行狀態定義**:
* 要實現「第 5 個操作已執行」,`historyStack[5]` 必須存儲該操作完成後的數據快照。
* **請確保在主邏輯中**:先修改變數(如 `charDefCodeMap`),**隨後**才調用 `saveStateForUndo()`。
3. **狀態同步**:`currentIndex` 始終指向當前頁面呈現的數據在 `historyStack` 中的索引位置。點擊 Log `i` 就讀取 `stack[i]`,這解決了「感覺是執行前狀態」的落差感。
4. **初始化建議**:建議在頁面加載或讀取文件完成時,主動調用一次 `saveStateForUndo('Initial State')`,這樣 index 0 就會是原始狀態。
代码: 全选
為了實現「點擊 Log 即進入該操作完成後的狀態」以及「中間操作時截斷後續歷史」的功能,請按照以下修改建議進行替換。主要邏輯變動在於 `saveStateForUndo` 的截斷處理,以及跳轉邏輯的統一。
### 1. 修改 `saveStateForUndo` 函式
在保存新狀態前,先根據當前 `currentIndex` 截斷(Truncate)掉「未來」的歷史記錄。
```javascript
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]);
}
historyStack.push({
lines: currentLines,
filecontent: filecontent,
charDefCodeMap: charDefMapCopy,
desc: description
});
// Always point to the latest state after saving
currentIndex = historyStack.length - 1;
renderActionLogs();
}
```
### 2. 統一提取 `applyState` 輔助函式(建議新增)
為了確保 `Undo`、`Redo` 和 `jumpToLog` 的行為一致(即:跳到該 index 就還原該 index 儲存的完成態),建議提取此邏輯。
```javascript
// Helper to restore state and refresh UI
function applyState(targetState) {
if (isSingleWordMode) {
charDefCodeMap = new Map();
for (let [k, v] of targetState.charDefCodeMap.entries()) {
charDefCodeMap.set(k, [...v]);
}
rebuildSingleWordFileContent();
processContent();
if (typeof runDomumorText === 'function') runDomumorText();
} else {
filecontent = targetState.filecontent;
processContent(JSON.stringify(targetState.lines));
}
}
```
### 3. 修改 `handleUndo`, `handleRedo` 與 `jumpToLog`
使用簡化的指標移動邏輯。注意:`currentIndex` 指向的就是「當前畫面呈現的狀態」。
```javascript
function handleUndo() {
if (currentIndex <= 0) return;
let oldState = getSystemState();
currentIndex--; // Move pointer back
applyState(historyStack[currentIndex]);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
function handleRedo() {
if (currentIndex >= historyStack.length - 1) return;
let oldState = getSystemState();
currentIndex++; // Move pointer forward
applyState(historyStack[currentIndex]);
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
applyState(historyStack[currentIndex]);
detectAndRenderChanges(oldState, getSystemState());
justMovedItem = null;
renderActionLogs();
}
```
### 4. 修改 `renderActionLogs` 中的高亮邏輯
確保索引與顯示內容一致,並在點擊時觸發正確跳轉。
```javascript
// Inside renderActionLogs looping through historyStack
historyStack.forEach((log, index) => {
let logDiv = document.createElement('div');
logDiv.className = 'log-item';
// Highlight exact match of currentIndex
if (index === currentIndex) {
logDiv.classList.add('active-log');
}
// State index + description
logDiv.textContent = `${index + 1}. ${log.desc}`;
logDiv.onclick = () => jumpToLog(index);
list.appendChild(logDiv);
});
```
### 關鍵改進說明:
1. **截斷未來 (Truncation)**:在 `saveStateForUndo` 中使用 `slice(0, currentIndex + 1)`。這確保了如果你撤銷到過去後進行了新操作,原本指標之後的「未來」操作會被刪除,重新開始一段歷史。
2. **狀態與索引對應 (Index Mapping)**:現在 `historyStack[index]` 代表的是執行完該描述 (desc) 動作後的數據快照。
* 當 `currentIndex` 為 5,畫面顯示的就是第 5 個 Log 的結果。
* `jumpToLog(index)` 直接將指標設為該索引並套用數據,保證「點到哪,畫面就到哪」。
3. **初始狀態建議**:為了讓使用者能退回到「最初始狀態」,建議在程式初始化(加載文件完成後)先手動呼叫一次 `saveStateForUndo('Initial State')`。這樣 index 0 就會是原始狀態。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 14:43
由 ejsoon
domchar576.html
改進:
1.當點擊一個log-item時,它跳轉到了這個操作執行之前的狀態。現在要改成跳轉到這個操作執行之後的狀態。
2.在finish的左邊增加一個cancel按鈕,當點擊時,edit區域將像點擊finish一樣關閉,但是所有的改動全都撤回。
需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
代码: 全选
改進:
1.當點擊一個log-item時,它跳轉到了這個操作執行之前的狀態。現在要改成跳轉到這個操作執行之後的狀態。
2.在finish的左邊增加一個cancel按鈕,當點擊時,edit區域將像點擊finish一樣關閉,但是所有的改動全都撤回。
需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 14:48
由 ejsoon
改進:
當輸入一個單字時,如這個單字的編碼為abcd,則程式會列出「a,ab,abc,abcd」所對應的字。這時如果再輸入「abef」,則之前不要清除,而是加上「abe,abef」。只有點擊finish(頁面上已有)後才會清除。
「save to undostack」以及「更新比對」等相關受影響的代碼也要進行更改。
需要給出所有更改的地方和所要替換的代碼,不用給出全部代碼。所有的代碼和注釋都要使用英文。
代码: 全选
改進:
當輸入一個單字時,如這個單字的編碼為abcd,則程式會列出「a,ab,abc,abcd」所對應的字。這時如果再輸入「abef」,則之前不要清除,而是加上「abe,abef」。只有點擊finish(頁面上已有)後才會清除。
「save to undostack」以及「更新比對」等相關受影響的代碼也要進行更改。
需要給出所有更改的地方和所要替換的代碼,不用給出全部代碼。所有的代碼和注釋都要使用英文。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 14:53
由 ejsoon
grmini 3.1 pro
代码: 全选
這兩個需求可以透過完善歷史紀錄機制來達成。
關於第一點,原先的 `historyStack` 只記錄了「執行動作前」的狀態,這導致 `jumpToLog`、`handleUndo` 與 `handleRedo` 在切換狀態時,永遠只能還原到動作前。為了解決這個問題,我們可以在 `saveStateForUndo` 中使用非同步的方式,自動補捉動作執行「之後」的狀態(`afterState`),並修改相關的跳轉邏輯。
關於第二點,加入 Cancel 按鈕只需新增對應的 UI 與一個還原到最初狀態並清空歷史的 JavaScript 函數。
以下是需要修改與替換的程式碼:
### 1. CSS 樣式新增 (Cancel 按鈕顏色)
請在 `<style>` 區塊內,找到 `#btnCloseEdit` 的樣式設定附近,加入 `#btnCancelEdit` 的樣式:
**新增代碼:**
```css
#btnCancelEdit {
background-color: #f44336;
color: white;
}
#btnCancelEdit:hover {
background-color: #da190b;
}
```
---
### 2. HTML 結構修改 (新增 Cancel 按鈕)
請找到編輯區塊底部的按鈕群組,並將 Cancel 按鈕插入在 Finish 左側。
**尋找以下代碼:**
```html
<div class="edit-actions" style="margin-top: 10px">
<button id="btnUndo" class="edit-action-btn" onclick="handleUndo()">Undo</button>
<button id="btnRedo" class="edit-action-btn" onclick="handleRedo()">Redo</button>
<button id="btnCloseEdit" class="edit-action-btn" onclick="handleEditCloseOnly()">Finish</button>
</div>
```
**替換為:**
```html
<div class="edit-actions" style="margin-top: 10px">
<button id="btnUndo" class="edit-action-btn" onclick="handleUndo()">Undo</button>
<button id="btnRedo" class="edit-action-btn" onclick="handleRedo()">Redo</button>
<button id="btnCancelEdit" class="edit-action-btn" onclick="handleCancelEdit()">Cancel</button>
<button id="btnCloseEdit" class="edit-action-btn" onclick="handleEditCloseOnly()">Finish</button>
</div>
```
---
### 3. JavaScript 修改 (核心歷史紀錄機制與跳轉修復)
請在 JavaScript 區段中,尋找原先的 `saveStateForUndo`、`handleUndo`、`handleRedo` 與 `jumpToLog` 函數,將它們**全部替換**為以下優化過邏輯的版本:
**替換代碼:**
```javascript
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 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();
}
```
---
### 4. JavaScript 新增 (Cancel 功能邏輯)
請在上述函數的下方(或與 `handleEditCloseOnly` 相同的位置附近),加入這個全新的 `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 = '';
// Reset tracking variables without updating originalSnapshotState
currentEditCode = '';
currentTargetWord = '';
selectedEditItem = null;
justMovedItem = null;
activeEditKeys.clear();
changedWords.clear();
pendingAction = null;
historyStack = [];
currentIndex = -1;
renderActionLogs();
updateActionButtonsUI();
}
```
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 15:08
由 ejsoon
改進:
當輸入一個單字時,如這個單字的編碼為abcd,則程式會列出「a,ab,abc,abcd」所對應的字。這時如果再輸入「abef」,則之前不要清除,而是加上「abe,abef」。只有點擊finish(頁面上已有)後才會清除。
「save to undostack」以及「更新比對」等相關受影響的代碼也要進行更改。但是不要影響到詞語模式,目前詞語模式的行為是正確的,其實就是要單字模式也做成像詞語模式一樣。
需要給出所有更改的地方和所要替換的代碼,不用給出全部代碼。所有的代碼和注釋都要使用英文。
代码: 全选
為了讓單字模式(Single Word mode)在連續輸入時也能像詞語模式一樣累積顯示(不清除先前的欄位),並且修復因此受影響的「更新比對(diff highlighting)」與復原機制基準,我們只需要修改 `handleEditSubmit` 函數中關於單字模式的初始化邏輯。
原先的代碼會在每次輸入時呼叫 `activeEditKeys.clear()` 清除先前的顯示列,並且無條件覆寫 `originalSnapshotState`(這會破壞「更新比對」的基準,導致高亮失效)。將其修改為只在第一次輸入時獲取狀態即可。
請在 JavaScript 區段中找到 `handleEditSubmit` 函數,並替換以下的代碼區塊:
**尋找以下代碼:**
```javascript
if (isSingleWordMode) {
activeEditKeys.clear(); // Only show sequences for this code
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
originalSnapshotState = getSystemState();
} else {
```
**替換為:**
```javascript
if (isSingleWordMode) {
// Do not clear activeEditKeys so previously opened columns remain visible for continuous inputs
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
// Only capture the baseline state if it hasn't been captured yet in this edit session.
// This preserves the initial baseline for "Cancel" and ensures diff highlighting works correctly.
if (!originalSnapshotState || originalSnapshotState.size === 0) {
originalSnapshotState = getSystemState();
}
} else {
```
透過移除 `activeEditKeys.clear()`,先前的字根欄位將會被保留並與新輸入的字根欄位並存;而透過加上 `!originalSnapshotState || originalSnapshotState.size === 0` 條件,能確保我們比對的基準點永遠是點擊 Finish/Cancel 前的最初狀態,這樣你在多次查詢與移動字元後,「save to undostack」與「更新比對」的結果就都能正確運作了。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 15:13
由 ejsoon
domchar5762.html
單字模式下,undo, redo, 點擊log-item,都不起作用。查找原因。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 15:30
由 ejsoon
gemini thinking
代码: 全选
經過對程式碼架構與邏輯的分析,在**單字模式(`edit` 頁籤)**下,`undo`(復原)、`redo`(重做)以及點擊 `log-item`(歷史變更項目)不起作用,主要是因為**「資料狀態更新」與「單字模式 UI 渲染」之間缺乏聯動機制**。
以下是導致此問題的具體原因分析以及修復建議:
### 核心原因分析
1. **單字模式的 UI 依賴於主動觸發(`handleEditSubmit`)**
從程式碼中可以看到,單字模式的內容是透過 `handleEditSubmit()` 函數,讀取當前 `#codeInput` 的值並對文字進行處理與渲染。
當你執行 `undo`、`redo` 或點擊 `log-item` 時,程式雖然修改了後台的全局碼表資料(例如全局字串或陣列),但**並沒有重新將當前編輯的單字代入並觸發 `handleEditSubmit()**`。因此,單字模式的畫面會停留在操作前的狀態,看起來就像沒有反應。
2. **頁籤切換缺乏單字模式的刷新邏輯**
在 `switchTab(tab)` 函數中,我們可以看到這段程式碼:
```javascript
if (tab === 'stats') {
const activeBtn = document.querySelector('#statsBtnContainer .stats-btn.active') || document.getElementById('btnDupStats');
if (activeBtn) activeBtn.click();
}
```
這裡只有針對切換到 `stats`(統計模式)時,才會自動點擊按鈕去刷新統計數據。而當處於 `edit`(單字模式)時,**沒有任何自動重新解析或整理當前單字的機制**。
3. **`log-item` 的點擊事件未連動單字編輯器**
點擊 `log-item` 通常是為了還原某個歷史版本。雖然全局數據恢復到了該版本的狀態,但因為沒有同步將最新數據餵給 `#codeInput` 的處理邏輯,導致單字模式的工作區依然顯示舊的查詢結果。
---
### 解決方案與修改建議
要解決這個問題,必須在 `undo`、`redo` 函數的末尾,以及 `log-item` 的點擊事件處理函數中,加入**檢測並刷新單字模式**的邏輯。
#### 修改步驟:
1. **建立一個統一的單字模式刷新函數**:
可以在 JavaScript 中加入一個簡單的刷新機制,用來檢查如果當前在單字模式,就重新跑一次更新:
```javascript
function refreshWordEditorIfNeeded() {
// 檢查當前是否處於單字模式 (edit 頁籤是 active 狀態)
const editTab = document.getElementById('tabEdit');
if (editTab && editTab.classList.contains('active')) {
const inputEl = document.getElementById('codeInput');
// 如果輸入框有值,就重新觸發提交解析
if (inputEl && inputEl.value) {
handleEditSubmit();
}
}
}
```
2. **將刷新函數綁定到 `undo`、`redo` 與 `log-item` 點擊事件中**:
* 找到你的 `undo()` 和 `redo()` 函數,在它們成功改變資料並重新渲染完全局數據後的**最後一行**,加上:
```javascript
refreshWordEditorIfNeeded();
```
* 找到生成或點擊 `log-item` 的事件監聽器(Event Listener),在還原歷史狀態的邏輯執行完畢後,同樣呼叫:
```javascript
refreshWordEditorIfNeeded();
```
這樣一來,無論使用者在單字模式下進行復原、重做還是點擊歷史紀錄,系統都會自動用最新的全局資料重新解析當前 `#codeInput` 中的單字,UI 就能即時同步更新了。
Re: 工欲善其事,必先利其器:準備更新哈哈倉頡
发表于 : 2026年 6月 12日 15:49
由 ejsoon
代码: 全选
handleEditSubmit執行時,如果isSingleWordMode 為 true,這時如果再次執行handleEditSubmit
```
function handleEditSubmit() {
let val = document.getElementById('codeInput').value.trim();
if (!val) return;
// 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.clear(); // Only show sequences for this code
for (let i = 1; i <= singleWordActiveCode.length; i++) {
activeEditKeys.add(singleWordActiveCode.substring(0, i));
}
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();
}
```
修改當前函式:
當為單字模式時,如果再輸入一個單字或一個不以z開頭的英文時,將和詞語模式一樣,保留當前的edit-row。