@remit/ui 0.0.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/package.json +50 -0
- package/src/components/address-display.render.test.ts +93 -0
- package/src/components/address-display.stories.tsx +55 -0
- package/src/components/address-display.tsx +132 -0
- package/src/components/address-tag.render.test.ts +39 -0
- package/src/components/address-tag.stories.tsx +41 -0
- package/src/components/address-tag.tsx +38 -0
- package/src/components/app-password-hint.render.test.ts +23 -0
- package/src/components/app-password-hint.stories.tsx +19 -0
- package/src/components/app-password-hint.tsx +24 -0
- package/src/components/app-shell-slotted.render.test.ts +128 -0
- package/src/components/app-shell-slotted.tsx +267 -0
- package/src/components/app-shell-types.ts +328 -0
- package/src/components/app-shell.render.test.ts +415 -0
- package/src/components/app-shell.tsx +277 -0
- package/src/components/auth-card.render.test.ts +24 -0
- package/src/components/auth-card.stories.tsx +50 -0
- package/src/components/auth-card.tsx +32 -0
- package/src/components/auth-footer.render.test.ts +17 -0
- package/src/components/auth-footer.stories.tsx +23 -0
- package/src/components/auth-footer.tsx +14 -0
- package/src/components/auth-hero.render.test.ts +31 -0
- package/src/components/auth-hero.stories.tsx +19 -0
- package/src/components/auth-hero.tsx +41 -0
- package/src/components/auto-moved-badge.render.test.ts +42 -0
- package/src/components/auto-moved-badge.stories.tsx +48 -0
- package/src/components/auto-moved-badge.tsx +57 -0
- package/src/components/avatar.tsx +60 -0
- package/src/components/badge.tsx +49 -0
- package/src/components/banner.render.test.ts +58 -0
- package/src/components/banner.stories.tsx +92 -0
- package/src/components/banner.tsx +81 -0
- package/src/components/bottom-sheet.render.test.ts +48 -0
- package/src/components/bottom-sheet.stories.tsx +71 -0
- package/src/components/bottom-sheet.tsx +146 -0
- package/src/components/brief-section.render.test.ts +98 -0
- package/src/components/brief-section.stories.tsx +68 -0
- package/src/components/brief-section.tsx +102 -0
- package/src/components/brief-sections.render.test.ts +72 -0
- package/src/components/brief-sections.stories.tsx +178 -0
- package/src/components/brief-sections.tsx +201 -0
- package/src/components/button.tsx +48 -0
- package/src/components/card.tsx +48 -0
- package/src/components/category-badge.render.test.ts +40 -0
- package/src/components/category-badge.stories.tsx +37 -0
- package/src/components/category-badge.tsx +71 -0
- package/src/components/checkbox.render.test.ts +65 -0
- package/src/components/checkbox.stories.tsx +77 -0
- package/src/components/checkbox.tsx +78 -0
- package/src/components/compose-action-bar.render.test.ts +47 -0
- package/src/components/compose-action-bar.stories.tsx +35 -0
- package/src/components/compose-action-bar.tsx +95 -0
- package/src/components/compose-form-shell.render.test.ts +54 -0
- package/src/components/compose-form-shell.stories.tsx +96 -0
- package/src/components/compose-form-shell.tsx +49 -0
- package/src/components/danger-zone-section.tsx +36 -0
- package/src/components/dialog.render.test.ts +55 -0
- package/src/components/dialog.tsx +104 -0
- package/src/components/email-frame-css.ts +137 -0
- package/src/components/field-label.render.test.ts +16 -0
- package/src/components/field-label.tsx +19 -0
- package/src/components/filter-sheet.render.test.ts +76 -0
- package/src/components/filter-sheet.stories.tsx +146 -0
- package/src/components/filter-sheet.tsx +316 -0
- package/src/components/folder-role.tsx +66 -0
- package/src/components/input.tsx +44 -0
- package/src/components/intelligence-panel.stories.tsx +149 -0
- package/src/components/intelligence-panel.tsx +546 -0
- package/src/components/isolated-email-frame.render.test.ts +147 -0
- package/src/components/isolated-email-frame.stories.tsx +192 -0
- package/src/components/isolated-email-frame.tsx +265 -0
- package/src/components/kbd.tsx +17 -0
- package/src/components/keyboard-hint-bar.tsx +58 -0
- package/src/components/list-item.tsx +39 -0
- package/src/components/mail-action-toolbar.render.test.ts +43 -0
- package/src/components/mail-action-toolbar.tsx +180 -0
- package/src/components/mail-header.render.test.ts +69 -0
- package/src/components/mail-header.stories.tsx +233 -0
- package/src/components/mail-header.tsx +123 -0
- package/src/components/message-body-view.render.test.ts +59 -0
- package/src/components/message-body-view.stories.tsx +146 -0
- package/src/components/message-body-view.tsx +150 -0
- package/src/components/message-header.render.test.ts +58 -0
- package/src/components/message-header.stories.tsx +79 -0
- package/src/components/message-header.tsx +59 -0
- package/src/components/message-list-pane.render.test.ts +136 -0
- package/src/components/message-list-pane.stories.tsx +246 -0
- package/src/components/message-list-pane.tsx +248 -0
- package/src/components/message-list-state.render.test.ts +72 -0
- package/src/components/message-list-state.tsx +97 -0
- package/src/components/message-row.render.test.ts +48 -0
- package/src/components/message-row.stories.tsx +121 -0
- package/src/components/message-row.tsx +231 -0
- package/src/components/mobile-message-action-bar.render.test.ts +87 -0
- package/src/components/mobile-message-action-bar.stories.tsx +57 -0
- package/src/components/mobile-message-action-bar.tsx +175 -0
- package/src/components/mobile-reading-pane.render.test.ts +102 -0
- package/src/components/mobile-reading-pane.tsx +188 -0
- package/src/components/mobile-search-view.stories.tsx +247 -0
- package/src/components/mobile-search-view.tsx +100 -0
- package/src/components/move-mailbox-picker.render.test.ts +113 -0
- package/src/components/move-mailbox-picker.stories.tsx +76 -0
- package/src/components/move-mailbox-picker.tsx +324 -0
- package/src/components/nav-sidebar.render.test.ts +351 -0
- package/src/components/nav-sidebar.stories.tsx +216 -0
- package/src/components/nav-sidebar.tsx +625 -0
- package/src/components/outbox-row.render.test.ts +61 -0
- package/src/components/outbox-row.stories.tsx +53 -0
- package/src/components/outbox-row.tsx +120 -0
- package/src/components/outbox-status-badge.tsx +60 -0
- package/src/components/pane-layout.render.test.ts +105 -0
- package/src/components/popover-menu.render.test.ts +32 -0
- package/src/components/popover-menu.stories.tsx +57 -0
- package/src/components/popover-menu.tsx +110 -0
- package/src/components/primitives.stories.tsx +173 -0
- package/src/components/pull-to-refresh.render.test.ts +46 -0
- package/src/components/pull-to-refresh.stories.tsx +71 -0
- package/src/components/pull-to-refresh.tsx +54 -0
- package/src/components/quoted-text.render.test.ts +39 -0
- package/src/components/quoted-text.stories.tsx +67 -0
- package/src/components/quoted-text.tsx +67 -0
- package/src/components/reading-pane-empty.tsx +44 -0
- package/src/components/reading-pane.render.test.ts +148 -0
- package/src/components/reading-pane.stories.tsx +194 -0
- package/src/components/reading-pane.tsx +351 -0
- package/src/components/rescue-banner.render.test.ts +32 -0
- package/src/components/rescue-banner.stories.tsx +31 -0
- package/src/components/rescue-banner.tsx +50 -0
- package/src/components/rescue-candidate-row.render.test.ts +68 -0
- package/src/components/rescue-candidate-row.stories.tsx +108 -0
- package/src/components/rescue-candidate-row.tsx +94 -0
- package/src/components/rescue-from-spam-flow.render.test.ts +100 -0
- package/src/components/rescue-from-spam-flow.stories.tsx +92 -0
- package/src/components/rescue-from-spam-flow.tsx +342 -0
- package/src/components/resizable.tsx +55 -0
- package/src/components/role-appointment-list.render.test.ts +97 -0
- package/src/components/role-appointment-list.stories.tsx +138 -0
- package/src/components/role-appointment-list.tsx +227 -0
- package/src/components/row-actions.render.test.ts +83 -0
- package/src/components/row-actions.stories.tsx +87 -0
- package/src/components/row-actions.tsx +139 -0
- package/src/components/search-bar.render.test.ts +55 -0
- package/src/components/search-bar.stories.tsx +46 -0
- package/src/components/search-bar.tsx +118 -0
- package/src/components/search-result-row.tsx +139 -0
- package/src/components/search-results.render.test.ts +89 -0
- package/src/components/search-results.stories.tsx +126 -0
- package/src/components/search-results.tsx +215 -0
- package/src/components/search-token-chip.render.test.ts +42 -0
- package/src/components/search-token-chip.stories.tsx +42 -0
- package/src/components/search-token-chip.tsx +66 -0
- package/src/components/security-select.render.test.ts +33 -0
- package/src/components/security-select.tsx +53 -0
- package/src/components/segmented-control.tsx +70 -0
- package/src/components/select.tsx +33 -0
- package/src/components/selection-top-bar.render.test.ts +67 -0
- package/src/components/selection-top-bar.stories.tsx +39 -0
- package/src/components/selection-top-bar.tsx +95 -0
- package/src/components/sender-group-switch.tsx +63 -0
- package/src/components/sender-trust-indicator.render.test.ts +67 -0
- package/src/components/sender-trust-indicator.stories.tsx +58 -0
- package/src/components/sender-trust-indicator.tsx +87 -0
- package/src/components/settings-primitives.render.test.ts +160 -0
- package/src/components/settings-primitives.stories.tsx +80 -0
- package/src/components/settings-screen.tsx +354 -0
- package/src/components/swipeable-row.render.test.ts +106 -0
- package/src/components/swipeable-row.stories.tsx +117 -0
- package/src/components/swipeable-row.tsx +268 -0
- package/src/components/touch-list-body.render.test.ts +66 -0
- package/src/components/touch-list-body.stories.tsx +76 -0
- package/src/components/touch-list.tsx +87 -0
- package/src/components/wizard.render.test.ts +49 -0
- package/src/components/wizard.stories.tsx +113 -0
- package/src/components/wizard.tsx +321 -0
- package/src/filter-presets.test.ts +93 -0
- package/src/filter-presets.ts +101 -0
- package/src/index.ts +378 -0
- package/src/lib/cid-resolver.ts +35 -0
- package/src/lib/cn.ts +7 -0
- package/src/lib/email-layout-clamp.test.ts +109 -0
- package/src/lib/email-layout-clamp.ts +64 -0
- package/src/lib/email-render-treatment.test.ts +82 -0
- package/src/lib/email-render-treatment.ts +53 -0
- package/src/lib/email-sanitizer.test.ts +362 -0
- package/src/lib/email-sanitizer.ts +298 -0
- package/src/lib/row-keyboard.test.ts +41 -0
- package/src/lib/row-keyboard.ts +20 -0
- package/src/tokens.css +217 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AddressDisplay,
|
|
3
|
+
type AddressDisplayProps,
|
|
4
|
+
AddressList,
|
|
5
|
+
type AddressListProps,
|
|
6
|
+
type EnvelopeAddress,
|
|
7
|
+
} from "./components/address-display.js";
|
|
8
|
+
export { AddressTag, type AddressTagProps } from "./components/address-tag.js";
|
|
9
|
+
export {
|
|
10
|
+
AppPasswordHint,
|
|
11
|
+
type AppPasswordHintProps,
|
|
12
|
+
} from "./components/app-password-hint.js";
|
|
13
|
+
export { AppShell } from "./components/app-shell.js";
|
|
14
|
+
export {
|
|
15
|
+
type AppShellLayoutContext,
|
|
16
|
+
AppShellSlotted,
|
|
17
|
+
type AppShellSlottedProps,
|
|
18
|
+
useAppShellLayout,
|
|
19
|
+
} from "./components/app-shell-slotted.js";
|
|
20
|
+
export {
|
|
21
|
+
type AppShellProps,
|
|
22
|
+
type BriefCategoryFilter,
|
|
23
|
+
briefCategories,
|
|
24
|
+
categoryTone,
|
|
25
|
+
type Density,
|
|
26
|
+
INTELLIGENCE_MIN_WIDTH,
|
|
27
|
+
type NarrowView,
|
|
28
|
+
type NavAccount,
|
|
29
|
+
type NavAccountStatus,
|
|
30
|
+
type NavLinkComponent,
|
|
31
|
+
type NavLinkRenderProps,
|
|
32
|
+
type NavMailbox,
|
|
33
|
+
type NavMailboxRole,
|
|
34
|
+
type PaneLayout,
|
|
35
|
+
READING_PANE_MIN_WIDTH,
|
|
36
|
+
resolvePaneLayout,
|
|
37
|
+
type ThreadCategory,
|
|
38
|
+
type ThreadData,
|
|
39
|
+
type ThreadMessageData,
|
|
40
|
+
type ThreadRowData,
|
|
41
|
+
type ThreadSection,
|
|
42
|
+
type TouchSeed,
|
|
43
|
+
useContainerWidth,
|
|
44
|
+
} from "./components/app-shell-types.js";
|
|
45
|
+
export { AuthCard, type AuthCardProps } from "./components/auth-card.js";
|
|
46
|
+
export {
|
|
47
|
+
AuthFooter,
|
|
48
|
+
type AuthFooterProps,
|
|
49
|
+
COGNITO_FOOTER_NOTE,
|
|
50
|
+
} from "./components/auth-footer.js";
|
|
51
|
+
export { AuthHero, type AuthHeroProps } from "./components/auth-hero.js";
|
|
52
|
+
export {
|
|
53
|
+
AutoMovedBadge,
|
|
54
|
+
type AutoMovedBadgeProps,
|
|
55
|
+
} from "./components/auto-moved-badge.js";
|
|
56
|
+
export { Avatar, type AvatarProps } from "./components/avatar.js";
|
|
57
|
+
export { Badge, type BadgeProps } from "./components/badge.js";
|
|
58
|
+
export {
|
|
59
|
+
Banner,
|
|
60
|
+
type BannerProps,
|
|
61
|
+
type BannerTone,
|
|
62
|
+
type BannerVariant,
|
|
63
|
+
} from "./components/banner.js";
|
|
64
|
+
export {
|
|
65
|
+
BottomSheet,
|
|
66
|
+
type BottomSheetProps,
|
|
67
|
+
} from "./components/bottom-sheet.js";
|
|
68
|
+
export {
|
|
69
|
+
BriefSection,
|
|
70
|
+
type BriefSectionProps,
|
|
71
|
+
SECTION_ROW_CAP,
|
|
72
|
+
} from "./components/brief-section.js";
|
|
73
|
+
export {
|
|
74
|
+
BriefSections,
|
|
75
|
+
type BriefSectionsProps,
|
|
76
|
+
} from "./components/brief-sections.js";
|
|
77
|
+
export { Button, type ButtonProps } from "./components/button.js";
|
|
78
|
+
export {
|
|
79
|
+
Card,
|
|
80
|
+
CardBody,
|
|
81
|
+
CardHeader,
|
|
82
|
+
type CardProps,
|
|
83
|
+
CardTitle,
|
|
84
|
+
} from "./components/card.js";
|
|
85
|
+
export {
|
|
86
|
+
CategoryBadge,
|
|
87
|
+
type CategoryBadgeProps,
|
|
88
|
+
getCategoryLabel,
|
|
89
|
+
type MessageCategory,
|
|
90
|
+
} from "./components/category-badge.js";
|
|
91
|
+
export { Checkbox, type CheckboxProps } from "./components/checkbox.js";
|
|
92
|
+
export {
|
|
93
|
+
ComposeActionBar,
|
|
94
|
+
type ComposeActionBarProps,
|
|
95
|
+
type ComposeSaveStatus,
|
|
96
|
+
} from "./components/compose-action-bar.js";
|
|
97
|
+
export {
|
|
98
|
+
ComposeFormShell,
|
|
99
|
+
type ComposeFormShellProps,
|
|
100
|
+
type ComposeMode,
|
|
101
|
+
composeModeLabels,
|
|
102
|
+
} from "./components/compose-form-shell.js";
|
|
103
|
+
export {
|
|
104
|
+
DangerZoneSection,
|
|
105
|
+
type DangerZoneSectionProps,
|
|
106
|
+
} from "./components/danger-zone-section.js";
|
|
107
|
+
export { Dialog, type DialogProps } from "./components/dialog.js";
|
|
108
|
+
export type { EmailFrameVariant } from "./components/email-frame-css.js";
|
|
109
|
+
export {
|
|
110
|
+
FieldLabel,
|
|
111
|
+
type FieldLabelProps,
|
|
112
|
+
} from "./components/field-label.js";
|
|
113
|
+
export {
|
|
114
|
+
FilterSheet,
|
|
115
|
+
type FilterSheetCategory,
|
|
116
|
+
type FilterSheetFilter,
|
|
117
|
+
type FilterSheetProps,
|
|
118
|
+
type FilterSheetSource,
|
|
119
|
+
} from "./components/filter-sheet.js";
|
|
120
|
+
export {
|
|
121
|
+
canonicalRoleLabel,
|
|
122
|
+
type FolderRole,
|
|
123
|
+
providerLeaf,
|
|
124
|
+
roleIcon,
|
|
125
|
+
} from "./components/folder-role.js";
|
|
126
|
+
export {
|
|
127
|
+
Input,
|
|
128
|
+
type InputProps,
|
|
129
|
+
type InputVariant,
|
|
130
|
+
} from "./components/input.js";
|
|
131
|
+
export {
|
|
132
|
+
type AuthenticityIntel,
|
|
133
|
+
type IntelligenceData,
|
|
134
|
+
IntelligencePanel,
|
|
135
|
+
type IntelligencePanelProps,
|
|
136
|
+
type IntelligenceQuickActions,
|
|
137
|
+
type MatchedChunk,
|
|
138
|
+
type SenderFlagsIntel,
|
|
139
|
+
type SenderIntel,
|
|
140
|
+
type SenderTrustLevel,
|
|
141
|
+
type SimilarMessageIntel,
|
|
142
|
+
type SimilarMessageLinkComponent,
|
|
143
|
+
type SimilarMessageLinkProps,
|
|
144
|
+
type SimilarState,
|
|
145
|
+
} from "./components/intelligence-panel.js";
|
|
146
|
+
export {
|
|
147
|
+
IsolatedEmailFrame,
|
|
148
|
+
type IsolatedEmailFrameProps,
|
|
149
|
+
measureContentAxis,
|
|
150
|
+
} from "./components/isolated-email-frame.js";
|
|
151
|
+
export { Kbd, type KbdProps } from "./components/kbd.js";
|
|
152
|
+
export {
|
|
153
|
+
defaultKeyboardHints,
|
|
154
|
+
type KeyboardHint,
|
|
155
|
+
KeyboardHintBar,
|
|
156
|
+
type KeyboardHintBarProps,
|
|
157
|
+
} from "./components/keyboard-hint-bar.js";
|
|
158
|
+
export { ListItem, type ListItemProps } from "./components/list-item.js";
|
|
159
|
+
export {
|
|
160
|
+
type MailAction,
|
|
161
|
+
MailActionToolbar,
|
|
162
|
+
type MailActionToolbarProps,
|
|
163
|
+
} from "./components/mail-action-toolbar.js";
|
|
164
|
+
export {
|
|
165
|
+
MailHeader,
|
|
166
|
+
type MailHeaderProps,
|
|
167
|
+
} from "./components/mail-header.js";
|
|
168
|
+
export {
|
|
169
|
+
type EmailRenderCategory,
|
|
170
|
+
MessageBodyView,
|
|
171
|
+
type MessageBodyViewProps,
|
|
172
|
+
} from "./components/message-body-view.js";
|
|
173
|
+
export {
|
|
174
|
+
MessageHeader,
|
|
175
|
+
type MessageHeaderProps,
|
|
176
|
+
} from "./components/message-header.js";
|
|
177
|
+
export { MessageListPane } from "./components/message-list-pane.js";
|
|
178
|
+
export {
|
|
179
|
+
type ListState,
|
|
180
|
+
MessageListEmpty,
|
|
181
|
+
MessageListError,
|
|
182
|
+
MessageListLoading,
|
|
183
|
+
} from "./components/message-list-state.js";
|
|
184
|
+
export {
|
|
185
|
+
type BriefRowComponent,
|
|
186
|
+
ComfortableRow,
|
|
187
|
+
ComfortableRowBody,
|
|
188
|
+
ComfortableRowTextContent,
|
|
189
|
+
CompactRow,
|
|
190
|
+
CompactRowBody,
|
|
191
|
+
comfortableRowClass,
|
|
192
|
+
compactRowClass,
|
|
193
|
+
} from "./components/message-row.js";
|
|
194
|
+
export {
|
|
195
|
+
type MobileMessageAction,
|
|
196
|
+
MobileMessageActionBar,
|
|
197
|
+
type MobileMessageActionBarProps,
|
|
198
|
+
} from "./components/mobile-message-action-bar.js";
|
|
199
|
+
export {
|
|
200
|
+
type MobileReadingMessageActions,
|
|
201
|
+
MobileReadingPane,
|
|
202
|
+
type MobileReadingPaneProps,
|
|
203
|
+
} from "./components/mobile-reading-pane.js";
|
|
204
|
+
export {
|
|
205
|
+
MobileSearchView,
|
|
206
|
+
type MobileSearchViewProps,
|
|
207
|
+
} from "./components/mobile-search-view.js";
|
|
208
|
+
export {
|
|
209
|
+
type MoveMailboxOption,
|
|
210
|
+
MoveMailboxPicker,
|
|
211
|
+
type MoveMailboxPickerLabels,
|
|
212
|
+
type MoveMailboxPickerProps,
|
|
213
|
+
} from "./components/move-mailbox-picker.js";
|
|
214
|
+
export {
|
|
215
|
+
NavSidebar,
|
|
216
|
+
type NavSidebarProps,
|
|
217
|
+
} from "./components/nav-sidebar.js";
|
|
218
|
+
export { OutboxRow, type OutboxRowProps } from "./components/outbox-row.js";
|
|
219
|
+
export {
|
|
220
|
+
type OutboxStatus,
|
|
221
|
+
OutboxStatusBadge,
|
|
222
|
+
type OutboxStatusBadgeProps,
|
|
223
|
+
outboxStatusConfig,
|
|
224
|
+
} from "./components/outbox-status-badge.js";
|
|
225
|
+
export {
|
|
226
|
+
PopoverMenu,
|
|
227
|
+
type PopoverMenuItem,
|
|
228
|
+
type PopoverMenuProps,
|
|
229
|
+
} from "./components/popover-menu.js";
|
|
230
|
+
export {
|
|
231
|
+
PullToRefresh,
|
|
232
|
+
type PullToRefreshProps,
|
|
233
|
+
} from "./components/pull-to-refresh.js";
|
|
234
|
+
export {
|
|
235
|
+
QuotedText,
|
|
236
|
+
type QuotedTextProps,
|
|
237
|
+
} from "./components/quoted-text.js";
|
|
238
|
+
export {
|
|
239
|
+
CollapsedMessage,
|
|
240
|
+
ExpandedMessage,
|
|
241
|
+
ReadingPane,
|
|
242
|
+
} from "./components/reading-pane.js";
|
|
243
|
+
export {
|
|
244
|
+
ReadingPaneEmpty,
|
|
245
|
+
type ReadingPaneEmptyProps,
|
|
246
|
+
} from "./components/reading-pane-empty.js";
|
|
247
|
+
export {
|
|
248
|
+
RescueBanner,
|
|
249
|
+
type RescueBannerProps,
|
|
250
|
+
} from "./components/rescue-banner.js";
|
|
251
|
+
export {
|
|
252
|
+
type RescueCandidate,
|
|
253
|
+
RescueCandidateRow,
|
|
254
|
+
type RescueCandidateRowProps,
|
|
255
|
+
} from "./components/rescue-candidate-row.js";
|
|
256
|
+
export {
|
|
257
|
+
RescueFromSpamFlow,
|
|
258
|
+
type RescueFromSpamFlowProps,
|
|
259
|
+
rescueMoveConsequence,
|
|
260
|
+
} from "./components/rescue-from-spam-flow.js";
|
|
261
|
+
export {
|
|
262
|
+
type PanelGroupProps,
|
|
263
|
+
type PanelProps,
|
|
264
|
+
type PanelResizeHandleProps,
|
|
265
|
+
ResizableHandle,
|
|
266
|
+
ResizablePanel,
|
|
267
|
+
ResizablePanelGroup,
|
|
268
|
+
} from "./components/resizable.js";
|
|
269
|
+
export {
|
|
270
|
+
APPOINTABLE_ROLES,
|
|
271
|
+
type CandidateFolder,
|
|
272
|
+
RoleAppointmentList,
|
|
273
|
+
type RoleAppointmentListProps,
|
|
274
|
+
} from "./components/role-appointment-list.js";
|
|
275
|
+
export {
|
|
276
|
+
type RowAction,
|
|
277
|
+
RowActions,
|
|
278
|
+
type RowActionsProps,
|
|
279
|
+
type RowDestructiveAction,
|
|
280
|
+
} from "./components/row-actions.js";
|
|
281
|
+
export { SearchBar, type SearchBarProps } from "./components/search-bar.js";
|
|
282
|
+
export {
|
|
283
|
+
type SearchResult,
|
|
284
|
+
SearchResultRow,
|
|
285
|
+
type SearchResultRowProps,
|
|
286
|
+
type SearchResultTone,
|
|
287
|
+
} from "./components/search-result-row.js";
|
|
288
|
+
export {
|
|
289
|
+
type SearchResultSection,
|
|
290
|
+
SearchResults,
|
|
291
|
+
type SearchResultsProps,
|
|
292
|
+
} from "./components/search-results.js";
|
|
293
|
+
export {
|
|
294
|
+
SearchTokenChip,
|
|
295
|
+
type SearchTokenChipProps,
|
|
296
|
+
SearchTokenChips,
|
|
297
|
+
type SearchTokenChipsProps,
|
|
298
|
+
} from "./components/search-token-chip.js";
|
|
299
|
+
export {
|
|
300
|
+
SecuritySelect,
|
|
301
|
+
type SecuritySelectProps,
|
|
302
|
+
type ServerSecurity,
|
|
303
|
+
securityToApi,
|
|
304
|
+
} from "./components/security-select.js";
|
|
305
|
+
export {
|
|
306
|
+
SegmentedControl,
|
|
307
|
+
type SegmentedControlProps,
|
|
308
|
+
type SegmentedOption,
|
|
309
|
+
} from "./components/segmented-control.js";
|
|
310
|
+
export { Select, type SelectProps } from "./components/select.js";
|
|
311
|
+
export {
|
|
312
|
+
SelectionTopBar,
|
|
313
|
+
type SelectionTopBarProps,
|
|
314
|
+
} from "./components/selection-top-bar.js";
|
|
315
|
+
export {
|
|
316
|
+
type SenderGroupOption,
|
|
317
|
+
SenderGroupSwitch,
|
|
318
|
+
type SenderGroupSwitchProps,
|
|
319
|
+
} from "./components/sender-group-switch.js";
|
|
320
|
+
export {
|
|
321
|
+
type SenderTrust,
|
|
322
|
+
SenderTrustIndicator,
|
|
323
|
+
type SenderTrustIndicatorProps,
|
|
324
|
+
type SenderTrustVariant,
|
|
325
|
+
selectSenderTrustVariant,
|
|
326
|
+
} from "./components/sender-trust-indicator.js";
|
|
327
|
+
export {
|
|
328
|
+
AccountHealthCard,
|
|
329
|
+
type AccountHealthCardProps,
|
|
330
|
+
SenderFlagRow,
|
|
331
|
+
type SenderFlagRowProps,
|
|
332
|
+
type SettingsNavItem,
|
|
333
|
+
SettingsShell,
|
|
334
|
+
type SettingsShellProps,
|
|
335
|
+
} from "./components/settings-screen.js";
|
|
336
|
+
export {
|
|
337
|
+
commitPeek,
|
|
338
|
+
SwipeableRow,
|
|
339
|
+
type SwipeableRowOpenProps,
|
|
340
|
+
type SwipePeek,
|
|
341
|
+
} from "./components/swipeable-row.js";
|
|
342
|
+
export { TouchListBody } from "./components/touch-list.js";
|
|
343
|
+
export {
|
|
344
|
+
CheckRow,
|
|
345
|
+
type CheckRowProps,
|
|
346
|
+
ConnectorTile,
|
|
347
|
+
type ConnectorTileProps,
|
|
348
|
+
ServerFields,
|
|
349
|
+
type ServerFieldsProps,
|
|
350
|
+
WizardShell,
|
|
351
|
+
type WizardShellProps,
|
|
352
|
+
} from "./components/wizard.js";
|
|
353
|
+
export {
|
|
354
|
+
briefFilterConfig,
|
|
355
|
+
type FilterAccount,
|
|
356
|
+
type FilterPreset,
|
|
357
|
+
flaggedFilterConfig,
|
|
358
|
+
inboxFilterConfig,
|
|
359
|
+
} from "./filter-presets.js";
|
|
360
|
+
export {
|
|
361
|
+
buildCidResolver,
|
|
362
|
+
type CidResolvableBodyPart,
|
|
363
|
+
type CidResolver,
|
|
364
|
+
} from "./lib/cid-resolver.js";
|
|
365
|
+
export { cn } from "./lib/cn.js";
|
|
366
|
+
export { generateLayoutClampCSS } from "./lib/email-layout-clamp.js";
|
|
367
|
+
export {
|
|
368
|
+
classifyEmailRenderTreatment,
|
|
369
|
+
type EmailRenderTreatment,
|
|
370
|
+
} from "./lib/email-render-treatment.js";
|
|
371
|
+
export {
|
|
372
|
+
createEmailSanitizer,
|
|
373
|
+
detectAuthorBackground,
|
|
374
|
+
type SanitizedEmail,
|
|
375
|
+
type SanitizeOptions,
|
|
376
|
+
sanitizeInlineStyle,
|
|
377
|
+
sanitizeStyleElementCss,
|
|
378
|
+
} from "./lib/email-sanitizer.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolver function for `cid:CONTENT_ID` references found in HTML bodies.
|
|
3
|
+
* Returns the CloudFront URL (`BodyPartResponse.contentUrl`) for the part
|
|
4
|
+
* with the matching `contentId`, or `undefined` when no matching part
|
|
5
|
+
* exists — letting the broken-image render through is the intentional
|
|
6
|
+
* fail-loud behaviour (#feedback_never_hide_failure).
|
|
7
|
+
*
|
|
8
|
+
* Lives in its own module so tests can import the pure function without
|
|
9
|
+
* loading `email-sanitizer.ts`, which evaluates `DOMPurify()` at import
|
|
10
|
+
* time and crashes outside a browser/JSDOM context.
|
|
11
|
+
*/
|
|
12
|
+
export type CidResolver = (contentId: string) => string | undefined;
|
|
13
|
+
|
|
14
|
+
export interface CidResolvableBodyPart {
|
|
15
|
+
contentId?: string;
|
|
16
|
+
contentUrl: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Index a list of body parts by their (angle-bracket-stripped) `contentId`
|
|
21
|
+
* and return a resolver. Calling the resolver with a `cid:` value's
|
|
22
|
+
* Content-ID returns the corresponding `contentUrl`. Pure function — safe
|
|
23
|
+
* to memoise on the body-parts reference.
|
|
24
|
+
*/
|
|
25
|
+
export const buildCidResolver = (
|
|
26
|
+
bodyParts: readonly CidResolvableBodyPart[],
|
|
27
|
+
): CidResolver => {
|
|
28
|
+
const byContentId = new Map<string, string>();
|
|
29
|
+
for (const part of bodyParts) {
|
|
30
|
+
if (!part.contentId || part.contentUrl.length === 0) continue;
|
|
31
|
+
const stripped = part.contentId.replace(/^<|>$/g, "");
|
|
32
|
+
byContentId.set(stripped, part.contentUrl);
|
|
33
|
+
}
|
|
34
|
+
return (contentId) => byContentId.get(contentId.replace(/^<|>$/g, ""));
|
|
35
|
+
};
|
package/src/lib/cn.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
/** Merge conditional class names, de-duplicating conflicting Tailwind utilities. */
|
|
5
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
6
|
+
return twMerge(clsx(inputs));
|
|
7
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { generateLayoutClampCSS } from "./email-layout-clamp.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Layout-clamp CSS injected alongside sanitized email HTML. Asserts on
|
|
7
|
+
* substrings — the goal is to lock in the rules that close #374 / #727 (mobile
|
|
8
|
+
* horizontal overflow when author markup is wider than the viewport). If any of
|
|
9
|
+
* these get dropped, wide tables / images / long URLs would start unlocking
|
|
10
|
+
* horizontal page scroll again.
|
|
11
|
+
*
|
|
12
|
+
* The sanitizer drops the email body straight into the iframe with no
|
|
13
|
+
* `.email-content` wrapper, so the rules target the document's own
|
|
14
|
+
* `html`/`body` and bare element selectors — a wrapper-scoped selector would
|
|
15
|
+
* never match and the clamp would silently do nothing (the original #727 bug).
|
|
16
|
+
*
|
|
17
|
+
* Pure string assertions — no DOM required, intentionally separate from
|
|
18
|
+
* `email-sanitizer.test.ts` because importing `email-sanitizer.ts` in plain
|
|
19
|
+
* Node fails on its eager `DOMPurify()` call at module load.
|
|
20
|
+
*/
|
|
21
|
+
describe("generateLayoutClampCSS (#374 / #727)", () => {
|
|
22
|
+
const css = generateLayoutClampCSS();
|
|
23
|
+
|
|
24
|
+
test("targets the document body directly, not a wrapper that never exists", () => {
|
|
25
|
+
// The sanitized email has no `.email-content` element — scoping there
|
|
26
|
+
// was the #727 bug (dead CSS). Rules must hit bare element selectors.
|
|
27
|
+
assert.ok(
|
|
28
|
+
!css.includes(".email-content"),
|
|
29
|
+
"clamp rules must not be scoped to a non-existent .email-content wrapper",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("zeros UA default html/body margin so iframe content fills edge-to-edge", () => {
|
|
34
|
+
assert.ok(
|
|
35
|
+
/html\s*,\s*body\s*\{[^}]*margin\s*:\s*0/.test(css),
|
|
36
|
+
"html, body margin must be reset to 0",
|
|
37
|
+
);
|
|
38
|
+
assert.ok(
|
|
39
|
+
/html\s*,\s*body\s*\{[^}]*padding\s*:\s*0/.test(css),
|
|
40
|
+
"html, body padding must be reset to 0",
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("clamps the document to the iframe width and wraps long tokens", () => {
|
|
45
|
+
assert.ok(css.includes("max-width: 100%"));
|
|
46
|
+
assert.ok(css.includes("overflow-wrap: anywhere"));
|
|
47
|
+
assert.ok(css.includes("word-break: break-word"));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("forces wide media (img/video/iframe) to fit the frame", () => {
|
|
51
|
+
assert.ok(/(^|\s)img\s*,/.test(css) || /\bimg\b/.test(css));
|
|
52
|
+
assert.ok(/\bvideo\b/.test(css));
|
|
53
|
+
assert.ok(/\biframe\b/.test(css));
|
|
54
|
+
// Author markup often sets `width="900"` — without !important we lose
|
|
55
|
+
// the cascade and the image overflows again.
|
|
56
|
+
assert.ok(
|
|
57
|
+
css.includes("max-width: 100% !important"),
|
|
58
|
+
"images need !important to override author width attributes",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("clamps fixed-width newsletter tables", () => {
|
|
63
|
+
assert.ok(/\btable\b/.test(css));
|
|
64
|
+
assert.ok(
|
|
65
|
+
css.includes("max-width: 100% !important"),
|
|
66
|
+
"author <table width='600'> must be capped to the frame width",
|
|
67
|
+
);
|
|
68
|
+
assert.ok(css.includes("table-layout: auto"));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("clamps fixed-width table cells (the real width carriers in newsletters)", () => {
|
|
72
|
+
// Newsletters pin widths on `<td width="600">`, not just the table.
|
|
73
|
+
assert.ok(
|
|
74
|
+
/\btd\s*,\s*th\b/.test(css),
|
|
75
|
+
"td, th must be capped to the frame",
|
|
76
|
+
);
|
|
77
|
+
assert.ok(
|
|
78
|
+
/td\s*,\s*th\s*\{[^}]*max-width:\s*100%\s*!important/.test(css),
|
|
79
|
+
"cell widths need !important to override author width attributes",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("zeros min-width so children can shrink below their intrinsic width", () => {
|
|
84
|
+
// `max-width` alone can't override an inline `min-width`; flex/grid/table
|
|
85
|
+
// children won't collapse without this.
|
|
86
|
+
assert.ok(
|
|
87
|
+
/\*\s*\{[^}]*min-width:\s*0/.test(css),
|
|
88
|
+
"a universal min-width:0 must let wide children shrink to the clamp",
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("wraps long unbroken lines in pre/code blocks", () => {
|
|
93
|
+
assert.ok(/\bpre\b/.test(css));
|
|
94
|
+
assert.ok(/\bcode\b/.test(css));
|
|
95
|
+
assert.ok(css.includes("white-space: pre-wrap"));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("does not touch colors or backgrounds (layout-only, by design)", () => {
|
|
99
|
+
// If any of these slip in, this stylesheet starts overlapping with
|
|
100
|
+
// the dark-mode / smart-invert CSS and the rule set is no longer
|
|
101
|
+
// scoped to the bug we're fixing. Keep it laser-focused on width/wrap.
|
|
102
|
+
assert.ok(!/\bcolor\s*:/.test(css), "no color rules in layout clamp");
|
|
103
|
+
assert.ok(
|
|
104
|
+
!/\bbackground(-color)?\s*:/.test(css),
|
|
105
|
+
"no background rules in layout clamp",
|
|
106
|
+
);
|
|
107
|
+
assert.ok(!/\bfont-/.test(css), "no font rules in layout clamp");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout-clamp CSS injected into rendered email bodies.
|
|
3
|
+
*
|
|
4
|
+
* Author markup commonly sets explicit pixel widths via `<table width="600">`,
|
|
5
|
+
* `<img width="900">`, oversized inline styles, or long unbroken URLs. On a
|
|
6
|
+
* narrow viewport these push the body past the iframe's width and unlock
|
|
7
|
+
* horizontal page scroll, dragging the surrounding app chrome with them (#727).
|
|
8
|
+
* We clamp the body and its media + tables to the viewport width and wrap long
|
|
9
|
+
* unbroken tokens so the rendered email fits the frame on a phone.
|
|
10
|
+
*
|
|
11
|
+
* The sanitizer prepends this block to the raw (DOMPurify'd) email and drops it
|
|
12
|
+
* into the iframe with no `.email-content` wrapper, so the rules target the
|
|
13
|
+
* iframe's own `html`/`body` and the document's media/table/code elements
|
|
14
|
+
* directly — a wrapper-scoped selector would never match.
|
|
15
|
+
*
|
|
16
|
+
* Layout-only: colors, backgrounds and typography are untouched (that is the
|
|
17
|
+
* dark-mode / smart-invert CSS's job in `IsolatedEmailFrame`). Uses
|
|
18
|
+
* `!important` because author width attributes win the cascade otherwise; the
|
|
19
|
+
* cascade win is the entire point on mobile. Not wrapped in a media query —
|
|
20
|
+
* natural content width still affects parent layout on desktop if it's wider
|
|
21
|
+
* than the column.
|
|
22
|
+
*
|
|
23
|
+
* In its own module so tests can import it without triggering the eager
|
|
24
|
+
* `DOMPurify()` call at the bottom of `email-sanitizer.ts` (which needs a DOM).
|
|
25
|
+
*/
|
|
26
|
+
export const generateLayoutClampCSS = (): string => `
|
|
27
|
+
/* Zero the UA default body margin and clamp the document to the iframe width so
|
|
28
|
+
wide author markup can't push the body (and the page) past the viewport. */
|
|
29
|
+
html, body {
|
|
30
|
+
margin: 0;
|
|
31
|
+
padding: 0;
|
|
32
|
+
max-width: 100%;
|
|
33
|
+
}
|
|
34
|
+
body {
|
|
35
|
+
overflow-wrap: anywhere;
|
|
36
|
+
word-break: break-word;
|
|
37
|
+
}
|
|
38
|
+
/* Media never wider than the frame; keep aspect ratio when width is capped. */
|
|
39
|
+
img, video, iframe, svg, canvas {
|
|
40
|
+
max-width: 100% !important;
|
|
41
|
+
height: auto;
|
|
42
|
+
}
|
|
43
|
+
/* Fixed-width author tables (\`<table width="600">\`) collapse to fit. */
|
|
44
|
+
table {
|
|
45
|
+
max-width: 100% !important;
|
|
46
|
+
table-layout: auto;
|
|
47
|
+
}
|
|
48
|
+
/* Cells carry the fixed widths in real newsletters (\`<td width="600">\`);
|
|
49
|
+
cap them too or the table can't actually shrink. */
|
|
50
|
+
td, th {
|
|
51
|
+
max-width: 100% !important;
|
|
52
|
+
}
|
|
53
|
+
/* \`max-width\` alone can't beat an inline \`min-width\`; zeroing the min lets
|
|
54
|
+
flex / grid / table children actually collapse to the clamped width. */
|
|
55
|
+
* {
|
|
56
|
+
min-width: 0;
|
|
57
|
+
}
|
|
58
|
+
/* Long unbroken strings (URLs, tokens) wrap instead of forcing a wide line. */
|
|
59
|
+
pre, code {
|
|
60
|
+
white-space: pre-wrap;
|
|
61
|
+
overflow-wrap: anywhere;
|
|
62
|
+
word-break: break-word;
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { classifyEmailRenderTreatment } from "./email-render-treatment.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Render-treatment classification (#424). Locks the framed-vs-plain decision
|
|
7
|
+
* that drives the owner's dark-mode fix: misclassifying a plain personal email
|
|
8
|
+
* as `framed` would skip the theme-aware base CSS and reintroduce
|
|
9
|
+
* black-text-on-dark.
|
|
10
|
+
*/
|
|
11
|
+
describe("classifyEmailRenderTreatment (#424)", () => {
|
|
12
|
+
test("newsletter category → framed (designed mail, colors preserved)", () => {
|
|
13
|
+
const t = classifyEmailRenderTreatment("newsletter", false);
|
|
14
|
+
assert.strictEqual(t.framed, true);
|
|
15
|
+
assert.strictEqual(t.isPlain, false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("marketing category → framed", () => {
|
|
19
|
+
const t = classifyEmailRenderTreatment("marketing", false);
|
|
20
|
+
assert.strictEqual(t.framed, true);
|
|
21
|
+
assert.strictEqual(t.isPlain, false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("author background detected → framed even for a personal category", () => {
|
|
25
|
+
const t = classifyEmailRenderTreatment("personal", true);
|
|
26
|
+
assert.strictEqual(t.framed, true);
|
|
27
|
+
assert.strictEqual(t.isPlain, false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("plain personal mail (no background) → isPlain (theme CSS applied)", () => {
|
|
31
|
+
// Mirrors the `<p style="color:#000">…</p>` case: text-color only, no
|
|
32
|
+
// background — the sanitizer returns hasAuthorBackground=false, so this
|
|
33
|
+
// must classify as plain and receive the theme-aware base CSS.
|
|
34
|
+
const t = classifyEmailRenderTreatment("personal", false);
|
|
35
|
+
assert.strictEqual(t.framed, false);
|
|
36
|
+
assert.strictEqual(t.isPlain, true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("undefined category, no background → isPlain (safe default for weak markup)", () => {
|
|
40
|
+
const t = classifyEmailRenderTreatment(undefined, false);
|
|
41
|
+
assert.strictEqual(t.framed, false);
|
|
42
|
+
assert.strictEqual(t.isPlain, true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("undefined category WITH author background → framed", () => {
|
|
46
|
+
const t = classifyEmailRenderTreatment(undefined, true);
|
|
47
|
+
assert.strictEqual(t.framed, true);
|
|
48
|
+
assert.strictEqual(t.isPlain, false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("automated/transactional/social categories with no background → isPlain", () => {
|
|
52
|
+
for (const category of ["automated", "transactional", "social"] as const) {
|
|
53
|
+
const t = classifyEmailRenderTreatment(category, false);
|
|
54
|
+
assert.strictEqual(
|
|
55
|
+
t.isPlain,
|
|
56
|
+
true,
|
|
57
|
+
`${category} with no background should be plain`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("framed and isPlain are always mutually exclusive", () => {
|
|
63
|
+
const cases: Array<
|
|
64
|
+
[Parameters<typeof classifyEmailRenderTreatment>[0], boolean]
|
|
65
|
+
> = [
|
|
66
|
+
["newsletter", false],
|
|
67
|
+
["marketing", true],
|
|
68
|
+
["personal", false],
|
|
69
|
+
["personal", true],
|
|
70
|
+
[undefined, false],
|
|
71
|
+
[undefined, true],
|
|
72
|
+
];
|
|
73
|
+
for (const [category, bg] of cases) {
|
|
74
|
+
const t = classifyEmailRenderTreatment(category, bg);
|
|
75
|
+
assert.notStrictEqual(
|
|
76
|
+
t.framed,
|
|
77
|
+
t.isPlain,
|
|
78
|
+
`framed and isPlain must differ for (${category}, ${bg})`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|