Files
FND 1d30b973c4 feat(editor): Mod+E shortcut to enter and leave Edit mode in place (#1492)
* feat(editor): Mod+E toggles markdown edit mode in place (#1479)

* fix(editor): scope the edit-mode exit chord and wire its tests into CI

- Early-return the dedicated Mod+E exit listener when the event target is
  an INPUT/TEXTAREA outside .cm-editor, so native text fields (Ask AI box,
  annotation textareas) keep their own Mod+E instead of committing and
  exiting the edit session underneath them; match the key case-insensitively
  so Caps Lock cannot dead-key the exit. CodeMirror's content DOM is
  contenteditable, so the editor path is untouched. Also note in code that
  mounting atomic-editor's selectionToolbar() would claim Mod-e and dead-key
  the chord.
- New DOM test pinning the ownership split (foreign textarea keeps the
  session open; the chord from inside CodeMirror still exits),
  revert-verified against the guard.
- De-flake "never discards an unsaved source-backed buffer silently":
  the single setTimeout(0) after a contentEditable DOM mutation raced
  CodeMirror's MutationObserver, which happy-dom intermittently never
  re-delivers after CM's stop()/start() observer cycling — the buffer is
  now dirtied through the editor's own dispatch, with a bounded poll for
  the toolstrip's Save readout. 8/8 stable under CI's exact invocation
  preceded by another App suite (previously ~3/8 failures).
- Add App.editModeShortcut.test.tsx to the DOM-gated file enumeration in
  .github/workflows/test.yml so the suite actually runs in CI.

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-09-10 16:43:47 -07:00

31 lines
1.4 KiB
TypeScript

/**
* Which element a markdown edit session's scroll offset actually lives on.
*
* The Viewer ↔ MarkdownEditor swap replaces the document area's children, and a
* real browser clamps the scroll container to the top the moment the old subtree
* leaves it — that is the jump #1479 reports. Carrying the offset across the
* swap needs the right target, and the target depends on the shell:
*
* - The desktop layout leaves the editor unbounded, so CodeMirror grows to
* full content height inside the document viewport: the VIEWPORT scrolls and
* `.cm-scroller` never overflows (restoring into it is a silent no-op).
* - A height-bounded shell (compact touch) bounds the editor, so CodeMirror
* scrolls itself and `.cm-scroller` is the scroller.
*
* Resolve the element that can actually scroll when the offset is READ (before
* the swap): the surface on screen has settled by then, so its overflow test is
* trustworthy. Restoring is deliberately not routed through this test — the
* surface that just mounted has no reliable measurement yet; see the restore
* effect in `App.tsx`.
*/
export function scrollableEditSurface(
editorScroller: HTMLElement | null,
documentViewport: HTMLElement | null,
isEditing: boolean,
): HTMLElement | null {
if (isEditing && editorScroller && editorScroller.scrollHeight > editorScroller.clientHeight) {
return editorScroller;
}
return documentViewport;
}