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

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

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

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

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

帖子 ejsoon »

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

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

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

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

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
        }
      }

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

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

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

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

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

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

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

帖子 ejsoon »

domchar573

加了修改log,能一次加多個詞,察看加詞後的騰挪結果。
附件
domchar573.7z
(9 KiB) 已下载 36 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5914
注册时间: 2022年 11月 18日 17:36
联系:

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

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

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

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

帖子 ejsoon »

domchar574

新加的詞不會出現在「初始欄」中。剛移的詞顯淡紫色。
附件
domchar574.7z
(8.99 KiB) 已下载 31 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5914
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

改進:
去掉所有與iframe-json相關的代碼,在Statistics分頁中,去掉Update和All按鈕。「High Freq」改為「二碼高頻」,「Common」改為「三碼常用」,「Repeat」改為「四碼詞語」,「Multi」改為「多字詞語」。

當點擊這些按鈕時,它將把JSON.stringify(tobj([...]))的數據用「標題——點擊展開」的方式呈現,標題將給出這個分支的名稱和數量。不要用原生的detail,用更美觀的js控制其點擊展開和收回。

當其展開後,點擊其中一個詞語,則它將切換回Edit標籤,並把這個詞語作為所輸入的文字。當Edit已把data改動,再切換回Statistics,將重新統計。

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

代码: 全选

改進:
去掉所有與iframe-json相關的代碼,在Statistics分頁中,去掉Update和All按鈕。「High Freq」改為「二碼高頻」,「Common」改為「三碼常用」,「Repeat」改為「四碼詞語」,「Multi」改為「多字詞語」。

當點擊這些按鈕時,它將把JSON.stringify(tobj([...]))的數據用「標題——點擊展開」的方式呈現,標題將給出這個分支的名稱和數量。不要用原生的detail,用更美觀的js控制其點擊展開和收回。

當其展開後,點擊其中一個詞語,則它將切換回Edit標籤,並把這個詞語作為所輸入的文字。當Edit已把data改動,再切換回Statistics,將重新統計。

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

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

帖子 ejsoon »

flash來吧!

代码: 全选

Here are the modifications required to complete your requested improvements. All UI elements, logic updates, and comments are written in English.

---

## 1. HTML Adjustments (UI Changes)

Replace the contents inside the `<div id="contentStats">` container and clear out the old `<iframe>` setup.

```html
<div id="contentStats" class="tab-content">
  <div class="stats-buttons" id="statsBtnContainer">
    <button class="stats-btn" id="btnMcc1All" onclick="renderStatsSection('mcc1all', '二碼高頻', this)">二碼高頻</button>
    <button class="stats-btn" id="btnMcc2All" onclick="renderStatsSection('mcc2all', '三碼常用', this)">三碼常用</button>
    <button class="stats-btn" id="btnMcc3" onclick="renderStatsSection('mcc3', '四碼詞語', this)">四碼詞語</button>
    <button class="stats-btn" id="btnMcc4" onclick="renderStatsSection('mcc4', '多字詞語', this)">多字詞語</button>
  </div>
  
  <div id="statsDisplayContainer" style="margin-top: 20px;"></div>
</div>

```

---

## 2. CSS Styles (Accordion UI styling)

Add these styling definitions to your `<style>` block to build an elegant accordion structure without using native `<details>` elements.

```css
/* Add to your existing <style> tag */
.accordion-wrapper {
  margin-bottom: 10px;
  border: 1px solid #ddd;
  border-radius: 6px;
  overflow: hidden;
  background: #fff;
}
.accordion-header {
  padding: 12px 16px;
  background-color: #f5f5f5;
  font-weight: bold;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  user-select: none;
  transition: background-color 0.2s;
}
.accordion-header:hover {
  background-color: #e0e0e0;
}
.accordion-header::after {
  content: '▼';
  font-size: 12px;
  transition: transform 0.2s;
  color: #666;
}
.accordion-wrapper.open .accordion-header::after {
  transform: rotate(180deg);
}
.accordion-content {
  display: none;
  padding: 15px;
  border-top: 1px solid #ddd;
  background: #fafafa;
}
.accordion-wrapper.open .accordion-content {
  display: block;
}
.stats-word-item {
  display: inline-block;
  padding: 4px 8px;
  margin: 3px;
  background: #fff;
  border: 1px solid #ccc;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.2s;
}
.stats-word-item:hover {
  background-color: #e3f2fd;
  border-color: #2196f3;
}

```

---

## 3. JavaScript Updates

### Remove / Clean up Old Code

* Locate and completely delete the `showContent(type, btnElement)` function.
* Remove the line `document.getElementById('mcc0update').style.display = 'none';` inside the `resetPage` function, as that button no longer exists.
* Inside the `DOMContentLoaded` event listener, delete the entire `try-catch` block targeting `domifr` and `iframeSrc` initialization logic.

### Add New Logic Functions

Add the following functions to your `<script>` area to handle the automated re-tabbing, responsive accordion behaviors, and interactive word selection hooks.

```javascript
// Handles active statistics calculation and visually renders the stylized interactive map
function renderStatsSection(type, branchName, btnElement) {
  // Sync active states on buttons
  if (btnElement) {
    document.querySelectorAll('#statsBtnContainer .stats-btn').forEach(btn => btn.classList.remove('active'));
    btnElement.classList.add('active');
  }

  const container = document.getElementById('statsDisplayContainer');
  container.innerHTML = ''; // Wipe out existing components

  let targetMapData;
  if (type === 'mcc1all') targetMapData = mcc1all;
  else if (type === 'mcc2all') targetMapData = mcc2all;
  else if (type === 'mcc3') targetMapData = mcc3;
  else if (type === 'mcc4') targetMapData = mcc4;

  if (!targetMapData) return;

  // Convert map profiles to standard structured key-value configurations
  const structuredObj = tobj([...targetMapData]);

  // Construct accordion blocks for every parsed sub-branch element
  for (let [keyCode, phrases] of Object.entries(structuredObj)) {
    if (!phrases || phrases.length === 0) continue;

    const wrapper = document.createElement('div');
    wrapper.className = 'accordion-wrapper';

    // Header displaying context labels and array counts
    const header = document.createElement('div');
    header.className = 'accordion-header';
    header.innerHTML = `<span>${branchName} (${keyCode}) — Count: ${phrases.length}</span>`;

    // Interactive expansion toggle control
    header.onclick = () => {
      wrapper.classList.toggle('open');
    };

    // Body area containing words
    const content = document.createElement('div');
    content.className = 'accordion-content';

    phrases.forEach(word => {
      const wordSpan = document.createElement('span');
      wordSpan.className = 'stats-word-item';
      wordSpan.textContent = word;
      
      // Interactive link back into the live workflow Editor tab interface
      wordSpan.onclick = (e) => {
        e.stopPropagation(); // Stop parent accordion event delegation loop hooks
        jumpToWordEditor(word);
      };
      content.appendChild(wordSpan);
    });

    wrapper.appendChild(header);
    wrapper.appendChild(content);
    container.appendChild(wrapper);
  }
}

// Redirects workflow focus straight back into the main text input workbench viewport
function jumpToWordEditor(word) {
  switchTab('edit');
  const inputEl = document.getElementById('codeInput');
  if (inputEl) {
    inputEl.value = word;
    handleEditSubmit();
  }
}

// Overwrite the core layout switch tab function to support automated parsing refreshes
function switchTab(tab) {
  document.getElementById('tabEdit').classList.toggle('active', tab === 'edit');
  document.getElementById('tabStats').classList.toggle('active', tab === 'stats');
  document.getElementById('contentEdit').classList.toggle('active', tab === 'edit');
  document.getElementById('contentStats').classList.toggle('active', tab === 'stats');

  // Automatically recalculate data states whenever user shifts view focus back onto Statistics
  if (tab === 'stats') {
    // Determine which filter button is active or fallback to default
    const activeBtn = document.querySelector('#statsBtnContainer .stats-btn.active') || document.getElementById('btnMcc1All');
    if (activeBtn) {
      activeBtn.click();
    }
  }
}

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

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

帖子 ejsoon »

把domu1.html的功能合並至domchar5741.html中,按以下要求進行:
一,把#output和#download,擴展為一個美觀的概覽,它的項目為:單字數量(cchardefine 行數),尹末去重(開頭不是z或x,後面含有z的行數),中文標點(開頭為x但第二個字母不為z的行數),英文符號(開頭為xz的行數),詞組數量(mcc0string 行數),詞組去重(所有以z開頭的行數減去mcc0string 行數)。下載按鈕添加美觀的css。

二,當文本載入時,先執行domu1.html中的邏輯,再執行domchar5741.html中的邏輯。

三,當在codeInput輸入文字時,不再在英文字母前自動加z。如果所輸入的是英文字母且開頭不是z,或者輸入的是一個漢字,則是單字模式,即展示這個字所在的序列的重碼情況,當點擊其中一個字,一樣會高亮,點擊另一個字,將移動到它的前面或後面。不同的是,單字模式將不會「擠出前幾名」,沒有前幾位的概念,它將一直保持在那裡。

例如輸入「轟」(或者jjjj),則下方將顯示:
j 都十
jj 幹艹卄廾
jjj 轉
jjjj 轟

當我點擊「轉」,再點擊「幹」,則jj一行將顯示為「轉幹艹卄廾」。

四,把domu1.html中的統計功能,加至Statistics欄,其最左邊增加一個切換按鈕「重碼統計」,下方將是domu1.html中的統計結果。「重碼統計」將是Statistics欄默認顯示的項目。

「總重碼統計」將改為:0重、1重、2重……。

「分類重碼統計」將改為:碼長1:0重、1重、2重……;碼長2:0重、1重、2重……;碼長3:0重、1重、2重……;碼長4:0重、1重、2重……。

將「重碼結果」和「去重結果」標題後面加上數量,去掉「下載」。把四個「超出字」合並成一個,數量也合並。

美化以上元素的css,不要用原生的html樣式。

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

代码: 全选

把domu1.html的功能合並至domchar5741.html中,按以下要求進行:
一,把#output和#download,擴展為一個美觀的概覽,它的項目為:單字數量(cchardefine 行數),尹末去重(開頭不是z或x,後面含有z的行數),中文標點(開頭為x但第二個字母不為z的行數),英文符號(開頭為xz的行數),詞組數量(mcc0string 行數),詞組去重(所有以z開頭的行數減去mcc0string 行數)。下載按鈕添加美觀的css。

二,當文本載入時,先執行domu1.html中的邏輯,再執行domchar5741.html中的邏輯。

三,當在codeInput輸入文字時,不再在英文字母前自動加z。如果所輸入的是英文字母且開頭不是z,或者輸入的是一個漢字,則是單字模式,即展示這個字所在的序列的重碼情況,當點擊其中一個字,一樣會高亮,點擊另一個字,將移動到它的前面或後面。不同的是,單字模式將不會「擠出前幾名」,沒有前幾位的概念,它將一直保持在那裡。

例如輸入「轟」(或者jjjj),則下方將顯示:
j	都十
jj	幹艹卄廾
jjj	轉
jjjj	轟

當我點擊「轉」,再點擊「幹」,則jj一行將顯示為「轉幹艹卄廾」。

四,把domu1.html中的統計功能,加至Statistics欄,其最左邊增加一個切換按鈕「重碼統計」,下方將是domu1.html中的統計結果。「重碼統計」將是Statistics欄默認顯示的項目。

「總重碼統計」將改為:0重、1重、2重……。

「分類重碼統計」將改為:碼長1:0重、1重、2重……;碼長2:0重、1重、2重……;碼長3:0重、1重、2重……;碼長4:0重、1重、2重……。

將「重碼結果」和「去重結果」標題後面加上數量,去掉「下載」。把四個「超出字」合並成一個,數量也合並。

美化以上元素的css,不要用原生的html樣式。

只需給出所要修改的地方,所有的代碼和注釋都要使用英文。
附件
domu1.7z
(3.81 KiB) 已下载 27 次
domchar5741.7z
(9.47 KiB) 已下载 27 次
https://ejsoon.vip/
弈趣極光:享受思維樂趣
头像
ejsoon
一枝独秀
一枝独秀
帖子: 5914
注册时间: 2022年 11月 18日 17:36
联系:

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

帖子 ejsoon »

gemini pro開始發力:

代码: 全选

Here are the specific modifications needed to integrate the functionality from `domu1.html` into `domchar5741.html`, following your requirements.

### 1. CSS Modifications

Add the following CSS inside your `<style>` tag to beautify the new overview panel, the statistics, and the download buttons.

```css
/* --- Overview Panel Styles --- */
.overview-panel {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
  background: #f4f6f8;
  padding: 15px;
  border-radius: 8px;
  border: 1px solid #e0e0e0;
  margin-bottom: 20px;
}
.stat-card {
  flex: 1 1 calc(33.333% - 15px);
  min-width: 150px;
  background: #fff;
  padding: 15px;
  border-radius: 6px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
  text-align: center;
}
.stat-card-title {
  font-size: 13px;
  color: #666;
  margin-bottom: 8px;
}
.stat-card-value {
  font-size: 24px;
  font-weight: bold;
  color: #2196f3;
}

/* --- Download Button Styles --- */
.download-btn-container {
  margin-top: 20px;
  text-align: center;
}
.beautiful-download-btn {
  display: inline-block;
  padding: 12px 24px;
  background: linear-gradient(135deg, #4caf50, #2e7d32);
  color: #fff !important;
  text-decoration: none;
  font-size: 16px;
  font-weight: bold;
  border-radius: 50px;
  box-shadow: 0 4px 6px rgba(76, 175, 80, 0.3);
  transition: transform 0.2s, box-shadow 0.2s;
}
.beautiful-download-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 12px rgba(76, 175, 80, 0.4);
}

/* --- Stats & Textarea Styles --- */
.dup-stats-grid {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}
.dup-stat-box {
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 6px;
  padding: 15px;
  flex: 1;
  min-width: 200px;
}
.dup-stat-box h4 {
  margin-top: 0;
  color: #333;
  border-bottom: 2px solid #2196f3;
  padding-bottom: 5px;
}
.dup-stat-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.dup-stat-list li {
  padding: 4px 0;
  border-bottom: 1px dashed #eee;
  display: flex;
  justify-content: space-between;
}
.dup-stat-list li span:first-child {
  color: #666;
}
.dup-stat-list li span:last-child {
  font-weight: bold;
}
.result-textarea {
  width: 100%;
  height: 120px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 10px;
  font-family: monospace;
  resize: vertical;
  box-sizing: border-box;
}
.textarea-header {
  font-weight: bold;
  margin: 15px 0 5px 0;
  color: #333;
}

```

### 2. HTML Modifications

Replace the original `<div id="output"></div> <div id="download"></div>` and the entire `<div id="contentStats">` structure with the following:

```html
<div id="overviewPanel" class="overview-panel" style="display: none;">
  <div class="stat-card"><div class="stat-card-title">Single Word Count</div><div class="stat-card-value" id="statSingleWord">0</div></div>
  <div class="stat-card"><div class="stat-card-title">Yinmo Deduplication</div><div class="stat-card-value" id="statYinmo">0</div></div>
  <div class="stat-card"><div class="stat-card-title">Chinese Punctuation</div><div class="stat-card-value" id="statChiPunc">0</div></div>
  <div class="stat-card"><div class="stat-card-title">English Symbol</div><div class="stat-card-value" id="statEngSym">0</div></div>
  <div class="stat-card"><div class="stat-card-title">Phrase Count</div><div class="stat-card-value" id="statPhrase">0</div></div>
  <div class="stat-card"><div class="stat-card-title">Phrase Deduplication</div><div class="stat-card-value" id="statPhraseDedup">0</div></div>
</div>
<div id="download" class="download-btn-container"></div>

```

```html
<div id="contentStats" class="tab-content">
  <div class="stats-buttons" id="statsBtnContainer">
    <button class="stats-btn active" id="btnDupStats" onclick="renderStatsSection('dupstats', '重碼統計', this)">重碼統計</button>
    <button class="stats-btn" id="btnMcc1All" onclick="renderStatsSection('mcc1all', '二碼高頻', this)">二碼高頻</button>
    <button class="stats-btn" id="btnMcc2All" onclick="renderStatsSection('mcc2all', '三碼常用', this)">三碼常用</button>
    <button class="stats-btn" id="btnMcc3" onclick="renderStatsSection('mcc3', '四碼詞語', this)">四碼詞語</button>
    <button class="stats-btn" id="btnMcc4" onclick="renderStatsSection('mcc4', '多字詞語', this)">多字詞語</button>
  </div>

  <div id="statsDisplayContainer" style="margin-top: 20px"></div>
  
  <div id="dupStatsContainer" style="display: none;">
    <div class="dup-stats-grid">
      <div class="dup-stat-box">
        <h4>Total Duplication Stats</h4>
        <ul class="dup-stat-list" id="totalDupList"></ul>
      </div>
      <div class="dup-stat-box" id="lenDupStatsContainer">
        </div>
    </div>

    <div class="textarea-header">Duplication Results (<span id="dupResultCount">0</span>)</div>
    <textarea id="collapseMultipleText" class="result-textarea" readonly></textarea>

    <div class="textarea-header">Deduplication Results (<span id="dedupResultCount">0</span>)</div>
    <textarea id="gzhahaText" class="result-textarea" readonly></textarea>

    <div class="textarea-header">Out of Bounds - Merged (<span id="outOfBoundsCount">0</span>)</div>
    <textarea id="outOfBoundsText" class="result-textarea" readonly></textarea>
  </div>
</div>

```

### 3. JavaScript Modifications

Add these global variables at the top of your `<script>` block:

```javascript
let isSingleWordMode = false;
let charDefCodeMap = new Map(); // code -> [chars]
let singleWordActiveCode = ''; // current single word root code

```

Replace the `handleFile` and `processContent` logic with the combined logic:

```javascript
// --- Integrated File Handling & Logic Execution ---
function handleFile(event) {
  const file = event.target.files[0];
  if (!file) return;

  document.getElementById('selectcinfile').classList.add('hide');

  const reader = new FileReader();
  reader.onload = function (e) {
    filecontent = e.target.result;
    
    // Execute domu1.html logic first, which processes stats and modifies raw text
    filecontent = runDomu1LogicAndGetIntegratedText(filecontent);
    
    // Execute domchar5741.html logic
    processContent();
  };
  reader.readAsText(file);
}

function runDomu1LogicAndGetIntegratedText(rawText) {
  let lines = rawText.split('\n').map(l => l.replace(/,.+$/g, '').trimRight());
  let inChardef = false;
  let endIndex = lines.length;
  charDefCodeMap.clear();

  // Find boundaries
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    if (line === '%chardef begin') { inChardef = true; continue; }
    if (!inChardef) continue;
    if (line === '%chardef end') { endIndex = i; break; }
    const code = line.split('\t')[0];
    if (code && /[^z]z/.test(code)) { endIndex = i; break; }
  }
  
  if (endIndex === lines.length) {
    inChardef = false;
    for (let i = 0; i < lines.length; i++) {
      if (lines[i] === '%chardef begin') { inChardef = true; continue; }
      if (!inChardef) continue;
      const code = lines[i].split('\t')[0];
      if (code && code.startsWith('x')) { endIndex = i; break; }
    }
  }

  // Populate charDefCodeMap
  inChardef = false;
  for (let i = 0; i < endIndex; i++) {
    const line = lines[i];
    if (line === '%chardef begin') { inChardef = true; continue; }
    if (!inChardef) continue;

    const [code, char] = line.split('\t');
    if (code && char && /^[a-z]{1,4}$/.test(code)) {
      if (!charDefCodeMap.has(code)) charDefCodeMap.set(code, []);
      charDefCodeMap.get(code).push(char);
    }
  }

  // Sort code map
  charDefCodeMap = new Map([...charDefCodeMap.entries()].sort());

  // 1. Total Duplication Stats (0 repeats = 1 item, 1 repeat = 2 items...)
  const dupStats = new Map();
  for (const chars of charDefCodeMap.values()) {
    const repeats = chars.length - 1; // 0 repeats, 1 repeat...
    dupStats.set(repeats, (dupStats.get(repeats) || 0) + 1);
  }
  const maxDup = Math.max(0, ...dupStats.keys());
  let totalHtml = '';
  for (let i = 0; i <= maxDup; i++) {
    totalHtml += `<li><span>${i} repeats:</span> <span>${dupStats.get(i) || 0}</span></li>`;
  }
  document.getElementById('totalDupList').innerHTML = totalHtml;

  // 2. Length Stats
  const lenStats = [new Map(), new Map(), new Map(), new Map()];
  for (const [code, chars] of charDefCodeMap) {
    const lenIdx = Math.min(code.length - 1, 3);
    const repeats = chars.length - 1;
    const map = lenStats[lenIdx];
    map.set(repeats, (map.get(repeats) || 0) + 1);
  }
  
  let lenHtml = '';
  lenStats.forEach((map, i) => {
    const maxReps = Math.max(0, ...map.keys());
    lenHtml += `<h4>Code Length ${i + 1}</h4><ul class="dup-stat-list">`;
    for (let j = 0; j <= maxReps; j++) {
      lenHtml += `<li><span>${j} repeats:</span> <span>${map.get(j) || 0}</span></li>`;
    }
    lenHtml += `</ul><br>`;
  });
  document.getElementById('lenDupStatsContainer').innerHTML = lenHtml;

  // 3. Results & Deduplication
  const collapseLines = [];
  const gzhahaLines = [];
  const outOfBoundsLines = [];

  for (const [code, chars] of charDefCodeMap) {
    if (chars.length > 1) {
      collapseLines.push(`${code}\t${chars.join('')}`);
    }
    if (chars.length <= 1) continue;
    
    const len = code.length;
    chars.forEach((char, idx) => {
      if (idx === 0) return; // Skip first item (no dedup needed)
      if (len === 1 || len === 2) {
        const suffix = ['z', 'zz', 'zx', 'xz'][idx - 1] || '';
        if (suffix) gzhahaLines.push(`${code}${suffix}\t${char}`);
        else outOfBoundsLines.push(`${code}\t${char}`);
      } else if (len === 3) {
        if (idx === 1) gzhahaLines.push(`${code}z\t${char}`);
        else if (idx === 2) gzhahaLines.push(`${code.slice(0, 2)}z${code.slice(2)}\t${char}`);
        else if (idx === 3) gzhahaLines.push(`${code.slice(0, 1)}z${code.slice(1)}\t${char}`);
        else outOfBoundsLines.push(`${code}\t${char}`);
      } else if (len === 4) {
        outOfBoundsLines.push(`${code}\t${char}`);
      }
    });
  }

  document.getElementById('collapseMultipleText').value = collapseLines.join('\n');
  document.getElementById('dupResultCount').textContent = collapseLines.length;
  document.getElementById('gzhahaText').value = gzhahaLines.join('\n');
  document.getElementById('dedupResultCount').textContent = gzhahaLines.length;
  document.getElementById('outOfBoundsText').value = outOfBoundsLines.join('\n');
  document.getElementById('outOfBoundsCount').textContent = outOfBoundsLines.length;

  // 4. Integrate back into text (Inject gzhahaLines)
  let integratedLines = [...lines];
  let chardefBeginIdx = integratedLines.indexOf('%chardef begin');
  if (chardefBeginIdx > -1) {
    // Overwrite the block with sorted definitions
    let sortedBlock = [];
    for (const [code, chars] of charDefCodeMap) {
      chars.forEach(c => sortedBlock.push(`${code}\t${c}`));
    }
    integratedLines.splice(chardefBeginIdx + 1, endIndex - chardefBeginIdx - 1, ...sortedBlock);
    
    // Find where to insert gzhahaLines (before first 'x' code)
    let insertIdx = integratedLines.length;
    for (let i = chardefBeginIdx + 1; i < integratedLines.length; i++) {
      if (integratedLines[i].startsWith('x')) { insertIdx = i; break; }
      if (integratedLines[i] === '%chardef end') { insertIdx = i; break; }
    }
    if (gzhahaLines.length > 0) {
      integratedLines.splice(insertIdx, 0, ...gzhahaLines);
    }
  }

  return integratedLines.join('\n');
}

```

Replace the top sections of your existing `processContent` with the updated Overview Counters:

```javascript
// Add these counters inside processContent(remcc0 = '') before processing lines
let statSingleWord = 0;
let statYinmo = 0;
let statChiPunc = 0;
let statEngSym = 0;
let allZLineCount = 0;

// During your line parsing loop in processContent, add checks:
charDefLines.forEach(line => {
  const parts = line.split('\t');
  const code = parts[0];
  const char = parts[1];
  if (code && char) {
    cchardefine.set(char, code);
    statSingleWord++;
    if (!code.startsWith('z') && !code.startsWith('x') && code.includes('z')) statYinmo++;
    if (code.startsWith('x') && code[1] !== 'z') statChiPunc++;
    if (code.startsWith('xz')) statEngSym++;
    if (code.startsWith('z')) allZLineCount++;
  }
});

// After processing mcc0Lines, update the UI:
document.getElementById('overviewPanel').style.display = 'flex';
document.getElementById('statSingleWord').textContent = statSingleWord;
document.getElementById('statYinmo').textContent = statYinmo;
document.getElementById('statChiPunc').textContent = statChiPunc;
document.getElementById('statEngSym').textContent = statEngSym;
document.getElementById('statPhrase').textContent = mcc0Lines.length;
document.getElementById('statPhraseDedup').textContent = Math.max(0, allZLineCount - mcc0Lines.length);

// Update Download Button Style
const newFileName = `haha_${timestamp}.txt`;
document.getElementById('download').innerHTML =
  `<a href="${url}" class="beautiful-download-btn" download="${newFileName}">Download File (${size} bytes)</a>`;

```

Update the `handleEditSubmit` logic for Single Word Mode:

```javascript
// Replace inside handleEditSubmit()
let val = document.getElementById('codeInput').value.trim();
if (!val) return;
document.getElementById('codeInput').value = '';

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

let isEnglishCode = /^[a-zA-Z]+$/.test(val);

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

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

if (isSingleWordMode) {
  activeEditKeys.clear(); // Only show sequences for this code
  for (let i = 1; i <= singleWordActiveCode.length; i++) {
    activeEditKeys.add(singleWordActiveCode.substring(0, i));
  }
} 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;
    // ... (Keep existing Phrase Logic for val insertion)
  }
  // ... (Keep existing activeEditKeys populating for phrases)
}

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

```

Replace `renderEditArea` mapping logic to support Single Word Arrays:

```javascript
// Inside renderEditArea(), when building activeItems:
if (isSingleWordMode) {
  label = `${k} (Single Word)`;
  let originalItems = charDefCodeMap.get(k) || [];
  originalItems.forEach(w => activeItems.push({ w: w, cls: '' }));
  // Set original snapshot dynamically so the UI recognizes identity
  originalSnapshotState.set(k, [...originalItems]);
} else {
  // Existing Phrase logic for k === 'z', k.length === 2, etc.
}

```

Update `handleItemClick` to reorder inside `charDefCodeMap` for single words:

```javascript
// Inside handleItemClick(word), replace the swapping block with this:
if (isSingleWordMode) {
  // Find which code row this word belongs to within active keys
  let targetCode = Array.from(activeEditKeys).find(k => (charDefCodeMap.get(k) || []).includes(word));
  let sourceCode = Array.from(activeEditKeys).find(k => (charDefCodeMap.get(k) || []).includes(selectedEditItem));
  
  if (targetCode && targetCode === sourceCode) {
    let arr = charDefCodeMap.get(targetCode);
    let originalIdxA = arr.indexOf(selectedEditItem);
    let originalIdxB = arr.indexOf(word);
    
    if (originalIdxA > -1 && originalIdxB > -1 && originalIdxA !== originalIdxB) {
      arr.splice(originalIdxA, 1);
      let newIdxB = arr.indexOf(word);
      arr.splice(originalIdxA > originalIdxB ? newIdxB : newIdxB + 1, 0, selectedEditItem);
      justMovedItem = selectedEditItem;
      
      // Force UI refresh without triggering global file re-parse (single words stay in memory until export)
      renderEditArea();
    }
  }
  selectedEditItem = null;
} else {
  // Existing Phrase swap logic (lines.splice...)
}

```

Finally, replace `renderStatsSection` to handle the new tab:

```javascript
// Update renderStatsSection and switchTab logic
function renderStatsSection(type, branchName, btnElement) {
  if (btnElement) {
    document.querySelectorAll('#statsBtnContainer .stats-btn').forEach(btn => btn.classList.remove('active'));
    btnElement.classList.add('active');
  }

  const container = document.getElementById('statsDisplayContainer');
  const dupContainer = document.getElementById('dupStatsContainer');

  if (type === 'dupstats') {
    container.style.display = 'none';
    dupContainer.style.display = 'block';
    return;
  } else {
    container.style.display = 'block';
    dupContainer.style.display = 'none';
  }

  container.innerHTML = '';
  // ... (Keep existing mcc1all, mcc2all dictionary loops here)
}

function switchTab(tab) {
  document.getElementById('tabEdit').classList.toggle('active', tab === 'edit');
  document.getElementById('tabStats').classList.toggle('active', tab === 'stats');
  document.getElementById('contentEdit').classList.toggle('active', tab === 'edit');
  document.getElementById('contentStats').classList.toggle('active', tab === 'stats');

  if (tab === 'stats') {
    const activeBtn = document.querySelector('#statsBtnContainer .stats-btn.active') || document.getElementById('btnDupStats');
    if (activeBtn) activeBtn.click();
  }
}

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

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

帖子 ejsoon »

修正:
一,當codeInput輸入了一個漢字或者一個不以z開頭的英文編碼時,即單字模式時,其對比欄應該立即顯示出初始狀態,而不是點擊其中一個字才顯示。

二,當codeInput輸入了一個漢字時,下方對應的漢字應該高亮。如果輸入的是一個不以z開頭的英文編碼,下方對應的英文編碼應該高亮。

三,在單字模式中,如果點擊了一個漢字A,它將被選中,如果點擊另一個漢字B,如果漢字A處於漢字B的後面,則將把漢字A置於B之前,如果A處於B之前,則把A置於B之後。然後執行processContent。

四,在Statistics的重碼統計的out of Bounds,應先按位數排,也就是三位的全部都在四位的前面。

五,在Statistics的重碼統計中,把dup-stat-box下方的空間平均分成三份,放入Duplication Results、Deduplication Results、Out of Bounds,最終與lenDupStatsContainer等高。

Duplication Results、Deduplication Results、Out of Bounds這三個裡面的textarea改成list,放進一個可以縱向scroll的div中。list裡面的每一個編碼和每一個字都是可以點擊的,當點擊時,它將作為value填入左邊的Edit中的codeInput。

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

代码: 全选

修正:
一,當codeInput輸入了一個漢字或者一個不以z開頭的英文編碼時,即單字模式時,其對比欄應該立即顯示出初始狀態,而不是點擊其中一個字才顯示。

二,當codeInput輸入了一個漢字時,下方對應的漢字應該高亮。如果輸入的是一個不以z開頭的英文編碼,下方對應的英文編碼應該高亮。

三,在單字模式中,如果點擊了一個漢字A,它將被選中,如果點擊另一個漢字B,如果漢字A處於漢字B的後面,則將把漢字A置於B之前,如果A處於B之前,則把A置於B之後。然後執行processContent。

四,在Statistics的重碼統計的out of Bounds,應先按位數排,也就是三位的全部都在四位的前面。 

五,在Statistics的重碼統計中,把dup-stat-box下方的空間平均分成三份,放入Duplication Results、Deduplication Results、Out of Bounds,最終與lenDupStatsContainer等高。

Duplication Results、Deduplication Results、Out of Bounds這三個裡面的textarea改成list,放進一個可以縱向scroll的div中。list裡面的每一個編碼和每一個字都是可以點擊的,當點擊時,它將作為value填入左邊的Edit中的codeInput。

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

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

帖子 ejsoon »

gemini pro再發力:

代码: 全选

Here are the modifications required to fulfill your specifications. All provided code and comments are entirely in English.

### 1. Update CSS Styles

Add the following styles to your `<style>` block to support the target highlights and the new 3-column scrollable lists:

```css
      /* --- Target Code Title Highlight (For Single Word English Codes) --- */
      .target-code-title {
        color: #d32f2f;
        background-color: #ffebee;
        padding: 2px 6px;
        border-radius: 4px;
        border: 1px solid #d32f2f;
        display: inline-block;
      }

      /* --- 3-Column Stats Results Layout --- */
      .results-three-col {
        display: flex;
        gap: 15px;
        height: 450px;
        margin-top: 20px;
        margin-bottom: 20px;
      }
      .result-col {
        flex: 1;
        display: flex;
        flex-direction: column;
        background: #fff;
        border: 1px solid #ddd;
        border-radius: 6px;
        padding: 10px;
      }
      .result-scroll-list {
        flex: 1;
        overflow-y: auto;
        list-style: none;
        padding: 10px;
        margin: 0;
        font-family: monospace;
        border: 1px solid #eee;
        border-radius: 4px;
        background: #fafafa;
      }
      .result-scroll-list li {
        padding: 6px 0;
        border-bottom: 1px dashed #ccc;
      }
      .clickable-stat {
        cursor: pointer;
        color: #1976d2;
        transition: color 0.2s;
        padding: 0 2px;
      }
      .clickable-stat:hover {
        color: #d32f2f;
        text-decoration: underline;
      }

```

### 2. Replace HTML Layout for Stats Textareas

Find the three `<textarea>` elements and their headers in `#dupStatsContainer` and replace them with this new 3-column grid structure:

**Replace this block:**

```html
          <div class="textarea-header">Duplication Results (<span id="dupResultCount">0</span>)</div>
          <textarea id="collapseMultipleText" class="result-textarea" readonly></textarea>

          <div class="textarea-header">Deduplication Results (<span id="dedupResultCount">0</span>)</div>
          <textarea id="gzhahaText" class="result-textarea" readonly></textarea>

          <div class="textarea-header">Out of Bounds - Merged (<span id="outOfBoundsCount">0</span>)</div>
          <textarea id="outOfBoundsText" class="result-textarea" readonly></textarea>

```

**With this block:**

```html
          <div class="results-three-col">
            <div class="result-col">
              <div class="textarea-header">Duplication Results (<span id="dupResultCount">0</span>)</div>
              <ul id="collapseMultipleText" class="result-scroll-list"></ul>
            </div>
            <div class="result-col">
              <div class="textarea-header">Deduplication Results (<span id="dedupResultCount">0</span>)</div>
              <ul id="gzhahaText" class="result-scroll-list"></ul>
            </div>
            <div class="result-col">
              <div class="textarea-header">Out of Bounds (<span id="outOfBoundsCount">0</span>)</div>
              <ul id="outOfBoundsText" class="result-scroll-list"></ul>
            </div>
          </div>

```

### 3. Update JavaScript `handleEditSubmit()`

Ensure `currentTargetWord` gets set properly during single-word input so characters highlight immediately.

**Replace the single word detection block:**

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

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

```

**With this block:**

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

```

### 4. Update JavaScript `renderEditArea()`

Fix the shadowing bug so the baseline shows immediately, and add code header highlighting.

**Replace the `isSingleWordMode` logic inside `sortedKeys.forEach`:**

```javascript
          if (isSingleWordMode) {
            label = `${k} (Single Word)`;
            let originalItems = charDefCodeMap.get(k) || [];
            originalItems.forEach(w => activeItems.push({ w: w, cls: '' }));
            // Set original snapshot dynamically so the UI recognizes identity
            originalSnapshotState.set(k, [...originalItems]);
          }

```

**With this block:**

```javascript
          if (isSingleWordMode) {
            label = `${k} (Single Word)`;
            let mapItems = charDefCodeMap.get(k) || [];
            
            // Populate snapshot if empty to display baseline instantly
            if (!originalSnapshotState.has(k)) {
               originalSnapshotState.set(k, [...mapItems]);
            }
            
            // Assign outer scope variable without 'let' keyword
            originalItems = originalSnapshotState.get(k) || [];
            mapItems.forEach(w => activeItems.push({ w: w, cls: '' }));
          }

```

**Immediately below that in `renderEditArea()`, replace the title rendering block:**

```javascript
            let activeTitle = document.createElement('div');
            activeTitle.textContent = label;
            activeTitle.style.fontWeight = 'bold';
            activeTitle.style.marginBottom = '8px';
            activeRowDiv.appendChild(activeTitle);

```

**With this block:**

```javascript
            let activeTitle = document.createElement('div');
            activeTitle.textContent = label;
            activeTitle.style.fontWeight = 'bold';
            activeTitle.style.marginBottom = '8px';
            
            // Apply highlight class if input is English code
            if (isSingleWordMode && singleWordActiveCode === k && !currentTargetWord) {
                activeTitle.className = 'target-code-title';
            }
            
            activeRowDiv.appendChild(activeTitle);

```

### 5. Update JavaScript `handleItemClick(word)`

Mutate the underlying `filecontent` string directly for single words and run `processContent()`.

**Replace the `if (targetCode && targetCode === sourceCode)` inner block:**

```javascript
              if (targetCode && targetCode === sourceCode) {
                let arr = charDefCodeMap.get(targetCode);
                let originalIdxA = arr.indexOf(selectedEditItem);
                let originalIdxB = arr.indexOf(word);

                if (originalIdxA > -1 && originalIdxB > -1 && originalIdxA !== originalIdxB) {
                  arr.splice(originalIdxA, 1);
                  let newIdxB = arr.indexOf(word);
                  arr.splice(originalIdxA > originalIdxB ? newIdxB : newIdxB + 1, 0, selectedEditItem);
                  justMovedItem = selectedEditItem;

                  // Force UI refresh without triggering global file re-parse (single words stay in memory until export)
                  renderEditArea();
                }
              }

```

**With this block:**

```javascript
              if (targetCode && targetCode === sourceCode) {
                let arr = charDefCodeMap.get(targetCode);
                let originalIdxA = arr.indexOf(selectedEditItem);
                let originalIdxB = arr.indexOf(word);

                if (originalIdxA > -1 && originalIdxB > -1 && originalIdxA !== originalIdxB) {
                  arr.splice(originalIdxA, 1);
                  let newIdxB = arr.indexOf(word);
                  arr.splice(originalIdxA > originalIdxB ? newIdxB : newIdxB + 1, 0, selectedEditItem);
                  justMovedItem = selectedEditItem;

                  // Rebuild %chardef block in filecontent directly to persist single word moves
                  let linesFile = filecontent.split('\n');
                  let chardefBeginIdx = linesFile.indexOf('%chardef begin');
                  let chardefEndIdx = -1;
                  let inDef = false;
                  
                  for (let i = 0; i < linesFile.length; i++) {
                      if (linesFile[i] === '%chardef begin') { inDef = true; continue; }
                      if (!inDef) continue;
                      if (linesFile[i] === '%chardef end' || linesFile[i].startsWith('x') || /[^z]z/.test(linesFile[i].split('\t')[0])) {
                          chardefEndIdx = i;
                          break;
                      }
                  }
                  
                  if (chardefBeginIdx > -1 && chardefEndIdx > -1) {
                      let newBlock = [];
                      for (let [c, chars] of charDefCodeMap) {
                          chars.forEach(ch => newBlock.push(`${c}\t${ch}`));
                      }
                      linesFile.splice(chardefBeginIdx + 1, chardefEndIdx - chardefBeginIdx - 1, ...newBlock);
                      filecontent = linesFile.join('\n');
                      
                      // Execute global processing phase to synchronize state
                      processContent(); 
                  }
                  
                  renderEditArea();
                }
              }

```

### 6. Update JavaScript `runDomu1LogicAndGetIntegratedText(rawText)`

Sort the out of bounds line, and convert the strings to HTML lists containing clickable elements.

**Replace the output injection block at the bottom of the function:**

```javascript
        document.getElementById('collapseMultipleText').value = collapseLines.join('\n');
        document.getElementById('dupResultCount').textContent = collapseLines.length;
        document.getElementById('gzhahaText').value = gzhahaLines.join('\n');
        document.getElementById('dedupResultCount').textContent = gzhahaLines.length;
        document.getElementById('outOfBoundsText').value = outOfBoundsLines.join('\n');
        document.getElementById('outOfBoundsCount').textContent = outOfBoundsLines.length;

```

**With this block:**

```javascript
        // Sort Out of Bounds by length first, then alphabetically
        outOfBoundsLines.sort((a, b) => {
            let codeA = a.split('\t')[0];
            let codeB = b.split('\t')[0];
            if (codeA.length !== codeB.length) return codeA.length - codeB.length;
            return codeA.localeCompare(codeB);
        });

        // Helper function to map data into HTML list elements with clickable triggers
        const createClickableList = (linesArray) => {
            return linesArray.map(line => {
                let parts = line.split('\t');
                if (parts.length === 2) {
                    let codeHtml = `<span class="clickable-stat" onclick="jumpToWordEditor('${parts[0]}')">${parts[0]}</span>`;
                    let charsHtml = parts[1].split('').map(ch => 
                        `<span class="clickable-stat" onclick="jumpToWordEditor('${ch}')">${ch}</span>`
                    ).join('');
                    return `<li>${codeHtml}  ${charsHtml}</li>`;
                }
                return `<li>${line}</li>`;
            }).join('');
        };

        document.getElementById('collapseMultipleText').innerHTML = createClickableList(collapseLines);
        document.getElementById('dupResultCount').textContent = collapseLines.length;
        
        document.getElementById('gzhahaText').innerHTML = createClickableList(gzhahaLines);
        document.getElementById('dedupResultCount').textContent = gzhahaLines.length;
        
        document.getElementById('outOfBoundsText').innerHTML = createClickableList(outOfBoundsLines);
        document.getElementById('outOfBoundsCount').textContent = outOfBoundsLines.length;

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

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

帖子 ejsoon »

修正:
一,在單字模式中,點擊一個漢字A,它將被選中,這時再點擊另一個漢字B,如果漢字A處於漢字B的後面,則將把漢字A置於B之前(data中的位置),如果A處於B之前,則把A置於B之後(data中的位置)。然後執行processContent,刷新當前的ui邏輯,包括剛移動的字高亮,操作log及undo-redo等。

二,在Statistics的重碼統計中,把lenDupStatsContainer內的四組數據横著放,當屏寬較寬時,它跟dup-stat-box横向並列,當屏寬稍窄時(如小於900px),則dup-stat-box自佔一行,lenDupStatsContainer在其下方。當屏寬很窄時,每一欄都佔一行,包括results-three-col內的三欄也都是每個佔一行。

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

代码: 全选

修正:
一,在單字模式中,點擊一個漢字A,它將被選中,這時再點擊另一個漢字B,如果漢字A處於漢字B的後面,則將把漢字A置於B之前(data中的位置),如果A處於B之前,則把A置於B之後(data中的位置)。然後執行processContent,刷新當前的ui邏輯,包括剛移動的字高亮,操作log及undo-redo等。

二,在Statistics的重碼統計中,把lenDupStatsContainer內的四組數據横著放,當屏寬較寬時,它跟dup-stat-box横向並列,當屏寬稍窄時(如小於900px),則dup-stat-box自佔一行,lenDupStatsContainer在其下方。當屏寬很窄時,每一欄都佔一行,包括results-three-col內的三欄也都是每個佔一行。

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

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

帖子 ejsoon »

gemini pro換了thinking:

代码: 全选

Here are the required modifications to implement the changes. All code modifications and comments are written in English.

## 1. Single Word Mode Selection & Undo-Redo Integration

### Update `getSystemState`

To ensure that single word mode movements are correctly tracked by the undo/redo and change detection system, update `getSystemState()` to include data from `charDefCodeMap`.

Replace the existing `getSystemState` function with the following:

```javascript
function getSystemState() {
  let state = new Map();
  state.set('z', mcc0.get('z') || []);
  for (let [k, v] of mcc1all) state.set(k, v.slice(0, 5));
  for (let [k, v] of mcc2all) state.set(k, v.slice(0, 4));
  for (let [k, v] of mcc3) {
    if (!state.has(k)) state.set(k, []);
    state.get(k).push(...v);
  }
  for (let [k, v] of mcc4) {
    if (!state.has(k)) state.set(k, []);
    state.get(k).push(...v);
  }
  // Track single word definitions state for change detection
  for (let [k, v] of charDefCodeMap) {
    state.set(k, [...v]);
  }
  return state;
}

```

---

### Update Undo, Redo, Log, and State Functions

Modify the state management functions to store deep copies of both `filecontent` and `charDefCodeMap` so single character shifts can be rolled back or replayed seamlessly.

Replace `saveStateForUndo`, `handleUndo`, `handleRedo`, and `jumpToLog` with the following implementation:

```javascript
function saveStateForUndo(description = 'Manual Edit') {
  let currentLines = mcc0string.split('\n').filter(l => l.trim() !== '');
  let charDefMapCopy = new Map();
  for (let [k, v] of charDefCodeMap.entries()) {
    charDefMapCopy.set(k, [...v]);
  }
  undoStack.push({
    lines: currentLines,
    filecontent: filecontent,
    charDefCodeMap: charDefMapCopy,
    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 charDefMapCopy = new Map();
  for (let [k, v] of charDefCodeMap.entries()) {
    charDefMapCopy.set(k, [...v]);
  }
  let lastActionDesc = undoStack[undoStack.length - 1].desc;
  redoStack.push({
    lines: currentLines,
    filecontent: filecontent,
    charDefCodeMap: charDefMapCopy,
    desc: `Undo: ${lastActionDesc}`
  });

  let prevState = undoStack.pop();
  filecontent = prevState.filecontent;
  charDefCodeMap = prevState.charDefCodeMap;

  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 charDefMapCopy = new Map();
  for (let [k, v] of charDefCodeMap.entries()) {
    charDefMapCopy.set(k, [...v]);
  }
  let nextState = redoStack.pop();
  undoStack.push({
    lines: currentLines,
    filecontent: filecontent,
    charDefCodeMap: charDefMapCopy,
    desc: nextState.desc
  });

  filecontent = nextState.filecontent;
  charDefCodeMap = nextState.charDefCodeMap;

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

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

  let targetState = undoStack[index];
  undoStack = undoStack.slice(0, index + 1);

  filecontent = targetState.filecontent;
  charDefCodeMap = targetState.charDefCodeMap;

  let oldState = getSystemState();
  processContent(JSON.stringify(targetState.lines));
  detectAndRenderChanges(oldState, getSystemState());

  justMovedItem = null;
  redoStack = []; // Clear redo stack on manual timeline jump
  renderActionLogs();
}

```

---

### Update Single Word Swapping inside `handleItemClick`

Update the `isSingleWordMode` processing condition inside `handleItemClick(word)` to include logging, `processContent()` synchronization, change state rendering, and highlighting.

Replace the `if (isSingleWordMode) { ... }` block inside `handleItemClick` with this code:

```javascript
if (isSingleWordMode) {
  // Find which code row this word belongs to within active keys
  let targetCode = Array.from(activeEditKeys).find(k => (charDefCodeMap.get(k) || []).includes(word));
  let sourceCode = Array.from(activeEditKeys).find(k =>
    (charDefCodeMap.get(k) || []).includes(selectedEditItem)
  );

  if (targetCode && targetCode === sourceCode) {
    let arr = charDefCodeMap.get(targetCode);
    let originalIdxA = arr.indexOf(selectedEditItem);
    let originalIdxB = arr.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}`);

      arr.splice(originalIdxA, 1);
      let newIdxB = arr.indexOf(word);
      arr.splice(originalIdxA > originalIdxB ? newIdxB : newIdxB + 1, 0, selectedEditItem);
      justMovedItem = selectedEditItem;

      // Rebuild %chardef block in filecontent directly to persist single word moves
      let linesFile = filecontent.split('\n');
      let chardefBeginIdx = linesFile.indexOf('%chardef begin');
      let chardefEndIdx = -1;
      let inDef = false;

      for (let i = 0; i < linesFile.length; i++) {
        if (linesFile[i] === '%chardef begin') {
          inDef = true;
          continue;
        }
        if (!inDef) continue;
        if (
          linesFile[i] === '%chardef end' ||
          linesFile[i].startsWith('x') ||
          /[^z]z/.test(linesFile[i].split('\t')[0])
        ) {
          chardefEndIdx = i;
          break;
        }
      }

      if (chardefBeginIdx > -1 && chardefEndIdx > -1) {
        let newBlock = [];
        for (let [c, chars] of charDefCodeMap) {
          chars.forEach(ch => newBlock.push(`${c}\t${ch}`));
        }
        linesFile.splice(chardefBeginIdx + 1, chardefEndIdx - chardefBeginIdx - 1, ...newBlock);
        filecontent = linesFile.join('\n');

        // Execute global processing phase to synchronize state and refresh UI structures
        processContent();
        let newState = getSystemState();
        detectAndRenderChanges(oldState, newState);
      }
      renderEditArea();
    }
  }
  selectedEditItem = null;
}

```

---

## 2. Statistics Tab Responsive Duplication Layout

### Update HTML Structure

Remove the class `dup-stat-box` from `lenDupStatsContainer` to handle column divisions cleanly via CSS grid/flex structures.

Find this section in the HTML file:

```html
<div class="dup-stat-box" id="lenDupStatsContainer"></div>

```

Replace it with:

```html
<div id="lenDupStatsContainer"></div>

```

---

### Update CSS Layout Rules

Add the styling definitions to support adaptive scaling for the length statistic boxes and the final 3-column analysis grid view across different viewports.

Add the following style rules inside the `<style>` block:

```css
/* --- Length Duplication Layout Adaptations --- */
#lenDupStatsContainer {
  display: flex;
  gap: 15px;
  flex-wrap: wrap;
  flex: 3;
  min-width: 500px;
}

.len-stat-block {
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 6px;
  padding: 15px;
  flex: 1;
  min-width: 140px;
  box-sizing: border-box;
}

.len-stat-block h4 {
  margin-top: 0;
  color: #333;
  border-bottom: 2px solid #2196f3;
  padding-bottom: 5px;
}

/* --- Media Queries for Responsive Scaling --- */
@media (max-width: 900px) {
  .dup-stats-grid {
    flex-direction: column;
  }
  #lenDupStatsContainer {
    width: 100%;
    min-width: 100%;
  }
}

@media (max-width: 600px) {
  #lenDupStatsContainer {
    flex-direction: column;
  }
  .results-three-col {
    flex-direction: column;
    height: auto;
  }
  .result-col {
    height: 300px;
  }
}

```

---

### Update JavaScript Rendering Logic for Length Stats

Modify the loop iteration statement generating the template content within `runDomu1LogicAndGetIntegratedText` to split the statistical blocks into individual nodes mapped to `.len-stat-block`.

Find the second step inside `runDomu1LogicAndGetIntegratedText(rawText)` that processes length statistics:

```javascript
let lenHtml = '';
lenStats.forEach((map, i) => {
  const maxReps = Math.max(0, ...map.keys());
  lenHtml += `<h4>Code Length ${i + 1}</h4><ul class="dup-stat-list">`;
  for (let j = 0; j <= maxReps; j++) {
    lenHtml += `<li><span>${j} repeats:</span> <span>${map.get(j) || 0}</span></li>`;
  }
  lenHtml += `</ul><br>`;
});
document.getElementById('lenDupStatsContainer').innerHTML = lenHtml;

```

Replace it with the following:

```javascript
let lenHtml = '';
lenStats.forEach((map, i) => {
  const maxReps = Math.max(0, ...map.keys());
  lenHtml += `<div class="len-stat-block"><h4>Code Length ${i + 1}</h4><ul class="dup-stat-list">`;
  for (let j = 0; j <= maxReps; j++) {
    lenHtml += `<li><span>${j} repeats:</span> <span>${map.get(j) || 0}</span></li>`;
  }
  lenHtml += `</ul></div>`;
});
document.getElementById('lenDupStatsContainer').innerHTML = lenHtml;

```
https://ejsoon.vip/
弈趣極光:享受思維樂趣
回复
  • 相似主题
    回复总数
    阅读次数
    最新帖子

在线用户

正浏览此版面之用户: Semrush [Bot] 和 1 访客