@bobfrankston/rmfmail 1.2.231 → 1.2.233

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 (158) hide show
  1. package/client/android-bootstrap.bundle.js +164 -152
  2. package/client/android-bootstrap.bundle.js.map +4 -4
  3. package/client/app.bundle.js +13 -107
  4. package/client/app.bundle.js.map +4 -4
  5. package/client/components/calendar-sidebar.js +1 -1
  6. package/client/components/calendar-sidebar.js.map +1 -1
  7. package/client/components/calendar-sidebar.ts +1 -1
  8. package/client/components/message-list.js +0 -60
  9. package/client/components/message-list.js.map +1 -1
  10. package/client/components/message-list.ts +0 -56
  11. package/client/components/message-viewer.js +3 -2
  12. package/client/components/message-viewer.js.map +1 -1
  13. package/client/components/message-viewer.ts +3 -2
  14. package/client/compose/compose.bundle.js +198 -211
  15. package/client/compose/compose.bundle.js.map +4 -4
  16. package/client/compose/compose.html +0 -8
  17. package/client/compose/compose.js +1 -1
  18. package/client/compose/compose.js.map +1 -1
  19. package/client/compose/compose.ts +1 -1
  20. package/client/compose/editor.js +30 -32
  21. package/client/compose/editor.js.map +1 -1
  22. package/client/compose/editor.ts +27 -29
  23. package/client/compose/ghost-text.js +0 -8
  24. package/client/compose/ghost-text.js.map +1 -1
  25. package/client/compose/ghost-text.ts +0 -6
  26. package/client/compose/spellcheck-core.js +0 -1
  27. package/client/compose/spellcheck-core.js.map +1 -1
  28. package/client/compose/spellcheck-core.ts +0 -1
  29. package/client/index.html +0 -8
  30. package/client/lib/local-service.js +0 -73
  31. package/client/lib/local-service.js.map +1 -1
  32. package/client/lib/local-service.ts +0 -71
  33. package/client/lib/local-store.js +0 -3
  34. package/client/lib/local-store.js.map +1 -1
  35. package/client/lib/local-store.ts +0 -4
  36. package/package.json +1 -3
  37. package/packages/mailx-host/index.d.ts +0 -1
  38. package/packages/mailx-host/index.d.ts.map +1 -1
  39. package/packages/mailx-host/index.js +0 -1
  40. package/packages/mailx-host/index.js.map +1 -1
  41. package/packages/mailx-host/index.ts +0 -1
  42. package/packages/mailx-host/package.json +1 -1
  43. package/packages/mailx-service/index.d.ts.map +1 -1
  44. package/packages/mailx-service/index.js +36 -139
  45. package/packages/mailx-service/index.js.map +1 -1
  46. package/packages/mailx-service/index.ts +36 -148
  47. package/packages/mailx-settings/cloud.d.ts +0 -5
  48. package/packages/mailx-settings/cloud.d.ts.map +1 -1
  49. package/packages/mailx-settings/cloud.js +0 -16
  50. package/packages/mailx-settings/cloud.js.map +1 -1
  51. package/packages/mailx-settings/cloud.ts +0 -12
  52. package/packages/mailx-settings/index.d.ts +0 -3
  53. package/packages/mailx-settings/index.d.ts.map +1 -1
  54. package/packages/mailx-settings/index.js +0 -5
  55. package/packages/mailx-settings/index.js.map +1 -1
  56. package/packages/mailx-settings/index.ts +0 -7
  57. package/packages/mailx-store-web/main-thread-host.d.ts +1 -1
  58. package/packages/mailx-store-web/main-thread-host.d.ts.map +1 -1
  59. package/packages/mailx-store-web/main-thread-host.js +1 -38
  60. package/packages/mailx-store-web/main-thread-host.js.map +1 -1
  61. package/packages/mailx-store-web/main-thread-host.ts +0 -42
  62. package/packages/mailx-store-web/package.json +1 -1
  63. package/packages/mailx-store-web/web-service.d.ts.map +1 -1
  64. package/packages/mailx-store-web/web-service.js +28 -114
  65. package/packages/mailx-store-web/web-service.js.map +1 -1
  66. package/packages/mailx-store-web/web-service.ts +28 -124
  67. package/packages/mailx-store-web/web-settings.d.ts +0 -4
  68. package/packages/mailx-store-web/web-settings.d.ts.map +1 -1
  69. package/packages/mailx-store-web/web-settings.js +0 -29
  70. package/packages/mailx-store-web/web-settings.js.map +1 -1
  71. package/packages/mailx-store-web/web-settings.ts +0 -24
  72. package/packages/mailx-types/index.d.ts +2 -0
  73. package/packages/mailx-types/index.d.ts.map +1 -1
  74. package/packages/mailx-types/index.js +1 -0
  75. package/packages/mailx-types/index.js.map +1 -1
  76. package/packages/mailx-types/index.ts +2 -0
  77. package/packages/mailx-types/mime-build.d.ts +74 -0
  78. package/packages/mailx-types/mime-build.d.ts.map +1 -0
  79. package/packages/mailx-types/mime-build.js +160 -0
  80. package/packages/mailx-types/mime-build.js.map +1 -0
  81. package/packages/mailx-types/mime-build.ts +217 -0
  82. package/packages/mailx-types/package.json +1 -1
  83. package/packages/mailx-types/test/mime-build.test.mjs +200 -0
  84. package/client/components/calendar.js +0 -220
  85. package/client/components/calendar.js.map +0 -1
  86. package/client/components/calendar.ts +0 -213
  87. package/client/components/tasks.js +0 -261
  88. package/client/components/tasks.js.map +0 -1
  89. package/client/components/tasks.ts +0 -234
  90. package/packages/mailx-compose/.gitattributes +0 -10
  91. package/packages/mailx-compose/index.d.ts +0 -21
  92. package/packages/mailx-compose/index.d.ts.map +0 -1
  93. package/packages/mailx-compose/index.js +0 -66
  94. package/packages/mailx-compose/index.js.map +0 -1
  95. package/packages/mailx-compose/index.ts +0 -91
  96. package/packages/mailx-compose/package.json +0 -18
  97. package/packages/mailx-compose/tsconfig.json +0 -9
  98. package/packages/mailx-core/index.d.ts +0 -142
  99. package/packages/mailx-core/index.d.ts.map +0 -1
  100. package/packages/mailx-core/index.js +0 -430
  101. package/packages/mailx-core/index.js.map +0 -1
  102. package/packages/mailx-core/index.ts +0 -450
  103. package/packages/mailx-core/ipc.d.ts +0 -13
  104. package/packages/mailx-core/ipc.d.ts.map +0 -1
  105. package/packages/mailx-core/ipc.js +0 -56
  106. package/packages/mailx-core/ipc.js.map +0 -1
  107. package/packages/mailx-core/ipc.ts +0 -62
  108. package/packages/mailx-core/package.json +0 -18
  109. package/packages/mailx-core/tsconfig.json +0 -9
  110. package/packages/mailx-send/.gitattributes +0 -10
  111. package/packages/mailx-send/README.md +0 -217
  112. package/packages/mailx-send/cli-queue.d.ts +0 -11
  113. package/packages/mailx-send/cli-queue.d.ts.map +0 -1
  114. package/packages/mailx-send/cli-queue.js +0 -59
  115. package/packages/mailx-send/cli-queue.js.map +0 -1
  116. package/packages/mailx-send/cli-queue.ts +0 -62
  117. package/packages/mailx-send/cli-send.d.ts +0 -17
  118. package/packages/mailx-send/cli-send.d.ts.map +0 -1
  119. package/packages/mailx-send/cli-send.js +0 -75
  120. package/packages/mailx-send/cli-send.js.map +0 -1
  121. package/packages/mailx-send/cli-send.ts +0 -83
  122. package/packages/mailx-send/cli.d.ts +0 -22
  123. package/packages/mailx-send/cli.d.ts.map +0 -1
  124. package/packages/mailx-send/cli.js +0 -115
  125. package/packages/mailx-send/cli.js.map +0 -1
  126. package/packages/mailx-send/cli.ts +0 -126
  127. package/packages/mailx-send/index.d.ts +0 -77
  128. package/packages/mailx-send/index.d.ts.map +0 -1
  129. package/packages/mailx-send/index.js +0 -269
  130. package/packages/mailx-send/index.js.map +0 -1
  131. package/packages/mailx-send/index.ts +0 -338
  132. package/packages/mailx-send/mailsend/README.md +0 -133
  133. package/packages/mailx-send/mailsend/cli.d.ts +0 -12
  134. package/packages/mailx-send/mailsend/cli.d.ts.map +0 -1
  135. package/packages/mailx-send/mailsend/cli.js +0 -70
  136. package/packages/mailx-send/mailsend/cli.js.map +0 -1
  137. package/packages/mailx-send/mailsend/cli.ts +0 -81
  138. package/packages/mailx-send/mailsend/index.d.ts +0 -77
  139. package/packages/mailx-send/mailsend/index.d.ts.map +0 -1
  140. package/packages/mailx-send/mailsend/index.js +0 -264
  141. package/packages/mailx-send/mailsend/index.js.map +0 -1
  142. package/packages/mailx-send/mailsend/index.ts +0 -336
  143. package/packages/mailx-send/mailsend/package-lock.json +0 -65
  144. package/packages/mailx-send/mailsend/package.json +0 -26
  145. package/packages/mailx-send/mailsend/tsconfig.json +0 -21
  146. package/packages/mailx-send/package-lock.json +0 -65
  147. package/packages/mailx-send/package.json +0 -37
  148. package/packages/mailx-send/tsconfig.json +0 -21
  149. package/packages/mailx-server/.gitattributes +0 -10
  150. package/packages/mailx-server/index.d.ts +0 -8
  151. package/packages/mailx-server/index.d.ts.map +0 -1
  152. package/packages/mailx-server/index.js +0 -412
  153. package/packages/mailx-server/index.js.map +0 -1
  154. package/packages/mailx-server/index.ts +0 -445
  155. package/packages/mailx-server/package.json +0 -28
  156. package/packages/mailx-server/tsconfig.json +0 -9
  157. package/packages/mailx-server/tsconfig.tsbuildinfo +0 -1
  158. /package/packages/mailx-imap/{node_modules.npmglobalize-stash-7472 → node_modules.npmglobalize-stash-40004}/.package-lock.json +0 -0
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Assemble an RFC 5322 / MIME message. The ONE place that does this.
3
+ *
4
+ * Why it exists: four live code paths built messages by hand — desktop send
5
+ * and draft (`mailx-service`), Android/web send and draft (`mailx-store-web`)
6
+ * — 72% identical line-for-line, plus four more copies in packages nothing
7
+ * could reach. Every fix to the shape had to be found and applied N times,
8
+ * and the N-th was routinely missed:
9
+ *
10
+ * - v1.2.166 envelope scraped quoted headers — "SAME shape in 3 files"
11
+ * - v1.2.179 inline images data: → cid: — had to land in BOTH send paths
12
+ * - v1.2.226 RFC 2047 encoded-words — 7 sites by hand, and the Android
13
+ * path still got only Subject: its From/To/Cc/Bcc were raw
14
+ * interpolation until this unification (2026-08-09), so an
15
+ * accented display name sent from Android was still an EAI
16
+ * message that non-SMTPUTF8 receivers bounce
17
+ *
18
+ * The shape produced is unchanged from what desktop send has emitted since
19
+ * v1.2.179 — this is an extraction, verified byte-for-byte against the old
20
+ * code over a matrix of body/attachment/inline-image/header combinations
21
+ * (see `test/mime-build.test.mjs`), not a rewrite.
22
+ *
23
+ * Structure it emits, narrowest form that carries the content:
24
+ *
25
+ * text only ............... text/plain
26
+ * html .................... multipart/alternative (plain derived + html)
27
+ * html + pasted images .... multipart/related [ alternative, image parts ]
28
+ * any of those + files .... multipart/mixed [ the above, attachments ]
29
+ * draft (simpleHtml) ...... a single text/html part, no alternative
30
+ */
31
+
32
+ import { encodeQuotedPrintable, encodeHeaderWord, encodeAddressHeader, htmlToPlainText } from "./index.js";
33
+ import { extractInlineImages } from "./mime-inline-images.js";
34
+
35
+ export interface MimeAttachment {
36
+ filename?: string;
37
+ mimeType?: string;
38
+ dataBase64?: string;
39
+ }
40
+
41
+ export interface BuildMimeOptions {
42
+ /** Already-formatted address strings ("Name <a@b>", comma-separated). */
43
+ from: string;
44
+ to?: string;
45
+ cc?: string;
46
+ bcc?: string;
47
+ subject: string;
48
+ bodyHtml?: string;
49
+ bodyText?: string;
50
+ attachments?: MimeAttachment[];
51
+ inReplyTo?: string;
52
+ references?: string[];
53
+ /** Supply to reuse an existing id; omit and one is generated from `domain`. */
54
+ messageId?: string;
55
+ /** Domain for the generated Message-ID and for inline-image cid: ids. */
56
+ domain: string;
57
+ /** Extra header lines inserted before MIME-Version (e.g. X-Mailx-Draft-ID). */
58
+ extraHeaders?: string[];
59
+ /** Emit no Message-ID header at all — the draft paths never carried one. */
60
+ omitMessageId?: boolean;
61
+ /** Draft mode: one text/html part, no alternative / related / mixed and no
62
+ * inline-image extraction. A draft is re-parsed by the compose window, not
63
+ * delivered, so the multipart machinery would only get in the way. */
64
+ simpleHtml?: boolean;
65
+ /** Injectable for tests; defaults to `new Date()`. */
66
+ now?: Date;
67
+ /** Injectable for tests so boundaries and ids are reproducible. */
68
+ unique?: () => string;
69
+ }
70
+
71
+ export interface BuiltMime {
72
+ /** The complete message, CRLF line endings, ready for SMTP or APPEND. */
73
+ raw: string;
74
+ /** The Message-ID actually used (empty when `omitMessageId`). */
75
+ messageId: string;
76
+ /** How many data: images became cid: parts — worth logging. */
77
+ inlineImageCount: number;
78
+ }
79
+
80
+ /** Base64 must be folded at ≤76 chars per RFC 2045. */
81
+ function wrap76(s: string): string {
82
+ return s.match(/.{1,76}/g)?.join("\r\n") || s;
83
+ }
84
+
85
+ export function buildMimeMessage(opts: BuildMimeOptions): BuiltMime {
86
+ const now = opts.now || new Date();
87
+ const unique = opts.unique
88
+ || (() => `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`);
89
+ const newBoundary = () => `mailx_${unique()}`;
90
+
91
+ const messageId = opts.omitMessageId
92
+ ? ""
93
+ : (opts.messageId || `<${Date.now()}.${Math.random().toString(36).slice(2)}@${opts.domain}>`);
94
+
95
+ const hasHtml = !!opts.bodyHtml;
96
+
97
+ // ── headers ──
98
+ // RFC 2047 on every header that can carry a display name or free text.
99
+ // Raw 8-bit here makes the whole message EAI (RFC 6530), which a receiver
100
+ // without SMTPUTF8 (RFC 6531) must refuse — one curly apostrophe in a
101
+ // Subject cost three recipients on 2026-08-08.
102
+ const headers: (string | null)[] = [
103
+ `From: ${encodeAddressHeader(opts.from)}`,
104
+ opts.to ? `To: ${encodeAddressHeader(opts.to)}` : null,
105
+ opts.cc ? `Cc: ${encodeAddressHeader(opts.cc)}` : null,
106
+ // Bcc belongs IN a draft — the SEND path strips it, so that a saved
107
+ // draft reopened for editing still shows who was on Bcc.
108
+ opts.bcc ? `Bcc: ${encodeAddressHeader(opts.bcc)}` : null,
109
+ `Subject: ${encodeHeaderWord(opts.subject)}`,
110
+ `Date: ${now.toUTCString()}`,
111
+ messageId ? `Message-ID: ${messageId}` : null,
112
+ opts.inReplyTo ? `In-Reply-To: ${opts.inReplyTo}` : null,
113
+ opts.references?.length ? `References: ${opts.references.join(" ")}` : null,
114
+ ...(opts.extraHeaders || []),
115
+ `MIME-Version: 1.0`,
116
+ ];
117
+ const commonHeaders = headers.filter((h): h is string => h !== null);
118
+
119
+ // ── draft: a single text/html part and nothing else ──
120
+ if (opts.simpleHtml) {
121
+ const body = opts.bodyHtml || opts.bodyText || "";
122
+ const raw = [
123
+ ...commonHeaders,
124
+ `Content-Type: text/html; charset=UTF-8`,
125
+ `Content-Transfer-Encoding: quoted-printable`,
126
+ ].join("\r\n") + `\r\n\r\n` + encodeQuotedPrintable(body);
127
+ return { raw, messageId, inlineImageCount: 0 };
128
+ }
129
+
130
+ // Pasted images live in the editor HTML as data: URIs — fine in a WebView,
131
+ // broken for recipients on Outlook desktop (won't render data: images) and
132
+ // Gmail web (sanitizer strips them). Extract to cid:-referenced parts.
133
+ const extracted = hasHtml
134
+ ? extractInlineImages(opts.bodyHtml!, opts.domain)
135
+ : { html: "", images: [] as ReturnType<typeof extractInlineImages>["images"] };
136
+ const htmlBody = extracted.html;
137
+ const inlineImages = extracted.images;
138
+ // HTML-bodied mail carries a text/plain alternative too: spam filters score
139
+ // HTML-only mail 1–2 points worse, and plain-text readers still exist.
140
+ const textBody = opts.bodyText || (hasHtml ? htmlToPlainText(htmlBody) : "");
141
+ const htmlEncoded = hasHtml ? encodeQuotedPrintable(htmlBody) : "";
142
+ const textEncoded = encodeQuotedPrintable(textBody);
143
+
144
+ /** The body part that carries the message text, without envelope headers,
145
+ * so it can stand alone OR become the first part of a multipart/mixed. */
146
+ const makeInner = (): { headers: string[]; body: string } => {
147
+ if (hasHtml) {
148
+ const altBoundary = newBoundary();
149
+ const body =
150
+ `--${altBoundary}\r\n` +
151
+ `Content-Type: text/plain; charset=UTF-8\r\n` +
152
+ `Content-Transfer-Encoding: quoted-printable\r\n\r\n` +
153
+ `${textEncoded}\r\n` +
154
+ `--${altBoundary}\r\n` +
155
+ `Content-Type: text/html; charset=UTF-8\r\n` +
156
+ `Content-Transfer-Encoding: quoted-printable\r\n\r\n` +
157
+ `${htmlEncoded}\r\n` +
158
+ `--${altBoundary}--\r\n`;
159
+ const alt = {
160
+ headers: [`Content-Type: multipart/alternative; boundary="${altBoundary}"`],
161
+ body,
162
+ };
163
+ if (inlineImages.length === 0) return alt;
164
+ const relBoundary = newBoundary();
165
+ const relParts = [
166
+ `--${relBoundary}\r\n` + alt.headers.join("\r\n") + `\r\n\r\n` + alt.body,
167
+ ...inlineImages.map(im =>
168
+ `--${relBoundary}\r\n` +
169
+ `Content-Type: ${im.mime}\r\n` +
170
+ `Content-ID: <${im.contentId}>\r\n` +
171
+ `Content-Disposition: inline\r\n` +
172
+ `Content-Transfer-Encoding: base64\r\n\r\n` +
173
+ `${wrap76(im.base64)}\r\n`),
174
+ ];
175
+ return {
176
+ headers: [`Content-Type: multipart/related; boundary="${relBoundary}"; type="multipart/alternative"`],
177
+ body: relParts.join("") + `--${relBoundary}--\r\n`,
178
+ };
179
+ }
180
+ return {
181
+ headers: [
182
+ `Content-Type: text/plain; charset=UTF-8`,
183
+ `Content-Transfer-Encoding: quoted-printable`,
184
+ ],
185
+ body: textEncoded,
186
+ };
187
+ };
188
+
189
+ const attachments = opts.attachments || [];
190
+ let raw: string;
191
+ if (attachments.length > 0) {
192
+ const mixedBoundary = newBoundary();
193
+ const inner = makeInner();
194
+ const parts: string[] = [
195
+ `--${mixedBoundary}\r\n` + inner.headers.join("\r\n") + `\r\n\r\n` + inner.body,
196
+ ];
197
+ for (const att of attachments) {
198
+ // A quote or newline in a filename would break out of the header.
199
+ const filename = (att.filename || "attachment").replace(/[\r\n"]/g, "_");
200
+ const mime = att.mimeType || "application/octet-stream";
201
+ parts.push(
202
+ `--${mixedBoundary}\r\n` +
203
+ `Content-Type: ${mime}; name="${filename}"\r\n` +
204
+ `Content-Disposition: attachment; filename="${filename}"\r\n` +
205
+ `Content-Transfer-Encoding: base64\r\n\r\n` +
206
+ `${wrap76(att.dataBase64 || "")}\r\n`
207
+ );
208
+ }
209
+ raw = [...commonHeaders, `Content-Type: multipart/mixed; boundary="${mixedBoundary}"`].join("\r\n")
210
+ + `\r\n\r\n` + parts.join("") + `--${mixedBoundary}--\r\n`;
211
+ } else {
212
+ const inner = makeInner();
213
+ raw = [...commonHeaders, ...inner.headers].join("\r\n") + `\r\n\r\n` + inner.body;
214
+ }
215
+
216
+ return { raw, messageId, inlineImageCount: inlineImages.length };
217
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-types",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -0,0 +1,200 @@
1
+ /**
2
+ * buildMimeMessage must be a pure EXTRACTION of the send/draft assembly that
3
+ * lived in mailx-service and mailx-store-web — same bytes out, not "close
4
+ * enough". This compares it against a verbatim copy of the pre-2026-08-09
5
+ * desktop code (below, `legacyBuild`) across the shapes the app actually
6
+ * sends. Volatile parts (boundary strings, Message-ID, Date) are injected or
7
+ * normalised so a diff means a real structural difference.
8
+ *
9
+ * Run: node packages/mailx-types/test/mime-build.test.mjs
10
+ */
11
+ import assert from "node:assert";
12
+ import {
13
+ buildMimeMessage, encodeQuotedPrintable, encodeHeaderWord,
14
+ encodeAddressHeader, htmlToPlainText, extractInlineImages,
15
+ } from "../index.js";
16
+
17
+ // ── the code as it stood in mailx-service/index.ts before the unification ──
18
+ function legacyBuild(o) {
19
+ const hasHtml = !!o.bodyHtml;
20
+ const domain = o.domain;
21
+ const messageId = o.messageId;
22
+ const extracted = hasHtml ? extractInlineImages(o.bodyHtml, domain) : { html: "", images: [] };
23
+ const htmlBody = extracted.html;
24
+ const inlineImages = extracted.images;
25
+ const textBody = o.bodyText || (hasHtml ? htmlToPlainText(htmlBody) : "");
26
+ const htmlEncoded = hasHtml ? encodeQuotedPrintable(htmlBody) : "";
27
+ const textEncoded = encodeQuotedPrintable(textBody);
28
+ const hasAttachments = Array.isArray(o.attachments) && o.attachments.length > 0;
29
+ const commonHeaders = [
30
+ `From: ${encodeAddressHeader(o.from)}`, `To: ${encodeAddressHeader(o.to)}`,
31
+ o.cc ? `Cc: ${encodeAddressHeader(o.cc)}` : null,
32
+ o.bcc ? `Bcc: ${encodeAddressHeader(o.bcc)}` : null,
33
+ `Subject: ${encodeHeaderWord(o.subject)}`, `Date: ${o.now.toUTCString()}`,
34
+ `Message-ID: ${messageId}`,
35
+ o.inReplyTo ? `In-Reply-To: ${o.inReplyTo}` : null,
36
+ o.references?.length ? `References: ${o.references.join(" ")}` : null,
37
+ `MIME-Version: 1.0`,
38
+ ].filter(h => h !== null);
39
+
40
+ let rawMessage;
41
+ let n = 0;
42
+ const newBoundary = () => `mailx_B${n++}`;
43
+ const wrap76 = (s) => s.match(/.{1,76}/g)?.join("\r\n") || s;
44
+ const makeInner = () => {
45
+ if (hasHtml) {
46
+ const altBoundary = newBoundary();
47
+ const body =
48
+ `--${altBoundary}\r\n` +
49
+ `Content-Type: text/plain; charset=UTF-8\r\n` +
50
+ `Content-Transfer-Encoding: quoted-printable\r\n\r\n` +
51
+ `${textEncoded}\r\n` +
52
+ `--${altBoundary}\r\n` +
53
+ `Content-Type: text/html; charset=UTF-8\r\n` +
54
+ `Content-Transfer-Encoding: quoted-printable\r\n\r\n` +
55
+ `${htmlEncoded}\r\n` +
56
+ `--${altBoundary}--\r\n`;
57
+ const alt = { headers: [`Content-Type: multipart/alternative; boundary="${altBoundary}"`], body };
58
+ if (inlineImages.length === 0) return alt;
59
+ const relBoundary = newBoundary();
60
+ const relParts = [
61
+ `--${relBoundary}\r\n` + alt.headers.join("\r\n") + `\r\n\r\n` + alt.body,
62
+ ...inlineImages.map(im =>
63
+ `--${relBoundary}\r\n` +
64
+ `Content-Type: ${im.mime}\r\n` +
65
+ `Content-ID: <${im.contentId}>\r\n` +
66
+ `Content-Disposition: inline\r\n` +
67
+ `Content-Transfer-Encoding: base64\r\n\r\n` +
68
+ `${wrap76(im.base64)}\r\n`),
69
+ ];
70
+ return {
71
+ headers: [`Content-Type: multipart/related; boundary="${relBoundary}"; type="multipart/alternative"`],
72
+ body: relParts.join("") + `--${relBoundary}--\r\n`,
73
+ };
74
+ }
75
+ return {
76
+ headers: [`Content-Type: text/plain; charset=UTF-8`, `Content-Transfer-Encoding: quoted-printable`],
77
+ body: textEncoded,
78
+ };
79
+ };
80
+ if (hasAttachments) {
81
+ const mixedBoundary = newBoundary();
82
+ const inner = makeInner();
83
+ const parts = [`--${mixedBoundary}\r\n` + inner.headers.join("\r\n") + `\r\n\r\n` + inner.body];
84
+ for (const att of o.attachments) {
85
+ const filename = (att.filename || "attachment").replace(/[\r\n"]/g, "_");
86
+ const mime = att.mimeType || "application/octet-stream";
87
+ parts.push(
88
+ `--${mixedBoundary}\r\n` +
89
+ `Content-Type: ${mime}; name="${filename}"\r\n` +
90
+ `Content-Disposition: attachment; filename="${filename}"\r\n` +
91
+ `Content-Transfer-Encoding: base64\r\n\r\n` +
92
+ `${wrap76(att.dataBase64 || "")}\r\n`
93
+ );
94
+ }
95
+ rawMessage = [...commonHeaders, `Content-Type: multipart/mixed; boundary="${mixedBoundary}"`].join("\r\n")
96
+ + `\r\n\r\n` + parts.join("") + `--${mixedBoundary}--\r\n`;
97
+ } else {
98
+ const inner = makeInner();
99
+ rawMessage = [...commonHeaders, ...inner.headers].join("\r\n") + `\r\n\r\n` + inner.body;
100
+ }
101
+ return rawMessage;
102
+ }
103
+
104
+ // ── the legacy DRAFT assembly, verbatim ──
105
+ function legacyDraft(o) {
106
+ const body = o.bodyHtml || o.bodyText || "";
107
+ return [
108
+ `From: ${encodeAddressHeader(o.from)}`,
109
+ o.to ? `To: ${encodeAddressHeader(o.to)}` : null,
110
+ o.cc ? `Cc: ${encodeAddressHeader(o.cc)}` : null,
111
+ o.bcc ? `Bcc: ${encodeAddressHeader(o.bcc)}` : null,
112
+ `Subject: ${encodeHeaderWord(o.subject)}`, `Date: ${o.now.toUTCString()}`,
113
+ `X-Mailx-Draft-ID: ${o.draftId}`,
114
+ `MIME-Version: 1.0`, `Content-Type: text/html; charset=UTF-8`,
115
+ `Content-Transfer-Encoding: quoted-printable`,
116
+ ].filter(h => h !== null).join("\r\n") + `\r\n\r\n` + encodeQuotedPrintable(body);
117
+ }
118
+
119
+ const PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
120
+ const now = new Date("Sat, 09 Aug 2026 13:30:00 GMT");
121
+ const messageId = "<fixed.id@bob.ma>";
122
+ const base = { from: "Bob <bob@bob.ma>", to: "A <a@x.com>", subject: "hello", domain: "bob.ma", now, messageId };
123
+
124
+ const cases = [
125
+ ["plain text only", { ...base, bodyText: "just text\nwith a line" }],
126
+ ["html (alternative)", { ...base, bodyHtml: "<p>hi <b>there</b></p>" }],
127
+ ["html + explicit text", { ...base, bodyHtml: "<p>hi</p>", bodyText: "hi" }],
128
+ ["html + inline image (related)", { ...base, bodyHtml: `<p>see</p><img src="data:image/png;base64,${PNG}">` }],
129
+ ["text + attachment (mixed)", { ...base, bodyText: "see file", attachments: [{ filename: "a.txt", mimeType: "text/plain", dataBase64: "aGVsbG8=" }] }],
130
+ ["html + inline image + 2 attachments", {
131
+ ...base, bodyHtml: `<p>x</p><img src="data:image/png;base64,${PNG}">`,
132
+ attachments: [
133
+ { filename: 'we"ird\nname.pdf', mimeType: "application/pdf", dataBase64: PNG },
134
+ { dataBase64: "AAAA" },
135
+ ],
136
+ }],
137
+ ["cc + bcc + threading headers", {
138
+ ...base, bodyText: "t", cc: "C <c@x.com>", bcc: "D <d@x.com>",
139
+ inReplyTo: "<prev@x.com>", references: ["<a@x.com>", "<b@x.com>"],
140
+ }],
141
+ ["non-ASCII display name and subject", {
142
+ ...base, from: "Bøb Frankstøn <bob@bob.ma>", to: "Renée <r@x.com>",
143
+ subject: "Canada’s fastest — really", bodyText: "t",
144
+ }],
145
+ ["long base64 folding", { ...base, bodyText: "t", attachments: [{ filename: "big.bin", dataBase64: "Q".repeat(500) }] }],
146
+ ];
147
+
148
+ /** extractInlineImages mints a random cid: id per call, so two runs over the
149
+ * same input differ there by design. Normalise it for the comparison — what
150
+ * must hold is that the SAME id appears in the <img src> and in that part's
151
+ * Content-ID, which is asserted separately below. */
152
+ const normCid = (s) => s.replace(/img\d+\.[a-z0-9]+@/g, "img.CID@");
153
+
154
+ let pass = 0;
155
+ for (const [name, o] of cases) {
156
+ let n = 0;
157
+ const got = buildMimeMessage({ ...o, unique: () => `B${n++}` }).raw;
158
+ const want = legacyBuild(o);
159
+ assert.strictEqual(normCid(got), normCid(want),
160
+ `MISMATCH in "${name}"\n--- new ---\n${got}\n--- old ---\n${want}`);
161
+ // cid self-consistency: every id occurs exactly twice (src + Content-ID)
162
+ const ids = [...got.matchAll(/img\d+\.[a-z0-9]+@[a-z0-9.]+/g)].map(m => m[0]);
163
+ for (const id of new Set(ids)) {
164
+ assert.strictEqual(ids.filter(x => x === id).length, 2,
165
+ `cid ${id} should appear in both the <img src> and its Content-ID in "${name}"`);
166
+ }
167
+ pass++;
168
+ }
169
+
170
+ // draft path
171
+ for (const [name, o] of [
172
+ ["draft html", { ...base, bodyHtml: "<p>draft</p>", draftId: "mailx-draft-1" }],
173
+ ["draft with bcc + alias From", { ...base, from: "Alias <alias@bob.ma>", bodyHtml: "<p>d</p>", cc: "c@x.com", bcc: "b@x.com", draftId: "mailx-draft-2" }],
174
+ ["draft non-ASCII recipient", { ...base, to: "Renée <r@x.com>", bodyHtml: "<p>d</p>", draftId: "mailx-draft-3" }],
175
+ ]) {
176
+ const got = buildMimeMessage({
177
+ ...o, simpleHtml: true, omitMessageId: true,
178
+ extraHeaders: [`X-Mailx-Draft-ID: ${o.draftId}`],
179
+ }).raw;
180
+ const want = legacyDraft(o);
181
+ assert.strictEqual(got, want, `MISMATCH in "${name}"\n--- new ---\n${got}\n--- old ---\n${want}`);
182
+ pass++;
183
+ }
184
+
185
+ // Structural guarantees that must hold regardless of the legacy comparison.
186
+ const withImg = buildMimeMessage({ ...base, bodyHtml: `<img src="data:image/png;base64,${PNG}">`, unique: (() => { let i = 0; return () => `B${i++}`; })() });
187
+ assert.match(withImg.raw, /multipart\/related/, "inline image must produce multipart/related");
188
+ assert.match(withImg.raw, /Content-ID: </, "inline image must carry a Content-ID");
189
+ assert.strictEqual(withImg.inlineImageCount, 1);
190
+ assert.ok(!/data:image\/png;base64/.test(withImg.raw), "no data: URI may survive into the wire format");
191
+ pass++;
192
+
193
+ const eai = buildMimeMessage({ ...base, from: "Bøb <bob@bob.ma>", to: "Renée <r@x.com>", subject: "Canada’s", bodyText: "t" });
194
+ for (const line of eai.raw.split("\r\n")) {
195
+ if (!line) break; // headers end at the blank line
196
+ assert.ok(!/[€-￿]/.test(line), `8-bit byte survived in header: ${line}`);
197
+ }
198
+ pass++;
199
+
200
+ console.log(`mime-build: ${pass} checks passed`);
@@ -1,220 +0,0 @@
1
- /**
2
- * Calendar pane — Thunderbird Lightning "Today Pane" style.
3
- *
4
- * Phase-1 scope (this commit): a usable mini-month + upcoming-events list
5
- * sourced from a local-only event store. Google Calendar sync is a separate
6
- * step (oauthsupport scope is already wired; the dispatcher branch + provider
7
- * are owed). UI lands first so the data layer can be slotted in without
8
- * UX rework.
9
- */
10
- const LOCAL_STORE_KEY = "mailx-cal-events-v1";
11
- let isOpen = false;
12
- function loadEvents() {
13
- try {
14
- const raw = localStorage.getItem(LOCAL_STORE_KEY);
15
- if (!raw)
16
- return [];
17
- const arr = JSON.parse(raw);
18
- return Array.isArray(arr) ? arr : [];
19
- }
20
- catch {
21
- return [];
22
- }
23
- }
24
- function saveEvents(events) {
25
- try {
26
- localStorage.setItem(LOCAL_STORE_KEY, JSON.stringify(events));
27
- }
28
- catch { /* */ }
29
- }
30
- export async function openCalendar() {
31
- if (isOpen)
32
- return;
33
- isOpen = true;
34
- const backdrop = document.createElement("div");
35
- backdrop.className = "mailx-modal-backdrop";
36
- const panel = document.createElement("div");
37
- panel.className = "mailx-modal mailx-modal-wide";
38
- panel.innerHTML = `
39
- <div class="mailx-modal-title">
40
- <span class="mailx-modal-title-text">Calendar</span>
41
- <button type="button" class="mailx-modal-close" id="cal-close" title="Close (Esc)" aria-label="Close">&times;</button>
42
- </div>
43
- <div class="cal-grid">
44
- <div class="cal-month" id="cal-month"></div>
45
- <div class="cal-upcoming">
46
- <div class="cal-section-title">Upcoming</div>
47
- <div id="cal-events"></div>
48
- <button type="button" class="mailx-modal-btn mailx-modal-btn-primary" id="cal-new" style="margin-top:12px;">+ New event</button>
49
- </div>
50
- </div>
51
- <div class="mailx-modal-buttons">
52
- <span class="mailx-modal-spacer"></span>
53
- <span class="cal-source-note">Local-only · Google Calendar sync pending</span>
54
- <button type="button" class="mailx-modal-btn" data-action="close">Close</button>
55
- </div>`;
56
- backdrop.appendChild(panel);
57
- document.body.appendChild(backdrop);
58
- const monthEl = panel.querySelector("#cal-month");
59
- const eventsEl = panel.querySelector("#cal-events");
60
- let viewYear = new Date().getFullYear();
61
- let viewMonth = new Date().getMonth();
62
- let selectedDay = new Date();
63
- let events = loadEvents();
64
- const renderMonth = () => {
65
- const first = new Date(viewYear, viewMonth, 1);
66
- const lastDay = new Date(viewYear, viewMonth + 1, 0).getDate();
67
- const firstWeekday = first.getDay();
68
- const monthName = first.toLocaleDateString(undefined, { month: "long", year: "numeric" });
69
- const today = new Date();
70
- const isToday = (y, m, d) => y === today.getFullYear() && m === today.getMonth() && d === today.getDate();
71
- const eventsByDay = new Map();
72
- for (const e of events) {
73
- const d = new Date(e.start);
74
- const k = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
75
- const arr = eventsByDay.get(k) || [];
76
- arr.push(e);
77
- eventsByDay.set(k, arr);
78
- }
79
- let html = `
80
- <div class="cal-nav">
81
- <button type="button" class="cal-nav-btn" data-nav="prev">‹</button>
82
- <span class="cal-nav-title">${monthName}</span>
83
- <button type="button" class="cal-nav-btn" data-nav="today">Today</button>
84
- <button type="button" class="cal-nav-btn" data-nav="next">›</button>
85
- </div>
86
- <div class="cal-month-grid">
87
- ${["S", "M", "T", "W", "T", "F", "S"].map(d => `<span class="cal-dow">${d}</span>`).join("")}`;
88
- for (let i = 0; i < firstWeekday; i++)
89
- html += `<span class="cal-day cal-day-blank"></span>`;
90
- for (let d = 1; d <= lastDay; d++) {
91
- const k = `${viewYear}-${viewMonth}-${d}`;
92
- const hasEv = eventsByDay.has(k);
93
- const sel = selectedDay.getFullYear() === viewYear && selectedDay.getMonth() === viewMonth && selectedDay.getDate() === d;
94
- const cls = ["cal-day"];
95
- if (isToday(viewYear, viewMonth, d))
96
- cls.push("cal-day-today");
97
- if (sel)
98
- cls.push("cal-day-selected");
99
- if (hasEv)
100
- cls.push("cal-day-has-event");
101
- html += `<button type="button" class="${cls.join(" ")}" data-day="${d}">${d}</button>`;
102
- }
103
- html += `</div>`;
104
- monthEl.innerHTML = html;
105
- monthEl.querySelectorAll("[data-day]").forEach(b => {
106
- b.addEventListener("click", () => {
107
- selectedDay = new Date(viewYear, viewMonth, +b.dataset.day);
108
- renderMonth();
109
- renderEvents();
110
- });
111
- });
112
- monthEl.querySelectorAll(".cal-nav-btn").forEach(b => {
113
- b.addEventListener("click", () => {
114
- const nav = b.dataset.nav;
115
- if (nav === "prev") {
116
- if (--viewMonth < 0) {
117
- viewMonth = 11;
118
- viewYear--;
119
- }
120
- }
121
- else if (nav === "next") {
122
- if (++viewMonth > 11) {
123
- viewMonth = 0;
124
- viewYear++;
125
- }
126
- }
127
- else if (nav === "today") {
128
- const t = new Date();
129
- viewYear = t.getFullYear();
130
- viewMonth = t.getMonth();
131
- selectedDay = t;
132
- }
133
- renderMonth();
134
- renderEvents();
135
- });
136
- });
137
- };
138
- const renderEvents = () => {
139
- const dayStart = new Date(selectedDay.getFullYear(), selectedDay.getMonth(), selectedDay.getDate()).getTime();
140
- const horizon = dayStart + 30 * 86400_000;
141
- const upcoming = events
142
- .filter(e => e.start >= dayStart && e.start < horizon)
143
- .sort((a, b) => a.start - b.start);
144
- if (upcoming.length === 0) {
145
- eventsEl.innerHTML = `<div class="cal-empty">No events in the next 30 days from ${selectedDay.toLocaleDateString()}.</div>`;
146
- return;
147
- }
148
- const fmt = (e) => {
149
- const d = new Date(e.start);
150
- const date = d.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" });
151
- const time = e.allDay ? "all day" : d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
152
- return `${date} · ${time}`;
153
- };
154
- eventsEl.innerHTML = upcoming.map(e => `
155
- <div class="cal-event" data-id="${e.id}">
156
- <div class="cal-event-time">${fmt(e)}</div>
157
- <div class="cal-event-title">${escapeHtml(e.title)}</div>
158
- ${e.location ? `<div class="cal-event-loc">${escapeHtml(e.location)}</div>` : ""}
159
- <button type="button" class="cal-event-del" title="Delete">×</button>
160
- </div>`).join("");
161
- eventsEl.querySelectorAll(".cal-event").forEach(row => {
162
- row.querySelector(".cal-event-del")?.addEventListener("click", () => {
163
- const id = row.dataset.id;
164
- events = events.filter(e => e.id !== id);
165
- saveEvents(events);
166
- renderEvents();
167
- renderMonth();
168
- });
169
- });
170
- };
171
- panel.querySelector("#cal-new")?.addEventListener("click", () => {
172
- const title = prompt("Event title:");
173
- if (!title)
174
- return;
175
- const dateStr = prompt("Date and time (YYYY-MM-DD HH:MM, or YYYY-MM-DD for all-day):",
176
- // Local date components — toISOString() is the UTC date, which
177
- // pre-fills the wrong day for users east of UTC.
178
- `${selectedDay.getFullYear()}-${String(selectedDay.getMonth() + 1).padStart(2, "0")}-${String(selectedDay.getDate()).padStart(2, "0")} 09:00`);
179
- if (!dateStr)
180
- return;
181
- const allDay = !/\d{1,2}:\d{2}/.test(dateStr);
182
- const start = Date.parse(dateStr.replace(" ", "T"));
183
- if (isNaN(start)) {
184
- alert("Couldn't parse that date.");
185
- return;
186
- }
187
- const ev = {
188
- id: `cal-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
189
- title, start, end: start + (allDay ? 86400_000 : 3600_000),
190
- allDay, source: "local",
191
- };
192
- events.push(ev);
193
- saveEvents(events);
194
- renderMonth();
195
- renderEvents();
196
- });
197
- const close = () => {
198
- backdrop.remove();
199
- document.removeEventListener("keydown", onKey, true);
200
- isOpen = false;
201
- };
202
- const onKey = (e) => {
203
- if (e.key === "Escape") {
204
- e.stopPropagation();
205
- e.preventDefault();
206
- close();
207
- }
208
- };
209
- document.addEventListener("keydown", onKey, true);
210
- panel.querySelector("#cal-close").addEventListener("click", close);
211
- panel.querySelector('[data-action="close"]').addEventListener("click", close);
212
- backdrop.addEventListener("mousedown", (e) => { if (e.target === backdrop)
213
- close(); });
214
- renderMonth();
215
- renderEvents();
216
- }
217
- function escapeHtml(s) {
218
- return s.replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#39;" }[c]));
219
- }
220
- //# sourceMappingURL=calendar.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"calendar.js","sourceRoot":"","sources":["calendar.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAC9C,IAAI,MAAM,GAAG,KAAK,CAAC;AAEnB,SAAS,UAAU;IACf,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAClD,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,CAAC;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AAC1B,CAAC;AACD,SAAS,UAAU,CAAC,MAAkB;IAClC,IAAI,CAAC;QAAC,YAAY,CAAC,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAC9B,IAAI,MAAM;QAAE,OAAO;IACnB,MAAM,GAAG,IAAI,CAAC;IAEd,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC/C,QAAQ,CAAC,SAAS,GAAG,sBAAsB,CAAC;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC5C,KAAK,CAAC,SAAS,GAAG,8BAA8B,CAAC;IACjD,KAAK,CAAC,SAAS,GAAG;;;;;;;;;;;;;;;;;eAiBP,CAAC;IACZ,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAc,YAAY,CAAE,CAAC;IAChE,MAAM,QAAQ,GAAG,KAAK,CAAC,aAAa,CAAc,aAAa,CAAE,CAAC;IAElE,IAAI,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxC,IAAI,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;IACtC,IAAI,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC;IAC7B,IAAI,MAAM,GAAG,UAAU,EAAE,CAAC;IAE1B,MAAM,WAAW,GAAG,GAAG,EAAE;QACrB,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,SAAS,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAC/D,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,KAAK,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC1F,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,EAAE,CAChD,CAAC,KAAK,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACjF,MAAM,WAAW,GAAG,IAAI,GAAG,EAAsB,CAAC;QAClD,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC5B,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9D,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACrC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACZ,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC5B,CAAC;QACD,IAAI,IAAI,GAAG;;;8CAG2B,SAAS;;;;;kBAKrC,CAAC,GAAG,EAAC,GAAG,EAAC,GAAG,EAAC,GAAG,EAAC,GAAG,EAAC,GAAG,EAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,yBAAyB,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACjG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC,EAAE;YAAE,IAAI,IAAI,6CAA6C,CAAC;QAC7F,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,GAAG,QAAQ,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,WAAW,CAAC,WAAW,EAAE,KAAK,QAAQ,IAAI,WAAW,CAAC,QAAQ,EAAE,KAAK,SAAS,IAAI,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC1H,MAAM,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;YACxB,IAAI,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC/D,IAAI,GAAG;gBAAE,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YACtC,IAAI,KAAK;gBAAE,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YACzC,IAAI,IAAI,gCAAgC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC;QAC3F,CAAC;QACD,IAAI,IAAI,QAAQ,CAAC;QACjB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;QACzB,OAAO,CAAC,gBAAgB,CAAoB,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YAClE,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;gBAC7B,WAAW,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAI,CAAC,CAAC;gBAC7D,WAAW,EAAE,CAAC;gBACd,YAAY,EAAE,CAAC;YACnB,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,gBAAgB,CAAoB,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACpE,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;gBAC7B,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;gBAC1B,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;oBACjB,IAAI,EAAE,SAAS,GAAG,CAAC,EAAE,CAAC;wBAAC,SAAS,GAAG,EAAE,CAAC;wBAAC,QAAQ,EAAE,CAAC;oBAAC,CAAC;gBACxD,CAAC;qBAAM,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;oBACxB,IAAI,EAAE,SAAS,GAAG,EAAE,EAAE,CAAC;wBAAC,SAAS,GAAG,CAAC,CAAC;wBAAC,QAAQ,EAAE,CAAC;oBAAC,CAAC;gBACxD,CAAC;qBAAM,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;oBACzB,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;oBACrB,QAAQ,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;oBAAC,SAAS,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;oBAAC,WAAW,GAAG,CAAC,CAAC;gBAC1E,CAAC;gBACD,WAAW,EAAE,CAAC;gBACd,YAAY,EAAE,CAAC;YACnB,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,GAAG,EAAE;QACtB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE,WAAW,CAAC,QAAQ,EAAE,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;QAC9G,MAAM,OAAO,GAAG,QAAQ,GAAG,EAAE,GAAG,SAAS,CAAC;QAC1C,MAAM,QAAQ,GAAG,MAAM;aAClB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,QAAQ,IAAI,CAAC,CAAC,KAAK,GAAG,OAAO,CAAC;aACrD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,QAAQ,CAAC,SAAS,GAAG,6DAA6D,WAAW,CAAC,kBAAkB,EAAE,SAAS,CAAC;YAC5H,OAAO;QACX,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,CAAW,EAAE,EAAE;YACxB,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;YACnG,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YAC5G,OAAO,GAAG,IAAI,MAAM,IAAI,EAAE,CAAC;QAC/B,CAAC,CAAC;QACF,QAAQ,CAAC,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;8CACD,CAAC,CAAC,EAAE;8CACJ,GAAG,CAAC,CAAC,CAAC;+CACL,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC;kBAChD,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,8BAA8B,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;;mBAE7E,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtB,QAAQ,CAAC,gBAAgB,CAAc,YAAY,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;YAC/D,GAAG,CAAC,aAAa,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;gBAChE,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,EAAG,CAAC;gBAC3B,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;gBACzC,UAAU,CAAC,MAAM,CAAC,CAAC;gBACnB,YAAY,EAAE,CAAC;gBACf,WAAW,EAAE,CAAC;YAClB,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;IAEF,KAAK,CAAC,aAAa,CAAoB,UAAU,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;QAC/E,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,OAAO,GAAG,MAAM,CAClB,8DAA8D;QAC9D,+DAA+D;QAC/D,iDAAiD;QACjD,GAAG,WAAW,CAAC,WAAW,EAAE,IAAI,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,QAAQ,CAChJ,CAAC;QACF,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,MAAM,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;QACpD,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QACjE,MAAM,EAAE,GAAa;YACjB,EAAE,EAAE,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;YACjE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;YAC1D,MAAM,EAAE,MAAM,EAAE,OAAO;SAC1B,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChB,UAAU,CAAC,MAAM,CAAC,CAAC;QACnB,WAAW,EAAE,CAAC;QACd,YAAY,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,GAAG,EAAE;QACf,QAAQ,CAAC,MAAM,EAAE,CAAC;QAClB,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QACrD,MAAM,GAAG,KAAK,CAAC;IACnB,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,CAAC,CAAgB,EAAE,EAAE;QAC/B,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YAAC,CAAC,CAAC,eAAe,EAAE,CAAC;YAAC,CAAC,CAAC,cAAc,EAAE,CAAC;YAAC,KAAK,EAAE,CAAC;QAAC,CAAC;IACjF,CAAC,CAAC;IACF,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAClD,KAAK,CAAC,aAAa,CAAoB,YAAY,CAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACvF,KAAK,CAAC,aAAa,CAAoB,uBAAuB,CAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAClG,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ;QAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvF,WAAW,EAAE,CAAC;IACd,YAAY,EAAE,CAAC;AACnB,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IACzB,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;AACtH,CAAC"}