@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.
- package/README.md +381 -381
- package/apps/admin/branding/index.tsx +39 -39
- package/apps/admin/data-requests/index.tsx +482 -482
- package/apps/admin/domains/[uid].tsx +166 -166
- package/apps/admin/escrow-scopes/[uid].tsx +350 -350
- package/apps/admin/index.tsx +129 -129
- package/apps/admin/mailboxes/[uid].tsx +271 -271
- package/apps/admin/mailboxes/new/index.tsx +30 -30
- package/apps/admin/plugins/index.tsx +15 -15
- package/apps/admin/retention-policy/index.tsx +39 -39
- package/apps/admin/signing-certificates/index.tsx +343 -343
- package/apps/escrow/audit-log/index.tsx +196 -196
- package/apps/escrow/matters/[uid].tsx +621 -621
- package/apps/shared/auth/adminAccess.ts +99 -99
- package/apps/shared/components/admin/layout/AdminShell.tsx +384 -384
- package/apps/shared/components/admin/mailboxes/EraseLeftoverDataDialog.tsx +238 -238
- package/apps/shared/components/admin/mailboxes/EscrowScopeCard.tsx +152 -152
- package/apps/shared/components/admin/mailboxes/LeftoverMailboxesSection.tsx +194 -194
- package/apps/shared/components/admin/settings/BrandingForm.tsx +423 -423
- package/apps/shared/components/admin/settings/EncryptionPolicyForm.tsx +119 -119
- package/apps/shared/components/admin/settings/MailboxPolicyForm.tsx +198 -198
- package/apps/shared/components/admin/settings/PluginsManager.tsx +2093 -2093
- package/apps/shared/components/admin/settings/RetentionPolicyForm.tsx +182 -182
- package/apps/shared/components/admin/setup/EscrowSetupStep.tsx +288 -288
- package/apps/shared/components/admin/setup/SetupWizard.tsx +446 -446
- package/apps/shared/components/admin/usePagedList.tsx +129 -129
- package/apps/shared/components/calendar/EventModal.tsx +206 -206
- package/apps/shared/components/calendar/MonthView.tsx +185 -185
- package/apps/shared/components/calendar/RecurrenceEditor.tsx +227 -227
- package/apps/shared/components/calendar/SplitDayView.tsx +144 -144
- package/apps/shared/components/calendar/TimeGridView.tsx +246 -246
- package/apps/shared/components/calendar/allDay.ts +124 -124
- package/apps/shared/components/contacts/ContactForm.tsx +383 -383
- package/apps/shared/components/contacts/ContactsToolbar.tsx +103 -103
- package/apps/shared/components/escrow/layout/EscrowShell.tsx +155 -155
- package/apps/shared/components/layout/AppShell.tsx +463 -461
- package/apps/shared/components/layout/KeyEnrollmentGate.tsx +398 -398
- package/apps/shared/components/layout/MailboxProvisioning.tsx +168 -168
- package/apps/shared/components/layout/ResponsiveToolbar.tsx +315 -315
- package/apps/shared/components/layout/ThemeSwitch.tsx +84 -84
- package/apps/shared/components/layout/UserMenu.tsx +439 -439
- package/apps/shared/components/mail/ConversationList.tsx +332 -332
- package/apps/shared/components/mail/ConversationThreadPane.tsx +613 -613
- package/apps/shared/components/mail/MailSelectionBar.tsx +240 -240
- package/apps/shared/components/mail/MenuButton.tsx +404 -404
- package/apps/shared/components/mail/MessageDetailPane.tsx +1757 -1757
- package/apps/shared/components/mail/compose/ComposeContext.tsx +312 -312
- package/apps/shared/components/mail/compose/ComposeToolbar.tsx +422 -422
- package/apps/shared/components/mail/compose/ComposeWindow.tsx +1705 -1681
- package/apps/shared/components/mail/compose/RichTextEditor.tsx +147 -147
- package/apps/shared/components/mail/compose/composeFlushRegistry.ts +60 -60
- package/apps/shared/components/mail/compose/quotedBody.ts +161 -161
- package/apps/shared/components/mail/reading/MessageMoreMenu.tsx +258 -258
- package/apps/shared/components/mail/reading/MessageSourceDialog.tsx +79 -79
- package/apps/shared/components/mail/reading/messageExport.ts +59 -59
- package/apps/shared/components/mail/reading/printMessage.ts +99 -99
- package/apps/shared/components/mail/reading/useMessageActions.ts +443 -443
- package/apps/shared/components/mail/verificationSeals.ts +125 -125
- package/apps/shared/components/rules/RuleBuilder.tsx +311 -311
- package/apps/shared/components/settings/SigningCertificateCard.tsx +344 -344
- package/apps/shared/keyboard/GlobalShortcuts.tsx +51 -51
- package/apps/shared/keyboard/ShortcutProvider.tsx +62 -62
- package/apps/shared/keyboard/ShortcutsDialog.tsx +84 -84
- package/apps/shared/keyboard/dispatch.ts +124 -124
- package/apps/shared/keyboard/format.ts +89 -89
- package/apps/shared/keyboard/keymap.ts +114 -114
- package/apps/shared/keyboard/registry.ts +65 -65
- package/apps/shared/keyboard/targets.ts +79 -79
- package/apps/shared/mail/folderOfType.ts +49 -49
- package/apps/shared/mail/folderTree.ts +143 -143
- package/apps/shared/mail/listAllPages.ts +39 -39
- package/apps/shared/mail/newMailNotifications.ts +171 -171
- package/apps/shared/mail/outbox/sendJob.ts +445 -445
- package/apps/shared/mail/outbox/sendOutcomes.ts +155 -155
- package/apps/shared/mail/reportNotices.ts +66 -66
- package/apps/shared/mail/senderBlocking.ts +141 -141
- package/apps/shared/mail/useMailConnection.ts +205 -205
- package/apps/shared/mail/useMailLiveUpdates.ts +277 -277
- package/apps/shared/mail/useMailboxUpdateAccess.ts +60 -60
- package/apps/shared/mail/useMarkMessageRead.ts +47 -47
- package/apps/shared/mail/useNewMailNotifications.ts +178 -178
- package/apps/shared/notifications/store.ts +560 -560
- package/apps/shared/search/LocalIndexLifecycle.tsx +114 -114
- package/apps/shared/search/localIndexBuilder.ts +481 -481
- package/apps/shared/signing/enrollmentStorage.ts +33 -33
- package/apps/shared/signing/enrollmentTracker.ts +385 -385
- package/apps/shared/signing/enrollmentView.ts +251 -251
- package/apps/shared/signing/useNow.ts +19 -19
- package/apps/shared/signing/useSigningEnrollmentWatcher.ts +90 -90
- package/apps/shared/styles/app.css +396 -396
- package/apps/www/calendar/index.tsx +581 -581
- package/apps/www/contacts/[uid].tsx +112 -112
- package/apps/www/index.tsx +3012 -3012
- package/apps/www/messages/[uid].tsx +139 -139
- package/apps/www/settings/auto-reply/index.tsx +136 -136
- package/apps/www/settings/blocked-senders/index.tsx +303 -303
- package/apps/www/settings/encryption/index.tsx +1290 -1290
- package/apps/www/settings/filters/[uid].tsx +179 -179
- package/apps/www/settings/filters/index.tsx +105 -105
- package/apps/www/settings/filters/new/index.tsx +165 -165
- package/apps/www/settings/labels/index.tsx +207 -207
- package/apps/www/settings/privacy/index.tsx +495 -495
- package/apps/www/settings/profile/index.tsx +251 -251
- package/apps/www/settings/read-receipts/index.tsx +150 -150
- package/apps/www/settings/sharing/index.tsx +259 -259
- package/apps/www/settings/signatures/[uid].tsx +175 -175
- package/apps/www/settings/signatures/index.tsx +91 -91
- package/apps/www/settings/signatures/new/index.tsx +138 -138
- package/apps/www/tasks/index.tsx +654 -654
- package/dist/apps/shared/components/admin/layout/AdminShell.js +2 -2
- package/dist/apps/shared/components/admin/settings/BrandingForm.js +3 -3
- package/dist/apps/shared/components/escrow/layout/EscrowShell.js +2 -2
- package/dist/apps/shared/components/layout/AppShell.js +4 -2
- package/dist/apps/shared/components/mail/compose/ComposeWindow.d.ts +7 -1
- package/dist/apps/shared/components/mail/compose/ComposeWindow.js +20 -1
- package/dist/apps/shared/components/mail/reading/printMessage.js +11 -11
- package/dist/apps/shared/styles/app.css +396 -396
- 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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|