@rapidmx/web-client 0.20.0 → 0.21.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 (118) hide show
  1. package/README.md +381 -381
  2. package/apps/admin/branding/index.tsx +39 -39
  3. package/apps/admin/data-requests/index.tsx +482 -482
  4. package/apps/admin/domains/[uid].tsx +166 -166
  5. package/apps/admin/escrow-scopes/[uid].tsx +350 -350
  6. package/apps/admin/index.tsx +129 -129
  7. package/apps/admin/mailboxes/[uid].tsx +271 -271
  8. package/apps/admin/mailboxes/new/index.tsx +30 -30
  9. package/apps/admin/plugins/index.tsx +15 -15
  10. package/apps/admin/retention-policy/index.tsx +39 -39
  11. package/apps/admin/signing-certificates/index.tsx +343 -343
  12. package/apps/escrow/audit-log/index.tsx +196 -196
  13. package/apps/escrow/matters/[uid].tsx +621 -621
  14. package/apps/shared/auth/adminAccess.ts +99 -99
  15. package/apps/shared/components/admin/layout/AdminShell.tsx +384 -384
  16. package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -238
  17. package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -152
  18. package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -194
  19. package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
  20. package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
  21. package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
  22. package/apps/shared/components/admin/settings/PluginsManager.tsx +2093 -2093
  23. package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
  24. package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
  25. package/apps/shared/components/admin/setup/SetupWizard.tsx +446 -446
  26. package/apps/shared/components/admin/usePagedList.tsx +129 -129
  27. package/apps/shared/components/calendar/EventModal.tsx +206 -206
  28. package/apps/shared/components/calendar/MonthView.tsx +185 -185
  29. package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -227
  30. package/apps/shared/components/calendar/SplitDayView.tsx +144 -144
  31. package/apps/shared/components/calendar/TimeGridView.tsx +246 -246
  32. package/apps/shared/components/calendar/allDay.ts +124 -124
  33. package/apps/shared/components/contacts/ContactForm.tsx +383 -383
  34. package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -103
  35. package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
  36. package/apps/shared/components/layout/AppShell.tsx +463 -461
  37. package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
  38. package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -168
  39. package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
  40. package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
  41. package/apps/shared/components/layout/UserMenu.tsx +439 -439
  42. package/apps/shared/components/mail/ConversationList.tsx +332 -332
  43. package/apps/shared/components/mail/ConversationThreadPane.tsx +613 -613
  44. package/apps/shared/components/mail/MailSelectionBar.tsx +240 -240
  45. package/apps/shared/components/mail/MenuButton.tsx +404 -404
  46. package/apps/shared/components/mail/MessageDetailPane.tsx +1757 -1757
  47. package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -312
  48. package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
  49. package/apps/shared/components/mail/compose/ComposeWindow.tsx +1705 -1681
  50. package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
  51. package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
  52. package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
  53. package/apps/shared/components/mail/reading/MessageMoreMenu.tsx +258 -258
  54. package/apps/shared/components/mail/reading/MessageSourceDialog.tsx +79 -79
  55. package/apps/shared/components/mail/reading/messageExport.ts +59 -59
  56. package/apps/shared/components/mail/reading/printMessage.ts +99 -99
  57. package/apps/shared/components/mail/reading/useMessageActions.ts +443 -443
  58. package/apps/shared/components/mail/verificationSeals.ts +125 -125
  59. package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
  60. package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
  61. package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
  62. package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
  63. package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
  64. package/apps/shared/keyboard/dispatch.ts +124 -124
  65. package/apps/shared/keyboard/format.ts +89 -89
  66. package/apps/shared/keyboard/keymap.ts +114 -114
  67. package/apps/shared/keyboard/registry.ts +65 -65
  68. package/apps/shared/keyboard/targets.ts +79 -79
  69. package/apps/shared/mail/folderOfType.ts +49 -49
  70. package/apps/shared/mail/folderTree.ts +143 -143
  71. package/apps/shared/mail/listAllPages.ts +39 -39
  72. package/apps/shared/mail/newMailNotifications.ts +171 -171
  73. package/apps/shared/mail/outbox/sendJob.ts +445 -445
  74. package/apps/shared/mail/outbox/sendOutcomes.ts +155 -155
  75. package/apps/shared/mail/reportNotices.ts +66 -66
  76. package/apps/shared/mail/senderBlocking.ts +141 -141
  77. package/apps/shared/mail/useMailConnection.ts +205 -205
  78. package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
  79. package/apps/shared/mail/useMailboxUpdateAccess.ts +60 -60
  80. package/apps/shared/mail/useMarkMessageRead.ts +47 -47
  81. package/apps/shared/mail/useNewMailNotifications.ts +178 -178
  82. package/apps/shared/notifications/store.ts +560 -560
  83. package/apps/shared/search/LocalIndexLifecycle.tsx +114 -114
  84. package/apps/shared/search/localIndexBuilder.ts +481 -481
  85. package/apps/shared/signing/enrollmentStorage.ts +33 -33
  86. package/apps/shared/signing/enrollmentTracker.ts +385 -385
  87. package/apps/shared/signing/enrollmentView.ts +251 -251
  88. package/apps/shared/signing/useNow.ts +19 -19
  89. package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -90
  90. package/apps/shared/styles/app.css +396 -396
  91. package/apps/www/calendar/index.tsx +581 -581
  92. package/apps/www/contacts/[uid].tsx +112 -112
  93. package/apps/www/index.tsx +3012 -3012
  94. package/apps/www/messages/[uid].tsx +139 -139
  95. package/apps/www/settings/auto-reply/index.tsx +136 -136
  96. package/apps/www/settings/blocked-senders/index.tsx +303 -303
  97. package/apps/www/settings/encryption/index.tsx +1290 -1290
  98. package/apps/www/settings/filters/[uid].tsx +179 -179
  99. package/apps/www/settings/filters/index.tsx +105 -105
  100. package/apps/www/settings/filters/new/index.tsx +165 -165
  101. package/apps/www/settings/labels/index.tsx +207 -207
  102. package/apps/www/settings/privacy/index.tsx +495 -495
  103. package/apps/www/settings/profile/index.tsx +251 -251
  104. package/apps/www/settings/read-receipts/index.tsx +150 -150
  105. package/apps/www/settings/sharing/index.tsx +259 -259
  106. package/apps/www/settings/signatures/[uid].tsx +175 -175
  107. package/apps/www/settings/signatures/index.tsx +91 -91
  108. package/apps/www/settings/signatures/new/index.tsx +138 -138
  109. package/apps/www/tasks/index.tsx +654 -654
  110. package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -2
  111. package/dist/apps/shared/components/admin/settings/BrandingForm.js +3 -3
  112. package/dist/apps/shared/components/escrow/layout/EscrowShell.js +2 -2
  113. package/dist/apps/shared/components/layout/AppShell.js +4 -2
  114. package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +7 -1
  115. package/dist/apps/shared/components/mail/compose/ComposeWindow.js +20 -1
  116. package/dist/apps/shared/components/mail/reading/printMessage.js +11 -11
  117. package/dist/apps/shared/styles/app.css +396 -396
  118. package/package.json +2 -2
@@ -9,7 +9,7 @@ import { useRouter } from "@rapidrest/react/client";
9
9
  import { HiOutlineClipboardDocumentList, HiOutlineClock, HiOutlineCpuChip, HiOutlineDocumentArrowDown, HiOutlineDocumentCheck, HiOutlineGlobeAlt, HiOutlineInboxStack, HiOutlineKey, HiOutlineLockClosed, HiOutlinePaintBrush, HiOutlinePuzzlePiece, HiOutlineQueueList, HiOutlineRocketLaunch, HiOutlineShieldCheck, HiOutlineShieldExclamation, HiOutlineUserGroup, HiOutlineWrenchScrewdriver, HiOutlineBars3, } from "react-icons/hi2";
10
10
  import { apiFetch, ApiRequestError } from "@rapidmx/react-shared/util/api.js";
11
11
  import { getSetupStatus } from "@rapidmx/react-shared/admin/setupApi.js";
12
- import { useRedirectIfUnauthenticated } from "@rapidmx/react-shared/auth/session.js";
12
+ import { useSessionRefresh } from "@rapidmx/react-shared/auth/session.js";
13
13
  import useBranding from "@rapidmx/react-shared/branding/useBranding.js";
14
14
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
15
15
  import Button from "@rapidmx/react-shared/components/buttons/Button.js";
@@ -118,7 +118,7 @@ export default function AdminShell({ active, userUid, authServerUrl, pluginNav,
118
118
  // console. `useBranding()` is still what injects the custom stylesheet and supplies the rail's icon.
119
119
  const { branding, iconSrc } = useBranding();
120
120
  const footer = useBrandingHtml(branding?.footerHtml);
121
- useRedirectIfUnauthenticated(userUid, authServerUrl);
121
+ useSessionRefresh(userUid, authServerUrl);
122
122
  useEffect(() => {
123
123
  if (!userUid) {
124
124
  return;
@@ -12,9 +12,9 @@ import CopyButton from "@rapidmx/react-shared/components/buttons/CopyButton.js";
12
12
  const INPUT_CLASS = "w-full text-sm py-2 px-3 border border-border rounded-sm bg-surface text-text focus:outline-none focus:border-primary";
13
13
  const MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
14
14
  /** A header that uses both variables - the snippet the Branding page offers to copy. */
15
- export const HEADER_HTML_EXAMPLE = `<div style="display:flex; align-items:center; justify-content:space-between; padding:0.5rem 1rem">
16
- <span>{APP_TITLE}</span>
17
- <span>{USER_MENU}</span>
15
+ export const HEADER_HTML_EXAMPLE = `<div style="display:flex; align-items:center; justify-content:space-between; padding:0.5rem 1rem">
16
+ <span>{APP_TITLE}</span>
17
+ <span>{USER_MENU}</span>
18
18
  </div>`;
19
19
  /** SVG logo/icon uploads are refused (the server refuses them too): an SVG served from this origin can carry script. */
20
20
  export function isSvgFile(file) {
@@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
8
8
  import { HiOutlineClipboardDocumentList, HiOutlineFolderOpen } from "react-icons/hi2";
9
9
  import { ApiRequestError } from "@rapidmx/react-shared/util/api.js";
10
10
  import { listMatters } from "@rapidmx/react-shared/admin/mattersApi.js";
11
- import { useRedirectIfUnauthenticated } from "@rapidmx/react-shared/auth/session.js";
11
+ import { useSessionRefresh } from "@rapidmx/react-shared/auth/session.js";
12
12
  import { getBranding } from "@rapidmx/react-shared/branding/brandingApi.js";
13
13
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
14
14
  import BottomTabBar from "@rapidmx/react-shared/components/navigation/BottomTabBar.js";
@@ -41,7 +41,7 @@ export default function EscrowShell({ active, userUid, authServerUrl, children }
41
41
  const [status, setStatus] = useState("checking");
42
42
  const [error, setError] = useState(null);
43
43
  const [iconSrc, setIconSrc] = useState(DEFAULT_ICON_SRC);
44
- useRedirectIfUnauthenticated(userUid, authServerUrl);
44
+ useSessionRefresh(userUid, authServerUrl);
45
45
  // Only the icon is taken from branding here. The escrow console deliberately skips `useBranding()`, which
46
46
  // would inject the custom stylesheet, and never renders the admin-configured header/footer HTML - this
47
47
  // console handles escrow key material, so no admin-supplied markup or CSS runs on it.
@@ -7,7 +7,7 @@ import "../../styles/app.css";
7
7
  import { useEffect, useLayoutEffect, useRef, useState } from "react";
8
8
  import { HiOutlineCalendarDays, HiOutlineClipboardDocumentList, HiOutlineEnvelope, HiOutlinePuzzlePiece, HiOutlineUsers, } from "react-icons/hi2";
9
9
  import { useRouter } from "@rapidrest/react/client";
10
- import { useRedirectIfUnauthenticated } from "@rapidmx/react-shared/auth/session.js";
10
+ import { useSessionRefresh } from "@rapidmx/react-shared/auth/session.js";
11
11
  import { getSetupStatus } from "@rapidmx/react-shared/admin/setupApi.js";
12
12
  import { stopImpersonating } from "@rapidmx/react-shared/mail/mailApi.js";
13
13
  import useBranding from "@rapidmx/react-shared/branding/useBranding.js";
@@ -118,7 +118,9 @@ export function AppChrome({ active, userUid, authServerUrl, impersonating, imper
118
118
  useSigningEnrollmentWatcher({ userUid, mailboxes: mail.mailboxes, enabled: inFrame });
119
119
  // A meeting reminder pops up on whichever page they are - see the hook.
120
120
  useCalendarReminders({ userUid, enabled: inFrame });
121
- useRedirectIfUnauthenticated(userUid, authServerUrl);
121
+ // A refused refresh means the sign-in has ended: open compose windows save what they hold (the access token still works for a while) and the
122
+ // browser goes to sign-in. Not while viewing as another user - the refresh cookie is the admin's own, and refreshing would end the impersonation.
123
+ useSessionRefresh(userUid, authServerUrl, { paused: impersonating, beforeRedirect: () => flushComposeDrafts(LOGOUT_TIMEOUT_MS) });
122
124
  // Any request this app makes that the server answers with a 401 - the session ended - raises one "Your session expired" pop-up with a
123
125
  // Sign in action (see `notifySessionExpired()`), whichever request noticed first, a background refresh included.
124
126
  useEffect(() => {
@@ -12,12 +12,18 @@ export interface ComposeWindowProps {
12
12
  /** How long (ms) after the last edit the draft is autosaved. Defaults to `DEFAULT_AUTOSAVE_DELAY_MS`;
13
13
  * only overridden by tests. */
14
14
  autosaveDelayMs?: number;
15
+ /** The longest (ms) an unsaved edit waits before the draft is saved even though typing hasn't paused - so a
16
+ * message written without a 2-second break is still saved regularly. Defaults to `DEFAULT_AUTOSAVE_MAX_WAIT_MS`
17
+ * and is never shorter than `autosaveDelayMs`; only overridden by tests. */
18
+ autosaveMaxWaitMs?: number;
15
19
  /** Backoff (ms) between automatic retries of a failed mailbox/encryption-policy load, one entry per retry.
16
20
  * Defaults to `DEFAULT_CRYPTO_RETRY_DELAYS_MS`; only overridden by tests. */
17
21
  cryptoRetryDelaysMs?: number[];
18
22
  }
19
23
  /** Debounce between the last edit and the draft autosave. */
20
24
  export declare const DEFAULT_AUTOSAVE_DELAY_MS = 2000;
25
+ /** The longest an edit stays unsaved while the user keeps typing (the debounce above restarts on every keystroke, so it alone never fires then). */
26
+ export declare const DEFAULT_AUTOSAVE_MAX_WAIT_MS = 30000;
21
27
  /** Automatic, silent retries of a failed mailbox/encryption-policy (or recipient key) load, one entry per retry - a flapping gateway is waited out
22
28
  * in the background, without a word to the user. */
23
29
  export declare const DEFAULT_CRYPTO_RETRY_DELAYS_MS: number[];
@@ -35,4 +41,4 @@ export declare const DEFAULT_CRYPTO_RETRY_DELAYS_MS: number[];
35
41
  * see `ComposeContext.tsx`'s own doc comment for how that interacts with several sessions being open at
36
42
  * once.
37
43
  */
38
- export default function ComposeWindow({ session, onClose, onToggleMinimize, userUid, trusted, autosaveDelayMs, cryptoRetryDelaysMs, }: ComposeWindowProps): React.JSX.Element;
44
+ export default function ComposeWindow({ session, onClose, onToggleMinimize, userUid, trusted, autosaveDelayMs, autosaveMaxWaitMs, cryptoRetryDelaysMs, }: ComposeWindowProps): React.JSX.Element;
@@ -36,6 +36,8 @@ import Button from "@rapidmx/react-shared/components/buttons/Button.js";
36
36
  import Modal from "@rapidmx/react-shared/components/overlays/Modal.js";
37
37
  /** Debounce between the last edit and the draft autosave. */
38
38
  export const DEFAULT_AUTOSAVE_DELAY_MS = 2000;
39
+ /** The longest an edit stays unsaved while the user keeps typing (the debounce above restarts on every keystroke, so it alone never fires then). */
40
+ export const DEFAULT_AUTOSAVE_MAX_WAIT_MS = 30000;
39
41
  /** Automatic, silent retries of a failed mailbox/encryption-policy (or recipient key) load, one entry per retry - a flapping gateway is waited out
40
42
  * in the background, without a word to the user. */
41
43
  export const DEFAULT_CRYPTO_RETRY_DELAYS_MS = [1000, 2000, 4000, 8000, 15000, 30000, 60000];
@@ -96,7 +98,7 @@ function HeaderButton({ label, onClick, icon: Icon, disabled, shortcut, }) {
96
98
  * see `ComposeContext.tsx`'s own doc comment for how that interacts with several sessions being open at
97
99
  * once.
98
100
  */
99
- export default function ComposeWindow({ session, onClose, onToggleMinimize, userUid, trusted, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, cryptoRetryDelaysMs = DEFAULT_CRYPTO_RETRY_DELAYS_MS, }) {
101
+ export default function ComposeWindow({ session, onClose, onToggleMinimize, userUid, trusted, autosaveDelayMs = DEFAULT_AUTOSAVE_DELAY_MS, autosaveMaxWaitMs = DEFAULT_AUTOSAVE_MAX_WAIT_MS, cryptoRetryDelaysMs = DEFAULT_CRYPTO_RETRY_DELAYS_MS, }) {
100
102
  const { id, initialTo, initialCc, initialSubject, initialQuotedHtml, initialEncrypt, signatureContext, suppressSigning, minimized, quotePending, late, resume } = session;
101
103
  // What a reply worked out after this window opened (see `OpenComposeInput.pending`): better recipients, applied below
102
104
  // only to fields still exactly as they opened - so these, not `initialTo`/`initialCc`, are what "untouched" is measured against.
@@ -197,6 +199,8 @@ export default function ComposeWindow({ session, onClose, onToggleMinimize, user
197
199
  const lastSavedRef = useRef(null);
198
200
  /** An edit is waiting on the autosave debounce - flushed if the window unmounts first. */
199
201
  const pendingSaveRef = useRef(false);
202
+ /** Fires the save when an edit has waited `autosaveMaxWaitMs` without the debounce ever getting a pause to fire in. */
203
+ const maxWaitTimerRef = useRef(undefined);
200
204
  /** Send was pressed and is being validated/handed over: a second press (a double click, the shortcut held or repeated) does nothing. */
201
205
  const submittingRef = useRef(false);
202
206
  /** Attachment/inline-image uploads on the wire, and who is waiting for them to finish (Send pressed meanwhile). */
@@ -751,6 +755,8 @@ export default function ComposeWindow({ session, onClose, onToggleMinimize, user
751
755
  const target = latestRef.current.draft;
752
756
  const previous = saveInFlightRef.current;
753
757
  pendingSaveRef.current = false;
758
+ clearTimeout(maxWaitTimerRef.current);
759
+ maxWaitTimerRef.current = undefined;
754
760
  setSaveStatus("saving");
755
761
  const outcome = (async () => {
756
762
  const before = await previous;
@@ -1022,14 +1028,27 @@ export default function ComposeWindow({ session, onClose, onToggleMinimize, user
1022
1028
  lastSavedRef.current !== `${draft.uid}:${contentKey}`;
1023
1029
  pendingSaveRef.current = due;
1024
1030
  if (!due) {
1031
+ clearTimeout(maxWaitTimerRef.current);
1032
+ maxWaitTimerRef.current = undefined;
1025
1033
  return;
1026
1034
  }
1035
+ // The debounce below restarts on every edit, so steady typing never lets it fire: this timer, started by the first unsaved edit and stopped
1036
+ // by the save that follows, is what saves such a message at a regular interval.
1037
+ if (maxWaitTimerRef.current === undefined) {
1038
+ maxWaitTimerRef.current = setTimeout(() => {
1039
+ maxWaitTimerRef.current = undefined;
1040
+ if (pendingSaveRef.current && !finishedRef.current) {
1041
+ void saveDraftNow();
1042
+ }
1043
+ }, Math.max(autosaveMaxWaitMs, autosaveDelayMs));
1044
+ }
1027
1045
  const timer = setTimeout(() => void saveDraftNow(), autosaveDelayMs);
1028
1046
  return () => clearTimeout(timer);
1029
1047
  }, [draft, contentReady, autosaveSuppressed, hasUserContent, contentKey]);
1030
1048
  // Unmounting with an edit still waiting on the debounce (e.g. the whole app shell unmounting) saves it
1031
1049
  // right away rather than losing it.
1032
1050
  useEffect(() => () => {
1051
+ clearTimeout(maxWaitTimerRef.current);
1033
1052
  if (pendingSaveRef.current && !finishedRef.current) {
1034
1053
  void saveDraftNow();
1035
1054
  }
@@ -1,17 +1,17 @@
1
1
  import { attachmentSource, makeCidResolver } from "./bodyContent.js";
2
2
  import { prepareBodyHtml } from "./bodyHtml.js";
3
3
  import { frameCsp } from "./frameDocument.js";
4
- const PRINT_CSS = `
5
- html{font:14px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:#000;background:#fff}
6
- body{margin:0;padding:24px}
7
- h1{font-size:20px;margin:0 0 12px}
8
- dl{margin:0 0 12px;display:grid;grid-template-columns:max-content 1fr;gap:2px 12px}
9
- dt{font-weight:600;color:#444}
10
- dd{margin:0;overflow-wrap:anywhere}
11
- hr{border:0;border-top:1px solid #999;margin:0 0 16px}
12
- #rr-body{overflow-wrap:break-word}
13
- #rr-body img{max-width:100%;height:auto}
14
- #rr-body pre,pre.rr-text{white-space:pre-wrap;font:inherit;margin:0}
4
+ const PRINT_CSS = `
5
+ html{font:14px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:#000;background:#fff}
6
+ body{margin:0;padding:24px}
7
+ h1{font-size:20px;margin:0 0 12px}
8
+ dl{margin:0 0 12px;display:grid;grid-template-columns:max-content 1fr;gap:2px 12px}
9
+ dt{font-weight:600;color:#444}
10
+ dd{margin:0;overflow-wrap:anywhere}
11
+ hr{border:0;border-top:1px solid #999;margin:0 0 16px}
12
+ #rr-body{overflow-wrap:break-word}
13
+ #rr-body img{max-width:100%;height:auto}
14
+ #rr-body pre,pre.rr-text{white-space:pre-wrap;font:inherit;margin:0}
15
15
  `;
16
16
  export function escapeHtml(text) {
17
17
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");