youssefreda9 commited on
Commit
16da498
·
1 Parent(s): afdf449

fix: Critical editor bugs - Dropdowns now visible (remove overflow:hidden from editor-shell) - Font/size/color menus z-index 200 (above editor) - Selection collapses after formatting (no more blue highlight) - Spelling overlay uses textContent for consistent offsets - Correction preserves formatting (replaces inside parent node) - Alt correction same fix - Fallback finds span by matching original text

Browse files
src/css/components.css CHANGED
@@ -244,8 +244,17 @@
244
  background: var(--color-surface);
245
  border: 1px solid var(--color-border);
246
  border-radius: var(--radius-card);
247
- overflow: hidden;
248
  box-shadow: var(--shadow-card);
 
 
 
 
 
 
 
 
 
249
  }
250
 
251
  .editor-toolbar {
@@ -292,13 +301,10 @@
292
  padding: 8px var(--spacing-md);
293
  border-bottom: 1px solid var(--color-border);
294
  background: var(--color-surface);
295
- overflow-x: auto;
296
- -webkit-overflow-scrolling: touch;
297
- scrollbar-width: none;
298
- }
299
-
300
- .format-toolbar::-webkit-scrollbar {
301
- display: none;
302
  }
303
 
304
  .fmt-group {
@@ -418,7 +424,7 @@
418
  border-radius: 8px;
419
  box-shadow: 0 8px 24px rgba(0,0,0,0.15);
420
  padding: 4px;
421
- z-index: 100;
422
  opacity: 0;
423
  visibility: hidden;
424
  transform: translateY(-8px);
 
244
  background: var(--color-surface);
245
  border: 1px solid var(--color-border);
246
  border-radius: var(--radius-card);
247
+ overflow: visible;
248
  box-shadow: var(--shadow-card);
249
+ position: relative;
250
+ }
251
+
252
+ .editor-shell > *:first-child {
253
+ border-radius: var(--radius-card) var(--radius-card) 0 0;
254
+ }
255
+
256
+ .editor-shell > *:last-child {
257
+ border-radius: 0 0 var(--radius-card) var(--radius-card);
258
  }
259
 
260
  .editor-toolbar {
 
301
  padding: 8px var(--spacing-md);
302
  border-bottom: 1px solid var(--color-border);
303
  background: var(--color-surface);
304
+ overflow: visible;
305
+ position: relative;
306
+ z-index: 50;
307
+ flex-shrink: 0;
 
 
 
308
  }
309
 
310
  .fmt-group {
 
424
  border-radius: 8px;
425
  box-shadow: 0 8px 24px rgba(0,0,0,0.15);
426
  padding: 4px;
427
+ z-index: 200;
428
  opacity: 0;
429
  visibility: hidden;
430
  transform: translateY(-8px);
src/js/editor.js CHANGED
@@ -307,16 +307,35 @@ function applySuggestionAtOffsets(suggestion) {
307
  const errorSpan = idx >= 0 ? document.querySelector(`[data-suggestion-id="${idx}"]`) : null;
308
 
309
  if (errorSpan) {
310
- // Replace the error span with the corrected text node
 
 
311
  const correctedNode = document.createTextNode(suggestion.correction);
312
- errorSpan.replaceWith(correctedNode);
 
 
313
  } else {
314
- // Fallback: use offset-based replacement
315
- const text = getEditorText();
316
- const before = text.substring(0, suggestion.start);
317
- const after = text.substring(suggestion.end);
318
- const newText = before + suggestion.correction + after;
319
- setEditorHTML(escapeHtml(newText));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  }
321
  hideTooltip();
322
  analyzeTextDelayed();
@@ -332,14 +351,31 @@ function applyAlternativeCorrection(suggestion, correctionText) {
332
  const errorSpan = idx >= 0 ? document.querySelector(`[data-suggestion-id="${idx}"]`) : null;
333
 
334
  if (errorSpan) {
 
335
  const correctedNode = document.createTextNode(correctionText);
336
- errorSpan.replaceWith(correctedNode);
 
 
337
  } else {
338
- const text = getEditorText();
339
- const before = text.substring(0, suggestion.start);
340
- const after = text.substring(suggestion.end);
341
- const newText = before + correctionText + after;
342
- setEditorHTML(escapeHtml(newText));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  }
344
  hideTooltip();
345
  analyzeTextDelayed();
 
307
  const errorSpan = idx >= 0 ? document.querySelector(`[data-suggestion-id="${idx}"]`) : null;
308
 
309
  if (errorSpan) {
310
+ // Replace the error span's text content with the correction
311
+ // while keeping it inside its formatting parent
312
+ const parent = errorSpan.parentNode;
313
  const correctedNode = document.createTextNode(suggestion.correction);
314
+ parent.insertBefore(correctedNode, errorSpan);
315
+ parent.removeChild(errorSpan);
316
+ parent.normalize();
317
  } else {
318
+ // Fallback: find span by matching original text
319
+ const allErrorSpans = document.querySelectorAll('.spelling-error, .grammar-error, .punctuation-suggestion');
320
+ let found = false;
321
+ allErrorSpans.forEach(span => {
322
+ if (!found && span.textContent === suggestion.original) {
323
+ const p = span.parentNode;
324
+ const correctedNode = document.createTextNode(suggestion.correction);
325
+ p.insertBefore(correctedNode, span);
326
+ p.removeChild(span);
327
+ p.normalize();
328
+ found = true;
329
+ }
330
+ });
331
+ if (!found) {
332
+ // Last resort: offset-based replacement
333
+ const text = getEditorText();
334
+ const before = text.substring(0, suggestion.start);
335
+ const after = text.substring(suggestion.end);
336
+ const newText = before + suggestion.correction + after;
337
+ setEditorHTML(escapeHtml(newText));
338
+ }
339
  }
340
  hideTooltip();
341
  analyzeTextDelayed();
 
351
  const errorSpan = idx >= 0 ? document.querySelector(`[data-suggestion-id="${idx}"]`) : null;
352
 
353
  if (errorSpan) {
354
+ const parent = errorSpan.parentNode;
355
  const correctedNode = document.createTextNode(correctionText);
356
+ parent.insertBefore(correctedNode, errorSpan);
357
+ parent.removeChild(errorSpan);
358
+ parent.normalize();
359
  } else {
360
+ const allErrorSpans = document.querySelectorAll('.spelling-error, .grammar-error, .punctuation-suggestion');
361
+ let found = false;
362
+ allErrorSpans.forEach(span => {
363
+ if (!found && span.textContent === suggestion.original) {
364
+ const p = span.parentNode;
365
+ const correctedNode = document.createTextNode(correctionText);
366
+ p.insertBefore(correctedNode, span);
367
+ p.removeChild(span);
368
+ p.normalize();
369
+ found = true;
370
+ }
371
+ });
372
+ if (!found) {
373
+ const text = getEditorText();
374
+ const before = text.substring(0, suggestion.start);
375
+ const after = text.substring(suggestion.end);
376
+ const newText = before + correctionText + after;
377
+ setEditorHTML(escapeHtml(newText));
378
+ }
379
  }
380
  hideTooltip();
381
  analyzeTextDelayed();
src/js/format.js CHANGED
@@ -3,11 +3,23 @@
3
 
4
  /**
5
  * Execute a formatting command on the current selection
 
 
 
6
  */
7
- function execFormat(command, value) {
8
- document.execCommand(command, false, value || null);
9
  const editor = getEditorElement();
10
  if (editor) editor.focus();
 
 
 
 
 
 
 
 
 
11
  updateFormatState();
12
  }
13
 
@@ -18,10 +30,10 @@ function formatUnderline() { execFormat('underline'); }
18
  function formatStrikethrough() { execFormat('strikethrough'); }
19
 
20
  /* ── Undo / Redo (handles both typing and formatting) ── */
21
- function formatUndo() { execFormat('undo'); }
22
- function formatRedo() { execFormat('redo'); }
23
 
24
- /* ── Alignment (applies to paragraph containing selection) ── */
25
  function formatAlignRight() { execFormat('justifyRight'); }
26
  function formatAlignCenter() { execFormat('justifyCenter'); }
27
  function formatAlignLeft() { execFormat('justifyLeft'); }
 
3
 
4
  /**
5
  * Execute a formatting command on the current selection
6
+ * @param {string} command - execCommand name
7
+ * @param {string} [value] - optional value
8
+ * @param {boolean} [keepSelection] - if true, don't collapse selection
9
  */
10
+ function execFormat(command, value, keepSelection) {
11
+ document.execCommand(command, false, value !== undefined ? value : null);
12
  const editor = getEditorElement();
13
  if (editor) editor.focus();
14
+
15
+ // Collapse selection after formatting so text doesn't stay highlighted
16
+ if (!keepSelection) {
17
+ const sel = window.getSelection();
18
+ if (sel && sel.rangeCount > 0 && !sel.isCollapsed) {
19
+ sel.collapseToEnd();
20
+ }
21
+ }
22
+
23
  updateFormatState();
24
  }
25
 
 
30
  function formatStrikethrough() { execFormat('strikethrough'); }
31
 
32
  /* ── Undo / Redo (handles both typing and formatting) ── */
33
+ function formatUndo() { execFormat('undo', undefined, true); }
34
+ function formatRedo() { execFormat('redo', undefined, true); }
35
 
36
+ /* ── Alignment (applies to paragraph containing selection/cursor) ── */
37
  function formatAlignRight() { execFormat('justifyRight'); }
38
  function formatAlignCenter() { execFormat('justifyCenter'); }
39
  function formatAlignLeft() { execFormat('justifyLeft'); }
src/js/selection.js CHANGED
@@ -187,7 +187,9 @@ function setCaretOffset(offset) {
187
  function getEditorText() {
188
  const editor = document.getElementById('editor-container');
189
  if (!editor) return '';
190
- return editor.innerText || editor.textContent || '';
 
 
191
  }
192
 
193
  /**
 
187
  function getEditorText() {
188
  const editor = document.getElementById('editor-container');
189
  if (!editor) return '';
190
+ // MUST use textContent to match the offset calculation in overlaySuggestions
191
+ // innerText adds '\n' for block elements which causes offset mismatch
192
+ return editor.textContent || '';
193
  }
194
 
195
  /**