youssefreda9 commited on
Commit
0f88e4f
·
1 Parent(s): afb0c39

feat: Add custom Undo/Redo for corrections and dismissals - Ctrl+Z undoes correction/dismiss (restores previous editor state) - Ctrl+Y or Ctrl+Shift+Z redoes - pushUndoState() called before every applyCorrection, applyAlternative, dismissSuggestion, applyAll - 50-level undo stack - Only intercepts when custom stack has entries (falls through to browser native otherwise)

Browse files
Files changed (1) hide show
  1. src/js/editor.js +55 -0
src/js/editor.js CHANGED
@@ -7,6 +7,44 @@ let _lastInputTime = 0;
7
  const ANALYZE_DEBOUNCE_MS = 1000;
8
  const MAX_ANALYZE_LENGTH = 5000;
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  // Dismissed words whitelist — words the user chose to keep as-is
11
  const _dismissedWords = new Set(
12
  JSON.parse(localStorage.getItem('bayan_dismissed_words') || '[]')
@@ -54,6 +92,19 @@ function initEditor() {
54
 
55
  document.addEventListener('keydown', (e) => {
56
  if (e.key === 'Escape') hideTooltip();
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  });
58
 
59
  document.addEventListener('click', (e) => {
@@ -336,6 +387,7 @@ function hideTooltip() {
336
  }
337
 
338
  function applySuggestionAtOffsets(suggestion) {
 
339
  // Find the error span in the DOM and replace its text content
340
  // This preserves formatting (bold, italic, etc.) around/inside the span
341
  const idx = (window.currentSuggestions || []).indexOf(suggestion);
@@ -383,6 +435,7 @@ function applyCorrection() {
383
  }
384
 
385
  function applyAlternativeCorrection(suggestion, correctionText) {
 
386
  const idx = (window.currentSuggestions || []).indexOf(suggestion);
387
  const errorSpan = idx >= 0 ? document.querySelector(`[data-suggestion-id="${idx}"]`) : null;
388
 
@@ -418,6 +471,7 @@ function applyAlternativeCorrection(suggestion, correctionText) {
418
  }
419
 
420
  function dismissSuggestion(suggestion) {
 
421
  // Add the word to dismissed whitelist so it's never flagged again
422
  if (suggestion.original) {
423
  _dismissedWords.add(suggestion.original);
@@ -464,6 +518,7 @@ function applyAllSuggestions() {
464
  const suggestions = [...(window.currentSuggestions || [])].sort((a, b) => b.start - a.start);
465
  if (suggestions.length === 0) return;
466
  if (!confirm('هل تريد تطبيق جميع التصحيحات (' + suggestions.length + ')؟')) return;
 
467
 
468
  let text = getEditorText();
469
  suggestions.forEach((s) => {
 
7
  const ANALYZE_DEBOUNCE_MS = 1000;
8
  const MAX_ANALYZE_LENGTH = 5000;
9
 
10
+ // ── Custom Undo/Redo Stack ──
11
+ const _undoStack = [];
12
+ const _redoStack = [];
13
+ const _MAX_UNDO = 50;
14
+
15
+ function pushUndoState() {
16
+ const editor = getEditorElement();
17
+ if (!editor) return;
18
+ const html = editor.innerHTML;
19
+ // Avoid duplicate consecutive entries
20
+ if (_undoStack.length > 0 && _undoStack[_undoStack.length - 1] === html) return;
21
+ _undoStack.push(html);
22
+ if (_undoStack.length > _MAX_UNDO) _undoStack.shift();
23
+ _redoStack.length = 0; // Clear redo on new action
24
+ }
25
+
26
+ function editorUndo() {
27
+ const editor = getEditorElement();
28
+ if (!editor || _undoStack.length === 0) return false;
29
+ _redoStack.push(editor.innerHTML);
30
+ editor.innerHTML = _undoStack.pop();
31
+ updateEditorStats();
32
+ updatePlaceholder();
33
+ analyzeTextDelayed();
34
+ return true;
35
+ }
36
+
37
+ function editorRedo() {
38
+ const editor = getEditorElement();
39
+ if (!editor || _redoStack.length === 0) return false;
40
+ _undoStack.push(editor.innerHTML);
41
+ editor.innerHTML = _redoStack.pop();
42
+ updateEditorStats();
43
+ updatePlaceholder();
44
+ analyzeTextDelayed();
45
+ return true;
46
+ }
47
+
48
  // Dismissed words whitelist — words the user chose to keep as-is
49
  const _dismissedWords = new Set(
50
  JSON.parse(localStorage.getItem('bayan_dismissed_words') || '[]')
 
92
 
93
  document.addEventListener('keydown', (e) => {
94
  if (e.key === 'Escape') hideTooltip();
95
+ // Custom Undo/Redo for corrections
96
+ if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
97
+ if (_undoStack.length > 0) {
98
+ e.preventDefault();
99
+ editorUndo();
100
+ }
101
+ }
102
+ if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
103
+ if (_redoStack.length > 0) {
104
+ e.preventDefault();
105
+ editorRedo();
106
+ }
107
+ }
108
  });
109
 
110
  document.addEventListener('click', (e) => {
 
387
  }
388
 
389
  function applySuggestionAtOffsets(suggestion) {
390
+ pushUndoState(); // Save state before correction
391
  // Find the error span in the DOM and replace its text content
392
  // This preserves formatting (bold, italic, etc.) around/inside the span
393
  const idx = (window.currentSuggestions || []).indexOf(suggestion);
 
435
  }
436
 
437
  function applyAlternativeCorrection(suggestion, correctionText) {
438
+ pushUndoState(); // Save state before correction
439
  const idx = (window.currentSuggestions || []).indexOf(suggestion);
440
  const errorSpan = idx >= 0 ? document.querySelector(`[data-suggestion-id="${idx}"]`) : null;
441
 
 
471
  }
472
 
473
  function dismissSuggestion(suggestion) {
474
+ pushUndoState(); // Save state before dismiss
475
  // Add the word to dismissed whitelist so it's never flagged again
476
  if (suggestion.original) {
477
  _dismissedWords.add(suggestion.original);
 
518
  const suggestions = [...(window.currentSuggestions || [])].sort((a, b) => b.start - a.start);
519
  if (suggestions.length === 0) return;
520
  if (!confirm('هل تريد تطبيق جميع التصحيحات (' + suggestions.length + ')؟')) return;
521
+ pushUndoState(); // Save state before applying all
522
 
523
  let text = getEditorText();
524
  suggestions.forEach((s) => {