@bobfrankston/rmfmail 1.1.103 → 1.1.104

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.
package/client/app.js CHANGED
@@ -3044,26 +3044,19 @@ document.addEventListener("keydown", (e) => {
3044
3044
  }
3045
3045
  }
3046
3046
  // Ctrl+D or Delete = Delete selected messages.
3047
- // P15: don't hijack Delete inside text inputs / textareas / contenteditable.
3048
- // Single-selection-while-typing was the bug Bob reported 2026-05-20:
3049
- // search input has focus, list has an auto-selected row, user presses
3050
- // Delete to backspace a character → mailx hard-deletes the row. Data loss.
3051
- //
3052
- // The 2026-05-09 multi-select carve-out (Delete-key works on a list
3053
- // selection even when focus drifted into search) is preserved only for
3054
- // *explicit* multi-select (>1 row). Single selection is ALWAYS the
3055
- // auto-selected first row — never an explicit "delete this" intent when
3056
- // you're typing in search. So:
3057
- // - focus in editable + ≤1 selected → native text edit (delete char)
3058
- // - focus in editable + >1 selected → user multi-selected on purpose, run delete
3059
- // - focus elsewhere → run delete
3047
+ // Focus is the only signal. Text input has focus Delete is a native
3048
+ // character edit, ALWAYS. List has focus (a row, the list container,
3049
+ // anything outside a text editor) Delete acts on the list selection,
3050
+ // single or multi. The 2026-05-09 "multi-select while typing" carve-out
3051
+ // is gone — it cost Bob real messages 2026-05-20 by hard-deleting the
3052
+ // auto-selected row every time he hit Delete to backspace in the search
3053
+ // input.
3060
3054
  if ((e.ctrlKey && e.key === "d") || e.key === "Delete") {
3061
3055
  const t = e.target;
3062
3056
  const tag = t?.tagName;
3063
3057
  const editable = t?.isContentEditable;
3064
3058
  const inEditable = tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || editable;
3065
- const listSelectionCount = document.querySelectorAll("#ml-body .ml-row.selected").length;
3066
- if (inEditable && listSelectionCount <= 1)
3059
+ if (inEditable)
3067
3060
  return;
3068
3061
  e.preventDefault();
3069
3062
  deleteSelection();
@@ -3084,10 +3077,18 @@ document.addEventListener("keydown", (e) => {
3084
3077
  e.preventDefault();
3085
3078
  document.getElementById("btn-sync")?.click();
3086
3079
  }
3087
- // R = Toggle read/unread
3088
- if (e.key.toLowerCase() === "r" && !e.ctrlKey && !e.metaKey && !e.altKey) {
3080
+ // R (no modifiers) or Ctrl+Q = Toggle read/unread on the focused row.
3081
+ // Ctrl+Q mirrors the Outlook-style "mark read/unread" combo so it works
3082
+ // even when focus is in a text input (Ctrl-modifier guarantees no
3083
+ // collision with typing). Bare R defers when typing.
3084
+ const isToggleSeen = (e.key.toLowerCase() === "r" && !e.ctrlKey && !e.metaKey && !e.altKey) ||
3085
+ (e.ctrlKey && !e.metaKey && !e.altKey && e.key.toLowerCase() === "q");
3086
+ if (isToggleSeen) {
3089
3087
  const active = document.activeElement;
3090
- if (active && (active.tagName === "INPUT" || active.tagName === "TEXTAREA" || active.tagName === "SELECT"))
3088
+ const inText = active && (active.tagName === "INPUT" || active.tagName === "TEXTAREA" || active.tagName === "SELECT" || active.isContentEditable);
3089
+ // Bare R yields to text inputs; Ctrl+Q overrides them so it's reachable
3090
+ // from the search box or compose draft list.
3091
+ if (!e.ctrlKey && inText)
3091
3092
  return;
3092
3093
  const sel = getCurrentFocused();
3093
3094
  if (!sel)