@rapidmx/web-client 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +3 -0
  2. package/apps/shared/components/admin/diagnostics/DiagnosticsManager.tsx +22 -10
  3. package/apps/shared/components/admin/diagnostics/InformationPanel.tsx +60 -0
  4. package/apps/shared/components/admin/diagnostics/SettingsTable.tsx +108 -0
  5. package/apps/shared/components/admin/diagnostics/diagnosticsApi.ts +25 -1
  6. package/apps/shared/components/calendar/CalendarSearchBox.tsx +83 -0
  7. package/apps/shared/components/calendar/EventMatchNav.tsx +72 -0
  8. package/apps/shared/components/calendar/EventModal.tsx +33 -12
  9. package/apps/shared/components/calendar/ListView.tsx +207 -0
  10. package/apps/shared/components/calendar/MonthView.tsx +14 -2
  11. package/apps/shared/components/calendar/SlideIn.tsx +47 -0
  12. package/apps/shared/components/calendar/SplitDayView.tsx +5 -0
  13. package/apps/shared/components/calendar/TimeGridView.tsx +23 -12
  14. package/apps/shared/components/calendar/activeOccurrence.ts +28 -0
  15. package/apps/shared/components/calendar/allDay.ts +16 -1
  16. package/apps/shared/components/calendar/calendarView.ts +41 -0
  17. package/apps/shared/components/layout/AppShell.tsx +13 -6
  18. package/apps/shared/components/mail/ConversationList.tsx +32 -6
  19. package/apps/shared/components/mail/ConversationThreadPane.tsx +15 -0
  20. package/apps/shared/components/mail/MailSelectionBar.tsx +117 -55
  21. package/apps/shared/components/mail/MessageDetailPane.tsx +3 -0
  22. package/apps/shared/components/mail/compose/ComposeContext.tsx +193 -35
  23. package/apps/shared/components/mail/compose/ComposeWindow.tsx +62 -11
  24. package/apps/shared/components/mail/compose/ComposeWindowPlaceholder.tsx +21 -10
  25. package/apps/shared/components/mail/compose/RichTextEditor.tsx +2 -1
  26. package/apps/shared/components/mail/compose/quotedBody.ts +45 -2
  27. package/apps/shared/components/mail/labelMenu.tsx +4 -0
  28. package/apps/shared/components/mail/layout/MailShell.tsx +9 -2
  29. package/apps/shared/components/mail/reading/MessageBody.tsx +1 -1
  30. package/apps/shared/components/mail/reading/bodyContent.ts +15 -13
  31. package/apps/shared/components/mail/reading/bodyHtml.ts +12 -7
  32. package/apps/shared/gestures/useEnterSlide.ts +1 -1
  33. package/apps/shared/keyboard/keymap.ts +2 -0
  34. package/apps/shared/mail/rangeSelection.ts +45 -0
  35. package/apps/shared/mail/rowExit.ts +39 -0
  36. package/apps/shared/mail/usePermanentDelete.tsx +14 -7
  37. package/apps/www/calendar/index.tsx +251 -53
  38. package/apps/www/index.tsx +563 -376
  39. package/dist/apps/shared/components/admin/diagnostics/DiagnosticsManager.d.ts +3 -3
  40. package/dist/apps/shared/components/admin/diagnostics/DiagnosticsManager.js +15 -9
  41. package/dist/apps/shared/components/admin/diagnostics/InformationPanel.d.ts +15 -0
  42. package/dist/apps/shared/components/admin/diagnostics/InformationPanel.js +16 -0
  43. package/dist/apps/shared/components/admin/diagnostics/SettingsTable.d.ts +20 -0
  44. package/dist/apps/shared/components/admin/diagnostics/SettingsTable.js +31 -0
  45. package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.d.ts +20 -1
  46. package/dist/apps/shared/components/admin/diagnostics/diagnosticsApi.js +7 -0
  47. package/dist/apps/shared/components/calendar/CalendarSearchBox.d.ts +22 -0
  48. package/dist/apps/shared/components/calendar/CalendarSearchBox.js +24 -0
  49. package/dist/apps/shared/components/calendar/EventMatchNav.d.ts +17 -0
  50. package/dist/apps/shared/components/calendar/EventMatchNav.js +37 -0
  51. package/dist/apps/shared/components/calendar/EventModal.d.ts +17 -1
  52. package/dist/apps/shared/components/calendar/EventModal.js +13 -11
  53. package/dist/apps/shared/components/calendar/ListView.d.ts +30 -0
  54. package/dist/apps/shared/components/calendar/ListView.js +111 -0
  55. package/dist/apps/shared/components/calendar/MonthView.js +12 -3
  56. package/dist/apps/shared/components/calendar/SlideIn.d.ts +16 -0
  57. package/dist/apps/shared/components/calendar/SlideIn.js +32 -0
  58. package/dist/apps/shared/components/calendar/SplitDayView.js +5 -1
  59. package/dist/apps/shared/components/calendar/TimeGridView.js +11 -3
  60. package/dist/apps/shared/components/calendar/activeOccurrence.d.ts +15 -0
  61. package/dist/apps/shared/components/calendar/activeOccurrence.js +21 -0
  62. package/dist/apps/shared/components/calendar/allDay.d.ts +4 -0
  63. package/dist/apps/shared/components/calendar/allDay.js +14 -1
  64. package/dist/apps/shared/components/calendar/calendarView.d.ts +16 -0
  65. package/dist/apps/shared/components/calendar/calendarView.js +36 -0
  66. package/dist/apps/shared/components/layout/AppShell.js +8 -4
  67. package/dist/apps/shared/components/mail/ConversationList.d.ts +11 -3
  68. package/dist/apps/shared/components/mail/ConversationList.js +11 -3
  69. package/dist/apps/shared/components/mail/ConversationThreadPane.js +9 -1
  70. package/dist/apps/shared/components/mail/MailSelectionBar.js +11 -4
  71. package/dist/apps/shared/components/mail/MessageDetailPane.js +3 -0
  72. package/dist/apps/shared/components/mail/compose/ComposeContext.d.ts +31 -0
  73. package/dist/apps/shared/components/mail/compose/ComposeContext.js +123 -11
  74. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +6 -1
  75. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +37 -11
  76. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.d.ts +5 -1
  77. package/dist/apps/shared/components/mail/compose/ComposeWindowPlaceholder.js +10 -6
  78. package/dist/apps/shared/components/mail/compose/RichTextEditor.js +2 -1
  79. package/dist/apps/shared/components/mail/compose/quotedBody.d.ts +3 -0
  80. package/dist/apps/shared/components/mail/compose/quotedBody.js +43 -1
  81. package/dist/apps/shared/components/mail/labelMenu.d.ts +3 -1
  82. package/dist/apps/shared/components/mail/labelMenu.js +2 -2
  83. package/dist/apps/shared/components/mail/layout/MailShell.js +7 -1
  84. package/dist/apps/shared/components/mail/reading/MessageBody.js +1 -1
  85. package/dist/apps/shared/components/mail/reading/bodyContent.d.ts +5 -1
  86. package/dist/apps/shared/components/mail/reading/bodyContent.js +11 -11
  87. package/dist/apps/shared/components/mail/reading/bodyHtml.d.ts +3 -2
  88. package/dist/apps/shared/components/mail/reading/bodyHtml.js +8 -4
  89. package/dist/apps/shared/gestures/useEnterSlide.d.ts +2 -0
  90. package/dist/apps/shared/gestures/useEnterSlide.js +1 -1
  91. package/dist/apps/shared/keyboard/keymap.d.ts +12 -0
  92. package/dist/apps/shared/keyboard/keymap.js +2 -0
  93. package/dist/apps/shared/mail/rangeSelection.d.ts +20 -0
  94. package/dist/apps/shared/mail/rangeSelection.js +34 -0
  95. package/dist/apps/shared/mail/rowExit.d.ts +12 -0
  96. package/dist/apps/shared/mail/rowExit.js +33 -0
  97. package/dist/apps/shared/mail/usePermanentDelete.d.ts +2 -0
  98. package/dist/apps/shared/mail/usePermanentDelete.js +13 -8
  99. package/dist/apps/www/calendar/index.js +177 -18
  100. package/dist/apps/www/index.js +257 -93
  101. package/dist/lib/calendar/eventSearch.d.ts +42 -0
  102. package/dist/lib/calendar/eventSearch.js +152 -0
  103. package/dist/lib/components/navigation/BottomTabBar.d.ts +15 -3
  104. package/dist/lib/components/navigation/BottomTabBar.js +52 -15
  105. package/dist/lib/components/navigation/NavBadge.d.ts +13 -0
  106. package/dist/lib/components/navigation/NavBadge.js +18 -0
  107. package/dist/lib/mail/compose/quotedImages.d.ts +21 -0
  108. package/dist/lib/mail/compose/quotedImages.js +41 -0
  109. package/dist/lib/mail/inlineImages.d.ts +38 -0
  110. package/dist/lib/mail/inlineImages.js +90 -0
  111. package/dist/lib/mail/mailApi.d.ts +8 -1
  112. package/dist/lib/mail/mailApi.js +57 -5
  113. package/lib/calendar/eventSearch.ts +172 -0
  114. package/lib/components/navigation/BottomTabBar.tsx +107 -22
  115. package/lib/components/navigation/NavBadge.tsx +33 -0
  116. package/lib/mail/compose/quotedImages.ts +49 -0
  117. package/lib/mail/inlineImages.ts +104 -0
  118. package/lib/mail/mailApi.ts +64 -5
  119. package/package.json +1 -1
  120. package/apps/shared/components/admin/diagnostics/VersionsPanel.tsx +0 -36
  121. package/dist/apps/shared/components/admin/diagnostics/VersionsPanel.d.ts +0 -10
  122. package/dist/apps/shared/components/admin/diagnostics/VersionsPanel.js +0 -11
@@ -18,6 +18,7 @@ import { ListParams, buildQuery } from "../util/apiQuery.js";
18
18
  import { deviceTimeZone } from "../util/timeZone.js";
19
19
  import type { EncryptionPreference, PublicKey } from "../crypto/keyvaultApi.js";
20
20
  import { bytesToBinaryString } from "../crypto/mime.js";
21
+ import { newContentId, parseDataImage } from "./inlineImages.js";
21
22
  import type { ResolvedPrincipal } from "./mailboxAccessApi.js";
22
23
 
23
24
  export type { ListParams };
@@ -1005,6 +1006,8 @@ export interface Attachment {
1005
1006
  mimeType: string;
1006
1007
  sizeBytes: number;
1007
1008
  isInline: boolean;
1009
+ /** The part's `Content-ID`, without angle brackets: what an inline image's `cid:` reference names. */
1010
+ contentId?: string;
1008
1011
  }
1009
1012
 
1010
1013
  /** Lists the attachments belonging to a single message. */
@@ -1012,6 +1015,11 @@ export function listAttachments(folderUid: string, messageUid: string, client?:
1012
1015
  return withClient(client, `/mail/attachments?${buildQuery({ limit: 200 }, { folderUid, messageUid })}`);
1013
1016
  }
1014
1017
 
1018
+ /** Removes an attachment (from a draft). The server re-derives the message's `hasAttachments` itself. */
1019
+ export function deleteAttachment(uid: string, version: number, client?: ApiClient): Promise<void> {
1020
+ return withClient(client, `/mail/attachments/${encodeURIComponent(uid)}?version=${version}`, { method: "DELETE" });
1021
+ }
1022
+
1015
1023
  /** The URL to download/display an attachment's binary content — not fetched via `apiFetch`, used directly as
1016
1024
  * a link/image `href`/`src`. Honors `configureApiBaseUrl()` (see `apiUrl()`). */
1017
1025
  export function attachmentContentUrl(uid: string): string {
@@ -1023,9 +1031,19 @@ export function attachmentContentUrl(uid: string): string {
1023
1031
  * always forces `Content-Type: application/json`, which would corrupt binary content; this sends the file's
1024
1032
  * own bytes/type directly instead, matching `BaseAttachmentRoute.upload`'s expectation of a raw request body.
1025
1033
  */
1026
- export async function uploadAttachment(messageUid: string, file: File): Promise<Attachment> {
1027
- const params = new URLSearchParams({ messageUid, filename: file.name, mimeType: file.type || "application/octet-stream" });
1028
- const res = await fetch(apiUrl(`/mail/attachments/upload?${params.toString()}`), {
1034
+ export async function uploadAttachment(messageUid: string, file: File, options: { inline?: boolean; contentId?: string } = {}): Promise<Attachment> {
1035
+ // Percent-encoded, not form-encoded (`URLSearchParams` writes a space as `+`, which the server reads as a plus sign: "My+photo.png").
1036
+ const query: Record<string, string> = { messageUid, filename: file.name, mimeType: file.type || "application/octet-stream" };
1037
+ if (options.inline) {
1038
+ // An image shown in the text of the message: it goes out as an inline part, which the text refers to by this `Content-ID`
1039
+ // (`BaseMailComposeRoute.rewriteInlineImageSources()` turns the editor's preview URL into that `cid:` reference).
1040
+ query.isInline = "true";
1041
+ query.contentId = options.contentId ?? newContentId();
1042
+ }
1043
+ const params = Object.entries(query)
1044
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
1045
+ .join("&");
1046
+ const res = await fetch(apiUrl(`/mail/attachments/upload?${params}`), {
1029
1047
  method: "POST",
1030
1048
  credentials: "include",
1031
1049
  headers: withCsrfHeader({ "Content-Type": file.type || "application/octet-stream" }),
@@ -1130,13 +1148,54 @@ export function emptyFolder(folderUid: string, client?: ApiClient): Promise<void
1130
1148
  * — see `BaseMailComposeRoute` (this app's own compose-assembly glue, since `@rapidmx/restapi`'s `send()`
1131
1149
  * itself does no MIME composition). Does not send the message.
1132
1150
  */
1133
- export function assembleDraft(messageUid: string, input: AssembleDraftInput, client?: ApiClient): Promise<Message> {
1151
+ export async function assembleDraft(messageUid: string, input: AssembleDraftInput, client?: ApiClient): Promise<Message> {
1152
+ const html = await attachDataImages(messageUid, input.html);
1134
1153
  return withClient(client, `/mail/compose/${encodeURIComponent(messageUid)}/assemble`, {
1135
1154
  method: "POST",
1136
- body: JSON.stringify(input),
1155
+ body: JSON.stringify({ ...input, html }),
1137
1156
  });
1138
1157
  }
1139
1158
 
1159
+ /** `data:` images already attached to a draft: its uid and the image, to the URL of the attachment it became. */
1160
+ const attachedDataImages = new Map<string, string>();
1161
+
1162
+ const DATA_IMAGE_TAG = /(<img\b[^>]*?\bsrc\s*=\s*)(["'])(data:image\/[^"']*)\2/gi;
1163
+
1164
+ /** Names a picture by its type, for the attachment it becomes. */
1165
+ function pictureName(contentType: string): string {
1166
+ return `image.${contentType.split("/")[1].replace(/\+.*/, "").replace("jpeg", "jpg")}`;
1167
+ }
1168
+
1169
+ /**
1170
+ * `html` with each `<img>` that embeds its picture as a `data:` URI (a quoted original's image, one pasted into the editor) pointed at an
1171
+ * attachment of the draft instead: the server drops `data:` images from the message it composes, and an attachment is what it makes an
1172
+ * inline part of. A picture is attached once per draft however often the draft is saved.
1173
+ */
1174
+ async function attachDataImages(messageUid: string, html: string): Promise<string> {
1175
+ const found = Array.from(html.matchAll(DATA_IMAGE_TAG));
1176
+ if (found.length === 0) {
1177
+ return html;
1178
+ }
1179
+ const urls = new Map<string, string>();
1180
+ for (const [, , , uri] of found) {
1181
+ const key = `${messageUid}\n${uri}`;
1182
+ const known = attachedDataImages.get(key);
1183
+ if (known) {
1184
+ urls.set(uri, known);
1185
+ continue;
1186
+ }
1187
+ const image = parseDataImage(uri);
1188
+ if (!image) {
1189
+ continue;
1190
+ }
1191
+ const attachment = await uploadAttachment(messageUid, new File([image.bytes as BlobPart], pictureName(image.contentType), { type: image.contentType }), { inline: true });
1192
+ const url = attachmentContentUrl(attachment.uid);
1193
+ attachedDataImages.set(key, url);
1194
+ urls.set(uri, url);
1195
+ }
1196
+ return html.replace(DATA_IMAGE_TAG, (match, start: string, quote: string, uri: string) => (urls.has(uri) ? `${start}${quote}${urls.get(uri)}${quote}` : match));
1197
+ }
1198
+
1140
1199
  export interface AssembleDraftRawInput {
1141
1200
  to: ComposeRecipientInput[];
1142
1201
  cc?: ComposeRecipientInput[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidmx/web-client",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "RapidMX's webmail and admin console React UI for web-based clients.",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/rapidmx/web-client",
@@ -1,36 +0,0 @@
1
- ///////////////////////////////////////////////////////////////////////////////
2
- // Copyright (C) 2026 Jean-Philippe Steinmetz
3
- // SPDX-License-Identifier: MPL-2.0
4
- ///////////////////////////////////////////////////////////////////////////////
5
- import React from "react";
6
- import Alert from "../../../../../lib/components/feedback/Alert.js";
7
- import type { DiagnosticsVersions } from "./diagnosticsApi.js";
8
- import type { DiagnosticsResource } from "./useDiagnosticsResource.js";
9
- import ComponentsTable from "./ComponentsTable.js";
10
- import InstalledPlugins, { InstalledPluginsData } from "./InstalledPlugins.js";
11
- import PackagesTable from "./PackagesTable.js";
12
- import ServerVersionCard from "./ServerVersionCard.js";
13
-
14
- export interface VersionsPanelProps {
15
- versions: DiagnosticsResource<DiagnosticsVersions>;
16
- plugins: DiagnosticsResource<InstalledPluginsData>;
17
- }
18
-
19
- /** The Versions tab: the server, its packages, the installed plugins and the install's other containers. */
20
- export default function VersionsPanel({ versions, plugins }: VersionsPanelProps) {
21
- const { data, error, loading } = versions;
22
- return (
23
- <div className="space-y-6">
24
- {error && <Alert>{error}</Alert>}
25
- {!data && loading && <p className="text-sm text-text-muted">Loading&hellip;</p>}
26
- {data && (
27
- <>
28
- <ServerVersionCard server={data.server} />
29
- <ComponentsTable components={data.components ?? []} kubernetes={data.kubernetes ?? { available: false }} />
30
- <InstalledPlugins data={plugins.data} error={plugins.error} loading={plugins.loading} />
31
- <PackagesTable packages={data.packages ?? []} />
32
- </>
33
- )}
34
- </div>
35
- );
36
- }
@@ -1,10 +0,0 @@
1
- import React from "react";
2
- import type { DiagnosticsVersions } from "./diagnosticsApi.js";
3
- import type { DiagnosticsResource } from "./useDiagnosticsResource.js";
4
- import { InstalledPluginsData } from "./InstalledPlugins.js";
5
- export interface VersionsPanelProps {
6
- versions: DiagnosticsResource<DiagnosticsVersions>;
7
- plugins: DiagnosticsResource<InstalledPluginsData>;
8
- }
9
- /** The Versions tab: the server, its packages, the installed plugins and the install's other containers. */
10
- export default function VersionsPanel({ versions, plugins }: VersionsPanelProps): React.JSX.Element;
@@ -1,11 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import Alert from "../../../../../lib/components/feedback/Alert.js";
3
- import ComponentsTable from "./ComponentsTable.js";
4
- import InstalledPlugins from "./InstalledPlugins.js";
5
- import PackagesTable from "./PackagesTable.js";
6
- import ServerVersionCard from "./ServerVersionCard.js";
7
- /** The Versions tab: the server, its packages, the installed plugins and the install's other containers. */
8
- export default function VersionsPanel({ versions, plugins }) {
9
- const { data, error, loading } = versions;
10
- return (_jsxs("div", { className: "space-y-6", children: [error && _jsx(Alert, { children: error }), !data && loading && _jsx("p", { className: "text-sm text-text-muted", children: "Loading\u2026" }), data && (_jsxs(_Fragment, { children: [_jsx(ServerVersionCard, { server: data.server }), _jsx(ComponentsTable, { components: data.components ?? [], kubernetes: data.kubernetes ?? { available: false } }), _jsx(InstalledPlugins, { data: plugins.data, error: plugins.error, loading: plugins.loading }), _jsx(PackagesTable, { packages: data.packages ?? [] })] }))] }));
11
- }