@dawnlit/outlook-bridge 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +150 -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 +81 -43
  9. package/dist/errors.js +98 -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 +95 -112
  22. package/dist/mac/bounces.js.map +1 -1
  23. package/dist/mac/cleanup.d.ts +11 -25
  24. package/dist/mac/cleanup.js +50 -76
  25. package/dist/mac/cleanup.js.map +1 -1
  26. package/dist/mac/drafts.d.ts +13 -34
  27. package/dist/mac/drafts.js +95 -169
  28. package/dist/mac/drafts.js.map +1 -1
  29. package/dist/mac/folders.d.ts +10 -21
  30. package/dist/mac/folders.js +33 -47
  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 +18 -38
  39. package/dist/mac/read.js +154 -210
  40. package/dist/mac/read.js.map +1 -1
  41. package/dist/mac/run.d.ts +35 -39
  42. package/dist/mac/run.js +97 -102
  43. package/dist/mac/run.js.map +1 -1
  44. package/dist/mac/scripts.d.ts +78 -86
  45. package/dist/mac/scripts.js +125 -115
  46. package/dist/mac/scripts.js.map +1 -1
  47. package/dist/mac/send.d.ts +10 -20
  48. package/dist/mac/send.js +64 -86
  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 +75 -113
  55. package/dist/mac/templates.js.map +1 -1
  56. package/dist/mail.d.ts +77 -45
  57. package/dist/mail.js +166 -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 +28 -16
  66. package/dist/shared/attachmentMatch.js +60 -22
  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 +12 -6
  78. package/dist/shared/json.js +29 -23
  79. package/dist/shared/json.js.map +1 -1
  80. package/dist/shared/locate.d.ts +39 -0
  81. package/dist/shared/locate.js +129 -0
  82. package/dist/shared/locate.js.map +1 -0
  83. package/dist/shared/replyBody.d.ts +42 -32
  84. package/dist/shared/replyBody.js +84 -71
  85. package/dist/shared/replyBody.js.map +1 -1
  86. package/dist/templateBody.d.ts +63 -0
  87. package/dist/{outlookTemplateSections.js → templateBody.js} +72 -72
  88. package/dist/templateBody.js.map +1 -0
  89. package/dist/types.d.ts +360 -167
  90. package/dist/types.js +8 -9
  91. package/dist/types.js.map +1 -1
  92. package/dist/windows/accounts.d.ts +7 -12
  93. package/dist/windows/accounts.js +19 -34
  94. package/dist/windows/accounts.js.map +1 -1
  95. package/dist/windows/attachments.d.ts +9 -24
  96. package/dist/windows/attachments.js +94 -119
  97. package/dist/windows/attachments.js.map +1 -1
  98. package/dist/windows/bounces.d.ts +13 -26
  99. package/dist/windows/bounces.js +168 -200
  100. package/dist/windows/bounces.js.map +1 -1
  101. package/dist/windows/cleanup.d.ts +19 -23
  102. package/dist/windows/cleanup.js +92 -111
  103. package/dist/windows/cleanup.js.map +1 -1
  104. package/dist/windows/drafts.d.ts +16 -35
  105. package/dist/windows/drafts.js +167 -253
  106. package/dist/windows/drafts.js.map +1 -1
  107. package/dist/windows/folders.d.ts +8 -13
  108. package/dist/windows/folders.js +44 -52
  109. package/dist/windows/folders.js.map +1 -1
  110. package/dist/windows/index.d.ts +2 -27
  111. package/dist/windows/index.js +11 -66
  112. package/dist/windows/index.js.map +1 -1
  113. package/dist/windows/read.d.ts +24 -60
  114. package/dist/windows/read.js +261 -306
  115. package/dist/windows/read.js.map +1 -1
  116. package/dist/windows/run.d.ts +39 -39
  117. package/dist/windows/run.js +151 -111
  118. package/dist/windows/run.js.map +1 -1
  119. package/dist/windows/scripts.d.ts +65 -55
  120. package/dist/windows/scripts.js +109 -85
  121. package/dist/windows/scripts.js.map +1 -1
  122. package/dist/windows/send.d.ts +6 -11
  123. package/dist/windows/send.js +68 -88
  124. package/dist/windows/send.js.map +1 -1
  125. package/dist/windows/signatures.d.ts +4 -10
  126. package/dist/windows/signatures.js +30 -37
  127. package/dist/windows/signatures.js.map +1 -1
  128. package/dist/windows/templates.d.ts +12 -18
  129. package/dist/windows/templates.js +122 -149
  130. package/dist/windows/templates.js.map +1 -1
  131. package/package.json +4 -3
  132. package/dist/OutlookService.d.ts +0 -43
  133. package/dist/OutlookService.js +0 -137
  134. package/dist/OutlookService.js.map +0 -1
  135. package/dist/outlookTemplateSections.d.ts +0 -62
  136. package/dist/outlookTemplateSections.js.map +0 -1
  137. package/dist/shared/capabilities.d.ts +0 -3
  138. package/dist/shared/capabilities.js +0 -9
  139. package/dist/shared/capabilities.js.map +0 -1
@@ -1,25 +1,25 @@
1
1
  "use strict";
2
2
  /**
3
- * Section + placeholder composition for the operator's Outlook template emails.
3
+ * Sections and placeholders in HTML email templates.
4
4
  *
5
- * A template email is a normal saved mail the operator edits in Outlook, so the
6
- * greeting, closing and signature are theirs. Where one template serves several
7
- * reply types, the variants live inside it between markers:
5
+ * A template email is an ordinary saved mail its owner edits in Outlook, so the
6
+ * greeting, closing and signature stay theirs. Where one template serves several
7
+ * kinds of reply, the variants live inside it between markers:
8
8
  *
9
9
  * Hello,
10
- * [[QUOTE]] Thank you for your quote... [[/QUOTE]]
11
- * [[NOQUOTE]] Thank you for letting us know... [[/NOQUOTE]]
12
- * [[CLARIFY]] {{QUESTIONS}} [[/CLARIFY]]
13
- * Best regards, <signature>
10
+ * [[ACCEPT]] Thank you — we accept... [[/ACCEPT]]
11
+ * [[DECLINE]] Thank you for letting us know... [[/DECLINE]]
12
+ * [[CLARIFY]] {{QUESTIONS}} [[/CLARIFY]]
13
+ * Best regards, {{SIGNATURE}}
14
14
  *
15
- * Composing keeps the requested section, drops the others, and substitutes any
16
- * {{PLACEHOLDER}} — so the shared wording is written once and the caller only
17
- * names a section instead of carrying (and editing) 40-50k of Word HTML.
15
+ * Composing keeps the requested section, drops the others, and substitutes the
16
+ * `{{PLACEHOLDER}}`s — so shared wording is written once and a caller names a
17
+ * section instead of carrying the whole body.
18
18
  *
19
- * Everything here works on the raw HTML string rather than a parsed tree: Word's
20
- * markup carries conditional comments and namespaced tags that a round-trip
21
- * through a parser would rewrite, and the reply body must stay byte-identical to
22
- * what the operator wrote.
19
+ * Everything works on the raw HTML string rather than a parsed tree: Word's
20
+ * markup carries conditional comments and namespaced tags a parser round-trip
21
+ * would rewrite, and the composed body must stay byte-identical to what was
22
+ * written.
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.findTemplateMarkers = findTemplateMarkers;
@@ -29,37 +29,37 @@ exports.findTokens = findTokens;
29
29
  exports.findUnfilledTokens = findUnfilledTokens;
30
30
  exports.composeTemplateBody = composeTemplateBody;
31
31
  const errors_1 = require("./errors");
32
- /** Marker/placeholder names: what an operator can reasonably type in Outlook. */
32
+ /** Marker and placeholder names: what a person can reasonably type in Outlook. */
33
33
  const NAME = '[A-Za-z0-9_-]{1,40}';
34
34
  /**
35
- * What Word may inject *between two characters of a marker*: tags (it splits runs
36
- * across <span>s freely), comments, non-breaking spaces, and the newlines it wraps
37
- * long lines with — sometimes mid-word. So `[[QUOTE]]` is matched character by
38
- * character with this between, never as a literal string.
35
+ * What Word may inject *between two characters of a marker*: tags (it splits
36
+ * runs across <span>s freely), comments, non-breaking spaces, and the newlines
37
+ * it wraps long lines with — sometimes mid-word. So `[[ACCEPT]]` is matched
38
+ * character by character with this between, never as a literal string.
39
39
  */
40
40
  const GAP = '(?:<!--[\\s\\S]*?-->|<[^>]*>|&nbsp;|&#160;|\\s)*';
41
41
  /** Block elements a marker can sit alone inside; the whole block goes when it does. */
42
42
  const BLOCK_TAGS = ['p', 'div', 'li'];
43
43
  /**
44
- * Block elements that make up one *line* of a template, for a placeholder whose
45
- * empty value takes its whole line out (see `removeTokenLine`). Ordered
46
- * outermost-first: Word puts a <p> inside every table cell, so a token in a
47
- * details table has to drop the <tr> — dropping the <p> would leave the row's
48
- * label cell sitting beside a blank one.
44
+ * Block elements that make up one *line* of a template, for a token whose
45
+ * empty value takes its whole line out (see `removeTokenLine`). Outermost
46
+ * first: Word puts a <p> inside every table cell, so a token in a details table
47
+ * has to drop the <tr> — dropping the <p> would leave the row's label beside a
48
+ * blank cell.
49
49
  *
50
50
  * <div> is deliberately absent: it is rarely a line and often the wrapper around
51
- * the entire body, so a token sitting directly in one would take the whole email
52
- * with it. Falling back to deleting just the token is the safer miss.
51
+ * the entire body, so a token directly inside one would take the whole email
52
+ * with it. Deleting just the token is the safer miss.
53
53
  */
54
54
  const LINE_TAGS = ['tr', 'li', 'p'];
55
55
  function escapeRe(s) {
56
56
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
57
57
  }
58
58
  /**
59
- * Tag-tolerant matcher for one literal marker, e.g. `[[/QUOTE]]` or `{{QUESTIONS}}`.
59
+ * Tag-tolerant matcher for one literal token, e.g. `[[/ACCEPT]]` or `{{QUESTIONS}}`.
60
60
  *
61
- * Memoized: the pattern interleaves `GAP` between every character, so a 15-character
62
- * token compiles to several hundred characters, and the replace loops ask for the
61
+ * Memoized: the pattern interleaves `GAP` between every character, so a short
62
+ * token compiles to hundreds of characters, and the replace loops ask for the
63
63
  * same token repeatedly while walking a template.
64
64
  */
65
65
  const markerRegexCache = new Map();
@@ -71,14 +71,14 @@ function markerRegex(token) {
71
71
  }
72
72
  return regex;
73
73
  }
74
- /** HTML → rough plain text, enough to spot markers and to test a block for emptiness. */
74
+ /** HTML → rough plain text, enough to spot markers and test a block for emptiness. */
75
75
  function stripToText(html) {
76
76
  return html
77
77
  .replace(/<!--[\s\S]*?-->/g, '')
78
78
  .replace(/<[^>]*>/g, '')
79
79
  .replace(/&nbsp;|&#160;/gi, ' ');
80
80
  }
81
- /** Find the markers in a template body (HTML or the plain-text body Outlook derives). */
81
+ /** Find the markers in a template body (HTML, or the plain text Outlook derives from it). */
82
82
  function findTemplateMarkers(body) {
83
83
  const text = stripToText(body || '');
84
84
  const opened = [];
@@ -106,9 +106,9 @@ function findTemplateMarkers(body) {
106
106
  return { sections: opened.filter(n => closed.has(n)), placeholders };
107
107
  }
108
108
  /**
109
- * Every `[[...]]` / `{{...}}` token still in a body, matched pairs or not — the
110
- * last check before a composed reply is handed to Outlook, so a marker the
111
- * operator half-deleted stops the run instead of going out to a carrier.
109
+ * Every `[[...]]` / `{{...}}` token still in a body, paired or not — the last
110
+ * check before a composed body goes out, so a half-deleted marker stops the
111
+ * send instead of reaching a recipient.
112
112
  */
113
113
  function markerTokens(body) {
114
114
  const text = stripToText(body || '');
@@ -125,8 +125,8 @@ function markerTokens(body) {
125
125
  /**
126
126
  * The nearest enclosing block element of [start, end), or null when the marker
127
127
  * isn't inside one. The closing tag is the first of the same name after the
128
- * marker — Word doesn't nest <p>, and where the guess is wrong the emptiness
129
- * test below simply fails and the caller falls back to the marker's own span.
128
+ * marker — Word doesn't nest <p>, and where that guess is wrong the emptiness
129
+ * test simply fails and the caller falls back to the marker's own span.
130
130
  */
131
131
  function enclosingBlock(html, start, end, tags = BLOCK_TAGS) {
132
132
  let best = null;
@@ -155,9 +155,8 @@ function enclosingBlock(html, start, end, tags = BLOCK_TAGS) {
155
155
  };
156
156
  }
157
157
  /**
158
- * What to delete for a marker: the whole paragraph when the marker is alone in it
159
- * (removing only the text would leave a blank line in the reply), otherwise just
160
- * the marker's own characters.
158
+ * What to delete for a marker: its whole paragraph when the marker is alone in
159
+ * it (removing only the text would leave a blank line), else just the marker.
161
160
  */
162
161
  function markerSpan(html, start, end) {
163
162
  const found = enclosingBlock(html, start, end);
@@ -170,13 +169,13 @@ function locate(html, token) {
170
169
  const m = markerRegex(token).exec(html);
171
170
  return m ? { start: m.index, end: m.index + m[0].length } : null;
172
171
  }
173
- // ── Single-token fill ───────────────────────────────────────────────
172
+ // ── Single-token fill ───────────────────────────────────────────────────
174
173
  //
175
- // The section machinery below is for templates that hold several variants of one
176
- // reply. These three are for the simpler case: a template whose wording is fixed
177
- // and whose details — a lane, a cargo weight — are typed somewhere else and dropped
178
- // into `{Token}` placeholders. Same tag-tolerant matching, since the operator
179
- // edits these templates in Outlook too.
174
+ // The section machinery above is for templates holding several variants of
175
+ // one email. These are for the simpler case: fixed wording with details typed
176
+ // elsewhere and dropped into tokens of the caller's own choosing (`{Name}`,
177
+ // `%NAME%`, …). Same tag-tolerant matching, since these templates are edited in
178
+ // Outlook too.
180
179
  /** Replace every occurrence of `token` with `value`, which is never rescanned. */
181
180
  function replaceToken(html, token, value) {
182
181
  let done = '';
@@ -190,9 +189,9 @@ function replaceToken(html, token, value) {
190
189
  }
191
190
  }
192
191
  /**
193
- * Remove `token` along with the line it sits on — the table row or paragraph,
194
- * label included. For an optional detail this is the difference between the email
195
- * not mentioning it at all and it carrying a heading with nothing under it.
192
+ * Remove `token` together with the line it sits on — the table row or
193
+ * paragraph, label included. For an optional detail this is the difference
194
+ * between the email not mentioning it and a heading with nothing under it.
196
195
  * Falls back to deleting just the token when it isn't inside a line element.
197
196
  */
198
197
  function removeTokenLine(html, token) {
@@ -206,13 +205,13 @@ function removeTokenLine(html, token) {
206
205
  }
207
206
  }
208
207
  /**
209
- * The line to delete for a token at `at`: its enclosing row or paragraph, or the
210
- * token's own span when it isn't in one.
208
+ * The line to delete for a token at `at`: its enclosing row or paragraph, or
209
+ * the token's own span when it isn't in one.
211
210
  *
212
211
  * `enclosingBlock` returns the *nearest* opening tag before the token, which is
213
- * only the enclosing one if it hasn't already been closed — a token in a
212
+ * only the enclosing one if it hasn't been closed already — a token in a
214
213
  * paragraph after a table has a `<tr>` before it and a `</tr>` after it without
215
- * ever being inside a row, and taking that span would delete the table.
214
+ * being inside a row, and taking that span would delete the table.
216
215
  */
217
216
  function lineSpan(html, at) {
218
217
  for (const tag of LINE_TAGS) {
@@ -231,9 +230,10 @@ function findTokens(html, tokens) {
231
230
  return tokens.filter(token => markerRegex(token).test(text));
232
231
  }
233
232
  /**
234
- * `{Token}`-shaped text still in a filled-in body: a placeholder misspelled in
235
- * Outlook, or one the app has no field for. Reads the visible text only, so the
236
- * braces in a Word <style> block are not mistaken for placeholders.
233
+ * `{Token}`-shaped text still in a filled-in body: a placeholder misspelled
234
+ * while editing, or one nothing supplied. Reads visible text only, so the
235
+ * braces of a <style> block are not mistaken for placeholders. Catches a
236
+ * leftover `{{TOKEN}}` too, reported by its inner `{TOKEN}`.
237
237
  */
238
238
  function findUnfilledTokens(html) {
239
239
  const text = stripToText((html || '').replace(/<(style|script)\b[^>]*>[\s\S]*?<\/\1>/gi, ''));
@@ -248,10 +248,10 @@ function findUnfilledTokens(html) {
248
248
  return found;
249
249
  }
250
250
  /**
251
- * Resolve a template body down to the one reply that goes out: keep `section`,
252
- * drop the rest, fill the placeholders. Throws rather than guessing — a template
253
- * the operator edited into an unusable state must stop the run, not quietly send
254
- * a reply with `[[QUOTE]]` or an empty question list in it.
251
+ * Reduce a template body to the one email that goes out: keep `section`, drop
252
+ * the rest, fill the placeholders. Throws INVALID_REQUEST rather than guessing —
253
+ * a template edited into an unusable state has to stop the send, not deliver a
254
+ * body with `[[ACCEPT]]` or an unfilled placeholder in it.
255
255
  */
256
256
  function composeTemplateBody(html, options = {}) {
257
257
  const label = options.label ? `Template '${options.label}'` : 'The template';
@@ -260,14 +260,14 @@ function composeTemplateBody(html, options = {}) {
260
260
  if (wanted && !sections.includes(wanted)) {
261
261
  throw new errors_1.InvalidRequestError(sections.length
262
262
  ? `${label} has no [[${wanted}]] section. Sections found: ${sections.join(', ')}.`
263
- : `${label} has no [[${wanted}]]...[[/${wanted}]] markers — it holds a single body, so drop templateSection (or add the markers in Outlook).`);
263
+ : `${label} has no [[${wanted}]]...[[/${wanted}]] markers — it holds a single body, so name no section (or add the markers to the template).`);
264
264
  }
265
265
  if (!wanted && sections.length) {
266
- throw new errors_1.InvalidRequestError(`${label} is split into sections (${sections.join(', ')}) — name one with templateSection, `
267
- + `otherwise the reply would go out with the markers and every variant in it.`);
266
+ throw new errors_1.InvalidRequestError(`${label} is split into sections (${sections.join(', ')}); name the one to send, `
267
+ + 'otherwise the email would go out with every variant and the markers in it.');
268
268
  }
269
- // Deletions are collected first and applied right-to-left, so each span's
270
- // indices still refer to the string it was measured against.
269
+ // Deletions are collected first and applied right to left, so each span's
270
+ // offsets still refer to the string they were measured against.
271
271
  const cuts = [];
272
272
  for (const name of sections) {
273
273
  const open = locate(html, `[[${name}]]`);
@@ -293,21 +293,21 @@ function composeTemplateBody(html, options = {}) {
293
293
  if (!available.includes(key)) {
294
294
  throw new errors_1.InvalidRequestError(`${label} has no {{${key}}} placeholder`
295
295
  + (available.length ? ` (found: ${available.map(p => `{{${p}}}`).join(', ')}).` : '.')
296
- + ` It may have been edited out in Outlook — ask the user rather than guessing where the text goes.`);
296
+ + ' It may have been removed when the template was last edited.');
297
297
  }
298
298
  const m = markerRegex(`{{${key}}}`).exec(out);
299
- // Absent from `out` but present in the template = it lived in a dropped
300
- // section, which is the caller over-supplying, not an error.
299
+ // In the template but gone from `out` means it lived in a dropped
300
+ // section: the caller supplying more than one variant needs, not an error.
301
301
  if (m)
302
302
  out = out.slice(0, m.index) + (value ?? '') + out.slice(m.index + m[0].length);
303
303
  }
304
304
  const leftover = markerTokens(out);
305
305
  if (leftover.length) {
306
306
  const hint = leftover.every(t => t.startsWith('{{'))
307
- ? `Pass a value for each in templatePlaceholders.`
308
- : `Check the template in Outlook — every [[SECTION]] needs its [[/SECTION]].`;
309
- throw new errors_1.InvalidRequestError(`${label} still contains unresolved markers after composing: ${leftover.join(', ')}. ${hint} The reply was not created.`);
307
+ ? 'Supply a value for each placeholder.'
308
+ : 'Check the template — every [[SECTION]] needs its [[/SECTION]].';
309
+ throw new errors_1.InvalidRequestError(`${label} still contains unresolved markers after composing: ${leftover.join(', ')}. ${hint} Nothing was created.`);
310
310
  }
311
311
  return out;
312
312
  }
313
- //# sourceMappingURL=outlookTemplateSections.js.map
313
+ //# sourceMappingURL=templateBody.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templateBody.js","sourceRoot":"","sources":["../src/templateBody.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Sections and placeholders in HTML email templates.\n *\n * A template email is an ordinary saved mail its owner edits in Outlook, so the\n * greeting, closing and signature stay theirs. Where one template serves several\n * kinds of reply, the variants live inside it between markers:\n *\n * Hello,\n * [[ACCEPT]] Thank you — we accept... [[/ACCEPT]]\n * [[DECLINE]] Thank you for letting us know... [[/DECLINE]]\n * [[CLARIFY]] {{QUESTIONS}} [[/CLARIFY]]\n * Best regards, {{SIGNATURE}}\n *\n * Composing keeps the requested section, drops the others, and substitutes the\n * `{{PLACEHOLDER}}`s — so shared wording is written once and a caller names a\n * section instead of carrying the whole body.\n *\n * Everything works on the raw HTML string rather than a parsed tree: Word's\n * markup carries conditional comments and namespaced tags a parser round-trip\n * would rewrite, and the composed body must stay byte-identical to what was\n * written.\n */\n\nimport { InvalidRequestError } from './errors';\n\n/** Marker and placeholder names: what a person can reasonably type in Outlook. */\nconst NAME = '[A-Za-z0-9_-]{1,40}';\n\n/**\n * What Word may inject *between two characters of a marker*: tags (it splits\n * runs across <span>s freely), comments, non-breaking spaces, and the newlines\n * it wraps long lines with — sometimes mid-word. So `[[ACCEPT]]` is matched\n * character by character with this between, never as a literal string.\n */\nconst GAP = '(?:<!--[\\\\s\\\\S]*?-->|<[^>]*>|&nbsp;|&#160;|\\\\s)*';\n\n/** Block elements a marker can sit alone inside; the whole block goes when it does. */\nconst BLOCK_TAGS = ['p', 'div', 'li'];\n\n/**\n * Block elements that make up one *line* of a template, for a token whose\n * empty value takes its whole line out (see `removeTokenLine`). Outermost\n * first: Word puts a <p> inside every table cell, so a token in a details table\n * has to drop the <tr> — dropping the <p> would leave the row's label beside a\n * blank cell.\n *\n * <div> is deliberately absent: it is rarely a line and often the wrapper around\n * the entire body, so a token directly inside one would take the whole email\n * with it. Deleting just the token is the safer miss.\n */\nconst LINE_TAGS = ['tr', 'li', 'p'];\n\nfunction escapeRe(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Tag-tolerant matcher for one literal token, e.g. `[[/ACCEPT]]` or `{{QUESTIONS}}`.\n *\n * Memoized: the pattern interleaves `GAP` between every character, so a short\n * token compiles to hundreds of characters, and the replace loops ask for the\n * same token repeatedly while walking a template.\n */\nconst markerRegexCache = new Map<string, RegExp>();\n\nfunction markerRegex(token: string): RegExp {\n let regex = markerRegexCache.get(token);\n if (!regex) {\n regex = new RegExp(token.split('').map(escapeRe).join(GAP), 'i');\n markerRegexCache.set(token, regex);\n }\n return regex;\n}\n\n/** HTML → rough plain text, enough to spot markers and test a block for emptiness. */\nfunction stripToText(html: string): string {\n return html\n .replace(/<!--[\\s\\S]*?-->/g, '')\n .replace(/<[^>]*>/g, '')\n .replace(/&nbsp;|&#160;/gi, ' ');\n}\n\nexport interface TemplateMarkers {\n /** Section names with both an opening and a closing marker, in document order. */\n sections: string[];\n /** `{{NAME}}` placeholders found anywhere in the template. */\n placeholders: string[];\n}\n\n/** Find the markers in a template body (HTML, or the plain text Outlook derives from it). */\nexport function findTemplateMarkers(body: string): TemplateMarkers {\n const text = stripToText(body || '');\n const opened: string[] = [];\n const closed = new Set<string>();\n const seen = new Set<string>();\n const sectionRe = new RegExp(`\\\\[\\\\[\\\\s*(/?)\\\\s*(${NAME})\\\\s*\\\\]\\\\]`, 'g');\n let m: RegExpExecArray | null;\n while ((m = sectionRe.exec(text)) !== null) {\n const name = m[2].toUpperCase();\n if (m[1]) {\n closed.add(name);\n } else if (!seen.has(name)) {\n seen.add(name);\n opened.push(name);\n }\n }\n const placeholders: string[] = [];\n const placeholderRe = new RegExp(`\\\\{\\\\{\\\\s*(${NAME})\\\\s*\\\\}\\\\}`, 'g');\n while ((m = placeholderRe.exec(text)) !== null) {\n const name = m[1].toUpperCase();\n if (!placeholders.includes(name)) placeholders.push(name);\n }\n return {sections: opened.filter(n => closed.has(n)), placeholders};\n}\n\n/**\n * Every `[[...]]` / `{{...}}` token still in a body, paired or not — the last\n * check before a composed body goes out, so a half-deleted marker stops the\n * send instead of reaching a recipient.\n */\nfunction markerTokens(body: string): string[] {\n const text = stripToText(body || '');\n const re = new RegExp(`\\\\[\\\\[\\\\s*/?\\\\s*${NAME}\\\\s*\\\\]\\\\]|\\\\{\\\\{\\\\s*${NAME}\\\\s*\\\\}\\\\}`, 'g');\n const found: string[] = [];\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n const token = m[0].replace(/\\s+/g, '');\n if (!found.includes(token)) found.push(token);\n }\n return found;\n}\n\ninterface Span {\n start: number;\n end: number;\n}\n\n/**\n * The nearest enclosing block element of [start, end), or null when the marker\n * isn't inside one. The closing tag is the first of the same name after the\n * marker — Word doesn't nest <p>, and where that guess is wrong the emptiness\n * test simply fails and the caller falls back to the marker's own span.\n */\nfunction enclosingBlock(html: string, start: number, end: number, tags: string[] = BLOCK_TAGS): {\n block: Span;\n contentStart: number;\n contentEnd: number;\n} | null {\n let best: { idx: number; contentStart: number; tag: string } | null = null;\n for (const tag of tags) {\n const openRe = new RegExp(`<${tag}(?:\\\\s[^>]*)?>`, 'gi');\n let m: RegExpExecArray | null;\n while ((m = openRe.exec(html)) !== null) {\n if (m.index >= start) break;\n if (!best || m.index > best.idx) {\n best = {idx: m.index, contentStart: m.index + m[0].length, tag};\n }\n }\n }\n if (!best) return null;\n const closeRe = new RegExp(`</${best.tag}\\\\s*>`, 'gi');\n closeRe.lastIndex = end;\n const close = closeRe.exec(html);\n if (!close) return null;\n return {\n block: {start: best.idx, end: close.index + close[0].length},\n contentStart: best.contentStart,\n contentEnd: close.index,\n };\n}\n\n/**\n * What to delete for a marker: its whole paragraph when the marker is alone in\n * it (removing only the text would leave a blank line), else just the marker.\n */\nfunction markerSpan(html: string, start: number, end: number): Span {\n const found = enclosingBlock(html, start, end);\n if (!found || found.contentStart > start || found.contentEnd < end) return {start, end};\n const rest = html.slice(found.contentStart, start) + html.slice(end, found.contentEnd);\n return stripToText(rest).trim() === '' ? found.block : {start, end};\n}\n\nfunction locate(html: string, token: string): Span | null {\n const m = markerRegex(token).exec(html);\n return m ? {start: m.index, end: m.index + m[0].length} : null;\n}\n\n// ── Single-token fill ───────────────────────────────────────────────────\n//\n// The section machinery above is for templates holding several variants of\n// one email. These are for the simpler case: fixed wording with details typed\n// elsewhere and dropped into tokens of the caller's own choosing (`{Name}`,\n// `%NAME%`, …). Same tag-tolerant matching, since these templates are edited in\n// Outlook too.\n\n/** Replace every occurrence of `token` with `value`, which is never rescanned. */\nexport function replaceToken(html: string, token: string, value: string): string {\n let done = '';\n let rest = html;\n for (; ;) {\n const at = locate(rest, token);\n if (!at) return done + rest;\n done += rest.slice(0, at.start) + value;\n rest = rest.slice(at.end);\n }\n}\n\n/**\n * Remove `token` together with the line it sits on — the table row or\n * paragraph, label included. For an optional detail this is the difference\n * between the email not mentioning it and a heading with nothing under it.\n * Falls back to deleting just the token when it isn't inside a line element.\n */\nexport function removeTokenLine(html: string, token: string): string {\n let out = html;\n for (; ;) {\n const at = locate(out, token);\n if (!at) return out;\n const cut = lineSpan(out, at);\n out = out.slice(0, cut.start) + out.slice(cut.end);\n }\n}\n\n/**\n * The line to delete for a token at `at`: its enclosing row or paragraph, or\n * the token's own span when it isn't in one.\n *\n * `enclosingBlock` returns the *nearest* opening tag before the token, which is\n * only the enclosing one if it hasn't been closed already — a token in a\n * paragraph after a table has a `<tr>` before it and a `</tr>` after it without\n * being inside a row, and taking that span would delete the table.\n */\nfunction lineSpan(html: string, at: Span): Span {\n for (const tag of LINE_TAGS) {\n const found = enclosingBlock(html, at.start, at.end, [tag]);\n if (!found) continue;\n const closedBefore = new RegExp(`</${tag}\\\\s*>`, 'i').test(html.slice(found.contentStart, at.start));\n if (!closedBefore) return found.block;\n }\n return at;\n}\n\n/** Which of `tokens` the template actually uses — what the caller has to supply. */\nexport function findTokens(html: string, tokens: readonly string[]): string[] {\n const text = stripToText(html || '');\n return tokens.filter(token => markerRegex(token).test(text));\n}\n\n/**\n * `{Token}`-shaped text still in a filled-in body: a placeholder misspelled\n * while editing, or one nothing supplied. Reads visible text only, so the\n * braces of a <style> block are not mistaken for placeholders. Catches a\n * leftover `{{TOKEN}}` too, reported by its inner `{TOKEN}`.\n */\nexport function findUnfilledTokens(html: string): string[] {\n const text = stripToText((html || '').replace(/<(style|script)\\b[^>]*>[\\s\\S]*?<\\/\\1>/gi, ''));\n const found: string[] = [];\n const re = /\\{\\s*([A-Za-z][A-Za-z0-9 _-]{0,30})\\s*\\}/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n const token = `{${m[1].trim()}}`;\n if (!found.includes(token)) found.push(token);\n }\n return found;\n}\n\nexport interface ComposeOptions {\n /** Section to keep; the other sections are dropped with their markers. */\n section?: string;\n /** `{{NAME}}` → HTML to substitute. Names match case-insensitively. */\n placeholders?: Readonly<Record<string, string>>;\n /** The template's name, for error messages. */\n label?: string;\n}\n\n/**\n * Reduce a template body to the one email that goes out: keep `section`, drop\n * the rest, fill the placeholders. Throws INVALID_REQUEST rather than guessing —\n * a template edited into an unusable state has to stop the send, not deliver a\n * body with `[[ACCEPT]]` or an unfilled placeholder in it.\n */\nexport function composeTemplateBody(html: string, options: ComposeOptions = {}): string {\n const label = options.label ? `Template '${options.label}'` : 'The template';\n const wanted = (options.section || '').trim().toUpperCase();\n const {sections, placeholders: available} = findTemplateMarkers(html);\n\n if (wanted && !sections.includes(wanted)) {\n throw new InvalidRequestError(\n sections.length\n ? `${label} has no [[${wanted}]] section. Sections found: ${sections.join(', ')}.`\n : `${label} has no [[${wanted}]]...[[/${wanted}]] markers — it holds a single body, so name no section (or add the markers to the template).`,\n );\n }\n if (!wanted && sections.length) {\n throw new InvalidRequestError(\n `${label} is split into sections (${sections.join(', ')}); name the one to send, `\n + 'otherwise the email would go out with every variant and the markers in it.',\n );\n }\n\n // Deletions are collected first and applied right to left, so each span's\n // offsets still refer to the string they were measured against.\n const cuts: Span[] = [];\n for (const name of sections) {\n const open = locate(html, `[[${name}]]`);\n const close = locate(html, `[[/${name}]]`);\n if (!open || !close || close.start < open.end) {\n throw new InvalidRequestError(`${label} has a malformed [[${name}]] section — expected [[${name}]] ... [[/${name}]] in that order.`);\n }\n const openSpan = markerSpan(html, open.start, open.end);\n const closeSpan = markerSpan(html, close.start, close.end);\n if (name === wanted) {\n cuts.push(openSpan, closeSpan); // keep the content, shed the markers\n } else {\n cuts.push({start: openSpan.start, end: closeSpan.end});\n }\n }\n let out = html;\n for (const cut of cuts.sort((a, b) => b.start - a.start)) {\n out = out.slice(0, cut.start) + out.slice(cut.end);\n }\n\n for (const [rawKey, value] of Object.entries(options.placeholders || {})) {\n const key = rawKey.trim().toUpperCase();\n if (!available.includes(key)) {\n throw new InvalidRequestError(\n `${label} has no {{${key}}} placeholder`\n + (available.length ? ` (found: ${available.map(p => `{{${p}}}`).join(', ')}).` : '.')\n + ' It may have been removed when the template was last edited.',\n );\n }\n const m = markerRegex(`{{${key}}}`).exec(out);\n // In the template but gone from `out` means it lived in a dropped\n // section: the caller supplying more than one variant needs, not an error.\n if (m) out = out.slice(0, m.index) + (value ?? '') + out.slice(m.index + m[0].length);\n }\n\n const leftover = markerTokens(out);\n if (leftover.length) {\n const hint = leftover.every(t => t.startsWith('{{'))\n ? 'Supply a value for each placeholder.'\n : 'Check the template — every [[SECTION]] needs its [[/SECTION]].';\n throw new InvalidRequestError(\n `${label} still contains unresolved markers after composing: ${leftover.join(', ')}. ${hint} Nothing was created.`,\n );\n }\n return out;\n}\n"]}