fontdue-js 3.0.0-alpha9 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -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 +6 -4
- 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,118 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { runWithPreview, isPreviewing, ambientPreviewHeaders } from '../preview/server.js';
|
|
3
|
+
import { createFontdueFetch } from '../server/index.js';
|
|
4
|
+
import { PREVIEW_TOKEN_COOKIE } from '../preview/constants.js';
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.unstubAllGlobals();
|
|
7
|
+
});
|
|
8
|
+
function req(token) {
|
|
9
|
+
return new Request('https://store.test/', {
|
|
10
|
+
headers: token ? {
|
|
11
|
+
cookie: `${PREVIEW_TOKEN_COOKIE}=${token}`
|
|
12
|
+
} : {}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
describe('runWithPreview', () => {
|
|
16
|
+
it('passes public (no-token) requests through untouched and cacheable', async () => {
|
|
17
|
+
const response = await runWithPreview(req(), async () => {
|
|
18
|
+
expect(isPreviewing()).toBe(false);
|
|
19
|
+
return new Response('ok', {
|
|
20
|
+
headers: {
|
|
21
|
+
'Netlify-CDN-Cache-Control': 'public, durable, s-maxage=300'
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
expect(response.headers.get('Cache-Control')).toBeNull();
|
|
26
|
+
expect(response.headers.get('Netlify-CDN-Cache-Control')).toBe('public, durable, s-maxage=300');
|
|
27
|
+
});
|
|
28
|
+
it('exposes the token in ambient context while previewing', async () => {
|
|
29
|
+
await runWithPreview(req('tok123'), async () => {
|
|
30
|
+
expect(isPreviewing()).toBe(true);
|
|
31
|
+
expect(ambientPreviewHeaders()).toEqual({
|
|
32
|
+
authorization: 'Bearer tok123'
|
|
33
|
+
});
|
|
34
|
+
return new Response('ok');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Context unwinds after the request.
|
|
38
|
+
expect(isPreviewing()).toBe(false);
|
|
39
|
+
expect(ambientPreviewHeaders()).toEqual({});
|
|
40
|
+
});
|
|
41
|
+
it('forces preview responses out of any shared/CDN cache', async () => {
|
|
42
|
+
const response = await runWithPreview(req('tok123'), async () => new Response('secret', {
|
|
43
|
+
headers: {
|
|
44
|
+
'Netlify-CDN-Cache-Control': 'public, durable, s-maxage=31536000',
|
|
45
|
+
'CDN-Cache-Control': 'public, s-maxage=300',
|
|
46
|
+
'Cache-Control': 'public, max-age=0, must-revalidate'
|
|
47
|
+
}
|
|
48
|
+
}));
|
|
49
|
+
expect(response.headers.get('Cache-Control')).toBe('private, no-store');
|
|
50
|
+
expect(response.headers.get('Netlify-CDN-Cache-Control')).toBeNull();
|
|
51
|
+
expect(response.headers.get('CDN-Cache-Control')).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
it('isolates concurrent preview contexts (no cross-request leak)', async () => {
|
|
54
|
+
const seen = {};
|
|
55
|
+
await Promise.all([runWithPreview(req('tokA'), async () => {
|
|
56
|
+
await new Promise(r => setTimeout(r, 15));
|
|
57
|
+
seen.a = ambientPreviewHeaders().authorization;
|
|
58
|
+
return new Response('a');
|
|
59
|
+
}), runWithPreview(req('tokB'), async () => {
|
|
60
|
+
seen.b = ambientPreviewHeaders().authorization;
|
|
61
|
+
return new Response('b');
|
|
62
|
+
}), runWithPreview(req(), async () => {
|
|
63
|
+
seen.public = ambientPreviewHeaders().authorization;
|
|
64
|
+
return new Response('public');
|
|
65
|
+
})]);
|
|
66
|
+
expect(seen.a).toBe('Bearer tokA');
|
|
67
|
+
expect(seen.b).toBe('Bearer tokB');
|
|
68
|
+
expect(seen.public).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('createFontdueFetch + ambient preview', () => {
|
|
72
|
+
function mockFetch() {
|
|
73
|
+
const fetchMock = vi.fn(async () => ({
|
|
74
|
+
status: 200,
|
|
75
|
+
json: async () => ({
|
|
76
|
+
data: {}
|
|
77
|
+
})
|
|
78
|
+
}));
|
|
79
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
80
|
+
return fetchMock;
|
|
81
|
+
}
|
|
82
|
+
it('a module-level fetcher forwards the ambient token, no per-call plumbing', async () => {
|
|
83
|
+
const fetchMock = mockFetch();
|
|
84
|
+
const fetchGraphql = createFontdueFetch({
|
|
85
|
+
url: 'https://acme.fontdue.com'
|
|
86
|
+
});
|
|
87
|
+
await runWithPreview(req('admin-tok'), async () => {
|
|
88
|
+
await fetchGraphql('Q', 'query Q { __typename }');
|
|
89
|
+
return new Response('ok');
|
|
90
|
+
});
|
|
91
|
+
const init = fetchMock.mock.calls[0][1];
|
|
92
|
+
expect(init.headers.authorization).toBe('Bearer admin-tok');
|
|
93
|
+
});
|
|
94
|
+
it('does not forward a token for public requests', async () => {
|
|
95
|
+
const fetchMock = mockFetch();
|
|
96
|
+
const fetchGraphql = createFontdueFetch({
|
|
97
|
+
url: 'https://acme.fontdue.com'
|
|
98
|
+
});
|
|
99
|
+
await fetchGraphql('Q', 'query Q { __typename }');
|
|
100
|
+
const init = fetchMock.mock.calls[0][1];
|
|
101
|
+
expect(init.headers.authorization).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
it('explicit per-fetcher headers override the ambient context', async () => {
|
|
104
|
+
const fetchMock = mockFetch();
|
|
105
|
+
const fetchGraphql = createFontdueFetch({
|
|
106
|
+
url: 'https://acme.fontdue.com',
|
|
107
|
+
headers: {
|
|
108
|
+
authorization: 'Bearer explicit'
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
await runWithPreview(req('ambient-tok'), async () => {
|
|
112
|
+
await fetchGraphql('Q', 'query Q { __typename }');
|
|
113
|
+
return new Response('ok');
|
|
114
|
+
});
|
|
115
|
+
const init = fetchMock.mock.calls[0][1];
|
|
116
|
+
expect(init.headers.authorization).toBe('Bearer explicit');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { derivePreviewState, expiresAtFromTokenResult, isSessionExpiredError } from '../components/FontdueAdminToolbar/previewState.js';
|
|
3
|
+
|
|
4
|
+
// The three-state derivation is the heart of the expired-preview fix, so it's
|
|
5
|
+
// tested in isolation (the full toolbar needs a browser + Relay environment +
|
|
6
|
+
// admin session to render, which isn't available here).
|
|
7
|
+
describe('derivePreviewState', () => {
|
|
8
|
+
const NOW = 1_000_000;
|
|
9
|
+
it('is "off" with no marker cookie', () => {
|
|
10
|
+
expect(derivePreviewState(false, undefined, NOW)).toBe('off');
|
|
11
|
+
expect(derivePreviewState(false, NOW + 1000, NOW)).toBe('off');
|
|
12
|
+
});
|
|
13
|
+
it('is "active" while the marker is present and the token is still valid', () => {
|
|
14
|
+
expect(derivePreviewState(true, NOW + 1, NOW)).toBe('active');
|
|
15
|
+
});
|
|
16
|
+
it('is "expired" once now has reached the stored expiry', () => {
|
|
17
|
+
expect(derivePreviewState(true, NOW, NOW)).toBe('expired');
|
|
18
|
+
expect(derivePreviewState(true, NOW - 1, NOW)).toBe('expired');
|
|
19
|
+
});
|
|
20
|
+
it('is "expired" for a marker with no/garbled expiry (stale legacy cookie)', () => {
|
|
21
|
+
expect(derivePreviewState(true, undefined, NOW)).toBe('expired');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('expiresAtFromTokenResult', () => {
|
|
25
|
+
it('accepts an epoch-ms number', () => {
|
|
26
|
+
expect(expiresAtFromTokenResult({
|
|
27
|
+
expiresAt: 123_456
|
|
28
|
+
})).toBe(123_456);
|
|
29
|
+
});
|
|
30
|
+
it('accepts an ISO-8601 string', () => {
|
|
31
|
+
const iso = '2030-01-01T00:00:00.000Z';
|
|
32
|
+
expect(expiresAtFromTokenResult({
|
|
33
|
+
expiresAt: iso
|
|
34
|
+
})).toBe(Date.parse(iso));
|
|
35
|
+
});
|
|
36
|
+
it('accepts a numeric string', () => {
|
|
37
|
+
expect(expiresAtFromTokenResult({
|
|
38
|
+
expiresAt: '987654321'
|
|
39
|
+
})).toBe(987654321);
|
|
40
|
+
});
|
|
41
|
+
it('falls back to now + 1h TTL when the field is absent (backend not yet shipped)', () => {
|
|
42
|
+
const before = Date.now();
|
|
43
|
+
expect(expiresAtFromTokenResult({
|
|
44
|
+
token: 'abc'
|
|
45
|
+
})).toBeGreaterThanOrEqual(before + 60 * 60 * 1000 - 5_000);
|
|
46
|
+
expect(expiresAtFromTokenResult(null)).toBeGreaterThanOrEqual(before + 60 * 60 * 1000 - 5_000);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('isSessionExpiredError', () => {
|
|
50
|
+
it('matches the resolver’s "Not authorized" message', () => {
|
|
51
|
+
expect(isSessionExpiredError([{
|
|
52
|
+
message: 'Not authorized: you must be signed in as an admin.'
|
|
53
|
+
}])).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
it('is false for unrelated errors and empty/absent error lists', () => {
|
|
56
|
+
expect(isSessionExpiredError([{
|
|
57
|
+
message: 'Something else failed'
|
|
58
|
+
}])).toBe(false);
|
|
59
|
+
expect(isSessionExpiredError([])).toBe(false);
|
|
60
|
+
expect(isSessionExpiredError(null)).toBe(false);
|
|
61
|
+
expect(isSessionExpiredError(undefined)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// The render-scoped slot and the ambient resolver are anchored on globalThis so
|
|
4
|
+
// they survive this module being bundled into more than one Next.js server
|
|
5
|
+
// chunk (the app/provider chunk vs. a per-embed chunk), which would otherwise
|
|
6
|
+
// give each chunk its own copy of the module-level state. Re-importing the
|
|
7
|
+
// module after vi.resetModules() faithfully simulates that second chunk: a
|
|
8
|
+
// fresh module instance with its own scope but the same globalThis.
|
|
9
|
+
const STORE_KEY = '__fontdueServerConfigStore__';
|
|
10
|
+
function clearStore() {
|
|
11
|
+
delete globalThis[STORE_KEY];
|
|
12
|
+
}
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.resetModules();
|
|
15
|
+
clearStore();
|
|
16
|
+
});
|
|
17
|
+
afterEach(clearStore);
|
|
18
|
+
describe('serverConfig store (chunk-duplication safety, FD-712)', () => {
|
|
19
|
+
it('shares the ambient resolver across module instances', async () => {
|
|
20
|
+
var _await$a$resolveFontd, _await$a$resolveFontd2, _await$b$resolveFontd, _await$b$resolveFontd2;
|
|
21
|
+
// First "chunk": register a resolver (what fontdue-js/next does when
|
|
22
|
+
// <FontdueProvider> mounts in single-tenant mode).
|
|
23
|
+
const a = await import("../relay/serverConfig.js");
|
|
24
|
+
a.registerAmbientConfigResolver(async () => ({
|
|
25
|
+
url: 'https://acme.fontdue.com',
|
|
26
|
+
headers: {
|
|
27
|
+
authorization: 'Bearer admin-tok'
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
expect((_await$a$resolveFontd = await a.resolveFontdueServerConfig()) === null || _await$a$resolveFontd === void 0 ? void 0 : (_await$a$resolveFontd2 = _await$a$resolveFontd.headers) === null || _await$a$resolveFontd2 === void 0 ? void 0 : _await$a$resolveFontd2.authorization).toBe('Bearer admin-tok');
|
|
31
|
+
|
|
32
|
+
// Second "chunk": a fresh module instance. Before the fix its module-level
|
|
33
|
+
// `ambientResolver` was undefined here, so an embed preloading from this
|
|
34
|
+
// chunk fetched without the preview token and a hidden-font reveal failed.
|
|
35
|
+
vi.resetModules();
|
|
36
|
+
const b = await import("../relay/serverConfig.js");
|
|
37
|
+
expect(b).not.toBe(a); // genuinely a re-evaluated module
|
|
38
|
+
expect((_await$b$resolveFontd = await b.resolveFontdueServerConfig()) === null || _await$b$resolveFontd === void 0 ? void 0 : (_await$b$resolveFontd2 = _await$b$resolveFontd.headers) === null || _await$b$resolveFontd2 === void 0 ? void 0 : _await$b$resolveFontd2.authorization).toBe('Bearer admin-tok');
|
|
39
|
+
});
|
|
40
|
+
it('reuses one shared store object across re-imports', async () => {
|
|
41
|
+
const a = await import("../relay/serverConfig.js");
|
|
42
|
+
// The store is created lazily on first use, not on import.
|
|
43
|
+
a.registerAmbientConfigResolver(() => undefined);
|
|
44
|
+
const first = globalThis[STORE_KEY];
|
|
45
|
+
expect(first).toBeDefined();
|
|
46
|
+
vi.resetModules();
|
|
47
|
+
const b = await import("../relay/serverConfig.js");
|
|
48
|
+
await b.resolveFontdueServerConfig();
|
|
49
|
+
const second = globalThis[STORE_KEY];
|
|
50
|
+
|
|
51
|
+
// Same store (slot factory + resolver), not a per-instance copy — so the
|
|
52
|
+
// React.cache-backed slot multi-tenant uses (setFontdueServerConfig via
|
|
53
|
+
// __prepareFontdueRender) is one shared cell every chunk reads/writes.
|
|
54
|
+
expect(second).toBe(first);
|
|
55
|
+
});
|
|
56
|
+
it('starts with no resolver (resolution unchanged until something registers)', async () => {
|
|
57
|
+
const {
|
|
58
|
+
resolveFontdueServerConfig
|
|
59
|
+
} = await import("../relay/serverConfig.js");
|
|
60
|
+
expect(await resolveFontdueServerConfig()).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { BuyButtonIDQuery } from '../../__generated__/BuyButtonIDQuery.graphql.js';
|
|
3
3
|
import { BuyButtonSlugQuery } from '../../__generated__/BuyButtonSlugQuery.graphql.js';
|
|
4
|
-
import { SerializablePreloadedQuery } from '../../relay/loadSerializableQuery.js';
|
|
4
|
+
import { SerializablePreloadedQuery, type LoadQueryOptions } from '../../relay/loadSerializableQuery.js';
|
|
5
5
|
import { Config } from '../ConfigContext.js';
|
|
6
6
|
export interface BuyButton_props {
|
|
7
7
|
collectionName?: string;
|
|
@@ -15,7 +15,7 @@ export type LoadBuyButtonQueryVariables = {
|
|
|
15
15
|
collectionId?: never;
|
|
16
16
|
collectionSlug: string;
|
|
17
17
|
};
|
|
18
|
-
export declare function loadBuyButtonQuery(variables: LoadBuyButtonQueryVariables): Promise<BuyButtonPreloadedQuery>;
|
|
18
|
+
export declare function loadBuyButtonQuery(variables: LoadBuyButtonQueryVariables, options?: LoadQueryOptions): Promise<BuyButtonPreloadedQuery>;
|
|
19
19
|
export declare function BuyButtonPreloadedIDQueryRenderer({ preloadedQuery, ...rest }: BuyButton_props & {
|
|
20
20
|
preloadedQuery: SerializablePreloadedQuery<BuyButtonIDQuery>;
|
|
21
21
|
}): React.JSX.Element;
|
|
@@ -57,16 +57,16 @@ function BuyButtonComponent(_ref) {
|
|
|
57
57
|
}
|
|
58
58
|
const idQuery = (_BuyButtonIDQuery.hash && _BuyButtonIDQuery.hash !== "4fbf1dbf9e6c530a5d38c697b174d8b0" && console.error("The definition of 'BuyButtonIDQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _BuyButtonIDQuery);
|
|
59
59
|
const slugQuery = (_BuyButtonSlugQuery.hash && _BuyButtonSlugQuery.hash !== "6e750adea09698f7cb61f435cd88fd26" && console.error("The definition of 'BuyButtonSlugQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _BuyButtonSlugQuery);
|
|
60
|
-
export async function loadBuyButtonQuery(variables) {
|
|
60
|
+
export async function loadBuyButtonQuery(variables, options) {
|
|
61
61
|
if (variables.collectionId) {
|
|
62
62
|
return loadSerializableQuery(BuyButtonIDQueryNode, {
|
|
63
63
|
collectionId: variables.collectionId
|
|
64
|
-
});
|
|
64
|
+
}, options);
|
|
65
65
|
}
|
|
66
66
|
if (variables.collectionSlug) {
|
|
67
67
|
return loadSerializableQuery(BuyButtonSlugQueryNode, {
|
|
68
68
|
collectionSlug: variables.collectionSlug
|
|
69
|
-
});
|
|
69
|
+
}, options);
|
|
70
70
|
}
|
|
71
71
|
throw new Error('loadBuyButtonQuery expected either a collectionId or collectionSlug');
|
|
72
72
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
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 { CharacterViewerIDQuery } from '../../__generated__/CharacterViewerIDQuery.graphql.js';
|
|
4
4
|
import { CharacterViewerSlugQuery } from '../../__generated__/CharacterViewerSlugQuery.graphql.js';
|
|
5
5
|
import { Config } from '../ConfigContext.js';
|
|
@@ -13,7 +13,7 @@ export type LoadCharacterViewerQueryVariables = {
|
|
|
13
13
|
collectionId?: never;
|
|
14
14
|
collectionSlug: string;
|
|
15
15
|
};
|
|
16
|
-
export declare function loadCharacterViewerQuery(variables: LoadCharacterViewerQueryVariables): Promise<CharacterViewerPreloadedQuery>;
|
|
16
|
+
export declare function loadCharacterViewerQuery(variables: LoadCharacterViewerQueryVariables, options?: LoadQueryOptions): Promise<CharacterViewerPreloadedQuery>;
|
|
17
17
|
export declare function CharacterViewerPreloadedIDQueryRenderer({ preloadedQuery, ...rest }: CharacterViewer_props & {
|
|
18
18
|
preloadedQuery: SerializablePreloadedQuery<CharacterViewerIDQuery>;
|
|
19
19
|
}): React.JSX.Element | null;
|
|
@@ -6,7 +6,7 @@ import _CharacterViewerIDQuery from "../../__generated__/CharacterViewerIDQuery.
|
|
|
6
6
|
import _CharacterViewer_style from "../../__generated__/CharacterViewer_style.graphql.js";
|
|
7
7
|
import _CharacterViewer_collection from "../../__generated__/CharacterViewer_collection.graphql.js";
|
|
8
8
|
import _CharacterViewer_family from "../../__generated__/CharacterViewer_family.graphql.js";
|
|
9
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
9
|
+
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
10
10
|
import { fetchQuery, graphql, useFragment, useLazyLoadQuery, usePreloadedQuery, useRefetchableFragment, useRelayEnvironment } from 'react-relay';
|
|
11
11
|
import useResizeObserver from '../../hooks/useResizeObserver.js';
|
|
12
12
|
import retryImport from '../../retryImport.js';
|
|
@@ -20,6 +20,8 @@ import StyleSelect from './StyleSelect.js';
|
|
|
20
20
|
import Checkbox from '../Checkbox/index.js';
|
|
21
21
|
import CharacterViewerStyleRefetchQueryNode from '../../__generated__/CharacterViewerStyleRefetchQuery.graphql.js';
|
|
22
22
|
import { EnsureFontdueContext } from '../FontdueContextProvider/index.js';
|
|
23
|
+
import ConfigContext from '../ConfigContext.js';
|
|
24
|
+
import { unicodeNamesUrl } from '../../data/unicodeNamesUrl.js';
|
|
23
25
|
function useSize() {
|
|
24
26
|
const ref = useRef(null);
|
|
25
27
|
const [size, setSize] = React.useState();
|
|
@@ -74,20 +76,27 @@ function GlyphMeta(_ref) {
|
|
|
74
76
|
onChange: e => setFeaturesChecked(e.target.checked)
|
|
75
77
|
}), glyph.features.join(', ')))));
|
|
76
78
|
}
|
|
79
|
+
|
|
80
|
+
// Glyph unicode names (~1.2 MB) are fetched lazily from the Fontdue CDN rather
|
|
81
|
+
// than bundled — see data/unicodeNamesUrl.ts. Purely cosmetic (the “Unicode
|
|
82
|
+
// name” label), so a failed fetch degrades silently. `retryImport` is reused
|
|
83
|
+
// here as a generic retry wrapper for transient network blips.
|
|
77
84
|
function useUnicodeData() {
|
|
85
|
+
const config = useContext(ConfigContext);
|
|
86
|
+
const url = unicodeNamesUrl(config.cdnUrl);
|
|
78
87
|
const [data, setData] = useState();
|
|
79
88
|
useEffect(() => {
|
|
80
|
-
function fetchData() {
|
|
81
|
-
retryImport(() => import('../../data/unicodeData.js')).then(data => {
|
|
82
|
-
if (!ignore) setData(data.default);
|
|
83
|
-
}).catch(() => {});
|
|
84
|
-
}
|
|
85
89
|
let ignore = false;
|
|
86
|
-
|
|
90
|
+
retryImport(() => fetch(url).then(res => {
|
|
91
|
+
if (!res.ok) throw new Error(`unicode names request failed: ${res.status}`);
|
|
92
|
+
return res.json();
|
|
93
|
+
})).then(json => {
|
|
94
|
+
if (!ignore) setData(json);
|
|
95
|
+
}).catch(() => {});
|
|
87
96
|
return () => {
|
|
88
97
|
ignore = true;
|
|
89
98
|
};
|
|
90
|
-
}, []);
|
|
99
|
+
}, [url]);
|
|
91
100
|
return data;
|
|
92
101
|
}
|
|
93
102
|
function compareGlyphs(a, b) {
|
|
@@ -416,16 +425,16 @@ function CharacterViewerComponent(_ref3) {
|
|
|
416
425
|
}
|
|
417
426
|
const idQuery = (_CharacterViewerIDQuery.hash && _CharacterViewerIDQuery.hash !== "f90b09a4df6d95307b0a5d5fda487cdc" && console.error("The definition of 'CharacterViewerIDQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CharacterViewerIDQuery);
|
|
418
427
|
const slugQuery = (_CharacterViewerSlugQuery.hash && _CharacterViewerSlugQuery.hash !== "afa08a8f050e0434308892fea6e3c267" && console.error("The definition of 'CharacterViewerSlugQuery' appears to have changed. Run `relay-compiler` to update the generated files to receive the expected data."), _CharacterViewerSlugQuery);
|
|
419
|
-
export async function loadCharacterViewerQuery(variables) {
|
|
428
|
+
export async function loadCharacterViewerQuery(variables, options) {
|
|
420
429
|
if (variables.collectionId) {
|
|
421
430
|
return loadSerializableQuery(CharacterViewerIDQueryNode, {
|
|
422
431
|
collectionId: variables.collectionId
|
|
423
|
-
});
|
|
432
|
+
}, options);
|
|
424
433
|
}
|
|
425
434
|
if (variables.collectionSlug) {
|
|
426
435
|
return loadSerializableQuery(CharacterViewerSlugQueryNode, {
|
|
427
436
|
collectionSlug: variables.collectionSlug
|
|
428
|
-
});
|
|
437
|
+
}, options);
|
|
429
438
|
}
|
|
430
439
|
throw new Error('loadCharacterViewerQuery expected either collectionId or collectionSlug');
|
|
431
440
|
}
|
|
@@ -23,13 +23,20 @@ interface TrackingConfig {
|
|
|
23
23
|
consentMessage?: string;
|
|
24
24
|
segment?: SegmentConfig;
|
|
25
25
|
}
|
|
26
|
+
interface PreviewConfig {
|
|
27
|
+
endpoint?: string;
|
|
28
|
+
revalidateEndpoint?: string;
|
|
29
|
+
clientSide?: boolean;
|
|
30
|
+
}
|
|
26
31
|
export interface Config {
|
|
27
32
|
form?: FormConfig;
|
|
28
33
|
storeModal?: StoreModalConfig;
|
|
29
34
|
typeTester?: TypeTesterConfig;
|
|
30
35
|
stripe?: StripeConfig;
|
|
31
36
|
tracking?: TrackingConfig;
|
|
37
|
+
preview?: PreviewConfig;
|
|
32
38
|
corsErrorModal?: boolean;
|
|
39
|
+
cdnUrl?: string;
|
|
33
40
|
}
|
|
34
41
|
export declare const mergeConfig: (base?: Config, override?: Config) => Config;
|
|
35
42
|
export declare const makeConfig: (config?: Config) => {
|
|
@@ -45,7 +52,7 @@ export declare const makeConfig: (config?: Config) => {
|
|
|
45
52
|
selectable: boolean;
|
|
46
53
|
priceBar: boolean;
|
|
47
54
|
textInput: boolean;
|
|
48
|
-
initialMode: "
|
|
55
|
+
initialMode: "group" | "local";
|
|
49
56
|
groupEdit: boolean;
|
|
50
57
|
bulletStyle: "round" | "square";
|
|
51
58
|
openTypeFeatures: {
|
|
@@ -102,7 +109,13 @@ export declare const makeConfig: (config?: Config) => {
|
|
|
102
109
|
consentMessage: string | undefined;
|
|
103
110
|
segment: SegmentConfig | undefined;
|
|
104
111
|
};
|
|
112
|
+
preview: {
|
|
113
|
+
endpoint: string;
|
|
114
|
+
revalidateEndpoint: string | undefined;
|
|
115
|
+
clientSide: boolean;
|
|
116
|
+
};
|
|
105
117
|
corsErrorModal: boolean;
|
|
118
|
+
cdnUrl: string;
|
|
106
119
|
};
|
|
107
120
|
declare const _default: React.Context<{
|
|
108
121
|
typeTester: {
|
|
@@ -117,7 +130,7 @@ declare const _default: React.Context<{
|
|
|
117
130
|
selectable: boolean;
|
|
118
131
|
priceBar: boolean;
|
|
119
132
|
textInput: boolean;
|
|
120
|
-
initialMode: "
|
|
133
|
+
initialMode: "group" | "local";
|
|
121
134
|
groupEdit: boolean;
|
|
122
135
|
bulletStyle: "round" | "square";
|
|
123
136
|
openTypeFeatures: {
|
|
@@ -174,6 +187,12 @@ declare const _default: React.Context<{
|
|
|
174
187
|
consentMessage: string | undefined;
|
|
175
188
|
segment: SegmentConfig | undefined;
|
|
176
189
|
};
|
|
190
|
+
preview: {
|
|
191
|
+
endpoint: string;
|
|
192
|
+
revalidateEndpoint: string | undefined;
|
|
193
|
+
clientSide: boolean;
|
|
194
|
+
};
|
|
177
195
|
corsErrorModal: boolean;
|
|
196
|
+
cdnUrl: string;
|
|
178
197
|
}>;
|
|
179
198
|
export default _default;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
|
+
import { PREVIEW_ENDPOINT } from '../preview/constants.js';
|
|
5
|
+
import { FONTDUE_CDN_URL } from '../data/unicodeNamesUrl.js';
|
|
4
6
|
const makeTypeTesterConfig = config => {
|
|
5
7
|
var _config$openTypeFeatu, _config$openTypeFeatu2, _config$openTypeFeatu3, _config$openTypeFeatu4, _config$size, _config$size2, _config$size3, _config$lineHeight, _config$lineHeight2, _config$lineHeight3, _config$letterSpacing, _config$letterSpacing2, _config$letterSpacing3, _config$columns, _config$columns2, _config$columns3;
|
|
6
8
|
let shy = (config === null || config === void 0 ? void 0 : config.shy) ?? false;
|
|
@@ -79,7 +81,7 @@ export const mergeConfig = (base, override) => {
|
|
|
79
81
|
return deepMerge(base, override);
|
|
80
82
|
};
|
|
81
83
|
export const makeConfig = config => {
|
|
82
|
-
var _config$form, _config$storeModal, _config$storeModal2, _config$storeModal3, _config$storeModal4, _config$stripe, _config$tracking, _config$tracking2, _config$tracking3, _config$tracking4;
|
|
84
|
+
var _config$form, _config$storeModal, _config$storeModal2, _config$storeModal3, _config$storeModal4, _config$stripe, _config$tracking, _config$tracking2, _config$tracking3, _config$tracking4, _config$preview, _config$preview2, _config$preview3;
|
|
83
85
|
return {
|
|
84
86
|
typeTester: makeTypeTesterConfig(config === null || config === void 0 ? void 0 : config.typeTester),
|
|
85
87
|
form: {
|
|
@@ -101,7 +103,15 @@ export const makeConfig = config => {
|
|
|
101
103
|
consentMessage: config === null || config === void 0 ? void 0 : (_config$tracking3 = config.tracking) === null || _config$tracking3 === void 0 ? void 0 : _config$tracking3.consentMessage,
|
|
102
104
|
segment: config === null || config === void 0 ? void 0 : (_config$tracking4 = config.tracking) === null || _config$tracking4 === void 0 ? void 0 : _config$tracking4.segment
|
|
103
105
|
},
|
|
104
|
-
|
|
106
|
+
preview: {
|
|
107
|
+
endpoint: (config === null || config === void 0 ? void 0 : (_config$preview = config.preview) === null || _config$preview === void 0 ? void 0 : _config$preview.endpoint) ?? PREVIEW_ENDPOINT,
|
|
108
|
+
// No default: an unset revalidate endpoint hides the toolbar's refresh
|
|
109
|
+
// button (frameworks without a client-callable purge leave it unset).
|
|
110
|
+
revalidateEndpoint: config === null || config === void 0 ? void 0 : (_config$preview2 = config.preview) === null || _config$preview2 === void 0 ? void 0 : _config$preview2.revalidateEndpoint,
|
|
111
|
+
clientSide: (config === null || config === void 0 ? void 0 : (_config$preview3 = config.preview) === null || _config$preview3 === void 0 ? void 0 : _config$preview3.clientSide) ?? false
|
|
112
|
+
},
|
|
113
|
+
corsErrorModal: (config === null || config === void 0 ? void 0 : config.corsErrorModal) ?? true,
|
|
114
|
+
cdnUrl: (config === null || config === void 0 ? void 0 : config.cdnUrl) ?? FONTDUE_CDN_URL
|
|
105
115
|
};
|
|
106
116
|
};
|
|
107
117
|
export default /*#__PURE__*/React.createContext(makeConfig());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function renderConnectionErrorToolbar(origin: string, fontdueUrl: string): void;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
|
|
4
|
+
// Connection-error status, mounted in its own React root (like the modal it
|
|
5
|
+
// replaces) rather than inside the admin toolbar's component tree.
|
|
6
|
+
//
|
|
7
|
+
// A CORS/connection outage commonly makes the consumer's own Relay-driven
|
|
8
|
+
// components throw, and their error boundary can replace the whole route
|
|
9
|
+
// wholesale — which would take an in-tree status down with it (verified in a
|
|
10
|
+
// single-tree framework: the page became an "Oops!" error page and the toolbar
|
|
11
|
+
// vanished). An independent root keeps this status visible regardless, while
|
|
12
|
+
// reusing the admin toolbar's restrained styling so it reads as the same small
|
|
13
|
+
// platform chrome — not the old full-screen modal.
|
|
14
|
+
//
|
|
15
|
+
// It deliberately takes no contexts (Relay, config, url): a blocked connection
|
|
16
|
+
// can't confirm an admin anyway, so this shows to whoever is on the page, with
|
|
17
|
+
// just the origin + Fontdue URL the detector hands it.
|
|
18
|
+
|
|
19
|
+
// Deep link to where an admin adds allowed origins, derived from the blocked
|
|
20
|
+
// request's own URL. Null if it can't be parsed, so the link is simply omitted.
|
|
21
|
+
function settingsUrl(fontdueUrl) {
|
|
22
|
+
try {
|
|
23
|
+
return `${new URL(fontdueUrl).origin}/admin/settings/integration`;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function ConnectionErrorToolbar(_ref) {
|
|
29
|
+
let {
|
|
30
|
+
origin,
|
|
31
|
+
fontdueUrl
|
|
32
|
+
} = _ref;
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
const fixUrl = settingsUrl(fontdueUrl);
|
|
35
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
36
|
+
className: "fontdue-admin-toolbar",
|
|
37
|
+
"data-connection-error": "true"
|
|
38
|
+
}, open && /*#__PURE__*/React.createElement("div", {
|
|
39
|
+
className: "fontdue-admin-toolbar__panel"
|
|
40
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
41
|
+
className: "fontdue-admin-toolbar__header"
|
|
42
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
43
|
+
className: "fontdue-admin-toolbar__title"
|
|
44
|
+
}, "Fontdue")), /*#__PURE__*/React.createElement("div", {
|
|
45
|
+
className: "fontdue-admin-toolbar__error-status",
|
|
46
|
+
role: "alert"
|
|
47
|
+
}, /*#__PURE__*/React.createElement("p", {
|
|
48
|
+
className: "fontdue-admin-toolbar__error-status-text"
|
|
49
|
+
}, /*#__PURE__*/React.createElement(WarningIcon, null), "Fontdue couldn\u2019t be reached from this site."), /*#__PURE__*/React.createElement("p", {
|
|
50
|
+
className: "fontdue-admin-toolbar__hint"
|
|
51
|
+
}, "This is most likely a cross-origin (CORS) setting \u2014 ", origin, " isn\u2019t on Fontdue\u2019s allowed list. Add it under Settings \u2192 Integration and this page will reload automatically."), /*#__PURE__*/React.createElement("p", {
|
|
52
|
+
className: "fontdue-admin-toolbar__hint"
|
|
53
|
+
}, "If it\u2019s already listed, the connection may be failing for another reason \u2014 a network problem or a temporary Fontdue outage."), fixUrl && /*#__PURE__*/React.createElement("a", {
|
|
54
|
+
className: "fontdue-admin-toolbar__action",
|
|
55
|
+
href: fixUrl,
|
|
56
|
+
target: "_blank",
|
|
57
|
+
rel: "noreferrer"
|
|
58
|
+
}, "Open integration settings \u2197")), /*#__PURE__*/React.createElement("p", {
|
|
59
|
+
className: "fontdue-admin-toolbar__meta"
|
|
60
|
+
}, "Shown because Fontdue couldn\u2019t load on this page.")), /*#__PURE__*/React.createElement("button", {
|
|
61
|
+
type: "button",
|
|
62
|
+
className: "fontdue-admin-toolbar__button",
|
|
63
|
+
onClick: () => setOpen(v => !v),
|
|
64
|
+
"aria-expanded": open
|
|
65
|
+
}, /*#__PURE__*/React.createElement(WarningIcon, null), "Fontdue \xB7 connection issue"));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Small inline warning glyph (a triangle with an exclamation), matching the
|
|
69
|
+
// admin toolbar's. Inherits the surrounding text color via currentColor.
|
|
70
|
+
function WarningIcon() {
|
|
71
|
+
return /*#__PURE__*/React.createElement("svg", {
|
|
72
|
+
className: "fontdue-admin-toolbar__warning-icon",
|
|
73
|
+
width: "13",
|
|
74
|
+
height: "13",
|
|
75
|
+
viewBox: "0 0 16 16",
|
|
76
|
+
fill: "none",
|
|
77
|
+
stroke: "currentColor",
|
|
78
|
+
strokeWidth: "1.4",
|
|
79
|
+
"aria-hidden": "true",
|
|
80
|
+
focusable: "false"
|
|
81
|
+
}, /*#__PURE__*/React.createElement("path", {
|
|
82
|
+
d: "M8 1.5 15 14H1L8 1.5Z",
|
|
83
|
+
strokeLinejoin: "miter"
|
|
84
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
85
|
+
d: "M8 6v3.5",
|
|
86
|
+
strokeLinecap: "square"
|
|
87
|
+
}), /*#__PURE__*/React.createElement("path", {
|
|
88
|
+
d: "M8 11.5v.5",
|
|
89
|
+
strokeLinecap: "square"
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
function mount(origin, fontdueUrl) {
|
|
93
|
+
const container = document.createElement('div');
|
|
94
|
+
document.body.appendChild(container);
|
|
95
|
+
createRoot(container).render( /*#__PURE__*/React.createElement(ConnectionErrorToolbar, {
|
|
96
|
+
origin: origin,
|
|
97
|
+
fontdueUrl: fontdueUrl
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
export function renderConnectionErrorToolbar(origin, fontdueUrl) {
|
|
101
|
+
if (document.body) {
|
|
102
|
+
mount(origin, fontdueUrl);
|
|
103
|
+
} else {
|
|
104
|
+
document.addEventListener('DOMContentLoaded', () => mount(origin, fontdueUrl));
|
|
105
|
+
}
|
|
106
|
+
}
|