分页: 3 / 3

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

发表于 : 2026年 5月 21日 13:22
ejsoon
修正:
當點擊一個詞A,再點擊另一個詞B,如果詞A是在詞B的後面,則詞A將移至詞B的前面(這部分現在是對的),如果詞A是在詞B的前面,則詞A將移至詞B的後面(這部分現在不對)。

當移動結束,應該取消高亮,也就是去掉「selected」。

當一個edit-row中的項目,跟原來original的項目一致時,它的背景應呈淺綠色,Orig前要加上「=」。

把Reset edit change 跟close放到下一行,並在左邊增加undo和redo。

當加詞確認後,輸入框和確認按鈕都不會消失,可以再次輸入和增加。

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。



gemini pro就是要這種複雜一點的:

代码: 全选

Here are the necessary modifications to implement all requested features (movement logic, background color for identical rows, UI button layout, undo/redo functionality, and input box persistence).

### 1. Update the HTML Buttons Layout

Locate the `.edit-actions` div in the HTML section and split it into two rows, adding the Undo and Redo buttons.

**Find:**

```html
          <div class="edit-actions">
            <button id="btnMoveEnd" class="edit-action-btn" onclick="handleEditAction('end')">Put to the end</button>
            <button id="btnDelete" class="edit-action-btn" onclick="handleEditAction('delete')">Delete</button>
            <button id="btnResetEdit" class="edit-action-btn" onclick="handleEditResetOnly()">Reset Edit Changes</button>
            <button id="btnCloseEdit" class="edit-action-btn" onclick="handleEditCloseOnly()">Close</button>
          </div>

```

**Replace with:**

```html
          <div class="edit-actions">
            <button id="btnMoveEnd" class="edit-action-btn" onclick="handleEditAction('end')">Put to the end</button>
            <button id="btnDelete" class="edit-action-btn" onclick="handleEditAction('delete')">Delete</button>
          </div>
          <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="btnResetEdit" class="edit-action-btn" onclick="handleEditResetOnly()">Reset Edit Changes</button>
            <button id="btnCloseEdit" class="edit-action-btn" onclick="handleEditCloseOnly()">Close</button>
          </div>

```

---

### 2. Add Global Undo/Redo State Functions

Locate the global variable declarations (around `let originalSnapshotState = new Map();`) and insert the history stacks and functions right below them.

**Find:**

```javascript
      let pendingAction = null;
      let originalSnapshotState = new Map();

      // Setup Event Listeners

```

**Replace with:**

```javascript
      let pendingAction = null;
      let originalSnapshotState = new Map();
      let undoStack = [];
      let redoStack = [];

      function saveStateForUndo() {
        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        undoStack.push(currentLines);
        redoStack = []; // Clear redo stack whenever a new action occurs
      }

      function handleUndo() {
        if (undoStack.length === 0) return;
        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        redoStack.push(currentLines);
        let prevState = undoStack.pop();
        let oldState = getSystemState();
        processContent(JSON.stringify(prevState));
        detectAndRenderChanges(oldState, getSystemState());
      }

      function handleRedo() {
        if (redoStack.length === 0) return;
        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        undoStack.push(currentLines);
        let nextState = redoStack.pop();
        let oldState = getSystemState();
        processContent(JSON.stringify(nextState));
        detectAndRenderChanges(oldState, getSystemState());
      }

      // Setup Event Listeners

```

---

### 3. Keep Input Box Visible and Save State on Add

Modify `handleEditSubmit` to keep the input box from disappearing, lock the baseline only once per session, and save the state if a new word is added.

**Find:**

```javascript
          if (!lines.includes(val)) {
            lines.push(val);
            processContent(JSON.stringify(lines));
          }

          let code = 'z';

```

**Replace with:**

```javascript
          if (!lines.includes(val)) {
            saveStateForUndo(); // Save state before adding new word
            lines.push(val);
            processContent(JSON.stringify(lines));
          }

          let code = 'z';

```

**Find (further down in `handleEditSubmit`):**

```javascript
        // CRITICAL FIX: Lock unchanged baseline structural footprint before user interaction
        originalSnapshotState = getSystemState();

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

```

**Replace with:**

```javascript
        // CRITICAL FIX: Lock unchanged baseline structural footprint only once per session
        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';
        renderEditArea();

```

---

### 4. Update Movement Logic and Clear Highlight

Modify `handleItemClick` to correctly place Word A before Word B if it was originally after, or after Word B if it was originally before, and finally save state.

**Find:**

```javascript
          if (idxA > -1) {
            let oldState = getSystemState();

            // Extract A from lines
            lines.splice(idxA, 1);

            // Recalculate target position index for element B
            let idxB = lines.indexOf(word);
            if (idxB > -1) {
              // ALWAYS place word A directly in front of word B
              lines.splice(idxB, 0, selectedEditItem);

              processContent(JSON.stringify(lines));
              let newState = getSystemState();
              detectAndRenderChanges(oldState, newState);
            }
          }
          selectedEditItem = null;

```

**Replace with:**

```javascript
          let originalIdxA = idxA;
          let originalIdxB = lines.indexOf(word);

          if (originalIdxA > -1 && originalIdxB > -1 && originalIdxA !== originalIdxB) {
            let oldState = getSystemState();
            saveStateForUndo(); // Save state before movement

            // Extract A from lines
            lines.splice(originalIdxA, 1);

            // Recalculate target position index for element B after A is removed
            let newIdxB = lines.indexOf(word);
            
            // If A was originally after B, move A to be before B
            if (originalIdxA > originalIdxB) {
              lines.splice(newIdxB, 0, selectedEditItem);
            } 
            // If A was originally before B, move A to be after B
            else {
              lines.splice(newIdxB + 1, 0, selectedEditItem);
            }

            processContent(JSON.stringify(lines));
            let newState = getSystemState();
            detectAndRenderChanges(oldState, newState);
          }
          selectedEditItem = null; // Clear highlight after moving

```

---

### 5. Add Background Color and "Orig" Tag Update for Identical Rows

Modify `renderEditArea` to detect if the current line entirely matches the baseline.

**Find:**

```javascript
          if (activeItems.length > 0 || originalItems.length > 0) {
            // Render active editable line elements
            let activeRowDiv = document.createElement('div');
            activeRowDiv.className = 'edit-row';

            let activeTitle = document.createElement('div');

```

**Replace with:**

```javascript
          if (activeItems.length > 0 || originalItems.length > 0) {
            // Check if active items perfectly match original baseline items
            let isIdentical = false;
            let activeWordsArr = activeItems.map(i => i.w);
            if (activeWordsArr.length === originalItems.length && activeWordsArr.every((v, i) => v === originalItems[i])) {
              isIdentical = true;
            }

            // Render active editable line elements
            let activeRowDiv = document.createElement('div');
            activeRowDiv.className = 'edit-row';
            
            // Apply light green background if the row exactly matches the original
            if (isIdentical && originalItems.length > 0) {
              activeRowDiv.style.backgroundColor = '#e8f5e9';
            }

            let activeTitle = document.createElement('div');

```

**Find (further down in `renderEditArea`):**

```javascript
            // Mobile-Only context insertion rendering logic block
            let mobilePreviewDiv = document.createElement('div');
            mobilePreviewDiv.className = 'mobile-original-preview';
            mobilePreviewDiv.innerHTML = '<strong>Orig: </strong>';
            if (originalItems.length === 0) {

```

**Replace with:**

```javascript
            // Mobile-Only context insertion rendering logic block
            let mobilePreviewDiv = document.createElement('div');
            mobilePreviewDiv.className = 'mobile-original-preview';
            
            // Add '=' indicator before 'Orig' if identical
            if (isIdentical && originalItems.length > 0) {
              mobilePreviewDiv.innerHTML = '<strong>= Orig: </strong>';
            } else {
              mobilePreviewDiv.innerHTML = '<strong>Orig: </strong>';
            }

            if (originalItems.length === 0) {

```

---

### 6. Save State on Delete/Put to End & Reset History Stacks

Ensure state is saved when deleting or moving to the end, and history resets when closing.

**Find in `applyActionToWord`:**

```javascript
        let oldState = getSystemState();

        if (action === 'delete') {

```

**Replace with:**

```javascript
        let oldState = getSystemState();
        
        saveStateForUndo(); // Save state before executing delete or put-to-end action

        if (action === 'delete') {

```

**Find in `handleEditCloseOnly`:**

```javascript
        originalSnapshotState.clear();
        updateActionButtonsUI();

```

**Replace with:**

```javascript
        originalSnapshotState.clear();
        undoStack = []; // Reset history
        redoStack = []; // Reset history
        updateActionButtonsUI();

```

**Find in `handleEditResetOnly`:**

```javascript
        changedWords.clear();
        selectedEditItem = null;
        pendingAction = null;
        updateActionButtonsUI();

```

**Replace with:**

```javascript
        changedWords.clear();
        selectedEditItem = null;
        pendingAction = null;
        undoStack = []; // Reset history after hard revert
        redoStack = []; // Reset history
        updateActionButtonsUI();

```

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

发表于 : 2026年 5月 21日 14:10
ejsoon
修正:
當順序調整結束,剛移動完的字詞會殘留「class=selected」。現在要把selected去掉,改為「just-move」(或者換個更好的英文單字表示剛移動),背景為淺綠色。

當按下enter或點擊submit,codeInput的內容將被清空。同時如果不是第一次加詞,之前的undo以及對比有無改動的log並不會消清除,只是合並上去。

把操作記錄log列表放至最下方,記錄的標題是「某詞 -> 某詞 之前/之後」。當點擊其中一個記錄,將能回到記錄的狀態。

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
domchar572.7z
(7.97 KiB) 已下载 7 次

gemjni pro又轉發給thinking了:

代码: 全选

這裡是一所有需要修改的地方。已將調整完的字詞高亮類別改為 `just-moved`(並加上淺綠色背景),實現了連續加詞不重設變更紀錄與清空輸入框的功能,並在最下方增加了可點擊還原狀態的操作記錄(Operation Log)區塊。所有的代碼和注釋均已使用英文編寫。

---

### 1. 新增 `just-moved` 樣式與最下方的 Log 區塊

請在 `<style>` 標籤中加入 `just-moved` 的外觀樣式:

**尋找:**

```css
      .edit-item.changed {
        border: 2px dashed #f44336 !important;
        background: #ffebee;
      }

```

**替換為:**

```css
      .edit-item.changed {
        border: 2px dashed #f44336 !important;
        background: #ffebee;
      }
      /* Highlight for the word that was just reordered */
      .edit-item.just-moved {
        background-color: #e8f5e9 !important;
        border: 2px solid #4caf50 !important;
        color: #2e7d32;
      }

```

在 `</body>` 標籤上方(`</script>` 之前)加入用於顯示操作記錄的 HTML 結構:

**尋找:**

```html
    </div>

    <script>

```

**替換為:**

```html
    </div>

    <div id="logArea" style="margin-top: 30px; border-top: 1px solid #ccc; padding-top: 15px;">
      <div style="font-weight: bold; font-size: 16px; margin-bottom: 10px; color: #333;">Operation Log</div>
      <div id="logList" style="max-height: 200px; overflow-y: auto; background: #fdfdfd; border: 1px solid #ddd; padding: 10px; border-radius: 4px;">
        <span style="color: #999; font-style: italic;">No operations recorded yet.</span>
      </div>
    </div>

    <script>

```

---

### 2. 新增全域變數與 Log 管理函數

在 JavaScript 變數宣告區塊中加入 `justMovedWord` 與 `operationLogs` 陣列,並加入處理 Log 的相關核心函數:

**尋找:**

```javascript
      let currentEditCode = '';
      let currentTargetWord = '';
      let selectedEditItem = null;
      let activeEditKeys = new Set();
      let changedWords = new Set();
      let pendingAction = null;
      let originalSnapshotState = new Map();

      // Setup Event Listeners

```

**替換為:**

```javascript
      let currentEditCode = '';
      let currentTargetWord = '';
      let selectedEditItem = null;
      let justMovedWord = null; // Tracks the word that was just reordered
      let activeEditKeys = new Set();
      let changedWords = new Set();
      let pendingAction = null;
      let originalSnapshotState = new Map();
      let operationLogs = []; // Stores historical operation states

      // Pushes a new operation state to the log history list
      function addOperationLog(text) {
        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        operationLogs.push({
          text: text,
          state: JSON.stringify(currentLines)
        });
        renderOperationLogs();
      }

      // Renders the interactive operation log UI entries
      function renderOperationLogs() {
        let logContainer = document.getElementById('logList');
        if (!logContainer) return;
        logContainer.innerHTML = '';
        if (operationLogs.length === 0) {
          logContainer.innerHTML = '<span style="color: #999; font-style: italic;">No operations recorded yet.</span>';
          return;
        }
        operationLogs.forEach((log, index) => {
          let item = document.createElement('div');
          item.style.padding = '6px 8px';
          item.style.margin = '4px 0';
          item.style.background = '#fff';
          item.style.border = '1px solid #eee';
          item.style.borderRadius = '4px';
          item.style.cursor = 'pointer';
          item.style.transition = 'background 0.2s';
          item.textContent = `${index + 1}. ${log.text}`;
          item.onmouseover = () => item.style.background = '#f5f5f5';
          item.onmouseout = () => item.style.background = '#fff';
          item.onclick = () => revertToLogState(index);
          logContainer.appendChild(item);
        });
      }

      // Restores the application to a chosen historical log state snapshot
      function revertToLogState(index) {
        let log = operationLogs[index];
        if (!log) return;
        let oldState = getSystemState();
        processContent(log.state);
        let newState = getSystemState();
        justMovedWord = null; // Reset single movement highlight upon historic navigation
        detectAndRenderChanges(oldState, newState);
      }

      // Setup Event Listeners

```

---

### 3. 修改 `handleEditSubmit` 與 `handleEditReset`

確保提交時輸入框被清空、保持輸入介面可見,且只在第一次啟動時鎖定原始 Baseline 快照(後續加詞只會合併追加而不重設對比基礎):

**尋找:**

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

        activeEditKeys.clear();
        changedWords.clear();
        selectedEditItem = null;
        pendingAction = null;
        updateActionButtonsUI();

        // 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)) {
            lines.push(val);
            processContent(JSON.stringify(lines));
          }

          let code = 'z';

```

**替換為:**

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

        // Clear the codeInput field immediately
        inputEl.value = '';

        selectedEditItem = null;
        pendingAction = null;
        justMovedWord = null; 
        updateActionButtonsUI();

        // Protect and retain the initial baseline state if it already exists
        if (!originalSnapshotState || originalSnapshotState.size === 0) {
          originalSnapshotState = getSystemState();
        }

        // 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)) {
            lines.push(val);
            processContent(JSON.stringify(lines));
            addOperationLog(`Added word: ${val}`); // Log the word addition
          }

          let code = 'z';

```

**尋找(`handleEditSubmit` 函數底部與 `handleEditReset`):**

```javascript
        // CRITICAL FIX: Lock unchanged baseline structural footprint before user interaction
        originalSnapshotState = getSystemState();

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

      function handleEditReset() {
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'none';
        document.getElementById('codeInput').value = '';
        currentEditCode = '';
        currentTargetWord = '';
        selectedEditItem = null;
        activeEditKeys.clear();
        changedWords.clear();
        pendingAction = null;
        originalSnapshotState.clear();
        updateActionButtonsUI();
      }

```

**替換為:**

```javascript
        // Keep input box visible for repeated inputs and additions
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'block';
        
        let oldState = getSystemState();
        detectAndRenderChanges(oldState, oldState);
      }

      function handleEditReset() {
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'none';
        document.getElementById('codeInput').value = '';
        currentEditCode = '';
        currentTargetWord = '';
        selectedEditItem = null;
        justMovedWord = null;
        activeEditKeys.clear();
        changedWords.clear();
        pendingAction = null;
        originalSnapshotState.clear();
        operationLogs = []; // Wipe operation history on complete reset
        renderOperationLogs();
        updateActionButtonsUI();
      }

```

---

### 4. 更新渲染邏輯以支援 `just-moved` 顯示

修改 `renderEditArea` 函數,為剛調整完順序的元素加上 `just-moved` 類別:

**尋找:**

```javascript
            activeItems.forEach(item => {
              let span = document.createElement('span');
              span.textContent = item.w;
              span.className = `edit-item ${item.cls}`;

              if (item.w === selectedEditItem) span.classList.add('selected');
              if (item.w === currentTargetWord) span.classList.add('target-word');
              if (changedWords.has(item.w)) span.classList.add('changed');

              span.onclick = () => handleItemClick(item.w);
              activeRowDiv.appendChild(span);
            });

```

**替換為:**

```javascript
            activeItems.forEach(item => {
              let span = document.createElement('span');
              span.textContent = item.w;
              span.className = `edit-item ${item.cls}`;

              if (item.w === selectedEditItem) span.classList.add('selected');
              if (item.w === justMovedWord) span.classList.add('just-moved'); // Apply green highlight to moved item
              if (item.w === currentTargetWord) span.classList.add('target-word');
              if (changedWords.has(item.w)) span.classList.add('changed');

              span.onclick = () => handleItemClick(item.w);
              activeRowDiv.appendChild(span);
            });

```

---

### 5. 更新按鈕行為的 Log 紀錄

修改 `applyActionToWord` 以便追蹤按鈕引發的刪除或置底操作:

**尋找:**

```javascript
        if (action === 'delete') {
          lines.splice(idx, 1);
        } else if (action === 'end') {
          lines.splice(idx, 1);
          lines.push(word);
        }

```

**替換為:**

```javascript
        if (action === 'delete') {
          lines.splice(idx, 1);
          addOperationLog(`Deleted word: ${word}`);
        } else if (action === 'end') {
          lines.splice(idx, 1);
          lines.push(word);
          addOperationLog(`${word} -> Moved to end`);
        }

```

---

### 6. 重構點擊移動邏輯(修正前後移動錯誤、套用新高亮並加入 Log 標題)

重構 `handleItemClick` 函數中的字詞交換邏輯。當 A 原本在 B 後面時移至 B 前面;原本在 B 前面時移至 B 後面。移動完成後移除 `selected` 狀態並標註為 `justMovedWord`,同時寫入對應格式的 Log:

**尋找:**

```javascript
      function handleItemClick(word) {
        if (pendingAction) {
          applyActionToWord(pendingAction, word);
          pendingAction = null;
          selectedEditItem = null;
          updateActionButtonsUI();
          return;
        }

        if (!selectedEditItem) {
          selectedEditItem = word;
          renderEditArea();
        } else {
          if (selectedEditItem === word) {
            selectedEditItem = null;
            renderEditArea();
            return;
          }

          let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
          let idxA = lines.indexOf(selectedEditItem);

          if (idxA > -1) {
            let oldState = getSystemState();

            // Extract A from lines
            lines.splice(idxA, 1);

            // Recalculate target position index for element B
            let idxB = lines.indexOf(word);
            if (idxB > -1) {
              // ALWAYS place word A directly in front of word B
              lines.splice(idxB, 0, selectedEditItem);

              processContent(JSON.stringify(lines));
              let newState = getSystemState();
              detectAndRenderChanges(oldState, newState);
            }
          }
          selectedEditItem = null;
        }
      }

```

**替換為:**

```javascript
      function handleItemClick(word) {
        if (pendingAction) {
          applyActionToWord(pendingAction, word);
          pendingAction = null;
          selectedEditItem = null;
          updateActionButtonsUI();
          return;
        }

        if (!selectedEditItem) {
          selectedEditItem = word;
          justMovedWord = null; // Clear previous movement highlight when a new word is selected
          renderEditArea();
        } else {
          if (selectedEditItem === word) {
            selectedEditItem = null;
            renderEditArea();
            return;
          }

          let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
          let idxA = lines.indexOf(selectedEditItem);
          let idxB = lines.indexOf(word);

          if (idxA > -1 && idxB > -1 && idxA !== idxB) {
            let oldState = getSystemState();
            let wordA = selectedEditItem;
            let wordB = word;
            let logText = "";

            // Remove item A from its original position
            lines.splice(idxA, 1);

            // Re-locate position index for item B after A's extraction
            let newIdxB = lines.indexOf(wordB);

            if (idxA > idxB) {
              // Word A was originally behind B -> Move A to be BEFORE B
              lines.splice(newIdxB, 0, wordA);
              logText = `${wordA} -> ${wordB} Before`;
            } else {
              // Word A was originally in front of B -> Move A to be AFTER B
              lines.splice(newIdxB + 1, 0, wordA);
              logText = `${wordA} -> ${wordB} After`;
            }

            justMovedWord = wordA; // Update highlight flag to show the reordered word

            processContent(JSON.stringify(lines));
            let newState = getSystemState();
            addOperationLog(logText); // Register action string inside historical list
            detectAndRenderChanges(oldState, newState);
          }
          selectedEditItem = null; // Clear active selected flag upon displacement execution
        }
      }

```

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

发表于 : 2026年 5月 21日 14:24
ejsoon
gemini pro來搞它:

代码: 全选

Here are the modifications to implement your requirements. The code and comments are provided entirely in English.

### 1. CSS Updates

Add the new styles for the `just-moved` state and the `action logs` list container inside the `<style>` tag:

```css
      .edit-item.just-moved {
        background: #e8f5e9 !important;
        border: 2px solid #4caf50;
        box-shadow: 0 0 5px rgba(76, 175, 80, 0.5);
      }

      .log-container {
        margin-top: 20px;
        padding: 10px;
        background: #f9f9f9;
        border: 1px solid #ccc;
        border-radius: 5px;
      }

      .log-item {
        padding: 8px;
        cursor: pointer;
        border-bottom: 1px solid #ddd;
        transition: background-color 0.2s;
        font-size: 14px;
      }

      .log-item:hover {
        background: #e0e0e0;
      }
      
      .log-item:last-child {
        border-bottom: none;
      }

```

### 2. HTML Structure Update

Add the Action Log container inside `<div id="editArea">`, placing it right below the `.edit-actions` div elements:

```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="btnResetEdit" class="edit-action-btn" onclick="handleEditResetOnly()">
              Reset Edit Changes
            </button>
            <button id="btnCloseEdit" class="edit-action-btn" onclick="handleEditCloseOnly()">Close</button>
          </div>
          
          <div id="actionLogContainer" class="log-container" style="display: none;">
            <div style="font-weight: bold; margin-bottom: 8px;">Action Logs</div>
            <div id="actionLogsList"></div>
          </div>

```

### 3. Global Variables Update

Add a new tracking variable for the `justMovedItem` near where `selectedEditItem` is declared:

```javascript
      let currentEditCode = '';
      let currentTargetWord = '';
      let selectedEditItem = null;
      let justMovedItem = null; // Track the recently moved item
      let activeEditKeys = new Set();
      let changedWords = new Set();
      let pendingAction = null;
      let originalSnapshotState = new Map();
      let undoStack = []; // Will now store objects: { lines: [...], desc: "..." }
      let redoStack = [];

```

### 4. JavaScript Functions: Undo/Redo and Log Implementation

Replace `saveStateForUndo`, `handleUndo`, and `handleRedo` with the logic below, and add the new `jumpToLog` and `renderActionLogs` functions:

```javascript
      // Update to accept descriptions to build the log history
      function saveStateForUndo(description = 'Manual Edit') {
        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        undoStack.push({ lines: currentLines, desc: description });
        redoStack = []; // Clear redo stack whenever a new action occurs
        renderActionLogs(); // Update visual logs
      }

      function handleUndo() {
        if (undoStack.length === 0) return;
        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        let lastActionDesc = undoStack[undoStack.length - 1].desc;
        redoStack.push({ lines: currentLines, desc: `Undo: ${lastActionDesc}` });

        let prevState = undoStack.pop();
        let oldState = getSystemState();
        processContent(JSON.stringify(prevState.lines));
        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null; // Clear just-moved highlight on undo
        renderActionLogs();
      }

      function handleRedo() {
        if (redoStack.length === 0) return;
        let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
        let nextState = redoStack.pop();
        undoStack.push({ lines: currentLines, desc: nextState.desc });

        let oldState = getSystemState();
        processContent(JSON.stringify(nextState.lines));
        detectAndRenderChanges(oldState, getSystemState());
        justMovedItem = null; // Clear just-moved highlight on redo
        renderActionLogs();
      }

      // Restores the application to a specific point in the log history
      function jumpToLog(index) {
        if (index < 0 || index >= undoStack.length) return;
        
        let targetState = undoStack[index];
        
        // Truncate the stack logically up to the selected target log
        undoStack = undoStack.slice(0, index + 1);

        let oldState = getSystemState();
        processContent(JSON.stringify(targetState.lines));
        detectAndRenderChanges(oldState, getSystemState());
        
        justMovedItem = null;
        redoStack = []; // Clear redo stack on manual timeline jump
        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);
        });
      }

```

### 5. JavaScript Functions: `handleEditSubmit` & `renderEditArea`

Update `handleEditSubmit` to clear the input while keeping previous session changes/history intact. Also, add the `just-moved` logic into `renderEditArea`.

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

        // Clear the code input upon submit/enter
        document.getElementById('codeInput').value = '';

        // DO NOT clear activeEditKeys, changedWords, undoStack, or redoStack here 
        // to preserve merged edit history across multiple inputs.
        selectedEditItem = null;
        justMovedItem = null; // Wipe out previously moved highlights
        pendingAction = null;
        updateActionButtonsUI();

        // 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)) {
            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);

        // CRITICAL FIX: Lock unchanged baseline structural footprint only once per session
        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';
        renderEditArea();
      }

      // Inside renderEditArea(), update the activeItems.forEach block:
            activeItems.forEach(item => {
              let span = document.createElement('span');
              span.textContent = item.w;
              span.className = `edit-item ${item.cls}`;

              if (item.w === selectedEditItem) span.classList.add('selected');
              if (item.w === justMovedItem) span.classList.add('just-moved'); // Render just-moved class
              if (item.w === currentTargetWord) span.classList.add('target-word');
              if (changedWords.has(item.w)) span.classList.add('changed');

              span.onclick = () => handleItemClick(item.w);
              activeRowDiv.appendChild(span);
            });

```

### 6. JavaScript Functions: Tracking Logs during Movement & Actions

Update `applyActionToWord` and `handleItemClick` to inject descriptions into the state history and clear/set `justMovedItem`.

```javascript
      function applyActionToWord(action, word) {
        let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
        let idx = lines.indexOf(word);
        if (idx === -1) return;

        let oldState = getSystemState();

        if (action === 'delete') {
          saveStateForUndo(`Deleted: ${word}`);
          lines.splice(idx, 1);
        } else if (action === 'end') {
          saveStateForUndo(`Moved to end: ${word}`);
          lines.splice(idx, 1);
          lines.push(word);
        }

        justMovedItem = null; // Reset highlight since item was deleted or thrown to back
        processContent(JSON.stringify(lines));
        let newState = getSystemState();
        detectAndRenderChanges(oldState, newState);
      }

      function handleItemClick(word) {
        if (pendingAction) {
          applyActionToWord(pendingAction, word);
          pendingAction = null;
          selectedEditItem = null;
          updateActionButtonsUI();
          return;
        }

        if (!selectedEditItem) {
          selectedEditItem = word;
          justMovedItem = null; // Clear previous just-moved styling when making a new selection
          renderEditArea();
        } else {
          if (selectedEditItem === word) {
            selectedEditItem = null;
            renderEditArea();
            return;
          }

          let lines = mcc0string.split('\n').filter(l => l.trim() !== '');
          let idxA = lines.indexOf(selectedEditItem);

          let originalIdxA = idxA;
          let originalIdxB = lines.indexOf(word);

          if (originalIdxA > -1 && originalIdxB > -1 && originalIdxA !== originalIdxB) {
            let oldState = getSystemState();
            
            // Format descriptive log: "Word A -> before/after Word B"
            let placement = originalIdxA > originalIdxB ? 'before' : 'after';
            saveStateForUndo(`${selectedEditItem} -> ${placement} ${word}`); 

            // Extract A from lines
            lines.splice(originalIdxA, 1);

            // Recalculate target position index for element B after A is removed
            let newIdxB = lines.indexOf(word);

            // If A was originally after B, move A to be before B
            if (originalIdxA > originalIdxB) {
              lines.splice(newIdxB, 0, selectedEditItem);
            }
            // If A was originally before B, move A to be after B
            else {
              lines.splice(newIdxB + 1, 0, selectedEditItem);
            }

            justMovedItem = selectedEditItem; // Target item now registers as just-moved
            processContent(JSON.stringify(lines));
            let newState = getSystemState();
            detectAndRenderChanges(oldState, newState);
          }
          selectedEditItem = null; // Deselect element once movement completes
        }
      }

```

### 7. Close and Reset Handling

Finally, update the close and reset handlers to ensure they effectively wipe variables (including your new `justMovedItem`) out completely when requested.

```javascript
      function handleEditCloseOnly() {
        document.getElementById('inputArea').style.display = 'block';
        document.getElementById('editArea').style.display = 'none';
        document.getElementById('codeInput').value = '';
        currentEditCode = '';
        currentTargetWord = '';
        selectedEditItem = null;
        justMovedItem = null;
        activeEditKeys.clear();
        changedWords.clear();
        pendingAction = null;
        originalSnapshotState.clear();
        undoStack = []; 
        redoStack = []; 
        renderActionLogs(); // Visually empty out the log UI on close
        updateActionButtonsUI();
      }

      function handleEditResetOnly() {
        // ... (Keep existing early logic)

        // Process back into application structures and redraw the canvas layout
        processContent(JSON.stringify(mergedRevertedLines));
        changedWords.clear();
        selectedEditItem = null;
        justMovedItem = null; // Strip the highlight
        pendingAction = null;
        updateActionButtonsUI();
        renderEditArea();
      }

```

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

发表于 : 2026年 5月 21日 14:45
ejsoon
iframe的src如果跟當前的域名不同,則把文件名之前的域名去除,只讀當前文件夾。

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。


找flash即可:

代码: 全选

Here is the modification to fulfill your requirement. It checks if the `iframe` URL domain differs from the current origin, and if so, extracts only the file name so that it loads relative to the current folder.

All code and comments are provided entirely in English.

### JavaScript Update

Locate the function where the `iframe.src` is handled (typically inside the `processContent` function or where the iframe source is set based on the parsed `.cin` filename). Replace that logic with the following block:

```javascript
        // Ensure the iframe URL loads from the current folder if it points to a different domain
        let iframeSrc = cinFileName; // Assuming cinFileName holds the source URL/path
        try {
          // Check if the URL is absolute and has a different origin
          if (iframeSrc.startsWith('http://') || iframeSrc.startsWith('https://')) {
            let currentOrigin = window.location.origin;
            let targetUrl = new URL(iframeSrc);
            
            if (targetUrl.origin !== currentOrigin) {
              // Extract only the file name from the path to read from the current folder
              let pathParts = targetUrl.pathname.split('/');
              iframeSrc = pathParts[pathParts.length - 1];
            }
          }
        } catch (e) {
          // Fallback to the original source path if URL parsing fails
          console.error("Error parsing iframe origin:", e);
          iframeSrc = cinFileName;
        }

        // Set the processed source path to the iframe
        document.getElementById('iframeId').src = iframeSrc; 

```

*(Note: Please replace `'iframeId'` and `cinFileName` with the actual variable and element ID names used in your file if they differ.)*

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

发表于 : 2026年 5月 21日 15:00
ejsoon
domchar573

加了修改log,能一次加多個詞,察看加詞後的騰挪結果。

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

发表于 : 2026年 5月 21日 16:02
ejsoon
修正:
在對比欄中,去掉「Original baseline View」這個標題。

當加詞時,應把未加詞前的狀態作為初始狀態。

例如我把「zqym 手上」一詞加入,而原先zqym有兩個編碼,則右邊初始欄應顯示沒有「手上」的版本,左邊含有「手上」的一欄也將不會是淺綠色。

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。


gemini pro轉flash:

代码: 全选

Here are the targeted code modifications required to implement your requested changes, keeping all source code and documentation strictly in English.

### 1. Remove the "Original Baseline View" Title

Locate the HTML structure within the `<div id="editArea">` section and remove or comment out the title text element.

**Find:**

```html
<div class="split-pane pane-original">
  <div style="font-weight: bold; font-size: 16px; margin-bottom: 12px; color: #555">
    Original Baseline View (Unmodifiable)
  </div>
  <div id="originalRows"></div>
</div>

```

**Replace with:**

```html
<div class="split-pane pane-original">
  <div id="originalRows"></div>
</div>

```

---

### 2. Dynamically Snapshot Baseline State Prior to Adding Words

Modify `handleEditSubmit()` so that whenever a word input is parsed and injected, the system dynamically locks and benchmarks the current layout structure as the fresh baseline state *before* the new word alters the structural arrays.

**Find:**

```javascript
        } else {
          currentTargetWord = val;
          let lines = mcc0string.split('\n').filter(l => l.trim() !== '');

          if (!lines.includes(val)) {
            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);

        // CRITICAL FIX: Lock unchanged baseline structural footprint only once per session
        if (!originalSnapshotState || originalSnapshotState.size === 0) {
          originalSnapshotState = getSystemState();
        }

```

**Replace with:**

```javascript
        } 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();
        }

```

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

发表于 : 2026年 5月 21日 16:10
ejsoon
domchar574

新加的詞不會出現在「初始欄」中。剛移的詞顯淡紫色。