@bobfrankston/rmfmail 1.2.173 → 1.2.177

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.
Files changed (45) hide show
  1. package/TODO.md +3 -3
  2. package/bin/build-rmfshare-exe.js +79 -0
  3. package/bin/mailx.js +52 -1
  4. package/bin/mailx.js.map +1 -1
  5. package/bin/mailx.ts +51 -1
  6. package/bin/rmfshare-src/Program.cs +225 -0
  7. package/bin/rmfshare-src/rmfshare.csproj +36 -0
  8. package/bin/rmfshare.exe +0 -0
  9. package/bin/share-target.js +314 -0
  10. package/bin/share-target.js.map +1 -0
  11. package/bin/share-target.ts +331 -0
  12. package/client/app.bundle.js +70 -9
  13. package/client/app.bundle.js.map +3 -3
  14. package/client/app.js +69 -0
  15. package/client/app.js.map +1 -1
  16. package/client/app.ts +72 -0
  17. package/client/components/alarms.js +19 -9
  18. package/client/components/alarms.js.map +1 -1
  19. package/client/components/alarms.ts +21 -7
  20. package/client/compose/compose.bundle.js +4 -0
  21. package/client/compose/compose.bundle.js.map +2 -2
  22. package/client/lib/api-client.js +7 -0
  23. package/client/lib/api-client.js.map +1 -1
  24. package/client/lib/api-client.ts +12 -0
  25. package/client/package.json +1 -1
  26. package/package.json +6 -6
  27. package/packages/mailx-imap/package-lock.json +2 -2
  28. package/packages/mailx-imap/package.json +1 -1
  29. package/packages/mailx-service/index.d.ts +22 -0
  30. package/packages/mailx-service/index.d.ts.map +1 -1
  31. package/packages/mailx-service/index.js +82 -0
  32. package/packages/mailx-service/index.js.map +1 -1
  33. package/packages/mailx-service/index.ts +76 -0
  34. package/packages/mailx-service/jsonrpc.js +2 -0
  35. package/packages/mailx-service/jsonrpc.js.map +1 -1
  36. package/packages/mailx-service/jsonrpc.ts +2 -0
  37. package/packages/mailx-service/package.json +1 -1
  38. package/packages/mailx-settings/package.json +1 -1
  39. package/packages/mailx-store/package.json +1 -1
  40. package/packages/mailx-store-web/package.json +1 -1
  41. package/packages/mailx-types/mailx-api.d.ts +3 -0
  42. package/packages/mailx-types/mailx-api.d.ts.map +1 -1
  43. package/packages/mailx-types/mailx-api.ts +3 -0
  44. package/packages/mailx-types/package.json +1 -1
  45. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-7284 → node_modules.npmglobalize-stash-69680}/.package-lock.json +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share-target.js","sourceRoot":"","sources":["share-target.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Windows Share-target integration (C46).
3
+ *
4
+ * Share flow when rmfmail is picked in the Windows Share sheet:
5
+ *
6
+ * 1. Windows activates rmfshare.exe (bin/rmfshare-src/, declared in the
7
+ * AppxManifest registered by `rmfmail -register-share`).
8
+ * 2. rmfshare.exe copies shared files to ~/.rmfmail/share-staging/<stamp>/,
9
+ * writes ~/.rmfmail/pending-share.json (atomic rename), and launches
10
+ * `node mailx.js` — which exits at the instance check if a daemon
11
+ * already runs (its fs.watch on the pending file has fired) or becomes
12
+ * the daemon (the client's startup poll consumes the file).
13
+ * 3. The daemon loads the staged files as base64 and pushes an `openShare`
14
+ * event; the client opens compose with the shared content attached.
15
+ *
16
+ * Same pending-file indirection as the P115 mailto handler (bin/mailto.ts) —
17
+ * no IPC socket needed, one share = one file write.
18
+ *
19
+ * Registration is a "loose" AppX deployment: a staged folder holding
20
+ * rmfshare.exe + AppxManifest.xml + Assets, registered per-user via
21
+ * `Add-AppxPackage -Register`. That requires Developer Mode (unsigned
22
+ * manifest) — the registration path checks and says so. No MSIX signing,
23
+ * no Store, no admin.
24
+ */
25
+
26
+ import fs from "node:fs";
27
+ import path from "node:path";
28
+ import { execFileSync } from "node:child_process";
29
+
30
+ export interface PendingShareFile {
31
+ name: string;
32
+ /** Absolute path inside ~/.rmfmail/share-staging/<stamp>/ */
33
+ path: string;
34
+ size: number;
35
+ }
36
+
37
+ export interface PendingShareData {
38
+ title: string;
39
+ text: string;
40
+ url: string;
41
+ files: PendingShareFile[];
42
+ writtenAt: number;
43
+ }
44
+
45
+ export interface ShareAttachment {
46
+ filename: string;
47
+ mimeType: string;
48
+ dataBase64: string;
49
+ }
50
+
51
+ /** Attachments larger than this are skipped (typical SMTP servers reject
52
+ * messages this size anyway); the skip is reported, never silent. */
53
+ export const SHARE_ATTACHMENT_MAX_BYTES = 25 * 1024 * 1024;
54
+
55
+ const IDENTITY_NAME = "BobFrankston.rmfmail";
56
+
57
+ function rmfmailDir(): string {
58
+ const home = process.env.USERPROFILE || process.env.HOME || ".";
59
+ return path.join(home, ".rmfmail");
60
+ }
61
+
62
+ export function getPendingShareFile(): string {
63
+ return path.join(rmfmailDir(), "pending-share.json");
64
+ }
65
+
66
+ /** Read + delete the pending-share file. One-shot, same contract as
67
+ * consumePendingMailto: caller acts on the result, the file is gone either
68
+ * way, stale files (crashed handoff) are dropped. */
69
+ export function consumePendingShare(): PendingShareData | null {
70
+ const target = getPendingShareFile();
71
+ let raw: string;
72
+ try { raw = fs.readFileSync(target, "utf-8"); }
73
+ catch { return null; }
74
+ try { fs.unlinkSync(target); } catch { /* */ }
75
+ let parsed: PendingShareData;
76
+ try { parsed = JSON.parse(raw) as PendingShareData; }
77
+ catch { return null; }
78
+ const age = Date.now() - (parsed.writtenAt || 0);
79
+ if (age > 10 * 60_000) {
80
+ // Crashed handoff — user shared but nothing consumed it for 10
81
+ // minutes. Drop it (staging cleanup reaps the copied files).
82
+ return null;
83
+ }
84
+ parsed.title = parsed.title || "";
85
+ parsed.text = parsed.text || "";
86
+ parsed.url = parsed.url || "";
87
+ parsed.files = Array.isArray(parsed.files) ? parsed.files : [];
88
+ return parsed;
89
+ }
90
+
91
+ const MIME_BY_EXT: Record<string, string> = {
92
+ ".pdf": "application/pdf",
93
+ ".png": "image/png",
94
+ ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
95
+ ".gif": "image/gif",
96
+ ".webp": "image/webp",
97
+ ".bmp": "image/bmp",
98
+ ".svg": "image/svg+xml",
99
+ ".heic": "image/heic",
100
+ ".txt": "text/plain", ".log": "text/plain", ".md": "text/plain",
101
+ ".html": "text/html", ".htm": "text/html",
102
+ ".csv": "text/csv",
103
+ ".json": "application/json",
104
+ ".xml": "application/xml",
105
+ ".zip": "application/zip",
106
+ ".7z": "application/x-7z-compressed",
107
+ ".doc": "application/msword",
108
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
109
+ ".xls": "application/vnd.ms-excel",
110
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
111
+ ".ppt": "application/vnd.ms-powerpoint",
112
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
113
+ ".eml": "message/rfc822",
114
+ ".ics": "text/calendar",
115
+ ".mp3": "audio/mpeg",
116
+ ".mp4": "video/mp4",
117
+ ".mov": "video/quicktime",
118
+ };
119
+
120
+ export function mimeForFilename(name: string): string {
121
+ return MIME_BY_EXT[path.extname(name).toLowerCase()] || "application/octet-stream";
122
+ }
123
+
124
+ /** Load staged share files as compose-init attachments (base64). Oversized
125
+ * or unreadable files land in `skipped` with a reason — surfaced to the
126
+ * user in the compose body, never dropped silently. Consumed staging dirs
127
+ * are deleted afterwards; the content lives on in the compose draft. */
128
+ export function loadShareAttachments(files: PendingShareFile[]): {
129
+ attachments: ShareAttachment[];
130
+ skipped: { name: string; reason: string }[];
131
+ } {
132
+ const attachments: ShareAttachment[] = [];
133
+ const skipped: { name: string; reason: string }[] = [];
134
+ const dirs = new Set<string>();
135
+ for (const f of files) {
136
+ try {
137
+ const st = fs.statSync(f.path);
138
+ if (st.size > SHARE_ATTACHMENT_MAX_BYTES) {
139
+ skipped.push({ name: f.name, reason: `larger than ${Math.round(SHARE_ATTACHMENT_MAX_BYTES / 1024 / 1024)} MB` });
140
+ } else {
141
+ attachments.push({
142
+ filename: f.name,
143
+ mimeType: mimeForFilename(f.name),
144
+ dataBase64: fs.readFileSync(f.path).toString("base64"),
145
+ });
146
+ }
147
+ dirs.add(path.dirname(f.path));
148
+ } catch (e: any) {
149
+ skipped.push({ name: f.name, reason: e?.message || "unreadable" });
150
+ }
151
+ }
152
+ for (const d of dirs) {
153
+ // Only reap inside our own staging root — a hand-written pending
154
+ // file must never turn this into an arbitrary directory delete.
155
+ if (d.startsWith(path.join(rmfmailDir(), "share-staging") + path.sep)) {
156
+ try { fs.rmSync(d, { recursive: true, force: true }); } catch { /* next startup sweep */ }
157
+ }
158
+ }
159
+ return { attachments, skipped };
160
+ }
161
+
162
+ /** Reap staging dirs older than 2 days — leftovers from shares that never
163
+ * got consumed (daemon crash between copy and compose). Called once at
164
+ * daemon startup. */
165
+ export function cleanupShareStaging(): void {
166
+ const root = path.join(rmfmailDir(), "share-staging");
167
+ let entries: fs.Dirent[];
168
+ try { entries = fs.readdirSync(root, { withFileTypes: true }); }
169
+ catch { return; }
170
+ const cutoff = Date.now() - 2 * 24 * 3600_000;
171
+ for (const e of entries) {
172
+ if (!e.isDirectory()) continue;
173
+ const full = path.join(root, e.name);
174
+ try {
175
+ if (fs.statSync(full).mtimeMs < cutoff) {
176
+ fs.rmSync(full, { recursive: true, force: true });
177
+ }
178
+ } catch { /* in use or already gone */ }
179
+ }
180
+ }
181
+
182
+ // ── Registration (Windows only) ─────────────────────────────────────────
183
+
184
+ function buildAppxManifest(version: string): string {
185
+ // Unsigned loose deployment: Publisher only has to be well-formed, it
186
+ // isn't validated against a certificate. AppListEntry="none" keeps the
187
+ // entry out of the Start menu — rmfshare.exe is a share sink, not an
188
+ // app the user launches.
189
+ return `<?xml version="1.0" encoding="utf-8"?>
190
+ <Package
191
+ xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
192
+ xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
193
+ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
194
+ IgnorableNamespaces="uap rescap">
195
+ <Identity Name="${IDENTITY_NAME}" Publisher="CN=Bob Frankston" Version="${version}" ProcessorArchitecture="x64" />
196
+ <Properties>
197
+ <DisplayName>rmfmail</DisplayName>
198
+ <PublisherDisplayName>Bob Frankston</PublisherDisplayName>
199
+ <Logo>Assets\\logo150.png</Logo>
200
+ </Properties>
201
+ <Dependencies>
202
+ <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
203
+ </Dependencies>
204
+ <Resources>
205
+ <Resource Language="en-us" />
206
+ </Resources>
207
+ <Applications>
208
+ <Application Id="rmfmail" Executable="rmfshare.exe" EntryPoint="Windows.FullTrustApplication">
209
+ <uap:VisualElements DisplayName="rmfmail" Description="Share to rmfmail"
210
+ BackgroundColor="transparent"
211
+ Square150x150Logo="Assets\\logo150.png" Square44x44Logo="Assets\\logo44.png"
212
+ AppListEntry="none" />
213
+ <Extensions>
214
+ <uap:Extension Category="windows.shareTarget">
215
+ <uap:ShareTarget Description="Share to rmfmail">
216
+ <uap:SupportedFileTypes>
217
+ <uap:SupportsAnyFileType />
218
+ </uap:SupportedFileTypes>
219
+ <uap:DataFormat>Text</uap:DataFormat>
220
+ <uap:DataFormat>WebLink</uap:DataFormat>
221
+ <uap:DataFormat>StorageItems</uap:DataFormat>
222
+ </uap:ShareTarget>
223
+ </uap:Extension>
224
+ </Extensions>
225
+ </Application>
226
+ </Applications>
227
+ <Capabilities>
228
+ <rescap:Capability Name="runFullTrust" />
229
+ </Capabilities>
230
+ </Package>
231
+ `;
232
+ }
233
+
234
+ function powershell(script: string): string {
235
+ return execFileSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", script],
236
+ { stdio: ["pipe", "pipe", "pipe"] }).toString();
237
+ }
238
+
239
+ function developerModeEnabled(): boolean {
240
+ try {
241
+ const out = powershell(
242
+ "(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock' -ErrorAction SilentlyContinue).AllowDevelopmentWithoutDevLicense");
243
+ return out.trim() === "1";
244
+ } catch { return false; }
245
+ }
246
+
247
+ /** Register (or unregister) rmfmail as a Windows Share target. Mirrors
248
+ * registerMailtoWindows in mailx.ts: returns outcome text instead of
249
+ * printing so the CLI and any future Settings toggle share one path. */
250
+ export function registerShareWindows(unregister: boolean): { ok: boolean; message: string } {
251
+ const lines: string[] = [];
252
+
253
+ if (unregister) {
254
+ try {
255
+ powershell(`Get-AppxPackage '${IDENTITY_NAME}' | Remove-AppxPackage`);
256
+ lines.push("rmfmail removed from the Windows Share menu.");
257
+ } catch (e: any) {
258
+ return { ok: false, message: `Remove-AppxPackage failed: ${e?.stderr?.toString?.() || e.message}` };
259
+ }
260
+ return { ok: true, message: lines.join("\n") };
261
+ }
262
+
263
+ const pkgExe = path.join(import.meta.dirname, "rmfshare.exe");
264
+ if (!fs.existsSync(pkgExe)) {
265
+ return { ok: false, message: `rmfshare.exe not found at ${pkgExe} — package was built without it. Re-run 'npm run build' on the build machine.` };
266
+ }
267
+ if (!developerModeEnabled()) {
268
+ return {
269
+ ok: false,
270
+ message: "Registering an unsigned Share target needs Windows Developer Mode:\n" +
271
+ " Settings → System → For developers → Developer Mode → On\n" +
272
+ "then re-run `rmfmail -register-share`.",
273
+ };
274
+ }
275
+
276
+ // Stage under %LOCALAPPDATA%\rmfmail\share-target — NOT inside
277
+ // node_modules: the registered package points at absolute paths, and an
278
+ // npm upgrade replacing node_modules would strand the registration.
279
+ const stageDir = path.join(process.env.LOCALAPPDATA || rmfmailDir(), "rmfmail", "share-target");
280
+ const assetsDir = path.join(stageDir, "Assets");
281
+ fs.mkdirSync(assetsDir, { recursive: true });
282
+ fs.copyFileSync(pkgExe, path.join(stageDir, "rmfshare.exe"));
283
+
284
+ // Manifest logos scaled from the app icon at register time (System.
285
+ // Drawing via PowerShell — registration is already a Windows-only,
286
+ // PowerShell-driven path).
287
+ const iconPng = path.join(import.meta.dirname, "..", "client", "icon.png");
288
+ try {
289
+ powershell(`
290
+ Add-Type -AssemblyName System.Drawing
291
+ $src = [System.Drawing.Image]::FromFile('${iconPng.replace(/'/g, "''")}')
292
+ foreach ($s in 44,150) {
293
+ $bmp = New-Object System.Drawing.Bitmap $s, $s
294
+ $g = [System.Drawing.Graphics]::FromImage($bmp)
295
+ $g.InterpolationMode = 'HighQualityBicubic'
296
+ $g.DrawImage($src, 0, 0, $s, $s)
297
+ $bmp.Save('${assetsDir.replace(/'/g, "''")}\\logo' + $s + '.png', [System.Drawing.Imaging.ImageFormat]::Png)
298
+ $g.Dispose(); $bmp.Dispose()
299
+ }
300
+ $src.Dispose()`);
301
+ } catch (e: any) {
302
+ return { ok: false, message: `Icon scaling failed: ${e?.stderr?.toString?.() || e.message}` };
303
+ }
304
+
305
+ // Manifest version tracks the package version so re-registering after an
306
+ // upgrade redeploys cleanly (4th part must be 0 for AppX).
307
+ let pkgVersion = "1.0.0";
308
+ try {
309
+ pkgVersion = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, "..", "package.json"), "utf-8")).version || pkgVersion;
310
+ } catch { /* fall back */ }
311
+ const appxVersion = pkgVersion.split(".").slice(0, 3).map(p => String(parseInt(p, 10) || 0)).join(".") + ".0";
312
+ fs.writeFileSync(path.join(stageDir, "AppxManifest.xml"), buildAppxManifest(appxVersion));
313
+
314
+ try {
315
+ // Remove any prior registration first — Add-AppxPackage -Register
316
+ // rejects same-version re-registration, and downgrades after a
317
+ // rollback would fail outright.
318
+ powershell(`Get-AppxPackage '${IDENTITY_NAME}' | Remove-AppxPackage`);
319
+ } catch { /* not registered — fine */ }
320
+ try {
321
+ powershell(`Add-AppxPackage -Register '${path.join(stageDir, "AppxManifest.xml").replace(/'/g, "''")}'`);
322
+ } catch (e: any) {
323
+ return { ok: false, message: `Add-AppxPackage failed: ${e?.stderr?.toString?.() || e.message}` };
324
+ }
325
+
326
+ lines.push("rmfmail registered as a Windows Share target.");
327
+ lines.push("It now appears in the Share sheet (Explorer right-click → Share, browser Share, etc.).");
328
+ lines.push(`Staged at: ${stageDir}`);
329
+ lines.push("Note: re-run -register-share after major upgrades so the staged rmfshare.exe stays current.");
330
+ return { ok: true, message: lines.join("\n") };
331
+ }
@@ -32,6 +32,7 @@ __export(api_client_exports, {
32
32
  connectEvents: () => connectEvents,
33
33
  connectWebSocket: () => connectWebSocket,
34
34
  consumePendingMailto: () => consumePendingMailto,
35
+ consumePendingShare: () => consumePendingShare,
35
36
  consumePopoutComposeInit: () => consumePopoutComposeInit,
36
37
  copyMessages: () => copyMessages,
37
38
  createCalendarEvent: () => createCalendarEvent,
@@ -618,6 +619,9 @@ function showReminderPopup(opts) {
618
619
  function consumePendingMailto() {
619
620
  return ipc().consumePendingMailto?.() ?? Promise.resolve(null);
620
621
  }
622
+ function consumePendingShare() {
623
+ return ipc().consumePendingShare?.() ?? Promise.resolve(null);
624
+ }
621
625
  function aiTransform(req) {
622
626
  return ipc().aiTransform?.(req) ?? Promise.resolve({ text: "", reason: "AI not available in this host" });
623
627
  }
@@ -6227,9 +6231,8 @@ async function firePopupForItem(item) {
6227
6231
  if (openPopups.has(item.uuid))
6228
6232
  return;
6229
6233
  openPopups.add(item.uuid);
6230
- const buttons = ["snooze", "Dismiss"];
6231
- if (item.htmlLink)
6232
- buttons.push("Open");
6234
+ const openUrl = item.kind === "calendar" ? item.htmlLink || "https://calendar.google.com/" : "https://tasks.google.com/";
6235
+ const buttons = ["snooze", "Dismiss", "Open"];
6233
6236
  if (item.kind === "calendar")
6234
6237
  buttons.push("Delete");
6235
6238
  const icon = item.kind === "calendar" ? "\u{1F4C5}" : "\u2611";
@@ -6243,9 +6246,7 @@ async function firePopupForItem(item) {
6243
6246
  { label: "1d", minutes: 60 * 24 }
6244
6247
  ];
6245
6248
  const presetChips = presets.map((p) => `<button type="button" class="chip" data-snooze-min="${p.minutes}">${p.label}</button>`).join("");
6246
- const actionBtns = ["Dismiss"];
6247
- if (item.htmlLink)
6248
- actionBtns.push("Open");
6249
+ const actionBtns = ["Dismiss", "Open"];
6249
6250
  if (item.kind === "calendar")
6250
6251
  actionBtns.push("Delete");
6251
6252
  const actionHtml = actionBtns.map((b) => `<button type="button" class="action" data-btn="${escapeHtml6(b)}">${escapeHtml6(b)}</button>`).join("");
@@ -6357,12 +6358,14 @@ async function firePopupForItem(item) {
6357
6358
  }
6358
6359
  const api = window.mailxapi;
6359
6360
  const openLink = () => {
6360
- if (!item.htmlLink)
6361
+ if (item.kind === "calendar" && item.htmlLink && api?.openCalendarEvent) {
6362
+ api.openCalendarEvent(item.htmlLink);
6361
6363
  return;
6364
+ }
6362
6365
  if (api?.openExternal)
6363
- api.openExternal(item.htmlLink);
6366
+ api.openExternal(openUrl);
6364
6367
  else
6365
- window.open(item.htmlLink, "_blank");
6368
+ window.open(openUrl, "_blank");
6366
6369
  };
6367
6370
  try {
6368
6371
  switch (r.button) {
@@ -10395,6 +10398,16 @@ onWsEvent((event) => {
10395
10398
  }).catch((e) => console.error("openComposeFromMailto failed:", e?.message || e));
10396
10399
  break;
10397
10400
  }
10401
+ case "openShare": {
10402
+ openComposeFromShare({
10403
+ title: event.title || "",
10404
+ text: event.text || "",
10405
+ url: event.url || "",
10406
+ attachments: Array.isArray(event.attachments) ? event.attachments : [],
10407
+ skipped: Array.isArray(event.skipped) ? event.skipped : []
10408
+ }).catch((e) => console.error("openComposeFromShare failed:", e?.message || e));
10409
+ break;
10410
+ }
10398
10411
  case "debugEval": {
10399
10412
  const { id, code } = event;
10400
10413
  if (!id || typeof code !== "string") break;
@@ -10465,6 +10478,43 @@ async function openComposeFromMailto(m) {
10465
10478
  } catch {
10466
10479
  }
10467
10480
  }
10481
+ async function openComposeFromShare(s) {
10482
+ const accountsP = getAccounts();
10483
+ let subject = s.title;
10484
+ if (!subject && s.attachments.length) {
10485
+ subject = s.attachments[0].filename + (s.attachments.length > 1 ? ` (+${s.attachments.length - 1} more)` : "");
10486
+ }
10487
+ const frame = showComposeOverlay(subject ? `Compose: ${subject}` : "Compose");
10488
+ const accounts = await accountsP;
10489
+ const escape = (str) => str.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
10490
+ const paras = [];
10491
+ if (s.text && s.text !== s.url) {
10492
+ paras.push("<p>" + escape(s.text).replace(/\r?\n/g, "</p><p>") + "</p>");
10493
+ }
10494
+ if (s.url) {
10495
+ const u = escape(s.url);
10496
+ paras.push(`<p><a href="${u}">${u}</a></p>`);
10497
+ }
10498
+ for (const sk of s.skipped) {
10499
+ paras.push(`<p>[Attachment "${escape(sk.name)}" was not added \u2014 ${escape(sk.reason)}]</p>`);
10500
+ }
10501
+ const init = {
10502
+ mode: "new",
10503
+ accountId: accounts[0]?.id || "",
10504
+ to: [],
10505
+ cc: [],
10506
+ bcc: [],
10507
+ subject,
10508
+ bodyHtml: paras.join(""),
10509
+ attachments: s.attachments,
10510
+ accounts: accounts.map((a) => ({ id: a.id, name: a.name, email: a.email, signature: a.signature, sig: a.sig }))
10511
+ };
10512
+ sessionStorage.setItem("composeInit", JSON.stringify(init));
10513
+ try {
10514
+ frame?.contentWindow?.postMessage({ type: "compose-init-ready" }, "*");
10515
+ } catch {
10516
+ }
10517
+ }
10468
10518
  window.addEventListener("keydown", (e) => {
10469
10519
  if (!e.ctrlKey || e.altKey || e.metaKey) return;
10470
10520
  if (e.key !== "r" && e.key !== "R") return;
@@ -11106,6 +11156,17 @@ function applyThreadFilter() {
11106
11156
  console.error("pending mailto pickup failed:", e?.message || e);
11107
11157
  }
11108
11158
  })();
11159
+ (async () => {
11160
+ try {
11161
+ const { consumePendingShare: consumePendingShare2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
11162
+ const data = await consumePendingShare2();
11163
+ if (data && (data.attachments.length || data.text || data.url || data.title || data.skipped.length)) {
11164
+ await openComposeFromShare(data);
11165
+ }
11166
+ } catch (e) {
11167
+ console.error("pending share pickup failed:", e?.message || e);
11168
+ }
11169
+ })();
11109
11170
  optTwoLine?.addEventListener("change", () => {
11110
11171
  const list = document.getElementById("message-list");
11111
11172
  if (optTwoLine.checked) {