@cassandragargoyle/react-viewer 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +69 -2
- package/dist/EmailViewer/AttachmentList.d.ts +4 -0
- package/dist/EmailViewer/EmailBody.d.ts +4 -0
- package/dist/EmailViewer/EmailHeader.d.ts +5 -0
- package/dist/EmailViewer/EmailToolbar.d.ts +1 -0
- package/dist/EmailViewer/EmailViewer.d.ts +6 -0
- package/dist/EmailViewer/icons.d.ts +7 -0
- package/dist/EmailViewer/index.d.ts +4 -0
- package/dist/EmailViewer/inlineHtml.d.ts +10 -0
- package/dist/EmailViewer/types.d.ts +47 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +344 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CassandraGargoyle Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
-
#
|
|
1
|
+
# React Viewer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React viewers for document formats, shared across the CassandraGargoyle ecosystem.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@cassandragargoyle/react-viewer)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
**Repository**: `react-viewer`
|
|
11
|
+
**Language**: TypeScript (React 19, Node 24+)
|
|
12
|
+
**Package**: [`@cassandragargoyle/react-viewer`](https://www.npmjs.com/package/@cassandragargoyle/react-viewer) on npmjs.com
|
|
13
|
+
|
|
14
|
+
A viewer shows one kind of document — an e-mail message today — from data the host has
|
|
15
|
+
already read. It never parses a file format and never calls a host API: the host reads the
|
|
16
|
+
file, maps it to the viewer's own type, and passes it in as props. General UI building
|
|
17
|
+
blocks live next door in [`react-components`](https://github.com/cassandragargoyle/react-components).
|
|
18
|
+
|
|
19
|
+
Viewers came from `portunix-vscode`; the placement is decided in its
|
|
20
|
+
[issue 132](https://github.com/cassandragargoyle/portunix-vscode/blob/main/docs/issues/done/132-shared-modules-and-group-b-components.md)
|
|
21
|
+
and [ADR-012](https://github.com/cassandragargoyle/portunix-vscode/blob/main/docs/adr/012-react-components-shared-submodule.md).
|
|
22
|
+
|
|
23
|
+
## Getting Started
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @cassandragargoyle/react-viewer dompurify
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
React 19 and `dompurify` are peer dependencies.
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { EmailViewer, type EmailMessage } from '@cassandragargoyle/react-viewer';
|
|
33
|
+
|
|
34
|
+
export function MailPane({ email }: { email: EmailMessage }): React.ReactElement {
|
|
35
|
+
return <EmailViewer email={email} />;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Stylesheets are bundled into the JavaScript and injected on first use; there is no CSS file
|
|
40
|
+
to import. Colours follow the Visual Studio Code theme variables when they are present.
|
|
41
|
+
|
|
42
|
+
## Viewers
|
|
43
|
+
|
|
44
|
+
| Viewer | What it shows |
|
|
45
|
+
| ------ | ------------- |
|
|
46
|
+
| [`EmailViewer`](https://github.com/cassandragargoyle/react-viewer/tree/main/src/EmailViewer) | An e-mail message: header, attachments and a sandboxed HTML / text / source / headers body |
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/cassandragargoyle/react-viewer.git
|
|
52
|
+
cd react-viewer
|
|
53
|
+
|
|
54
|
+
npm install
|
|
55
|
+
npm run typecheck
|
|
56
|
+
npm run build
|
|
57
|
+
npm test
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Releasing
|
|
61
|
+
|
|
62
|
+
Bump `version` in `package.json`, commit, and push a matching `vX.Y.Z` tag. The publish
|
|
63
|
+
workflow refuses a tag that does not match `package.json` and publishes to npmjs.com through
|
|
64
|
+
npm Trusted Publishing, so no npm token exists in the repository. The trusted publisher
|
|
65
|
+
(this repository, `publish.yml`) has to be set in the package settings on npmjs.com before
|
|
66
|
+
the first release.
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function EmailToolbar(): JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function ReplyIcon(): JSX.Element;
|
|
2
|
+
export declare function ReplyAllIcon(): JSX.Element;
|
|
3
|
+
export declare function ForwardIcon(): JSX.Element;
|
|
4
|
+
export declare function DownloadIcon(): JSX.Element;
|
|
5
|
+
export declare function AttachmentIcon(): JSX.Element;
|
|
6
|
+
export declare function PriorityHighIcon(): JSX.Element;
|
|
7
|
+
export declare function PriorityLowIcon(): JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EmailAttachment } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Build the sandboxed-iframe document for a mail's HTML body. Inline cid: references are
|
|
4
|
+
* rewritten to data: URIs built from the matching inline attachments — data: (not blob:)
|
|
5
|
+
* because a fully sandboxed iframe has an opaque origin and cannot load the parent's blob:
|
|
6
|
+
* URLs. Everything else is sanitized and wrapped in a restrictive CSP that permits only
|
|
7
|
+
* data: images and inline styles, so remote content (tracking pixels, external CSS) is
|
|
8
|
+
* blocked by default.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildInlineHtml(rawHtml: string, attachments: EmailAttachment[]): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** A single mailbox: an address plus an optional display name */
|
|
2
|
+
export interface EmailAddress {
|
|
3
|
+
name?: string;
|
|
4
|
+
address: string;
|
|
5
|
+
}
|
|
6
|
+
/** One attachment or inline part, carried as raw bytes so the viewer owns rendering */
|
|
7
|
+
export interface EmailAttachment {
|
|
8
|
+
filename: string;
|
|
9
|
+
mimeType: string;
|
|
10
|
+
size: number;
|
|
11
|
+
/** Content-ID (angle brackets stripped) used to resolve inline cid: references */
|
|
12
|
+
contentId?: string;
|
|
13
|
+
disposition: 'attachment' | 'inline';
|
|
14
|
+
bytes: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
/** Message importance; 'normal' renders without a badge */
|
|
17
|
+
export type EmailImportance = 'high' | 'normal' | 'low';
|
|
18
|
+
/**
|
|
19
|
+
* Normalized message the viewer renders. `html` is the message's raw HTML — the viewer
|
|
20
|
+
* sanitizes it and resolves cid: references itself, so a reader can stay DOM-free.
|
|
21
|
+
*/
|
|
22
|
+
export interface EmailMessage {
|
|
23
|
+
subject: string;
|
|
24
|
+
from?: EmailAddress;
|
|
25
|
+
to: EmailAddress[];
|
|
26
|
+
cc: EmailAddress[];
|
|
27
|
+
date?: Date;
|
|
28
|
+
importance: EmailImportance;
|
|
29
|
+
html?: string;
|
|
30
|
+
text?: string;
|
|
31
|
+
attachments: EmailAttachment[];
|
|
32
|
+
headers: Array<{
|
|
33
|
+
key: string;
|
|
34
|
+
value: string;
|
|
35
|
+
}>;
|
|
36
|
+
/** Original source shown on the Source tab */
|
|
37
|
+
raw: string;
|
|
38
|
+
}
|
|
39
|
+
/** Turns the message date into the text of the Date row */
|
|
40
|
+
export type EmailDateFormatter = (date: Date) => string;
|
|
41
|
+
/**
|
|
42
|
+
* Default date format: local time as `YYYY-MM-DD HH:MM:SS`.
|
|
43
|
+
*
|
|
44
|
+
* Unambiguous in every region and never drops the seconds a mail Date: header carries.
|
|
45
|
+
* An invalid Date yields an empty string, so the row is omitted rather than showing `NaN`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatEmailDate(date: Date): string;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { useEffect as e, useMemo as t, useState as n } from "react";
|
|
2
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
3
|
+
import a from "dompurify";
|
|
4
|
+
//#region src/EmailViewer/icons.tsx
|
|
5
|
+
var o = {
|
|
6
|
+
width: 16,
|
|
7
|
+
height: 16,
|
|
8
|
+
viewBox: "0 0 16 16",
|
|
9
|
+
fill: "none",
|
|
10
|
+
stroke: "currentColor",
|
|
11
|
+
strokeWidth: 1.3,
|
|
12
|
+
strokeLinecap: "round",
|
|
13
|
+
strokeLinejoin: "round"
|
|
14
|
+
};
|
|
15
|
+
function s() {
|
|
16
|
+
return /* @__PURE__ */ i("svg", {
|
|
17
|
+
...o,
|
|
18
|
+
"aria-hidden": "true",
|
|
19
|
+
children: [/* @__PURE__ */ r("path", { d: "M6 4 2 8l4 4" }), /* @__PURE__ */ r("path", { d: "M2 8h6a5 5 0 0 1 5 5" })]
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function c() {
|
|
23
|
+
return /* @__PURE__ */ i("svg", {
|
|
24
|
+
...o,
|
|
25
|
+
"aria-hidden": "true",
|
|
26
|
+
children: [
|
|
27
|
+
/* @__PURE__ */ r("path", { d: "M5 4 1 8l4 4" }),
|
|
28
|
+
/* @__PURE__ */ r("path", { d: "M9 4 5 8l4 4" }),
|
|
29
|
+
/* @__PURE__ */ r("path", { d: "M5 8h4a5 5 0 0 1 5 5" })
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function l() {
|
|
34
|
+
return /* @__PURE__ */ i("svg", {
|
|
35
|
+
...o,
|
|
36
|
+
"aria-hidden": "true",
|
|
37
|
+
children: [/* @__PURE__ */ r("path", { d: "M10 4l4 4-4 4" }), /* @__PURE__ */ r("path", { d: "M14 8H8a5 5 0 0 0-5 5" })]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function u() {
|
|
41
|
+
return /* @__PURE__ */ i("svg", {
|
|
42
|
+
...o,
|
|
43
|
+
"aria-hidden": "true",
|
|
44
|
+
children: [
|
|
45
|
+
/* @__PURE__ */ r("path", { d: "M8 2v8" }),
|
|
46
|
+
/* @__PURE__ */ r("path", { d: "M4.5 7 8 10.5 11.5 7" }),
|
|
47
|
+
/* @__PURE__ */ r("path", { d: "M3 13h10" })
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function d() {
|
|
52
|
+
return /* @__PURE__ */ r("svg", {
|
|
53
|
+
...o,
|
|
54
|
+
"aria-hidden": "true",
|
|
55
|
+
children: /* @__PURE__ */ r("path", { d: "M12.5 6.5 7 12a3 3 0 0 1-4-4l5.5-5.5a2 2 0 0 1 3 3L6 11" })
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function f() {
|
|
59
|
+
return /* @__PURE__ */ i("svg", {
|
|
60
|
+
...o,
|
|
61
|
+
"aria-hidden": "true",
|
|
62
|
+
children: [/* @__PURE__ */ r("path", { d: "M4 9l4-4 4 4" }), /* @__PURE__ */ r("path", { d: "M4 12l4-4 4 4" })]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function p() {
|
|
66
|
+
return /* @__PURE__ */ r("svg", {
|
|
67
|
+
...o,
|
|
68
|
+
"aria-hidden": "true",
|
|
69
|
+
children: /* @__PURE__ */ r("path", { d: "M4 6l4 4 4-4" })
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/EmailViewer/EmailToolbar.tsx
|
|
74
|
+
function m() {
|
|
75
|
+
return /* @__PURE__ */ i("div", {
|
|
76
|
+
className: "email-toolbar",
|
|
77
|
+
role: "toolbar",
|
|
78
|
+
"aria-label": "Message actions",
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ i("button", {
|
|
81
|
+
className: "email-toolbar-btn",
|
|
82
|
+
type: "button",
|
|
83
|
+
disabled: !0,
|
|
84
|
+
title: "Reply (coming soon)",
|
|
85
|
+
children: [/* @__PURE__ */ r(s, {}), /* @__PURE__ */ r("span", { children: "Reply" })]
|
|
86
|
+
}),
|
|
87
|
+
/* @__PURE__ */ i("button", {
|
|
88
|
+
className: "email-toolbar-btn",
|
|
89
|
+
type: "button",
|
|
90
|
+
disabled: !0,
|
|
91
|
+
title: "Reply all (coming soon)",
|
|
92
|
+
children: [/* @__PURE__ */ r(c, {}), /* @__PURE__ */ r("span", { children: "Reply all" })]
|
|
93
|
+
}),
|
|
94
|
+
/* @__PURE__ */ i("button", {
|
|
95
|
+
className: "email-toolbar-btn",
|
|
96
|
+
type: "button",
|
|
97
|
+
disabled: !0,
|
|
98
|
+
title: "Forward (coming soon)",
|
|
99
|
+
children: [/* @__PURE__ */ r(l, {}), /* @__PURE__ */ r("span", { children: "Forward" })]
|
|
100
|
+
})
|
|
101
|
+
]
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/EmailViewer/types.ts
|
|
106
|
+
function h(e, t = 2) {
|
|
107
|
+
return String(e).padStart(t, "0");
|
|
108
|
+
}
|
|
109
|
+
function g(e) {
|
|
110
|
+
return Number.isNaN(e.getTime()) ? "" : `${`${h(e.getFullYear(), 4)}-${h(e.getMonth() + 1)}-${h(e.getDate())}`} ${`${h(e.getHours())}:${h(e.getMinutes())}:${h(e.getSeconds())}`}`;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/EmailViewer/EmailHeader.tsx
|
|
114
|
+
function _(e) {
|
|
115
|
+
return e.name ? `${e.name} <${e.address}>` : e.address;
|
|
116
|
+
}
|
|
117
|
+
function v(e) {
|
|
118
|
+
return e.map(_).join(", ");
|
|
119
|
+
}
|
|
120
|
+
function y({ importance: e }) {
|
|
121
|
+
return e === "high" ? /* @__PURE__ */ i("span", {
|
|
122
|
+
className: "email-importance email-importance-high",
|
|
123
|
+
title: "High importance",
|
|
124
|
+
children: [/* @__PURE__ */ r(f, {}), "High priority"]
|
|
125
|
+
}) : e === "low" ? /* @__PURE__ */ i("span", {
|
|
126
|
+
className: "email-importance email-importance-low",
|
|
127
|
+
title: "Low importance",
|
|
128
|
+
children: [/* @__PURE__ */ r(p, {}), "Low priority"]
|
|
129
|
+
}) : null;
|
|
130
|
+
}
|
|
131
|
+
function b({ label: e, value: t }) {
|
|
132
|
+
return t ? /* @__PURE__ */ i("div", {
|
|
133
|
+
className: "email-header-row",
|
|
134
|
+
children: [/* @__PURE__ */ r("span", {
|
|
135
|
+
className: "email-header-label",
|
|
136
|
+
children: e
|
|
137
|
+
}), /* @__PURE__ */ r("span", {
|
|
138
|
+
className: "email-header-value",
|
|
139
|
+
children: t
|
|
140
|
+
})]
|
|
141
|
+
}) : null;
|
|
142
|
+
}
|
|
143
|
+
function x({ email: e, formatDate: t = g }) {
|
|
144
|
+
let n = e.date ? t(e.date) : "";
|
|
145
|
+
return /* @__PURE__ */ i("div", {
|
|
146
|
+
className: "email-header",
|
|
147
|
+
children: [
|
|
148
|
+
/* @__PURE__ */ i("div", {
|
|
149
|
+
className: "email-header-subject-row",
|
|
150
|
+
children: [/* @__PURE__ */ r("span", {
|
|
151
|
+
className: "email-header-subject",
|
|
152
|
+
children: e.subject
|
|
153
|
+
}), /* @__PURE__ */ r(y, { importance: e.importance })]
|
|
154
|
+
}),
|
|
155
|
+
/* @__PURE__ */ r(b, {
|
|
156
|
+
label: "From",
|
|
157
|
+
value: e.from ? _(e.from) : ""
|
|
158
|
+
}),
|
|
159
|
+
/* @__PURE__ */ r(b, {
|
|
160
|
+
label: "To",
|
|
161
|
+
value: v(e.to)
|
|
162
|
+
}),
|
|
163
|
+
/* @__PURE__ */ r(b, {
|
|
164
|
+
label: "Cc",
|
|
165
|
+
value: v(e.cc)
|
|
166
|
+
}),
|
|
167
|
+
/* @__PURE__ */ r(b, {
|
|
168
|
+
label: "Date",
|
|
169
|
+
value: n
|
|
170
|
+
})
|
|
171
|
+
]
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/EmailViewer/AttachmentList.tsx
|
|
176
|
+
function S(e) {
|
|
177
|
+
return e < 1024 ? `${e} B` : e < 1048576 ? `${(e / 1024).toFixed(1)} KB` : `${(e / 1048576).toFixed(1)} MB`;
|
|
178
|
+
}
|
|
179
|
+
function C(e) {
|
|
180
|
+
let t = URL.createObjectURL(new Blob([e.bytes], { type: e.mimeType })), n = document.createElement("a");
|
|
181
|
+
n.href = t, n.download = e.filename, document.body.appendChild(n), n.click(), n.remove(), URL.revokeObjectURL(t);
|
|
182
|
+
}
|
|
183
|
+
function w({ attachments: e }) {
|
|
184
|
+
return e.length === 0 ? null : /* @__PURE__ */ r("div", {
|
|
185
|
+
className: "email-attachments",
|
|
186
|
+
"aria-label": "Attachments",
|
|
187
|
+
children: e.map((e, t) => /* @__PURE__ */ i("button", {
|
|
188
|
+
type: "button",
|
|
189
|
+
className: "email-attachment-chip",
|
|
190
|
+
onClick: () => C(e),
|
|
191
|
+
title: `Download ${e.filename} (${S(e.size)})`,
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ r("span", {
|
|
194
|
+
className: "email-attachment-icon",
|
|
195
|
+
children: /* @__PURE__ */ r(d, {})
|
|
196
|
+
}),
|
|
197
|
+
/* @__PURE__ */ r("span", {
|
|
198
|
+
className: "email-attachment-name",
|
|
199
|
+
children: e.filename
|
|
200
|
+
}),
|
|
201
|
+
/* @__PURE__ */ r("span", {
|
|
202
|
+
className: "email-attachment-size",
|
|
203
|
+
children: S(e.size)
|
|
204
|
+
}),
|
|
205
|
+
/* @__PURE__ */ r("span", {
|
|
206
|
+
className: "email-attachment-download",
|
|
207
|
+
children: /* @__PURE__ */ r(u, {})
|
|
208
|
+
})
|
|
209
|
+
]
|
|
210
|
+
}, `${e.filename}-${t}`))
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/EmailViewer/inlineHtml.ts
|
|
215
|
+
var T = [
|
|
216
|
+
"script",
|
|
217
|
+
"iframe",
|
|
218
|
+
"object",
|
|
219
|
+
"embed",
|
|
220
|
+
"form",
|
|
221
|
+
"base",
|
|
222
|
+
"meta",
|
|
223
|
+
"link"
|
|
224
|
+
];
|
|
225
|
+
function E(e) {
|
|
226
|
+
let t = "", n = 32768;
|
|
227
|
+
for (let r = 0; r < e.length; r += n) t += String.fromCharCode(...e.subarray(r, r + n));
|
|
228
|
+
return btoa(t);
|
|
229
|
+
}
|
|
230
|
+
function D(e, t) {
|
|
231
|
+
let n = /* @__PURE__ */ new Map();
|
|
232
|
+
for (let e of t) e.disposition === "inline" && e.contentId && n.set(e.contentId, `data:${e.mimeType};base64,${E(e.bytes)}`);
|
|
233
|
+
let r = new DOMParser().parseFromString(e, "text/html");
|
|
234
|
+
return r.querySelectorAll("[src]").forEach((e) => {
|
|
235
|
+
let t = e.getAttribute("src") ?? "";
|
|
236
|
+
if (t.toLowerCase().startsWith("cid:")) {
|
|
237
|
+
let r = t.slice(4).replace(/^<|>$/g, ""), i = n.get(r);
|
|
238
|
+
i ? e.setAttribute("src", i) : e.removeAttribute("src");
|
|
239
|
+
}
|
|
240
|
+
}), [
|
|
241
|
+
"<!doctype html><html><head><meta charset=\"utf-8\">",
|
|
242
|
+
"<meta http-equiv=\"Content-Security-Policy\" ",
|
|
243
|
+
"content=\"default-src 'none'; img-src data:; style-src 'unsafe-inline'; font-src data:;\">",
|
|
244
|
+
"<style>html,body{margin:0;padding:12px;background:#ffffff;color:#111111;",
|
|
245
|
+
"font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:13px;line-height:1.5;}",
|
|
246
|
+
"img{max-width:100%;height:auto;}a{color:#0645ad;}</style>",
|
|
247
|
+
"</head><body>",
|
|
248
|
+
a.sanitize(r.body.innerHTML, {
|
|
249
|
+
FORBID_TAGS: T,
|
|
250
|
+
FORBID_ATTR: ["ping"],
|
|
251
|
+
ALLOW_UNKNOWN_PROTOCOLS: !1
|
|
252
|
+
}),
|
|
253
|
+
"</body></html>"
|
|
254
|
+
].join("");
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/EmailViewer/EmailBody.tsx
|
|
258
|
+
function O({ email: e }) {
|
|
259
|
+
let n = t(() => D(e.html ?? "", e.attachments), [e]);
|
|
260
|
+
return /* @__PURE__ */ r("iframe", {
|
|
261
|
+
className: "email-html-frame",
|
|
262
|
+
title: "Message body",
|
|
263
|
+
sandbox: "",
|
|
264
|
+
srcDoc: n
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function k({ email: a }) {
|
|
268
|
+
let o = t(() => {
|
|
269
|
+
let e = [];
|
|
270
|
+
return a.html && e.push("html"), a.text && e.push("text"), e.push("source", "headers"), e;
|
|
271
|
+
}, [a]), [s, c] = n(o[0]);
|
|
272
|
+
e(() => c(o[0]), [o]);
|
|
273
|
+
let l = {
|
|
274
|
+
html: "HTML",
|
|
275
|
+
text: "Plain text",
|
|
276
|
+
source: "Source",
|
|
277
|
+
headers: "Headers"
|
|
278
|
+
};
|
|
279
|
+
return /* @__PURE__ */ i("div", {
|
|
280
|
+
className: "email-body",
|
|
281
|
+
children: [/* @__PURE__ */ r("div", {
|
|
282
|
+
className: "email-body-tabs",
|
|
283
|
+
role: "tablist",
|
|
284
|
+
children: o.map((e) => /* @__PURE__ */ r("button", {
|
|
285
|
+
type: "button",
|
|
286
|
+
role: "tab",
|
|
287
|
+
"aria-selected": s === e,
|
|
288
|
+
className: `email-body-tab${s === e ? " email-body-tab-active" : ""}`,
|
|
289
|
+
onClick: () => c(e),
|
|
290
|
+
children: l[e]
|
|
291
|
+
}, e))
|
|
292
|
+
}), /* @__PURE__ */ i("div", {
|
|
293
|
+
className: "email-body-content",
|
|
294
|
+
children: [
|
|
295
|
+
s === "html" && /* @__PURE__ */ r(O, { email: a }),
|
|
296
|
+
s === "text" && /* @__PURE__ */ r("pre", {
|
|
297
|
+
className: "email-body-text",
|
|
298
|
+
children: a.text
|
|
299
|
+
}),
|
|
300
|
+
s === "source" && /* @__PURE__ */ r("pre", {
|
|
301
|
+
className: "email-body-source",
|
|
302
|
+
children: a.raw
|
|
303
|
+
}),
|
|
304
|
+
s === "headers" && /* @__PURE__ */ r("table", {
|
|
305
|
+
className: "email-headers-table",
|
|
306
|
+
children: /* @__PURE__ */ r("tbody", { children: a.headers.map((e, t) => /* @__PURE__ */ i("tr", { children: [/* @__PURE__ */ r("td", {
|
|
307
|
+
className: "email-headers-key",
|
|
308
|
+
children: e.key
|
|
309
|
+
}), /* @__PURE__ */ r("td", {
|
|
310
|
+
className: "email-headers-value",
|
|
311
|
+
children: e.value
|
|
312
|
+
})] }, `${e.key}-${t}`)) })
|
|
313
|
+
})
|
|
314
|
+
]
|
|
315
|
+
})]
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/EmailViewer/EmailViewer.css?inline
|
|
320
|
+
var A = ".email-viewer{background:var(--vscode-editor-background,#1e1e1e);width:100%;height:100%;min-height:0;color:var(--vscode-foreground,#ccc);font-family:var(--vscode-font-family,\"Segoe UI\", sans-serif);flex-direction:column;font-size:13px;display:flex}.email-toolbar{border-bottom:1px solid var(--vscode-sideBar-border,#3c3c3c);flex-shrink:0;align-items:center;gap:4px;padding:8px 12px;display:flex}.email-toolbar-btn{color:var(--vscode-foreground,#ccc);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:4px;align-items:center;gap:6px;padding:4px 8px;font-size:12px;font-weight:500;display:inline-flex}.email-toolbar-btn:hover:not(:disabled){background:var(--vscode-toolbar-hoverBackground,#ffffff14)}.email-toolbar-btn:focus-visible{outline:2px solid var(--vscode-focusBorder,#75beff);outline-offset:2px}.email-toolbar-btn:disabled{opacity:.3;cursor:default}.email-toolbar-btn svg{flex-shrink:0}.email-header{border-bottom:1px solid var(--vscode-sideBar-border,#3c3c3c);flex-shrink:0;padding:12px}.email-header-subject-row{align-items:center;gap:8px;margin-bottom:8px;display:flex}.email-header-subject{color:var(--vscode-foreground,#ccc);font-size:16px;font-weight:600}.email-importance{white-space:nowrap;border-radius:4px;flex-shrink:0;align-items:center;gap:4px;padding:2px 6px;font-size:11px;font-weight:600;display:inline-flex}.email-importance svg{width:14px;height:14px}.email-importance-high{color:#febc2e;border:1px solid #febc2e80}.email-importance-low{color:var(--vscode-descriptionForeground,#858585);border:1px solid var(--vscode-sideBar-border,#3c3c3c)}.email-header-row{gap:8px;font-size:12px;line-height:1.6;display:flex}.email-header-label{width:44px;color:var(--vscode-descriptionForeground,#858585);flex-shrink:0}.email-header-value{color:var(--vscode-foreground,#ccc);word-break:break-word}.email-attachments{border-bottom:1px solid var(--vscode-sideBar-border,#3c3c3c);flex-wrap:wrap;flex-shrink:0;gap:8px;padding:8px 12px;display:flex}.email-attachment-chip{max-width:260px;color:var(--vscode-foreground,#ccc);background:var(--vscode-badge-background,#ffffff0f);border:1px solid var(--vscode-sideBar-border,#3c3c3c);cursor:pointer;border-radius:4px;align-items:center;gap:6px;padding:4px 8px;font-size:12px;display:inline-flex}.email-attachment-chip:hover{background:var(--vscode-list-hoverBackground,#ffffff0d)}.email-attachment-chip:focus-visible{outline:2px solid var(--vscode-focusBorder,#75beff);outline-offset:2px}.email-attachment-icon,.email-attachment-download{color:var(--vscode-descriptionForeground,#858585);flex-shrink:0;display:inline-flex}.email-attachment-name{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.email-attachment-size{color:var(--vscode-descriptionForeground,#858585);flex-shrink:0}.email-body{flex-direction:column;flex:1 1 0;min-height:0;display:flex}.email-body-tabs{border-bottom:1px solid var(--vscode-sideBar-border,#3c3c3c);flex-shrink:0;gap:2px;padding:0 12px;display:flex}.email-body-tab{color:var(--vscode-descriptionForeground,#858585);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:6px 12px;font-size:12px}.email-body-tab:hover{color:var(--vscode-foreground,#ccc)}.email-body-tab:focus-visible{outline:2px solid var(--vscode-focusBorder,#75beff);outline-offset:-2px}.email-body-tab-active{color:var(--vscode-foreground,#ccc);border-bottom-color:var(--vscode-focusBorder,#75beff)}.email-body-content{flex:1 1 0;min-height:0;position:relative;overflow:auto}.email-html-frame{background:#fff;border:none;width:100%;height:100%}.email-body-text,.email-body-source{white-space:pre-wrap;word-break:break-word;font-family:var(--vscode-editor-font-family,\"Consolas\", monospace);color:var(--vscode-foreground,#ccc);margin:0;padding:12px;font-size:12px}.email-headers-table{border-collapse:collapse;width:100%;font-size:12px}.email-headers-table td{vertical-align:top;border-bottom:1px solid var(--vscode-sideBar-border,#3c3c3c);padding:4px 12px}.email-headers-key{width:180px;color:var(--vscode-descriptionForeground,#858585);word-break:break-word}.email-headers-value{color:var(--vscode-foreground,#ccc);word-break:break-word;font-family:var(--vscode-editor-font-family,\"Consolas\", monospace)}";
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/EmailViewer/EmailViewer.tsx
|
|
323
|
+
if (typeof document < "u" && !document.getElementById("email-viewer-styles")) {
|
|
324
|
+
let e = document.createElement("style");
|
|
325
|
+
e.id = "email-viewer-styles", e.textContent = A, document.head.appendChild(e);
|
|
326
|
+
}
|
|
327
|
+
function j({ email: e, formatDate: t }) {
|
|
328
|
+
return /* @__PURE__ */ i("div", {
|
|
329
|
+
className: "email-viewer",
|
|
330
|
+
children: [
|
|
331
|
+
/* @__PURE__ */ r(m, {}),
|
|
332
|
+
/* @__PURE__ */ r(x, {
|
|
333
|
+
email: e,
|
|
334
|
+
formatDate: t
|
|
335
|
+
}),
|
|
336
|
+
/* @__PURE__ */ r(w, { attachments: e.attachments }),
|
|
337
|
+
/* @__PURE__ */ r(k, { email: e })
|
|
338
|
+
]
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
//#endregion
|
|
342
|
+
export { j as EmailViewer, g as formatEmailDate };
|
|
343
|
+
|
|
344
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["emailViewerStyles","DOMPurify","base","ReplyIcon","ReplyAllIcon","ForwardIcon","DownloadIcon","AttachmentIcon","PriorityHighIcon","PriorityLowIcon","EmailToolbar","pad","value","width","formatEmailDate","date","formatAddress","a","formatAddresses","list","ImportanceBadge","importance","AddressRow","label","EmailHeader","email","formatDate","formatSize","bytes","downloadAttachment","url","att","AttachmentList","attachments","i","FORBID_TAGS","bytesToBase64","binary","chunk","buildInlineHtml","cidMap","doc","rawHtml","src","el","id","mapped","HtmlBody","srcDoc","useMemo","EmailBody","tabs","active","setActive","useState","labels","tab","h","EmailViewer_default","styleEl","EmailViewer"],"sources":["../src/EmailViewer/icons.tsx","../src/EmailViewer/EmailToolbar.tsx","../src/EmailViewer/types.ts","../src/EmailViewer/EmailHeader.tsx","../src/EmailViewer/AttachmentList.tsx","../src/EmailViewer/inlineHtml.ts","../src/EmailViewer/EmailBody.tsx","../src/EmailViewer/EmailViewer.css?inline","../src/EmailViewer/EmailViewer.tsx"],"sourcesContent":["/*\n * This file is part of CassandraGargoyle Community Project\n * Licensed under the MIT License - see LICENSE file for details\n */\n\n// Monochrome stroke icons for the EmailViewer (16px, stroke inherits currentColor)\n// Follows the GUI design principles: single-color, fill:none, rounded caps\n\nimport React from 'react';\n\nconst base = {\n width: 16,\n height: 16,\n viewBox: '0 0 16 16',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 1.3,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n};\n\nexport function ReplyIcon(): JSX.Element {\n return (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M6 4 2 8l4 4\" />\n <path d=\"M2 8h6a5 5 0 0 1 5 5\" />\n </svg>\n );\n}\n\nexport function ReplyAllIcon(): JSX.Element {\n return (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M5 4 1 8l4 4\" />\n <path d=\"M9 4 5 8l4 4\" />\n <path d=\"M5 8h4a5 5 0 0 1 5 5\" />\n </svg>\n );\n}\n\nexport function ForwardIcon(): JSX.Element {\n return (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M10 4l4 4-4 4\" />\n <path d=\"M14 8H8a5 5 0 0 0-5 5\" />\n </svg>\n );\n}\n\nexport function DownloadIcon(): JSX.Element {\n return (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M8 2v8\" />\n <path d=\"M4.5 7 8 10.5 11.5 7\" />\n <path d=\"M3 13h10\" />\n </svg>\n );\n}\n\nexport function AttachmentIcon(): JSX.Element {\n return (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M12.5 6.5 7 12a3 3 0 0 1-4-4l5.5-5.5a2 2 0 0 1 3 3L6 11\" />\n </svg>\n );\n}\n\n// High importance: double chevron up\nexport function PriorityHighIcon(): JSX.Element {\n return (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M4 9l4-4 4 4\" />\n <path d=\"M4 12l4-4 4 4\" />\n </svg>\n );\n}\n\n// Low importance: single chevron down\nexport function PriorityLowIcon(): JSX.Element {\n return (\n <svg {...base} aria-hidden=\"true\">\n <path d=\"M4 6l4 4 4-4\" />\n </svg>\n );\n}\n","/*\n * This file is part of CassandraGargoyle Community Project\n * Licensed under the MIT License - see LICENSE file for details\n */\n\n// EmailViewer toolbar: Reply / Reply-All / Forward actions\n// Composing is out of scope for issue 075, so the buttons render disabled (future work)\n\nimport React from 'react';\nimport { ReplyIcon, ReplyAllIcon, ForwardIcon } from './icons';\n\nexport function EmailToolbar(): JSX.Element {\n return (\n <div className=\"email-toolbar\" role=\"toolbar\" aria-label=\"Message actions\">\n <button className=\"email-toolbar-btn\" type=\"button\" disabled title=\"Reply (coming soon)\">\n <ReplyIcon />\n <span>Reply</span>\n </button>\n <button className=\"email-toolbar-btn\" type=\"button\" disabled title=\"Reply all (coming soon)\">\n <ReplyAllIcon />\n <span>Reply all</span>\n </button>\n <button className=\"email-toolbar-btn\" type=\"button\" disabled title=\"Forward (coming soon)\">\n <ForwardIcon />\n <span>Forward</span>\n </button>\n </div>\n );\n}\n","/*\n * This file is part of CassandraGargoyle Community Project\n * Licensed under the MIT License - see LICENSE file for details\n */\n\n// The message shape EmailViewer renders, independent of the format it was read from\n// A reader (.eml, .msg, ...) produces it; the viewer never sees MIME or MAPI\n\n/** A single mailbox: an address plus an optional display name */\nexport interface EmailAddress {\n name?: string;\n address: string;\n}\n\n/** One attachment or inline part, carried as raw bytes so the viewer owns rendering */\nexport interface EmailAttachment {\n filename: string;\n mimeType: string;\n size: number;\n /** Content-ID (angle brackets stripped) used to resolve inline cid: references */\n contentId?: string;\n disposition: 'attachment' | 'inline';\n bytes: Uint8Array;\n}\n\n/** Message importance; 'normal' renders without a badge */\nexport type EmailImportance = 'high' | 'normal' | 'low';\n\n/**\n * Normalized message the viewer renders. `html` is the message's raw HTML — the viewer\n * sanitizes it and resolves cid: references itself, so a reader can stay DOM-free.\n */\nexport interface EmailMessage {\n subject: string;\n from?: EmailAddress;\n to: EmailAddress[];\n cc: EmailAddress[];\n date?: Date;\n importance: EmailImportance;\n html?: string;\n text?: string;\n attachments: EmailAttachment[];\n headers: Array<{ key: string; value: string }>;\n /** Original source shown on the Source tab */\n raw: string;\n}\n\n/** Turns the message date into the text of the Date row */\nexport type EmailDateFormatter = (date: Date) => string;\n\n/** Zero-pad a positive integer to the given width */\nfunction pad(value: number, width = 2): string {\n return String(value).padStart(width, '0');\n}\n\n/**\n * Default date format: local time as `YYYY-MM-DD HH:MM:SS`.\n *\n * Unambiguous in every region and never drops the seconds a mail Date: header carries.\n * An invalid Date yields an empty string, so the row is omitted rather than showing `NaN`.\n */\nexport function formatEmailDate(date: Date): string {\n if (Number.isNaN(date.getTime())) return '';\n const ymd = `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;\n const hms = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;\n return `${ymd} ${hms}`;\n}\n","/*\n * This file is part of CassandraGargoyle Community Project\n * Licensed under the MIT License - see LICENSE file for details\n */\n\n// EmailViewer header block: subject plus From / To / Cc / Date rows\n// Renders the normalized EmailMessage fields; omits empty recipient rows\n\nimport React from 'react';\nimport { formatEmailDate, type EmailAddress, type EmailDateFormatter, type EmailMessage } from './types';\nimport { PriorityHighIcon, PriorityLowIcon } from './icons';\n\n// Render an address as \"Name <addr>\" or just the address when unnamed\nfunction formatAddress(a: EmailAddress): string {\n return a.name ? `${a.name} <${a.address}>` : a.address;\n}\n\nfunction formatAddresses(list: EmailAddress[]): string {\n return list.map(formatAddress).join(', ');\n}\n\n// Badge shown next to the subject for non-normal importance; normal renders nothing\nfunction ImportanceBadge({ importance }: { importance: EmailMessage['importance'] }): JSX.Element | null {\n if (importance === 'high') {\n return (\n <span className=\"email-importance email-importance-high\" title=\"High importance\">\n <PriorityHighIcon />\n High priority\n </span>\n );\n }\n if (importance === 'low') {\n return (\n <span className=\"email-importance email-importance-low\" title=\"Low importance\">\n <PriorityLowIcon />\n Low priority\n </span>\n );\n }\n return null;\n}\n\nfunction AddressRow({ label, value }: { label: string; value: string }): JSX.Element | null {\n if (!value) return null;\n return (\n <div className=\"email-header-row\">\n <span className=\"email-header-label\">{label}</span>\n <span className=\"email-header-value\">{value}</span>\n </div>\n );\n}\n\nexport function EmailHeader({ email, formatDate = formatEmailDate }: { email: EmailMessage; formatDate?: EmailDateFormatter }): JSX.Element {\n const date = email.date ? formatDate(email.date) : '';\n return (\n <div className=\"email-header\">\n <div className=\"email-header-subject-row\">\n <span className=\"email-header-subject\">{email.subject}</span>\n <ImportanceBadge importance={email.importance} />\n </div>\n <AddressRow label=\"From\" value={email.from ? formatAddress(email.from) : ''} />\n <AddressRow label=\"To\" value={formatAddresses(email.to)} />\n <AddressRow label=\"Cc\" value={formatAddresses(email.cc)} />\n <AddressRow label=\"Date\" value={date} />\n </div>\n );\n}\n","/*\n * This file is part of CassandraGargoyle Community Project\n * Licensed under the MIT License - see LICENSE file for details\n */\n\n// EmailViewer attachment strip: one chip per attachment with a download action\n// Attachment bytes are never executed — download only, no auto-open (issue 075 security)\n\nimport React from 'react';\nimport type { EmailAttachment } from './types';\nimport { AttachmentIcon, DownloadIcon } from './icons';\n\n// Human-readable byte size (B / KB / MB) for the chip label\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// Save an attachment's raw bytes via a transient object URL, revoked right after the click\nfunction downloadAttachment(att: EmailAttachment): void {\n const url = URL.createObjectURL(new Blob([att.bytes as BlobPart], { type: att.mimeType }));\n const a = document.createElement('a');\n a.href = url;\n a.download = att.filename;\n document.body.appendChild(a);\n a.click();\n a.remove();\n URL.revokeObjectURL(url);\n}\n\nexport function AttachmentList({ attachments }: { attachments: EmailAttachment[] }): JSX.Element | null {\n if (attachments.length === 0) return null;\n return (\n <div className=\"email-attachments\" aria-label=\"Attachments\">\n {attachments.map((att, i) => (\n <button\n key={`${att.filename}-${i}`}\n type=\"button\"\n className=\"email-attachment-chip\"\n onClick={() => downloadAttachment(att)}\n title={`Download ${att.filename} (${formatSize(att.size)})`}\n >\n <span className=\"email-attachment-icon\">\n <AttachmentIcon />\n </span>\n <span className=\"email-attachment-name\">{att.filename}</span>\n <span className=\"email-attachment-size\">{formatSize(att.size)}</span>\n <span className=\"email-attachment-download\">\n <DownloadIcon />\n </span>\n </button>\n ))}\n </div>\n );\n}\n","/*\n * This file is part of CassandraGargoyle Community Project\n * Licensed under the MIT License - see LICENSE file for details\n */\n\n// Turns a message's raw HTML into a safe sandboxed-iframe document: resolves inline cid:\n// images to data: URIs, sanitizes with DOMPurify, and blocks remote content via CSP (issue 075)\n\nimport DOMPurify from 'dompurify';\nimport type { EmailAttachment } from './types';\n\n// Tags never useful in a mail body and risky to keep: scripts, framing, plugins, forms, and\n// resource-loading head elements (remote CSS/base). DOMPurify strips scripts/handlers anyway;\n// this is defense in depth alongside the iframe sandbox and CSP.\nconst FORBID_TAGS = ['script', 'iframe', 'object', 'embed', 'form', 'base', 'meta', 'link'];\n\n// Base64-encode raw bytes (chunked to avoid call-stack limits on large inputs)\nfunction bytesToBase64(bytes: Uint8Array): string {\n let binary = '';\n const chunk = 0x8000;\n for (let i = 0; i < bytes.length; i += chunk) {\n binary += String.fromCharCode(...bytes.subarray(i, i + chunk));\n }\n return btoa(binary);\n}\n\n/**\n * Build the sandboxed-iframe document for a mail's HTML body. Inline cid: references are\n * rewritten to data: URIs built from the matching inline attachments — data: (not blob:)\n * because a fully sandboxed iframe has an opaque origin and cannot load the parent's blob:\n * URLs. Everything else is sanitized and wrapped in a restrictive CSP that permits only\n * data: images and inline styles, so remote content (tracking pixels, external CSS) is\n * blocked by default.\n */\nexport function buildInlineHtml(rawHtml: string, attachments: EmailAttachment[]): string {\n const cidMap = new Map<string, string>();\n for (const att of attachments) {\n if (att.disposition === 'inline' && att.contentId) {\n cidMap.set(att.contentId, `data:${att.mimeType};base64,${bytesToBase64(att.bytes)}`);\n }\n }\n\n // Rewrite cid: sources to their data: URI before sanitizing\n const doc = new DOMParser().parseFromString(rawHtml, 'text/html');\n doc.querySelectorAll('[src]').forEach((el) => {\n const src = el.getAttribute('src') ?? '';\n if (src.toLowerCase().startsWith('cid:')) {\n const id = src.slice(4).replace(/^<|>$/g, '');\n const mapped = cidMap.get(id);\n if (mapped) el.setAttribute('src', mapped);\n else el.removeAttribute('src');\n }\n });\n\n const clean = DOMPurify.sanitize(doc.body.innerHTML, {\n FORBID_TAGS,\n FORBID_ATTR: ['ping'],\n ALLOW_UNKNOWN_PROTOCOLS: false,\n });\n\n return [\n '<!doctype html><html><head><meta charset=\"utf-8\">',\n '<meta http-equiv=\"Content-Security-Policy\" ',\n 'content=\"default-src \\'none\\'; img-src data:; style-src \\'unsafe-inline\\'; font-src data:;\">',\n '<style>html,body{margin:0;padding:12px;background:#ffffff;color:#111111;',\n \"font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:13px;line-height:1.5;}\",\n 'img{max-width:100%;height:auto;}a{color:#0645ad;}</style>',\n '</head><body>',\n clean,\n '</body></html>',\n ].join('');\n}\n","/*\n * This file is part of CassandraGargoyle Community Project\n * Licensed under the MIT License - see LICENSE file for details\n */\n\n// EmailViewer tabbed body: HTML (sandboxed iframe) / Plain text / Source (raw MIME) / Headers\n// Only the tabs backed by content are shown; the HTML tab is preferred when present\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport type { EmailMessage } from './types';\nimport { buildInlineHtml } from './inlineHtml';\n\ntype BodyTab = 'html' | 'text' | 'source' | 'headers';\n\n// Renders the sanitized HTML body in a fully sandboxed iframe (no scripts, no same-origin).\n// Inline images are embedded as data: URIs, so nothing outside the srcDoc is loaded.\nfunction HtmlBody({ email }: { email: EmailMessage }): JSX.Element {\n const srcDoc = useMemo(() => buildInlineHtml(email.html ?? '', email.attachments), [email]);\n return <iframe className=\"email-html-frame\" title=\"Message body\" sandbox=\"\" srcDoc={srcDoc} />;\n}\n\nexport function EmailBody({ email }: { email: EmailMessage }): JSX.Element {\n // Available tabs, in display order — HTML first when the message has an HTML part\n const tabs = useMemo<BodyTab[]>(() => {\n const list: BodyTab[] = [];\n if (email.html) list.push('html');\n if (email.text) list.push('text');\n list.push('source', 'headers');\n return list;\n }, [email]);\n\n const [active, setActive] = useState<BodyTab>(tabs[0]);\n\n // Reset to the first available tab whenever a different message is shown\n useEffect(() => setActive(tabs[0]), [tabs]);\n\n const labels: Record<BodyTab, string> = {\n html: 'HTML',\n text: 'Plain text',\n source: 'Source',\n headers: 'Headers',\n };\n\n return (\n <div className=\"email-body\">\n <div className=\"email-body-tabs\" role=\"tablist\">\n {tabs.map((tab) => (\n <button\n key={tab}\n type=\"button\"\n role=\"tab\"\n aria-selected={active === tab}\n className={`email-body-tab${active === tab ? ' email-body-tab-active' : ''}`}\n onClick={() => setActive(tab)}\n >\n {labels[tab]}\n </button>\n ))}\n </div>\n <div className=\"email-body-content\">\n {active === 'html' && <HtmlBody email={email} />}\n {active === 'text' && <pre className=\"email-body-text\">{email.text}</pre>}\n {active === 'source' && <pre className=\"email-body-source\">{email.raw}</pre>}\n {active === 'headers' && (\n <table className=\"email-headers-table\">\n <tbody>\n {email.headers.map((h, i) => (\n <tr key={`${h.key}-${i}`}>\n <td className=\"email-headers-key\">{h.key}</td>\n <td className=\"email-headers-value\">{h.value}</td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </div>\n </div>\n );\n}\n","/*\n * This file is part of CassandraGargoyle Community Project\n * Licensed under the MIT License - see LICENSE file for details\n */\n\n/*\n * EmailViewer styles: monochrome icons, theme CSS variables, 4px spacing grid\n * (per docs/architecture/GUI-DESIGN-PRINCIPLES.md). Fills its host container.\n */\n.email-viewer {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--vscode-editor-background, #1e1e1e);\n color: var(--vscode-foreground, #cccccc);\n font-family: var(--vscode-font-family, 'Segoe UI', sans-serif);\n font-size: 13px;\n}\n\n/* Toolbar */\n.email-toolbar {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-shrink: 0;\n padding: 8px 12px;\n border-bottom: 1px solid var(--vscode-sideBar-border, #3c3c3c);\n}\n\n.email-toolbar-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n font-size: 12px;\n font-weight: 500;\n color: var(--vscode-foreground, #cccccc);\n background: transparent;\n border: 1px solid transparent;\n border-radius: 4px;\n cursor: pointer;\n}\n\n.email-toolbar-btn:hover:not(:disabled) {\n background: var(--vscode-toolbar-hoverBackground, rgba(255, 255, 255, 0.08));\n}\n\n.email-toolbar-btn:focus-visible {\n outline: 2px solid var(--vscode-focusBorder, #75beff);\n outline-offset: 2px;\n}\n\n.email-toolbar-btn:disabled {\n opacity: 0.3;\n cursor: default;\n}\n\n.email-toolbar-btn svg {\n flex-shrink: 0;\n}\n\n/* Header block */\n.email-header {\n flex-shrink: 0;\n padding: 12px;\n border-bottom: 1px solid var(--vscode-sideBar-border, #3c3c3c);\n}\n\n.email-header-subject-row {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 8px;\n}\n\n.email-header-subject {\n font-size: 16px;\n font-weight: 600;\n color: var(--vscode-foreground, #cccccc);\n}\n\n/* Importance badge — a small status indicator, so semantic color is allowed here */\n.email-importance {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex-shrink: 0;\n padding: 2px 6px;\n font-size: 11px;\n font-weight: 600;\n border-radius: 4px;\n white-space: nowrap;\n}\n\n.email-importance svg {\n width: 14px;\n height: 14px;\n}\n\n.email-importance-high {\n color: #febc2e;\n border: 1px solid rgba(254, 188, 46, 0.5);\n}\n\n.email-importance-low {\n color: var(--vscode-descriptionForeground, #858585);\n border: 1px solid var(--vscode-sideBar-border, #3c3c3c);\n}\n\n.email-header-row {\n display: flex;\n gap: 8px;\n font-size: 12px;\n line-height: 1.6;\n}\n\n.email-header-label {\n flex-shrink: 0;\n width: 44px;\n color: var(--vscode-descriptionForeground, #858585);\n}\n\n.email-header-value {\n color: var(--vscode-foreground, #cccccc);\n word-break: break-word;\n}\n\n/* Attachment chips */\n.email-attachments {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n flex-shrink: 0;\n padding: 8px 12px;\n border-bottom: 1px solid var(--vscode-sideBar-border, #3c3c3c);\n}\n\n.email-attachment-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 260px;\n padding: 4px 8px;\n font-size: 12px;\n color: var(--vscode-foreground, #cccccc);\n background: var(--vscode-badge-background, rgba(255, 255, 255, 0.06));\n border: 1px solid var(--vscode-sideBar-border, #3c3c3c);\n border-radius: 4px;\n cursor: pointer;\n}\n\n.email-attachment-chip:hover {\n background: var(--vscode-list-hoverBackground, rgba(255, 255, 255, 0.05));\n}\n\n.email-attachment-chip:focus-visible {\n outline: 2px solid var(--vscode-focusBorder, #75beff);\n outline-offset: 2px;\n}\n\n.email-attachment-icon,\n.email-attachment-download {\n display: inline-flex;\n flex-shrink: 0;\n color: var(--vscode-descriptionForeground, #858585);\n}\n\n.email-attachment-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.email-attachment-size {\n flex-shrink: 0;\n color: var(--vscode-descriptionForeground, #858585);\n}\n\n/* Tabbed body */\n.email-body {\n flex: 1 1 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.email-body-tabs {\n display: flex;\n gap: 2px;\n flex-shrink: 0;\n padding: 0 12px;\n border-bottom: 1px solid var(--vscode-sideBar-border, #3c3c3c);\n}\n\n.email-body-tab {\n padding: 6px 12px;\n font-size: 12px;\n color: var(--vscode-descriptionForeground, #858585);\n background: transparent;\n border: none;\n border-bottom: 2px solid transparent;\n cursor: pointer;\n}\n\n.email-body-tab:hover {\n color: var(--vscode-foreground, #cccccc);\n}\n\n.email-body-tab:focus-visible {\n outline: 2px solid var(--vscode-focusBorder, #75beff);\n outline-offset: -2px;\n}\n\n.email-body-tab-active {\n color: var(--vscode-foreground, #cccccc);\n border-bottom-color: var(--vscode-focusBorder, #75beff);\n}\n\n.email-body-content {\n flex: 1 1 0;\n min-height: 0;\n overflow: auto;\n position: relative;\n}\n\n/* HTML body renders in an isolated, sandboxed iframe (its own document/styles) */\n.email-html-frame {\n width: 100%;\n height: 100%;\n border: none;\n background: #ffffff;\n}\n\n.email-body-text,\n.email-body-source {\n margin: 0;\n padding: 12px;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--vscode-editor-font-family, 'Consolas', monospace);\n font-size: 12px;\n color: var(--vscode-foreground, #cccccc);\n}\n\n.email-headers-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 12px;\n}\n\n.email-headers-table td {\n padding: 4px 12px;\n vertical-align: top;\n border-bottom: 1px solid var(--vscode-sideBar-border, #3c3c3c);\n}\n\n.email-headers-key {\n width: 180px;\n color: var(--vscode-descriptionForeground, #858585);\n word-break: break-word;\n}\n\n.email-headers-value {\n color: var(--vscode-foreground, #cccccc);\n word-break: break-word;\n font-family: var(--vscode-editor-font-family, 'Consolas', monospace);\n}\n","/*\n * This file is part of CassandraGargoyle Community Project\n * Licensed under the MIT License - see LICENSE file for details\n */\n\n// Read-only e-mail viewer: renders a format-agnostic EmailMessage (from .eml, .msg or any reader)\n// Layout: toolbar, header, attachment chips, tabbed body — fills its host container\n\nimport React from 'react';\nimport type { EmailDateFormatter, EmailMessage } from './types';\nimport { EmailToolbar } from './EmailToolbar';\nimport { EmailHeader } from './EmailHeader';\nimport { AttachmentList } from './AttachmentList';\nimport { EmailBody } from './EmailBody';\nimport emailViewerStyles from './EmailViewer.css';\n\n// Inject styles once when bundled with the 'text' loader (single-file bundle, the\n// published package). Under the 'css' loader the import resolves to a non-string and\n// a sibling stylesheet is emitted instead, so this injection is skipped.\nif (\n typeof document !== 'undefined' &&\n typeof emailViewerStyles === 'string' &&\n emailViewerStyles &&\n !document.getElementById('email-viewer-styles')\n) {\n const styleEl = document.createElement('style');\n styleEl.id = 'email-viewer-styles';\n styleEl.textContent = emailViewerStyles;\n document.head.appendChild(styleEl);\n}\n\nexport interface EmailViewerProps {\n // Normalized message to display; produced by a reader independent of format\n email: EmailMessage;\n // Formats the Date row. The host decides how dates look — typically the operating\n // system's own pattern, which a webview or an Electron window cannot read from its\n // environment. Omitted means the fixed `YYYY-MM-DD HH:MM:SS` local-time pattern\n formatDate?: EmailDateFormatter;\n}\n\nexport function EmailViewer({ email, formatDate }: EmailViewerProps): JSX.Element {\n return (\n <div className=\"email-viewer\">\n <EmailToolbar />\n <EmailHeader email={email} formatDate={formatDate} />\n <AttachmentList attachments={email.attachments} />\n <EmailBody email={email} />\n </div>\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cassandragargoyle/react-viewer",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React viewers for document formats, shared across the CassandraGargoyle ecosystem",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"react",
|
|
9
|
+
"viewer",
|
|
10
|
+
"email",
|
|
11
|
+
"typescript",
|
|
12
|
+
"vscode-webview",
|
|
13
|
+
"cassandragargoyle"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/cassandragargoyle/react-viewer#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/cassandragargoyle/react-viewer/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/cassandragargoyle/react-viewer.git"
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "vite build",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"import": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"registry": "https://registry.npmjs.org",
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
50
|
+
"@testing-library/react": "^16.1.0",
|
|
51
|
+
"@types/react": "^19.0.0",
|
|
52
|
+
"@types/react-dom": "^19.0.0",
|
|
53
|
+
"dompurify": "^3.2.7",
|
|
54
|
+
"jsdom": "^25.0.1",
|
|
55
|
+
"react": "^19.0.0",
|
|
56
|
+
"react-dom": "^19.0.0",
|
|
57
|
+
"typescript": "^5.9.3",
|
|
58
|
+
"vite": "^8.1.4",
|
|
59
|
+
"vite-plugin-dts": "^5.0.3",
|
|
60
|
+
"vitest": "^4.1.10"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"dompurify": "^3.2.0",
|
|
64
|
+
"react": "^19.0.0",
|
|
65
|
+
"react-dom": "^19.0.0"
|
|
66
|
+
}
|
|
67
|
+
}
|