@bobfrankston/rmfmail 1.1.106 → 1.1.107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -322,19 +322,48 @@ export async function createTinyMceEditor(container, opts = {}) {
322
322
  // (reply / forward — user types above the quoted block);
323
323
  // anything else → cursor at END (legacy "put cursor at end"
324
324
  // semantics).
325
- try {
326
- editor.selection.select(editor.getBody(), true);
327
- editor.selection.collapse(pos === 0 /* true = start */);
328
- editor.focus();
329
- // Scroll the viewport to match the caret. For pos 0 that's
330
- // the TOP of the body on a reply the user types above the
331
- // quoted block and must land looking at that empty line, not
332
- // scrolled down inside the quote (Bob 2026-05-18: "the cursor
333
- // should be at the top of the window").
334
- if (pos === 0)
325
+ const place = () => {
326
+ const body = editor.getBody();
327
+ if (pos === 0) {
328
+ // Put the caret INSIDE the first block element. Collapsing
329
+ // to raw body-start lands it outside any block (before /
330
+ // between bare nodes) where contentEditable insertion is
331
+ // unpredictable Bob 2026-05-21: "typing goes in the
332
+ // wrong place until you try again". rmfmail's reply body
333
+ // now leads with a real <p>; drop the caret into it.
334
+ const first = body.firstChild;
335
+ if (first && first.nodeType === 1 /* element */) {
336
+ editor.selection.setCursorLocation(first, 0);
337
+ }
338
+ else {
339
+ editor.selection.select(body, true);
340
+ editor.selection.collapse(true);
341
+ }
342
+ editor.focus();
343
+ // Viewport to the top so the user looks at the empty
344
+ // reply line, not scrolled down into the quote.
335
345
  editor.getWin()?.scrollTo(0, 0);
336
- else
346
+ }
347
+ else {
348
+ editor.selection.select(body, true);
349
+ editor.selection.collapse(false);
350
+ editor.focus();
337
351
  editor.selection.scrollIntoView();
352
+ }
353
+ };
354
+ try {
355
+ place();
356
+ // Re-apply on the next frame. Cross-iframe focus (compose is
357
+ // an iframe; TinyMCE's edit area is a nested iframe) lets the
358
+ // first selection set get clobbered by a late layout / focus
359
+ // event — the "have to click in and try again" symptom. A
360
+ // second pass after the frame settles makes it stick.
361
+ editor.getWin()?.requestAnimationFrame?.(() => {
362
+ try {
363
+ place();
364
+ }
365
+ catch { /* */ }
366
+ });
338
367
  }
339
368
  catch { /* */ }
340
369
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/rmfmail",
3
- "version": "1.1.106",
3
+ "version": "1.1.107",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",