Commit ·
f1671d1
1
Parent(s): 09be182
FIX-32: Fix word duplication bug in website frontend
Browse filesRoot cause: applyAllSuggestions used a mixed DOM+text fallback approach.
After DOM-based fixes shifted the editor text, the text-based fallback
used stale offsets from the original API response, inserting corrections
at wrong positions. This caused 'المشروعالمشروع.' (word duplication).
Fix:
1. applyAllSuggestions now uses PURE text-based reverse-order replacement.
Since patches are sorted highest-offset-first, each offset is valid
because nothing before it has been modified yet.
2. _isApplyingSuggestion guard now stays active 400ms (past the 300ms
re-analysis timeout) to prevent normalize()/input events from
triggering duplicate analysis calls.
- src/js/editor.js +18 -30
src/js/editor.js
CHANGED
|
@@ -531,7 +531,9 @@ function applySuggestionAtOffsets(suggestion) {
|
|
| 531 |
}
|
| 532 |
// Pipeline Hardening v3.3: Do NOT call analyzeTextDelayed() — prevents recursive re-analysis
|
| 533 |
} finally {
|
| 534 |
-
|
|
|
|
|
|
|
| 535 |
}
|
| 536 |
// P2/User Request: Auto re-analyze after applying suggestion
|
| 537 |
// Calls analyzeText() DIRECTLY (not delayed) for instant re-analysis.
|
|
@@ -617,7 +619,8 @@ function applyAlternativeCorrection(suggestion, correctionText) {
|
|
| 617 |
}
|
| 618 |
// Pipeline Hardening v3.3: Do NOT call analyzeTextDelayed() — prevents recursive re-analysis
|
| 619 |
} finally {
|
| 620 |
-
|
|
|
|
| 621 |
}
|
| 622 |
// P2/User Request: Auto re-analyze after applying alternative correction
|
| 623 |
setTimeout(() => { analyzeText(); }, 300);
|
|
@@ -679,36 +682,19 @@ function applyAllSuggestions() {
|
|
| 679 |
try {
|
| 680 |
pushUndoState(); // Save state before applying all
|
| 681 |
|
| 682 |
-
//
|
| 683 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
suggestions.forEach((s) => {
|
| 685 |
-
|
| 686 |
-
const errorSpan = sid ? document.querySelector(`[data-suggestion-id="${sid}"]`) : null;
|
| 687 |
-
if (errorSpan) {
|
| 688 |
-
const parent = errorSpan.parentNode;
|
| 689 |
-
const correctedNode = document.createTextNode(s.correction);
|
| 690 |
-
parent.insertBefore(correctedNode, errorSpan);
|
| 691 |
-
parent.removeChild(errorSpan);
|
| 692 |
-
parent.normalize();
|
| 693 |
-
appliedCount++;
|
| 694 |
-
}
|
| 695 |
-
});
|
| 696 |
-
|
| 697 |
-
// Fallback: if DOM approach missed some, do text-based replacement
|
| 698 |
-
if (appliedCount < suggestions.length) {
|
| 699 |
-
let text = getEditorText();
|
| 700 |
-
// Re-sort remaining by offset (they weren't found as spans)
|
| 701 |
-
const remaining = suggestions.filter(s => {
|
| 702 |
-
const sid = s.id;
|
| 703 |
-
return !sid || !document.querySelector(`[data-suggestion-id="${sid}"]`);
|
| 704 |
-
}).sort((a, b) => b.start - a.start);
|
| 705 |
-
remaining.forEach((s) => {
|
| 706 |
text = text.substring(0, s.start) + s.correction + text.substring(s.end);
|
| 707 |
-
});
|
| 708 |
-
if (remaining.length > 0) {
|
| 709 |
-
setEditorHTML(escapeHtml(text));
|
| 710 |
}
|
| 711 |
-
}
|
|
|
|
| 712 |
|
| 713 |
// Place cursor at end of editor content
|
| 714 |
const editor = getEditorElement();
|
|
@@ -733,7 +719,9 @@ function applyAllSuggestions() {
|
|
| 733 |
updateSuggestionsList([]);
|
| 734 |
if (typeof showToast === 'function') showToast('✓ تم تطبيق ' + suggestions.length + ' تصحيح');
|
| 735 |
} finally {
|
| 736 |
-
|
|
|
|
|
|
|
| 737 |
}
|
| 738 |
// P2/User Request: Auto re-analyze after applying all suggestions
|
| 739 |
setTimeout(() => { analyzeText(); }, 300);
|
|
|
|
| 531 |
}
|
| 532 |
// Pipeline Hardening v3.3: Do NOT call analyzeTextDelayed() — prevents recursive re-analysis
|
| 533 |
} finally {
|
| 534 |
+
// FIX-32: Delay guard reset until AFTER re-analysis fires,
|
| 535 |
+
// preventing normalize()/input events from triggering double analysis.
|
| 536 |
+
setTimeout(() => { _isApplyingSuggestion = false; }, 400);
|
| 537 |
}
|
| 538 |
// P2/User Request: Auto re-analyze after applying suggestion
|
| 539 |
// Calls analyzeText() DIRECTLY (not delayed) for instant re-analysis.
|
|
|
|
| 619 |
}
|
| 620 |
// Pipeline Hardening v3.3: Do NOT call analyzeTextDelayed() — prevents recursive re-analysis
|
| 621 |
} finally {
|
| 622 |
+
// FIX-32: Delay guard reset until AFTER re-analysis fires.
|
| 623 |
+
setTimeout(() => { _isApplyingSuggestion = false; }, 400);
|
| 624 |
}
|
| 625 |
// P2/User Request: Auto re-analyze after applying alternative correction
|
| 626 |
setTimeout(() => { analyzeText(); }, 300);
|
|
|
|
| 682 |
try {
|
| 683 |
pushUndoState(); // Save state before applying all
|
| 684 |
|
| 685 |
+
// FIX-32: Use PURE TEXT-BASED approach to avoid DOM/offset mismatch.
|
| 686 |
+
// The old mixed DOM+fallback approach caused word duplication because
|
| 687 |
+
// after DOM-based fixes shifted the text, the fallback's stale offsets
|
| 688 |
+
// would insert corrections at wrong positions.
|
| 689 |
+
// Reverse-order text replacement is safe — each patch's offsets are
|
| 690 |
+
// valid because we haven't modified anything before them yet.
|
| 691 |
+
let text = getEditorText();
|
| 692 |
suggestions.forEach((s) => {
|
| 693 |
+
if (s.start >= 0 && s.end <= text.length && s.start <= s.end) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 694 |
text = text.substring(0, s.start) + s.correction + text.substring(s.end);
|
|
|
|
|
|
|
|
|
|
| 695 |
}
|
| 696 |
+
});
|
| 697 |
+
setEditorHTML(escapeHtml(text));
|
| 698 |
|
| 699 |
// Place cursor at end of editor content
|
| 700 |
const editor = getEditorElement();
|
|
|
|
| 719 |
updateSuggestionsList([]);
|
| 720 |
if (typeof showToast === 'function') showToast('✓ تم تطبيق ' + suggestions.length + ' تصحيح');
|
| 721 |
} finally {
|
| 722 |
+
// FIX-32: Delay guard reset until AFTER re-analysis fires,
|
| 723 |
+
// preventing input events from triggering double analysis.
|
| 724 |
+
setTimeout(() => { _isApplyingSuggestion = false; }, 400);
|
| 725 |
}
|
| 726 |
// P2/User Request: Auto re-analyze after applying all suggestions
|
| 727 |
setTimeout(() => { analyzeText(); }, 300);
|