分享和討論倉頡輸入法相關的資訊和内容
版面规则
公正客觀講理,杜絶廢話連篇
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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()相關代碼。

需要準確給出所要修改的地方以及所要替換的代碼,不用給出全部代碼,所有的注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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也是切換高亮。

需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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;
}

```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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;
}
```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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中間截斷,之後再在後面增加新的操作。

需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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中間截斷,之後再在後面增加新的操作。

需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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 很多時,高亮項會自動滾動到視線內。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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 就會是原始狀態。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 ejsoon »

domchar576.html

改進:
1.當點擊一個log-item時,它跳轉到了這個操作執行之前的狀態。現在要改成跳轉到這個操作執行之後的狀態。

2.在finish的左邊增加一個cancel按鈕,當點擊時,edit區域將像點擊finish一樣關閉,但是所有的改動全都撤回。

需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。

代码: 全选

改進:
1.當點擊一個log-item時,它跳轉到了這個操作執行之前的狀態。現在要改成跳轉到這個操作執行之後的狀態。

2.在finish的左邊增加一個cancel按鈕,當點擊時,edit區域將像點擊finish一樣關閉,但是所有的改動全都撤回。

需要明確給出所要修改的地方和替換的代碼,不用給出全部代碼。所有的注釋都要使用英文。
附件
domchar576.html.7z
(14.23 KiB) 已下载 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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」以及「更新比對」等相關受影響的代碼也要進行更改。

需要給出所有更改的地方和所要替換的代碼,不用給出全部代碼。所有的代碼和注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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();
      }

```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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」與「更新比對」的結果就都能正確運作了。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 ejsoon »

domchar5762.html

單字模式下,undo, redo, 點擊log-item,都不起作用。查找原因。
附件
domchar5762.html.7z
(14.56 KiB) 已下载 4 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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 就能即時同步更新了。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

Re: 工欲善其事,必先利其器:準備更新哈哈倉頡

帖子 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。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

正浏览此版面之用户: 没有注册用户 和 1 访客