fix: auto-focus rich markdown editor on mount (#750)
* fix: auto-focus rich markdown editor on mount Match MonacoEditor's behavior so opening a new markdown file (Cmd+Shift+N) or switching to a rich-markdown tab lands the cursor in the editor. Guards against focus theft from modals/dialogs and passes `scrollIntoView: false` so the focus call doesn't race with useEditorScrollRestore's RAF retry loop. * fix: use 'start' position to avoid AllSelection on empty editor Passing null to commands.focus() keeps the editor's current selection, which for a fresh empty document is an AllSelection — rendered as a visible 0-width highlight inside the placeholder instead of a normal caret. 'start' resolves to a proper TextSelection at doc start. Also update review-and-submit skill to push before gh pr create.
This commit is contained in:
parent
d1a3324e7e
commit
fcbdd95b43
|
|
@ -133,6 +133,17 @@ fi
|
|||
|
||||
### 2a. Create PR
|
||||
|
||||
**FIRST**: Push the branch to the remote so `gh pr create` doesn't fail with
|
||||
`aborted: you must first push the current branch to a remote`. The
|
||||
`create-pr` skill rebases locally but does not always push before invoking
|
||||
`gh pr create`, and `gh` refuses to create a PR for an un-pushed branch.
|
||||
|
||||
```bash
|
||||
git push --force-with-lease -u origin HEAD
|
||||
```
|
||||
|
||||
Then invoke:
|
||||
|
||||
```
|
||||
Use the Skill tool: skill: "create-pr"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { createRichMarkdownKeyHandler } from './rich-markdown-key-handler'
|
|||
import { DOMSerializer } from '@tiptap/pm/model'
|
||||
import { TextSelection } from '@tiptap/pm/state'
|
||||
import { normalizeSoftBreaks } from './rich-markdown-normalize'
|
||||
import { autoFocusRichEditor } from './rich-markdown-auto-focus'
|
||||
import { cutVisualLine, getVisualLineRange } from './rich-markdown-visual-line'
|
||||
|
||||
type RichMarkdownEditorProps = {
|
||||
|
|
@ -270,6 +271,12 @@ export default function RichMarkdownEditor({
|
|||
// Why: clear the flag *after* normalizeSoftBreaks so any onUpdate
|
||||
// triggered by the normalization transaction is still suppressed.
|
||||
isInitializingRef.current = false
|
||||
// Why: MonacoEditor already auto-focuses on mount so users can start
|
||||
// typing immediately. The rich markdown editor must do the same,
|
||||
// otherwise opening a new markdown file (Cmd+Shift+N) or switching to
|
||||
// an existing markdown tab leaves the cursor outside the editing
|
||||
// surface and the user has to click before typing.
|
||||
autoFocusRichEditor(nextEditor, rootRef.current)
|
||||
},
|
||||
onUpdate: ({ editor: nextEditor }) => {
|
||||
syncSlashMenu(nextEditor, rootRef.current, setSlashMenu)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import type { Editor } from '@tiptap/react'
|
||||
|
||||
/**
|
||||
* Auto-focuses the rich markdown editor on mount so users can start typing
|
||||
* immediately (matching MonacoEditor's behavior). Guards against focus theft
|
||||
* from modals/dialogs and skips scrollIntoView to avoid racing with
|
||||
* useEditorScrollRestore.
|
||||
*/
|
||||
export function autoFocusRichEditor(nextEditor: Editor, rootEl: HTMLElement | null): void {
|
||||
requestAnimationFrame(() => {
|
||||
if (nextEditor.isDestroyed) {
|
||||
return
|
||||
}
|
||||
// Why: don't steal focus if something outside the editor root is already
|
||||
// focused (modal, rename dialog, sidebar search input, etc.). Only
|
||||
// auto-focus when focus is nowhere or already inside the editor.
|
||||
const active = document.activeElement
|
||||
const isNeutralFocus =
|
||||
active === null || active === document.body || (rootEl?.contains(active) ?? false)
|
||||
if (!isNeutralFocus) {
|
||||
return
|
||||
}
|
||||
// Why: pass 'start' (not null) to resolve to a proper TextSelection at
|
||||
// doc position 1. With null, Tiptap keeps whatever the editor's current
|
||||
// selection happens to be on mount — for a freshly-created empty doc
|
||||
// that's an AllSelection, which renders as a visible 0-width highlight
|
||||
// inside the placeholder instead of a normal blinking caret.
|
||||
//
|
||||
// Why: `scrollIntoView: false` prevents Tiptap's focus command from
|
||||
// scrolling the cursor into view, which would otherwise race with
|
||||
// useEditorScrollRestore's RAF retry loop and clobber the cached
|
||||
// scroll position on every tab switch.
|
||||
nextEditor.commands.focus('start', { scrollIntoView: false })
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue