Here are the modifications to implement the requested improvements. All changes are contained within the <script> block.
Find the importExportedText(text) function and modify the beginning of it to handle the character replacements and the FEN fallback:
JavaScript
function importExportedText(text) {
text = text.trim();
// --- Improvement 1: Replace specific Chinese characters and fix ' r' to ' w' ---
text = text.replace(/车/g, '車')
.replace(/马/g, '馬')
.replace(/帅/g, '帥')
.replace(/将/g, '將')
.replace(/进/g, '進');
text = text.replace(/ r(\n|$)/g, ' w$1');
if (!text) {
renderRecordUI();
renderNoteUI();
updateToolHighlights();
return;
}
// Treat as JSON structure
if (text.startsWith('{')) {
// ... (keep existing JSON try-catch block) ...
try {
const data = JSON.parse(text);
function expand(node, parentFen) {
let dc, simfen, newNode;
if (node.m) {
dc = deriveCoordsFromMove(parentFen, node.m);
simfen = simulateMove(parentFen, dc);
newNode = {
fen: simfen,
move: node.m,
lastMove: dc,
c: node.c || "",
v: []
};
} else {
newNode = { fen: parentFen, c: node.c || "", v: [] };
}
if (node.v) {
newNode.v = node.v.map(child => expand(child, newNode.fen));
}
return newNode;
}
historyFEN = expand(data, data.fen);
initBranch();
currentStepIndex = 0;
renderRecordUI();
renderNoteUI();
jumpToStep(0);
saveStateToUndo();
return;
} catch (e) {
console.log("JSON parse failed, falling back to text format.");
}
}
// --- Improvement 2: FEN fallback to INITIAL_FEN if not found ---
const fenMatch = text.match(/([a-zA-Z0-9/]+ [wb])/);
let baseFen = INITIAL_FEN;
if (fenMatch) {
baseFen = fenMatch[1];
}
let lines = text.split('\n');
let movesList = [];
let currentIsRed = baseFen.includes(' w');
// ... (keep the rest of importExportedText unchanged) ...
Add these new state variables at the top of your <script> block (near let isExportTextMode = false;):
JavaScript
let isEditMode = false;
let editUndoStack = [];
let editRedoStack = [];
let selectedPalettePiece = null;
let isTile1Selected = true;
let editMiniBoardState = 0; // 0 = INITIAL_FEN, 1 = Kings only
let preEditFEN = '';
let editPickedPieceId = null;
Next, replace the svgElement.addEventListener('click', ...) with the following logic to intercept clicks during Edit Mode:
JavaScript
// --- 4. Click Event Handler ---
svgElement.addEventListener('click', (event) => {
if (isAnimating || isEditingComment || isExportTextMode) return;
const pt = svgElement.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
const cursorPt = pt.matrixTransform(svgElement.getScreenCTM().inverse());
const clickX = Math.floor(cursorPt.x);
const clickY = Math.floor(cursorPt.y);
if (clickX < 0 || clickX >= 432 || clickY < 0 || clickY >= 480) return;
const gridX_vis = Math.floor(clickX / 48);
const gridY_vis = Math.floor(clickY / 48);
const logicalPt = getLogicalCoords(gridX_vis, gridY_vis);
const gridX = logicalPt.x;
const gridY = logicalPt.y;
const clickedId = tileMap.get(`${gridX},${gridY}`);
let outputText = `(${clickX},${clickY}), (${gridX},${gridY})`;
if (clickedId) outputText += ` - ID: ${clickedId}`;
outputElement.textContent = outputText;
// --- Handle Edit Mode Clicks ---
if (isEditMode) {
handleEditBoardClick(gridX, gridY, clickedId);
return;
}
// ... (Keep existing normal play click logic here) ...
if (!pickedPieceId) {
// ... (Normal logic) ...
Now, add the main logic for Edit Mode right above // --- 5. Step Controls & Tools Events ---:
JavaScript
// --- Edit Mode Core Logic ---
function getEditIconHTML(char, cssClass, widthPct) {
const tileId = pieceToHrefMap[char] || 'tile1';
const origTile = document.getElementById(tileId);
const defsContent = origTile ? origTile.outerHTML : '';
return `
<svg class="${cssClass}" data-char="${char}" viewBox="0 0 48 48" style="width: ${widthPct}; aspect-ratio: 1; cursor:pointer; box-sizing: border-box; border: 2px solid transparent; float: left;">
<defs>${defsContent}</defs>
<use id="edit-${char}" href="#${tileId}" fill="none" stroke="none" transform="translate(24,24) scale(1,1) rotate(0)"></use>
</svg>`;
}
function enterEditMode() {
isEditMode = true;
preEditFEN = boardToFEN();
editUndoStack = [preEditFEN];
editRedoStack = [];
isTile1Selected = true;
selectedPalettePiece = null;
editMiniBoardState = 0;
editPickedPieceId = null;
// Clear ongoing states
if (pickedPieceId) setPickup(pickedPieceId, false);
pickedPieceId = null;
const pieces = etboard.querySelectorAll('use');
pieces.forEach(el => {
el.removeAttribute('currentmove');
el.setAttribute('stroke', 'none');
el.removeAttribute('stroke-width');
});
const dot = document.getElementById('ejceesstartdot');
if (dot) {
dot.setAttribute('cx', initialDotCx);
dot.setAttribute('cy', initialDotCy);
}
// Inject Palette into ejceesrecord
const recordContainer = document.querySelector('.ejceesrecord');
recordContainer.innerHTML = '';
const row1 = ['R', 'N', 'n', 'r'];
const row2 = ['C', 'P', 'p', 'c'];
const row3 = ['B', 'A', 'a', 'b'];
let paletteHTML = '<div style="display:flex; justify-content:space-around; width:100%; margin-bottom:10px;">' + row1.map(c => getEditIconHTML(c, 'edit-palette-item', '11.11%')).join('') + '</div>';
paletteHTML += '<div style="display:flex; justify-content:space-around; width:100%; margin-bottom:10px;">' + row2.map(c => getEditIconHTML(c, 'edit-palette-item', '11.11%')).join('') + '</div>';
paletteHTML += '<div style="display:flex; justify-content:space-around; width:100%; margin-bottom:10px;">' + row3.map(c => getEditIconHTML(c, 'edit-palette-item', '11.11%')).join('') + '</div>';
recordContainer.innerHTML = paletteHTML;
// Inject Comment tools
const commentContainer = document.querySelector('.ejceescomment');
let commentHTML = '<div style="display:flex; justify-content:space-around; align-items:center; width:100%; margin-bottom:10px;">';
commentHTML += getEditIconHTML('tile1', 'edit-comment-item', '16.66%');
commentHTML += `<div class="ejceespbmini-container" style="width:16.66%; aspect-ratio:432/480; cursor:pointer; box-sizing:border-box; border:2px solid transparent;"></div>`;
commentHTML += '</div>';
commentHTML += '<div style="display:flex; justify-content:space-around; align-items:center; width:100%; margin-bottom:10px;">';
commentHTML += getEditIconHTML('K', 'edit-comment-item', '16.66%');
commentHTML += getEditIconHTML('k', 'edit-comment-item', '16.66%');
commentHTML += '</div>';
commentContainer.innerHTML = commentHTML;
// Clone board into mini board
const miniContainer = document.querySelector('.ejceespbmini-container');
const cloneSVG = document.querySelector('.ejceespb').cloneNode(true);
cloneSVG.setAttribute('class', 'ejceespbmini');
cloneSVG.style.width = '100%';
cloneSVG.style.height = '100%';
miniContainer.appendChild(cloneSVG);
updateMiniBoard();
// Buttons
const btnDiv = document.querySelector('.ejceestextbtn');
btnDiv.innerHTML = `
<div class="ejceestextbtninner">
<div class="note-btn btn-confirm" id="edit-confirm" title="Confirm">
<svg viewBox="0 0 24 24"><path fill="#4CAF50" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
</div>
<div class="note-btn btn-cancel" id="edit-cancel" title="Cancel">
<svg viewBox="0 0 24 24"><path fill="#F44336" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</div>
</div>
`;
document.getElementById('edit-confirm').addEventListener('click', exitEditModeConfirm);
document.getElementById('edit-cancel').addEventListener('click', exitEditModeCancel);
bindEditUIEvents();
updateEditUI();
updateToolHighlights();
}
function bindEditUIEvents() {
document.querySelectorAll('.edit-palette-item').forEach(item => {
item.addEventListener('click', () => {
if (item.classList.contains('disabled')) return;
const char = item.getAttribute('data-char');
selectedPalettePiece = (selectedPalettePiece === char) ? null : char;
updateEditUI();
});
});
document.querySelectorAll('.edit-comment-item').forEach(item => {
item.addEventListener('click', () => {
const char = item.getAttribute('data-char');
if (char === 'tile1') {
isTile1Selected = !isTile1Selected;
} else if (char === 'K' || char === 'k') {
currentTurn = (char === 'K') ? 'w' : 'b';
saveEditState();
}
updateEditUI();
});
});
document.querySelector('.ejceespbmini-container').addEventListener('click', () => {
editMiniBoardState = 1 - editMiniBoardState;
const newFen = editMiniBoardState === 0 ? INITIAL_FEN : '4k4/9/9/9/9/9/9/9/9/4K4 w';
const savedTurn = currentTurn;
loadFEN(newFen);
currentTurn = savedTurn;
updateMiniBoard();
saveEditState();
updateEditUI();
});
}
function updateEditUI() {
const counts = {};
for (const id of tileMap.values()) counts[id[0]] = (counts[id[0]] || 0) + 1;
document.querySelectorAll('.edit-palette-item').forEach(item => {
const char = item.getAttribute('data-char');
const max = (char === 'p' || char === 'P') ? 5 : 2;
if ((counts[char] || 0) >= max) {
item.style.opacity = '0.36';
item.classList.add('disabled');
if (selectedPalettePiece === char) selectedPalettePiece = null;
} else {
item.style.opacity = '1';
item.classList.remove('disabled');
}
item.style.borderColor = (selectedPalettePiece === char) ? 'red' : 'transparent';
});
document.querySelectorAll('.edit-comment-item').forEach(item => {
const char = item.getAttribute('data-char');
if (char === 'tile1') item.style.borderColor = isTile1Selected ? 'red' : 'transparent';
else if (char === 'K') item.style.borderColor = (currentTurn === 'w') ? 'red' : 'transparent';
else if (char === 'k') item.style.borderColor = (currentTurn === 'b') ? 'red' : 'transparent';
});
}
function updateMiniBoard() {
const miniBoard = document.querySelector('.ejceespbmini');
if (!miniBoard) return;
const targetFen = editMiniBoardState === 1 ? '4k4/9/9/9/9/9/9/9/9/4K4' : INITIAL_FEN.split(' ')[0];
const pieces = miniBoard.querySelectorAll('use');
pieces.forEach(el => {
const href = el.getAttribute('href') || el.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
if (/^#(tile([2-9]|1[0-5]))$/.test(href)) el.remove();
});
const etboardMini = miniBoard.querySelector('.etboard') || miniBoard.querySelector('g:last-child');
targetFen.split('/').forEach((row, y) => {
let x = 0;
for (let i = 0; i < row.length; i++) {
const char = row[i];
if (/[0-9]/.test(char)) x += parseInt(char, 10);
else {
const useEl = document.createElementNS('http://www.w3.org/2000/svg', 'use');
useEl.setAttribute('href', `#${pieceToHrefMap[char]}`);
useEl.setAttribute('transform', `translate(${x * 48 + 24}, ${y * 48 + 24})`);
etboardMini.appendChild(useEl);
x++;
}
}
});
}
function isValidEditPlacement(char, x, y) {
if (char === 'K') return x >= 3 && x <= 5 && y >= 7 && y <= 9;
if (char === 'k') return x >= 3 && x <= 5 && y >= 0 && y <= 2;
if (char === 'A') return x >= 3 && x <= 5 && y >= 7 && y <= 9 && Math.abs(x - 4) === Math.abs(y - 8);
if (char === 'a') return x >= 3 && x <= 5 && y >= 0 && y <= 2 && Math.abs(x - 4) === Math.abs(y - 1);
if (char === 'B') return y >= 5 && y <= 9 && y % 2 !== 0 && ((y === 5 || y === 9) ? (x === 2 || x === 6) : (x === 0 || x === 4 || x === 8));
if (char === 'b') return y >= 0 && y <= 4 && y % 2 === 0 && ((y === 0 || y === 4) ? (x === 2 || x === 6) : (x === 0 || x === 4 || x === 8));
return true;
}
function handleEditBoardClick(x, y, clickedId) {
if (!isTile1Selected) {
if (!selectedPalettePiece) {
if (clickedId && clickedId[0] !== 'K' && clickedId[0] !== 'k') {
removeEditPiece(clickedId);
saveEditState();
updateEditUI();
}
} else {
if (clickedId) {
if (clickedId[0] !== 'K' && clickedId[0] !== 'k' && isValidEditPlacement(selectedPalettePiece, x, y)) {
removeEditPiece(clickedId);
placeEditPiece(selectedPalettePiece, x, y);
saveEditState();
updateEditUI();
}
} else {
if (isValidEditPlacement(selectedPalettePiece, x, y)) {
placeEditPiece(selectedPalettePiece, x, y);
saveEditState();
updateEditUI();
}
}
}
} else {
if (editPickedPieceId) {
if (clickedId === editPickedPieceId) {
setPickup(editPickedPieceId, false);
editPickedPieceId = null;
} else if (clickedId) {
setPickup(editPickedPieceId, false);
editPickedPieceId = clickedId;
setPickup(editPickedPieceId, true);
} else {
if (isValidEditPlacement(editPickedPieceId[0], x, y)) {
const pos = piecePos.get(editPickedPieceId);
tileMap.delete(`${pos.x},${pos.y}`);
tileMap.set(`${x},${y}`, editPickedPieceId);
piecePos.set(editPickedPieceId, {x, y});
const pieceEl = document.getElementById(editPickedPieceId);
const vis = getVisualCoords(x, y);
pieceEl.setAttribute('transform', `translate(${vis.x*48+24}, ${vis.y*48+24})`);
setPickup(editPickedPieceId, false);
editPickedPieceId = null;
saveEditState();
updateEditUI();
}
}
} else {
if (clickedId) {
editPickedPieceId = clickedId;
setPickup(editPickedPieceId, true);
} else {
if (selectedPalettePiece && isValidEditPlacement(selectedPalettePiece, x, y)) {
placeEditPiece(selectedPalettePiece, x, y);
saveEditState();
updateEditUI();
}
}
}
}
}
function removeEditPiece(id) {
const el = document.getElementById(id);
if (el) el.remove();
const pos = piecePos.get(id);
if (pos) tileMap.delete(`${pos.x},${pos.y}`);
piecePos.delete(id);
}
function placeEditPiece(char, x, y) {
let idx = 0;
while (piecePos.has(`${char}${idx}`)) idx++;
const newId = `${char}${idx}`;
const useEl = document.createElementNS('http://www.w3.org/2000/svg', 'use');
useEl.setAttribute('id', newId);
useEl.setAttribute('href', `#${pieceToHrefMap[char]}`);
const vis = getVisualCoords(x, y);
useEl.setAttribute('transform', `translate(${vis.x * 48 + 24}, ${vis.y * 48 + 24})`);
document.querySelector('.etboard').appendChild(useEl);
tileMap.set(`${x},${y}`, newId);
piecePos.set(newId, {x, y});
}
function saveEditState() {
const fen = boardToFEN();
editUndoStack.push(fen);
if (editUndoStack.length > 36) editUndoStack.shift();
editRedoStack = [];
updateToolHighlights();
}
function undoEdit() {
if (editUndoStack.length <= 1) return;
if (editPickedPieceId) { setPickup(editPickedPieceId, false); editPickedPieceId = null; }
editRedoStack.push(editUndoStack.pop());
const fen = editUndoStack[editUndoStack.length - 1];
loadFEN(fen);
updateEditUI();
updateToolHighlights();
}
function redoEdit() {
if (editRedoStack.length === 0) return;
if (editPickedPieceId) { setPickup(editPickedPieceId, false); editPickedPieceId = null; }
const fen = editRedoStack.pop();
editUndoStack.push(fen);
loadFEN(fen);
updateEditUI();
updateToolHighlights();
}
function exitEditModeCancel() {
isEditMode = false;
loadFEN(preEditFEN);
renderRecordUI();
renderNoteUI();
updateToolHighlights();
}
function exitEditModeConfirm() {
isEditMode = false;
const finalFEN = boardToFEN();
historyFEN = { fen: finalFEN, move: null, lastMove: null, c: "", v: [] };
currentBranch = [];
currentStepIndex = 0;
stepSlider.max = 0;
stepSlider.value = 0;
saveStateToUndo();
renderRecordUI();
renderNoteUI();
updateToolHighlights();
}
Modify updateToolHighlights() to disable non-relevant buttons in Edit Mode and fix undo/redo integration:
JavaScript
function updateToolHighlights() {
if (isEditMode) {
const editHighlights = {
'tool-new': false, 'tool-open': false, 'tool-edit': false, 'tool-save': false,
'tool-undo': editUndoStack.length > 1,
'tool-redo': editRedoStack.length > 0,
'tool-rotate': true, 'tool-flip': true,
'tool-exp-txt': false, 'tool-exp-svg': false
};
for (const [id, shouldEnable] of Object.entries(editHighlights)) {
const btn = document.getElementById(id);
if (btn) btn.classList.toggle('enable', shouldEnable);
}
return;
}
const isModified = currentStepIndex > 0 || historyFEN.fen !== INITIAL_FEN;
const highlights = {
'tool-new': isModified && !isExportTextMode,
'tool-open': !isExportTextMode,
'tool-edit': !isEditingComment && !isExportTextMode,
// ... (rest of the highlights remain identical) ...
Finally, wire up the Edit Tool button, edit undo/redo, and keybinds near your existing event listeners:
JavaScript
// Existing edit button listener integration
document.getElementById('tool-edit').addEventListener('click', () => {
if (isExportTextMode || isEditingComment) return;
if (!isEditMode) enterEditMode();
});
// Modify undo/redo listeners to check for edit mode
document.getElementById('tool-undo').addEventListener('click', () => {
if (isExportTextMode) return;
isEditMode ? undoEdit() : undo();
});
document.getElementById('tool-redo').addEventListener('click', () => {
if (isExportTextMode) return;
isEditMode ? redoEdit() : redo();
});
// Replace existing keydown block
document.addEventListener('keydown', (e) => {
if (isExportTextMode) return;
if (e.ctrlKey && e.key === 'z') {
e.preventDefault();
isEditMode ? undoEdit() : undo();
}
if (e.ctrlKey && e.key === 'y') {
e.preventDefault();
isEditMode ? redoEdit() : redo();
}
});