@limetech/lime-elements 39.5.0 → 39.5.2
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 +14 -0
- package/dist/cjs/limel-email-viewer.cjs.entry.js +1 -1
- package/dist/cjs/limel-file-viewer.cjs.entry.js +140 -14
- package/dist/collection/components/email-viewer/email-loader.js +150 -14
- package/dist/collection/components/email-viewer/email-viewer.css +6 -4
- package/dist/esm/limel-email-viewer.entry.js +1 -1
- package/dist/esm/limel-file-viewer.entry.js +140 -14
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-656b8f6e.entry.js → p-8d265f52.entry.js} +1 -1
- package/dist/lime-elements/{p-4bac6803.entry.js → p-fd223ac8.entry.js} +1 -1
- package/dist/types/components/email-viewer/email-loader.d.ts +29 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [39.5.2](https://github.com/Lundalogik/lime-elements/compare/v39.5.1...v39.5.2) (2026-02-27)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
* **email-viewer:** fix date overflowing its container and overlapping download button ([df03885](https://github.com/Lundalogik/lime-elements/commit/df038850da36bc07b70313d8a270d05cca5ab91f)), closes [Lundalogik/lime-elements#3908](https://github.com/Lundalogik/lime-elements/issues/3908)
|
|
7
|
+
|
|
8
|
+
## [39.5.1](https://github.com/Lundalogik/lime-elements/compare/v39.5.0...v39.5.1) (2026-02-26)
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
* **email-viewer:** render inline cid logos in .eml previews ([29b85d3](https://github.com/Lundalogik/lime-elements/commit/29b85d3759e81ce0c3209fb911031d5a4f82a292))
|
|
14
|
+
|
|
1
15
|
## [39.5.0](https://github.com/Lundalogik/lime-elements/compare/v39.4.1...v39.5.0) (2026-02-25)
|
|
2
16
|
|
|
3
17
|
### Features
|
|
@@ -200,7 +200,7 @@ function formatBytes(bytes, decimals = 1) {
|
|
|
200
200
|
return `${rounded} ${sizes[i]}`;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
const emailViewerCss = () => `@charset "UTF-8";:host(limel-email-viewer){display:block;width:100%;height:100%;box-sizing:border-box}*,*::before,*::after{box-sizing:border-box;min-width:0;min-height:0}.email{display:flex;flex-direction:column;width:100%;height:100%;padding-bottom:0.5rem;box-shadow:var(--shadow-depth-8)}.email-headers{position:relative;flex-shrink:0;display:flex;flex-direction:column}.email-headers dl,.email-headers dt,.email-headers dd{margin:0}.email-headers dl{display:flex;flex-wrap:wrap;gap:0 0.5rem;padding:0.5rem 0.75rem;font-size:0.75rem}.email-headers dl:nth-child(even){background-color:rgb(var(--contrast-800), 0.1)}.email-headers dl dt{opacity:0.6;min-width:3rem}.email-headers dl dt::after{content:":"}.email-headers dl dd:not(:last-child)::after{content:",";opacity:0.6}.email-headers dl.subject dd{font-weight:bold}.email-headers dl.date{position:absolute;right:0.25rem;transform:translateY(
|
|
203
|
+
const emailViewerCss = () => `@charset "UTF-8";:host(limel-email-viewer){display:block;width:100%;height:100%;box-sizing:border-box}*,*::before,*::after{box-sizing:border-box;min-width:0;min-height:0}.email{display:flex;flex-direction:column;width:100%;height:100%;padding-bottom:0.5rem;box-shadow:var(--shadow-depth-8)}.email-headers{position:relative;flex-shrink:0;display:flex;flex-direction:column}.email-headers dl,.email-headers dt,.email-headers dd{margin:0}.email-headers dl{display:flex;flex-wrap:wrap;gap:0 0.5rem;padding:0.5rem 0.75rem;font-size:0.75rem}.email-headers dl:nth-child(even){background-color:rgb(var(--contrast-800), 0.1)}.email-headers dl dt{opacity:0.6;min-width:3rem}.email-headers dl dt::after{content:":"}.email-headers dl dd:not(:last-child)::after{content:",";opacity:0.6}.email-headers dl.subject dd{font-weight:bold}.email-headers dl.date{position:absolute;right:0.25rem;bottom:0;transform:translateY(50%);font-size:0.625rem;border-radius:9rem;padding:0.125rem 0.25rem;background-color:rgb(var(--contrast-100), 0.8);border:rgb(var(--contrast-600)) solid 1px}.email-headers dl.date dt{position:absolute;width:0;height:0;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0, 0, 0, 0);clip-path:inset(50%);white-space:nowrap}.attachments{flex-shrink:0;padding:0.5rem;border-bottom:1px dashed rga(var(--contrast-700))}.attachments span{padding-left:0.25rem;font-size:0.75rem;opacity:0.6}.attachments span:first-child::after{content:":"}.attachments ul{all:unset;display:grid;grid-template-columns:repeat(auto-fill, minmax(8rem, 1fr));gap:0.5rem;padding:0.5rem 0}.attachments li{all:unset;position:relative;display:flex;flex-direction:column;gap:0.25rem;font-size:0.6875rem;line-height:normal;padding:0.5rem 0.5rem 1rem 0.5rem;border-radius:0.5rem;border:1px solid rgba(var(--contrast-600));background-color:rgba(var(--contrast-400))}.attachments .attachment-filename{font-weight:500}.attachments .attachment-mime-type{opacity:0.7}.attachments limel-badge{--badge-max-width:auto;--badge-background-color:rgb(var(--contrast-1000), 0.7);--badge-text-color:rgb(var(--color-white));position:absolute;bottom:0.125rem;right:0.125rem;box-shadow:var(--shadow-brighten-edges-outside)}section{flex-grow:1;display:flex;flex-direction:column;border-top:1px dashed rgba(var(--contrast-700));min-height:2rem;overflow-y:auto}limel-collapsible-section{--closed-header-background-color:var( --lime-elevated-surface-background-color );flex-grow:1;flex-shrink:0;margin:0.5rem;border-radius:0.75rem;box-shadow:var(--shadow-depth-8)}limel-collapsible-section button{all:unset;flex-shrink:0;border-radius:0.375rem;padding:0.25rem 0.5rem;font-size:var(--limel-theme-small-font-size);margin:0 0.5rem}limel-collapsible-section button.load-images{transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--lime-primary-color, var(--limel-theme-primary-color));background-color:var(--lime-elevated-surface-background-color);box-shadow:var(--button-shadow-normal)}limel-collapsible-section button.load-images:hover,limel-collapsible-section button.load-images:focus,limel-collapsible-section button.load-images:focus-visible{will-change:color, background-color, box-shadow, transform}limel-collapsible-section button.load-images:hover,limel-collapsible-section button.load-images:focus-visible{transform:translate3d(0, -0.04rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color);box-shadow:var(--button-shadow-hovered)}limel-collapsible-section button.load-images:active{--limel-clickable-transform-timing-function:cubic-bezier( 0.83, -0.15, 0.49, 1.16 );transform:translate3d(0, 0.05rem, 0);box-shadow:var(--button-shadow-pressed)}limel-collapsible-section button.load-images:hover,limel-collapsible-section button.load-images:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}limel-collapsible-section limel-markdown{padding:0.5rem}.body{flex-grow:1;max-width:100%;padding:0.75rem}.body.plain-text{white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-family:inherit}.body img{max-width:100% !important}`;
|
|
204
204
|
|
|
205
205
|
const EmailViewer = class {
|
|
206
206
|
constructor(hostRef) {
|
|
@@ -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
|
|
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 =
|
|
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
|
|
4894
|
-
if (!contentId || !
|
|
4895
|
-
const size =
|
|
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
|
|
4906
|
-
const base64 =
|
|
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
|
|
4955
|
-
|
|
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
|
|
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 =
|
|
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
|
|
82
|
-
if (!contentId || !
|
|
83
|
-
const size =
|
|
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
|
|
94
|
-
const base64 =
|
|
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
|
|
143
|
-
|
|
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
|
+
};
|
|
@@ -148,11 +148,13 @@
|
|
|
148
148
|
.email-headers dl.date {
|
|
149
149
|
position: absolute;
|
|
150
150
|
right: 0.25rem;
|
|
151
|
-
|
|
151
|
+
bottom: 0;
|
|
152
|
+
transform: translateY(50%);
|
|
152
153
|
font-size: 0.625rem;
|
|
153
154
|
border-radius: 9rem;
|
|
154
|
-
padding: 0.
|
|
155
|
-
background-color: rgb(var(--contrast-100), 0.
|
|
155
|
+
padding: 0.125rem 0.25rem;
|
|
156
|
+
background-color: rgb(var(--contrast-100), 0.8);
|
|
157
|
+
border: rgb(var(--contrast-600)) solid 1px;
|
|
156
158
|
}
|
|
157
159
|
.email-headers dl.date dt {
|
|
158
160
|
position: absolute;
|
|
@@ -170,7 +172,7 @@
|
|
|
170
172
|
.attachments {
|
|
171
173
|
flex-shrink: 0;
|
|
172
174
|
padding: 0.5rem;
|
|
173
|
-
border-bottom: 1px dashed
|
|
175
|
+
border-bottom: 1px dashed rga(var(--contrast-700));
|
|
174
176
|
}
|
|
175
177
|
.attachments span {
|
|
176
178
|
padding-left: 0.25rem;
|
|
@@ -198,7 +198,7 @@ function formatBytes(bytes, decimals = 1) {
|
|
|
198
198
|
return `${rounded} ${sizes[i]}`;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
const emailViewerCss = () => `@charset "UTF-8";:host(limel-email-viewer){display:block;width:100%;height:100%;box-sizing:border-box}*,*::before,*::after{box-sizing:border-box;min-width:0;min-height:0}.email{display:flex;flex-direction:column;width:100%;height:100%;padding-bottom:0.5rem;box-shadow:var(--shadow-depth-8)}.email-headers{position:relative;flex-shrink:0;display:flex;flex-direction:column}.email-headers dl,.email-headers dt,.email-headers dd{margin:0}.email-headers dl{display:flex;flex-wrap:wrap;gap:0 0.5rem;padding:0.5rem 0.75rem;font-size:0.75rem}.email-headers dl:nth-child(even){background-color:rgb(var(--contrast-800), 0.1)}.email-headers dl dt{opacity:0.6;min-width:3rem}.email-headers dl dt::after{content:":"}.email-headers dl dd:not(:last-child)::after{content:",";opacity:0.6}.email-headers dl.subject dd{font-weight:bold}.email-headers dl.date{position:absolute;right:0.25rem;transform:translateY(
|
|
201
|
+
const emailViewerCss = () => `@charset "UTF-8";:host(limel-email-viewer){display:block;width:100%;height:100%;box-sizing:border-box}*,*::before,*::after{box-sizing:border-box;min-width:0;min-height:0}.email{display:flex;flex-direction:column;width:100%;height:100%;padding-bottom:0.5rem;box-shadow:var(--shadow-depth-8)}.email-headers{position:relative;flex-shrink:0;display:flex;flex-direction:column}.email-headers dl,.email-headers dt,.email-headers dd{margin:0}.email-headers dl{display:flex;flex-wrap:wrap;gap:0 0.5rem;padding:0.5rem 0.75rem;font-size:0.75rem}.email-headers dl:nth-child(even){background-color:rgb(var(--contrast-800), 0.1)}.email-headers dl dt{opacity:0.6;min-width:3rem}.email-headers dl dt::after{content:":"}.email-headers dl dd:not(:last-child)::after{content:",";opacity:0.6}.email-headers dl.subject dd{font-weight:bold}.email-headers dl.date{position:absolute;right:0.25rem;bottom:0;transform:translateY(50%);font-size:0.625rem;border-radius:9rem;padding:0.125rem 0.25rem;background-color:rgb(var(--contrast-100), 0.8);border:rgb(var(--contrast-600)) solid 1px}.email-headers dl.date dt{position:absolute;width:0;height:0;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0, 0, 0, 0);clip-path:inset(50%);white-space:nowrap}.attachments{flex-shrink:0;padding:0.5rem;border-bottom:1px dashed rga(var(--contrast-700))}.attachments span{padding-left:0.25rem;font-size:0.75rem;opacity:0.6}.attachments span:first-child::after{content:":"}.attachments ul{all:unset;display:grid;grid-template-columns:repeat(auto-fill, minmax(8rem, 1fr));gap:0.5rem;padding:0.5rem 0}.attachments li{all:unset;position:relative;display:flex;flex-direction:column;gap:0.25rem;font-size:0.6875rem;line-height:normal;padding:0.5rem 0.5rem 1rem 0.5rem;border-radius:0.5rem;border:1px solid rgba(var(--contrast-600));background-color:rgba(var(--contrast-400))}.attachments .attachment-filename{font-weight:500}.attachments .attachment-mime-type{opacity:0.7}.attachments limel-badge{--badge-max-width:auto;--badge-background-color:rgb(var(--contrast-1000), 0.7);--badge-text-color:rgb(var(--color-white));position:absolute;bottom:0.125rem;right:0.125rem;box-shadow:var(--shadow-brighten-edges-outside)}section{flex-grow:1;display:flex;flex-direction:column;border-top:1px dashed rgba(var(--contrast-700));min-height:2rem;overflow-y:auto}limel-collapsible-section{--closed-header-background-color:var( --lime-elevated-surface-background-color );flex-grow:1;flex-shrink:0;margin:0.5rem;border-radius:0.75rem;box-shadow:var(--shadow-depth-8)}limel-collapsible-section button{all:unset;flex-shrink:0;border-radius:0.375rem;padding:0.25rem 0.5rem;font-size:var(--limel-theme-small-font-size);margin:0 0.5rem}limel-collapsible-section button.load-images{transition:color var(--limel-clickable-transition-speed, 0.4s) ease, background-color var(--limel-clickable-transition-speed, 0.4s) ease, box-shadow var(--limel-clickable-transform-speed, 0.4s) ease, transform var(--limel-clickable-transform-speed, 0.4s) var(--limel-clickable-transform-timing-function, ease);cursor:pointer;color:var(--lime-primary-color, var(--limel-theme-primary-color));background-color:var(--lime-elevated-surface-background-color);box-shadow:var(--button-shadow-normal)}limel-collapsible-section button.load-images:hover,limel-collapsible-section button.load-images:focus,limel-collapsible-section button.load-images:focus-visible{will-change:color, background-color, box-shadow, transform}limel-collapsible-section button.load-images:hover,limel-collapsible-section button.load-images:focus-visible{transform:translate3d(0, -0.04rem, 0);color:var(--limel-theme-on-surface-color);background-color:var(--lime-elevated-surface-background-color);box-shadow:var(--button-shadow-hovered)}limel-collapsible-section button.load-images:active{--limel-clickable-transform-timing-function:cubic-bezier( 0.83, -0.15, 0.49, 1.16 );transform:translate3d(0, 0.05rem, 0);box-shadow:var(--button-shadow-pressed)}limel-collapsible-section button.load-images:hover,limel-collapsible-section button.load-images:active{--limel-clickable-transition-speed:0.2s;--limel-clickable-transform-speed:0.16s}limel-collapsible-section limel-markdown{padding:0.5rem}.body{flex-grow:1;max-width:100%;padding:0.75rem}.body.plain-text{white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-family:inherit}.body img{max-width:100% !important}`;
|
|
202
202
|
|
|
203
203
|
const EmailViewer = class {
|
|
204
204
|
constructor(hostRef) {
|