fontdue-js 3.0.0-alpha9 → 3.0.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/CHANGELOG.md +14 -0
- package/README.md +182 -13
- package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderCompleteOrderMutation.graphql.js +9 -3
- package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderRemoveDiscountMutation.graphql.js +9 -3
- package/dist/__generated__/CartOrderUpdateMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartOrderUpdateMutation.graphql.js +9 -3
- package/dist/__generated__/CartQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CartQuery.graphql.js +9 -3
- package/dist/__generated__/CartStateUpdateMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CartStateUpdateMutation.graphql.js +9 -3
- package/dist/__generated__/CharacterViewerIDQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CharacterViewerIDQuery.graphql.js +9 -3
- package/dist/__generated__/CharacterViewerSlugQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CharacterViewerSlugQuery.graphql.js +9 -3
- package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/CharacterViewerStyleRefetchQuery.graphql.js +9 -3
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CheckoutUpdateCustomerMutation.graphql.js +9 -3
- package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.d.ts +1 -1
- package/dist/__generated__/CheckoutUpdateOrderMutation.graphql.js +9 -3
- package/dist/__generated__/CollectionAa_Query.graphql.d.ts +1 -1
- package/dist/__generated__/CollectionAa_Query.graphql.js +9 -3
- package/dist/__generated__/FontFamiliesQuery.graphql.d.ts +1 -1
- package/dist/__generated__/FontFamiliesQuery.graphql.js +9 -3
- package/dist/__generated__/FontdueAdminToolbarQuery.graphql.d.ts +20 -0
- package/dist/__generated__/FontdueAdminToolbarQuery.graphql.js +80 -0
- package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.d.ts +18 -0
- package/dist/__generated__/FontdueAdminToolbarTokenMutation.graphql.js +56 -0
- package/dist/__generated__/PrecartAddToCartMutation.graphql.d.ts +1 -1
- package/dist/__generated__/PrecartAddToCartMutation.graphql.js +9 -3
- package/dist/__generated__/StoreModalCartQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalCartQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalContainerQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalContainerQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalIndexQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalIndexQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalProductQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductQuery.graphql.js +9 -3
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/StoreModalProductRefetchQuery.graphql.js +9 -3
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.d.ts +1 -1
- package/dist/__generated__/TestFontsFormUpdateCustomerMutation.graphql.js +9 -3
- package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTesterStandaloneChangedStylesQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTesterStandaloneQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTesterStandaloneQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersChangedStylesQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersIDQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersIDQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersRefetchQuery.graphql.js +9 -3
- package/dist/__generated__/TypeTestersSlugQuery.graphql.d.ts +1 -1
- package/dist/__generated__/TypeTestersSlugQuery.graphql.js +9 -3
- package/dist/__generated__/useFontStyle_fontStyle.graphql.d.ts +2 -1
- package/dist/__generated__/useFontStyle_fontStyle.graphql.js +8 -2
- package/dist/__tests__/createFontdueFetch.test.js +276 -0
- package/dist/__tests__/imageLoader.test.js +62 -0
- package/dist/__tests__/metricFallback.test.js +74 -0
- package/dist/__tests__/networkFetch.test.js +125 -3
- package/dist/__tests__/nextAdapter.test.js +175 -60
- package/dist/__tests__/preview.test.js +217 -0
- package/dist/__tests__/previewServer.test.js +118 -0
- package/dist/__tests__/previewState.test.js +63 -0
- package/dist/__tests__/serverConfig.test.js +62 -0
- package/dist/components/BuyButton/index.d.ts +2 -2
- package/dist/components/BuyButton/index.js +3 -3
- package/dist/components/CharacterViewer/index.d.ts +2 -2
- package/dist/components/CharacterViewer/index.js +20 -11
- package/dist/components/ConfigContext.d.ts +21 -2
- package/dist/components/ConfigContext.js +12 -2
- package/dist/components/ConnectionErrorToolbar.d.ts +1 -0
- package/dist/components/ConnectionErrorToolbar.js +106 -0
- package/dist/components/FontdueAdminToolbar/index.d.ts +2 -0
- package/dist/components/FontdueAdminToolbar/index.js +299 -0
- package/dist/components/FontdueAdminToolbar/previewState.d.ts +7 -0
- package/dist/components/FontdueAdminToolbar/previewState.js +58 -0
- package/dist/components/FontdueContextProvider/index.js +4 -2
- package/dist/components/FontdueProvider/index.js +6 -1
- package/dist/components/FontdueProvider/index.server.d.ts +1 -0
- package/dist/components/FontdueProvider/index.server.js +10 -0
- package/dist/components/NewsletterSignup/index.d.ts +2 -2
- package/dist/components/NewsletterSignup/index.js +2 -2
- package/dist/components/Root/index.js +16 -2
- package/dist/components/TestFontsForm/index.d.ts +2 -2
- package/dist/components/TestFontsForm/index.js +2 -2
- package/dist/components/TypeTester/TypeTesterStandalone.d.ts +2 -2
- package/dist/components/TypeTester/TypeTesterStandalone.js +2 -2
- package/dist/components/TypeTesters/index.d.ts +2 -2
- package/dist/components/TypeTesters/index.js +3 -3
- package/dist/components/useFontStyle.d.ts +1 -0
- package/dist/components/useFontStyle.js +12 -3
- package/dist/corsError.d.ts +1 -5
- package/dist/corsError.js +23 -13
- package/dist/data/unicodeNamesUrl.d.ts +2 -0
- package/dist/data/unicodeNamesUrl.js +18 -0
- package/dist/data/unicodeNamesVersion.d.ts +1 -0
- package/dist/data/unicodeNamesVersion.js +4 -0
- package/dist/fallbackFontData.d.ts +2 -0
- package/dist/fallbackFontData.js +10 -0
- package/dist/fontdue.css +231 -4
- package/dist/loadFontdueProviderQuery.d.ts +2 -1
- package/dist/loadFontdueProviderQuery.js +5 -2
- package/dist/metricFallback.d.ts +48 -0
- package/dist/metricFallback.js +98 -0
- package/dist/next/image-loader.js +22 -3
- package/dist/next/index.d.ts +1 -2
- package/dist/next/index.js +14 -6
- package/dist/next/registerSingleTenantResolver.d.ts +1 -0
- package/dist/next/registerSingleTenantResolver.js +35 -0
- package/dist/next/revalidate.js +1 -1
- package/dist/next/tenant.d.ts +4 -4
- package/dist/next/tenant.js +89 -58
- package/dist/preview/constants.d.ts +9 -0
- package/dist/preview/constants.js +117 -0
- package/dist/preview/index.d.ts +53 -0
- package/dist/preview/index.js +190 -0
- package/dist/preview/server.d.ts +20 -0
- package/dist/preview/server.js +89 -0
- package/dist/relay/environment.d.ts +8 -0
- package/dist/relay/environment.js +81 -35
- package/dist/relay/loadSerializableQuery.d.ts +13 -3
- package/dist/relay/loadSerializableQuery.js +2 -0
- package/dist/relay/serverConfig.d.ts +5 -7
- package/dist/relay/serverConfig.js +83 -8
- package/dist/scripts/publishUnicodeData.js +68 -0
- package/dist/scripts/updateUnicodeData.js +41 -6
- package/dist/server/index.d.ts +37 -0
- package/dist/server/index.js +160 -0
- package/package.json +5 -1
- package/types/next-headers.d.ts +9 -0
- package/types/next-navigation.d.ts +4 -0
- package/vitest.config.ts +5 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import _FontdueAdminToolbarTokenMutation from "../../__generated__/FontdueAdminToolbarTokenMutation.graphql.js";
|
|
4
|
+
import _FontdueAdminToolbarQuery from "../../__generated__/FontdueAdminToolbarQuery.graphql.js";
|
|
5
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
6
|
+
import { commitMutation, fetchQuery, graphql, useRelayEnvironment } from 'react-relay';
|
|
7
|
+
import ConfigContext from '../ConfigContext.js';
|
|
8
|
+
import { useFontdueUrl } from '../UrlContext.js';
|
|
9
|
+
import { PREVIEW_ENDPOINT, DEFAULT_PREVIEW_TTL_MS, setPreviewMarkerCookie } from '../../preview/constants.js';
|
|
10
|
+
import { readPreviewState, expiresAtFromTokenResult, isSessionExpiredError } from './previewState.js';
|
|
11
|
+
import { fontdueBaseUrl, version } from '../../relay/environment.js';
|
|
12
|
+
// Admin-only affordance for logged-in foundry admins: reveal unpublished
|
|
13
|
+
// ("hidden") fonts across the whole storefront. Storefront pages are
|
|
14
|
+
// server-rendered sessionless and cached, and the provider's own query is
|
|
15
|
+
// preloaded server-side without the session — so `adminUser` can't ride it.
|
|
16
|
+
// Admin presence is detected here, client-side, where the request carries
|
|
17
|
+
// the session; the public never sees this control.
|
|
18
|
+
//
|
|
19
|
+
// The reveal itself is not done here: entering preview brokers a short-lived
|
|
20
|
+
// admin token and hands it to the framework's preview route, which switches
|
|
21
|
+
// the whole app to a live, token-bearing render. Exiting clears it. Public
|
|
22
|
+
// renders never touch any of this and stay static and cached.
|
|
23
|
+
const adminQuery = (_FontdueAdminToolbarQuery.hash && _FontdueAdminToolbarQuery.hash !== "8660b45f086137d249eee82459ad648d" && console.error("The definition of 'FontdueAdminToolbarQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _FontdueAdminToolbarQuery);
|
|
24
|
+
|
|
25
|
+
// createAdminToken mints a short-lived, stateless admin token from the admin
|
|
26
|
+
// session; it's handed to the preview route to enter draft mode.
|
|
27
|
+
const tokenMutation = (_FontdueAdminToolbarTokenMutation.hash && _FontdueAdminToolbarTokenMutation.hash !== "2b82f195747c86d50fd5884d76f3b709" && console.error("The definition of 'FontdueAdminToolbarTokenMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _FontdueAdminToolbarTokenMutation);
|
|
28
|
+
export default function FontdueAdminToolbar() {
|
|
29
|
+
const environment = useRelayEnvironment();
|
|
30
|
+
const previewConfig = useContext(ConfigContext).preview;
|
|
31
|
+
// The preview entry/exit route is part of the portable preview contract (see
|
|
32
|
+
// fontdue-js/preview); it defaults to '/api/preview' but is configurable so
|
|
33
|
+
// non-Next apps can mount it where their router expects. The revalidate route
|
|
34
|
+
// is opt-in (no default) — see config.preview.revalidateEndpoint.
|
|
35
|
+
const previewEndpoint = (previewConfig === null || previewConfig === void 0 ? void 0 : previewConfig.endpoint) ?? PREVIEW_ENDPOINT;
|
|
36
|
+
const revalidateEndpoint = previewConfig === null || previewConfig === void 0 ? void 0 : previewConfig.revalidateEndpoint;
|
|
37
|
+
// Client-only embeds (the script-tag CDN bundle, a client SPA) have no server
|
|
38
|
+
// preview route to broker a token through, so they enter/exit preview by
|
|
39
|
+
// toggling the readable marker cookie directly — see setPreviewMarkerCookie.
|
|
40
|
+
const clientSidePreview = (previewConfig === null || previewConfig === void 0 ? void 0 : previewConfig.clientSide) ?? false;
|
|
41
|
+
|
|
42
|
+
// Where the admin is signed in: the configured Fontdue origin. Used for the
|
|
43
|
+
// "signed in at" line and the deep link back to the admin. Falls back to the
|
|
44
|
+
// URL the embed was initialized with (the script-tag bundle sets no
|
|
45
|
+
// FONTDUE_URL env); undefined (e.g. multi-tenant) drops both gracefully.
|
|
46
|
+
const contextUrl = useFontdueUrl();
|
|
47
|
+
const fontdueUrl = fontdueBaseUrl() ?? (contextUrl || undefined);
|
|
48
|
+
const fontdueHost = fontdueUrl ? safeHost(fontdueUrl) : null;
|
|
49
|
+
const adminUrl = fontdueUrl ? `${fontdueUrl.replace(/\/+$/, '')}/admin` : null;
|
|
50
|
+
const [adminName, setAdminName] = useState(null);
|
|
51
|
+
const [ready, setReady] = useState(false);
|
|
52
|
+
const [open, setOpen] = useState(false);
|
|
53
|
+
const [previewState, setPreviewState] = useState('off');
|
|
54
|
+
const [busy, setBusy] = useState(false);
|
|
55
|
+
const [error, setError] = useState(null);
|
|
56
|
+
const [notice, setNotice] = useState(null);
|
|
57
|
+
const previewing = previewState === 'active';
|
|
58
|
+
const expired = previewState === 'expired';
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setPreviewState(readPreviewState());
|
|
61
|
+
// network-only: the provider's preloaded query already wrote a sessionless
|
|
62
|
+
// `viewer` to the store, so we must go to the network (with the session)
|
|
63
|
+
// rather than read that cached, admin-blind copy.
|
|
64
|
+
const subscription = fetchQuery(environment, adminQuery, {}, {
|
|
65
|
+
fetchPolicy: 'network-only'
|
|
66
|
+
}).subscribe({
|
|
67
|
+
next: data => {
|
|
68
|
+
var _data$viewer;
|
|
69
|
+
const admin = (_data$viewer = data.viewer) === null || _data$viewer === void 0 ? void 0 : _data$viewer.adminUser;
|
|
70
|
+
if (admin) setAdminName(admin.name ?? 'admin');
|
|
71
|
+
},
|
|
72
|
+
error: () => setReady(true),
|
|
73
|
+
complete: () => setReady(true)
|
|
74
|
+
});
|
|
75
|
+
return () => subscription.unsubscribe();
|
|
76
|
+
}, [environment]);
|
|
77
|
+
|
|
78
|
+
// Re-derive the state when the tab regains focus or becomes visible. A tab
|
|
79
|
+
// left open past the token's expiry won't otherwise notice — there's no
|
|
80
|
+
// event when a cookie's encoded expiry passes — so the warning would never
|
|
81
|
+
// appear until the next reload. Recovery stays user-initiated; we only
|
|
82
|
+
// refresh what state is shown.
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (typeof window === 'undefined') return;
|
|
85
|
+
const recheck = () => setPreviewState(readPreviewState());
|
|
86
|
+
window.addEventListener('focus', recheck);
|
|
87
|
+
document.addEventListener('visibilitychange', recheck);
|
|
88
|
+
return () => {
|
|
89
|
+
window.removeEventListener('focus', recheck);
|
|
90
|
+
document.removeEventListener('visibilitychange', recheck);
|
|
91
|
+
};
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
// Hidden until we've confirmed an admin session. The public always lands here.
|
|
95
|
+
if (!ready || !adminName) return null;
|
|
96
|
+
async function startPreview(token, expiresAt) {
|
|
97
|
+
const res = await fetch(previewEndpoint, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: {
|
|
100
|
+
'content-type': 'application/json'
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
token,
|
|
104
|
+
expiresAt
|
|
105
|
+
})
|
|
106
|
+
});
|
|
107
|
+
if (res.ok) {
|
|
108
|
+
window.location.reload();
|
|
109
|
+
} else {
|
|
110
|
+
setBusy(false);
|
|
111
|
+
setError('Could not start preview.');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function enterPreview() {
|
|
115
|
+
setBusy(true);
|
|
116
|
+
setError(null);
|
|
117
|
+
setNotice(null);
|
|
118
|
+
// Client-only: no server render needs a token, so just mark preview on and
|
|
119
|
+
// reload — the admin's session cookie rides the next fetches and reveals
|
|
120
|
+
// hidden fonts now that the marker makes the client send `fontdue-preview`.
|
|
121
|
+
// There's no token to mint, so the marker just gets the default 1h expiry.
|
|
122
|
+
if (clientSidePreview) {
|
|
123
|
+
setPreviewMarkerCookie(Date.now() + DEFAULT_PREVIEW_TTL_MS);
|
|
124
|
+
window.location.reload();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
commitMutation(environment, {
|
|
128
|
+
mutation: tokenMutation,
|
|
129
|
+
variables: {},
|
|
130
|
+
onCompleted: (res, errors) => {
|
|
131
|
+
var _res$createAdminToken;
|
|
132
|
+
const token = (_res$createAdminToken = res.createAdminToken) === null || _res$createAdminToken === void 0 ? void 0 : _res$createAdminToken.token;
|
|
133
|
+
if (errors && errors.length > 0 || !token) {
|
|
134
|
+
setBusy(false);
|
|
135
|
+
// createAdminToken is gated on the admin session (current_user); a
|
|
136
|
+
// "Not authorized" error means the session itself has lapsed, which
|
|
137
|
+
// needs a fresh sign-in, not a retry. Surface that distinctly and
|
|
138
|
+
// clear the now-meaningless marker so the toolbar resets.
|
|
139
|
+
if (isSessionExpiredError(errors)) {
|
|
140
|
+
setPreviewMarkerCookie(false);
|
|
141
|
+
setPreviewState('off');
|
|
142
|
+
setError('Your session expired — sign in again.');
|
|
143
|
+
} else {
|
|
144
|
+
setError('Could not start preview.');
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
startPreview(token, expiresAtFromTokenResult(res.createAdminToken));
|
|
149
|
+
},
|
|
150
|
+
onError: () => {
|
|
151
|
+
setBusy(false);
|
|
152
|
+
setError('Could not start preview.');
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async function exitPreview() {
|
|
157
|
+
setBusy(true);
|
|
158
|
+
setError(null);
|
|
159
|
+
setNotice(null);
|
|
160
|
+
if (clientSidePreview) {
|
|
161
|
+
setPreviewMarkerCookie(false);
|
|
162
|
+
} else {
|
|
163
|
+
await fetch(previewEndpoint, {
|
|
164
|
+
method: 'DELETE'
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
window.location.reload();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Recover from the expired state: mint a fresh token and re-enter. This is the
|
|
171
|
+
// same enter flow — a successful POST overwrites the stale token + marker
|
|
172
|
+
// cookies and reloads, so there's nothing to clear first. If the session has
|
|
173
|
+
// also lapsed, enterPreview surfaces the distinct "sign in again" message.
|
|
174
|
+
function reEnterPreview() {
|
|
175
|
+
enterPreview();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Purge the cached storefront so public visitors see freshly published (or
|
|
179
|
+
// newly hidden) content. Targets the configured revalidate route as-is.
|
|
180
|
+
async function refreshCache() {
|
|
181
|
+
if (!revalidateEndpoint) return;
|
|
182
|
+
setBusy(true);
|
|
183
|
+
setError(null);
|
|
184
|
+
setNotice(null);
|
|
185
|
+
try {
|
|
186
|
+
const res = await fetch(revalidateEndpoint, {
|
|
187
|
+
method: 'POST'
|
|
188
|
+
});
|
|
189
|
+
if (!res.ok) throw new Error(`status ${res.status}`);
|
|
190
|
+
setNotice('Public cache refreshed.');
|
|
191
|
+
} catch {
|
|
192
|
+
setError('Could not refresh the cache.');
|
|
193
|
+
} finally {
|
|
194
|
+
setBusy(false);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
198
|
+
className: "fontdue-admin-toolbar",
|
|
199
|
+
"data-previewing": previewing,
|
|
200
|
+
"data-preview-state": previewState,
|
|
201
|
+
"data-testid": "fontdue-admin-toolbar"
|
|
202
|
+
}, open && /*#__PURE__*/React.createElement("div", {
|
|
203
|
+
className: "fontdue-admin-toolbar__panel"
|
|
204
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
205
|
+
className: "fontdue-admin-toolbar__header"
|
|
206
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
207
|
+
className: "fontdue-admin-toolbar__title"
|
|
208
|
+
}, "Fontdue"), /*#__PURE__*/React.createElement("span", {
|
|
209
|
+
className: "fontdue-admin-toolbar__user"
|
|
210
|
+
}, adminName)), expired ? /*#__PURE__*/React.createElement("div", {
|
|
211
|
+
className: "fontdue-admin-toolbar__expired",
|
|
212
|
+
role: "status",
|
|
213
|
+
"data-testid": "preview-expired"
|
|
214
|
+
}, /*#__PURE__*/React.createElement("p", {
|
|
215
|
+
className: "fontdue-admin-toolbar__expired-text"
|
|
216
|
+
}, /*#__PURE__*/React.createElement(WarningIcon, null), "Preview expired. Hidden fonts are no longer shown."), /*#__PURE__*/React.createElement("button", {
|
|
217
|
+
type: "button",
|
|
218
|
+
className: "fontdue-admin-toolbar__action",
|
|
219
|
+
onClick: reEnterPreview,
|
|
220
|
+
disabled: busy,
|
|
221
|
+
"data-testid": "reenter-preview-button"
|
|
222
|
+
}, "Re-enter preview")) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("label", {
|
|
223
|
+
className: "fontdue-admin-toolbar__toggle"
|
|
224
|
+
}, /*#__PURE__*/React.createElement("input", {
|
|
225
|
+
type: "checkbox",
|
|
226
|
+
checked: previewing,
|
|
227
|
+
disabled: busy,
|
|
228
|
+
onChange: e => e.target.checked ? enterPreview() : exitPreview(),
|
|
229
|
+
"data-testid": "preview-toggle"
|
|
230
|
+
}), /*#__PURE__*/React.createElement("span", null, "Preview hidden fonts")), /*#__PURE__*/React.createElement("p", {
|
|
231
|
+
className: "fontdue-admin-toolbar__hint"
|
|
232
|
+
}, previewing ? 'Unpublished fonts are visible across the site — only to you.' : 'Reveal unpublished fonts everywhere, only for you.')), (revalidateEndpoint || adminUrl) && /*#__PURE__*/React.createElement("div", {
|
|
233
|
+
className: "fontdue-admin-toolbar__actions"
|
|
234
|
+
}, revalidateEndpoint && /*#__PURE__*/React.createElement("button", {
|
|
235
|
+
type: "button",
|
|
236
|
+
className: "fontdue-admin-toolbar__action",
|
|
237
|
+
onClick: refreshCache,
|
|
238
|
+
disabled: busy,
|
|
239
|
+
"data-testid": "revalidate-button"
|
|
240
|
+
}, "Refresh public cache"), adminUrl && /*#__PURE__*/React.createElement("a", {
|
|
241
|
+
className: "fontdue-admin-toolbar__action",
|
|
242
|
+
href: adminUrl,
|
|
243
|
+
target: "_blank",
|
|
244
|
+
rel: "noreferrer",
|
|
245
|
+
"data-testid": "open-admin-link"
|
|
246
|
+
}, "Open Fontdue admin \u2197")), notice && /*#__PURE__*/React.createElement("p", {
|
|
247
|
+
className: "fontdue-admin-toolbar__notice"
|
|
248
|
+
}, notice), error && /*#__PURE__*/React.createElement("p", {
|
|
249
|
+
className: "fontdue-admin-toolbar__error"
|
|
250
|
+
}, error), /*#__PURE__*/React.createElement("p", {
|
|
251
|
+
className: "fontdue-admin-toolbar__meta"
|
|
252
|
+
}, fontdueHost ? `Shown because you’re signed in to Fontdue at ${fontdueHost}.` : 'Shown because you’re signed in to Fontdue.', /*#__PURE__*/React.createElement("span", {
|
|
253
|
+
className: "fontdue-admin-toolbar__version"
|
|
254
|
+
}, "fontdue-js ", version))), /*#__PURE__*/React.createElement("button", {
|
|
255
|
+
type: "button",
|
|
256
|
+
className: "fontdue-admin-toolbar__button",
|
|
257
|
+
onClick: () => setOpen(v => !v),
|
|
258
|
+
"aria-expanded": open,
|
|
259
|
+
"data-testid": "fontdue-admin-toolbar-button"
|
|
260
|
+
}, expired ? /*#__PURE__*/React.createElement(WarningIcon, null) : /*#__PURE__*/React.createElement("span", {
|
|
261
|
+
className: "fontdue-admin-toolbar__dot",
|
|
262
|
+
"aria-hidden": "true"
|
|
263
|
+
}), "Fontdue", previewing ? ' · previewing' : '', expired ? ' · preview expired' : ''));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Small inline warning glyph (a triangle with an exclamation). Inherits the
|
|
267
|
+
// surrounding text color so it stays monochrome with the toolbar chrome.
|
|
268
|
+
function WarningIcon() {
|
|
269
|
+
return /*#__PURE__*/React.createElement("svg", {
|
|
270
|
+
className: "fontdue-admin-toolbar__warning-icon",
|
|
271
|
+
width: "13",
|
|
272
|
+
height: "13",
|
|
273
|
+
viewBox: "0 0 16 16",
|
|
274
|
+
fill: "none",
|
|
275
|
+
stroke: "currentColor",
|
|
276
|
+
strokeWidth: "1.4",
|
|
277
|
+
"aria-hidden": "true",
|
|
278
|
+
focusable: "false"
|
|
279
|
+
}, /*#__PURE__*/React.createElement("path", {
|
|
280
|
+
d: "M8 1.5 15 14H1L8 1.5Z",
|
|
281
|
+
strokeLinejoin: "miter"
|
|
282
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
283
|
+
d: "M8 6v3.5",
|
|
284
|
+
strokeLinecap: "square"
|
|
285
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
286
|
+
d: "M8 11.5v.5",
|
|
287
|
+
strokeLinecap: "square"
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Hostname of a base URL, or the raw value if it can't be parsed (so a
|
|
292
|
+
// misconfigured URL still shows something rather than throwing).
|
|
293
|
+
function safeHost(url) {
|
|
294
|
+
try {
|
|
295
|
+
return new URL(url).host;
|
|
296
|
+
} catch {
|
|
297
|
+
return url;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type PreviewState = 'off' | 'active' | 'expired';
|
|
2
|
+
export declare function derivePreviewState(hasMarker: boolean, expiresAt: number | undefined, now?: number): PreviewState;
|
|
3
|
+
export declare function readPreviewState(): PreviewState;
|
|
4
|
+
export declare function expiresAtFromTokenResult(result: unknown): number;
|
|
5
|
+
export declare function isSessionExpiredError(errors: ReadonlyArray<{
|
|
6
|
+
message?: string | null;
|
|
7
|
+
}> | null | undefined): boolean;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Pure preview-state logic for the admin toolbar, split out from the component
|
|
2
|
+
// so it can be unit-tested without a browser, a Relay environment, or the
|
|
3
|
+
// `graphql` Babel transform (importing the .tsx triggers the tag at load).
|
|
4
|
+
//
|
|
5
|
+
// The preview token is a stateless, time-only-expiring Phoenix.Token. There is
|
|
6
|
+
// no way to learn its validity client-side (it's httpOnly, and the toolbar's
|
|
7
|
+
// admin check rides the longer-lived *session* cookie, not the token), so the
|
|
8
|
+
// toolbar tracks the token's *expiry* instead and derives three states from the
|
|
9
|
+
// readable marker cookie:
|
|
10
|
+
//
|
|
11
|
+
// - 'off' — no marker cookie: not previewing.
|
|
12
|
+
// - 'active' — marker present and now < expiresAt: previewing.
|
|
13
|
+
// - 'expired' — marker present and now >= expiresAt: the token has lapsed,
|
|
14
|
+
// so server renders have silently fallen back to the public
|
|
15
|
+
// catalog. Show a warning and offer "re-enter".
|
|
16
|
+
|
|
17
|
+
import { DEFAULT_PREVIEW_TTL_MS, hasPreviewMarkerCookie, getPreviewExpiry } from '../../preview/constants.js';
|
|
18
|
+
// Pure derivation. `hasMarker` and `expiresAt` come from the marker cookie;
|
|
19
|
+
// `now` is injectable for tests.
|
|
20
|
+
export function derivePreviewState(hasMarker, expiresAt) {
|
|
21
|
+
let now = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Date.now();
|
|
22
|
+
if (!hasMarker) return 'off';
|
|
23
|
+
// A marker with no/garbled expiry (e.g. a stale legacy `=1` cookie) is
|
|
24
|
+
// treated as expired: safer to prompt a re-enter than to imply it's live.
|
|
25
|
+
if (expiresAt === undefined || now >= expiresAt) return 'expired';
|
|
26
|
+
return 'active';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Read the current preview state straight off the document's cookies.
|
|
30
|
+
export function readPreviewState() {
|
|
31
|
+
return derivePreviewState(hasPreviewMarkerCookie(), getPreviewExpiry());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Pull an expiry out of the createAdminToken response. The backend will grow an
|
|
35
|
+
// `expiresAt` field (ISO-8601 or epoch-ms); until it does, the field is absent
|
|
36
|
+
// and we fall back to now + DEFAULT_PREVIEW_TTL_MS — the canonical 1h TTL lives
|
|
37
|
+
// in the backend `token.ex` `:admin_token` `max_age`. Read defensively so the
|
|
38
|
+
// client and backend halves can ship independently.
|
|
39
|
+
export function expiresAtFromTokenResult(result) {
|
|
40
|
+
const value = result === null || result === void 0 ? void 0 : result.expiresAt;
|
|
41
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
42
|
+
if (typeof value === 'string' && value !== '') {
|
|
43
|
+
const asNumber = Number(value);
|
|
44
|
+
if (Number.isFinite(asNumber)) return asNumber;
|
|
45
|
+
const asDate = Date.parse(value);
|
|
46
|
+
if (!Number.isNaN(asDate)) return asDate;
|
|
47
|
+
}
|
|
48
|
+
return Date.now() + DEFAULT_PREVIEW_TTL_MS;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// True when the createAdminToken mutation failed because the admin *session*
|
|
52
|
+
// (not the preview token) has expired. The resolver returns a "Not authorized"
|
|
53
|
+
// GraphQL error when current_user is nil, so we match that message rather than
|
|
54
|
+
// treating every failure as a recoverable retry.
|
|
55
|
+
export function isSessionExpiredError(errors) {
|
|
56
|
+
if (!errors) return false;
|
|
57
|
+
return errors.some(e => /not authoriz/i.test(e.message ?? ''));
|
|
58
|
+
}
|
|
@@ -8,7 +8,7 @@ import ConfigContext, { makeConfig, mergeConfig } from '../ConfigContext.js';
|
|
|
8
8
|
import { createDefaultStore } from '../../reducer.js';
|
|
9
9
|
import ComponentsContext from '../ComponentsContext.js';
|
|
10
10
|
import UrlContext from '../UrlContext.js';
|
|
11
|
-
import {
|
|
11
|
+
import { setConnectionErrorUiEnabled } from '../../corsError.js';
|
|
12
12
|
import TypeTesterFamiliesProvider from '../TypeTester/TypeTesterFamilies.js';
|
|
13
13
|
|
|
14
14
|
// Marker context used by self-wrapping components to detect a parent
|
|
@@ -126,7 +126,9 @@ export default function FontdueContextProvider(_ref3) {
|
|
|
126
126
|
stripeIntegration
|
|
127
127
|
});
|
|
128
128
|
const configValue = makeConfig(config);
|
|
129
|
-
|
|
129
|
+
// `corsErrorModal` is the legacy config name; it now gates the toolbar's
|
|
130
|
+
// connection-error status rather than a modal.
|
|
131
|
+
setConnectionErrorUiEnabled(configValue.corsErrorModal);
|
|
130
132
|
const sharedStore = useSharedStore(store, config);
|
|
131
133
|
return /*#__PURE__*/React.createElement(FontdueContextMarker.Provider, {
|
|
132
134
|
value: true
|
|
@@ -13,6 +13,11 @@ import retryImport from '../../retryImport.js';
|
|
|
13
13
|
import useSerializablePreloadedQuery from '../../relay/useSerializablePreloadedQuery.js';
|
|
14
14
|
const ConsentBanner = /*#__PURE__*/lazy(() => retryImport(() => import('../ConsentBanner/index.js')));
|
|
15
15
|
const Tracking = /*#__PURE__*/lazy(() => retryImport(() => import('../Tracking/index.js')));
|
|
16
|
+
// Admin-only; self-gates to an admin session and renders nothing otherwise, so
|
|
17
|
+
// it's safe to mount for everyone. It detects admins client-side (storefront
|
|
18
|
+
// renders are sessionless), so it needs no fragment on the provider query.
|
|
19
|
+
// Lazy so it stays out of the provider's main chunk.
|
|
20
|
+
const FontdueAdminToolbar = /*#__PURE__*/lazy(() => retryImport(() => import('../FontdueAdminToolbar/index.js')));
|
|
16
21
|
|
|
17
22
|
// Single layout-level query that spreads every aux UI component's fragment.
|
|
18
23
|
// Components declare their own data needs via fragments — adding a new aux
|
|
@@ -72,6 +77,6 @@ function AuxUI(_ref4) {
|
|
|
72
77
|
codeConfig: codeConfig
|
|
73
78
|
}, /*#__PURE__*/React.createElement(Suspense, {
|
|
74
79
|
fallback: null
|
|
75
|
-
}, /*#__PURE__*/React.createElement(ConsentBanner, null), /*#__PURE__*/React.createElement(Tracking, null))));
|
|
80
|
+
}, /*#__PURE__*/React.createElement(ConsentBanner, null), /*#__PURE__*/React.createElement(Tracking, null), /*#__PURE__*/React.createElement(FontdueAdminToolbar, null))));
|
|
76
81
|
}
|
|
77
82
|
export default FontdueProvider;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { FontdueProvider_props } from './index.js';
|
|
3
|
+
import '../../next/registerSingleTenantResolver.js';
|
|
3
4
|
export type { FontdueProvider_props } from './index.js';
|
|
4
5
|
export type { FontdueProviderPreloadedQuery } from '../../loadFontdueProviderQuery.js';
|
|
5
6
|
export type { FontdueServerConfig } from '../../relay/serverConfig.js';
|
|
@@ -3,6 +3,16 @@ import React from 'react';
|
|
|
3
3
|
import FontdueProvider from './index.js';
|
|
4
4
|
import loadFontdueProviderQueryImpl from '../../loadFontdueProviderQuery.js';
|
|
5
5
|
import { setFontdueServerConfig } from '../../relay/serverConfig.js';
|
|
6
|
+
// Side-effect import: registers the single-tenant ambient config resolver so a
|
|
7
|
+
// single-tenant foundry that mounts <FontdueProvider> never has to call a
|
|
8
|
+
// per-render setup function. The module statically pulls in only browser-safe
|
|
9
|
+
// code and lazy-loads the Next request APIs (next/headers, next/navigation)
|
|
10
|
+
// behind a dynamic import that runs only when the resolver resolves config —
|
|
11
|
+
// keeping next/* off this react-server entrypoint's eager graph. The resolver
|
|
12
|
+
// no-ops in multi-tenant mode (which drives config through the slot) and is
|
|
13
|
+
// inert in the default build Astro/RR7 use. See
|
|
14
|
+
// ../../next/registerSingleTenantResolver.ts.
|
|
15
|
+
import '../../next/registerSingleTenantResolver.js';
|
|
6
16
|
// Stub for the RSC export condition. The default `<FontdueProvider>` server
|
|
7
17
|
// entrypoint (this file) awaits the query for consumers, so RSC users should
|
|
8
18
|
// never call it manually. Re-exporting a throwing stub makes the mistake
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
|
|
2
|
+
import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
|
|
3
3
|
import { NewsletterSignupQuery } from '../../__generated__/NewsletterSignupQuery.graphql.js';
|
|
4
4
|
import { Config } from '../ConfigContext.js';
|
|
5
5
|
export type NewsletterSignupPreloadedQuery = SerializablePreloadedQuery<NewsletterSignupQuery>;
|
|
6
|
-
export declare function loadNewsletterSignupQuery(): Promise<NewsletterSignupPreloadedQuery>;
|
|
6
|
+
export declare function loadNewsletterSignupQuery(options?: LoadQueryOptions): Promise<NewsletterSignupPreloadedQuery>;
|
|
7
7
|
export interface NewsletterSignup_props {
|
|
8
8
|
optInLabel?: string;
|
|
9
9
|
successLabel?: string;
|
|
@@ -13,8 +13,8 @@ import useSerializablePreloadedQuery from '../../relay/useSerializablePreloadedQ
|
|
|
13
13
|
import NewsletterSignupQueryNode from '../../__generated__/NewsletterSignupQuery.graphql.js';
|
|
14
14
|
import { EnsureFontdueContext } from '../FontdueContextProvider/index.js';
|
|
15
15
|
const updateCustomerMutation = (_NewsletterSignupUpdateCustomerMutation.hash && _NewsletterSignupUpdateCustomerMutation.hash !== "769087891b6f263122bbb630b3f2ca6c" && console.error("The definition of 'NewsletterSignupUpdateCustomerMutation' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _NewsletterSignupUpdateCustomerMutation);
|
|
16
|
-
export async function loadNewsletterSignupQuery() {
|
|
17
|
-
return loadSerializableQuery(NewsletterSignupQueryNode, {});
|
|
16
|
+
export async function loadNewsletterSignupQuery(options) {
|
|
17
|
+
return loadSerializableQuery(NewsletterSignupQueryNode, {}, options);
|
|
18
18
|
}
|
|
19
19
|
const query = (_NewsletterSignupQuery.hash && _NewsletterSignupQuery.hash !== "24b303198a6038318723fc0124548862" && console.error("The definition of 'NewsletterSignupQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _NewsletterSignupQuery);
|
|
20
20
|
function NewsletterSignupComponent(_ref) {
|
|
@@ -71,11 +71,25 @@ const getInitialElements = () => {
|
|
|
71
71
|
return map;
|
|
72
72
|
};
|
|
73
73
|
const Root = props => {
|
|
74
|
+
var _props$config;
|
|
75
|
+
// The script-tag integration is client-only — there is no server render to
|
|
76
|
+
// forward a preview token to — so the admin toolbar must enter/exit preview
|
|
77
|
+
// by toggling the marker cookie in the browser. Force it on here, where the
|
|
78
|
+
// integration is unambiguously client-side. (The admin's Fontdue session
|
|
79
|
+
// rides the credentialed cross-origin GraphQL fetch and gates the reveal.)
|
|
80
|
+
const config = {
|
|
81
|
+
...props.config,
|
|
82
|
+
preview: {
|
|
83
|
+
...((_props$config = props.config) === null || _props$config === void 0 ? void 0 : _props$config.preview),
|
|
84
|
+
clientSide: true
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
74
88
|
// use a Map here instead of an array in case we register the same element
|
|
75
89
|
// more than once (otherwise we run into issues rendering the component
|
|
76
90
|
// twice into the same element). this also gives us a way to set unique keys.
|
|
77
91
|
const [elements, setElements] = useState(getInitialElements());
|
|
78
|
-
const store = useRef(createDefaultStore(
|
|
92
|
+
const store = useRef(createDefaultStore(config));
|
|
79
93
|
useEffect(() => {
|
|
80
94
|
// watch for any new or removed fontdue elements
|
|
81
95
|
|
|
@@ -190,7 +204,7 @@ const Root = props => {
|
|
|
190
204
|
return /*#__PURE__*/React.createElement(FontdueProvider, {
|
|
191
205
|
url: props.url,
|
|
192
206
|
stripeIntegration: props.stripeIntegration,
|
|
193
|
-
config:
|
|
207
|
+
config: config,
|
|
194
208
|
components: props.components,
|
|
195
209
|
store: store.current
|
|
196
210
|
}, /*#__PURE__*/React.createElement(React.Fragment, null, Array.from(elements).map(_ref => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { TestFontsForm_Query } from '../../__generated__/TestFontsForm_Query.graphql.js';
|
|
3
|
-
import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
|
|
3
|
+
import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
|
|
4
4
|
import { Config } from '../ConfigContext.js';
|
|
5
5
|
export interface TestFontsForm_props {
|
|
6
6
|
agreementLabel?: string;
|
|
@@ -8,7 +8,7 @@ export interface TestFontsForm_props {
|
|
|
8
8
|
newsletterCheckboxChecked?: boolean;
|
|
9
9
|
}
|
|
10
10
|
export type TestFontsFormPreloadedQuery = SerializablePreloadedQuery<TestFontsForm_Query>;
|
|
11
|
-
export declare function loadTestFontsFormQuery(): Promise<TestFontsFormPreloadedQuery>;
|
|
11
|
+
export declare function loadTestFontsFormQuery(options?: LoadQueryOptions): Promise<TestFontsFormPreloadedQuery>;
|
|
12
12
|
export declare function TestFontsFormPreloadedQueryRenderer({ preloadedQuery, ...rest }: TestFontsForm_props & {
|
|
13
13
|
preloadedQuery: TestFontsFormPreloadedQuery;
|
|
14
14
|
}): React.JSX.Element;
|
|
@@ -194,8 +194,8 @@ const TestFontsFormComponent = _ref2 => {
|
|
|
194
194
|
}));
|
|
195
195
|
};
|
|
196
196
|
const query = (_TestFontsForm_Query.hash && _TestFontsForm_Query.hash !== "cd43f0cacc4dcf01cf94fb1ff97197ca" && console.error("The definition of 'TestFontsForm_Query' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TestFontsForm_Query);
|
|
197
|
-
export async function loadTestFontsFormQuery() {
|
|
198
|
-
return loadSerializableQuery(TestFontsFormQueryNode, {});
|
|
197
|
+
export async function loadTestFontsFormQuery(options) {
|
|
198
|
+
return loadSerializableQuery(TestFontsFormQueryNode, {}, options);
|
|
199
199
|
}
|
|
200
200
|
export function TestFontsFormPreloadedQueryRenderer(_ref4) {
|
|
201
201
|
let {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Alignment, Direction, FeaturesProp } from './types.js';
|
|
3
|
-
import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
|
|
3
|
+
import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
|
|
4
4
|
import { TypeTesterStandaloneQuery } from '../../__generated__/TypeTesterStandaloneQuery.graphql.js';
|
|
5
5
|
import { Config } from '../ConfigContext.js';
|
|
6
6
|
interface TypeTesterStandaloneComponent_props {
|
|
@@ -30,7 +30,7 @@ export interface LoadTypeTesterQueryVariables {
|
|
|
30
30
|
familyName: string;
|
|
31
31
|
styleName: string;
|
|
32
32
|
}
|
|
33
|
-
export declare function loadTypeTesterQuery(variables: LoadTypeTesterQueryVariables): Promise<TypeTesterPreloadedQuery>;
|
|
33
|
+
export declare function loadTypeTesterQuery(variables: LoadTypeTesterQueryVariables, options?: LoadQueryOptions): Promise<TypeTesterPreloadedQuery>;
|
|
34
34
|
export declare function TypeTesterStandalonePreloadedQueryRenderer({ preloadedQuery, ...rest }: TypeTesterStandaloneComponent_props & {
|
|
35
35
|
preloadedQuery: TypeTesterPreloadedQuery;
|
|
36
36
|
}): React.JSX.Element;
|
|
@@ -100,11 +100,11 @@ function TypeTesterStandaloneComponent(_ref) {
|
|
|
100
100
|
}));
|
|
101
101
|
}
|
|
102
102
|
const query = (_TypeTesterStandaloneQuery.hash && _TypeTesterStandaloneQuery.hash !== "951214eacb2370ea3ca0b19bebe7fbe7" && console.error("The definition of 'TypeTesterStandaloneQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _TypeTesterStandaloneQuery);
|
|
103
|
-
export async function loadTypeTesterQuery(variables) {
|
|
103
|
+
export async function loadTypeTesterQuery(variables, options) {
|
|
104
104
|
return loadSerializableQuery(TypeTesterStandaloneQueryNode, {
|
|
105
105
|
familyName: variables.familyName,
|
|
106
106
|
styleName: variables.styleName
|
|
107
|
-
});
|
|
107
|
+
}, options);
|
|
108
108
|
}
|
|
109
109
|
export function TypeTesterStandalonePreloadedQueryRenderer(_ref3) {
|
|
110
110
|
let {
|
|
@@ -3,7 +3,7 @@ import { TypeTestersIDQuery } from '../../__generated__/TypeTestersIDQuery.graph
|
|
|
3
3
|
import { TypeTestersSlugQuery } from '../../__generated__/TypeTestersSlugQuery.graphql.js';
|
|
4
4
|
import { Config } from '../ConfigContext.js';
|
|
5
5
|
import { FeaturesProp } from '../TypeTester/types.js';
|
|
6
|
-
import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
|
|
6
|
+
import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
|
|
7
7
|
export interface TypeTesters_props {
|
|
8
8
|
collectionId?: string;
|
|
9
9
|
collectionSlug?: string;
|
|
@@ -33,7 +33,7 @@ export type LoadTypeTestersQueryVariables = ({
|
|
|
33
33
|
tags?: string[] | null;
|
|
34
34
|
excludeTags?: string[] | null;
|
|
35
35
|
};
|
|
36
|
-
export declare function loadTypeTestersQuery(variables: LoadTypeTestersQueryVariables): Promise<TypeTestersPreloadedQuery>;
|
|
36
|
+
export declare function loadTypeTestersQuery(variables: LoadTypeTestersQueryVariables, options?: LoadQueryOptions): Promise<TypeTestersPreloadedQuery>;
|
|
37
37
|
export type TypeTesters_unifiedProps = TypeTesters_props & {
|
|
38
38
|
preloadedQuery?: TypeTestersPreloadedQuery;
|
|
39
39
|
config?: Config;
|
|
@@ -251,7 +251,7 @@ function TypeTestersSlugQueryRenderer(_ref6) {
|
|
|
251
251
|
excludeTags: excludeTags
|
|
252
252
|
}));
|
|
253
253
|
}
|
|
254
|
-
export async function loadTypeTestersQuery(variables) {
|
|
254
|
+
export async function loadTypeTestersQuery(variables, options) {
|
|
255
255
|
const tags = variables.tags ?? null;
|
|
256
256
|
const excludeTags = variables.excludeTags ?? null;
|
|
257
257
|
if (variables.collectionId) {
|
|
@@ -259,14 +259,14 @@ export async function loadTypeTestersQuery(variables) {
|
|
|
259
259
|
collectionId: variables.collectionId,
|
|
260
260
|
tags,
|
|
261
261
|
excludeTags
|
|
262
|
-
});
|
|
262
|
+
}, options);
|
|
263
263
|
}
|
|
264
264
|
if (variables.collectionSlug) {
|
|
265
265
|
return loadSerializableQuery(TypeTestersSlugQueryNode, {
|
|
266
266
|
collectionSlug: variables.collectionSlug,
|
|
267
267
|
tags,
|
|
268
268
|
excludeTags
|
|
269
|
-
});
|
|
269
|
+
}, options);
|
|
270
270
|
}
|
|
271
271
|
throw new Error('loadTypeTestersQuery expected either collectionId or collectionSlug');
|
|
272
272
|
}
|
|
@@ -8,6 +8,7 @@ export type VerticalMetrics = {
|
|
|
8
8
|
readonly ascender: number;
|
|
9
9
|
readonly descender: number;
|
|
10
10
|
readonly lineGap: number | null;
|
|
11
|
+
readonly avgCharWidth: number | null;
|
|
11
12
|
};
|
|
12
13
|
declare const useFontStyle: ({ fontStyle: fontStyleKey, }: UseFontStyle_props) => {
|
|
13
14
|
style: React.CSSProperties;
|