@dawnlit/outlook-bridge 2.0.0 → 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 (88) hide show
  1. package/README.md +6 -4
  2. package/dist/backend.d.ts +2 -2
  3. package/dist/backend.js.map +1 -1
  4. package/dist/bridge.js +3 -3
  5. package/dist/bridge.js.map +1 -1
  6. package/dist/errors.d.ts +0 -2
  7. package/dist/errors.js +1 -6
  8. package/dist/errors.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/mac/accounts.js.map +1 -1
  12. package/dist/mac/attachments.js +1 -1
  13. package/dist/mac/attachments.js.map +1 -1
  14. package/dist/mac/bounces.js +8 -12
  15. package/dist/mac/bounces.js.map +1 -1
  16. package/dist/mac/cleanup.js +4 -41
  17. package/dist/mac/cleanup.js.map +1 -1
  18. package/dist/mac/drafts.d.ts +3 -1
  19. package/dist/mac/drafts.js +34 -33
  20. package/dist/mac/drafts.js.map +1 -1
  21. package/dist/mac/folders.d.ts +5 -3
  22. package/dist/mac/folders.js +18 -19
  23. package/dist/mac/folders.js.map +1 -1
  24. package/dist/mac/profile.js.map +1 -1
  25. package/dist/mac/read.d.ts +4 -0
  26. package/dist/mac/read.js +54 -16
  27. package/dist/mac/read.js.map +1 -1
  28. package/dist/mac/run.d.ts +19 -2
  29. package/dist/mac/run.js +42 -2
  30. package/dist/mac/run.js.map +1 -1
  31. package/dist/mac/scripts.d.ts +21 -14
  32. package/dist/mac/scripts.js +52 -21
  33. package/dist/mac/scripts.js.map +1 -1
  34. package/dist/mac/send.d.ts +3 -3
  35. package/dist/mac/send.js +44 -12
  36. package/dist/mac/send.js.map +1 -1
  37. package/dist/mac/templates.js +15 -18
  38. package/dist/mac/templates.js.map +1 -1
  39. package/dist/mail.d.ts +5 -3
  40. package/dist/mail.js +19 -6
  41. package/dist/mail.js.map +1 -1
  42. package/dist/runtime.js.map +1 -1
  43. package/dist/shared/args.js.map +1 -1
  44. package/dist/shared/attachmentMatch.d.ts +0 -6
  45. package/dist/shared/attachmentMatch.js +0 -1
  46. package/dist/shared/attachmentMatch.js.map +1 -1
  47. package/dist/shared/exec.js.map +1 -1
  48. package/dist/shared/json.d.ts +11 -6
  49. package/dist/shared/json.js +12 -7
  50. package/dist/shared/json.js.map +1 -1
  51. package/dist/shared/locate.d.ts +0 -8
  52. package/dist/shared/locate.js +0 -1
  53. package/dist/shared/locate.js.map +1 -1
  54. package/dist/shared/replyBody.d.ts +0 -15
  55. package/dist/shared/replyBody.js +0 -2
  56. package/dist/shared/replyBody.js.map +1 -1
  57. package/dist/shared/templateLimits.d.ts +14 -0
  58. package/dist/shared/templateLimits.js +21 -0
  59. package/dist/shared/templateLimits.js.map +1 -0
  60. package/dist/templateBody.d.ts +6 -0
  61. package/dist/templateBody.js +8 -1
  62. package/dist/templateBody.js.map +1 -1
  63. package/dist/types.d.ts +23 -3
  64. package/dist/types.js.map +1 -1
  65. package/dist/windows/bounces.js +4 -4
  66. package/dist/windows/bounces.js.map +1 -1
  67. package/dist/windows/cleanup.d.ts +5 -1
  68. package/dist/windows/cleanup.js +16 -11
  69. package/dist/windows/cleanup.js.map +1 -1
  70. package/dist/windows/drafts.d.ts +4 -4
  71. package/dist/windows/drafts.js +20 -31
  72. package/dist/windows/drafts.js.map +1 -1
  73. package/dist/windows/folders.js +1 -1
  74. package/dist/windows/folders.js.map +1 -1
  75. package/dist/windows/read.js +6 -2
  76. package/dist/windows/read.js.map +1 -1
  77. package/dist/windows/run.d.ts +0 -2
  78. package/dist/windows/run.js +1 -2
  79. package/dist/windows/run.js.map +1 -1
  80. package/dist/windows/scripts.d.ts +4 -4
  81. package/dist/windows/scripts.js +10 -10
  82. package/dist/windows/scripts.js.map +1 -1
  83. package/dist/windows/send.d.ts +8 -3
  84. package/dist/windows/send.js +10 -2
  85. package/dist/windows/send.js.map +1 -1
  86. package/dist/windows/templates.js +9 -15
  87. package/dist/windows/templates.js.map +1 -1
  88. package/package.json +2 -2
@@ -52,8 +52,16 @@ export declare function dateProperty(rootId: number): string;
52
52
  * AppleScript literal is unreadable, and the parts are known here anyway.
53
53
  */
54
54
  export declare function macFolderPath(emailAccount: string, rootLabel: string, segments: readonly string[]): string;
55
- /** The dictionary term for a root, or NOT_IMPLEMENTED naming what was asked. */
56
- export declare function rootTerm(ref: MailFolderRef, folderLabel?: string): string;
55
+ /**
56
+ * Walk a folder's `container` chain to the top as the list `chainNames`,
57
+ * nearest first. Requires `theFolder`, and leaves `cur` on the folder the walk
58
+ * stopped at: the account's nameless root folder, when it got that far.
59
+ *
60
+ * The chain's last element is the folder directly under the account, and an
61
+ * account can't hold two top-level folders of one name, so comparing that name
62
+ * is exact.
63
+ */
64
+ export declare function folderChainSnippet(indent: string): string;
57
65
  /**
58
66
  * Resolve `scopeFolder` from `targetAcct`: a well-known root walked down through
59
67
  * any further segments. Also binds `scopeCreated`.
@@ -81,16 +89,15 @@ export declare function partitionMessageIds(entryIds: readonly string[]): {
81
89
  invalid: ItemFailure[];
82
90
  };
83
91
  /**
84
- * Resolve `variable` from a message id, or fail as NOT_FOUND. `message id N`
85
- * resolves against the application, so the message is found wherever it sits.
92
+ * Bind `theMsg` to a message id, or fail as NOT_FOUND. `message id N` resolves
93
+ * against the application, so the message is found wherever it sits.
86
94
  */
87
- export declare function messageLookupSnippet(id: string, variable?: string): string;
95
+ export declare function messageLookupSnippet(id: string): string;
88
96
  /**
89
- * The sender's display name and address as `sndName` / `sndAddr`. `sender` is a
90
- * record, and `address of sender of m` doesn't coerce, so the record is bound
91
- * first.
97
+ * `theMsg`'s sender as `sndName` / `sndAddr`. `sender` is a record, and
98
+ * `address of sender of m` doesn't coerce, so the record is bound first.
92
99
  */
93
- export declare function senderSnippet(messageVariable?: string, indent?: string): string;
100
+ export declare function senderSnippet(indent?: string): string;
94
101
  /**
95
102
  * Everything a reader wants off one message bound to `theMsg`, emitted together
96
103
  * with the row it produces (see `messageDetailFields`) so the read order and the
@@ -111,12 +118,12 @@ export declare const MessageDetail: {
111
118
  readonly body: 6;
112
119
  };
113
120
  /**
114
- * The first recipient's name and address as `sndName` / `sndAddr` — what
115
- * outgoing mail reports in place of a sender.
121
+ * `theMsg`'s first recipient as `sndName` / `sndAddr` — what outgoing mail
122
+ * reports in place of a sender.
116
123
  */
117
- export declare function firstRecipientSnippet(messageVariable?: string, indent?: string): string;
118
- /** Every address a message was sent to — To, CC and BCC — as the list `addrList`. */
119
- export declare function allRecipientsSnippet(messageVariable?: string, indent?: string): string;
124
+ export declare function firstRecipientSnippet(indent?: string): string;
125
+ /** Every address `theMsg` was sent to — To, CC and BCC — as the list `addrList`. */
126
+ export declare function allRecipientsSnippet(indent?: string): string;
120
127
  /**
121
128
  * Recursive, case-insensitive, depth-capped folder search from `startFolder` —
122
129
  * the macOS counterpart of the Windows Find-FolderByName.
@@ -6,7 +6,7 @@ exports.accountLookupSnippet = accountLookupSnippet;
6
6
  exports.rootFolderSnippet = rootFolderSnippet;
7
7
  exports.dateProperty = dateProperty;
8
8
  exports.macFolderPath = macFolderPath;
9
- exports.rootTerm = rootTerm;
9
+ exports.folderChainSnippet = folderChainSnippet;
10
10
  exports.mailScopeSnippet = mailScopeSnippet;
11
11
  exports.macMessageId = macMessageId;
12
12
  exports.partitionMessageIds = partitionMessageIds;
@@ -113,6 +113,38 @@ function dateProperty(rootId) {
113
113
  function macFolderPath(emailAccount, rootLabel, segments) {
114
114
  return ['\\\\' + emailAccount, rootLabel, ...segments].join('\\');
115
115
  }
116
+ /**
117
+ * Walk a folder's `container` chain to the top as the list `chainNames`,
118
+ * nearest first. Requires `theFolder`, and leaves `cur` on the folder the walk
119
+ * stopped at: the account's nameless root folder, when it got that far.
120
+ *
121
+ * The chain's last element is the folder directly under the account, and an
122
+ * account can't hold two top-level folders of one name, so comparing that name
123
+ * is exact.
124
+ */
125
+ function folderChainSnippet(indent) {
126
+ return `${indent}set chainNames to {}
127
+ ${indent}set cur to theFolder
128
+ ${indent}set guard to 0
129
+ ${indent}repeat while cur is not missing value and guard < 25
130
+ ${indent} -- The account's root folder has a name of missing value, and coercing
131
+ ${indent} -- that yields the STRING "missing value" — so test before coercing.
132
+ ${indent} set rawName to missing value
133
+ ${indent} try
134
+ ${indent} set rawName to name of cur
135
+ ${indent} end try
136
+ ${indent} if rawName is missing value then exit repeat
137
+ ${indent} set nm to rawName as string
138
+ ${indent} if nm is "" then exit repeat
139
+ ${indent} set end of chainNames to nm
140
+ ${indent} set nextUp to missing value
141
+ ${indent} try
142
+ ${indent} set nextUp to container of cur
143
+ ${indent} end try
144
+ ${indent} set cur to nextUp
145
+ ${indent} set guard to guard + 1
146
+ ${indent}end repeat`;
147
+ }
116
148
  /** The dictionary term for a root, or NOT_IMPLEMENTED naming what was asked. */
117
149
  function rootTerm(ref, folderLabel = '') {
118
150
  const term = exports.MAC_ROOT_TERMS[ref.rootId];
@@ -188,27 +220,26 @@ function partitionMessageIds(entryIds) {
188
220
  return { valid, invalid };
189
221
  }
190
222
  /**
191
- * Resolve `variable` from a message id, or fail as NOT_FOUND. `message id N`
192
- * resolves against the application, so the message is found wherever it sits.
223
+ * Bind `theMsg` to a message id, or fail as NOT_FOUND. `message id N` resolves
224
+ * against the application, so the message is found wherever it sits.
193
225
  */
194
- function messageLookupSnippet(id, variable = 'theMsg') {
226
+ function messageLookupSnippet(id) {
195
227
  const complaint = `${(0, errors_1.failureTag)('NOT_FOUND', 'email')}No email found for message id '${id}'. It may have been deleted - list the mail again for a current id.`;
196
- return ` set ${variable} to missing value
228
+ return ` set theMsg to missing value
197
229
  try
198
- set ${variable} to message id ${id}
230
+ set theMsg to message id ${id}
199
231
  end try
200
- if ${variable} is missing value then error ${(0, run_1.asString)(complaint)}`;
232
+ if theMsg is missing value then error ${(0, run_1.asString)(complaint)}`;
201
233
  }
202
234
  /**
203
- * The sender's display name and address as `sndName` / `sndAddr`. `sender` is a
204
- * record, and `address of sender of m` doesn't coerce, so the record is bound
205
- * first.
235
+ * `theMsg`'s sender as `sndName` / `sndAddr`. `sender` is a record, and
236
+ * `address of sender of m` doesn't coerce, so the record is bound first.
206
237
  */
207
- function senderSnippet(messageVariable = 'theMsg', indent = ' ') {
238
+ function senderSnippet(indent = ' ') {
208
239
  return `${indent}set sndName to ""
209
240
  ${indent}set sndAddr to ""
210
241
  ${indent}try
211
- ${indent} set snd to sender of ${messageVariable}
242
+ ${indent} set snd to sender of theMsg
212
243
  ${indent} try
213
244
  ${indent} set sndAddr to (address of snd) as string
214
245
  ${indent} end try
@@ -228,7 +259,7 @@ function messageDetailSnippet(withBody = false, indent = ' ') {
228
259
  ${indent}try
229
260
  ${indent} set subj to (subject of theMsg) as string
230
261
  ${indent}end try
231
- ${senderSnippet('theMsg', indent)}
262
+ ${senderSnippet(indent)}
232
263
  ${indent}set recvd to ""
233
264
  ${indent}try
234
265
  ${indent} set recvd to my isoDate(time received of theMsg)
@@ -248,7 +279,7 @@ ${indent}end try` : '');
248
279
  }
249
280
  /** The fields `messageDetailSnippet` fills, in row order — see `MessageDetail`. */
250
281
  function messageDetailFields(withBody = false) {
251
- const fields = ['(id of theMsg as string)', 'subj', 'sndName', 'sndAddr', 'recvd', 'my sanitizeList(attNames)'];
282
+ const fields = ['(id of theMsg as string)', 'subj', 'sndName', 'sndAddr', 'recvd', 'my sanitizePositionalList(attNames)'];
252
283
  if (withBody)
253
284
  fields.push('bodyText');
254
285
  return fields;
@@ -264,14 +295,14 @@ exports.MessageDetail = {
264
295
  body: 6,
265
296
  };
266
297
  /**
267
- * The first recipient's name and address as `sndName` / `sndAddr` — what
268
- * outgoing mail reports in place of a sender.
298
+ * `theMsg`'s first recipient as `sndName` / `sndAddr` — what outgoing mail
299
+ * reports in place of a sender.
269
300
  */
270
- function firstRecipientSnippet(messageVariable = 'theMsg', indent = ' ') {
301
+ function firstRecipientSnippet(indent = ' ') {
271
302
  return `${indent}set sndName to ""
272
303
  ${indent}set sndAddr to ""
273
304
  ${indent}try
274
- ${indent} set rcps to to recipients of ${messageVariable}
305
+ ${indent} set rcps to to recipients of theMsg
275
306
  ${indent} if (count of rcps) > 0 then
276
307
  ${indent} set ea to email address of (item 1 of rcps)
277
308
  ${indent} try
@@ -283,11 +314,11 @@ ${indent} end try
283
314
  ${indent} end if
284
315
  ${indent}end try`;
285
316
  }
286
- /** Every address a message was sent to — To, CC and BCC — as the list `addrList`. */
287
- function allRecipientsSnippet(messageVariable = 'theMsg', indent = ' ') {
317
+ /** Every address `theMsg` was sent to — To, CC and BCC — as the list `addrList`. */
318
+ function allRecipientsSnippet(indent = ' ') {
288
319
  return `${indent}set addrList to {}
289
320
  ${indent}try
290
- ${indent} repeat with r in (every recipient of ${messageVariable})
321
+ ${indent} repeat with r in (every recipient of theMsg)
291
322
  ${indent} try
292
323
  ${indent} -- The record is bound before its field is read: a nested
293
324
  ${indent} -- 'address of (email address of r)' doesn't coerce, and fails
@@ -1 +1 @@
1
- {"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/mac/scripts.ts"],"names":[],"mappings":"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","sourcesContent":["// The AppleScript fragments more than one macOS operation is built from.\nimport { asString } from './run';\nimport { readProfileAccounts } from './profile';\nimport { failureTag, InvalidRequestError, NotImplementedError } from '../errors';\nimport { FolderId, isOutgoingRoot } from '../mail';\nimport type { ItemFailure, MailFolderRef } from '../types';\n\n/**\n * An account as the generated AppleScript reaches it.\n *\n * `folderIds` carries what Outlook's profile database knows about the mailbox's\n * well-known folders — the only handle on a mailbox the dictionary publishes no\n * account object for (see profile.ts). It is filled in for every account the\n * profile lists, because the generated script chooses between the account\n * object and the folder id at run time; nothing here has to predict which kinds\n * of account a given Outlook build exposes.\n */\nexport interface MacAccount {\n readonly emailAccount: string;\n readonly folderIds?: Readonly<Record<string, number>>;\n}\n\n/** Look an address up in Outlook's profile database, for the folder ids it can add. */\nexport async function resolveMacAccount(emailAccount: string): Promise<MacAccount> {\n const wanted = emailAccount.trim().toLowerCase();\n const found = (await readProfileAccounts())\n .find(account => account.emailAccount.trim().toLowerCase() === wanted);\n return found ? {emailAccount, folderIds: found.folderIds} : {emailAccount};\n}\n\n/**\n * Resolve the account whose address matches into `targetAcct`, inside a\n * `tell application \"Microsoft Outlook\"` block.\n *\n * `every account` errors even in legacy mode, so the typed classes are probed.\n * A `whose email address is …` filter can't be used either: `email address` is\n * also a class name, and AppleScript resolves it as one.\n *\n * A mailbox the dictionary publishes no account object for leaves `targetAcct`\n * as `missing value` rather than failing, provided the profile gave a folder id\n * to reach it by. `needsAccountObject` is for composing mail, which can't work\n * without the object: the account a message is sent from can only be set from\n * it, and a message composed without it goes out from the default mailbox.\n */\nexport function accountLookupSnippet(acct: MacAccount, needsAccountObject = false): string {\n const probe = ` set targetAcct to missing value\n try\n repeat with a in (exchange accounts & imap accounts & pop accounts)\n if (email address of a as string) is ${asString(acct.emailAccount)} then\n set targetAcct to a\n exit repeat\n end if\n end repeat\n end try`;\n if (acct.folderIds && !needsAccountObject) return probe;\n const complaint = acct.folderIds\n ? `${failureTag('INVALID_REQUEST')}Outlook publishes no account object for '${acct.emailAccount}', `\n + 'so the account a message is sent from cannot be set. Its folders can be read; mail cannot be composed from it.'\n : `${failureTag('ACCOUNT_NOT_FOUND')}Account '${acct.emailAccount}' not found in Outlook.`;\n return `${probe}\n if targetAcct is missing value then error ${asString(complaint)}`;\n}\n\n/**\n * Bind `variable` to one of the account's well-known folders: through the\n * account object when the probe found one, else by the folder's own id. `mail\n * folder id N` resolves against the application rather than an account, and N is\n * the id Outlook's profile database records for that folder.\n */\nexport function rootFolderSnippet(acct: MacAccount, term: string, variable: string): string {\n const viaAccount = ` set ${variable} to ${term} of targetAcct`;\n const id = acct.folderIds?.[term];\n if (id !== undefined) {\n return ` if targetAcct is missing value then\n set ${variable} to mail folder id ${id}\n else\n${viaAccount}\n end if`;\n }\n // The profile listed the mailbox but not this root, so the probe is the only\n // way in — and it may have come up empty. Name the folder out of reach\n // rather than let `<term> of missing value` say it.\n if (acct.folderIds) {\n const complaint = `${failureTag('NOT_FOUND', 'folder')}Account '${acct.emailAccount}' has no ${term} folder AppleScript can reach.`;\n return ` if targetAcct is missing value then error ${asString(complaint)}\n${viaAccount}`;\n }\n return viaAccount;\n}\n\n/**\n * Outlook for Mac's dictionary term for each well-known root, by olDefaultFolders\n * id. Verified against the running app: the terms are NOT what the Windows names\n * suggest — `sent mail` and `junk email` don't compile, `sent items` and `junk\n * mail` do — and one bad term fails the whole script at compile time.\n */\nexport const MAC_ROOT_TERMS: Readonly<Record<number, string>> = {\n [FolderId.Inbox]: 'inbox',\n [FolderId.SentMail]: 'sent items',\n [FolderId.Drafts]: 'drafts',\n [FolderId.DeletedItems]: 'deleted items',\n [FolderId.Outbox]: 'outbox',\n [FolderId.Junk]: 'junk mail',\n};\n\n/** The date property a root's items carry. */\nexport function dateProperty(rootId: number): string {\n return isOutgoingRoot(rootId) ? 'time sent' : 'time received';\n}\n\n/**\n * The Windows folder-path shape (`\\\\mailbox\\Inbox\\Invoices`), assembled in\n * TypeScript: escaping backslashes through a template literal and then an\n * AppleScript literal is unreadable, and the parts are known here anyway.\n */\nexport function macFolderPath(emailAccount: string, rootLabel: string, segments: readonly string[]): string {\n return ['\\\\\\\\' + emailAccount, rootLabel, ...segments].join('\\\\');\n}\n\n/** The dictionary term for a root, or NOT_IMPLEMENTED naming what was asked. */\nexport function rootTerm(ref: MailFolderRef, folderLabel = ''): string {\n const term = MAC_ROOT_TERMS[ref.rootId];\n if (!term) {\n const asked = folderLabel ? ` (asked for '${folderLabel}')` : '';\n throw new NotImplementedError(\n `Reading the '${ref.rootLabel}' folder${asked}`,\n `macOS, whose readable roots are ${Object.values(MAC_ROOT_TERMS).join(', ')}`,\n );\n }\n return term;\n}\n\n/**\n * Resolve `scopeFolder` from `targetAcct`: a well-known root walked down through\n * any further segments. Also binds `scopeCreated`.\n *\n * Names compare with `is`, which is case-insensitive in AppleScript, matching\n * the Windows walk. A missing segment fails as NOT_FOUND rather than falling\n * back to the root; `createMissing` builds the chain instead.\n */\nexport function mailScopeSnippet(acct: MacAccount, ref: MailFolderRef, folderLabel = '', createMissing = false): string {\n const term = rootTerm(ref, folderLabel);\n const walk = ref.segments.map(segment => {\n const complaint = `${failureTag('NOT_FOUND', 'folder')}Folder '${segment}' not found under '${ref.rootLabel}'.`;\n const create = createMissing\n ? ` set foundFolder to (make new mail folder at scopeFolder with properties {name:${asString(segment)}})\n set scopeCreated to true`\n : ` error ${asString(complaint)}`;\n return `\n set foundFolder to missing value\n repeat with sf in (mail folders of scopeFolder)\n if (name of sf as string) is ${asString(segment)} then\n set foundFolder to sf\n exit repeat\n end if\n end repeat\n if foundFolder is missing value then\n${create}\n end if\n set scopeFolder to foundFolder`;\n }).join('');\n return ` set scopeCreated to false\n${rootFolderSnippet(acct, term, 'scopeFolder')}${walk}`;\n}\n\n/**\n * The message id text for a script, or INVALID_REQUEST.\n *\n * Outlook for Mac ids are small integers; a Windows EntryID is refused up front\n * rather than left to fail as a confusing \"not found\", because a caller that\n * persisted one from a Windows run needs to be told exactly that.\n */\nexport function macMessageId(entryId: string): string {\n const id = String(entryId ?? '').trim();\n if (!/^\\d+$/.test(id)) {\n throw new InvalidRequestError(\n `'${entryId}' is not an Outlook for Mac message id. Mac ids are small integers `\n + '(e.g. \"1263\") listed on this machine; a Windows EntryID cannot be resolved here.',\n );\n }\n return id;\n}\n\n/**\n * Split ids into those a script can look up and those that can only fail. A\n * batch reports an unusable id beside a genuine miss rather than throwing and\n * discarding the ids that would have worked.\n */\nexport function partitionMessageIds(entryIds: readonly string[]): { valid: string[]; invalid: ItemFailure[] } {\n const valid: string[] = [];\n const invalid: ItemFailure[] = [];\n for (const entryId of entryIds) {\n try {\n valid.push(macMessageId(entryId));\n } catch (error) {\n invalid.push({entryId: String(entryId), subject: '', error: (error as Error).message});\n }\n }\n return {valid, invalid};\n}\n\n/**\n * Resolve `variable` from a message id, or fail as NOT_FOUND. `message id N`\n * resolves against the application, so the message is found wherever it sits.\n */\nexport function messageLookupSnippet(id: string, variable = 'theMsg'): string {\n const complaint = `${failureTag('NOT_FOUND', 'email')}No email found for message id '${id}'. It may have been deleted - list the mail again for a current id.`;\n return ` set ${variable} to missing value\n try\n set ${variable} to message id ${id}\n end try\n if ${variable} is missing value then error ${asString(complaint)}`;\n}\n\n/**\n * The sender's display name and address as `sndName` / `sndAddr`. `sender` is a\n * record, and `address of sender of m` doesn't coerce, so the record is bound\n * first.\n */\nexport function senderSnippet(messageVariable = 'theMsg', indent = ' '): string {\n return `${indent}set sndName to \"\"\n${indent}set sndAddr to \"\"\n${indent}try\n${indent} set snd to sender of ${messageVariable}\n${indent} try\n${indent} set sndAddr to (address of snd) as string\n${indent} end try\n${indent} try\n${indent} set sndName to (name of snd) as string\n${indent} end try\n${indent}end try`;\n}\n\n/**\n * Everything a reader wants off one message bound to `theMsg`, emitted together\n * with the row it produces (see `messageDetailFields`) so the read order and the\n * positions the decoders index into cannot drift apart. `bodyText` is emitted\n * LAST when asked for, so a stray separator in an earlier field can't shift it.\n */\nexport function messageDetailSnippet(withBody = false, indent = ' '): string {\n return `${indent}set subj to \"\"\n${indent}try\n${indent} set subj to (subject of theMsg) as string\n${indent}end try\n${senderSnippet('theMsg', indent)}\n${indent}set recvd to \"\"\n${indent}try\n${indent} set recvd to my isoDate(time received of theMsg)\n${indent}on error\n${indent} try\n${indent} set recvd to my isoDate(time sent of theMsg)\n${indent} end try\n${indent}end try\n${indent}set attNames to {}\n${indent}try\n${indent} set attNames to name of every attachment of theMsg\n${indent}end try` + (withBody ? `\n${indent}set bodyText to \"\"\n${indent}try\n${indent} set bodyText to (plain text content of theMsg) as string\n${indent}end try` : '');\n}\n\n/** The fields `messageDetailSnippet` fills, in row order — see `MessageDetail`. */\nexport function messageDetailFields(withBody = false): string[] {\n const fields = ['(id of theMsg as string)', 'subj', 'sndName', 'sndAddr', 'recvd', 'my sanitizeList(attNames)'];\n if (withBody) fields.push('bodyText');\n return fields;\n}\n\n/** Where each of those fields lands in the row. */\nexport const MessageDetail = {\n id: 0,\n subject: 1,\n senderName: 2,\n senderEmail: 3,\n receivedTime: 4,\n attachmentNames: 5,\n body: 6,\n} as const;\n\n/**\n * The first recipient's name and address as `sndName` / `sndAddr` — what\n * outgoing mail reports in place of a sender.\n */\nexport function firstRecipientSnippet(messageVariable = 'theMsg', indent = ' '): string {\n return `${indent}set sndName to \"\"\n${indent}set sndAddr to \"\"\n${indent}try\n${indent} set rcps to to recipients of ${messageVariable}\n${indent} if (count of rcps) > 0 then\n${indent} set ea to email address of (item 1 of rcps)\n${indent} try\n${indent} set sndAddr to (address of ea) as string\n${indent} end try\n${indent} try\n${indent} set sndName to (name of ea) as string\n${indent} end try\n${indent} end if\n${indent}end try`;\n}\n\n/** Every address a message was sent to — To, CC and BCC — as the list `addrList`. */\nexport function allRecipientsSnippet(messageVariable = 'theMsg', indent = ' '): string {\n return `${indent}set addrList to {}\n${indent}try\n${indent} repeat with r in (every recipient of ${messageVariable})\n${indent} try\n${indent} -- The record is bound before its field is read: a nested\n${indent} -- 'address of (email address of r)' doesn't coerce, and fails\n${indent} -- inside the try as an empty list rather than an error.\n${indent} set ea to email address of r\n${indent} set oneAddr to (address of ea) as string\n${indent} if oneAddr is not \"\" then set end of addrList to oneAddr\n${indent} end try\n${indent} end repeat\n${indent}end try`;\n}\n\n/**\n * Recursive, case-insensitive, depth-capped folder search from `startFolder` —\n * the macOS counterpart of the Windows Find-FolderByName.\n */\nexport const FIND_FOLDER_HANDLER = `\non findFolderByName(startFolder, wantedName, depth)\n tell application \"Microsoft Outlook\"\n set subs to mail folders of startFolder\n end tell\n repeat with f in subs\n if (name of f as string) is wantedName then return f\n end repeat\n if depth is less than or equal to 1 then return missing value\n repeat with f in subs\n set hit to my findFolderByName(f, wantedName, depth - 1)\n if hit is not missing value then return hit\n end repeat\n return missing value\nend findFolderByName\n`;\n\n/**\n * Enumerate the accounts, probing each class separately: legacy Outlook errors\n * on `every account`, and New Outlook returns empty lists for the classes.\n */\nexport const LIST_ACCOUNTS_SNIPPET = `\nset acctList to {}\ntell application \"Microsoft Outlook\"\n repeat with acctClass in {\"exchange\", \"imap\", \"pop\"}\n try\n if (acctClass as string) is \"exchange\" then\n set accts to exchange accounts\n else if (acctClass as string) is \"imap\" then\n set accts to imap accounts\n else\n set accts to pop accounts\n end if\n repeat with a in accts\n try\n set end of acctList to (email address of a as string)\n end try\n end repeat\n end try\n end repeat\nend tell\n`;\n"]}
1
+ {"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/mac/scripts.ts"],"names":[],"mappings":"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","sourcesContent":["// The AppleScript fragments more than one macOS operation is built from.\nimport { asString } from './run';\nimport { readProfileAccounts } from './profile';\nimport { failureTag, InvalidRequestError, NotImplementedError } from '../errors';\nimport { FolderId, isOutgoingRoot } from '../mail';\nimport type { ItemFailure, MailFolderRef } from '../types';\n\n/**\n * An account as the generated AppleScript reaches it.\n *\n * `folderIds` carries what Outlook's profile database knows about the mailbox's\n * well-known folders — the only handle on a mailbox the dictionary publishes no\n * account object for (see profile.ts). It is filled in for every account the\n * profile lists, because the generated script chooses between the account\n * object and the folder id at run time; nothing here has to predict which kinds\n * of account a given Outlook build exposes.\n */\nexport interface MacAccount {\n readonly emailAccount: string;\n readonly folderIds?: Readonly<Record<string, number>>;\n}\n\n/** Look an address up in Outlook's profile database, for the folder ids it can add. */\nexport async function resolveMacAccount(emailAccount: string): Promise<MacAccount> {\n const wanted = emailAccount.trim().toLowerCase();\n const found = (await readProfileAccounts())\n .find(account => account.emailAccount.trim().toLowerCase() === wanted);\n return found ? { emailAccount, folderIds: found.folderIds } : { emailAccount };\n}\n\n/**\n * Resolve the account whose address matches into `targetAcct`, inside a\n * `tell application \"Microsoft Outlook\"` block.\n *\n * `every account` errors even in legacy mode, so the typed classes are probed.\n * A `whose email address is …` filter can't be used either: `email address` is\n * also a class name, and AppleScript resolves it as one.\n *\n * A mailbox the dictionary publishes no account object for leaves `targetAcct`\n * as `missing value` rather than failing, provided the profile gave a folder id\n * to reach it by. `needsAccountObject` is for composing mail, which can't work\n * without the object: the account a message is sent from can only be set from\n * it, and a message composed without it goes out from the default mailbox.\n */\nexport function accountLookupSnippet(acct: MacAccount, needsAccountObject = false): string {\n const probe = ` set targetAcct to missing value\n try\n repeat with a in (exchange accounts & imap accounts & pop accounts)\n if (email address of a as string) is ${asString(acct.emailAccount)} then\n set targetAcct to a\n exit repeat\n end if\n end repeat\n end try`;\n if (acct.folderIds && !needsAccountObject) return probe;\n const complaint = acct.folderIds\n ? `${failureTag('INVALID_REQUEST')}Outlook publishes no account object for '${acct.emailAccount}', `\n + 'so the account a message is sent from cannot be set. Its folders can be read; mail cannot be composed from it.'\n : `${failureTag('ACCOUNT_NOT_FOUND')}Account '${acct.emailAccount}' not found in Outlook.`;\n return `${probe}\n if targetAcct is missing value then error ${asString(complaint)}`;\n}\n\n/**\n * Bind `variable` to one of the account's well-known folders: through the\n * account object when the probe found one, else by the folder's own id. `mail\n * folder id N` resolves against the application rather than an account, and N is\n * the id Outlook's profile database records for that folder.\n */\nexport function rootFolderSnippet(acct: MacAccount, term: string, variable: string): string {\n const viaAccount = ` set ${variable} to ${term} of targetAcct`;\n const id = acct.folderIds?.[term];\n if (id !== undefined) {\n return ` if targetAcct is missing value then\n set ${variable} to mail folder id ${id}\n else\n${viaAccount}\n end if`;\n }\n // The profile listed the mailbox but not this root, so the probe is the only\n // way in — and it may have come up empty. Name the folder out of reach\n // rather than let `<term> of missing value` say it.\n if (acct.folderIds) {\n const complaint = `${failureTag('NOT_FOUND', 'folder')}Account '${acct.emailAccount}' has no ${term} folder AppleScript can reach.`;\n return ` if targetAcct is missing value then error ${asString(complaint)}\n${viaAccount}`;\n }\n return viaAccount;\n}\n\n/**\n * Outlook for Mac's dictionary term for each well-known root, by olDefaultFolders\n * id. Verified against the running app: the terms are NOT what the Windows names\n * suggest — `sent mail` and `junk email` don't compile, `sent items` and `junk\n * mail` do — and one bad term fails the whole script at compile time.\n */\nexport const MAC_ROOT_TERMS: Readonly<Record<number, string>> = {\n [FolderId.Inbox]: 'inbox',\n [FolderId.SentMail]: 'sent items',\n [FolderId.Drafts]: 'drafts',\n [FolderId.DeletedItems]: 'deleted items',\n [FolderId.Outbox]: 'outbox',\n [FolderId.Junk]: 'junk mail',\n};\n\n/** The date property a root's items carry. */\nexport function dateProperty(rootId: number): string {\n return isOutgoingRoot(rootId) ? 'time sent' : 'time received';\n}\n\n/**\n * The Windows folder-path shape (`\\\\mailbox\\Inbox\\Invoices`), assembled in\n * TypeScript: escaping backslashes through a template literal and then an\n * AppleScript literal is unreadable, and the parts are known here anyway.\n */\nexport function macFolderPath(emailAccount: string, rootLabel: string, segments: readonly string[]): string {\n return ['\\\\\\\\' + emailAccount, rootLabel, ...segments].join('\\\\');\n}\n\n/**\n * Walk a folder's `container` chain to the top as the list `chainNames`,\n * nearest first. Requires `theFolder`, and leaves `cur` on the folder the walk\n * stopped at: the account's nameless root folder, when it got that far.\n *\n * The chain's last element is the folder directly under the account, and an\n * account can't hold two top-level folders of one name, so comparing that name\n * is exact.\n */\nexport function folderChainSnippet(indent: string): string {\n return `${indent}set chainNames to {}\n${indent}set cur to theFolder\n${indent}set guard to 0\n${indent}repeat while cur is not missing value and guard < 25\n${indent} -- The account's root folder has a name of missing value, and coercing\n${indent} -- that yields the STRING \"missing value\" — so test before coercing.\n${indent} set rawName to missing value\n${indent} try\n${indent} set rawName to name of cur\n${indent} end try\n${indent} if rawName is missing value then exit repeat\n${indent} set nm to rawName as string\n${indent} if nm is \"\" then exit repeat\n${indent} set end of chainNames to nm\n${indent} set nextUp to missing value\n${indent} try\n${indent} set nextUp to container of cur\n${indent} end try\n${indent} set cur to nextUp\n${indent} set guard to guard + 1\n${indent}end repeat`;\n}\n\n/** The dictionary term for a root, or NOT_IMPLEMENTED naming what was asked. */\nfunction rootTerm(ref: MailFolderRef, folderLabel = ''): string {\n const term = MAC_ROOT_TERMS[ref.rootId];\n if (!term) {\n const asked = folderLabel ? ` (asked for '${folderLabel}')` : '';\n throw new NotImplementedError(\n `Reading the '${ref.rootLabel}' folder${asked}`,\n `macOS, whose readable roots are ${Object.values(MAC_ROOT_TERMS).join(', ')}`,\n );\n }\n return term;\n}\n\n/**\n * Resolve `scopeFolder` from `targetAcct`: a well-known root walked down through\n * any further segments. Also binds `scopeCreated`.\n *\n * Names compare with `is`, which is case-insensitive in AppleScript, matching\n * the Windows walk. A missing segment fails as NOT_FOUND rather than falling\n * back to the root; `createMissing` builds the chain instead.\n */\nexport function mailScopeSnippet(acct: MacAccount, ref: MailFolderRef, folderLabel = '', createMissing = false): string {\n const term = rootTerm(ref, folderLabel);\n const walk = ref.segments.map(segment => {\n const complaint = `${failureTag('NOT_FOUND', 'folder')}Folder '${segment}' not found under '${ref.rootLabel}'.`;\n const create = createMissing\n ? ` set foundFolder to (make new mail folder at scopeFolder with properties {name:${asString(segment)}})\n set scopeCreated to true`\n : ` error ${asString(complaint)}`;\n return `\n set foundFolder to missing value\n repeat with sf in (mail folders of scopeFolder)\n if (name of sf as string) is ${asString(segment)} then\n set foundFolder to sf\n exit repeat\n end if\n end repeat\n if foundFolder is missing value then\n${create}\n end if\n set scopeFolder to foundFolder`;\n }).join('');\n return ` set scopeCreated to false\n${rootFolderSnippet(acct, term, 'scopeFolder')}${walk}`;\n}\n\n/**\n * The message id text for a script, or INVALID_REQUEST.\n *\n * Outlook for Mac ids are small integers; a Windows EntryID is refused up front\n * rather than left to fail as a confusing \"not found\", because a caller that\n * persisted one from a Windows run needs to be told exactly that.\n */\nexport function macMessageId(entryId: string): string {\n const id = String(entryId ?? '').trim();\n if (!/^\\d+$/.test(id)) {\n throw new InvalidRequestError(\n `'${entryId}' is not an Outlook for Mac message id. Mac ids are small integers `\n + '(e.g. \"1263\") listed on this machine; a Windows EntryID cannot be resolved here.',\n );\n }\n return id;\n}\n\n/**\n * Split ids into those a script can look up and those that can only fail. A\n * batch reports an unusable id beside a genuine miss rather than throwing and\n * discarding the ids that would have worked.\n */\nexport function partitionMessageIds(entryIds: readonly string[]): { valid: string[]; invalid: ItemFailure[] } {\n const valid: string[] = [];\n const invalid: ItemFailure[] = [];\n for (const entryId of entryIds) {\n try {\n valid.push(macMessageId(entryId));\n } catch (error) {\n invalid.push({ entryId: String(entryId), subject: '', error: (error as Error).message });\n }\n }\n return { valid, invalid };\n}\n\n/**\n * Bind `theMsg` to a message id, or fail as NOT_FOUND. `message id N` resolves\n * against the application, so the message is found wherever it sits.\n */\nexport function messageLookupSnippet(id: string): string {\n const complaint = `${failureTag('NOT_FOUND', 'email')}No email found for message id '${id}'. It may have been deleted - list the mail again for a current id.`;\n return ` set theMsg to missing value\n try\n set theMsg to message id ${id}\n end try\n if theMsg is missing value then error ${asString(complaint)}`;\n}\n\n/**\n * `theMsg`'s sender as `sndName` / `sndAddr`. `sender` is a record, and\n * `address of sender of m` doesn't coerce, so the record is bound first.\n */\nexport function senderSnippet(indent = ' '): string {\n return `${indent}set sndName to \"\"\n${indent}set sndAddr to \"\"\n${indent}try\n${indent} set snd to sender of theMsg\n${indent} try\n${indent} set sndAddr to (address of snd) as string\n${indent} end try\n${indent} try\n${indent} set sndName to (name of snd) as string\n${indent} end try\n${indent}end try`;\n}\n\n/**\n * Everything a reader wants off one message bound to `theMsg`, emitted together\n * with the row it produces (see `messageDetailFields`) so the read order and the\n * positions the decoders index into cannot drift apart. `bodyText` is emitted\n * LAST when asked for, so a stray separator in an earlier field can't shift it.\n */\nexport function messageDetailSnippet(withBody = false, indent = ' '): string {\n return `${indent}set subj to \"\"\n${indent}try\n${indent} set subj to (subject of theMsg) as string\n${indent}end try\n${senderSnippet(indent)}\n${indent}set recvd to \"\"\n${indent}try\n${indent} set recvd to my isoDate(time received of theMsg)\n${indent}on error\n${indent} try\n${indent} set recvd to my isoDate(time sent of theMsg)\n${indent} end try\n${indent}end try\n${indent}set attNames to {}\n${indent}try\n${indent} set attNames to name of every attachment of theMsg\n${indent}end try` + (withBody ? `\n${indent}set bodyText to \"\"\n${indent}try\n${indent} set bodyText to (plain text content of theMsg) as string\n${indent}end try` : '');\n}\n\n/** The fields `messageDetailSnippet` fills, in row order — see `MessageDetail`. */\nexport function messageDetailFields(withBody = false): string[] {\n const fields = ['(id of theMsg as string)', 'subj', 'sndName', 'sndAddr', 'recvd', 'my sanitizePositionalList(attNames)'];\n if (withBody) fields.push('bodyText');\n return fields;\n}\n\n/** Where each of those fields lands in the row. */\nexport const MessageDetail = {\n id: 0,\n subject: 1,\n senderName: 2,\n senderEmail: 3,\n receivedTime: 4,\n attachmentNames: 5,\n body: 6,\n} as const;\n\n/**\n * `theMsg`'s first recipient as `sndName` / `sndAddr` — what outgoing mail\n * reports in place of a sender.\n */\nexport function firstRecipientSnippet(indent = ' '): string {\n return `${indent}set sndName to \"\"\n${indent}set sndAddr to \"\"\n${indent}try\n${indent} set rcps to to recipients of theMsg\n${indent} if (count of rcps) > 0 then\n${indent} set ea to email address of (item 1 of rcps)\n${indent} try\n${indent} set sndAddr to (address of ea) as string\n${indent} end try\n${indent} try\n${indent} set sndName to (name of ea) as string\n${indent} end try\n${indent} end if\n${indent}end try`;\n}\n\n/** Every address `theMsg` was sent to — To, CC and BCC — as the list `addrList`. */\nexport function allRecipientsSnippet(indent = ' '): string {\n return `${indent}set addrList to {}\n${indent}try\n${indent} repeat with r in (every recipient of theMsg)\n${indent} try\n${indent} -- The record is bound before its field is read: a nested\n${indent} -- 'address of (email address of r)' doesn't coerce, and fails\n${indent} -- inside the try as an empty list rather than an error.\n${indent} set ea to email address of r\n${indent} set oneAddr to (address of ea) as string\n${indent} if oneAddr is not \"\" then set end of addrList to oneAddr\n${indent} end try\n${indent} end repeat\n${indent}end try`;\n}\n\n/**\n * Recursive, case-insensitive, depth-capped folder search from `startFolder` —\n * the macOS counterpart of the Windows Find-FolderByName.\n */\nexport const FIND_FOLDER_HANDLER = `\non findFolderByName(startFolder, wantedName, depth)\n tell application \"Microsoft Outlook\"\n set subs to mail folders of startFolder\n end tell\n repeat with f in subs\n if (name of f as string) is wantedName then return f\n end repeat\n if depth is less than or equal to 1 then return missing value\n repeat with f in subs\n set hit to my findFolderByName(f, wantedName, depth - 1)\n if hit is not missing value then return hit\n end repeat\n return missing value\nend findFolderByName\n`;\n\n/**\n * Enumerate the accounts, probing each class separately: legacy Outlook errors\n * on `every account`, and New Outlook returns empty lists for the classes.\n */\nexport const LIST_ACCOUNTS_SNIPPET = `\nset acctList to {}\ntell application \"Microsoft Outlook\"\n repeat with acctClass in {\"exchange\", \"imap\", \"pop\"}\n try\n if (acctClass as string) is \"exchange\" then\n set accts to exchange accounts\n else if (acctClass as string) is \"imap\" then\n set accts to imap accounts\n else\n set accts to pop accounts\n end if\n repeat with a in accts\n try\n set end of acctList to (email address of a as string)\n end try\n end repeat\n end try\n end repeat\nend tell\n`;\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { ReplyRequest, SendRequest } from '../backend';
2
- import type { ReplyEmailResult } from '../types';
3
- /** Send, display or file a new email. */
4
- export declare function sendOutlookEmail(request: SendRequest): Promise<void>;
2
+ import type { ReplyEmailResult, SendEmailResult } from '../types';
3
+ /** Send, display or file a new email; a filed draft reports its id. */
4
+ export declare function sendOutlookEmail(request: SendRequest): Promise<SendEmailResult>;
5
5
  /**
6
6
  * Reply to an email with the composed HTML above the quoted original.
7
7
  *
package/dist/mac/send.js CHANGED
@@ -7,25 +7,55 @@ const run_1 = require("./run");
7
7
  const scripts_1 = require("./scripts");
8
8
  const args_1 = require("../shared/args");
9
9
  /**
10
- * What to do with a composed message.
10
+ * What to do with a composed message. A filed draft leaves its id in
11
+ * `filedDraftId` ("" for the other dispositions, or when it can't be told).
11
12
  *
12
13
  * A new outgoing message lands in Temporary Items, not Drafts — `open` only
13
14
  * shows it — so a windowless draft is moved into Drafts explicitly. `move`
14
15
  * reports success even where it silently does nothing, so the folder is
15
- * confirmed to have grown rather than report a draft that doesn't exist.
16
+ * confirmed to hold a message it didn't before rather than report a draft that
17
+ * doesn't exist.
18
+ *
19
+ * Moving rewrites a message's id, so the draft's id is the one Drafts gained.
20
+ * Another draft can land in the same moment — an autosave of a compose window,
21
+ * a sync — and then the subject tells them apart; a tie is left unnamed rather
22
+ * than guessed, since a caller that keeps the id may later send by it.
16
23
  */
17
24
  function dispose(variable, disposition) {
18
25
  switch (disposition) {
19
26
  case 'send':
20
- return { prelude: '', action: ` send ${variable}` };
27
+ return { prelude: ' set filedDraftId to ""', action: ` send ${variable}` };
21
28
  case 'display':
22
- return { prelude: '', action: ` open ${variable}` };
29
+ return { prelude: ' set filedDraftId to ""', action: ` open ${variable}` };
23
30
  case 'save':
24
31
  return {
25
- prelude: ` set draftsFolder to drafts of targetAcct
26
- set draftsBefore to count of messages of draftsFolder`,
27
- action: ` move ${variable} to draftsFolder
28
- if (count of messages of draftsFolder) is not greater than draftsBefore then error "Outlook did not file the draft in the Drafts folder."`,
32
+ prelude: ` set filedDraftId to ""
33
+ set draftsFolder to drafts of targetAcct
34
+ set draftIdsBefore to id of every message of draftsFolder`,
35
+ action: ` set filedSubject to ""
36
+ try
37
+ set filedSubject to (subject of ${variable}) as string
38
+ end try
39
+ move ${variable} to draftsFolder
40
+ -- Bound before the loop, so it walks a local list rather than asking
41
+ -- Outlook for each item in turn.
42
+ set draftIdsAfter to id of every message of draftsFolder
43
+ set newIds to {}
44
+ repeat with anId in draftIdsAfter
45
+ if draftIdsBefore does not contain (contents of anId) then set end of newIds to (contents of anId)
46
+ end repeat
47
+ if (count of newIds) is 0 then error "Outlook did not file the draft in the Drafts folder."
48
+ if (count of newIds) is 1 then
49
+ set filedDraftId to (item 1 of newIds) as string
50
+ else
51
+ set sameSubject to {}
52
+ repeat with anId in newIds
53
+ try
54
+ if ((subject of message id (contents of anId)) as string) is filedSubject then set end of sameSubject to (contents of anId)
55
+ end try
56
+ end repeat
57
+ if (count of sameSubject) is 1 then set filedDraftId to (item 1 of sameSubject) as string
58
+ end if`,
29
59
  };
30
60
  }
31
61
  }
@@ -39,7 +69,7 @@ function recipientLines(kind, entries) {
39
69
  return ` make new ${kind} recipient at newMsg with properties {email address:${emailAddress}}`;
40
70
  });
41
71
  }
42
- /** Send, display or file a new email. */
72
+ /** Send, display or file a new email; a filed draft reports its id. */
43
73
  async function sendOutlookEmail(request) {
44
74
  const acct = await (0, scripts_1.resolveMacAccount)(request.account);
45
75
  const { prelude, action } = dispose('newMsg', request.disposition);
@@ -49,7 +79,7 @@ async function sendOutlookEmail(request) {
49
79
  ...recipientLines('bcc', request.bcc),
50
80
  ...request.attachments.map(file => ` make new attachment at newMsg with properties {file:POSIX file ${(0, run_1.asString)(file)}}`),
51
81
  ];
52
- await (0, run_1.runOsaScript)(`
82
+ const raw = await (0, run_1.runOsaScript)(`
53
83
  tell application "Microsoft Outlook"
54
84
  ${(0, scripts_1.accountLookupSnippet)(acct, true)}
55
85
  ${prelude}
@@ -57,7 +87,9 @@ ${prelude}
57
87
  ${lines.join('\n')}
58
88
  set account of newMsg to targetAcct
59
89
  ${action}
90
+ return filedDraftId
60
91
  end tell`, 'standard');
92
+ return { draftEntryId: raw.trim() };
61
93
  }
62
94
  /**
63
95
  * Reply to an email with the composed HTML above the quoted original.
@@ -105,9 +137,9 @@ ${prelude}
105
137
  set subj to (subject of theReply) as string
106
138
  end try
107
139
  ${action}
108
- return my sanitize(toLine) & (character id 31) & my sanitize(subj) & (character id 31) & my sanitize(repliedTo)
140
+ return ${(0, run_1.asRow)(['toLine', 'subj', 'repliedTo'])}
109
141
  end tell`, 'standard');
110
- const parts = (0, run_1.splitFields)(raw);
142
+ const parts = (0, run_1.summaryFields)(raw);
111
143
  return { to: (0, run_1.field)(parts, 0), subject: (0, run_1.field)(parts, 1), repliedToSender: (0, run_1.field)(parts, 2) };
112
144
  }
113
145
  //# sourceMappingURL=send.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/mac/send.ts"],"names":[],"mappings":"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","sourcesContent":["// Composing outgoing mail on macOS: a new email, and a reply to an existing one.\nimport { asString, field, runOsaScript, splitFields } from './run';\nimport { accountLookupSnippet, macMessageId, messageLookupSnippet, resolveMacAccount } from './scripts';\nimport { parseRecipient } from '../shared/args';\nimport type { Disposition, ReplyRequest, SendRequest } from '../backend';\nimport type { ReplyEmailResult } from '../types';\n\n/**\n * What to do with a composed message.\n *\n * A new outgoing message lands in Temporary Items, not Drafts — `open` only\n * shows it — so a windowless draft is moved into Drafts explicitly. `move`\n * reports success even where it silently does nothing, so the folder is\n * confirmed to have grown rather than report a draft that doesn't exist.\n */\nfunction dispose(variable: string, disposition: Disposition): { prelude: string; action: string } {\n switch (disposition) {\n case 'send':\n return {prelude: '', action: ` send ${variable}`};\n case 'display':\n return {prelude: '', action: ` open ${variable}`};\n case 'save':\n return {\n prelude: ` set draftsFolder to drafts of targetAcct\n set draftsBefore to count of messages of draftsFolder`,\n action: ` move ${variable} to draftsFolder\n if (count of messages of draftsFolder) is not greater than draftsBefore then error \"Outlook did not file the draft in the Drafts folder.\"`,\n };\n }\n}\n\n/** One `make new … recipient` line per entry, keeping a display name when given. */\nfunction recipientLines(kind: 'to' | 'cc' | 'bcc', entries: readonly string[]): string[] {\n return entries.map(entry => {\n const {name, address} = parseRecipient(entry);\n const emailAddress = name\n ? `{name:${asString(name)}, address:${asString(address)}}`\n : `{address:${asString(address)}}`;\n return ` make new ${kind} recipient at newMsg with properties {email address:${emailAddress}}`;\n });\n}\n\n/** Send, display or file a new email. */\nexport async function sendOutlookEmail(request: SendRequest): Promise<void> {\n const acct = await resolveMacAccount(request.account);\n const {prelude, action} = dispose('newMsg', request.disposition);\n const lines = [\n ...recipientLines('to', request.to),\n ...recipientLines('cc', request.cc),\n ...recipientLines('bcc', request.bcc),\n ...request.attachments.map(file => ` make new attachment at newMsg with properties {file:POSIX file ${asString(file)}}`),\n ];\n await runOsaScript(`\ntell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct, true)}\n${prelude}\n set newMsg to make new outgoing message with properties {subject:${asString(request.subject)}, content:${asString(request.htmlBody)}}\n${lines.join('\\n')}\n set account of newMsg to targetAcct\n${action}\nend tell`, 'standard');\n}\n\n/**\n * Reply to an email with the composed HTML above the quoted original.\n *\n * `reply to` builds the same quoted body COM's Reply() returns, so the insertion\n * point is the one thing the platforms must agree on (see WORD_SECTION_ANCHOR).\n * `to` comes back as the reply's resolved addresses: Outlook for Mac exposes\n * recipients as records, and the address is the half worth checking.\n */\nexport async function replyOutlookEmail(request: ReplyRequest): Promise<ReplyEmailResult> {\n const id = macMessageId(request.email.entryId);\n const acct = await resolveMacAccount(request.account);\n const {prelude, action} = dispose('theReply', request.disposition);\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct, true)}\n${messageLookupSnippet(id)}\n set repliedTo to \"\"\n try\n -- Bind the record before reading its field: a nested\n -- 'address of (sender of theMsg)' does not coerce.\n set origSender to sender of theMsg\n set repliedTo to (address of origSender) as string\n end try\n${prelude}\n set theReply to reply to theMsg opening window false ${request.replyAll ? 'reply to all true' : 'without reply to all'}\n -- Neither of these is wrapped in a try. A swallowed account assignment sends\n -- from the default mailbox, and a swallowed insertion files a reply missing\n -- its text — both invisible to the caller if tolerated here.\n set account of theReply to targetAcct\n set content of theReply to my insertAboveQuoted(content of theReply, ${asString(request.html)})\n -- Read the outgoing fields BEFORE acting: sending moves the item, and the\n -- reference can't be read afterwards.\n set toList to {}\n try\n repeat with r in (every to recipient of theReply)\n try\n set ea to email address of r\n set end of toList to (address of ea) as string\n end try\n end repeat\n end try\n set toLine to my joinList(toList, \", \")\n set subj to \"\"\n try\n set subj to (subject of theReply) as string\n end try\n${action}\n return my sanitize(toLine) & (character id 31) & my sanitize(subj) & (character id 31) & my sanitize(repliedTo)\nend tell`, 'standard');\n const parts = splitFields(raw);\n return {to: field(parts, 0), subject: field(parts, 1), repliedToSender: field(parts, 2)};\n}\n"]}
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/mac/send.ts"],"names":[],"mappings":"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","sourcesContent":["// Composing outgoing mail on macOS: a new email, and a reply to an existing one.\nimport { asRow, asString, field, runOsaScript, summaryFields } from './run';\nimport { accountLookupSnippet, macMessageId, messageLookupSnippet, resolveMacAccount } from './scripts';\nimport { parseRecipient } from '../shared/args';\nimport type { Disposition, ReplyRequest, SendRequest } from '../backend';\nimport type { ReplyEmailResult, SendEmailResult } from '../types';\n\n/**\n * What to do with a composed message. A filed draft leaves its id in\n * `filedDraftId` (\"\" for the other dispositions, or when it can't be told).\n *\n * A new outgoing message lands in Temporary Items, not Drafts — `open` only\n * shows it — so a windowless draft is moved into Drafts explicitly. `move`\n * reports success even where it silently does nothing, so the folder is\n * confirmed to hold a message it didn't before rather than report a draft that\n * doesn't exist.\n *\n * Moving rewrites a message's id, so the draft's id is the one Drafts gained.\n * Another draft can land in the same moment — an autosave of a compose window,\n * a sync — and then the subject tells them apart; a tie is left unnamed rather\n * than guessed, since a caller that keeps the id may later send by it.\n */\nfunction dispose(variable: string, disposition: Disposition): { prelude: string; action: string } {\n switch (disposition) {\n case 'send':\n return { prelude: ' set filedDraftId to \"\"', action: ` send ${variable}` };\n case 'display':\n return { prelude: ' set filedDraftId to \"\"', action: ` open ${variable}` };\n case 'save':\n return {\n prelude: ` set filedDraftId to \"\"\n set draftsFolder to drafts of targetAcct\n set draftIdsBefore to id of every message of draftsFolder`,\n action: ` set filedSubject to \"\"\n try\n set filedSubject to (subject of ${variable}) as string\n end try\n move ${variable} to draftsFolder\n -- Bound before the loop, so it walks a local list rather than asking\n -- Outlook for each item in turn.\n set draftIdsAfter to id of every message of draftsFolder\n set newIds to {}\n repeat with anId in draftIdsAfter\n if draftIdsBefore does not contain (contents of anId) then set end of newIds to (contents of anId)\n end repeat\n if (count of newIds) is 0 then error \"Outlook did not file the draft in the Drafts folder.\"\n if (count of newIds) is 1 then\n set filedDraftId to (item 1 of newIds) as string\n else\n set sameSubject to {}\n repeat with anId in newIds\n try\n if ((subject of message id (contents of anId)) as string) is filedSubject then set end of sameSubject to (contents of anId)\n end try\n end repeat\n if (count of sameSubject) is 1 then set filedDraftId to (item 1 of sameSubject) as string\n end if`,\n };\n }\n}\n\n/** One `make new … recipient` line per entry, keeping a display name when given. */\nfunction recipientLines(kind: 'to' | 'cc' | 'bcc', entries: readonly string[]): string[] {\n return entries.map(entry => {\n const { name, address } = parseRecipient(entry);\n const emailAddress = name\n ? `{name:${asString(name)}, address:${asString(address)}}`\n : `{address:${asString(address)}}`;\n return ` make new ${kind} recipient at newMsg with properties {email address:${emailAddress}}`;\n });\n}\n\n/** Send, display or file a new email; a filed draft reports its id. */\nexport async function sendOutlookEmail(request: SendRequest): Promise<SendEmailResult> {\n const acct = await resolveMacAccount(request.account);\n const { prelude, action } = dispose('newMsg', request.disposition);\n const lines = [\n ...recipientLines('to', request.to),\n ...recipientLines('cc', request.cc),\n ...recipientLines('bcc', request.bcc),\n ...request.attachments.map(file => ` make new attachment at newMsg with properties {file:POSIX file ${asString(file)}}`),\n ];\n const raw = await runOsaScript(`\ntell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct, true)}\n${prelude}\n set newMsg to make new outgoing message with properties {subject:${asString(request.subject)}, content:${asString(request.htmlBody)}}\n${lines.join('\\n')}\n set account of newMsg to targetAcct\n${action}\n return filedDraftId\nend tell`, 'standard');\n return { draftEntryId: raw.trim() };\n}\n\n/**\n * Reply to an email with the composed HTML above the quoted original.\n *\n * `reply to` builds the same quoted body COM's Reply() returns, so the insertion\n * point is the one thing the platforms must agree on (see WORD_SECTION_ANCHOR).\n * `to` comes back as the reply's resolved addresses: Outlook for Mac exposes\n * recipients as records, and the address is the half worth checking.\n */\nexport async function replyOutlookEmail(request: ReplyRequest): Promise<ReplyEmailResult> {\n const id = macMessageId(request.email.entryId);\n const acct = await resolveMacAccount(request.account);\n const { prelude, action } = dispose('theReply', request.disposition);\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct, true)}\n${messageLookupSnippet(id)}\n set repliedTo to \"\"\n try\n -- Bind the record before reading its field: a nested\n -- 'address of (sender of theMsg)' does not coerce.\n set origSender to sender of theMsg\n set repliedTo to (address of origSender) as string\n end try\n${prelude}\n set theReply to reply to theMsg opening window false ${request.replyAll ? 'reply to all true' : 'without reply to all'}\n -- Neither of these is wrapped in a try. A swallowed account assignment sends\n -- from the default mailbox, and a swallowed insertion files a reply missing\n -- its text — both invisible to the caller if tolerated here.\n set account of theReply to targetAcct\n set content of theReply to my insertAboveQuoted(content of theReply, ${asString(request.html)})\n -- Read the outgoing fields BEFORE acting: sending moves the item, and the\n -- reference can't be read afterwards.\n set toList to {}\n try\n repeat with r in (every to recipient of theReply)\n try\n set ea to email address of r\n set end of toList to (address of ea) as string\n end try\n end repeat\n end try\n set toLine to my joinList(toList, \", \")\n set subj to \"\"\n try\n set subj to (subject of theReply) as string\n end try\n${action}\n return ${asRow(['toLine', 'subj', 'repliedTo'])}\nend tell`, 'standard');\n const parts = summaryFields(raw);\n return { to: field(parts, 0), subject: field(parts, 1), repliedToSender: field(parts, 2) };\n}\n"]}
@@ -11,21 +11,12 @@ exports.editEmailTemplate = editEmailTemplate;
11
11
  // two platforms.
12
12
  const run_1 = require("./run");
13
13
  const scripts_1 = require("./scripts");
14
+ const templateLimits_1 = require("../shared/templateLimits");
14
15
  const templateBody_1 = require("../templateBody");
15
16
  const errors_1 = require("../errors");
16
- /** How deep under the mailbox root a template folder is searched for. */
17
- const TEMPLATE_SEARCH_DEPTH = 3;
18
- /** Cap on one template body, matching Windows. */
19
- const MAX_TEMPLATE_HTML = 100_000;
20
- /** Plain text standing in for a body that wasn't asked for. */
21
- const PREVIEW_CHARS = 200;
22
- /**
23
- * Embedded (cid:) images live as attachments on the TEMPLATE item, so its HTML
24
- * reused elsewhere shows broken-image placeholders. Template emails are text
25
- * and markup only.
26
- */
17
+ /** A template body without its embedded images — see EMBEDDED_IMAGE_SOURCE. */
27
18
  function stripEmbeddedImages(html) {
28
- return html.replace(/<img[^>]*src="cid:[^"]*"[^>]*>/gi, '');
19
+ return html.replace(new RegExp(templateLimits_1.EMBEDDED_IMAGE_SOURCE, 'gi'), '');
29
20
  }
30
21
  /**
31
22
  * The template emails in a mailbox folder, newest first; a missing folder is
@@ -43,7 +34,7 @@ tell application "Microsoft Outlook"
43
34
  ${(0, scripts_1.accountLookupSnippet)(acct)}
44
35
  ${(0, scripts_1.rootFolderSnippet)(acct, 'root folder', 'rootFolder')}
45
36
  end tell
46
- set theFolder to my findFolderByName(rootFolder, ${(0, run_1.asString)(request.folder)}, ${(0, run_1.asInt)(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)})
47
38
  tell application "Microsoft Outlook"
48
39
  if theFolder is missing value then
49
40
  set names to {}
@@ -62,7 +53,10 @@ tell application "Microsoft Outlook"
62
53
  set modList to {}
63
54
  end try
64
55
  set hasMods to ((count of modList) is (count of idList))
65
- 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)'])}
66
60
  repeat with i from 1 to (count of idList)
67
61
  set modAt to ""
68
62
  if hasMods then set modAt to my isoDate(item i of modList)
@@ -74,7 +68,10 @@ end tell`, 'standard'));
74
68
  if ((0, run_1.field)(header, 0) !== '1') {
75
69
  return { folderFound: false, folderPath: '', templates: [], availableFolders: (0, run_1.splitList)((0, run_1.field)(header, 1)) };
76
70
  }
77
- const folderPath = (0, scripts_1.macFolderPath)(request.account, (0, run_1.field)(header, 1) || request.folder, []);
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, []);
78
75
  const wanted = (request.subject ?? '').toLowerCase();
79
76
  const chosen = records.slice(1)
80
77
  .map(record => {
@@ -113,7 +110,7 @@ end tell`, 'standard');
113
110
  }
114
111
  const templates = chosen.map(item => {
115
112
  const body = bodies.get(item.id) ?? '';
116
- const html = request.includeBody ? stripEmbeddedImages(body).slice(0, MAX_TEMPLATE_HTML) : '';
113
+ const html = request.includeBody ? stripEmbeddedImages(body).slice(0, templateLimits_1.MAX_TEMPLATE_HTML) : '';
117
114
  // With the HTML in hand it is authoritative (markers survive Word
118
115
  // splitting them across tags); without it, the plain text is scanned,
119
116
  // where a marker can't have been split.
@@ -122,7 +119,7 @@ end tell`, 'standard');
122
119
  entryId: item.id,
123
120
  subject: item.subject,
124
121
  htmlBody: html,
125
- bodyPreview: request.includeBody ? '' : body.trim().slice(0, PREVIEW_CHARS),
122
+ bodyPreview: request.includeBody ? '' : body.trim().slice(0, templateLimits_1.TEMPLATE_PREVIEW_CHARS),
126
123
  sections: markers.sections,
127
124
  placeholders: markers.placeholders,
128
125
  lastModified: item.lastModified,
@@ -141,7 +138,7 @@ tell application "Microsoft Outlook"
141
138
  ${(0, scripts_1.accountLookupSnippet)(acct)}
142
139
  ${(0, scripts_1.rootFolderSnippet)(acct, 'root folder', 'rootFolder')}
143
140
  end tell
144
- set theFolder to my findFolderByName(rootFolder, ${(0, run_1.asString)(request.folder)}, ${(0, run_1.asInt)(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)})
145
142
  tell application "Microsoft Outlook"
146
143
  set wasCreated to false
147
144
  if theFolder is missing value then