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

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

帖子 ejsoon »

gemini thinking

代码: 全选

為了解決你的需求,我們需要在函式的一開始**紀錄進入函式前的 `isSingleWordMode` 狀態**(例如使用 `wasSingleWordMode` 變數)。

在原本的程式碼中,只要判定為單字模式,就會無條件執行 `activeEditKeys.clear()`,這會導致之前產生的編輯行(edit-row)全部被清空。為了讓它和詞語模式一樣可以「保留並累加」編輯行,我們只需要改為:**只有在「從詞語模式切換到單字模式」時才清空,如果是「連續輸入單字」則不清空 `activeEditKeys**`。

以下是修改後的完整 `handleEditSubmit` 函式:

```javascript
function handleEditSubmit() {
  let val = document.getElementById('codeInput').value.trim();
  if (!val) return;

  // 【新增】記錄執行前的單字模式狀態,用來判斷是否為「再次執行單字模式」
  let wasSingleWordMode = isSingleWordMode;

  // Validation profile for the input string
  let isEnglishCode = /^[a-zA-Z]+$/.test(val);
  let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
  let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
  let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;

  // Check input constraints if the editor interface is already operational
  if (document.getElementById('editArea').style.display === 'block') {
    if (isSingleWordMode && isPhraseInput) {
      alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
      return;
    }
    if (!isSingleWordMode && isSingleWordInput) {
      alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
      return;
    }
  }

  document.getElementById('codeInput').value = '';
  selectedEditItem = null;
  justMovedItem = null;
  pendingAction = null;
  updateActionButtonsUI();

  // Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
  isSingleWordMode = false;
  singleWordActiveCode = '';
  currentTargetWord = '';

  if (isEnglishCode && val[0].toLowerCase() !== 'z') {
    isSingleWordMode = true;
    singleWordActiveCode = val.toLowerCase().substring(0, 4);
  } else if (!isEnglishCode && val.length === 1) {
    isSingleWordMode = true;
    currentTargetWord = val; // Set the target word to highlight the Chinese char
    // Reverse lookup code from charDefCodeMap
    for (let [code, chars] of charDefCodeMap) {
      if (chars.includes(val)) {
        singleWordActiveCode = code;
        break;
      }
    }
  }

  if (isSingleWordMode) {
    // 【修改】如果原本就已經是單字模式,就不清空 activeEditKeys,以保留當前的 edit-row
    if (!wasSingleWordMode) {
      activeEditKeys.clear(); // 只有從詞語模式切換過來時才清空
    }
    
    for (let i = 1; i <= singleWordActiveCode.length; i++) {
      activeEditKeys.add(singleWordActiveCode.substring(0, i));
    }
    
    // 【修改】比照詞語模式,若已有歷史快照則不重複覆蓋,確保 Undo 機制正常
    if (!originalSnapshotState || originalSnapshotState.size === 0) {
      originalSnapshotState = getSystemState();
    }
  } else {
    // Original Phrase Logic
    if (isEnglishCode) {
      if (val[0].toLowerCase() !== 'z') val = 'z' + val;
      val = val.substring(0, 4).toLowerCase();
      currentEditCode = val;
      currentTargetWord = '';
    } else {
      currentTargetWord = val;
      // Check input pattern profile
      if (/^[a-zA-Z]+$/.test(val)) {
        if (val[0].toLowerCase() !== 'z') val = 'z' + val;
        val = val.substring(0, 4).toLowerCase();
        currentEditCode = val;
        currentTargetWord = '';
      } else {
        currentTargetWord = val;
        let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
        if (!lines.includes(val)) {
          // CRITICAL FIX: Capture baseline state *before* pushing the new word
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
          saveStateForUndo(`Added new word: ${val}`);
          lines.push(val);
          processContent(JSON.stringify(lines));
        }
        let code = 'z';
        let c1 = ccharfirst.get(val[0]) || '';
        if (val.length === 1) {
          code += c1;
        } else if (val.length === 2) {
          let c2 = ccharfirst.get(val[1]) || '';
          let c3 = ccharsecond.get(val[1]) || 'x';
          code += c1 + c2 + c3;
        } else {
          let c2 = ccharfirst.get(val[1]) || '';
          let c3 = ccharfirst.get(val[2]) || '';
          code += c1 + c2 + c3;
        }
        currentEditCode = code.substring(0, 4);
      }
    }
    if (currentEditCode.length >= 1) activeEditKeys.add('z');
    if (currentEditCode.length >= 2) activeEditKeys.add(currentEditCode.substring(0, 2));
    if (currentEditCode.length >= 3) activeEditKeys.add(currentEditCode.substring(0, 3));
    if (currentEditCode.length >= 4) activeEditKeys.add(currentEditCode);
    // Standard initialization fallback block
    if (!originalSnapshotState || originalSnapshotState.size === 0) {
      originalSnapshotState = getSystemState();
    }
  }

  // Keep input area visible allowing repeated inputs
  document.getElementById('inputArea').style.display = 'block';
  document.getElementById('editArea').style.display = 'block';

  // Toggle action buttons visibility based on single word vs phrase mode
  if (isSingleWordMode) {
    document.getElementById('btnMoveEnd').style.display = 'none';
    document.getElementById('btnDelete').style.display = 'none';
  } else {
    document.getElementById('btnMoveEnd').style.display = 'inline-block';
    document.getElementById('btnDelete').style.display = 'inline-block';
  }

  document.getElementById('inputArea').style.display = 'block';
  document.getElementById('editArea').style.display = 'block';
  renderEditArea();
}

```

### 主要修改點說明:

1. **狀態攔截**:在函式頂端加入 `let wasSingleWordMode = isSingleWordMode;`,用來捕捉本次輸入前的模式。
2. **條件式清空**:將 `if (isSingleWordMode)` 區塊內的 `activeEditKeys.clear();` 包裹在 `if (!wasSingleWordMode)` 判斷中。這樣一來,若前一次已經是單字模式,連續輸入新單字時,舊有的編碼按鈕與 row 就不會被洗掉,而是會像詞語模式一樣疊加保留。
3. **快照保護**:同步將 `originalSnapshotState = getSystemState();` 改為安全守衛寫法 `if (!originalSnapshotState ...)`,避免連續輸入時把最初的備份狀態給覆蓋掉了。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

把所有注釋裡的中文全部翻譯成英文,同時,不存在「詞語模式跳到單字模式」的情況,當處於一種模式時,不允許跳到另一種模式。

代码: 全选

Here is the updated `handleEditSubmit` function. All comments have been translated into English, and the logic handles mode persistence correctly.

Since mode switching is strictly disallowed once active, the check `if (!wasSingleWordMode)` will only evaluate to `true` during the very first initialization of the single-word mode session (when `editArea` is not yet open), ensuring a clean slate. On all subsequent sequential inputs within the same mode, it will safely skip clearing the keys, preserving your edit rows just like phrase mode does.

```javascript
function handleEditSubmit() {
  let val = document.getElementById('codeInput').value.trim();
  if (!val) return;

  // Record the single word mode state before execution to check for repeated inputs
  let wasSingleWordMode = isSingleWordMode;

  // Validation profile for the input string
  let isEnglishCode = /^[a-zA-Z]+$/.test(val);
  let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
  let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
  let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;

  // Check input constraints if the editor interface is already operational
  if (document.getElementById('editArea').style.display === 'block') {
    if (isSingleWordMode && isPhraseInput) {
      alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
      return;
    }
    if (!isSingleWordMode && isSingleWordInput) {
      alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
      return;
    }
  }

  document.getElementById('codeInput').value = '';
  selectedEditItem = null;
  justMovedItem = null;
  pendingAction = null;
  updateActionButtonsUI();

  // Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
  isSingleWordMode = false;
  singleWordActiveCode = '';
  currentTargetWord = '';

  if (isEnglishCode && val[0].toLowerCase() !== 'z') {
    isSingleWordMode = true;
    singleWordActiveCode = val.toLowerCase().substring(0, 4);
  } else if (!isEnglishCode && val.length === 1) {
    isSingleWordMode = true;
    currentTargetWord = val; // Set the target word to highlight the Chinese char
    // Reverse lookup code from charDefCodeMap
    for (let [code, chars] of charDefCodeMap) {
      if (chars.includes(val)) {
        singleWordActiveCode = code;
        break;
      }
    }
  }

  if (isSingleWordMode) {
    // If already in single word mode, do not clear activeEditKeys to retain current edit rows
    if (!wasSingleWordMode) {
      activeEditKeys.clear(); // Only clear during initial session setup
    }
    
    for (let i = 1; i <= singleWordActiveCode.length; i++) {
      activeEditKeys.add(singleWordActiveCode.substring(0, i));
    }
    
    // Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
    if (!originalSnapshotState || originalSnapshotState.size === 0) {
      originalSnapshotState = getSystemState();
    }
  } else {
    // Original Phrase Logic
    if (isEnglishCode) {
      if (val[0].toLowerCase() !== 'z') val = 'z' + val;
      val = val.substring(0, 4).toLowerCase();
      currentEditCode = val;
      currentTargetWord = '';
    } else {
      currentTargetWord = val;
      // Check input pattern profile
      if (/^[a-zA-Z]+$/.test(val)) {
        if (val[0].toLowerCase() !== 'z') val = 'z' + val;
        val = val.substring(0, 4).toLowerCase();
        currentEditCode = val;
        currentTargetWord = '';
      } else {
        currentTargetWord = val;
        let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
        if (!lines.includes(val)) {
          // CRITICAL FIX: Capture baseline state *before* pushing the new word
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
          saveStateForUndo(`Added new word: ${val}`);
          lines.push(val);
          processContent(JSON.stringify(lines));
        }
        let code = 'z';
        let c1 = ccharfirst.get(val[0]) || '';
        if (val.length === 1) {
          code += c1;
        } else if (val.length === 2) {
          let c2 = ccharfirst.get(val[1]) || '';
          let c3 = ccharsecond.get(val[1]) || 'x';
          code += c1 + c2 + c3;
        } else {
          let c2 = ccharfirst.get(val[1]) || '';
          let c3 = ccharfirst.get(val[2]) || '';
          code += c1 + c2 + c3;
        }
        currentEditCode = code.substring(0, 4);
      }
    }
    if (currentEditCode.length >= 1) activeEditKeys.add('z');
    if (currentEditCode.length >= 2) activeEditKeys.add(currentEditCode.substring(0, 2));
    if (currentEditCode.length >= 3) activeEditKeys.add(currentEditCode.substring(0, 3));
    if (currentEditCode.length >= 4) activeEditKeys.add(currentEditCode);
    // Standard initialization fallback block
    if (!originalSnapshotState || originalSnapshotState.size === 0) {
      originalSnapshotState = getSystemState();
    }
  }

  // Keep input area visible allowing repeated inputs
  document.getElementById('inputArea').style.display = 'block';
  document.getElementById('editArea').style.display = 'block';

  // Toggle action buttons visibility based on single word vs phrase mode
  if (isSingleWordMode) {
    document.getElementById('btnMoveEnd').style.display = 'none';
    document.getElementById('btnDelete').style.display = 'none';
  } else {
    document.getElementById('btnMoveEnd').style.display = 'inline-block';
    document.getElementById('btnDelete').style.display = 'inline-block';
  }

  document.getElementById('inputArea').style.display = 'block';
  document.getElementById('editArea').style.display = 'block';
  renderEditArea();
}

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

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

帖子 ejsoon »

domchar5763.html

在單字模式下,undo,redo,點擊log-item,都無效。查找原因並修改代碼。

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

代码: 全选

```
      function handleEditSubmit() {
        let val = document.getElementById('codeInput').value.trim();
        if (!val) return;

        // Record the single word mode state before execution to check for repeated inputs
        let wasSingleWordMode = isSingleWordMode;

        // Validation profile for the input string
        let isEnglishCode = /^[a-zA-Z]+$/.test(val);
        let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
        let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
        let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;

        // Check input constraints if the editor interface is already operational
        if (document.getElementById('editArea').style.display === 'block') {
          if (isSingleWordMode && isPhraseInput) {
            alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
            return;
          }
          if (!isSingleWordMode && isSingleWordInput) {
            alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
            return;
          }
        }

        document.getElementById('codeInput').value = '';
        selectedEditItem = null;
        justMovedItem = null;
        pendingAction = null;
        updateActionButtonsUI();

        // Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
        isSingleWordMode = false;
        singleWordActiveCode = '';
        currentTargetWord = '';

        if (isEnglishCode && val[0].toLowerCase() !== 'z') {
          isSingleWordMode = true;
          singleWordActiveCode = val.toLowerCase().substring(0, 4);
        } else if (!isEnglishCode && val.length === 1) {
          isSingleWordMode = true;
          currentTargetWord = val; // Set the target word to highlight the Chinese char
          // Reverse lookup code from charDefCodeMap
          for (let [code, chars] of charDefCodeMap) {
            if (chars.includes(val)) {
              singleWordActiveCode = code;
              break;
            }
          }
        }

        if (isSingleWordMode) {
          // If already in single word mode, do not clear activeEditKeys to retain current edit rows
          if (!wasSingleWordMode) {
            activeEditKeys.clear(); // Only clear during initial session setup
          }

          for (let i = 1; i <= singleWordActiveCode.length; i++) {
            activeEditKeys.add(singleWordActiveCode.substring(0, i));
          }

          // Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
        } else {
          // Original Phrase Logic
          if (isEnglishCode) {
            if (val[0].toLowerCase() !== 'z') val = 'z' + val;
            val = val.substring(0, 4).toLowerCase();
            currentEditCode = val;
            currentTargetWord = '';
          } else {
            currentTargetWord = val;
            // Check input pattern profile
            if (/^[a-zA-Z]+$/.test(val)) {
              if (val[0].toLowerCase() !== 'z') val = 'z' + val;
              val = val.substring(0, 4).toLowerCase();
              currentEditCode = val;
              currentTargetWord = '';
            } else {
              currentTargetWord = val;
              let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
              if (!lines.includes(val)) {
                // CRITICAL FIX: Capture baseline state *before* pushing the new word
                if (!originalSnapshotState || originalSnapshotState.size === 0) {
                  originalSnapshotState = getSystemState();
                }
                saveStateForUndo(`Added new word: ${val}`);
                lines.push(val);
                processContent(JSON.stringify(lines));
              }
              let code = 'z';
              let c1 = ccharfirst.get(val[0]) || '';
              if (val.length === 1) {
                code += c1;
              } else if (val.length === 2) {
                let c2 = ccharfirst.get(val[1]) || '';
                let c3 = ccharsecond.get(val[1]) || 'x';
                code += c1 + c2 + c3;
              } else {
                let c2 = ccharfirst.get(val[1]) || '';
                let c3 = ccharfirst.get(val[2]) || '';
                code += c1 + c2 + c3;
              }
              currentEditCode = code.substring(0, 4);
            }
          }
          if (currentEditCode.length >= 1) activeEditKeys.add('z');
          if (currentEditCode.length >= 2) activeEditKeys.add(currentEditCode.substring(0, 2));
          if (currentEditCode.length >= 3) activeEditKeys.add(currentEditCode.substring(0, 3));
          if (currentEditCode.length >= 4) activeEditKeys.add(currentEditCode);
          // Standard initialization fallback block
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
        }

        // Keep input area visible allowing repeated inputs
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'block';

        // Toggle action buttons visibility based on single word vs phrase mode
        if (isSingleWordMode) {
          document.getElementById('btnMoveEnd').style.display = 'none';
          document.getElementById('btnDelete').style.display = 'none';
        } else {
          document.getElementById('btnMoveEnd').style.display = 'inline-block';
          document.getElementById('btnDelete').style.display = 'inline-block';
        }

        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'block';
        renderEditArea();
      }

      // Helper to restore state and refresh UI
      function applyState(targetState) {
        if (isSingleWordMode) {
          for (let i = 1; i <= singleWordActiveCode.length; i++) {
            activeEditKeys.add(singleWordActiveCode.substring(0, i));
          }

          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
        } else {
          filecontent = targetState.filecontent;
          processContent(JSON.stringify(targetState.lines));
        }
      }

      function handleUndo() {
        if (currentIndex < 0) return; // Allow undoing down to the initial state

        let oldState = getSystemState();
        currentIndex--; // Move pointer back

        let targetState;
        if (currentIndex === -1) {
          // Apply the BEFORE state of the first action (Initial State)
          targetState = historyStack[0];
        } else {
          // Apply the AFTER state of the action we stepped back to
          targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
        }

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function handleRedo() {
        if (currentIndex >= historyStack.length - 1) return;

        let oldState = getSystemState();
        currentIndex++; // Move pointer forward

        // Apply the AFTER state of the redone action
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function jumpToLog(index) {
        if (index < 0 || index >= historyStack.length || index === currentIndex) return;

        let oldState = getSystemState();
        currentIndex = index; // Jump directly to the selected state's index

        // Apply the AFTER state of the selected action log
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function saveStateForUndo(description = 'Manual Edit') {
        // Truncate the history stack if the pointer is not at the end
        if (currentIndex < historyStack.length - 1) {
          historyStack = historyStack.slice(0, currentIndex + 1);
        }

        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        let charDefMapCopy = new Map();
        for (let [k, v] of charDefCodeMap.entries()) {
          charDefMapCopy.set(k, [...v]);
        }

        // Push the 'before' state
        historyStack.push({
          lines: currentLines,
          filecontent: filecontent,
          charDefCodeMap: charDefMapCopy,
          desc: description,
          afterState: null // Will be populated after the current action completes
        });

        // Always point to the latest state after saving
        currentIndex = historyStack.length - 1;
        renderActionLogs();

        // Asynchronously capture the state AFTER the synchronous action finishes
        let capturedIndex = currentIndex;
        setTimeout(() => {
          if (historyStack[capturedIndex]) {
            let afterLines = mcc0string.split('\n').filter(l => l.trim() !== '');
            let afterCharDefMapCopy = new Map();
            for (let [k, v] of charDefCodeMap.entries()) {
              afterCharDefMapCopy.set(k, [...v]);
            }
            historyStack[capturedIndex].afterState = {
              lines: afterLines,
              filecontent: filecontent,
              charDefCodeMap: afterCharDefMapCopy
            };
          }
        }, 0);
      }
```

在單字模式下,undo,redo,點擊log-item,都無效。查找原因並修改代碼。

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

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

帖子 ejsoon »

gemini thinking

代码: 全选

### 原因分析

在單字模式(`isSingleWordMode === true`)下,`undo`、`redo` 和點擊 `log-item` 失效的核心原因在於 **`applyState` 函數的邏輯缺失**:

1. **未復原數據狀態**:當 `isSingleWordMode` 為 true 時,`applyState` 只更新了 `activeEditKeys`,卻完全跳過了實際數據(`filecontent` 與記錄單字編碼映射的 `charDefCodeMap`)的復原。
2. **未刷新 UI 渲染**:單字模式下的修改直接作用於字元映射,狀態回溯後必須重新呼叫 `renderEditArea()`,否則畫面上的編輯列表不會同步更新。

---

### 修改方案

請將原本的 `applyState` 函數替換為以下代碼。此修改確保了在單字模式下能正確還原 `filecontent`、深拷貝(Deep Copy)還原 `charDefCodeMap`,並即時刷新編輯區界面。

#### 尋找並替換 `applyState` 函數:

```javascript
      // Helper to restore state and refresh UI
      function applyState(targetState) {
        if (isSingleWordMode) {
          // Restore core data state for single word mode
          filecontent = targetState.filecontent;
          if (targetState.charDefCodeMap) {
            charDefCodeMap = new Map();
            for (let [k, v] of targetState.charDefCodeMap.entries()) {
              charDefCodeMap.set(k, [...v]);
            }
          }

          // Re-populate active edit keys configuration
          for (let i = 1; i <= singleWordActiveCode.length; i++) {
            activeEditKeys.add(singleWordActiveCode.substring(0, i));
          }

          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }

          // Crucial: Refresh the edit workspace UI to reflect the restored state
          renderEditArea();
        } else {
          // Original Phrase Logic
          filecontent = targetState.filecontent;
          processContent(JSON.stringify(targetState.lines));
        }
      }

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

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

帖子 ejsoon »

domchar5764.html

我認為問題已解決,就是花了太多時間。
附件
domchar5764.html.7z
(14.76 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

當前的代碼,如果一開始輸入的是一個新詞,則log中會出現一個item,點擊undo時會回到沒加新詞的狀態。如果一開始輸入的是一個已有的詞,則log中不會出現item。

現在要改成,如果一開始輸入的是一個新詞,則log中會出現一個item,點擊undo時不會回到沒加新詞的狀態,也就是說historyStack至少存在一項,currentIndex最小只能是0而不能是-1。如果一開始輸入的是一個已有的詞,則log中會出現第一個item,它的內容是「begin with searching ...」。點擊undo時回到第一個item就不能再回退。注意只限於一開始時輸入已有的詞會在log中出現,即在editArea不存在時。

代码: 全选

```
      function handleEditSubmit() {
        let val = document.getElementById('codeInput').value.trim();
        if (!val) return;

        // Record the single word mode state before execution to check for repeated inputs
        let wasSingleWordMode = isSingleWordMode;

        // Validation profile for the input string
        let isEnglishCode = /^[a-zA-Z]+$/.test(val);
        let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
        let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
        let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;

        // Check input constraints if the editor interface is already operational
        if (document.getElementById('editArea').style.display === 'block') {
          if (isSingleWordMode && isPhraseInput) {
            alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
            return;
          }
          if (!isSingleWordMode && isSingleWordInput) {
            alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
            return;
          }
        }

        document.getElementById('codeInput').value = '';
        selectedEditItem = null;
        justMovedItem = null;
        pendingAction = null;
        updateActionButtonsUI();

        // Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
        isSingleWordMode = false;
        singleWordActiveCode = '';
        currentTargetWord = '';

        if (isEnglishCode && val[0].toLowerCase() !== 'z') {
          isSingleWordMode = true;
          singleWordActiveCode = val.toLowerCase().substring(0, 4);
        } else if (!isEnglishCode && val.length === 1) {
          isSingleWordMode = true;
          currentTargetWord = val; // Set the target word to highlight the Chinese char
          // Reverse lookup code from charDefCodeMap
          for (let [code, chars] of charDefCodeMap) {
            if (chars.includes(val)) {
              singleWordActiveCode = code;
              break;
            }
          }
        }

        if (isSingleWordMode) {
          // If already in single word mode, do not clear activeEditKeys to retain current edit rows
          if (!wasSingleWordMode) {
            activeEditKeys.clear(); // Only clear during initial session setup
          }

          for (let i = 1; i <= singleWordActiveCode.length; i++) {
            activeEditKeys.add(singleWordActiveCode.substring(0, i));
          }

          // Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
        } else {
          // Original Phrase Logic
          if (isEnglishCode) {
            if (val[0].toLowerCase() !== 'z') val = 'z' + val;
            val = val.substring(0, 4).toLowerCase();
            currentEditCode = val;
            currentTargetWord = '';
          } else {
            currentTargetWord = val;
            // Check input pattern profile
            if (/^[a-zA-Z]+$/.test(val)) {
              if (val[0].toLowerCase() !== 'z') val = 'z' + val;
              val = val.substring(0, 4).toLowerCase();
              currentEditCode = val;
              currentTargetWord = '';
            } else {
              currentTargetWord = val;
              let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
              if (!lines.includes(val)) {
                // CRITICAL FIX: Capture baseline state *before* pushing the new word
                if (!originalSnapshotState || originalSnapshotState.size === 0) {
                  originalSnapshotState = getSystemState();
                }
                saveStateForUndo(`Added new word: ${val}`);
                lines.push(val);
                processContent(JSON.stringify(lines));
              }
              let code = 'z';
              let c1 = ccharfirst.get(val[0]) || '';
              if (val.length === 1) {
                code += c1;
              } else if (val.length === 2) {
                let c2 = ccharfirst.get(val[1]) || '';
                let c3 = ccharsecond.get(val[1]) || 'x';
                code += c1 + c2 + c3;
              } else {
                let c2 = ccharfirst.get(val[1]) || '';
                let c3 = ccharfirst.get(val[2]) || '';
                code += c1 + c2 + c3;
              }
              currentEditCode = code.substring(0, 4);
            }
          }
          if (currentEditCode.length >= 1) activeEditKeys.add('z');
          if (currentEditCode.length >= 2) activeEditKeys.add(currentEditCode.substring(0, 2));
          if (currentEditCode.length >= 3) activeEditKeys.add(currentEditCode.substring(0, 3));
          if (currentEditCode.length >= 4) activeEditKeys.add(currentEditCode);
          // Standard initialization fallback block
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
        }

        // Keep input area visible allowing repeated inputs
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'block';

        // Toggle action buttons visibility based on single word vs phrase mode
        if (isSingleWordMode) {
          document.getElementById('btnMoveEnd').style.display = 'none';
          document.getElementById('btnDelete').style.display = 'none';
        } else {
          document.getElementById('btnMoveEnd').style.display = 'inline-block';
          document.getElementById('btnDelete').style.display = 'inline-block';
        }

        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'block';
        renderEditArea();
      }

      // Helper to restore state and refresh UI
      function applyState(targetState) {
        if (isSingleWordMode) {
          // Restore core data state for single word mode
          filecontent = targetState.filecontent;
          if (targetState.charDefCodeMap) {
            charDefCodeMap = new Map();
            for (let [k, v] of targetState.charDefCodeMap.entries()) {
              charDefCodeMap.set(k, [...v]);
            }
          }

          // Re-populate active edit keys configuration
          for (let i = 1; i <= singleWordActiveCode.length; i++) {
            activeEditKeys.add(singleWordActiveCode.substring(0, i));
          }

          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }

          // Crucial: Refresh the edit workspace UI to reflect the restored state
          renderEditArea();
        } else {
          // Original Phrase Logic
          filecontent = targetState.filecontent;
          processContent(JSON.stringify(targetState.lines));
        }
      }

      function handleUndo() {
        if (currentIndex < 0) return; // Allow undoing down to the initial state

        let oldState = getSystemState();
        currentIndex--; // Move pointer back

        let targetState;
        if (currentIndex === -1) {
          // Apply the BEFORE state of the first action (Initial State)
          targetState = historyStack[0];
        } else {
          // Apply the AFTER state of the action we stepped back to
          targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];
        }

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function handleRedo() {
        if (currentIndex >= historyStack.length - 1) return;

        let oldState = getSystemState();
        currentIndex++; // Move pointer forward

        // Apply the AFTER state of the redone action
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function jumpToLog(index) {
        if (index < 0 || index >= historyStack.length || index === currentIndex) return;

        let oldState = getSystemState();
        currentIndex = index; // Jump directly to the selected state's index

        // Apply the AFTER state of the selected action log
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function saveStateForUndo(description = 'Manual Edit') {
        // Truncate the history stack if the pointer is not at the end
        if (currentIndex < historyStack.length - 1) {
          historyStack = historyStack.slice(0, currentIndex + 1);
        }

        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        let charDefMapCopy = new Map();
        for (let [k, v] of charDefCodeMap.entries()) {
          charDefMapCopy.set(k, [...v]);
        }

        // Push the 'before' state
        historyStack.push({
          lines: currentLines,
          filecontent: filecontent,
          charDefCodeMap: charDefMapCopy,
          desc: description,
          afterState: null // Will be populated after the current action completes
        });

        // Always point to the latest state after saving
        currentIndex = historyStack.length - 1;
        renderActionLogs();

        // Asynchronously capture the state AFTER the synchronous action finishes
        let capturedIndex = currentIndex;
        setTimeout(() => {
          if (historyStack[capturedIndex]) {
            let afterLines = mcc0string.split('\n').filter(l => l.trim() !== '');
            let afterCharDefMapCopy = new Map();
            for (let [k, v] of charDefCodeMap.entries()) {
              afterCharDefMapCopy.set(k, [...v]);
            }
            historyStack[capturedIndex].afterState = {
              lines: afterLines,
              filecontent: filecontent,
              charDefCodeMap: afterCharDefMapCopy
            };
          }
        }, 0);
      }
```

當前的代碼,如果一開始輸入的是一個新詞,則log中會出現一個item,點擊undo時會回到沒加新詞的狀態。如果一開始輸入的是一個已有的詞,則log中不會出現item。

現在要改成,如果一開始輸入的是一個新詞,則log中會出現一個item,點擊undo時不會回到沒加新詞的狀態,也就是說historyStack至少存在一項,currentIndex最小只能是0而不能是-1。如果一開始輸入的是一個已有的詞,則log中會出現第一個item,它的內容是「begin with searching ...」。點擊undo時回到第一個item就不能再回退。注意只限於一開始時輸入已有的詞會在log中出現,即在editArea不存在時。

所有的注釋都要使用英文。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

gemini 3.1pro

代码: 全选

Here is the modified code to meet your requirements.

I have adjusted the logic so that any initial input (when `editArea` is not yet visible) will register as the foundational action in the history log. If it's a new word, it logs as "Added new word: ..."; if it's an existing word, it logs as "begin with searching ...". I also updated the `handleUndo` function to establish `0` as the absolute minimum `currentIndex`, ensuring the user cannot back out of the very first state (the initial new word or the initial search). All comments are provided in English.

```javascript
      function handleEditSubmit() {
        let val = document.getElementById('codeInput').value.trim();
        if (!val) return;

        // Check if the editor interface is hidden, indicating this is the initial input
        let isInitialInput = document.getElementById('editArea').style.display !== 'block';

        // Record the single word mode state before execution to check for repeated inputs
        let wasSingleWordMode = isSingleWordMode;

        // Validation profile for the input string
        let isEnglishCode = /^[a-zA-Z]+$/.test(val);
        let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
        let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
        let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;

        // Check input constraints if the editor interface is already operational
        if (!isInitialInput) {
          if (isSingleWordMode && isPhraseInput) {
            alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
            return;
          }
          if (!isSingleWordMode && isSingleWordInput) {
            alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
            return;
          }
        }

        // Determine if a new word is about to be added to the dictionary
        let willAddNewWord = false;
        if (!isEnglishCode && val.length > 1) {
          let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
          if (!lines.includes(val)) {
            willAddNewWord = true;
          }
        }

        // If this is the initial input and it's an existing word/code, log the starting action
        if (isInitialInput && !willAddNewWord) {
          saveStateForUndo(`begin with searching ${val}`);
        }

        document.getElementById('codeInput').value = '';
        selectedEditItem = null;
        justMovedItem = null;
        pendingAction = null;
        updateActionButtonsUI();

        // Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
        isSingleWordMode = false;
        singleWordActiveCode = '';
        currentTargetWord = '';

        if (isEnglishCode && val[0].toLowerCase() !== 'z') {
          isSingleWordMode = true;
          singleWordActiveCode = val.toLowerCase().substring(0, 4);
        } else if (!isEnglishCode && val.length === 1) {
          isSingleWordMode = true;
          currentTargetWord = val; // Set the target word to highlight the Chinese char
          // Reverse lookup code from charDefCodeMap
          for (let [code, chars] of charDefCodeMap) {
            if (chars.includes(val)) {
              singleWordActiveCode = code;
              break;
            }
          }
        }

        if (isSingleWordMode) {
          // If already in single word mode, do not clear activeEditKeys to retain current edit rows
          if (!wasSingleWordMode) {
            activeEditKeys.clear(); // Only clear during initial session setup
          }

          for (let i = 1; i <= singleWordActiveCode.length; i++) {
            activeEditKeys.add(singleWordActiveCode.substring(0, i));
          }

          // Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
        } else {
          // Original Phrase Logic
          if (isEnglishCode) {
            if (val[0].toLowerCase() !== 'z') val = 'z' + val;
            val = val.substring(0, 4).toLowerCase();
            currentEditCode = val;
            currentTargetWord = '';
          } else {
            currentTargetWord = val;
            // Check input pattern profile
            if (/^[a-zA-Z]+$/.test(val)) {
              if (val[0].toLowerCase() !== 'z') val = 'z' + val;
              val = val.substring(0, 4).toLowerCase();
              currentEditCode = val;
              currentTargetWord = '';
            } else {
              currentTargetWord = val;
              let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
              if (!lines.includes(val)) {
                // CRITICAL FIX: Capture baseline state *before* pushing the new word
                if (!originalSnapshotState || originalSnapshotState.size === 0) {
                  originalSnapshotState = getSystemState();
                }
                saveStateForUndo(`Added new word: ${val}`);
                lines.push(val);
                processContent(JSON.stringify(lines));
              }
              let code = 'z';
              let c1 = ccharfirst.get(val[0]) || '';
              if (val.length === 1) {
                code += c1;
              } else if (val.length === 2) {
                let c2 = ccharfirst.get(val[1]) || '';
                let c3 = ccharsecond.get(val[1]) || 'x';
                code += c1 + c2 + c3;
              } else {
                let c2 = ccharfirst.get(val[1]) || '';
                let c3 = ccharfirst.get(val[2]) || '';
                code += c1 + c2 + c3;
              }
              currentEditCode = code.substring(0, 4);
            }
          }
          if (currentEditCode.length >= 1) activeEditKeys.add('z');
          if (currentEditCode.length >= 2) activeEditKeys.add(currentEditCode.substring(0, 2));
          if (currentEditCode.length >= 3) activeEditKeys.add(currentEditCode.substring(0, 3));
          if (currentEditCode.length >= 4) activeEditKeys.add(currentEditCode);
          // Standard initialization fallback block
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
        }

        // Keep input area visible allowing repeated inputs
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'block';

        // Toggle action buttons visibility based on single word vs phrase mode
        if (isSingleWordMode) {
          document.getElementById('btnMoveEnd').style.display = 'none';
          document.getElementById('btnDelete').style.display = 'none';
        } else {
          document.getElementById('btnMoveEnd').style.display = 'inline-block';
          document.getElementById('btnDelete').style.display = 'inline-block';
        }

        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'block';
        renderEditArea();
      }

      // Helper to restore state and refresh UI
      function applyState(targetState) {
        if (isSingleWordMode) {
          // Restore core data state for single word mode
          filecontent = targetState.filecontent;
          if (targetState.charDefCodeMap) {
            charDefCodeMap = new Map();
            for (let [k, v] of targetState.charDefCodeMap.entries()) {
              charDefCodeMap.set(k, [...v]);
            }
          }

          // Re-populate active edit keys configuration
          for (let i = 1; i <= singleWordActiveCode.length; i++) {
            activeEditKeys.add(singleWordActiveCode.substring(0, i));
          }

          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }

          // Crucial: Refresh the edit workspace UI to reflect the restored state
          renderEditArea();
        } else {
          // Original Phrase Logic
          filecontent = targetState.filecontent;
          processContent(JSON.stringify(targetState.lines));
        }
      }

      function handleUndo() {
        // Prevent undoing past the initial logged action (index 0).
        // The user can no longer revert to the unlogged state (-1).
        if (currentIndex <= 0) return;

        let oldState = getSystemState();
        currentIndex--; // Move pointer back

        // Apply the AFTER state of the action we stepped back to
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function handleRedo() {
        if (currentIndex >= historyStack.length - 1) return;

        let oldState = getSystemState();
        currentIndex++; // Move pointer forward

        // Apply the AFTER state of the redone action
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function jumpToLog(index) {
        if (index < 0 || index >= historyStack.length || index === currentIndex) return;

        let oldState = getSystemState();
        currentIndex = index; // Jump directly to the selected state's index

        // Apply the AFTER state of the selected action log
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function saveStateForUndo(description = 'Manual Edit') {
        // Truncate the history stack if the pointer is not at the end
        if (currentIndex < historyStack.length - 1) {
          historyStack = historyStack.slice(0, currentIndex + 1);
        }

        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        let charDefMapCopy = new Map();
        for (let [k, v] of charDefCodeMap.entries()) {
          charDefMapCopy.set(k, [...v]);
        }

        // Push the 'before' state
        historyStack.push({
          lines: currentLines,
          filecontent: filecontent,
          charDefCodeMap: charDefMapCopy,
          desc: description,
          afterState: null // Will be populated after the current action completes
        });

        // Always point to the latest state after saving
        currentIndex = historyStack.length - 1;
        renderActionLogs();

        // Asynchronously capture the state AFTER the synchronous action finishes
        let capturedIndex = currentIndex;
        setTimeout(() => {
          if (historyStack[capturedIndex]) {
            let afterLines = mcc0string.split('\n').filter(l => l.trim() !== '');
            let afterCharDefMapCopy = new Map();
            for (let [k, v] of charDefCodeMap.entries()) {
              afterCharDefMapCopy.set(k, [...v]);
            }
            historyStack[capturedIndex].afterState = {
              lines: afterLines,
              filecontent: filecontent,
              charDefCodeMap: afterCharDefMapCopy
            };
          }
        }, 0);
      }

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

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

帖子 ejsoon »

當我在單字模式中作了改動,點擊cancel或finish之後,如果再次進入詞語模式,對比欄全部變為空,這是不對的,程式應在上一次cancal或finish後,清空對比欄的數據,不能影響到下一次的進程。

代码: 全选

```javascript
      function handleEditSubmit() {
        let val = document.getElementById('codeInput').value.trim();
        if (!val) return;

        // Check if the editor interface is hidden, indicating this is the initial input
        let isInitialInput = document.getElementById('editArea').style.display !== 'block';

        // Record the single word mode state before execution to check for repeated inputs
        let wasSingleWordMode = isSingleWordMode;

        // Validation profile for the input string
        let isEnglishCode = /^[a-zA-Z]+$/.test(val);
        let isZCode = isEnglishCode && val[0].toLowerCase() === 'z';
        let isSingleWordInput = (!isEnglishCode && val.length === 1) || (isEnglishCode && !isZCode);
        let isPhraseInput = (!isEnglishCode && val.length > 1) || isZCode;

        // Check input constraints if the editor interface is already operational
        if (!isInitialInput) {
          if (isSingleWordMode && isPhraseInput) {
            alert('Constraint Error: Single Word mode active. Input cannot contain phrases or z-starting codes.');
            return;
          }
          if (!isSingleWordMode && isSingleWordInput) {
            alert('Constraint Error: Phrase mode active. Input cannot contain single words or non-z codes.');
            return;
          }
        }

        // Determine if a new word is about to be added to the dictionary
        let willAddNewWord = false;
        if (!isEnglishCode && val.length > 1) {
          let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
          if (!lines.includes(val)) {
            willAddNewWord = true;
          }
        }

        // If this is the initial input and it's an existing word/code, log the starting action
        if (isInitialInput && !willAddNewWord) {
          saveStateForUndo(`begin with searching ${val}`);
        }

        document.getElementById('codeInput').value = '';
        selectedEditItem = null;
        justMovedItem = null;
        pendingAction = null;
        updateActionButtonsUI();

        // Detect Single Word Mode (English without 'z' prefix, or a single Chinese char)
        isSingleWordMode = false;
        singleWordActiveCode = '';
        currentTargetWord = '';

        if (isEnglishCode && val[0].toLowerCase() !== 'z') {
          isSingleWordMode = true;
          singleWordActiveCode = val.toLowerCase().substring(0, 4);
        } else if (!isEnglishCode && val.length === 1) {
          isSingleWordMode = true;
          currentTargetWord = val; // Set the target word to highlight the Chinese char
          // Reverse lookup code from charDefCodeMap
          for (let [code, chars] of charDefCodeMap) {
            if (chars.includes(val)) {
              singleWordActiveCode = code;
              break;
            }
          }
        }

        if (isSingleWordMode) {
          // If already in single word mode, do not clear activeEditKeys to retain current edit rows
          if (!wasSingleWordMode) {
            activeEditKeys.clear(); // Only clear during initial session setup
          }

          for (let i = 1; i <= singleWordActiveCode.length; i++) {
            activeEditKeys.add(singleWordActiveCode.substring(0, i));
          }

          // Do not overwrite if an original snapshot state already exists, ensuring Undo mechanism works correctly
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
        } else {
          // Original Phrase Logic
          if (isEnglishCode) {
            if (val[0].toLowerCase() !== 'z') val = 'z' + val;
            val = val.substring(0, 4).toLowerCase();
            currentEditCode = val;
            currentTargetWord = '';
          } else {
            currentTargetWord = val;
            // Check input pattern profile
            if (/^[a-zA-Z]+$/.test(val)) {
              if (val[0].toLowerCase() !== 'z') val = 'z' + val;
              val = val.substring(0, 4).toLowerCase();
              currentEditCode = val;
              currentTargetWord = '';
            } else {
              currentTargetWord = val;
              let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
              if (!lines.includes(val)) {
                // CRITICAL FIX: Capture baseline state *before* pushing the new word
                if (!originalSnapshotState || originalSnapshotState.size === 0) {
                  originalSnapshotState = getSystemState();
                }
                saveStateForUndo(`Added new word: ${val}`);
                lines.push(val);
                processContent(JSON.stringify(lines));
              }
              let code = 'z';
              let c1 = ccharfirst.get(val[0]) || '';
              if (val.length === 1) {
                code += c1;
              } else if (val.length === 2) {
                let c2 = ccharfirst.get(val[1]) || '';
                let c3 = ccharsecond.get(val[1]) || 'x';
                code += c1 + c2 + c3;
              } else {
                let c2 = ccharfirst.get(val[1]) || '';
                let c3 = ccharfirst.get(val[2]) || '';
                code += c1 + c2 + c3;
              }
              currentEditCode = code.substring(0, 4);
            }
          }
          if (currentEditCode.length >= 1) activeEditKeys.add('z');
          if (currentEditCode.length >= 2) activeEditKeys.add(currentEditCode.substring(0, 2));
          if (currentEditCode.length >= 3) activeEditKeys.add(currentEditCode.substring(0, 3));
          if (currentEditCode.length >= 4) activeEditKeys.add(currentEditCode);
          // Standard initialization fallback block
          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }
        }

        // Keep input area visible allowing repeated inputs
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'block';

        // Toggle action buttons visibility based on single word vs phrase mode
        if (isSingleWordMode) {
          document.getElementById('btnMoveEnd').style.display = 'none';
          document.getElementById('btnDelete').style.display = 'none';
        } else {
          document.getElementById('btnMoveEnd').style.display = 'inline-block';
          document.getElementById('btnDelete').style.display = 'inline-block';
        }

        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'block';
        renderEditArea();
      }

      // Helper to restore state and refresh UI
      function applyState(targetState) {
        if (isSingleWordMode) {
          // Restore core data state for single word mode
          filecontent = targetState.filecontent;
          if (targetState.charDefCodeMap) {
            charDefCodeMap = new Map();
            for (let [k, v] of targetState.charDefCodeMap.entries()) {
              charDefCodeMap.set(k, [...v]);
            }
          }

          // Re-populate active edit keys configuration
          for (let i = 1; i <= singleWordActiveCode.length; i++) {
            activeEditKeys.add(singleWordActiveCode.substring(0, i));
          }

          if (!originalSnapshotState || originalSnapshotState.size === 0) {
            originalSnapshotState = getSystemState();
          }

          // Crucial: Refresh the edit workspace UI to reflect the restored state
          renderEditArea();
        } else {
          // Original Phrase Logic
          filecontent = targetState.filecontent;
          processContent(JSON.stringify(targetState.lines));
        }
      }

      function handleUndo() {
        // Prevent undoing past the initial logged action (index 0).
        // The user can no longer revert to the unlogged state (-1).
        if (currentIndex <= 0) return;

        let oldState = getSystemState();
        currentIndex--; // Move pointer back

        // Apply the AFTER state of the action we stepped back to
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function handleRedo() {
        if (currentIndex >= historyStack.length - 1) return;

        let oldState = getSystemState();
        currentIndex++; // Move pointer forward

        // Apply the AFTER state of the redone action
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function jumpToLog(index) {
        if (index < 0 || index >= historyStack.length || index === currentIndex) return;

        let oldState = getSystemState();
        currentIndex = index; // Jump directly to the selected state's index

        // Apply the AFTER state of the selected action log
        let targetState = historyStack[currentIndex].afterState || historyStack[currentIndex];

        applyState(targetState);

        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null;
        renderActionLogs();
      }

      function saveStateForUndo(description = 'Manual Edit') {
        // Truncate the history stack if the pointer is not at the end
        if (currentIndex < historyStack.length - 1) {
          historyStack = historyStack.slice(0, currentIndex + 1);
        }

        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        let charDefMapCopy = new Map();
        for (let [k, v] of charDefCodeMap.entries()) {
          charDefMapCopy.set(k, [...v]);
        }

        // Push the 'before' state
        historyStack.push({
          lines: currentLines,
          filecontent: filecontent,
          charDefCodeMap: charDefMapCopy,
          desc: description,
          afterState: null // Will be populated after the current action completes
        });

        // Always point to the latest state after saving
        currentIndex = historyStack.length - 1;
        renderActionLogs();

        // Asynchronously capture the state AFTER the synchronous action finishes
        let capturedIndex = currentIndex;
        setTimeout(() => {
          if (historyStack[capturedIndex]) {
            let afterLines = mcc0string.split('\n').filter(l => l.trim() !== '');
            let afterCharDefMapCopy = new Map();
            for (let [k, v] of charDefCodeMap.entries()) {
              afterCharDefMapCopy.set(k, [...v]);
            }
            historyStack[capturedIndex].afterState = {
              lines: afterLines,
              filecontent: filecontent,
              charDefCodeMap: afterCharDefMapCopy
            };
          }
        }, 0);
      }

      function handleCancelEdit() {
        // Revert to initial state before any edits in this session were made
        if (historyStack.length > 0) {
          applyState(historyStack[0]);
        }

        // Close the edit interface
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'none';
        document.getElementById('codeInput').value = '';

        // Reset tracking variables without updating originalSnapshotState
        currentEditCode = '';
        currentTargetWord = '';
        selectedEditItem = null;
        justMovedItem = null;
        activeEditKeys.clear();
        changedWords.clear();
        pendingAction = null;
        historyStack = [];
        currentIndex = -1;
        renderActionLogs();
        updateActionButtonsUI();
      }

      function handleEditCloseOnly() {
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'none';
        document.getElementById('codeInput').value = '';

        originalSnapshotState = getSystemState();

        currentEditCode = '';
        currentTargetWord = '';
        selectedEditItem = null;
        justMovedItem = null;
        activeEditKeys.clear();
        changedWords.clear();
        pendingAction = null;
        historyStack = [];
        currentIndex = -1;
        renderActionLogs(); // Visually empty out the log UI on close
        updateActionButtonsUI();
      }

```

當我在單字模式中作了改動,點擊cancel或finish之後,如果再次進入詞語模式,對比欄全部變為空,這是不對的,程式應在上一次cancal或finish後,清空對比欄的數據,不能影響到下一次的進程。
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

代码: 全选

完全理解你的困擾。狀態殘留導致下一次操作的對比欄位失效,這在切換不同模式時確實是個常見且讓人頭痛的邏輯漏洞。

問題的根源在於你的 **`originalSnapshotState`(原始快照狀態)沒有被正確釋放**。

在 `handleEditSubmit` 中,程式依賴以下邏輯來擷取當前進程的基準點:

```javascript
if (!originalSnapshotState || originalSnapshotState.size === 0) {
  originalSnapshotState = getSystemState();
}

```

但在目前的 `handleCancelEdit` 與 `handleEditCloseOnly` 函數中,`originalSnapshotState` 要麼被原封不動地保留,要麼被更新成當前狀態,**卻從未被清空**。這導致你下一次進入「詞語模式」時,程式誤以為基準點已經存在,從而跳過了擷取新基準點的步驟,最終導致對比欄抓不到正確的差異數據而變為空。

以下是針對這兩個函數的修正程式碼。

### 修正方案

你需要將 `originalSnapshotState` 在退出編輯時強制設為 `null`,強迫下一次的 `handleEditSubmit` 重新抓取乾淨的基準點。

**1. 修正 `handleCancelEdit**`

```javascript
      function handleCancelEdit() {
        // Revert to initial state before any edits in this session were made
        if (historyStack.length > 0) {
          applyState(historyStack[0]);
        }

        // Close the edit interface
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'none';
        document.getElementById('codeInput').value = '';

        // 【關鍵修正】:徹底清空快照狀態,避免干擾下一次進程
        originalSnapshotState = null; 

        // Reset other tracking variables
        currentEditCode = '';
        currentTargetWord = '';
        selectedEditItem = null;
        justMovedItem = null;
        activeEditKeys.clear();
        changedWords.clear();
        pendingAction = null;
        historyStack = [];
        currentIndex = -1;
        renderActionLogs();
        updateActionButtonsUI();
      }

```

**2. 修正 `handleEditCloseOnly**`

```javascript
      function handleEditCloseOnly() {
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'none';
        document.getElementById('codeInput').value = '';

        // 【關鍵修正】:這裡原本是 originalSnapshotState = getSystemState(); 
        // 應改為 null,讓下一次重新進入時再透過 handleEditSubmit 抓取最新的基準點
        originalSnapshotState = null; 

        currentEditCode = '';
        currentTargetWord = '';
        selectedEditItem = null;
        justMovedItem = null;
        activeEditKeys.clear();
        changedWords.clear();
        pendingAction = null;
        historyStack = [];
        currentIndex = -1;
        renderActionLogs(); // Visually empty out the log UI on close
        updateActionButtonsUI();
      }

```

### 為什麼這樣改有效?

* **Cancel (取消)**:放棄所有更改並恢復到歷史紀錄的第一筆。此時清空 `originalSnapshotState`、`activeEditKeys` 與 `changedWords`,確保系統狀態完全重置。
* **Finish / Close (完成並關閉)**:保留現有的更改並退出編輯區。將 `originalSnapshotState` 設為 `null` 可以確保下次使用者再次觸發 `handleEditSubmit` 輸入新代碼時,程式能精準地把「那一刻」當作新的比較基準,對比欄就能正常顯示該次進程的改動了。
gemini 3.1 pro
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5879
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

domchar5765.html

修正了一個極其嚴重的問題。
附件
domchar5765.html.7z
(14.86 KiB) 已下载 3 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

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