@dawnlit/outlook-bridge 1.1.1 → 2.1.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 (142) hide show
  1. package/README.md +152 -183
  2. package/dist/backend.d.ts +151 -0
  3. package/dist/backend.js +3 -0
  4. package/dist/backend.js.map +1 -0
  5. package/dist/bridge.d.ts +56 -0
  6. package/dist/bridge.js +507 -0
  7. package/dist/bridge.js.map +1 -0
  8. package/dist/errors.d.ts +79 -43
  9. package/dist/errors.js +93 -52
  10. package/dist/errors.js.map +1 -1
  11. package/dist/index.d.ts +10 -10
  12. package/dist/index.js +25 -52
  13. package/dist/index.js.map +1 -1
  14. package/dist/mac/accounts.d.ts +7 -2
  15. package/dist/mac/accounts.js +23 -6
  16. package/dist/mac/accounts.js.map +1 -1
  17. package/dist/mac/attachments.d.ts +5 -25
  18. package/dist/mac/attachments.js +40 -99
  19. package/dist/mac/attachments.js.map +1 -1
  20. package/dist/mac/bounces.d.ts +12 -27
  21. package/dist/mac/bounces.js +96 -117
  22. package/dist/mac/bounces.js.map +1 -1
  23. package/dist/mac/cleanup.d.ts +11 -25
  24. package/dist/mac/cleanup.js +44 -107
  25. package/dist/mac/cleanup.js.map +1 -1
  26. package/dist/mac/drafts.d.ts +15 -34
  27. package/dist/mac/drafts.js +113 -186
  28. package/dist/mac/drafts.js.map +1 -1
  29. package/dist/mac/folders.d.ts +12 -21
  30. package/dist/mac/folders.js +44 -59
  31. package/dist/mac/folders.js.map +1 -1
  32. package/dist/mac/index.d.ts +2 -22
  33. package/dist/mac/index.js +19 -81
  34. package/dist/mac/index.js.map +1 -1
  35. package/dist/mac/profile.d.ts +13 -0
  36. package/dist/mac/profile.js +149 -0
  37. package/dist/mac/profile.js.map +1 -0
  38. package/dist/mac/read.d.ts +21 -37
  39. package/dist/mac/read.js +198 -216
  40. package/dist/mac/read.js.map +1 -1
  41. package/dist/mac/run.d.ts +51 -38
  42. package/dist/mac/run.js +138 -103
  43. package/dist/mac/run.js.map +1 -1
  44. package/dist/mac/scripts.d.ts +90 -91
  45. package/dist/mac/scripts.js +167 -126
  46. package/dist/mac/scripts.js.map +1 -1
  47. package/dist/mac/send.d.ts +10 -20
  48. package/dist/mac/send.js +99 -89
  49. package/dist/mac/send.js.map +1 -1
  50. package/dist/mac/signatures.d.ts +2 -2
  51. package/dist/mac/signatures.js +9 -11
  52. package/dist/mac/signatures.js.map +1 -1
  53. package/dist/mac/templates.d.ts +14 -26
  54. package/dist/mac/templates.js +79 -120
  55. package/dist/mac/templates.js.map +1 -1
  56. package/dist/mail.d.ts +80 -46
  57. package/dist/mail.js +179 -73
  58. package/dist/mail.js.map +1 -1
  59. package/dist/runtime.d.ts +76 -43
  60. package/dist/runtime.js +103 -48
  61. package/dist/runtime.js.map +1 -1
  62. package/dist/shared/args.d.ts +48 -0
  63. package/dist/shared/args.js +216 -0
  64. package/dist/shared/args.js.map +1 -0
  65. package/dist/shared/attachmentMatch.d.ts +26 -20
  66. package/dist/shared/attachmentMatch.js +60 -23
  67. package/dist/shared/attachmentMatch.js.map +1 -1
  68. package/dist/shared/bounceReport.d.ts +11 -0
  69. package/dist/shared/bounceReport.js +45 -0
  70. package/dist/shared/bounceReport.js.map +1 -0
  71. package/dist/shared/bounceRules.d.ts +38 -10
  72. package/dist/shared/bounceRules.js +50 -32
  73. package/dist/shared/bounceRules.js.map +1 -1
  74. package/dist/shared/exec.d.ts +21 -0
  75. package/dist/shared/exec.js +47 -0
  76. package/dist/shared/exec.js.map +1 -0
  77. package/dist/shared/json.d.ts +17 -6
  78. package/dist/shared/json.js +35 -24
  79. package/dist/shared/json.js.map +1 -1
  80. package/dist/shared/locate.d.ts +31 -0
  81. package/dist/shared/locate.js +128 -0
  82. package/dist/shared/locate.js.map +1 -0
  83. package/dist/shared/replyBody.d.ts +31 -36
  84. package/dist/shared/replyBody.js +83 -72
  85. package/dist/shared/replyBody.js.map +1 -1
  86. package/dist/shared/templateLimits.d.ts +14 -0
  87. package/dist/shared/templateLimits.js +21 -0
  88. package/dist/shared/templateLimits.js.map +1 -0
  89. package/dist/templateBody.d.ts +69 -0
  90. package/dist/{outlookTemplateSections.js → templateBody.js} +80 -73
  91. package/dist/templateBody.js.map +1 -0
  92. package/dist/types.d.ts +381 -168
  93. package/dist/types.js +8 -9
  94. package/dist/types.js.map +1 -1
  95. package/dist/windows/accounts.d.ts +7 -12
  96. package/dist/windows/accounts.js +19 -34
  97. package/dist/windows/accounts.js.map +1 -1
  98. package/dist/windows/attachments.d.ts +9 -24
  99. package/dist/windows/attachments.js +94 -119
  100. package/dist/windows/attachments.js.map +1 -1
  101. package/dist/windows/bounces.d.ts +13 -26
  102. package/dist/windows/bounces.js +168 -200
  103. package/dist/windows/bounces.js.map +1 -1
  104. package/dist/windows/cleanup.d.ts +23 -23
  105. package/dist/windows/cleanup.js +99 -113
  106. package/dist/windows/cleanup.js.map +1 -1
  107. package/dist/windows/drafts.d.ts +16 -35
  108. package/dist/windows/drafts.js +156 -253
  109. package/dist/windows/drafts.js.map +1 -1
  110. package/dist/windows/folders.d.ts +8 -13
  111. package/dist/windows/folders.js +44 -52
  112. package/dist/windows/folders.js.map +1 -1
  113. package/dist/windows/index.d.ts +2 -27
  114. package/dist/windows/index.js +11 -66
  115. package/dist/windows/index.js.map +1 -1
  116. package/dist/windows/read.d.ts +24 -60
  117. package/dist/windows/read.js +265 -306
  118. package/dist/windows/read.js.map +1 -1
  119. package/dist/windows/run.d.ts +38 -40
  120. package/dist/windows/run.js +151 -112
  121. package/dist/windows/run.js.map +1 -1
  122. package/dist/windows/scripts.d.ts +65 -55
  123. package/dist/windows/scripts.js +109 -85
  124. package/dist/windows/scripts.js.map +1 -1
  125. package/dist/windows/send.d.ts +9 -9
  126. package/dist/windows/send.js +76 -88
  127. package/dist/windows/send.js.map +1 -1
  128. package/dist/windows/signatures.d.ts +4 -10
  129. package/dist/windows/signatures.js +30 -37
  130. package/dist/windows/signatures.js.map +1 -1
  131. package/dist/windows/templates.d.ts +12 -18
  132. package/dist/windows/templates.js +117 -150
  133. package/dist/windows/templates.js.map +1 -1
  134. package/package.json +5 -4
  135. package/dist/OutlookService.d.ts +0 -43
  136. package/dist/OutlookService.js +0 -137
  137. package/dist/OutlookService.js.map +0 -1
  138. package/dist/outlookTemplateSections.d.ts +0 -62
  139. package/dist/outlookTemplateSections.js.map +0 -1
  140. package/dist/shared/capabilities.d.ts +0 -3
  141. package/dist/shared/capabilities.js +0 -9
  142. package/dist/shared/capabilities.js.map +0 -1
@@ -4,52 +4,37 @@ exports.readTemplateEmails = readTemplateEmails;
4
4
  exports.saveTemplateEmail = saveTemplateEmail;
5
5
  exports.editEmailTemplate = editEmailTemplate;
6
6
  // Template emails: ordinary mail items kept in a mailbox folder and reused as
7
- // reply bodies, plus the compose-window editor for one.
7
+ // email bodies, plus the compose-window editor for one.
8
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.
9
+ // Outlook for Mac serves a message's HTML through `content` — the same thing
10
+ // COM's HTMLBody returns — which is what makes templates portable between the
11
+ // two platforms.
12
12
  const run_1 = require("./run");
13
13
  const scripts_1 = require("./scripts");
14
- const outlookTemplateSections_1 = require("../outlookTemplateSections");
15
- const mail_1 = require("../mail");
14
+ const templateLimits_1 = require("../shared/templateLimits");
15
+ const templateBody_1 = require("../templateBody");
16
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
- */
17
+ /** A template body without its embedded images — see EMBEDDED_IMAGE_SOURCE. */
29
18
  function stripEmbeddedImages(html) {
30
- return html.replace(/<img[^>]*src="cid:[^"]*"[^>]*>/gi, '');
19
+ return html.replace(new RegExp(templateLimits_1.EMBEDDED_IMAGE_SOURCE, 'gi'), '');
31
20
  }
32
21
  /**
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.
22
+ * The template emails in a mailbox folder, newest first; a missing folder is
23
+ * reported as folderFound:false with the mailbox's top-level folder names.
37
24
  *
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.
25
+ * Bodies are read in a second pass, for the chosen templates only: a folder of
26
+ * Word-sized templates is megabytes, and pulling all of them through stdout on
27
+ * a call that wanted one is what overruns the output limit.
42
28
  */
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();
29
+ async function readTemplateEmails(request) {
30
+ const acct = await (0, scripts_1.resolveMacAccount)(request.account);
46
31
  // Pass 1 — find the folder and index it: three bulk reads, no bodies.
47
- const indexScript = `${scripts_1.FIND_FOLDER_HANDLER}
32
+ const records = (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(`${scripts_1.FIND_FOLDER_HANDLER}
48
33
  tell application "Microsoft Outlook"
49
- ${(0, scripts_1.accountLookupSnippet)(emailAccount)}
50
- set rootFolder to root folder of targetAcct
34
+ ${(0, scripts_1.accountLookupSnippet)(acct)}
35
+ ${(0, scripts_1.rootFolderSnippet)(acct, 'root folder', 'rootFolder')}
51
36
  end tell
52
- set theFolder to my findFolderByName(rootFolder, "${(0, run_1.asEscape)(folderName)}", ${TEMPLATE_SEARCH_DEPTH})
37
+ set theFolder to my findFolderByName(rootFolder, ${(0, run_1.asString)(request.folder)}, ${(0, run_1.asInt)(templateLimits_1.TEMPLATE_SEARCH_DEPTH)})
53
38
  tell application "Microsoft Outlook"
54
39
  if theFolder is missing value then
55
40
  set names to {}
@@ -68,52 +53,43 @@ tell application "Microsoft Outlook"
68
53
  set modList to {}
69
54
  end try
70
55
  set hasMods to ((count of modList) is (count of idList))
71
- set out to ${(0, run_1.asRow)(['"1"', '(name of theFolder as string)'])}
56
+ -- The folder may sit up to the search depth down, so its whole chain is
57
+ -- reported, not just its name: the path is the one Windows' FolderPath gives.
58
+ ${(0, scripts_1.folderChainSnippet)(' ')}
59
+ set out to ${(0, run_1.asRow)(['"1"', 'my sanitizeList(chainNames)'])}
72
60
  repeat with i from 1 to (count of idList)
73
61
  set modAt to ""
74
62
  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
- ])}
63
+ set out to out & ${(0, run_1.asRow)(['(item i of idList as string)', '(item i of subjList)', 'modAt'])}
80
64
  end repeat
81
65
  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] || '');
66
+ end tell`, 'standard'));
67
+ const header = (0, run_1.splitFields)(records[0] ?? '');
85
68
  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
- };
69
+ return { folderFound: false, folderPath: '', templates: [], availableFolders: (0, run_1.splitList)((0, run_1.field)(header, 1)) };
92
70
  }
93
- const folderPath = (0, scripts_1.macFolderPath)(emailAccount, (0, run_1.field)(header, 1) || folderName, []);
94
- const indexed = records.slice(1).map(record => {
71
+ const chain = (0, run_1.splitList)((0, run_1.field)(header, 1)).reverse();
72
+ const folderPath = chain.length
73
+ ? (0, scripts_1.macFolderPath)(request.account, chain[0], chain.slice(1))
74
+ : (0, scripts_1.macFolderPath)(request.account, request.folder, []);
75
+ const wanted = (request.subject ?? '').toLowerCase();
76
+ const chosen = records.slice(1)
77
+ .map(record => {
95
78
  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
79
+ return { id: (0, run_1.field)(parts, 0), subject: (0, run_1.field)(parts, 1).trim(), lastModified: (0, run_1.field)(parts, 2) };
80
+ })
81
+ // Newest first, before the cap, so "the newest 20" means that.
82
+ .sort((a, b) => b.lastModified.localeCompare(a.lastModified))
107
83
  .filter(item => !wanted || item.subject.toLowerCase() === wanted)
108
- .slice(0, cap);
109
- if (chosen.length === 0) {
84
+ .slice(0, request.limit);
85
+ if (chosen.length === 0)
110
86
  return { folderFound: true, folderPath, templates: [], availableFolders: [] };
111
- }
112
87
  // Pass 2 — the bodies, for the chosen templates only.
113
- const bodyProperty = includeBody ? 'content' : 'plain text content';
114
- const bodyScript = `tell application "Microsoft Outlook"
88
+ const bodyProperty = request.includeBody ? 'content' : 'plain text content';
89
+ const bodies = new Map();
90
+ const raw = await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
115
91
  set out to ""
116
- repeat with theId in {${chosen.map(item => item.id).join(', ')}}
92
+ repeat with theId in ${(0, run_1.asIdList)(chosen.map(item => item.id))}
117
93
  set theMsg to missing value
118
94
  try
119
95
  set theMsg to message id theId
@@ -127,26 +103,23 @@ end tell`;
127
103
  end if
128
104
  end repeat
129
105
  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))) {
106
+ end tell`, 'standard');
107
+ for (const record of (0, run_1.splitRecords)(raw)) {
133
108
  const parts = (0, run_1.splitFields)(record);
134
109
  bodies.set((0, run_1.field)(parts, 0), (0, run_1.field)(parts, 1));
135
110
  }
136
111
  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);
112
+ const body = bodies.get(item.id) ?? '';
113
+ const html = request.includeBody ? stripEmbeddedImages(body).slice(0, templateLimits_1.MAX_TEMPLATE_HTML) : '';
114
+ // With the HTML in hand it is authoritative (markers survive Word
115
+ // splitting them across tags); without it, the plain text is scanned,
116
+ // where a marker can't have been split.
117
+ const markers = (0, templateBody_1.findTemplateMarkers)(request.includeBody ? html : body);
145
118
  return {
146
119
  entryId: item.id,
147
120
  subject: item.subject,
148
121
  htmlBody: html,
149
- bodyPreview: includeBody ? '' : body.trim().slice(0, PREVIEW_CHARS),
122
+ bodyPreview: request.includeBody ? '' : body.trim().slice(0, templateLimits_1.TEMPLATE_PREVIEW_CHARS),
150
123
  sections: markers.sections,
151
124
  placeholders: markers.placeholders,
152
125
  lastModified: item.lastModified,
@@ -155,27 +128,25 @@ end tell`;
155
128
  return { folderFound: true, folderPath, templates, availableFolders: [] };
156
129
  }
157
130
  /**
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.
131
+ * Save a new template email into a folder, creating the folder at the mailbox
132
+ * root when absent. Always adds a new item — never overwrites one.
163
133
  */
164
- async function saveTemplateEmail(emailAccount, subject, htmlBody, folderName = 'Templates') {
165
- const script = `${scripts_1.FIND_FOLDER_HANDLER}
134
+ async function saveTemplateEmail(request) {
135
+ const acct = await (0, scripts_1.resolveMacAccount)(request.account);
136
+ const parts = (0, run_1.summaryFields)(await (0, run_1.runOsaScript)(`${scripts_1.FIND_FOLDER_HANDLER}
166
137
  tell application "Microsoft Outlook"
167
- ${(0, scripts_1.accountLookupSnippet)(emailAccount)}
168
- set rootFolder to root folder of targetAcct
138
+ ${(0, scripts_1.accountLookupSnippet)(acct)}
139
+ ${(0, scripts_1.rootFolderSnippet)(acct, 'root folder', 'rootFolder')}
169
140
  end tell
170
- set theFolder to my findFolderByName(rootFolder, "${(0, run_1.asEscape)(folderName)}", ${TEMPLATE_SEARCH_DEPTH})
141
+ set theFolder to my findFolderByName(rootFolder, ${(0, run_1.asString)(request.folder)}, ${(0, run_1.asInt)(templateLimits_1.TEMPLATE_SEARCH_DEPTH)})
171
142
  tell application "Microsoft Outlook"
172
143
  set wasCreated to false
173
144
  if theFolder is missing value then
174
- set theFolder to (make new mail folder at rootFolder with properties {name:"${(0, run_1.asEscape)(folderName)}"})
145
+ set theFolder to (make new mail folder at rootFolder with properties {name:${(0, run_1.asString)(request.folder)}})
175
146
  set wasCreated to true
176
147
  end if
177
148
  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)}"}
149
+ set newMsg to make new outgoing message with properties {subject:${(0, run_1.asString)(request.subject)}, content:${(0, run_1.asString)(request.htmlBody)}}
179
150
  try
180
151
  set account of newMsg to targetAcct
181
152
  end try
@@ -184,37 +155,28 @@ tell application "Microsoft Outlook"
184
155
  move newMsg to theFolder
185
156
  if (count of messages of theFolder) is not greater than countBefore then error "Outlook did not file the template in '" & (name of theFolder) & "'."
186
157
  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));
158
+ end tell`, 'standard'));
189
159
  return {
190
- folderPath: (0, scripts_1.macFolderPath)(emailAccount, (0, run_1.field)(parts, 0) || folderName, []),
160
+ folderPath: (0, scripts_1.macFolderPath)(request.account, (0, run_1.field)(parts, 0) || request.folder, []),
191
161
  folderCreated: (0, run_1.boolField)(parts, 1),
192
162
  };
193
163
  }
194
164
  /**
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.
165
+ * Open a compose window pre-filled with the HTML, wait for it to close, and
166
+ * return the saved draft's HTML. On macOS the edit is kept by answering
167
+ * Outlook's own "save this message?" prompt on close.
205
168
  */
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)}"}
169
+ async function editEmailTemplate(request) {
170
+ const subject = `${request.label} - Save and close when done`;
171
+ const saved = await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
172
+ set editSubject to ${(0, run_1.asString)(subject)}
173
+ set draftMsg to make new outgoing message with properties {subject:editSubject, content:${(0, run_1.asString)(request.html)}}
211
174
  open draftMsg
212
175
  activate
213
176
  end tell
214
177
 
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.
178
+ -- Poll until the compose window is gone, identifying ours by subject rather
179
+ -- than counting windows the person may also have open.
218
180
  set stillOpen to true
219
181
  repeat while stillOpen
220
182
  delay 0.7
@@ -234,9 +196,8 @@ repeat while stillOpen
234
196
  end repeat
235
197
 
236
198
  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.
199
+ -- Prefer the saved draft: it is what the person chose to keep, and it
200
+ -- outlives the window. Every editor draft is removed so none pile up.
240
201
  set savedHtml to ""
241
202
  try
242
203
  repeat with acctList in {exchange accounts, imap accounts, pop accounts}
@@ -255,11 +216,9 @@ tell application "Microsoft Outlook"
255
216
  end repeat
256
217
  end try
257
218
  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);
219
+ end tell`, 'interactive');
261
220
  if (!saved) {
262
- throw new errors_1.NotFoundError('template', 'No template saved. Did you save the draft before closing?');
221
+ throw new errors_1.NotFoundError('template', 'No template was saved. Save the draft before closing the window.');
263
222
  }
264
223
  return saved;
265
224
  }
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/mac/templates.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/mac/templates.ts"],"names":[],"mappings":"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","sourcesContent":["// Template emails: ordinary mail items kept in a mailbox folder and reused as\n// email bodies, plus the compose-window editor for one.\n//\n// Outlook for Mac serves a message's HTML through `content` — the same thing\n// COM's HTMLBody returns — which is what makes templates portable between the\n// two platforms.\nimport {\n asIdList,\n asInt,\n asRow,\n asString,\n boolField,\n field,\n runOsaScript,\n splitFields,\n splitList,\n splitRecords,\n summaryFields,\n} from './run';\nimport {\n accountLookupSnippet,\n FIND_FOLDER_HANDLER,\n folderChainSnippet,\n macFolderPath,\n resolveMacAccount,\n rootFolderSnippet,\n} from './scripts';\nimport {\n EMBEDDED_IMAGE_SOURCE,\n MAX_TEMPLATE_HTML,\n TEMPLATE_PREVIEW_CHARS,\n TEMPLATE_SEARCH_DEPTH,\n} from '../shared/templateLimits';\nimport { findTemplateMarkers } from '../templateBody';\nimport { NotFoundError } from '../errors';\nimport type { EditTemplateRequest, ReadTemplatesRequest, SaveTemplateRequest } from '../backend';\nimport type { SaveTemplateResult, TemplateEmail, TemplateFolderResult } from '../types';\n\n/** A template body without its embedded images — see EMBEDDED_IMAGE_SOURCE. */\nfunction stripEmbeddedImages(html: string): string {\n return html.replace(new RegExp(EMBEDDED_IMAGE_SOURCE, 'gi'), '');\n}\n\n/**\n * The template emails in a mailbox folder, newest first; a missing folder is\n * reported as folderFound:false with the mailbox's top-level folder names.\n *\n * Bodies are read in a second pass, for the chosen templates only: a folder of\n * Word-sized templates is megabytes, and pulling all of them through stdout on\n * a call that wanted one is what overruns the output limit.\n */\nexport async function readTemplateEmails(request: ReadTemplatesRequest): Promise<TemplateFolderResult> {\n const acct = await resolveMacAccount(request.account);\n // Pass 1 — find the folder and index it: three bulk reads, no bodies.\n const records = splitRecords(await runOsaScript(`${FIND_FOLDER_HANDLER}\ntell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'root folder', 'rootFolder')}\nend tell\nset theFolder to my findFolderByName(rootFolder, ${asString(request.folder)}, ${asInt(TEMPLATE_SEARCH_DEPTH)})\ntell application \"Microsoft Outlook\"\n if theFolder is missing value then\n set names to {}\n repeat with f in (mail folders of rootFolder)\n try\n set end of names to (name of f) as string\n end try\n end repeat\n return ${asRow(['\"0\"', 'my sanitizeList(names)'])}\n end if\n set idList to id of every message of theFolder\n set subjList to subject of every message of theFolder\n try\n set modList to modification date of every message of theFolder\n on error\n set modList to {}\n end try\n set hasMods to ((count of modList) is (count of idList))\n -- The folder may sit up to the search depth down, so its whole chain is\n -- reported, not just its name: the path is the one Windows' FolderPath gives.\n${folderChainSnippet(' ')}\n set out to ${asRow(['\"1\"', 'my sanitizeList(chainNames)'])}\n repeat with i from 1 to (count of idList)\n set modAt to \"\"\n if hasMods then set modAt to my isoDate(item i of modList)\n set out to out & ${asRow(['(item i of idList as string)', '(item i of subjList)', 'modAt'])}\n end repeat\n return out\nend tell`, 'standard'));\n\n const header = splitFields(records[0] ?? '');\n if (field(header, 0) !== '1') {\n return { folderFound: false, folderPath: '', templates: [], availableFolders: splitList(field(header, 1)) };\n }\n const chain = splitList(field(header, 1)).reverse();\n const folderPath = chain.length\n ? macFolderPath(request.account, chain[0], chain.slice(1))\n : macFolderPath(request.account, request.folder, []);\n const wanted = (request.subject ?? '').toLowerCase();\n const chosen = records.slice(1)\n .map(record => {\n const parts = splitFields(record);\n return { id: field(parts, 0), subject: field(parts, 1).trim(), lastModified: field(parts, 2) };\n })\n // Newest first, before the cap, so \"the newest 20\" means that.\n .sort((a, b) => b.lastModified.localeCompare(a.lastModified))\n .filter(item => !wanted || item.subject.toLowerCase() === wanted)\n .slice(0, request.limit);\n if (chosen.length === 0) return { folderFound: true, folderPath, templates: [], availableFolders: [] };\n\n // Pass 2 — the bodies, for the chosen templates only.\n const bodyProperty = request.includeBody ? 'content' : 'plain text content';\n const bodies = new Map<string, string>();\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n set out to \"\"\n repeat with theId in ${asIdList(chosen.map(item => item.id))}\n set theMsg to missing value\n try\n set theMsg to message id theId\n end try\n if theMsg is not missing value then\n set bodyText to \"\"\n try\n set bodyText to (${bodyProperty} of theMsg) as string\n end try\n set out to out & ${asRow(['(theId as string)', 'bodyText'])}\n end if\n end repeat\n return out\nend tell`, 'standard');\n for (const record of splitRecords(raw)) {\n const parts = splitFields(record);\n bodies.set(field(parts, 0), field(parts, 1));\n }\n\n const templates: TemplateEmail[] = chosen.map(item => {\n const body = bodies.get(item.id) ?? '';\n const html = request.includeBody ? stripEmbeddedImages(body).slice(0, MAX_TEMPLATE_HTML) : '';\n // With the HTML in hand it is authoritative (markers survive Word\n // splitting them across tags); without it, the plain text is scanned,\n // where a marker can't have been split.\n const markers = findTemplateMarkers(request.includeBody ? html : body);\n return {\n entryId: item.id,\n subject: item.subject,\n htmlBody: html,\n bodyPreview: request.includeBody ? '' : body.trim().slice(0, TEMPLATE_PREVIEW_CHARS),\n sections: markers.sections,\n placeholders: markers.placeholders,\n lastModified: item.lastModified,\n };\n });\n return { folderFound: true, folderPath, templates, availableFolders: [] };\n}\n\n/**\n * Save a new template email into a folder, creating the folder at the mailbox\n * root when absent. Always adds a new item — never overwrites one.\n */\nexport async function saveTemplateEmail(request: SaveTemplateRequest): Promise<SaveTemplateResult> {\n const acct = await resolveMacAccount(request.account);\n const parts = summaryFields(await runOsaScript(`${FIND_FOLDER_HANDLER}\ntell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'root folder', 'rootFolder')}\nend tell\nset theFolder to my findFolderByName(rootFolder, ${asString(request.folder)}, ${asInt(TEMPLATE_SEARCH_DEPTH)})\ntell application \"Microsoft Outlook\"\n set wasCreated to false\n if theFolder is missing value then\n set theFolder to (make new mail folder at rootFolder with properties {name:${asString(request.folder)}})\n set wasCreated to true\n end if\n set countBefore to count of messages of theFolder\n set newMsg to make new outgoing message with properties {subject:${asString(request.subject)}, content:${asString(request.htmlBody)}}\n try\n set account of newMsg to targetAcct\n end try\n -- 'move' reports success even where it silently does nothing, so a template\n -- that was never filed must not be reported as saved.\n move newMsg to theFolder\n if (count of messages of theFolder) is not greater than countBefore then error \"Outlook did not file the template in '\" & (name of theFolder) & \"'.\"\n return ${asRow(['(name of theFolder as string)', '(wasCreated as string)'])}\nend tell`, 'standard'));\n return {\n folderPath: macFolderPath(request.account, field(parts, 0) || request.folder, []),\n folderCreated: boolField(parts, 1),\n };\n}\n\n/**\n * Open a compose window pre-filled with the HTML, wait for it to close, and\n * return the saved draft's HTML. On macOS the edit is kept by answering\n * Outlook's own \"save this message?\" prompt on close.\n */\nexport async function editEmailTemplate(request: EditTemplateRequest): Promise<string> {\n const subject = `${request.label} - Save and close when done`;\n const saved = await runOsaScript(`tell application \"Microsoft Outlook\"\n set editSubject to ${asString(subject)}\n set draftMsg to make new outgoing message with properties {subject:editSubject, content:${asString(request.html)}}\n open draftMsg\n activate\nend tell\n\n-- Poll until the compose window is gone, identifying ours by subject rather\n-- than counting windows the person may also have open.\nset stillOpen to true\nrepeat while stillOpen\n delay 0.7\n set stillOpen to false\n tell application \"Microsoft Outlook\"\n try\n repeat with w in draft windows\n try\n if ((subject of (object of w)) as string) is editSubject then\n set stillOpen to true\n exit repeat\n end if\n end try\n end repeat\n end try\n end tell\nend repeat\n\ntell application \"Microsoft Outlook\"\n -- Prefer the saved draft: it is what the person chose to keep, and it\n -- outlives the window. Every editor draft is removed so none pile up.\n set savedHtml to \"\"\n try\n repeat with acctList in {exchange accounts, imap accounts, pop accounts}\n repeat with a in acctList\n try\n repeat with m in (messages of (drafts of a))\n try\n if ((subject of m) as string) is editSubject then\n if savedHtml is \"\" then set savedHtml to (content of m) as string\n delete m\n end if\n end try\n end repeat\n end try\n end repeat\n end repeat\n end try\n return my sanitize(savedHtml)\nend tell`, 'interactive');\n if (!saved) {\n throw new NotFoundError('template', 'No template was saved. Save the draft before closing the window.');\n }\n return saved;\n}\n"]}
package/dist/mail.d.ts CHANGED
@@ -1,69 +1,103 @@
1
- import type { MailFolderRef } from './types';
1
+ import type { DeleteMailOutcome, DeleteMailResult, MailFolderRef } from './types';
2
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.
3
+ * Outlook's olDefaultFolders ids for the folders this package addresses by
4
+ * role rather than by name. The ids are Outlook's own, and a folder's NAME is
5
+ * localized, so a role is always resolved through its id.
9
6
  */
10
- export declare function clamp(value: number, min: number, max: number): number;
7
+ export declare const FolderId: Readonly<{
8
+ DeletedItems: 3;
9
+ Outbox: 4;
10
+ SentMail: 5;
11
+ Inbox: 6;
12
+ Drafts: 16;
13
+ Junk: 23;
14
+ }>;
11
15
  /**
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`).
16
+ * The folders addressable by name rather than by walking from the Inbox, with
17
+ * their olDefaultFolders id. A well-known name wins over a user folder of the
18
+ * same name; reach the latter by qualifying it (`Inbox\Drafts`).
19
19
  */
20
- export declare const WELL_KNOWN_FOLDERS: Record<string, number>;
20
+ export declare const WELL_KNOWN_FOLDERS: Readonly<Record<string, number>>;
21
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.
22
+ * Why `deleteOutlookEmails` refuses an item, worded once for both platforms —
23
+ * it reaches the caller verbatim as a per-item `reason`.
30
24
  */
31
25
  export declare const PROTECTED_MAIL_REASON = "received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override";
26
+ /** The status a delete script reported for one item; anything it shouldn't have said is a failure. */
27
+ export declare function deleteStatus(reported: string): DeleteMailOutcome['status'];
28
+ /** A deleteOutlookEmails result: every outcome, and how many ended each way. */
29
+ export declare function deleteMailResult(dryRun: boolean, items: DeleteMailOutcome[]): DeleteMailResult;
32
30
  /**
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.
31
+ * Roots whose items are outgoing mail, which carries a sent time where incoming
32
+ * mail carries a received one — so a date filter has to know which it's on.
39
33
  */
40
34
  export declare function isOutgoingRoot(rootId: number): boolean;
41
35
  /**
42
- * Resolve a caller's folder string to a well-known root plus the segments below it.
36
+ * Roots an Inbox walk must never descend into.
43
37
  *
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").
38
+ * On an Exchange profile these sit beside the Inbox and a walk from it can't
39
+ * reach them anyway. On an IMAP profile Outlook nests them UNDER the Inbox, so
40
+ * the same walk would hand back mail already sent, drafted or thrown away as if
41
+ * it had just arrived. They are matched by id, never by name: the names are
42
+ * localized.
43
+ */
44
+ export declare const NON_INCOMING_ROOTS: readonly number[];
45
+ /** The reply/forward prefixes as one case-insensitive pattern source. */
46
+ export declare const REPLY_PREFIX_SOURCE: string;
47
+ export declare const REPLY_PREFIX: RegExp;
48
+ /**
49
+ * Whether a subject opens on a reply or forward prefix.
50
+ *
51
+ * This reads what the subject claims, not how the email was made: a forward
52
+ * carries a prefix too, and a client can leave one off a real reply.
53
+ */
54
+ export declare function hasReplyPrefix(subject: string): boolean;
55
+ /**
56
+ * The subject every message in a thread shares: every reply and forward prefix
57
+ * stripped, however many clients stacked them (`RE: FW: RE[2]: Rate request`
58
+ * is `Rate request`), and whitespace collapsed. Case is kept, so lower-case
59
+ * both sides to compare two threads.
60
+ */
61
+ export declare function threadSubject(subject: string): string;
62
+ /**
63
+ * A subject glob as an anchored regex source: `*` matches any run, `?` one
64
+ * character, everything else itself. Written with constructs JavaScript and
65
+ * .NET read identically, so both platforms apply exactly the same test.
66
+ */
67
+ export declare function subjectGlobSource(glob: string): string;
68
+ /** The leaf name of a folder path or bare name. */
69
+ export declare function folderLeafName(entry: string): string;
70
+ /**
71
+ * Resolve a folder string to a well-known root plus the segments below it.
48
72
  *
49
- * An unrecognized first segment means Inbox-relative, which is what keeps a bare
50
- * subfolder name working unchanged.
73
+ * Accepts every shape a person has to hand: a bare name ('Invoices'), a
74
+ * relative path ('Invoices\Paid'), a well-known folder ('Sent Items'), a path
75
+ * under one ('Deleted Items\2026'), and the full path listInboxFolders prints
76
+ * ('\\team@example.com\Inbox\Invoices'). Anything not starting at a well-known
77
+ * folder is taken as relative to the Inbox.
51
78
  */
52
79
  export declare function mailFolderRef(folder: string): MailFolderRef;
80
+ /**
81
+ * A caller's folder option as a reference: none given means the Inbox root,
82
+ * and a string that names no folder at all (only separators) is refused rather
83
+ * than silently read as the Inbox root.
84
+ */
85
+ export declare function folderOption(folder: string | undefined): {
86
+ ref: MailFolderRef;
87
+ label: string;
88
+ };
53
89
  /**
54
90
  * Split a plain-text reply into the sender's own text and the quoted thread
55
91
  * below it.
56
92
  *
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.
93
+ * This matters more than it looks: the quoted original carries the text of the
94
+ * message being answered, and handing a reader one blob invites a figure from
95
+ * that message to be read back as the sender's own.
61
96
  *
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.
97
+ * Splits at the earliest separator Outlook or the common webmail clients emit.
98
+ * If that would leave no new text at all — a bottom-posted reply, or a body
99
+ * that opens on a quote — the split is abandoned and everything is `body`,
100
+ * since losing the sender's actual words is the one outcome worth avoiding.
67
101
  */
68
102
  export declare function splitQuotedOriginal(text: string): {
69
103
  body: string;