@bobfrankston/rmfmail 1.2.173 → 1.2.175

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 (42) 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 +62 -0
  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/compose/compose.bundle.js +4 -0
  18. package/client/compose/compose.bundle.js.map +2 -2
  19. package/client/lib/api-client.js +7 -0
  20. package/client/lib/api-client.js.map +1 -1
  21. package/client/lib/api-client.ts +12 -0
  22. package/client/package.json +1 -1
  23. package/package.json +6 -6
  24. package/packages/mailx-imap/package-lock.json +2 -2
  25. package/packages/mailx-imap/package.json +1 -1
  26. package/packages/mailx-service/index.d.ts +22 -0
  27. package/packages/mailx-service/index.d.ts.map +1 -1
  28. package/packages/mailx-service/index.js +82 -0
  29. package/packages/mailx-service/index.js.map +1 -1
  30. package/packages/mailx-service/index.ts +76 -0
  31. package/packages/mailx-service/jsonrpc.js +2 -0
  32. package/packages/mailx-service/jsonrpc.js.map +1 -1
  33. package/packages/mailx-service/jsonrpc.ts +2 -0
  34. package/packages/mailx-service/package.json +1 -1
  35. package/packages/mailx-settings/package.json +1 -1
  36. package/packages/mailx-store/package.json +1 -1
  37. package/packages/mailx-store-web/package.json +1 -1
  38. package/packages/mailx-types/mailx-api.d.ts +3 -0
  39. package/packages/mailx-types/mailx-api.d.ts.map +1 -1
  40. package/packages/mailx-types/mailx-api.ts +3 -0
  41. package/packages/mailx-types/package.json +1 -1
  42. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-7284 → node_modules.npmglobalize-stash-33656}/.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
  }
@@ -10395,6 +10399,16 @@ onWsEvent((event) => {
10395
10399
  }).catch((e) => console.error("openComposeFromMailto failed:", e?.message || e));
10396
10400
  break;
10397
10401
  }
10402
+ case "openShare": {
10403
+ openComposeFromShare({
10404
+ title: event.title || "",
10405
+ text: event.text || "",
10406
+ url: event.url || "",
10407
+ attachments: Array.isArray(event.attachments) ? event.attachments : [],
10408
+ skipped: Array.isArray(event.skipped) ? event.skipped : []
10409
+ }).catch((e) => console.error("openComposeFromShare failed:", e?.message || e));
10410
+ break;
10411
+ }
10398
10412
  case "debugEval": {
10399
10413
  const { id, code } = event;
10400
10414
  if (!id || typeof code !== "string") break;
@@ -10465,6 +10479,43 @@ async function openComposeFromMailto(m) {
10465
10479
  } catch {
10466
10480
  }
10467
10481
  }
10482
+ async function openComposeFromShare(s) {
10483
+ const accountsP = getAccounts();
10484
+ let subject = s.title;
10485
+ if (!subject && s.attachments.length) {
10486
+ subject = s.attachments[0].filename + (s.attachments.length > 1 ? ` (+${s.attachments.length - 1} more)` : "");
10487
+ }
10488
+ const frame = showComposeOverlay(subject ? `Compose: ${subject}` : "Compose");
10489
+ const accounts = await accountsP;
10490
+ const escape = (str) => str.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
10491
+ const paras = [];
10492
+ if (s.text && s.text !== s.url) {
10493
+ paras.push("<p>" + escape(s.text).replace(/\r?\n/g, "</p><p>") + "</p>");
10494
+ }
10495
+ if (s.url) {
10496
+ const u = escape(s.url);
10497
+ paras.push(`<p><a href="${u}">${u}</a></p>`);
10498
+ }
10499
+ for (const sk of s.skipped) {
10500
+ paras.push(`<p>[Attachment "${escape(sk.name)}" was not added \u2014 ${escape(sk.reason)}]</p>`);
10501
+ }
10502
+ const init = {
10503
+ mode: "new",
10504
+ accountId: accounts[0]?.id || "",
10505
+ to: [],
10506
+ cc: [],
10507
+ bcc: [],
10508
+ subject,
10509
+ bodyHtml: paras.join(""),
10510
+ attachments: s.attachments,
10511
+ accounts: accounts.map((a) => ({ id: a.id, name: a.name, email: a.email, signature: a.signature, sig: a.sig }))
10512
+ };
10513
+ sessionStorage.setItem("composeInit", JSON.stringify(init));
10514
+ try {
10515
+ frame?.contentWindow?.postMessage({ type: "compose-init-ready" }, "*");
10516
+ } catch {
10517
+ }
10518
+ }
10468
10519
  window.addEventListener("keydown", (e) => {
10469
10520
  if (!e.ctrlKey || e.altKey || e.metaKey) return;
10470
10521
  if (e.key !== "r" && e.key !== "R") return;
@@ -11106,6 +11157,17 @@ function applyThreadFilter() {
11106
11157
  console.error("pending mailto pickup failed:", e?.message || e);
11107
11158
  }
11108
11159
  })();
11160
+ (async () => {
11161
+ try {
11162
+ const { consumePendingShare: consumePendingShare2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
11163
+ const data = await consumePendingShare2();
11164
+ if (data && (data.attachments.length || data.text || data.url || data.title || data.skipped.length)) {
11165
+ await openComposeFromShare(data);
11166
+ }
11167
+ } catch (e) {
11168
+ console.error("pending share pickup failed:", e?.message || e);
11169
+ }
11170
+ })();
11109
11171
  optTwoLine?.addEventListener("change", () => {
11110
11172
  const list = document.getElementById("message-list");
11111
11173
  if (optTwoLine.checked) {