@dawnlit/outlook-bridge 1.0.0

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 (117) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +253 -0
  3. package/dist/OutlookService.d.ts +43 -0
  4. package/dist/OutlookService.js +137 -0
  5. package/dist/OutlookService.js.map +1 -0
  6. package/dist/errors.d.ts +112 -0
  7. package/dist/errors.js +140 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/index.d.ts +11 -0
  10. package/dist/index.js +71 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/mac/accounts.d.ts +5 -0
  13. package/dist/mac/accounts.js +17 -0
  14. package/dist/mac/accounts.js.map +1 -0
  15. package/dist/mac/attachments.d.ts +28 -0
  16. package/dist/mac/attachments.js +120 -0
  17. package/dist/mac/attachments.js.map +1 -0
  18. package/dist/mac/bounces.d.ts +32 -0
  19. package/dist/mac/bounces.js +285 -0
  20. package/dist/mac/bounces.js.map +1 -0
  21. package/dist/mac/cleanup.d.ts +29 -0
  22. package/dist/mac/cleanup.js +219 -0
  23. package/dist/mac/cleanup.js.map +1 -0
  24. package/dist/mac/drafts.d.ts +27 -0
  25. package/dist/mac/drafts.js +214 -0
  26. package/dist/mac/drafts.js.map +1 -0
  27. package/dist/mac/folders.d.ts +28 -0
  28. package/dist/mac/folders.js +127 -0
  29. package/dist/mac/folders.js.map +1 -0
  30. package/dist/mac/index.d.ts +22 -0
  31. package/dist/mac/index.js +119 -0
  32. package/dist/mac/index.js.map +1 -0
  33. package/dist/mac/read.d.ts +46 -0
  34. package/dist/mac/read.js +406 -0
  35. package/dist/mac/read.js.map +1 -0
  36. package/dist/mac/run.d.ts +64 -0
  37. package/dist/mac/run.js +250 -0
  38. package/dist/mac/run.js.map +1 -0
  39. package/dist/mac/scripts.d.ts +137 -0
  40. package/dist/mac/scripts.js +337 -0
  41. package/dist/mac/scripts.js.map +1 -0
  42. package/dist/mac/send.d.ts +23 -0
  43. package/dist/mac/send.js +135 -0
  44. package/dist/mac/send.js.map +1 -0
  45. package/dist/mac/signatures.d.ts +4 -0
  46. package/dist/mac/signatures.js +36 -0
  47. package/dist/mac/signatures.js.map +1 -0
  48. package/dist/mac/templates.d.ts +34 -0
  49. package/dist/mac/templates.js +266 -0
  50. package/dist/mac/templates.js.map +1 -0
  51. package/dist/mail.d.ts +72 -0
  52. package/dist/mail.js +131 -0
  53. package/dist/mail.js.map +1 -0
  54. package/dist/outlookTemplateSections.d.ts +62 -0
  55. package/dist/outlookTemplateSections.js +313 -0
  56. package/dist/outlookTemplateSections.js.map +1 -0
  57. package/dist/runtime.d.ts +76 -0
  58. package/dist/runtime.js +123 -0
  59. package/dist/runtime.js.map +1 -0
  60. package/dist/shared/attachmentMatch.d.ts +32 -0
  61. package/dist/shared/attachmentMatch.js +50 -0
  62. package/dist/shared/attachmentMatch.js.map +1 -0
  63. package/dist/shared/bounceRules.d.ts +33 -0
  64. package/dist/shared/bounceRules.js +104 -0
  65. package/dist/shared/bounceRules.js.map +1 -0
  66. package/dist/shared/capabilities.d.ts +3 -0
  67. package/dist/shared/capabilities.js +9 -0
  68. package/dist/shared/capabilities.js.map +1 -0
  69. package/dist/shared/json.d.ts +11 -0
  70. package/dist/shared/json.js +45 -0
  71. package/dist/shared/json.js.map +1 -0
  72. package/dist/shared/replyBody.d.ts +49 -0
  73. package/dist/shared/replyBody.js +113 -0
  74. package/dist/shared/replyBody.js.map +1 -0
  75. package/dist/types.d.ts +365 -0
  76. package/dist/types.js +13 -0
  77. package/dist/types.js.map +1 -0
  78. package/dist/windows/accounts.d.ts +2 -0
  79. package/dist/windows/accounts.js +22 -0
  80. package/dist/windows/accounts.js.map +1 -0
  81. package/dist/windows/attachments.d.ts +28 -0
  82. package/dist/windows/attachments.js +141 -0
  83. package/dist/windows/attachments.js.map +1 -0
  84. package/dist/windows/bounces.d.ts +34 -0
  85. package/dist/windows/bounces.js +267 -0
  86. package/dist/windows/bounces.js.map +1 -0
  87. package/dist/windows/cleanup.d.ts +29 -0
  88. package/dist/windows/cleanup.js +165 -0
  89. package/dist/windows/cleanup.js.map +1 -0
  90. package/dist/windows/drafts.d.ts +29 -0
  91. package/dist/windows/drafts.js +253 -0
  92. package/dist/windows/drafts.js.map +1 -0
  93. package/dist/windows/folders.d.ts +16 -0
  94. package/dist/windows/folders.js +88 -0
  95. package/dist/windows/folders.js.map +1 -0
  96. package/dist/windows/index.d.ts +27 -0
  97. package/dist/windows/index.js +100 -0
  98. package/dist/windows/index.js.map +1 -0
  99. package/dist/windows/read.d.ts +54 -0
  100. package/dist/windows/read.js +372 -0
  101. package/dist/windows/read.js.map +1 -0
  102. package/dist/windows/run.d.ts +54 -0
  103. package/dist/windows/run.js +161 -0
  104. package/dist/windows/run.js.map +1 -0
  105. package/dist/windows/scripts.d.ts +56 -0
  106. package/dist/windows/scripts.js +163 -0
  107. package/dist/windows/scripts.js.map +1 -0
  108. package/dist/windows/send.d.ts +11 -0
  109. package/dist/windows/send.js +114 -0
  110. package/dist/windows/send.js.map +1 -0
  111. package/dist/windows/signatures.d.ts +14 -0
  112. package/dist/windows/signatures.js +69 -0
  113. package/dist/windows/signatures.js.map +1 -0
  114. package/dist/windows/templates.d.ts +25 -0
  115. package/dist/windows/templates.js +246 -0
  116. package/dist/windows/templates.js.map +1 -0
  117. package/package.json +50 -0
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readTemplateEmails = readTemplateEmails;
4
+ exports.saveTemplateEmail = saveTemplateEmail;
5
+ exports.editEmailTemplate = editEmailTemplate;
6
+ // Template emails: ordinary mail items kept in a mailbox folder and reused as
7
+ // reply bodies, plus the compose-window editor for one.
8
+ //
9
+ // Outlook for Mac serves a message's HTML through `content` when `has html` is
10
+ // true — the same thing COM's HTMLBody returns — which is what makes templates
11
+ // portable between the two platforms rather than a Windows-only feature.
12
+ const run_1 = require("./run");
13
+ const scripts_1 = require("./scripts");
14
+ const outlookTemplateSections_1 = require("../outlookTemplateSections");
15
+ const mail_1 = require("../mail");
16
+ const errors_1 = require("../errors");
17
+ /** How deep under the mailbox root a template folder is searched for. */
18
+ const TEMPLATE_SEARCH_DEPTH = 3;
19
+ /** The cap Windows applies to a template body; a Word-generated one is huge. */
20
+ const MAX_TEMPLATE_HTML = 100000;
21
+ /** How much plain text stands in for a body when one wasn't asked for. */
22
+ const PREVIEW_CHARS = 200;
23
+ /**
24
+ * Embedded (cid:) images live as attachments on the TEMPLATE item, so reusing
25
+ * the HTML on a new email shows a broken "linked image" placeholder, and
26
+ * rewriting the refs renders invisibly. Neither works, so the img tags go —
27
+ * template emails are text/HTML only.
28
+ */
29
+ function stripEmbeddedImages(html) {
30
+ return html.replace(/<img[^>]*src="cid:[^"]*"[^>]*>/gi, '');
31
+ }
32
+ /**
33
+ * Read the template emails saved in a mailbox folder (default "Templates"),
34
+ * returning each item's full HTML body. When the folder doesn't exist, returns
35
+ * folderFound:false plus the mailbox's folder names instead of throwing, so the
36
+ * caller can ask the user whether to create it rather than fail.
37
+ *
38
+ * Bodies are read in a second pass, for the chosen templates only. That is not
39
+ * a micro-optimisation: a folder of Word-sized templates is megabytes, and
40
+ * pulling all of them through stdout on a call that wanted one is what overruns
41
+ * the buffer — the same reason the Windows reader filters before reading.
42
+ */
43
+ async function readTemplateEmails(emailAccount, folderName = 'Templates', limit = 20, includeBody = true, subject = '') {
44
+ const cap = (0, mail_1.clamp)(limit, 1, 50);
45
+ const wanted = (subject || '').trim().toLowerCase();
46
+ // Pass 1 — find the folder and index it: three bulk reads, no bodies.
47
+ const indexScript = `${scripts_1.FIND_FOLDER_HANDLER}
48
+ tell application "Microsoft Outlook"
49
+ ${(0, scripts_1.accountLookupSnippet)(emailAccount)}
50
+ set rootFolder to root folder of targetAcct
51
+ end tell
52
+ set theFolder to my findFolderByName(rootFolder, "${(0, run_1.asEscape)(folderName)}", ${TEMPLATE_SEARCH_DEPTH})
53
+ tell application "Microsoft Outlook"
54
+ if theFolder is missing value then
55
+ set names to {}
56
+ repeat with f in (mail folders of rootFolder)
57
+ try
58
+ set end of names to (name of f) as string
59
+ end try
60
+ end repeat
61
+ return ${(0, run_1.asRow)(['"0"', 'my sanitizeList(names)'])}
62
+ end if
63
+ set idList to id of every message of theFolder
64
+ set subjList to subject of every message of theFolder
65
+ try
66
+ set modList to modification date of every message of theFolder
67
+ on error
68
+ set modList to {}
69
+ end try
70
+ set hasMods to ((count of modList) is (count of idList))
71
+ set out to ${(0, run_1.asRow)(['"1"', '(name of theFolder as string)'])}
72
+ repeat with i from 1 to (count of idList)
73
+ set modAt to ""
74
+ if hasMods then set modAt to my isoDate(item i of modList)
75
+ set out to out & ${(0, run_1.asRow)([
76
+ '(item i of idList as string)',
77
+ '(item i of subjList)',
78
+ 'modAt',
79
+ ])}
80
+ end repeat
81
+ return out
82
+ end tell`;
83
+ const records = (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(indexScript, 120000));
84
+ const header = (0, run_1.splitFields)(records[0] || '');
85
+ if ((0, run_1.field)(header, 0) !== '1') {
86
+ return {
87
+ folderFound: false,
88
+ folderPath: '',
89
+ templates: [],
90
+ availableFolders: (0, run_1.splitList)((0, run_1.field)(header, 1)),
91
+ };
92
+ }
93
+ const folderPath = (0, scripts_1.macFolderPath)(emailAccount, (0, run_1.field)(header, 1) || folderName, []);
94
+ const indexed = records.slice(1).map(record => {
95
+ const parts = (0, run_1.splitFields)(record);
96
+ return {
97
+ id: (0, run_1.field)(parts, 0),
98
+ subject: (0, run_1.field)(parts, 1).trim(),
99
+ lastModified: (0, run_1.field)(parts, 2),
100
+ };
101
+ });
102
+ // Newest first, matching the Windows reader's Sort on LastModificationTime —
103
+ // and done before the cap, so "the newest 20" means that rather than an
104
+ // arbitrary 20. 'yyyy-MM-dd HH:mm' sorts lexicographically.
105
+ indexed.sort((a, b) => b.lastModified.localeCompare(a.lastModified));
106
+ const chosen = indexed
107
+ .filter(item => !wanted || item.subject.toLowerCase() === wanted)
108
+ .slice(0, cap);
109
+ if (chosen.length === 0) {
110
+ return { folderFound: true, folderPath, templates: [], availableFolders: [] };
111
+ }
112
+ // Pass 2 — the bodies, for the chosen templates only.
113
+ const bodyProperty = includeBody ? 'content' : 'plain text content';
114
+ const bodyScript = `tell application "Microsoft Outlook"
115
+ set out to ""
116
+ repeat with theId in {${chosen.map(item => item.id).join(', ')}}
117
+ set theMsg to missing value
118
+ try
119
+ set theMsg to message id theId
120
+ end try
121
+ if theMsg is not missing value then
122
+ set bodyText to ""
123
+ try
124
+ set bodyText to (${bodyProperty} of theMsg) as string
125
+ end try
126
+ set out to out & ${(0, run_1.asRow)(['(theId as string)', 'bodyText'])}
127
+ end if
128
+ end repeat
129
+ return out
130
+ end tell`;
131
+ const bodies = new Map();
132
+ for (const record of (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(bodyScript, 120000))) {
133
+ const parts = (0, run_1.splitFields)(record);
134
+ bodies.set((0, run_1.field)(parts, 0), (0, run_1.field)(parts, 1));
135
+ }
136
+ const templates = chosen.map(item => {
137
+ const body = bodies.get(item.id) || '';
138
+ const html = includeBody
139
+ ? stripEmbeddedImages(body).slice(0, MAX_TEMPLATE_HTML)
140
+ : '';
141
+ // With a body in hand the HTML is authoritative (it survives Word splitting
142
+ // a marker across tags); without one, scan the plain-text body, where a
143
+ // marker cannot have been split.
144
+ const markers = (0, outlookTemplateSections_1.findTemplateMarkers)(includeBody ? html : body);
145
+ return {
146
+ entryId: item.id,
147
+ subject: item.subject,
148
+ htmlBody: html,
149
+ bodyPreview: includeBody ? '' : body.trim().slice(0, PREVIEW_CHARS),
150
+ sections: markers.sections,
151
+ placeholders: markers.placeholders,
152
+ lastModified: item.lastModified,
153
+ };
154
+ });
155
+ return { folderFound: true, folderPath, templates, availableFolders: [] };
156
+ }
157
+ /**
158
+ * Save a new template email into a mailbox folder (default "Templates"),
159
+ * creating the folder at the mailbox root if it doesn't exist. The item is a
160
+ * plain unsent mail (subject + HTML body) that can be edited in Outlook.
161
+ * Never overwrites an existing item — it always adds a new one, so check with
162
+ * readTemplateEmails first and only call after the user has agreed to create it.
163
+ */
164
+ async function saveTemplateEmail(emailAccount, subject, htmlBody, folderName = 'Templates') {
165
+ const script = `${scripts_1.FIND_FOLDER_HANDLER}
166
+ tell application "Microsoft Outlook"
167
+ ${(0, scripts_1.accountLookupSnippet)(emailAccount)}
168
+ set rootFolder to root folder of targetAcct
169
+ end tell
170
+ set theFolder to my findFolderByName(rootFolder, "${(0, run_1.asEscape)(folderName)}", ${TEMPLATE_SEARCH_DEPTH})
171
+ tell application "Microsoft Outlook"
172
+ set wasCreated to false
173
+ if theFolder is missing value then
174
+ set theFolder to (make new mail folder at rootFolder with properties {name:"${(0, run_1.asEscape)(folderName)}"})
175
+ set wasCreated to true
176
+ end if
177
+ set countBefore to count of messages of theFolder
178
+ set newMsg to make new outgoing message with properties {subject:"${(0, run_1.asEscape)(subject)}", content:"${(0, run_1.asEscape)(htmlBody)}"}
179
+ try
180
+ set account of newMsg to targetAcct
181
+ end try
182
+ -- 'move' reports success even where it silently does nothing, so a template
183
+ -- that was never filed must not be reported as saved.
184
+ move newMsg to theFolder
185
+ if (count of messages of theFolder) is not greater than countBefore then error "Outlook did not file the template in '" & (name of theFolder) & "'."
186
+ return ${(0, run_1.asRow)(['(name of theFolder as string)', '(wasCreated as string)'])}
187
+ end tell`;
188
+ const parts = (0, run_1.summaryFields)(await (0, run_1.runOsaScript)(script, 120000));
189
+ return {
190
+ folderPath: (0, scripts_1.macFolderPath)(emailAccount, (0, run_1.field)(parts, 0) || folderName, []),
191
+ folderCreated: (0, run_1.boolField)(parts, 1),
192
+ };
193
+ }
194
+ /**
195
+ * Open an Outlook compose window pre-filled with `currentHtml`, wait for the
196
+ * user to close it, and return the HTML they saved.
197
+ *
198
+ * Deliberately unbounded: the run lasts as long as the person is editing. Give
199
+ * it an AbortSignal (`bridge.withOptions({ signal })`) if the caller needs a way
200
+ * to give up on them.
201
+ *
202
+ * The macOS half of the round trip differs from Windows in one visible way: an
203
+ * edited draft is filed by answering Outlook's own "save this message?" prompt
204
+ * on close, so the subject tells the user to save rather than to press a key.
205
+ */
206
+ async function editEmailTemplate(label, currentHtml) {
207
+ const subject = `${label} - Save and close when done`;
208
+ const script = `tell application "Microsoft Outlook"
209
+ set editSubject to "${(0, run_1.asEscape)(subject)}"
210
+ set draftMsg to make new outgoing message with properties {subject:editSubject, content:"${(0, run_1.asEscape)(currentHtml)}"}
211
+ open draftMsg
212
+ activate
213
+ end tell
214
+
215
+ -- Poll until the compose window is gone. A draft window carries the message it
216
+ -- is editing as its object, so the one we opened is identified by subject rather
217
+ -- than by counting windows the user may also have open.
218
+ set stillOpen to true
219
+ repeat while stillOpen
220
+ delay 0.7
221
+ set stillOpen to false
222
+ tell application "Microsoft Outlook"
223
+ try
224
+ repeat with w in draft windows
225
+ try
226
+ if ((subject of (object of w)) as string) is editSubject then
227
+ set stillOpen to true
228
+ exit repeat
229
+ end if
230
+ end try
231
+ end repeat
232
+ end try
233
+ end tell
234
+ end repeat
235
+
236
+ tell application "Microsoft Outlook"
237
+ -- Prefer the saved draft: it reflects what the user chose to keep, and it
238
+ -- survives the window closing. Any leftover editor drafts are removed so
239
+ -- they don't pile up in the Drafts folder.
240
+ set savedHtml to ""
241
+ try
242
+ repeat with acctList in {exchange accounts, imap accounts, pop accounts}
243
+ repeat with a in acctList
244
+ try
245
+ repeat with m in (messages of (drafts of a))
246
+ try
247
+ if ((subject of m) as string) is editSubject then
248
+ if savedHtml is "" then set savedHtml to (content of m) as string
249
+ delete m
250
+ end if
251
+ end try
252
+ end repeat
253
+ end try
254
+ end repeat
255
+ end repeat
256
+ end try
257
+ return my sanitize(savedHtml)
258
+ end tell`;
259
+ // 0 disables the timeout: this run is as long as the edit takes.
260
+ const saved = await (0, run_1.runOsaScript)(script, 0);
261
+ if (!saved) {
262
+ throw new errors_1.NotFoundError('template', 'No template saved. Did you save the draft before closing?');
263
+ }
264
+ return saved;
265
+ }
266
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/mac/templates.ts"],"names":[],"mappings":"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"}
package/dist/mail.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ import type { MailFolderRef } from './types';
2
+ /**
3
+ * Clamp a caller's count to the range an operation actually accepts.
4
+ *
5
+ * These bounds ARE the argument contract — how far back a scan reaches, how many
6
+ * rows come back, how deep a folder walk goes — so they are stated here rather
7
+ * than once per platform, which is how the two came to disagree about what a
8
+ * `daysBack` of 0 means.
9
+ */
10
+ export declare function clamp(value: number, min: number, max: number): number;
11
+ /**
12
+ * The Outlook folders addressable by name rather than by walking from the Inbox,
13
+ * with their olDefaultFolders id. Sent Items is the one that earns this: "has this
14
+ * already gone out?" is otherwise unanswerable, and no amount of Inbox scanning
15
+ * substitutes for it.
16
+ *
17
+ * A well-known name always wins over a user folder of the same name — reach the
18
+ * latter by qualifying it (`Inbox\Drafts`).
19
+ */
20
+ export declare const WELL_KNOWN_FOLDERS: Record<string, number>;
21
+ /**
22
+ * Why `deleteOutlookEmails` refuses a message, worded once for both platforms.
23
+ *
24
+ * It reaches the caller verbatim in a per-email `reason`, so the two generated
25
+ * scripts must not word it independently — the same reasoning that put the
26
+ * attachment "not found" sentence in `shared/attachmentMatch.ts`. It also names
27
+ * the option a consumer of THIS package actually passes: the text used to say
28
+ * `allow_protected`, which is the tool-layer spelling and not a name this
29
+ * package exposes.
30
+ */
31
+ export declare const PROTECTED_MAIL_REASON = "received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override";
32
+ /**
33
+ * Roots whose items are OUTGOING mail.
34
+ *
35
+ * Outgoing mail carries a sent timestamp where received mail carries a received
36
+ * one, and both platforms have to pick the same folders out on that basis —
37
+ * which is why it lives beside `WELL_KNOWN_FOLDERS` rather than as raw
38
+ * `olDefaultFolders` ids compared inline in each reader.
39
+ */
40
+ export declare function isOutgoingRoot(rootId: number): boolean;
41
+ /**
42
+ * Resolve a caller's folder string to a well-known root plus the segments below it.
43
+ *
44
+ * Accepts every shape a user has to hand: a bare name ("Invoices"), a relative
45
+ * path ("Invoices\\Paid"), a well-known folder ("Sent Items"), a path under one
46
+ * ("Deleted Items\\2026"), and the full FolderPath listInboxFolders prints
47
+ * ("\\\\team@x.com\\Inbox\\Invoices").
48
+ *
49
+ * An unrecognized first segment means Inbox-relative, which is what keeps a bare
50
+ * subfolder name working unchanged.
51
+ */
52
+ export declare function mailFolderRef(folder: string): MailFolderRef;
53
+ /**
54
+ * Split a plain-text reply into the sender's own text and the quoted thread
55
+ * below it.
56
+ *
57
+ * This matters more than it looks: the quoted original of a reply carries the
58
+ * text of the message it answers. Handing a caller one blob invites a figure
59
+ * from the outgoing message to be read back as the sender's answer, which is a
60
+ * worse failure than the truncation this exists to fix.
61
+ *
62
+ * Scans for the earliest of the separators Outlook and the common webmail
63
+ * clients emit. If the split would leave no new text at all — a bottom-posted
64
+ * reply, or a body that opens on a quote — it is abandoned and everything is
65
+ * returned as `body`, since dropping the sender's actual words is the one
66
+ * outcome worth avoiding.
67
+ */
68
+ export declare function splitQuotedOriginal(text: string): {
69
+ body: string;
70
+ quoted: string;
71
+ separator: string;
72
+ };
package/dist/mail.js ADDED
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PROTECTED_MAIL_REASON = exports.WELL_KNOWN_FOLDERS = void 0;
4
+ exports.clamp = clamp;
5
+ exports.isOutgoingRoot = isOutgoingRoot;
6
+ exports.mailFolderRef = mailFolderRef;
7
+ exports.splitQuotedOriginal = splitQuotedOriginal;
8
+ /**
9
+ * Clamp a caller's count to the range an operation actually accepts.
10
+ *
11
+ * These bounds ARE the argument contract — how far back a scan reaches, how many
12
+ * rows come back, how deep a folder walk goes — so they are stated here rather
13
+ * than once per platform, which is how the two came to disagree about what a
14
+ * `daysBack` of 0 means.
15
+ */
16
+ function clamp(value, min, max) {
17
+ return Math.max(min, Math.min(max, Math.floor(value)));
18
+ }
19
+ /**
20
+ * The Outlook folders addressable by name rather than by walking from the Inbox,
21
+ * with their olDefaultFolders id. Sent Items is the one that earns this: "has this
22
+ * already gone out?" is otherwise unanswerable, and no amount of Inbox scanning
23
+ * substitutes for it.
24
+ *
25
+ * A well-known name always wins over a user folder of the same name — reach the
26
+ * latter by qualifying it (`Inbox\Drafts`).
27
+ */
28
+ exports.WELL_KNOWN_FOLDERS = {
29
+ inbox: 6,
30
+ 'sent items': 5,
31
+ sent: 5,
32
+ 'sent mail': 5,
33
+ drafts: 16,
34
+ 'deleted items': 3,
35
+ deleted: 3,
36
+ trash: 3,
37
+ 'junk email': 23,
38
+ junk: 23,
39
+ outbox: 4,
40
+ };
41
+ /**
42
+ * Why `deleteOutlookEmails` refuses a message, worded once for both platforms.
43
+ *
44
+ * It reaches the caller verbatim in a per-email `reason`, so the two generated
45
+ * scripts must not word it independently — the same reasoning that put the
46
+ * attachment "not found" sentence in `shared/attachmentMatch.ts`. It also names
47
+ * the option a consumer of THIS package actually passes: the text used to say
48
+ * `allow_protected`, which is the tool-layer spelling and not a name this
49
+ * package exposes.
50
+ */
51
+ exports.PROTECTED_MAIL_REASON = 'received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override';
52
+ /**
53
+ * Roots whose items are OUTGOING mail.
54
+ *
55
+ * Outgoing mail carries a sent timestamp where received mail carries a received
56
+ * one, and both platforms have to pick the same folders out on that basis —
57
+ * which is why it lives beside `WELL_KNOWN_FOLDERS` rather than as raw
58
+ * `olDefaultFolders` ids compared inline in each reader.
59
+ */
60
+ function isOutgoingRoot(rootId) {
61
+ return rootId === exports.WELL_KNOWN_FOLDERS['sent items']
62
+ || rootId === exports.WELL_KNOWN_FOLDERS.outbox
63
+ || rootId === exports.WELL_KNOWN_FOLDERS.drafts;
64
+ }
65
+ /**
66
+ * Resolve a caller's folder string to a well-known root plus the segments below it.
67
+ *
68
+ * Accepts every shape a user has to hand: a bare name ("Invoices"), a relative
69
+ * path ("Invoices\\Paid"), a well-known folder ("Sent Items"), a path under one
70
+ * ("Deleted Items\\2026"), and the full FolderPath listInboxFolders prints
71
+ * ("\\\\team@x.com\\Inbox\\Invoices").
72
+ *
73
+ * An unrecognized first segment means Inbox-relative, which is what keeps a bare
74
+ * subfolder name working unchanged.
75
+ */
76
+ function mailFolderRef(folder) {
77
+ const rest = folder.trim().replace(/^\\\\[^\\/]+[\\/]/, '');
78
+ const segments = rest.split(/[\\/]+/).map(s => s.trim()).filter(Boolean);
79
+ if (segments.length > 0) {
80
+ const head = segments[0].toLowerCase();
81
+ if (Object.prototype.hasOwnProperty.call(exports.WELL_KNOWN_FOLDERS, head)) {
82
+ return { rootId: exports.WELL_KNOWN_FOLDERS[head], rootLabel: segments[0], segments: segments.slice(1) };
83
+ }
84
+ }
85
+ return { rootId: 6, rootLabel: 'Inbox', segments };
86
+ }
87
+ /**
88
+ * Split a plain-text reply into the sender's own text and the quoted thread
89
+ * below it.
90
+ *
91
+ * This matters more than it looks: the quoted original of a reply carries the
92
+ * text of the message it answers. Handing a caller one blob invites a figure
93
+ * from the outgoing message to be read back as the sender's answer, which is a
94
+ * worse failure than the truncation this exists to fix.
95
+ *
96
+ * Scans for the earliest of the separators Outlook and the common webmail
97
+ * clients emit. If the split would leave no new text at all — a bottom-posted
98
+ * reply, or a body that opens on a quote — it is abandoned and everything is
99
+ * returned as `body`, since dropping the sender's actual words is the one
100
+ * outcome worth avoiding.
101
+ */
102
+ function splitQuotedOriginal(text) {
103
+ const lines = text.split(/\r?\n/);
104
+ const whole = { body: text, quoted: '', separator: '' };
105
+ for (let i = 0; i < lines.length; i++) {
106
+ const line = lines[i].trim();
107
+ let separator = '';
108
+ if (/^-{2,}\s*original message\s*-{2,}$/i.test(line))
109
+ separator = '-----Original Message-----';
110
+ // Outlook's HTML thread rule, which sits directly above the From:/Sent: block.
111
+ else if (/^_{10,}$/.test(line))
112
+ separator = 'Outlook thread rule';
113
+ // "On Tue, Jul 21, 2026 at 9:14 AM John Doe <j@x.com> wrote:"
114
+ else if (/^on\b.{5,300}\bwrote:$/i.test(line))
115
+ separator = 'On … wrote:';
116
+ else if (/^>/.test(lines[i]))
117
+ separator = '> quoted lines';
118
+ // A bare header block: "From: …" followed by another header line.
119
+ else if (/^from:\s*\S/i.test(line) && /^(sent|date|to|subject|cc):/i.test((lines[i + 1] || '').trim())) {
120
+ separator = 'From:/Sent: header block';
121
+ }
122
+ if (!separator)
123
+ continue;
124
+ const body = lines.slice(0, i).join('\n').trimEnd();
125
+ if (!body.trim())
126
+ return whole;
127
+ return { body, quoted: lines.slice(i).join('\n').trim(), separator };
128
+ }
129
+ return whole;
130
+ }
131
+ //# sourceMappingURL=mail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.js","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Section + placeholder composition for the operator's Outlook template emails.
3
+ *
4
+ * A template email is a normal saved mail the operator edits in Outlook, so the
5
+ * greeting, closing and signature are theirs. Where one template serves several
6
+ * reply types, the variants live inside it between markers:
7
+ *
8
+ * Hello,
9
+ * [[QUOTE]] Thank you for your quote... [[/QUOTE]]
10
+ * [[NOQUOTE]] Thank you for letting us know... [[/NOQUOTE]]
11
+ * [[CLARIFY]] {{QUESTIONS}} [[/CLARIFY]]
12
+ * Best regards, <signature>
13
+ *
14
+ * Composing keeps the requested section, drops the others, and substitutes any
15
+ * {{PLACEHOLDER}} — so the shared wording is written once and the caller only
16
+ * names a section instead of carrying (and editing) 40-50k of Word HTML.
17
+ *
18
+ * Everything here works on the raw HTML string rather than a parsed tree: Word's
19
+ * markup carries conditional comments and namespaced tags that a round-trip
20
+ * through a parser would rewrite, and the reply body must stay byte-identical to
21
+ * what the operator wrote.
22
+ */
23
+ export interface TemplateMarkers {
24
+ /** Section names with both an opening and a closing marker, in document order. */
25
+ sections: string[];
26
+ /** `{{NAME}}` placeholders found anywhere in the template. */
27
+ placeholders: string[];
28
+ }
29
+ /** Find the markers in a template body (HTML or the plain-text body Outlook derives). */
30
+ export declare function findTemplateMarkers(body: string): TemplateMarkers;
31
+ /** Replace every occurrence of `token` with `value`, which is never rescanned. */
32
+ export declare function replaceToken(html: string, token: string, value: string): string;
33
+ /**
34
+ * Remove `token` along with the line it sits on — the table row or paragraph,
35
+ * label included. For an optional detail this is the difference between the email
36
+ * not mentioning it at all and it carrying a heading with nothing under it.
37
+ * Falls back to deleting just the token when it isn't inside a line element.
38
+ */
39
+ export declare function removeTokenLine(html: string, token: string): string;
40
+ /** Which of `tokens` the template actually uses — what the caller has to supply. */
41
+ export declare function findTokens(html: string, tokens: string[]): string[];
42
+ /**
43
+ * `{Token}`-shaped text still in a filled-in body: a placeholder misspelled in
44
+ * Outlook, or one the app has no field for. Reads the visible text only, so the
45
+ * braces in a Word <style> block are not mistaken for placeholders.
46
+ */
47
+ export declare function findUnfilledTokens(html: string): string[];
48
+ export interface ComposeOptions {
49
+ /** Section to keep; the other sections are dropped along with their markers. */
50
+ section?: string;
51
+ /** `{{NAME}}` → HTML to substitute. Names are matched case-insensitively. */
52
+ placeholders?: Record<string, string>;
53
+ /** Template name, for error messages. */
54
+ label?: string;
55
+ }
56
+ /**
57
+ * Resolve a template body down to the one reply that goes out: keep `section`,
58
+ * drop the rest, fill the placeholders. Throws rather than guessing — a template
59
+ * the operator edited into an unusable state must stop the run, not quietly send
60
+ * a reply with `[[QUOTE]]` or an empty question list in it.
61
+ */
62
+ export declare function composeTemplateBody(html: string, options?: ComposeOptions): string;