@bobfrankston/rmfmail 1.1.71 → 1.1.73
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/android-bootstrap.bundle.js +12 -8
- package/client/android-bootstrap.bundle.js.map +2 -2
- package/client/app.bundle.js +1 -1
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +3 -1
- package/client/app.js.map +1 -1
- package/client/app.ts +3 -1
- package/client/styles/components.css +10 -4
- package/package.json +1 -1
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +9 -2
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +9 -2
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-settings/docs/multi-view.md +81 -0
- package/packages/mailx-settings/docs/search.md +5 -1
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-28028 → node_modules.npmglobalize-stash-34412}/.package-lock.json +0 -0
|
@@ -3057,7 +3057,13 @@ export class ImapManager extends EventEmitter {
|
|
|
3057
3057
|
await api.setFlags(folder.path, action.uid, action.flags || []);
|
|
3058
3058
|
console.log(` [api] ${accountId}: flags synced UID ${action.uid}`);
|
|
3059
3059
|
} else if ((action.action === "delete" || action.action === "trash") && api.trashMessage) {
|
|
3060
|
-
|
|
3060
|
+
// Pass the stored Gmail message id (provider_id) so
|
|
3061
|
+
// the provider doesn't fall back to its capped
|
|
3062
|
+
// list-and-hash search — that search misses any
|
|
3063
|
+
// message past the most-recent ~1000 in a large
|
|
3064
|
+
// mailbox, fails, and the deletion un-happens.
|
|
3065
|
+
const env: any = this.db.getMessageByUid(accountId, action.uid);
|
|
3066
|
+
await api.trashMessage(folder.path, action.uid, env?.providerId);
|
|
3061
3067
|
console.log(` [api] ${accountId}: trashed UID ${action.uid} from ${folder.path}`);
|
|
3062
3068
|
} else if (action.action === "move" && api.moveMessage) {
|
|
3063
3069
|
const target = folders.find(f => f.id === action.targetFolderId);
|
|
@@ -3067,7 +3073,8 @@ export class ImapManager extends EventEmitter {
|
|
|
3067
3073
|
this.db.completeSyncAction(action.id);
|
|
3068
3074
|
continue;
|
|
3069
3075
|
}
|
|
3070
|
-
|
|
3076
|
+
const env: any = this.db.getMessageByUid(accountId, action.uid);
|
|
3077
|
+
await api.moveMessage(folder.path, action.uid, target.path, env?.providerId);
|
|
3071
3078
|
console.log(` [api] ${accountId}: moved UID ${action.uid} ${folder.path} → ${target.path}`);
|
|
3072
3079
|
} else {
|
|
3073
3080
|
// Unsupported action on Gmail. After 5 retries, drop it
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bobfrankston/mailx-imap",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.51",
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@bobfrankston/iflow-direct": "^0.1.27",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Multi-view: tabs, tear-off, separate windows
|
|
2
|
+
|
|
3
|
+
Goal: multiple **views** over one backend — like Thunderbird/Outlook — so the
|
|
4
|
+
user can keep a message open while working the list, compare two folders, etc.
|
|
5
|
+
NOT multiple daemon instances (`--another` already exists for that, and a
|
|
6
|
+
second daemon means a second DB connection — contention, not what's wanted).
|
|
7
|
+
|
|
8
|
+
## Key fact: reminders already spawn separate native windows
|
|
9
|
+
|
|
10
|
+
`MailxService.showReminderPopup` → injected `popupFn` → msger
|
|
11
|
+
`showMessageBoxEx` opens a **real OS WebView window** fed a self-contained HTML
|
|
12
|
+
document (`rawHtml: true`: the caller ships a full HTML doc with its own button
|
|
13
|
+
row + an inline script that posts results back over the wry bridge). It returns
|
|
14
|
+
a handle with `pid` + `close()`.
|
|
15
|
+
|
|
16
|
+
Consequence: msger spawning extra native windows is **not** the blocker. The
|
|
17
|
+
"popups don't inherit the custom protocol" limitation only blocks a window that
|
|
18
|
+
must load the *multi-file app* (index.html + app.bundle.js + CSS + importmap
|
|
19
|
+
deps, all served from `msger.localhost`). A **single rendered view** is
|
|
20
|
+
self-contained content — the reminder shape.
|
|
21
|
+
|
|
22
|
+
So:
|
|
23
|
+
- **Torn-off READ-ONLY message reader** = the reminder mechanism. The daemon
|
|
24
|
+
already has rendered `bodyHtml` from `getMessage`; wrap header + sanitized
|
|
25
|
+
body as a standalone HTML doc, hand to `showMessageBoxEx`. No custom
|
|
26
|
+
protocol, no second IPC channel. Cheap.
|
|
27
|
+
- **Torn-off INTERACTIVE view** (reply / navigate / delete / live sync
|
|
28
|
+
updates) needs a real IPC channel back to the daemon → msger multi-channel
|
|
29
|
+
work. Expensive. Deferred.
|
|
30
|
+
|
|
31
|
+
## Three view modes, one backend
|
|
32
|
+
|
|
33
|
+
1. **Tabs** inside the main window — one WebView, one IPC channel, a tab strip;
|
|
34
|
+
each tab is an independent three-pane (or a single-message view). Pure
|
|
35
|
+
client work.
|
|
36
|
+
2. **Tear-off** — detach a tab into its own OS window.
|
|
37
|
+
- Read-only message tab → `showMessageBoxEx` + `rawHtml` (reminder path).
|
|
38
|
+
- Interactive tab → needs msger multi-channel IPC (later).
|
|
39
|
+
3. **Re-dock** — drag a torn-off window's content back in as a tab. For the
|
|
40
|
+
read-only reader this is just: close the popup, re-open the message as an
|
|
41
|
+
in-window tab. For interactive, follows whatever (2) settles on.
|
|
42
|
+
|
|
43
|
+
## Tabs architecture (the real work)
|
|
44
|
+
|
|
45
|
+
Today the list view is module-level global state in `client/components/
|
|
46
|
+
message-list.ts` — `currentAccountId`, `currentFolderId`, `searchMode`,
|
|
47
|
+
`currentSearchQuery`, `positionMemory`, `listCache` — and `message-state.ts` is
|
|
48
|
+
a single global "source of truth" for the list+viewer. One window = one view.
|
|
49
|
+
|
|
50
|
+
Tabs require that view-state to become **per-tab**:
|
|
51
|
+
|
|
52
|
+
- **Per-tab:** selected account+folder (or search), selected message, list
|
|
53
|
+
scroll position, the `message-state` instance, sort order, flagged-only
|
|
54
|
+
toggle.
|
|
55
|
+
- **Shared (stays global):** the IPC connection + sync event stream, folder
|
|
56
|
+
counts, the contacts cache, settings, the parsed-body LRU on the daemon.
|
|
57
|
+
Sync/new-mail events broadcast to every tab; each tab decides if the event
|
|
58
|
+
touches its folder.
|
|
59
|
+
|
|
60
|
+
Shape: a `TabManager` owning an array of `ViewTab`, each `ViewTab` holding its
|
|
61
|
+
own `MessageListState` + the DOM subtree for its three-pane. Only the active
|
|
62
|
+
tab's subtree is in layout; inactive tabs keep their DOM detached (cheap) or
|
|
63
|
+
their state and rebuild on activate. `message-list.ts` functions take a
|
|
64
|
+
`ViewTab` (or read `TabManager.active`) instead of the module globals.
|
|
65
|
+
|
|
66
|
+
## Staging
|
|
67
|
+
|
|
68
|
+
1. **Tab strip + per-tab three-pane.** Extract the message-list globals into a
|
|
69
|
+
`ViewTab`/`TabManager`; render a tab strip; New Tab opens another inbox
|
|
70
|
+
view. The bulk of the work and the architectural commit.
|
|
71
|
+
2. **Open-in-new-tab** for a message (and a folder). Exercises per-tab state on
|
|
72
|
+
the simplest views.
|
|
73
|
+
3. **Tear-off a read-only message tab** → `showMessageBoxEx` standalone window
|
|
74
|
+
(reminder mechanism). Re-dock = reopen as a tab.
|
|
75
|
+
4. **Interactive tear-off / true second app window.** Needs msger to let a
|
|
76
|
+
spawned window inherit the custom protocol AND carry its own IPC channel,
|
|
77
|
+
plus the daemon multiplexing IPC and broadcasting events to N channels.
|
|
78
|
+
Separate, cross-component project — do last, only if the read-only
|
|
79
|
+
tear-off proves insufficient.
|
|
80
|
+
|
|
81
|
+
Start at stage 1. Each stage is independently shippable.
|
|
@@ -19,7 +19,7 @@ These are parsed by mailx before dispatch, so they apply to all three scopes:
|
|
|
19
19
|
| `from:bob` | Match sender substring |
|
|
20
20
|
| `to:eleanor` | Match recipient substring |
|
|
21
21
|
| `subject:lunch` | Match subject substring (everything until the next `keyword:` or end) |
|
|
22
|
-
| `/regex/` | Client-side regex over the currently-visible rows. Local only — never sent to the server. |
|
|
22
|
+
| `/regex/` | Client-side regex over the currently-visible rows. **Case-insensitive.** Local only — never sent to the server. |
|
|
23
23
|
|
|
24
24
|
The remaining (unqualified) text becomes the **body** search term in the server query, or a free-text FTS5 phrase in local mode.
|
|
25
25
|
|
|
@@ -80,6 +80,10 @@ So if you typed `bob AND eleanor` on bobma and got results, what likely happened
|
|
|
80
80
|
|
|
81
81
|
That explains the asymmetry you may have noticed: `bob AND eleanor` "worked" against bobma (literal-text coincidence) but not against Gmail (no IMAP/API call ever fired).
|
|
82
82
|
|
|
83
|
+
## Case sensitivity
|
|
84
|
+
|
|
85
|
+
All three modes are **case-insensitive** — local FTS5, IMAP server SEARCH, and `/regex/` filtering alike. `Lunch`, `lunch`, and `LUNCH` match the same messages. There's no way to force a case-sensitive search.
|
|
86
|
+
|
|
83
87
|
## Limitations
|
|
84
88
|
|
|
85
89
|
- **No regex on the server side** (any provider). `/pattern/` only filters the visible local rows.
|