@elytracms/next 0.0.92 → 0.0.93
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/dist/host-image.d.ts +14 -18
- package/dist/host-image.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +33 -79
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/host-image.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
+
import { ResolvedAsset } from '@elytracms/core/cms-core';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* `@elytracms/next/host-image` — the host image-delivery seam for DUAL-RENDERED
|
|
@@ -37,26 +38,21 @@ import { ReactNode } from 'react';
|
|
|
37
38
|
* second singleton instance into the server entry, disconnecting
|
|
38
39
|
* `setHostImage` from `getHostImage` — pinned by `package-build.test.ts`).
|
|
39
40
|
*/
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
x: number;
|
|
48
|
-
y: number;
|
|
49
|
-
} | null;
|
|
50
|
-
/**
|
|
51
|
-
* LQIP blur-placeholder data URI (EC-420) → next/image blurDataURL. Optional so a
|
|
52
|
-
* consumer mapping a `ResolvedAsset` in stays source-compatible; when present the
|
|
53
|
-
* delivered image blurs up while the optimized bytes load.
|
|
54
|
-
*/
|
|
55
|
-
lqip?: string | null;
|
|
56
|
-
}
|
|
41
|
+
/**
|
|
42
|
+
* The resolved-asset slice a host image renderer needs — DERIVED from `ResolvedAsset`
|
|
43
|
+
* (EC-587; it used to mirror it by hand). `lqip` (EC-420 → next/image blurDataURL) is
|
|
44
|
+
* optional so a caller handing a partial asset stays source-compatible; when present
|
|
45
|
+
* the delivered image blurs up while the optimized bytes load.
|
|
46
|
+
*/
|
|
47
|
+
type HostImageAsset = Pick<ResolvedAsset, 'url' | 'width' | 'height' | 'alt' | 'focalPoint'> & Partial<Pick<ResolvedAsset, 'lqip'>>;
|
|
57
48
|
interface HostImageProps {
|
|
58
49
|
asset: HostImageAsset;
|
|
59
|
-
|
|
50
|
+
/**
|
|
51
|
+
* The image's alt text. Omitted → the ASSET's alt (EC-587: an asset's alt has one
|
|
52
|
+
* home and one reader). Pass it only to override deliberately — e.g. `''` for a
|
|
53
|
+
* decorative image.
|
|
54
|
+
*/
|
|
55
|
+
alt?: string;
|
|
60
56
|
className?: string;
|
|
61
57
|
loading?: 'eager' | 'lazy';
|
|
62
58
|
/** Above-the-fold opt-out of lazy-loading (the hero image). */
|
package/dist/host-image.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/host-image.ts"],"names":[],"mappings":";AAoEA,IAAI,SAAA,GAAsC,IAAA;AAGnC,SAAS,aAAa,QAAA,EAA0C;AACrE,EAAA,SAAA,GAAY,QAAA;AACd;AAGO,SAAS,YAAA,GAAyC;AACvD,EAAA,OAAO,SAAA;AACT;AAiBO,IAAM,yBAAA,GAAgF;AAAA,EAC3F,EAAE,QAAA,EAAU,OAAA,EAAS,QAAA,EAAU,gBAAA;AACjC","file":"host-image.js","sourcesContent":["import type { ReactNode } from 'react'\n\n/**\n * `@elytracms/next/host-image` — the host image-delivery seam for DUAL-RENDERED\n * project components (EC-368, from the consumer-proven EC-271 pattern).\n *\n * The `project.*` page-builder components are dual-rendered: the SAME module\n * renders in the Vite studio canvas AND the Next host (AD-2/AD-11), so it may\n * not statically import `next/image` — that would break the Vite build. And in\n * the Next host these components render as Server Components, where React\n * context is unavailable, so an optimized `<Image>` can't be threaded through a\n * provider either.\n *\n * The seam that satisfies both: a tiny module singleton the host sets once at\n * boot. The studio never sets it (it renders the components via the root\n * `elytra.config`, which never imports the Next host), so the canvas keeps a\n * plain-`<img>` fallback; the Next host injects the `ElytraImage`-backed\n * renderer so content images go through `next/image`'s optimizer:\n *\n * ```ts\n * // frontend/lib/host.ts — imported once by the catch-all route at boot\n * import { elytraHostImage } from '@elytracms/next'\n * import { setHostImage } from '@elytracms/next/host-image'\n *\n * setHostImage(elytraHostImage())\n * ```\n *\n * WHEN (and whether) to register stays consumer-owned — legacy-interop windows\n * are real: a host whose global `next/image` config is still owned by a\n * previous stack (e.g. a Sanity-era custom loader) deliberately skips\n * registration until the conflict clears, and its components keep the honest\n * plain-`<img>` fallback.\n *\n * This module is deliberately its OWN subpath entry importing nothing but React\n * types — safe in Vite, in RSC, and in `next.config.*`. The main\n * `@elytracms/next` entry never imports it at runtime (that would bundle a\n * second singleton instance into the server entry, disconnecting\n * `setHostImage` from `getHostImage` — pinned by `package-build.test.ts`).\n */\n\n
|
|
1
|
+
{"version":3,"sources":["../src/host-image.ts"],"names":[],"mappings":";AAoEA,IAAI,SAAA,GAAsC,IAAA;AAGnC,SAAS,aAAa,QAAA,EAA0C;AACrE,EAAA,SAAA,GAAY,QAAA;AACd;AAGO,SAAS,YAAA,GAAyC;AACvD,EAAA,OAAO,SAAA;AACT;AAiBO,IAAM,yBAAA,GAAgF;AAAA,EAC3F,EAAE,QAAA,EAAU,OAAA,EAAS,QAAA,EAAU,gBAAA;AACjC","file":"host-image.js","sourcesContent":["import type { ReactNode } from 'react'\nimport type { ResolvedAsset } from '@elytracms/core/cms-core'\n\n/**\n * `@elytracms/next/host-image` — the host image-delivery seam for DUAL-RENDERED\n * project components (EC-368, from the consumer-proven EC-271 pattern).\n *\n * The `project.*` page-builder components are dual-rendered: the SAME module\n * renders in the Vite studio canvas AND the Next host (AD-2/AD-11), so it may\n * not statically import `next/image` — that would break the Vite build. And in\n * the Next host these components render as Server Components, where React\n * context is unavailable, so an optimized `<Image>` can't be threaded through a\n * provider either.\n *\n * The seam that satisfies both: a tiny module singleton the host sets once at\n * boot. The studio never sets it (it renders the components via the root\n * `elytra.config`, which never imports the Next host), so the canvas keeps a\n * plain-`<img>` fallback; the Next host injects the `ElytraImage`-backed\n * renderer so content images go through `next/image`'s optimizer:\n *\n * ```ts\n * // frontend/lib/host.ts — imported once by the catch-all route at boot\n * import { elytraHostImage } from '@elytracms/next'\n * import { setHostImage } from '@elytracms/next/host-image'\n *\n * setHostImage(elytraHostImage())\n * ```\n *\n * WHEN (and whether) to register stays consumer-owned — legacy-interop windows\n * are real: a host whose global `next/image` config is still owned by a\n * previous stack (e.g. a Sanity-era custom loader) deliberately skips\n * registration until the conflict clears, and its components keep the honest\n * plain-`<img>` fallback.\n *\n * This module is deliberately its OWN subpath entry importing nothing but React\n * types — safe in Vite, in RSC, and in `next.config.*`. The main\n * `@elytracms/next` entry never imports it at runtime (that would bundle a\n * second singleton instance into the server entry, disconnecting\n * `setHostImage` from `getHostImage` — pinned by `package-build.test.ts`).\n */\n\n/**\n * The resolved-asset slice a host image renderer needs — DERIVED from `ResolvedAsset`\n * (EC-587; it used to mirror it by hand). `lqip` (EC-420 → next/image blurDataURL) is\n * optional so a caller handing a partial asset stays source-compatible; when present\n * the delivered image blurs up while the optimized bytes load.\n */\nexport type HostImageAsset = Pick<ResolvedAsset, 'url' | 'width' | 'height' | 'alt' | 'focalPoint'> &\n Partial<Pick<ResolvedAsset, 'lqip'>>\n\nexport interface HostImageProps {\n asset: HostImageAsset\n /**\n * The image's alt text. Omitted → the ASSET's alt (EC-587: an asset's alt has one\n * home and one reader). Pass it only to override deliberately — e.g. `''` for a\n * decorative image.\n */\n alt?: string\n className?: string\n loading?: 'eager' | 'lazy'\n /** Above-the-fold opt-out of lazy-loading (the hero image). */\n priority?: boolean\n /** Responsive `sizes` hint so the optimizer serves a column-sized variant. */\n sizes?: string\n}\n\nexport type HostImageRenderer = (props: HostImageProps) => ReactNode\n\nlet hostImage: HostImageRenderer | null = null\n\n/** Host-only: register the optimized image renderer (called once at host boot). */\nexport function setHostImage(renderer: HostImageRenderer | null): void {\n hostImage = renderer\n}\n\n/** The registered host renderer, or `null` in the studio canvas (plain-`<img>` fallback). */\nexport function getHostImage(): HostImageRenderer | null {\n return hostImage\n}\n\n/**\n * The `next.config.*` `images.remotePatterns` preset for Elytra asset delivery\n * (EC-368 image defaults): Convex file-storage serve URLs, where uploaded\n * assets live in connected mode. Without it `next/image` refuses the remote\n * origin and delivery images 400 — spread it into your config:\n *\n * ```js\n * // next.config.mjs\n * import { convexAssetRemotePatterns } from '@elytracms/next/host-image'\n *\n * const nextConfig = {\n * images: { remotePatterns: [...convexAssetRemotePatterns] },\n * }\n * ```\n */\nexport const convexAssetRemotePatterns: readonly { protocol: 'https'; hostname: string }[] = [\n { protocol: 'https', hostname: '*.convex.cloud' },\n]\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -310,6 +310,12 @@ interface StaticContentData {
|
|
|
310
310
|
* explicit unresolved-placeholder scheme (never a silent empty string).
|
|
311
311
|
*/
|
|
312
312
|
assetUrl?: (asset: AssetRecord) => string;
|
|
313
|
+
/**
|
|
314
|
+
* EC-585 — the stored rows the host could not read (`parseDeliveredDocuments`), handed
|
|
315
|
+
* to every client of this source as its `sourceIssues` (and in the results they
|
|
316
|
+
* concern). A malformed row is not delivered, and never silently.
|
|
317
|
+
*/
|
|
318
|
+
sourceIssues?: readonly CmsValidationIssue[];
|
|
313
319
|
}
|
|
314
320
|
/**
|
|
315
321
|
* Assemble a `ContentSource` from plain prefetched data — the pure half of
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { registerComponents, renderDocumentBodyItems, documentBodyItems, serializeListingQuery, documentBodyRichTextValues, documentBodyCompositionNodes, richTextEmbedsInValue, collectReferencedAssetIds, collectRichTextRefsInValue, collectReferencedRelations, collectReferencedListings, collectReferencedRichTextLinks,
|
|
2
|
-
import { Fragment as Fragment$1, isValidElement } from 'react';
|
|
1
|
+
import { registerComponents, createRenderContext, renderDocumentBodyItems, documentBodyItems, serializeListingQuery, documentBodyRichTextValues, documentBodyCompositionNodes, richTextEmbedsInValue, collectReferencedAssetIds, collectRichTextRefsInValue, collectReferencedRelations, collectReferencedListings, collectReferencedRichTextLinks, listingRequestOf } from '@elytracms/core/runtime-renderer';
|
|
3
2
|
import { isBlockSequenceValueShape } from '@elytracms/core/rich-text';
|
|
4
|
-
import { jsx, Fragment
|
|
5
|
-
import { cacheTags, isDocumentHistory, resolvedAsset, createContentClient, createContentContext } from '@elytracms/core/content';
|
|
6
|
-
import { DEFAULT_SEO_FIELD, parseLocaleConfig, documentKey, liveRedirectRecords, SEO_SUBFIELDS, splitLocalePath, DRAFT_PERSPECTIVE, splitPath, localeUrlPath, variantOf, DRAFT_VARIANT, createRouter, presentationBodyFields,
|
|
3
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { cacheTags, isDocumentHistory, resolvedAsset, parseDeliveredDocuments, createContentClient, createContentContext } from '@elytracms/core/content';
|
|
5
|
+
import { DEFAULT_SEO_FIELD, parseLocaleConfig, documentKey, liveRedirectRecords, SEO_SUBFIELDS, splitLocalePath, DRAFT_PERSPECTIVE, splitPath, localeUrlPath, variantOf, DRAFT_VARIANT, createRouter, presentationBodyFields, redirectRecordSchema } from '@elytracms/core/cms-core';
|
|
7
6
|
export { DRAFT_PERSPECTIVE, documentSchema, localeConfigSchema, localeUrlPath, parseLocaleConfig, redirectRecordSchema, resolveLocale, routeRecordSchema, splitLocalePath, textDirection, variantOf } from '@elytracms/core/cms-core';
|
|
8
7
|
import { revalidateTag, cacheTag } from 'next/cache';
|
|
9
8
|
import { draftMode } from 'next/headers';
|
|
@@ -34,33 +33,15 @@ function listingResolver(listings) {
|
|
|
34
33
|
return Array.isArray(baked) ? baked : null;
|
|
35
34
|
};
|
|
36
35
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (listing.state === "invalid") return listing.messages;
|
|
43
|
-
const baked = listings?.[serializeListingQuery(listing.query)];
|
|
44
|
-
return baked && !Array.isArray(baked) ? baked.errors.map((error) => error.message) : null;
|
|
45
|
-
}
|
|
46
|
-
function InvalidListingNotice(props) {
|
|
47
|
-
return /* @__PURE__ */ jsx("div", { "data-ec-fallback": "invalid-listing", "data-ec-node-id": props.nodeId, role: "note", children: `This listing can't run: ${props.messages.join(" ")}` });
|
|
36
|
+
function bakedListingFailure(listings) {
|
|
37
|
+
return (query) => {
|
|
38
|
+
const baked = listings[serializeListingQuery(query)];
|
|
39
|
+
return baked && !Array.isArray(baked) ? baked.errors.map((error) => error.message) : null;
|
|
40
|
+
};
|
|
48
41
|
}
|
|
49
42
|
function assetResolver(assets) {
|
|
50
43
|
const byId = new Map(assets.map((asset) => [asset.id, asset]));
|
|
51
|
-
return (id) =>
|
|
52
|
-
const asset = byId.get(id);
|
|
53
|
-
return asset ? {
|
|
54
|
-
url: asset.url,
|
|
55
|
-
width: asset.width,
|
|
56
|
-
height: asset.height,
|
|
57
|
-
alt: asset.alt,
|
|
58
|
-
focalPoint: asset.focalPoint,
|
|
59
|
-
// EC-420: carry the LQIP so a placed Image blurs up on delivery.
|
|
60
|
-
lqip: asset.lqip,
|
|
61
|
-
meta: asset.meta
|
|
62
|
-
} : null;
|
|
63
|
-
};
|
|
44
|
+
return (id) => byId.get(id) ?? null;
|
|
64
45
|
}
|
|
65
46
|
function UnresolvedPageFallback(props) {
|
|
66
47
|
return /* @__PURE__ */ jsx("div", { "data-ec-fallback": "unresolved-page", "data-ec-status": props.status, children: `This URL did not resolve to a page (status: ${props.status}).` });
|
|
@@ -68,41 +49,28 @@ function UnresolvedPageFallback(props) {
|
|
|
68
49
|
function MissingDocumentFallback() {
|
|
69
50
|
return /* @__PURE__ */ jsx("div", { "data-ec-fallback": "missing-document", children: `Route resolved but carried no page document to render.` });
|
|
70
51
|
}
|
|
71
|
-
function stripServerErrorBoundary(_node, rendered) {
|
|
72
|
-
if (isValidElement(rendered) && rendered.type === RenderErrorBoundary) {
|
|
73
|
-
return rendered.props.children ?? null;
|
|
74
|
-
}
|
|
75
|
-
return rendered;
|
|
76
|
-
}
|
|
77
52
|
async function CanvasRenderer(props) {
|
|
78
53
|
const { result, components } = props;
|
|
79
54
|
if (result.status !== "ok") {
|
|
80
55
|
return /* @__PURE__ */ jsx(UnresolvedPageFallback, { status: result.status });
|
|
81
56
|
}
|
|
82
|
-
const ctx = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const stripped = stripServerErrorBoundary(node, rendered);
|
|
98
|
-
const failure = listingFailureOf(node, components, props.listings);
|
|
99
|
-
if (!failure) return stripped;
|
|
100
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
101
|
-
/* @__PURE__ */ jsx(InvalidListingNotice, { nodeId: node.id, messages: failure }),
|
|
102
|
-
stripped
|
|
103
|
-
] }, node.id);
|
|
57
|
+
const ctx = createRenderContext({
|
|
58
|
+
components,
|
|
59
|
+
mode: "delivery",
|
|
60
|
+
source: {
|
|
61
|
+
resolveAsset: assetResolver(props.assets ?? []),
|
|
62
|
+
// EC-254: wire relation population only when the host baked the populated
|
|
63
|
+
// targets, so a host that did not opt in keeps the raw-ref passthrough.
|
|
64
|
+
...props.relations ? { resolveRelation: relationResolver(props.relations) } : {},
|
|
65
|
+
// EC-255: wire the listing resolver only when the host baked the query results,
|
|
66
|
+
// so a host that did not opt in keeps listing props empty (the block's own state).
|
|
67
|
+
...props.listings ? { resolveListing: listingResolver(props.listings), listingFailure: bakedListingFailure(props.listings) } : {},
|
|
68
|
+
// EC-291: wire the rich-text link resolver only when the host baked the link map,
|
|
69
|
+
// so a host that did not opt in degrades a reference link to the visible
|
|
70
|
+
// `unresolved-ref` issue (EC-290) rather than a wrong href.
|
|
71
|
+
...props.links ? { resolveLink: linkResolver(props.links) } : {}
|
|
104
72
|
}
|
|
105
|
-
};
|
|
73
|
+
});
|
|
106
74
|
const document = result.document;
|
|
107
75
|
if (document) {
|
|
108
76
|
return /* @__PURE__ */ jsx(Fragment, { children: renderDocumentBodyItems(bodyItemsOf(document, props.presentation), ctx) });
|
|
@@ -181,7 +149,8 @@ function createStaticContentSource(data) {
|
|
|
181
149
|
locales
|
|
182
150
|
}),
|
|
183
151
|
routes,
|
|
184
|
-
redirects
|
|
152
|
+
redirects,
|
|
153
|
+
...data.sourceIssues ? { sourceIssues: data.sourceIssues } : {}
|
|
185
154
|
});
|
|
186
155
|
return {
|
|
187
156
|
projectId: data.projectId,
|
|
@@ -243,16 +212,12 @@ function seoContextOf(result, assets = []) {
|
|
|
243
212
|
function str(value) {
|
|
244
213
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
245
214
|
}
|
|
246
|
-
function looksLikeUrl(value) {
|
|
247
|
-
return /^(https?:)?\/\//.test(value) || value.startsWith("/");
|
|
248
|
-
}
|
|
249
215
|
function resolveSeoImage(value, assets) {
|
|
250
216
|
if (value && typeof value === "object") {
|
|
251
217
|
const url = value.url;
|
|
252
218
|
return typeof url === "string" && url.length > 0 ? url : null;
|
|
253
219
|
}
|
|
254
220
|
if (typeof value !== "string" || value.length === 0) return null;
|
|
255
|
-
if (looksLikeUrl(value)) return value;
|
|
256
221
|
return assets.find((asset) => asset.id === value)?.url ?? null;
|
|
257
222
|
}
|
|
258
223
|
function openGraphLocale(locale) {
|
|
@@ -878,7 +843,7 @@ function elytraHostImage(options = {}) {
|
|
|
878
843
|
return ({ asset, alt, className, priority, sizes, loading }) => /* @__PURE__ */ jsx(
|
|
879
844
|
ElytraImage,
|
|
880
845
|
{
|
|
881
|
-
asset: { ...asset, alt },
|
|
846
|
+
asset: { ...asset, alt: alt ?? asset.alt },
|
|
882
847
|
...className !== void 0 ? { className } : {},
|
|
883
848
|
...priority ? { priority: true } : {},
|
|
884
849
|
...sizes !== void 0 ? { sizes } : {},
|
|
@@ -930,14 +895,6 @@ async function runConvexQuery(fetchImpl, convexUrl, path, args) {
|
|
|
930
895
|
}
|
|
931
896
|
return body.value;
|
|
932
897
|
}
|
|
933
|
-
function parseDocuments(rows) {
|
|
934
|
-
const documents = [];
|
|
935
|
-
for (const row of rows) {
|
|
936
|
-
const parsed = documentSchema.safeParse(row);
|
|
937
|
-
if (parsed.success) documents.push(parsed.data);
|
|
938
|
-
}
|
|
939
|
-
return documents;
|
|
940
|
-
}
|
|
941
898
|
function parseRedirects(rows) {
|
|
942
899
|
const redirects = [];
|
|
943
900
|
for (const row of rows) {
|
|
@@ -948,7 +905,7 @@ function parseRedirects(rows) {
|
|
|
948
905
|
}
|
|
949
906
|
function assembleLiveSource(payload, options, perspective) {
|
|
950
907
|
const locales = options.locales;
|
|
951
|
-
const documents =
|
|
908
|
+
const { documents, issues: sourceIssues } = parseDeliveredDocuments(payload.documents);
|
|
952
909
|
const routes = [...options.routes];
|
|
953
910
|
const documentSources = perspective === "published" ? documents.map((doc) => ({ draft: doc, published: doc, versions: [] })) : documents;
|
|
954
911
|
return createStaticContentSource({
|
|
@@ -961,7 +918,8 @@ function assembleLiveSource(payload, options, perspective) {
|
|
|
961
918
|
redirects: parseRedirects(payload.redirects ?? []),
|
|
962
919
|
collections: [...options.collections],
|
|
963
920
|
documents: documentSources,
|
|
964
|
-
assets: payload.assets
|
|
921
|
+
assets: payload.assets,
|
|
922
|
+
...sourceIssues.length > 0 ? { sourceIssues } : {}
|
|
965
923
|
});
|
|
966
924
|
}
|
|
967
925
|
async function loadLiveContent(options, perspective) {
|
|
@@ -1023,11 +981,7 @@ function resolveGlobals(source, options = {}) {
|
|
|
1023
981
|
const navDocs = listDocs(navigationCollection);
|
|
1024
982
|
const navigation = (slot) => navDocs.find((doc) => doc.alias === slot) ?? null;
|
|
1025
983
|
const assetUrl = (value) => {
|
|
1026
|
-
if (typeof value === "string")
|
|
1027
|
-
const found = assetById.get(value);
|
|
1028
|
-
if (found) return found.url;
|
|
1029
|
-
return value.startsWith("http") || value.startsWith("/") ? value : void 0;
|
|
1030
|
-
}
|
|
984
|
+
if (typeof value === "string") return assetById.get(value)?.url;
|
|
1031
985
|
const url = record(value).url;
|
|
1032
986
|
return typeof url === "string" && url.length > 0 ? url : void 0;
|
|
1033
987
|
};
|