@limetech/lime-elements 39.5.0 → 39.5.1

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [39.5.1](https://github.com/Lundalogik/lime-elements/compare/v39.5.0...v39.5.1) (2026-02-26)
2
+
3
+ ### Bug Fixes
4
+
5
+
6
+ * **email-viewer:** render inline cid logos in .eml previews ([29b85d3](https://github.com/Lundalogik/lime-elements/commit/29b85d3759e81ce0c3209fb911031d5a4f82a292))
7
+
1
8
  ## [39.5.0](https://github.com/Lundalogik/lime-elements/compare/v39.4.1...v39.5.0) (2026-02-25)
2
9
 
3
10
  ### Features
@@ -4878,11 +4878,13 @@ function extractAttachments(email) {
4878
4878
  const cidUrlById = new Map();
4879
4879
  for (const attachment of email.attachments || []) {
4880
4880
  const contentId = normalizeContentId(attachment.contentId);
4881
- const isInline = attachment.related || attachment.disposition === 'inline';
4881
+ const hasContentId = Boolean(contentId);
4882
+ const isInline = (hasContentId && attachment.disposition !== 'attachment') ||
4883
+ attachment.related ||
4884
+ attachment.disposition === 'inline';
4885
+ const contentBytes = getAttachmentBytes(attachment.content);
4882
4886
  if (!isInline) {
4883
- const size = attachment.content instanceof ArrayBuffer
4884
- ? attachment.content.byteLength
4885
- : undefined;
4887
+ const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
4886
4888
  attachments.push({
4887
4889
  filename: attachment.filename || undefined,
4888
4890
  mimeType: attachment.mimeType || undefined,
@@ -4890,11 +4892,9 @@ function extractAttachments(email) {
4890
4892
  });
4891
4893
  continue;
4892
4894
  }
4893
- const hasArrayBufferContent = attachment.content instanceof ArrayBuffer;
4894
- if (!contentId || !hasArrayBufferContent) {
4895
- const size = hasArrayBufferContent
4896
- ? attachment.content.byteLength
4897
- : undefined;
4895
+ const hasBinaryContent = Boolean(contentBytes);
4896
+ if (!contentId || !hasBinaryContent) {
4897
+ const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
4898
4898
  attachments.push({
4899
4899
  filename: attachment.filename || undefined,
4900
4900
  mimeType: attachment.mimeType || 'application/octet-stream',
@@ -4902,8 +4902,8 @@ function extractAttachments(email) {
4902
4902
  });
4903
4903
  continue;
4904
4904
  }
4905
- const mimeType = attachment.mimeType || 'application/octet-stream';
4906
- const base64 = arrayBufferToBase64(attachment.content);
4905
+ const mimeType = resolveDataUrlMimeType(attachment.mimeType, contentBytes, attachment.filename);
4906
+ const base64 = byteArrayToBase64(contentBytes);
4907
4907
  const dataUrl = `data:${mimeType};base64,${base64}`;
4908
4908
  cidUrlById.set(contentId, dataUrl);
4909
4909
  }
@@ -4930,6 +4930,9 @@ function normalizeContentId(contentId) {
4930
4930
  return '';
4931
4931
  }
4932
4932
  let normalized = contentId.trim();
4933
+ if (normalized.toLowerCase().startsWith('cid:')) {
4934
+ normalized = normalized.slice(4).trim();
4935
+ }
4933
4936
  if (normalized.startsWith('<')) {
4934
4937
  normalized = normalized.slice(1);
4935
4938
  }
@@ -4943,7 +4946,7 @@ function replaceCidReferences(html, cidUrlById) {
4943
4946
  return html;
4944
4947
  }
4945
4948
  return html.replaceAll(/(src\s*=\s*["']?)cid:([^"'\s>]+)(["']?)/gi, (match, prefix, cid, suffix) => {
4946
- const normalized = normalizeContentId(cid);
4949
+ const normalized = normalizeContentId(decodeCidReference(cid));
4947
4950
  const replacement = cidUrlById.get(normalized);
4948
4951
  if (!replacement) {
4949
4952
  return match;
@@ -4951,8 +4954,24 @@ function replaceCidReferences(html, cidUrlById) {
4951
4954
  return `${prefix}${replacement}${suffix}`;
4952
4955
  });
4953
4956
  }
4954
- function arrayBufferToBase64(buffer) {
4955
- const bytes = new Uint8Array(buffer);
4957
+ function decodeCidReference(cid) {
4958
+ try {
4959
+ return decodeURIComponent(cid);
4960
+ }
4961
+ catch (_a) {
4962
+ return cid;
4963
+ }
4964
+ }
4965
+ function getAttachmentBytes(content) {
4966
+ if (content instanceof ArrayBuffer) {
4967
+ return new Uint8Array(content);
4968
+ }
4969
+ if (ArrayBuffer.isView(content)) {
4970
+ return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
4971
+ }
4972
+ return undefined;
4973
+ }
4974
+ function byteArrayToBase64(bytes) {
4956
4975
  if (typeof btoa === 'function') {
4957
4976
  let binary = '';
4958
4977
  for (const byte of bytes) {
@@ -4963,6 +4982,113 @@ function arrayBufferToBase64(buffer) {
4963
4982
  // Jest/Node fallback
4964
4983
  return globalThis.Buffer.from(bytes).toString('base64');
4965
4984
  }
4985
+ function resolveDataUrlMimeType(mimeType, bytes, filename) {
4986
+ const normalizedMimeType = typeof mimeType === 'string' ? mimeType.trim().toLowerCase() : '';
4987
+ const detectedFromBytes = detectImageMimeTypeFromBytes(bytes);
4988
+ if (normalizedMimeType.startsWith('image/')) {
4989
+ if (isTrustedDeclaredImageMimeType(normalizedMimeType, detectedFromBytes)) {
4990
+ return normalizedMimeType;
4991
+ }
4992
+ if (detectedFromBytes) {
4993
+ return detectedFromBytes;
4994
+ }
4995
+ }
4996
+ if (detectedFromBytes) {
4997
+ return detectedFromBytes;
4998
+ }
4999
+ const detectedFromFilename = detectImageMimeTypeFromFilename(filename);
5000
+ if (detectedFromFilename) {
5001
+ return detectedFromFilename;
5002
+ }
5003
+ return normalizedMimeType || 'application/octet-stream';
5004
+ }
5005
+ function isTrustedDeclaredImageMimeType(declaredMimeType, detectedMimeType) {
5006
+ if (!detectedMimeType) {
5007
+ return true;
5008
+ }
5009
+ if (declaredMimeType === detectedMimeType) {
5010
+ return true;
5011
+ }
5012
+ if (declaredMimeType === 'image/jpg' && detectedMimeType === 'image/jpeg') {
5013
+ return true;
5014
+ }
5015
+ return (declaredMimeType === 'image/vnd.microsoft.icon' &&
5016
+ detectedMimeType === 'image/x-icon');
5017
+ }
5018
+ function detectImageMimeTypeFromBytes(bytes) {
5019
+ var _a, _b, _c, _d, _e;
5020
+ return ((_e = (_d = (_c = (_b = (_a = detectPngMimeType(bytes)) !== null && _a !== void 0 ? _a : detectJpegMimeType(bytes)) !== null && _b !== void 0 ? _b : detectGifMimeType(bytes)) !== null && _c !== void 0 ? _c : detectWebpMimeType(bytes)) !== null && _d !== void 0 ? _d : detectIconMimeType(bytes)) !== null && _e !== void 0 ? _e : detectSvgMimeType(bytes));
5021
+ }
5022
+ function detectPngMimeType(bytes) {
5023
+ if (startsWithBytes(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
5024
+ return 'image/png';
5025
+ }
5026
+ }
5027
+ function detectJpegMimeType(bytes) {
5028
+ if (startsWithBytes(bytes, [0xff, 0xd8, 0xff])) {
5029
+ return 'image/jpeg';
5030
+ }
5031
+ }
5032
+ function detectGifMimeType(bytes) {
5033
+ const firstSix = bytesToAscii(bytes, 0, 6);
5034
+ if (firstSix === 'GIF87a' || firstSix === 'GIF89a') {
5035
+ return 'image/gif';
5036
+ }
5037
+ }
5038
+ function detectWebpMimeType(bytes) {
5039
+ const riff = bytesToAscii(bytes, 0, 4);
5040
+ const webp = bytesToAscii(bytes, 8, 12);
5041
+ if (riff === 'RIFF' && webp === 'WEBP') {
5042
+ return 'image/webp';
5043
+ }
5044
+ }
5045
+ function detectIconMimeType(bytes) {
5046
+ if (startsWithBytes(bytes, [0x00, 0x00, 0x01, 0x00])) {
5047
+ return 'image/x-icon';
5048
+ }
5049
+ }
5050
+ function detectSvgMimeType(bytes) {
5051
+ if (bytes.length < 5) {
5052
+ return;
5053
+ }
5054
+ const utf8Prefix = new TextDecoder('utf8', { fatal: false }).decode(bytes.slice(0, Math.min(bytes.length, 256)));
5055
+ const normalizedPrefix = utf8Prefix.trimStart().toLowerCase();
5056
+ if (normalizedPrefix.startsWith('<svg') ||
5057
+ (normalizedPrefix.startsWith('<?xml') &&
5058
+ normalizedPrefix.includes('<svg'))) {
5059
+ return 'image/svg+xml';
5060
+ }
5061
+ }
5062
+ function startsWithBytes(bytes, prefix) {
5063
+ if (bytes.length < prefix.length) {
5064
+ return false;
5065
+ }
5066
+ return prefix.every((value, index) => bytes[index] === value);
5067
+ }
5068
+ function bytesToAscii(bytes, start, endExclusive) {
5069
+ if (bytes.length < endExclusive) {
5070
+ return '';
5071
+ }
5072
+ return String.fromCodePoint(...bytes.slice(start, endExclusive));
5073
+ }
5074
+ function detectImageMimeTypeFromFilename(filename) {
5075
+ const normalized = filename === null || filename === void 0 ? void 0 : filename.trim().toLowerCase();
5076
+ if (!normalized || !normalized.includes('.')) {
5077
+ return;
5078
+ }
5079
+ const extension = normalized.slice(normalized.lastIndexOf('.') + 1);
5080
+ const mimeTypes = {
5081
+ png: 'image/png',
5082
+ jpg: 'image/jpeg',
5083
+ jpeg: 'image/jpeg',
5084
+ gif: 'image/gif',
5085
+ webp: 'image/webp',
5086
+ svg: 'image/svg+xml',
5087
+ ico: 'image/x-icon',
5088
+ bmp: 'image/bmp',
5089
+ };
5090
+ return mimeTypes[extension];
5091
+ }
4966
5092
  function isNonEmptyString(value) {
4967
5093
  return typeof value === 'string' && value.length > 0;
4968
5094
  }
@@ -66,11 +66,13 @@ function extractAttachments(email) {
66
66
  const cidUrlById = new Map();
67
67
  for (const attachment of email.attachments || []) {
68
68
  const contentId = normalizeContentId(attachment.contentId);
69
- const isInline = attachment.related || attachment.disposition === 'inline';
69
+ const hasContentId = Boolean(contentId);
70
+ const isInline = (hasContentId && attachment.disposition !== 'attachment') ||
71
+ attachment.related ||
72
+ attachment.disposition === 'inline';
73
+ const contentBytes = getAttachmentBytes(attachment.content);
70
74
  if (!isInline) {
71
- const size = attachment.content instanceof ArrayBuffer
72
- ? attachment.content.byteLength
73
- : undefined;
75
+ const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
74
76
  attachments.push({
75
77
  filename: attachment.filename || undefined,
76
78
  mimeType: attachment.mimeType || undefined,
@@ -78,11 +80,9 @@ function extractAttachments(email) {
78
80
  });
79
81
  continue;
80
82
  }
81
- const hasArrayBufferContent = attachment.content instanceof ArrayBuffer;
82
- if (!contentId || !hasArrayBufferContent) {
83
- const size = hasArrayBufferContent
84
- ? attachment.content.byteLength
85
- : undefined;
83
+ const hasBinaryContent = Boolean(contentBytes);
84
+ if (!contentId || !hasBinaryContent) {
85
+ const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
86
86
  attachments.push({
87
87
  filename: attachment.filename || undefined,
88
88
  mimeType: attachment.mimeType || 'application/octet-stream',
@@ -90,8 +90,8 @@ function extractAttachments(email) {
90
90
  });
91
91
  continue;
92
92
  }
93
- const mimeType = attachment.mimeType || 'application/octet-stream';
94
- const base64 = arrayBufferToBase64(attachment.content);
93
+ const mimeType = resolveDataUrlMimeType(attachment.mimeType, contentBytes, attachment.filename);
94
+ const base64 = byteArrayToBase64(contentBytes);
95
95
  const dataUrl = `data:${mimeType};base64,${base64}`;
96
96
  cidUrlById.set(contentId, dataUrl);
97
97
  }
@@ -118,6 +118,9 @@ function normalizeContentId(contentId) {
118
118
  return '';
119
119
  }
120
120
  let normalized = contentId.trim();
121
+ if (normalized.toLowerCase().startsWith('cid:')) {
122
+ normalized = normalized.slice(4).trim();
123
+ }
121
124
  if (normalized.startsWith('<')) {
122
125
  normalized = normalized.slice(1);
123
126
  }
@@ -131,7 +134,7 @@ function replaceCidReferences(html, cidUrlById) {
131
134
  return html;
132
135
  }
133
136
  return html.replaceAll(/(src\s*=\s*["']?)cid:([^"'\s>]+)(["']?)/gi, (match, prefix, cid, suffix) => {
134
- const normalized = normalizeContentId(cid);
137
+ const normalized = normalizeContentId(decodeCidReference(cid));
135
138
  const replacement = cidUrlById.get(normalized);
136
139
  if (!replacement) {
137
140
  return match;
@@ -139,8 +142,24 @@ function replaceCidReferences(html, cidUrlById) {
139
142
  return `${prefix}${replacement}${suffix}`;
140
143
  });
141
144
  }
142
- function arrayBufferToBase64(buffer) {
143
- const bytes = new Uint8Array(buffer);
145
+ function decodeCidReference(cid) {
146
+ try {
147
+ return decodeURIComponent(cid);
148
+ }
149
+ catch (_a) {
150
+ return cid;
151
+ }
152
+ }
153
+ function getAttachmentBytes(content) {
154
+ if (content instanceof ArrayBuffer) {
155
+ return new Uint8Array(content);
156
+ }
157
+ if (ArrayBuffer.isView(content)) {
158
+ return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
159
+ }
160
+ return undefined;
161
+ }
162
+ function byteArrayToBase64(bytes) {
144
163
  if (typeof btoa === 'function') {
145
164
  let binary = '';
146
165
  for (const byte of bytes) {
@@ -151,6 +170,113 @@ function arrayBufferToBase64(buffer) {
151
170
  // Jest/Node fallback
152
171
  return globalThis.Buffer.from(bytes).toString('base64');
153
172
  }
173
+ function resolveDataUrlMimeType(mimeType, bytes, filename) {
174
+ const normalizedMimeType = typeof mimeType === 'string' ? mimeType.trim().toLowerCase() : '';
175
+ const detectedFromBytes = detectImageMimeTypeFromBytes(bytes);
176
+ if (normalizedMimeType.startsWith('image/')) {
177
+ if (isTrustedDeclaredImageMimeType(normalizedMimeType, detectedFromBytes)) {
178
+ return normalizedMimeType;
179
+ }
180
+ if (detectedFromBytes) {
181
+ return detectedFromBytes;
182
+ }
183
+ }
184
+ if (detectedFromBytes) {
185
+ return detectedFromBytes;
186
+ }
187
+ const detectedFromFilename = detectImageMimeTypeFromFilename(filename);
188
+ if (detectedFromFilename) {
189
+ return detectedFromFilename;
190
+ }
191
+ return normalizedMimeType || 'application/octet-stream';
192
+ }
193
+ function isTrustedDeclaredImageMimeType(declaredMimeType, detectedMimeType) {
194
+ if (!detectedMimeType) {
195
+ return true;
196
+ }
197
+ if (declaredMimeType === detectedMimeType) {
198
+ return true;
199
+ }
200
+ if (declaredMimeType === 'image/jpg' && detectedMimeType === 'image/jpeg') {
201
+ return true;
202
+ }
203
+ return (declaredMimeType === 'image/vnd.microsoft.icon' &&
204
+ detectedMimeType === 'image/x-icon');
205
+ }
206
+ function detectImageMimeTypeFromBytes(bytes) {
207
+ var _a, _b, _c, _d, _e;
208
+ return ((_e = (_d = (_c = (_b = (_a = detectPngMimeType(bytes)) !== null && _a !== void 0 ? _a : detectJpegMimeType(bytes)) !== null && _b !== void 0 ? _b : detectGifMimeType(bytes)) !== null && _c !== void 0 ? _c : detectWebpMimeType(bytes)) !== null && _d !== void 0 ? _d : detectIconMimeType(bytes)) !== null && _e !== void 0 ? _e : detectSvgMimeType(bytes));
209
+ }
210
+ function detectPngMimeType(bytes) {
211
+ if (startsWithBytes(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
212
+ return 'image/png';
213
+ }
214
+ }
215
+ function detectJpegMimeType(bytes) {
216
+ if (startsWithBytes(bytes, [0xff, 0xd8, 0xff])) {
217
+ return 'image/jpeg';
218
+ }
219
+ }
220
+ function detectGifMimeType(bytes) {
221
+ const firstSix = bytesToAscii(bytes, 0, 6);
222
+ if (firstSix === 'GIF87a' || firstSix === 'GIF89a') {
223
+ return 'image/gif';
224
+ }
225
+ }
226
+ function detectWebpMimeType(bytes) {
227
+ const riff = bytesToAscii(bytes, 0, 4);
228
+ const webp = bytesToAscii(bytes, 8, 12);
229
+ if (riff === 'RIFF' && webp === 'WEBP') {
230
+ return 'image/webp';
231
+ }
232
+ }
233
+ function detectIconMimeType(bytes) {
234
+ if (startsWithBytes(bytes, [0x00, 0x00, 0x01, 0x00])) {
235
+ return 'image/x-icon';
236
+ }
237
+ }
238
+ function detectSvgMimeType(bytes) {
239
+ if (bytes.length < 5) {
240
+ return;
241
+ }
242
+ const utf8Prefix = new TextDecoder('utf8', { fatal: false }).decode(bytes.slice(0, Math.min(bytes.length, 256)));
243
+ const normalizedPrefix = utf8Prefix.trimStart().toLowerCase();
244
+ if (normalizedPrefix.startsWith('<svg') ||
245
+ (normalizedPrefix.startsWith('<?xml') &&
246
+ normalizedPrefix.includes('<svg'))) {
247
+ return 'image/svg+xml';
248
+ }
249
+ }
250
+ function startsWithBytes(bytes, prefix) {
251
+ if (bytes.length < prefix.length) {
252
+ return false;
253
+ }
254
+ return prefix.every((value, index) => bytes[index] === value);
255
+ }
256
+ function bytesToAscii(bytes, start, endExclusive) {
257
+ if (bytes.length < endExclusive) {
258
+ return '';
259
+ }
260
+ return String.fromCodePoint(...bytes.slice(start, endExclusive));
261
+ }
262
+ function detectImageMimeTypeFromFilename(filename) {
263
+ const normalized = filename === null || filename === void 0 ? void 0 : filename.trim().toLowerCase();
264
+ if (!normalized || !normalized.includes('.')) {
265
+ return;
266
+ }
267
+ const extension = normalized.slice(normalized.lastIndexOf('.') + 1);
268
+ const mimeTypes = {
269
+ png: 'image/png',
270
+ jpg: 'image/jpeg',
271
+ jpeg: 'image/jpeg',
272
+ gif: 'image/gif',
273
+ webp: 'image/webp',
274
+ svg: 'image/svg+xml',
275
+ ico: 'image/x-icon',
276
+ bmp: 'image/bmp',
277
+ };
278
+ return mimeTypes[extension];
279
+ }
154
280
  function isNonEmptyString(value) {
155
281
  return typeof value === 'string' && value.length > 0;
156
282
  }
@@ -208,3 +334,13 @@ function quoteDisplayNameIfNeeded(name) {
208
334
  const escaped = name.replaceAll('\\', '\\\\').replaceAll('"', '\\' + '"');
209
335
  return `"${escaped}"`;
210
336
  }
337
+ export const emailLoaderHelpers = {
338
+ normalizeContentId,
339
+ decodeCidReference,
340
+ replaceCidReferences,
341
+ getAttachmentBytes,
342
+ detectImageMimeTypeFromBytes,
343
+ detectImageMimeTypeFromFilename,
344
+ resolveDataUrlMimeType,
345
+ extractAttachments,
346
+ };
@@ -4876,11 +4876,13 @@ function extractAttachments(email) {
4876
4876
  const cidUrlById = new Map();
4877
4877
  for (const attachment of email.attachments || []) {
4878
4878
  const contentId = normalizeContentId(attachment.contentId);
4879
- const isInline = attachment.related || attachment.disposition === 'inline';
4879
+ const hasContentId = Boolean(contentId);
4880
+ const isInline = (hasContentId && attachment.disposition !== 'attachment') ||
4881
+ attachment.related ||
4882
+ attachment.disposition === 'inline';
4883
+ const contentBytes = getAttachmentBytes(attachment.content);
4880
4884
  if (!isInline) {
4881
- const size = attachment.content instanceof ArrayBuffer
4882
- ? attachment.content.byteLength
4883
- : undefined;
4885
+ const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
4884
4886
  attachments.push({
4885
4887
  filename: attachment.filename || undefined,
4886
4888
  mimeType: attachment.mimeType || undefined,
@@ -4888,11 +4890,9 @@ function extractAttachments(email) {
4888
4890
  });
4889
4891
  continue;
4890
4892
  }
4891
- const hasArrayBufferContent = attachment.content instanceof ArrayBuffer;
4892
- if (!contentId || !hasArrayBufferContent) {
4893
- const size = hasArrayBufferContent
4894
- ? attachment.content.byteLength
4895
- : undefined;
4893
+ const hasBinaryContent = Boolean(contentBytes);
4894
+ if (!contentId || !hasBinaryContent) {
4895
+ const size = contentBytes === null || contentBytes === void 0 ? void 0 : contentBytes.byteLength;
4896
4896
  attachments.push({
4897
4897
  filename: attachment.filename || undefined,
4898
4898
  mimeType: attachment.mimeType || 'application/octet-stream',
@@ -4900,8 +4900,8 @@ function extractAttachments(email) {
4900
4900
  });
4901
4901
  continue;
4902
4902
  }
4903
- const mimeType = attachment.mimeType || 'application/octet-stream';
4904
- const base64 = arrayBufferToBase64(attachment.content);
4903
+ const mimeType = resolveDataUrlMimeType(attachment.mimeType, contentBytes, attachment.filename);
4904
+ const base64 = byteArrayToBase64(contentBytes);
4905
4905
  const dataUrl = `data:${mimeType};base64,${base64}`;
4906
4906
  cidUrlById.set(contentId, dataUrl);
4907
4907
  }
@@ -4928,6 +4928,9 @@ function normalizeContentId(contentId) {
4928
4928
  return '';
4929
4929
  }
4930
4930
  let normalized = contentId.trim();
4931
+ if (normalized.toLowerCase().startsWith('cid:')) {
4932
+ normalized = normalized.slice(4).trim();
4933
+ }
4931
4934
  if (normalized.startsWith('<')) {
4932
4935
  normalized = normalized.slice(1);
4933
4936
  }
@@ -4941,7 +4944,7 @@ function replaceCidReferences(html, cidUrlById) {
4941
4944
  return html;
4942
4945
  }
4943
4946
  return html.replaceAll(/(src\s*=\s*["']?)cid:([^"'\s>]+)(["']?)/gi, (match, prefix, cid, suffix) => {
4944
- const normalized = normalizeContentId(cid);
4947
+ const normalized = normalizeContentId(decodeCidReference(cid));
4945
4948
  const replacement = cidUrlById.get(normalized);
4946
4949
  if (!replacement) {
4947
4950
  return match;
@@ -4949,8 +4952,24 @@ function replaceCidReferences(html, cidUrlById) {
4949
4952
  return `${prefix}${replacement}${suffix}`;
4950
4953
  });
4951
4954
  }
4952
- function arrayBufferToBase64(buffer) {
4953
- const bytes = new Uint8Array(buffer);
4955
+ function decodeCidReference(cid) {
4956
+ try {
4957
+ return decodeURIComponent(cid);
4958
+ }
4959
+ catch (_a) {
4960
+ return cid;
4961
+ }
4962
+ }
4963
+ function getAttachmentBytes(content) {
4964
+ if (content instanceof ArrayBuffer) {
4965
+ return new Uint8Array(content);
4966
+ }
4967
+ if (ArrayBuffer.isView(content)) {
4968
+ return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
4969
+ }
4970
+ return undefined;
4971
+ }
4972
+ function byteArrayToBase64(bytes) {
4954
4973
  if (typeof btoa === 'function') {
4955
4974
  let binary = '';
4956
4975
  for (const byte of bytes) {
@@ -4961,6 +4980,113 @@ function arrayBufferToBase64(buffer) {
4961
4980
  // Jest/Node fallback
4962
4981
  return globalThis.Buffer.from(bytes).toString('base64');
4963
4982
  }
4983
+ function resolveDataUrlMimeType(mimeType, bytes, filename) {
4984
+ const normalizedMimeType = typeof mimeType === 'string' ? mimeType.trim().toLowerCase() : '';
4985
+ const detectedFromBytes = detectImageMimeTypeFromBytes(bytes);
4986
+ if (normalizedMimeType.startsWith('image/')) {
4987
+ if (isTrustedDeclaredImageMimeType(normalizedMimeType, detectedFromBytes)) {
4988
+ return normalizedMimeType;
4989
+ }
4990
+ if (detectedFromBytes) {
4991
+ return detectedFromBytes;
4992
+ }
4993
+ }
4994
+ if (detectedFromBytes) {
4995
+ return detectedFromBytes;
4996
+ }
4997
+ const detectedFromFilename = detectImageMimeTypeFromFilename(filename);
4998
+ if (detectedFromFilename) {
4999
+ return detectedFromFilename;
5000
+ }
5001
+ return normalizedMimeType || 'application/octet-stream';
5002
+ }
5003
+ function isTrustedDeclaredImageMimeType(declaredMimeType, detectedMimeType) {
5004
+ if (!detectedMimeType) {
5005
+ return true;
5006
+ }
5007
+ if (declaredMimeType === detectedMimeType) {
5008
+ return true;
5009
+ }
5010
+ if (declaredMimeType === 'image/jpg' && detectedMimeType === 'image/jpeg') {
5011
+ return true;
5012
+ }
5013
+ return (declaredMimeType === 'image/vnd.microsoft.icon' &&
5014
+ detectedMimeType === 'image/x-icon');
5015
+ }
5016
+ function detectImageMimeTypeFromBytes(bytes) {
5017
+ var _a, _b, _c, _d, _e;
5018
+ return ((_e = (_d = (_c = (_b = (_a = detectPngMimeType(bytes)) !== null && _a !== void 0 ? _a : detectJpegMimeType(bytes)) !== null && _b !== void 0 ? _b : detectGifMimeType(bytes)) !== null && _c !== void 0 ? _c : detectWebpMimeType(bytes)) !== null && _d !== void 0 ? _d : detectIconMimeType(bytes)) !== null && _e !== void 0 ? _e : detectSvgMimeType(bytes));
5019
+ }
5020
+ function detectPngMimeType(bytes) {
5021
+ if (startsWithBytes(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
5022
+ return 'image/png';
5023
+ }
5024
+ }
5025
+ function detectJpegMimeType(bytes) {
5026
+ if (startsWithBytes(bytes, [0xff, 0xd8, 0xff])) {
5027
+ return 'image/jpeg';
5028
+ }
5029
+ }
5030
+ function detectGifMimeType(bytes) {
5031
+ const firstSix = bytesToAscii(bytes, 0, 6);
5032
+ if (firstSix === 'GIF87a' || firstSix === 'GIF89a') {
5033
+ return 'image/gif';
5034
+ }
5035
+ }
5036
+ function detectWebpMimeType(bytes) {
5037
+ const riff = bytesToAscii(bytes, 0, 4);
5038
+ const webp = bytesToAscii(bytes, 8, 12);
5039
+ if (riff === 'RIFF' && webp === 'WEBP') {
5040
+ return 'image/webp';
5041
+ }
5042
+ }
5043
+ function detectIconMimeType(bytes) {
5044
+ if (startsWithBytes(bytes, [0x00, 0x00, 0x01, 0x00])) {
5045
+ return 'image/x-icon';
5046
+ }
5047
+ }
5048
+ function detectSvgMimeType(bytes) {
5049
+ if (bytes.length < 5) {
5050
+ return;
5051
+ }
5052
+ const utf8Prefix = new TextDecoder('utf8', { fatal: false }).decode(bytes.slice(0, Math.min(bytes.length, 256)));
5053
+ const normalizedPrefix = utf8Prefix.trimStart().toLowerCase();
5054
+ if (normalizedPrefix.startsWith('<svg') ||
5055
+ (normalizedPrefix.startsWith('<?xml') &&
5056
+ normalizedPrefix.includes('<svg'))) {
5057
+ return 'image/svg+xml';
5058
+ }
5059
+ }
5060
+ function startsWithBytes(bytes, prefix) {
5061
+ if (bytes.length < prefix.length) {
5062
+ return false;
5063
+ }
5064
+ return prefix.every((value, index) => bytes[index] === value);
5065
+ }
5066
+ function bytesToAscii(bytes, start, endExclusive) {
5067
+ if (bytes.length < endExclusive) {
5068
+ return '';
5069
+ }
5070
+ return String.fromCodePoint(...bytes.slice(start, endExclusive));
5071
+ }
5072
+ function detectImageMimeTypeFromFilename(filename) {
5073
+ const normalized = filename === null || filename === void 0 ? void 0 : filename.trim().toLowerCase();
5074
+ if (!normalized || !normalized.includes('.')) {
5075
+ return;
5076
+ }
5077
+ const extension = normalized.slice(normalized.lastIndexOf('.') + 1);
5078
+ const mimeTypes = {
5079
+ png: 'image/png',
5080
+ jpg: 'image/jpeg',
5081
+ jpeg: 'image/jpeg',
5082
+ gif: 'image/gif',
5083
+ webp: 'image/webp',
5084
+ svg: 'image/svg+xml',
5085
+ ico: 'image/x-icon',
5086
+ bmp: 'image/bmp',
5087
+ };
5088
+ return mimeTypes[extension];
5089
+ }
4964
5090
  function isNonEmptyString(value) {
4965
5091
  return typeof value === 'string' && value.length > 0;
4966
5092
  }