@comet/site-nextjs 9.0.0-beta.3 → 9.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/lib/blocks/DamVideoBlock.js +2 -13
  2. package/lib/blocks/InternalLinkBlock.js +2 -18
  3. package/lib/blocks/PixelImageBlock.d.ts.map +1 -1
  4. package/lib/blocks/PixelImageBlock.js +2 -69
  5. package/lib/blocks/PixelImageBlock.module.scss.js +1 -8
  6. package/lib/blocks/VimeoVideoBlock.js +2 -13
  7. package/lib/blocks/YouTubeVideoBlock.js +2 -13
  8. package/lib/blocks/factories/SeoBlock.js +5 -39
  9. package/lib/blocks/helpers/VideoPreviewImage.js +2 -9
  10. package/lib/graphQLFetch/graphQLFetch.js +1 -36
  11. package/lib/image/Image.d.ts.map +1 -1
  12. package/lib/image/Image.js +2 -10
  13. package/lib/index.d.ts +2 -5
  14. package/lib/index.d.ts.map +1 -1
  15. package/lib/index.js +1 -112
  16. package/lib/persistedQueries/webpackPersistedQueriesLoader.d.ts +2 -0
  17. package/lib/persistedQueries/webpackPersistedQueriesLoader.d.ts.map +1 -0
  18. package/lib/persistedQueries/webpackPersistedQueriesLoader.js +1 -0
  19. package/lib/server.d.ts +5 -0
  20. package/lib/server.d.ts.map +1 -0
  21. package/lib/server.js +1 -0
  22. package/lib/site-react/lib/blockLoader/blockLoader.js +1 -59
  23. package/lib/site-react/lib/blocks/DamFileDownloadLinkBlock.js +1 -27
  24. package/lib/site-react/lib/blocks/DamVideoBlock.js +3 -99
  25. package/lib/site-react/lib/blocks/DamVideoBlock.module.scss.js +1 -14
  26. package/lib/site-react/lib/blocks/EmailLinkBlock.js +1 -21
  27. package/lib/site-react/lib/blocks/ExternalLinkBlock.js +1 -47
  28. package/lib/site-react/lib/blocks/PhoneLinkBlock.js +1 -21
  29. package/lib/site-react/lib/blocks/SvgImageBlock.js +1 -30
  30. package/lib/site-react/lib/blocks/VimeoVideoBlock.js +2 -129
  31. package/lib/site-react/lib/blocks/VimeoVideoBlock.module.scss.js +1 -14
  32. package/lib/site-react/lib/blocks/YouTubeVideoBlock.js +2 -136
  33. package/lib/site-react/lib/blocks/YouTubeVideoBlock.module.scss.js +1 -14
  34. package/lib/site-react/lib/blocks/factories/BlocksBlock.js +1 -27
  35. package/lib/site-react/lib/blocks/factories/ListBlock.js +1 -13
  36. package/lib/site-react/lib/blocks/factories/OneOfBlock.js +1 -23
  37. package/lib/site-react/lib/blocks/factories/OptionalBlock.js +1 -10
  38. package/lib/site-react/lib/blocks/helpers/PlayPauseButton.js +1 -9
  39. package/lib/site-react/lib/blocks/helpers/PlayPauseButton.module.scss.js +1 -14
  40. package/lib/site-react/lib/blocks/helpers/RichTextBlockHelper.js +1 -7
  41. package/lib/site-react/lib/blocks/helpers/TipTapRichTextRenderer.js +1 -0
  42. package/lib/site-react/lib/blocks/helpers/VideoPreviewImage.js +2 -23
  43. package/lib/site-react/lib/blocks/helpers/VideoPreviewImage.module.scss.js +1 -17
  44. package/lib/site-react/lib/blocks/helpers/useIsElementInViewport.js +1 -21
  45. package/lib/site-react/lib/cookies/CookieApiContext.js +1 -18
  46. package/lib/site-react/lib/cookies/CookieSafe.js +1 -18
  47. package/lib/site-react/lib/cookies/useCookieBotCookieApi.js +1 -33
  48. package/lib/site-react/lib/cookies/useLocalStorageCookieApi.js +1 -40
  49. package/lib/site-react/lib/cookies/useOneTrustCookieApi.js +1 -45
  50. package/lib/site-react/lib/errorHandler/ErrorHandlerBoundary.js +1 -10
  51. package/lib/site-react/lib/errorHandler/ErrorHandlerBoundaryInternal.js +1 -26
  52. package/lib/site-react/lib/errorHandler/ErrorHandlerProvider.js +1 -20
  53. package/lib/site-react/lib/graphQLFetch/fetchInMemoryCache.js +1 -37
  54. package/lib/site-react/lib/graphQLFetch/graphQLFetch.js +1 -125
  55. package/lib/site-react/lib/iframebridge/IFrameBridge.js +3 -253
  56. package/lib/site-react/lib/iframebridge/IFrameBridge.module.scss.js +1 -8
  57. package/lib/site-react/lib/iframebridge/IFrameMessage.js +1 -21
  58. package/lib/site-react/lib/iframebridge/Preview.js +1 -56
  59. package/lib/site-react/lib/iframebridge/Preview.module.scss.js +1 -8
  60. package/lib/site-react/lib/iframebridge/PreviewOverlay.js +1 -28
  61. package/lib/site-react/lib/iframebridge/PreviewOverlay.module.scss.js +1 -8
  62. package/lib/site-react/lib/iframebridge/PreviewOverlayElement.js +1 -36
  63. package/lib/site-react/lib/iframebridge/PreviewOverlayElement.module.scss.js +1 -20
  64. package/lib/site-react/lib/iframebridge/useBlockPreviewFetch.js +1 -29
  65. package/lib/site-react/lib/iframebridge/useIFrameBridge.js +1 -8
  66. package/lib/site-react/lib/iframebridge/utils.js +1 -46
  67. package/lib/site-react/lib/iframebridge/withPreview.js +1 -33
  68. package/lib/site-react/lib/image/image.utils.js +1 -55
  69. package/lib/site-react/lib/jsonLd/JsonLd.js +1 -0
  70. package/lib/site-react/lib/persistedQueries/createPersistedQueryGraphQLFetch.js +1 -70
  71. package/lib/site-react/lib/persistedQueries/persistedQueryRoute.js +1 -100
  72. package/lib/site-react/lib/persistedQueries/webpackPersistedQueriesLoader.js +1 -51
  73. package/lib/site-react/lib/preview/BlockPreviewProvider.js +1 -32
  74. package/lib/site-react/lib/preview/PreviewContext.js +1 -9
  75. package/lib/site-react/lib/preview/usePreview.js +1 -41
  76. package/lib/site-react/lib/previewskeleton/PreviewSkeleton.js +4 -64
  77. package/lib/site-react/lib/previewskeleton/PreviewSkeleton.module.scss.js +1 -17
  78. package/lib/site-react/lib/sitePreview/iframebridge/SitePreviewIFrameMessage.js +1 -8
  79. package/lib/site-react/lib/sitePreview/iframebridge/sendSitePreviewIFrameMessage.js +1 -6
  80. package/lib/sitePreview/SitePreviewProvider.js +1 -40
  81. package/lib/sitePreview/appRouter/sitePreviewRoute.js +1 -27
  82. package/lib/sitePreview/pagesRouter/legacyPagesRouterSitePreviewApiHandler.js +1 -20
  83. package/lib/sitePreview/previewUtils.js +1 -54
  84. package/lib/style.css +1 -1
  85. package/package.json +15 -11
@@ -1,70 +1 @@
1
- function createPersistedQueryGraphQLFetch(fetch, url) {
2
- return async function(query, variables, init) {
3
- if (typeof query === "string") {
4
- throw new Error("at runtime only hashed queries are supported");
5
- }
6
- const hash = query.hash;
7
- let response;
8
- if ((init == null ? void 0 : init.method) === "GET") {
9
- const fetchUrl = new URL(url, window.location.origin);
10
- fetchUrl.searchParams.append("extensions.persistedQuery.sha256Hash", hash);
11
- fetchUrl.searchParams.append("variables", JSON.stringify(variables));
12
- response = await fetch(fetchUrl, {
13
- ...init,
14
- headers: {
15
- /**
16
- * It's recommended to add the `Apollo-Require-Preflight` header to GET requests, running on an Apollo Server 4.
17
- *
18
- * If this header is missing, Apollo Server 4 will return: This operation has been blocked as a potential Cross-Site Request Forgery (CSRF).
19
- *
20
- * see: https://www.apollographql.com/docs/graphos/routing/security/csrf#enable-csrf-prevention
21
- */
22
- "Apollo-Require-Preflight": "true",
23
- ...init.headers
24
- }
25
- });
26
- } else {
27
- response = await fetch(url, {
28
- method: "POST",
29
- ...init,
30
- headers: { "Content-Type": "application/json", ...init == null ? void 0 : init.headers },
31
- body: JSON.stringify({
32
- extensions: {
33
- persistedQuery: {
34
- version: 1,
35
- sha256Hash: hash
36
- }
37
- },
38
- variables
39
- })
40
- });
41
- }
42
- if (!response.ok) {
43
- let errorMessage = `Network response was not ok. Status: ${response.status}`;
44
- const body = await response.text();
45
- try {
46
- const json = JSON.parse(body);
47
- const { errors: errors2 } = json;
48
- if (errors2) {
49
- errorMessage += `
50
-
51
- GraphQL error(s):
52
- - ${errors2.map((error) => error.message).join("\n- ")}`;
53
- }
54
- } catch {
55
- errorMessage += `
56
- ${body}`;
57
- }
58
- throw new Error(errorMessage);
59
- }
60
- const { data, errors } = await response.json();
61
- if (errors) {
62
- throw new Error(`GraphQL error(s):
63
- - ${errors.map((error) => error.message).join("\n- ")}`);
64
- }
65
- return data;
66
- };
67
- }
68
- export {
69
- createPersistedQueryGraphQLFetch
70
- };
1
+ function e(e,r){return async function(s,a,t){if("string"==typeof s)throw new Error("at runtime only hashed queries are supported");const n=s.hash;let o;if("GET"===t?.method){const s=new URL(r,window.location.origin);s.searchParams.append("extensions.persistedQuery.sha256Hash",n),s.searchParams.append("variables",JSON.stringify(a)),o=await e(s,{...t,headers:{"Apollo-Require-Preflight":"true",...t.headers}})}else o=await e(r,{method:"POST",...t,headers:{"Content-Type":"application/json",...t?.headers},body:JSON.stringify({extensions:{persistedQuery:{version:1,sha256Hash:n}},variables:a})});if(!o.ok){let e=`Network response was not ok. Status: ${o.status}`;const r=await o.text();try{const s=JSON.parse(r),{errors:a}=s;a&&(e+=`\n\nGraphQL error(s):\n- ${a.map(e=>e.message).join("\n- ")}`)}catch{e+=`\n${r}`}throw new Error(e)}const{data:i,errors:h}=await o.json();if(h)throw new Error(`GraphQL error(s):\n- ${h.map(e=>e.message).join("\n- ")}`);return i}}export{e as createPersistedQueryGraphQLFetch};
@@ -1,100 +1 @@
1
- import { readFile } from "fs/promises";
2
- let queryMap;
3
- let fragmentsMap;
4
- async function loadPersistedQueries(path) {
5
- const file = await readFile(path, { encoding: "utf-8" });
6
- queryMap = JSON.parse(file.toString());
7
- fragmentsMap = {};
8
- for (const [, query] of Object.entries(queryMap)) {
9
- const match = query.match(/^\s*fragment\s+(\w+)\s+on\s+\w+/m);
10
- if (match) {
11
- const fragmentName = match[1];
12
- fragmentsMap[fragmentName] = query.replace(/\${.*?}/g, "");
13
- }
14
- }
15
- }
16
- function injectFragments(query) {
17
- query = query.replace(/\${.*?}/g, "");
18
- const injected = /* @__PURE__ */ new Set();
19
- for (const m of query.matchAll(/^\s*fragment\s+(\w+)\s+on\s+\w+/gm)) {
20
- injected.add(m[1]);
21
- }
22
- function inject(q) {
23
- const spreads = Array.from(q.matchAll(/\.\.\.(\w+)/g)).map((m) => m[1]);
24
- let result = q;
25
- for (const fragmentName of spreads) {
26
- if (!injected.has(fragmentName) && fragmentsMap[fragmentName]) {
27
- injected.add(fragmentName);
28
- result += `
29
- ${inject(fragmentsMap[fragmentName])}`;
30
- }
31
- }
32
- return result;
33
- }
34
- query = inject(query);
35
- return query;
36
- }
37
- async function persistedQueryRoute(req, {
38
- graphqlTarget,
39
- headers: headersInit,
40
- persistedQueriesPath = ".persisted-queries.json",
41
- cacheMaxAge
42
- }) {
43
- var _a, _b;
44
- if (!queryMap) {
45
- await loadPersistedQueries(persistedQueriesPath);
46
- }
47
- let hash;
48
- let variables;
49
- if (req.method === "POST") {
50
- const body = await req.json();
51
- hash = (_b = (_a = body.extensions) == null ? void 0 : _a.persistedQuery) == null ? void 0 : _b.sha256Hash;
52
- variables = body.variables;
53
- } else if (req.method === "GET") {
54
- const url = new URL(req.url);
55
- hash = url.searchParams.get("extensions.persistedQuery.sha256Hash");
56
- const variablesParam = url.searchParams.get("variables");
57
- variables = variablesParam ? JSON.parse(variablesParam) : void 0;
58
- } else {
59
- return Response.json({ error: "MethodNotAllowed" }, { status: 405 });
60
- }
61
- if (!hash) {
62
- return Response.json({ error: "OnlyPersistedQueriesAllowed" }, { status: 400 });
63
- }
64
- let query = queryMap[hash];
65
- if (!query) {
66
- return Response.json({ error: "PersistedQueryNotFound", hash }, { status: 400 });
67
- }
68
- query = injectFragments(query);
69
- if (req.method === "GET") {
70
- const apolloPreflight = req.headers.get("Apollo-Require-Preflight");
71
- if (apolloPreflight !== "true") {
72
- return Response.json({ error: "CSRFProtection", message: "Missing Apollo-Require-Preflight header" }, { status: 403 });
73
- }
74
- }
75
- const headers = new Headers(headersInit);
76
- headers.set("Content-Type", "application/json");
77
- for (const header of ["x-include-invisible-content", "x-preview-dam-urls"]) {
78
- const value = req.headers.get(header);
79
- if (value !== null) {
80
- headers.set(header, value);
81
- }
82
- }
83
- const upstreamRes = await fetch(graphqlTarget, {
84
- method: "POST",
85
- headers,
86
- body: JSON.stringify({ query, variables })
87
- });
88
- const responseHeaders = {
89
- "Content-Type": "application/json"
90
- };
91
- if (req.method === "GET" && upstreamRes.ok && cacheMaxAge !== void 0) {
92
- responseHeaders["Cache-Control"] = `public, max-age=${cacheMaxAge}`;
93
- }
94
- return new Response(upstreamRes.body, {
95
- headers: responseHeaders
96
- });
97
- }
98
- export {
99
- persistedQueryRoute
100
- };
1
+ import{readFile as e}from"fs/promises";let s,t;async function r(r,{graphqlTarget:o,headers:n,persistedQueriesPath:a=".persisted-queries.json",cacheMaxAge:i}){let c,l;if(s||await async function(r){const o=await e(r,{encoding:"utf-8"});s=JSON.parse(o.toString()),t={};for(const[,e]of Object.entries(s)){const s=e.match(/^\s*fragment\s+(\w+)\s+on\s+\w+/m);if(s){const r=s[1];t[r]=e.replace(/\${.*?}/g,"")}}}(a),"POST"===r.method){const e=await r.json();c=e.extensions?.persistedQuery?.sha256Hash,l=e.variables}else{if("GET"!==r.method)return Response.json({error:"MethodNotAllowed"},{status:405});{const e=new URL(r.url);c=e.searchParams.get("extensions.persistedQuery.sha256Hash");const s=e.searchParams.get("variables");l=s?JSON.parse(s):void 0}}if(!c)return Response.json({error:"OnlyPersistedQueriesAllowed"},{status:400});let d=s[c];if(!d)return Response.json({error:"PersistedQueryNotFound",hash:c},{status:400});if(d=function(e){e=e.replace(/\${.*?}/g,"");const s=/* @__PURE__ */new Set;for(const t of e.matchAll(/^\s*fragment\s+(\w+)\s+on\s+\w+/gm))s.add(t[1]);return e=function e(r){const o=Array.from(r.matchAll(/\.\.\.(\w+)/g)).map(e=>e[1]);let n=r;for(const a of o)!s.has(a)&&t[a]&&(s.add(a),n+=`\n${e(t[a])}`);return n}(e)}(d),"GET"===r.method&&"true"!==r.headers.get("Apollo-Require-Preflight"))return Response.json({error:"CSRFProtection",message:"Missing Apollo-Require-Preflight header"},{status:403});const u=new Headers(n);u.set("Content-Type","application/json");for(const e of["x-include-invisible-content","x-preview-dam-urls"]){const s=r.headers.get(e);null!==s&&u.set(e,s)}const h=await fetch(o,{method:"POST",headers:u,body:JSON.stringify({query:d,variables:l})}),p={"Content-Type":"application/json"};return"GET"===r.method&&h.ok&&void 0!==i&&(p["Cache-Control"]=`public, max-age=${i}`),new Response(h.body,{headers:p})}export{r as persistedQueryRoute};
@@ -1,51 +1 @@
1
- import { createHash } from "crypto";
2
- import * as fs from "fs";
3
- function loadHashMap(path) {
4
- if (fs.existsSync(path)) {
5
- return JSON.parse(fs.readFileSync(path, "utf-8"));
6
- }
7
- return {};
8
- }
9
- function saveHashMap(path, hashMap) {
10
- fs.writeFileSync(path, JSON.stringify(hashMap, null, 2));
11
- }
12
- function hashQuery(query) {
13
- return createHash("sha256").update(query).digest("hex");
14
- }
15
- const gqlTagRegex = /gql`([\s\S]*?)`/gm;
16
- const webpackPersistedQueriesLoader = function(source) {
17
- const options = this.getOptions() || {};
18
- const persistedQueriesPath = options.persistedQueriesPath || ".persisted-queries.json";
19
- const hashMap = loadHashMap(persistedQueriesPath);
20
- const replacements = [];
21
- let match;
22
- while ((match = gqlTagRegex.exec(source)) !== null) {
23
- const query = match[1];
24
- const hash = hashQuery(query);
25
- hashMap[hash] = query;
26
- const fragmentVariables = Array.from(query.matchAll(/\${.*?}/g) ?? []).map((m) => m[0]).join(" ");
27
- if (query.match(/^\s*fragment\s+(\w+)\s+on\s+\w+/)) {
28
- replacements.push({
29
- start: match.index,
30
- end: match.index + match[0].length,
31
- replacement: `{ fragment: true, fragmentVariables: \`${fragmentVariables}\` }`
32
- });
33
- } else {
34
- replacements.push({
35
- start: match.index,
36
- end: match.index + match[0].length,
37
- replacement: `{ hash: "${hash}", fragmentVariables: \`${fragmentVariables}\` }`
38
- });
39
- }
40
- }
41
- let modifiedSource = source;
42
- for (let i = replacements.length - 1; i >= 0; i--) {
43
- const { start, end, replacement } = replacements[i];
44
- modifiedSource = modifiedSource.slice(0, start) + replacement + modifiedSource.slice(end);
45
- }
46
- saveHashMap(persistedQueriesPath, hashMap);
47
- return modifiedSource;
48
- };
49
- export {
50
- webpackPersistedQueriesLoader as default
51
- };
1
+ import{createHash as e}from"crypto";import*as t from"fs";function n(t){return e("sha256").update(t).digest("hex")}const s=/gql`([\s\S]*?)`/gm,r=function(e){const r=(this.getOptions()||{}).persistedQueriesPath||".persisted-queries.json",a=(i=r,t.existsSync(i)?JSON.parse(t.readFileSync(i,"utf-8")):{});var i;const l=[];let o;for(;null!==(o=s.exec(e));){const e=o[1],t=n(e);a[t]=e;const s=Array.from(e.matchAll(/\${.*?}/g)??[]).map(e=>e[0]).join(" ");e.match(/^\s*fragment\s+(\w+)\s+on\s+\w+/)?l.push({start:o.index,end:o.index+o[0].length,replacement:`{ fragment: true, fragmentVariables: \`${s}\` }`}):l.push({start:o.index,end:o.index+o[0].length,replacement:`{ hash: "${t}", fragmentVariables: \`${s}\` }`})}let c=e;for(let t=l.length-1;t>=0;t--){const{start:e,end:n,replacement:s}=l[t];c=c.slice(0,e)+s+c.slice(n)}return m=r,f=a,t.writeFileSync(m,JSON.stringify(f,null,2)),c;var m,f};export{r as default};
@@ -1,33 +1,2 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { useIFrameBridge } from "../iframebridge/useIFrameBridge.js";
4
- import { PreviewContext } from "./PreviewContext.js";
5
- let currentContentScopeJwt = null;
6
- if (typeof window !== "undefined") {
7
- const originalFetch = window.fetch;
8
- window.fetch = async (input, init = {}) => {
9
- const headers = new Headers(init.headers);
10
- const url = new URL(typeof input === "string" ? input : input.toString(), window.location.origin);
11
- if (url.host === window.location.host && currentContentScopeJwt) {
12
- headers.set("X-Block-Preview", currentContentScopeJwt);
13
- }
14
- return originalFetch(input, { ...init, headers });
15
- };
16
- }
17
- const BlockPreviewProvider = ({ children }) => {
18
- const { contentScopeJwt } = useIFrameBridge();
19
- currentContentScopeJwt = contentScopeJwt;
20
- return /* @__PURE__ */ jsx(
21
- PreviewContext.Provider,
22
- {
23
- value: {
24
- previewType: "BlockPreview",
25
- showPreviewSkeletons: true
26
- },
27
- children
28
- }
29
- );
30
- };
31
- export {
32
- BlockPreviewProvider
33
- };
2
+ import{jsx as e}from"react/jsx-runtime";import{useIFrameBridge as r}from"../iframebridge/useIFrameBridge.js";import{PreviewContext as o}from"./PreviewContext.js";let t=null;if("undefined"!=typeof window){const e=window.fetch;window.fetch=async(r,o={})=>{const i=new Headers(o.headers);return new URL("string"==typeof r?r:r.toString(),window.location.origin).host===window.location.host&&t&&i.set("X-Block-Preview",t),e(r,{...o,headers:i})}}const i=({children:i})=>{const{contentScopeJwt:n}=r();return t=n,/* @__PURE__ */e(o.Provider,{value:{previewType:"BlockPreview",showPreviewSkeletons:!0},children:i})};export{i as BlockPreviewProvider};
@@ -1,10 +1,2 @@
1
1
  "use client";
2
- import { createContext } from "react";
3
- const defaultPreviewContextValue = {
4
- previewType: "NoPreview",
5
- showPreviewSkeletons: false
6
- };
7
- const PreviewContext = createContext(defaultPreviewContextValue);
8
- export {
9
- PreviewContext
10
- };
2
+ import{createContext as e}from"react";const o=e({previewType:"NoPreview",showPreviewSkeletons:!1});export{o as PreviewContext};
@@ -1,41 +1 @@
1
- import { useContext, useCallback } from "react";
2
- import { useIFrameBridge } from "../iframebridge/useIFrameBridge.js";
3
- import { PreviewContext } from "./PreviewContext.js";
4
- function usePreview() {
5
- const iFrameBridge = useIFrameBridge();
6
- const previewContext = useContext(PreviewContext);
7
- const isSelected = useCallback(
8
- (url, options) => {
9
- var _a;
10
- if (!iFrameBridge.selectedAdminRoute) {
11
- return false;
12
- }
13
- const exactMatch = (options == null ? void 0 : options.exactMatch) ?? true;
14
- if (exactMatch) {
15
- return url === iFrameBridge.selectedAdminRoute;
16
- } else {
17
- return (_a = iFrameBridge.selectedAdminRoute) == null ? void 0 : _a.startsWith(url);
18
- }
19
- },
20
- [iFrameBridge.selectedAdminRoute]
21
- );
22
- const isHovered = useCallback(
23
- (url, options) => {
24
- var _a;
25
- if (!iFrameBridge.hoveredAdminRoute) {
26
- return false;
27
- }
28
- const exactMatch = (options == null ? void 0 : options.exactMatch) ?? true;
29
- if (exactMatch) {
30
- return url === iFrameBridge.hoveredAdminRoute;
31
- } else {
32
- return (_a = iFrameBridge.hoveredAdminRoute) == null ? void 0 : _a.startsWith(url);
33
- }
34
- },
35
- [iFrameBridge.hoveredAdminRoute]
36
- );
37
- return { ...previewContext, isSelected, isHovered };
38
- }
39
- export {
40
- usePreview
41
- };
1
+ import{useCallback as e,useContext as t}from"react";import{useIFrameBridge as r}from"../iframebridge/useIFrameBridge.js";import{PreviewContext as o}from"./PreviewContext.js";function i(){const i=r();return{...t(o),isSelected:e((e,t)=>!!i.selectedAdminRoute&&(t?.exactMatch??1?e===i.selectedAdminRoute:i.selectedAdminRoute?.startsWith(e)),[i.selectedAdminRoute]),isHovered:e((e,t)=>!!i.hoveredAdminRoute&&(t?.exactMatch??1?e===i.hoveredAdminRoute:i.hoveredAdminRoute?.startsWith(e)),[i.hoveredAdminRoute])}}export{i as usePreview};
@@ -1,65 +1,5 @@
1
1
  "use client";
2
- import { jsx, Fragment, jsxs } from "react/jsx-runtime";
3
- import { usePreview } from "../preview/usePreview.js";
4
- import styles from "./PreviewSkeleton.module.scss.js";
5
- const PreviewSkeleton = ({
6
- children,
7
- customContainer,
8
- title,
9
- type = "bar",
10
- aspectRatio,
11
- height: passedHeight,
12
- hasContent,
13
- color = "#A8A7A8",
14
- backgroundColor = type === "media" ? "#efefef" : "#E0DDE0",
15
- fill = false
16
- }) => {
17
- const preview = usePreview();
18
- const validAspectRatio = getValidAspectRatio(aspectRatio);
19
- if (preview.showPreviewSkeletons && !hasContent) {
20
- if (customContainer) {
21
- return /* @__PURE__ */ jsx(Fragment, { children: customContainer });
22
- } else if (type === "bar") {
23
- return /* @__PURE__ */ jsx("div", { className: styles.barSkeleton, style: { "--background-color": backgroundColor, "--color": color }, children: title });
24
- } else if (type === "rows") {
25
- return /* @__PURE__ */ jsxs("div", { className: styles.rowsContainer, children: [
26
- /* @__PURE__ */ jsx("div", { className: styles.rowSkeleton, style: { "--width": "75%", "--background-color": backgroundColor, "--color": color }, children: title }),
27
- /* @__PURE__ */ jsx("div", { className: styles.rowSkeleton, style: { "--width": "100%", "--background-color": backgroundColor, "--color": color } }),
28
- /* @__PURE__ */ jsx("div", { className: styles.rowSkeleton, style: { "--width": "50%", "--background-color": backgroundColor, "--color": color } })
29
- ] });
30
- } else if (type === "media") {
31
- const height = fill ? "100%" : passedHeight ?? 300;
32
- return /* @__PURE__ */ jsx(
33
- "div",
34
- {
35
- className: styles.imageContainer,
36
- style: {
37
- "--background-color": backgroundColor,
38
- "--color": color,
39
- ...validAspectRatio === void 0 || fill ? { "--height": typeof height === "string" ? height : `${height}px` } : { "--aspect-ratio": validAspectRatio }
40
- },
41
- children: title
42
- }
43
- );
44
- }
45
- }
46
- if (!hasContent) {
47
- return null;
48
- }
49
- return /* @__PURE__ */ jsx(Fragment, { children });
50
- };
51
- const getValidAspectRatio = (aspectRatio) => {
52
- if (aspectRatio === "inherit") {
53
- return void 0;
54
- }
55
- if (aspectRatio === "auto") {
56
- return void 0;
57
- }
58
- if (typeof aspectRatio === "string") {
59
- return aspectRatio.replace("x", "/");
60
- }
61
- return aspectRatio;
62
- };
63
- export {
64
- PreviewSkeleton
65
- };
2
+ import{jsx as e,Fragment as r,jsxs as o}from"react/jsx-runtime";import{usePreview as t}from"../preview/usePreview.js";import i from"./PreviewSkeleton.module.scss.js";const l=({children:l,customContainer:s,title:c,type:a="bar",aspectRatio:d,height:u,hasContent:m,color:h="#A8A7A8",backgroundColor:f=("media"===a?"#efefef":"#E0DDE0"),fill:w=!1})=>{const k=t(),v=n(d);if(k.showPreviewSkeletons&&!m){if(s)return e(r,{children:s});if("bar"===a)return e("div",{className:i.barSkeleton,style:{"--background-color":f,"--color":h},children:c});if("rows"===a)return o("div",{className:i.rowsContainer,children:[
3
+ /* @__PURE__ */e("div",{className:i.rowSkeleton,style:{"--width":"75%","--background-color":f,"--color":h},children:c}),
4
+ /* @__PURE__ */e("div",{className:i.rowSkeleton,style:{"--width":"100%","--background-color":f,"--color":h}}),
5
+ /* @__PURE__ */e("div",{className:i.rowSkeleton,style:{"--width":"50%","--background-color":f,"--color":h}})]});if("media"===a){const r=w?"100%":u??300;return e("div",{className:i.imageContainer,style:{"--background-color":f,"--color":h,...void 0===v||w?{"--height":"string"==typeof r?r:`${r}px`}:{"--aspect-ratio":v}},children:c})}}return m?/* @__PURE__ */e(r,{children:l}):null},n=e=>{if("inherit"!==e&&"auto"!==e)return"string"==typeof e?e.replace("x","/"):e};export{l as PreviewSkeleton};
@@ -1,17 +1 @@
1
- const barSkeleton = "_barSkeleton_zomv4_1";
2
- const rowsContainer = "_rowsContainer_zomv4_9";
3
- const rowSkeleton = "_rowSkeleton_zomv4_14";
4
- const imageContainer = "_imageContainer_zomv4_22";
5
- const styles = {
6
- barSkeleton,
7
- rowsContainer,
8
- rowSkeleton,
9
- imageContainer
10
- };
11
- export {
12
- barSkeleton,
13
- styles as default,
14
- imageContainer,
15
- rowSkeleton,
16
- rowsContainer
17
- };
1
+ const o="_barSkeleton_zomv4_1",e="_rowsContainer_zomv4_9",n="_rowSkeleton_zomv4_14",a="_imageContainer_zomv4_22",r={barSkeleton:o,rowsContainer:e,rowSkeleton:n,imageContainer:a};export{o as barSkeleton,r as default,a as imageContainer,n as rowSkeleton,e as rowsContainer};
@@ -1,8 +1 @@
1
- var SitePreviewIFrameMessageType = /* @__PURE__ */ ((SitePreviewIFrameMessageType2) => {
2
- SitePreviewIFrameMessageType2["OpenLink"] = "OpenLink";
3
- SitePreviewIFrameMessageType2["SitePreviewLocation"] = "SitePreviewLocation";
4
- return SitePreviewIFrameMessageType2;
5
- })(SitePreviewIFrameMessageType || {});
6
- export {
7
- SitePreviewIFrameMessageType
8
- };
1
+ var e=/* @__PURE__ */(e=>(e.OpenLink="OpenLink",e.SitePreviewLocation="SitePreviewLocation",e))(e||{});export{e as SitePreviewIFrameMessageType};
@@ -1,6 +1 @@
1
- function sendSitePreviewIFrameMessage(message) {
2
- window.parent.postMessage(JSON.stringify(message), "*");
3
- }
4
- export {
5
- sendSitePreviewIFrameMessage
6
- };
1
+ function e(e){window.parent.postMessage(JSON.stringify(e),"*")}export{e as sendSitePreviewIFrameMessage};
@@ -1,41 +1,2 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { PreviewContext } from "../site-react/lib/preview/PreviewContext.js";
4
- import { sendSitePreviewIFrameMessage } from "../site-react/lib/sitePreview/iframebridge/sendSitePreviewIFrameMessage.js";
5
- import { SitePreviewIFrameMessageType } from "../site-react/lib/sitePreview/iframebridge/SitePreviewIFrameMessage.js";
6
- import { usePathname, useSearchParams } from "next/navigation";
7
- import { useEffect } from "react";
8
- const SitePreview = ({ children }) => {
9
- const pathname = usePathname();
10
- const searchParams = useSearchParams();
11
- useEffect(() => {
12
- function sendUpstreamMessage() {
13
- const message = {
14
- cometType: SitePreviewIFrameMessageType.SitePreviewLocation,
15
- data: { search: searchParams.toString(), pathname }
16
- };
17
- sendSitePreviewIFrameMessage(message);
18
- }
19
- sendUpstreamMessage();
20
- window.addEventListener("load", sendUpstreamMessage);
21
- return () => {
22
- window.removeEventListener("load", sendUpstreamMessage);
23
- };
24
- }, [pathname, searchParams]);
25
- return /* @__PURE__ */ jsx(
26
- PreviewContext.Provider,
27
- {
28
- value: {
29
- previewType: "SitePreview",
30
- showPreviewSkeletons: false
31
- },
32
- children
33
- }
34
- );
35
- };
36
- const SitePreviewProvider = ({ children }) => {
37
- return /* @__PURE__ */ jsx(SitePreview, { children });
38
- };
39
- export {
40
- SitePreviewProvider
41
- };
2
+ import{jsx as e}from"react/jsx-runtime";import{PreviewContext as r}from"../site-react/lib/preview/PreviewContext.js";import{sendSitePreviewIFrameMessage as i}from"../site-react/lib/sitePreview/iframebridge/sendSitePreviewIFrameMessage.js";import{SitePreviewIFrameMessageType as t}from"../site-react/lib/sitePreview/iframebridge/SitePreviewIFrameMessage.js";import{usePathname as o,useSearchParams as a}from"next/navigation";import{useEffect as s}from"react";const n=({children:n})=>{const m=o(),v=a();return s(()=>{function e(){const e={cometType:t.SitePreviewLocation,data:{search:v.toString(),pathname:m}};i(e)}return e(),window.addEventListener("load",e),()=>{window.removeEventListener("load",e)}},[m,v]),/* @__PURE__ */e(r.Provider,{value:{previewType:"SitePreview",showPreviewSkeletons:!1},children:n})},m=({children:r})=>/* @__PURE__ */e(n,{children:r});export{m as SitePreviewProvider};
@@ -1,27 +1 @@
1
- import "server-only";
2
- import { redirect } from "next/navigation";
3
- import { NextResponse } from "next/server";
4
- import { verifyJwt, setSitePreviewParams } from "../previewUtils.js";
5
- async function sitePreviewRoute(request) {
6
- const params = request.nextUrl.searchParams;
7
- const jwt = params.get("jwt");
8
- if (!jwt) {
9
- return NextResponse.json({ error: "JWT-Parameter is missing." }, { status: 400 });
10
- }
11
- const data = await verifyJwt(jwt);
12
- if (!data) {
13
- return NextResponse.json({ error: "JWT-validation failed." }, { status: 400 });
14
- }
15
- await setSitePreviewParams({
16
- scope: data.scope,
17
- previewData: data.previewData,
18
- userId: data.userId
19
- });
20
- if (!data.path.startsWith("/") || data.path.startsWith("//")) {
21
- return NextResponse.json({ error: `Redirect to ${data.path} disallowed: only relative paths are valid.` }, { status: 400 });
22
- }
23
- return redirect(data.path);
24
- }
25
- export {
26
- sitePreviewRoute
27
- };
1
+ import"server-only";import{redirect as t}from"next/navigation";import{NextResponse as r}from"next/server";import{verifyJwt as e,setSitePreviewParams as a}from"../previewUtils.js";async function s(s){const i=s.nextUrl.searchParams.get("jwt");if(!i)return r.json({error:"JWT-Parameter is missing."},{status:400});const o=await e(i);return o?(await a({scope:o.scope,previewData:o.previewData,userId:o.userId}),!o.path.startsWith("/")||o.path.startsWith("//")?r.json({error:`Redirect to ${o.path} disallowed: only relative paths are valid.`},{status:400}):t(o.path)):r.json({error:"JWT-validation failed."},{status:400})}export{s as sitePreviewRoute};
@@ -1,20 +1 @@
1
- import { verifyJwt } from "../previewUtils.js";
2
- async function legacyPagesRouterSitePreviewApiHandler(req, res) {
3
- const params = req.query;
4
- const jwt = params.jwt;
5
- if (typeof jwt !== "string") {
6
- return res.status(400).json({ error: "JWT-Parameter is missing." });
7
- }
8
- const data = await verifyJwt(jwt);
9
- if (!data) {
10
- return res.status(400).json({ error: "JWT-validation failed." });
11
- }
12
- if (!data.path.startsWith("/") || data.path.startsWith("//")) {
13
- return res.status(400).json({ error: `Redirect to ${data.path} disallowed: only relative paths are valid.` });
14
- }
15
- res.setPreviewData(data);
16
- res.redirect(data.path);
17
- }
18
- export {
19
- legacyPagesRouterSitePreviewApiHandler
20
- };
1
+ import{verifyJwt as t}from"../previewUtils.js";async function r(r,s){const a=r.query.jwt;if("string"!=typeof a)return s.status(400).json({error:"JWT-Parameter is missing."});const e=await t(a);return e?!e.path.startsWith("/")||e.path.startsWith("//")?s.status(400).json({error:`Redirect to ${e.path} disallowed: only relative paths are valid.`}):(s.setPreviewData(e),void s.redirect(e.path)):s.status(400).json({error:"JWT-validation failed."})}export{r as legacyPagesRouterSitePreviewApiHandler};
@@ -1,54 +1 @@
1
- import { jwtVerify, errors, SignJWT } from "jose";
2
- import { headers, draftMode, cookies } from "next/headers";
3
- async function verifyJwt(jwt) {
4
- if (!process.env.SITE_PREVIEW_SECRET) {
5
- throw new Error("SITE_PREVIEW_SECRET environment variable is required.");
6
- }
7
- try {
8
- const data = await jwtVerify(jwt, new TextEncoder().encode(process.env.SITE_PREVIEW_SECRET));
9
- return data.payload;
10
- } catch (e) {
11
- if (e instanceof errors.JOSEError) {
12
- return null;
13
- }
14
- throw e;
15
- }
16
- }
17
- async function setSitePreviewParams(payload) {
18
- const jwt = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setExpirationTime("1 day").sign(new TextEncoder().encode(process.env.SITE_PREVIEW_SECRET));
19
- (await cookies()).set("__comet_site_preview", jwt, { httpOnly: true, sameSite: "lax" });
20
- (await draftMode()).enable();
21
- }
22
- async function previewParams(options = { skipDraftModeCheck: false }) {
23
- if (options.skipDraftModeCheck) {
24
- const headers$1 = await headers();
25
- if (headers$1.has("x-block-preview")) {
26
- return verifyJwt(headers$1.get("x-block-preview") || "");
27
- }
28
- }
29
- if (!options.skipDraftModeCheck) {
30
- if (!(await draftMode()).isEnabled) {
31
- return null;
32
- }
33
- }
34
- const cookie = (await cookies()).get("__comet_site_preview");
35
- if (cookie) {
36
- return verifyJwt(cookie.value);
37
- }
38
- return null;
39
- }
40
- async function legacyPagesRouterPreviewParams(req) {
41
- if (req.headers["x-block-preview"]) {
42
- return verifyJwt(req.headers["x-block-preview"].toString());
43
- }
44
- if (req.previewData) {
45
- return req.previewData;
46
- }
47
- return null;
48
- }
49
- export {
50
- legacyPagesRouterPreviewParams,
51
- previewParams,
52
- setSitePreviewParams,
53
- verifyJwt
54
- };
1
+ import{jwtVerify as e,errors as t,SignJWT as r}from"jose";import{headers as n,draftMode as a,cookies as i}from"next/headers";async function o(r){if(!process.env.SITE_PREVIEW_SECRET)throw new Error("SITE_PREVIEW_SECRET environment variable is required.");try{return(await e(r,(new TextEncoder).encode(process.env.SITE_PREVIEW_SECRET))).payload}catch(n){if(n instanceof t.JOSEError)return null;throw n}}async function c(e){const t=await new r(e).setProtectedHeader({alg:"HS256"}).setExpirationTime("1 day").sign((new TextEncoder).encode(process.env.SITE_PREVIEW_SECRET));(await i()).set("__comet_site_preview",t,{httpOnly:!0,sameSite:"lax"}),(await a()).enable()}async function s(e={skipDraftModeCheck:!1}){if(e.skipDraftModeCheck){const e=await n();if(e.has("x-block-preview"))return o(e.get("x-block-preview")||"")}if(!e.skipDraftModeCheck&&!(await a()).isEnabled)return null;const t=(await i()).get("__comet_site_preview");return t?o(t.value):null}async function E(e){return e.headers["x-block-preview"]?o(e.headers["x-block-preview"].toString()):e.previewData?e.previewData:null}export{E as legacyPagesRouterPreviewParams,s as previewParams,c as setSitePreviewParams,o as verifyJwt};
package/lib/style.css CHANGED
@@ -1 +1 @@
1
- ._previewElementContainer_nwhl6_1{display:contents}._childrenWrapper_qguhx_1{position:relative;z-index:1}._root_v4248_1{position:absolute;z-index:2;top:0;left:0;right:0;min-height:100vh;height:var(--height)}._root_1ya0k_1{position:absolute;cursor:pointer;outline:1px solid transparent;outline-offset:-1px}._root_1ya0k_1:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;opacity:.25}._root_1ya0k_1:hover{outline-color:#29b6f6;outline-style:solid}._root_1ya0k_1:hover:after{background-color:#29b6f6}._isHoveredInBlockList_1ya0k_24{outline-color:#29b6f6;outline-style:solid}._isHoveredInBlockList_1ya0k_24:after{background-color:#29b6f6}._showBlockOutlines_1ya0k_32:not(:hover){outline-color:#d9d9d9;outline-style:dashed}._blockIsSelected_1ya0k_37{outline-color:#29b6f6}._blockIsSelected_1ya0k_37 ._label_1ya0k_40{display:inline-block}._label_1ya0k_40{position:absolute;padding:2px;background-color:#57b0eb;line-height:16px;color:#fff;top:1px;right:1px;font-size:12px;display:none}._barSkeleton_zomv4_1{min-height:20px;margin-bottom:5px;margin-right:5px;background-color:var(--background-color);color:var(--color)}._rowsContainer_zomv4_9{width:100%;min-width:300px}._rowSkeleton_zomv4_14{margin-bottom:10px;min-height:20px;width:var(--width);background-color:var(--background-color);color:var(--color)}._imageContainer_zomv4_22{width:100%;aspect-ratio:var(--aspect-ratio);height:var(--height);background-color:var(--background-color);color:var(--color)}._root_1jlho_1{position:relative;height:100%}._video_1jlho_6{width:100%;object-fit:cover;aspect-ratio:var(--aspect-ratio)}._fill_1jlho_12{height:100%}._button_4r8gs_1{position:absolute;left:8px;bottom:8px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;background-color:#000000b3;z-index:10}._button_4r8gs_1:hover,._button_4r8gs_1:active{background-color:#9e9e9e}._button_4r8gs_1:focus{border-radius:6px}._animatedPlayPause_4r8gs_23{box-sizing:border-box;height:12px;border-color:transparent transparent transparent #fff;cursor:pointer;border-style:solid;border-width:6px 0 6px 9px;transition:border-width .1s ease,border-style .1s ease}._animatedPlayPausePaused_4r8gs_32{border-style:double;border-width:0 0 0 9px}._root_1k5a5_1{position:relative;width:100%}._fill_1k5a5_6{height:100%}._iconWrapper_1k5a5_10{position:absolute;top:0;right:0;left:0;bottom:0;display:flex;align-items:center;justify-content:center;background-color:#000;opacity:.5;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;padding:0;cursor:pointer}._iconWrapper_1k5a5_10:focus ._playIcon_1k5a5_26{outline:4px solid white;outline-offset:10px;border-radius:2px}._playIcon_1k5a5_26{width:64px;height:64px;box-sizing:border-box;border-style:solid;border-width:32px 0 32px 64px;border-color:transparent transparent transparent white}._videoContainer_1flq1_1{overflow:hidden;position:relative;aspect-ratio:var(--aspect-ratio)}._fill_1flq1_7{width:100%;height:100%}._vimeoContainer_1flq1_12{position:absolute;border:0;top:0;left:0;width:100%;height:100%}._videoContainer_bhmhr_1{overflow:hidden;position:relative;aspect-ratio:var(--aspect-ratio)}._fill_bhmhr_7{width:100%;height:100%}._youtubeContainer_bhmhr_12{position:absolute;border:0;top:0;left:0;width:100%;height:100%}._imageContainer_ecpdr_1{position:relative;width:100%;aspect-ratio:var(--aspect-ratio)}
1
+ ._previewElementContainer_nwhl6_1{display:contents}._childrenWrapper_qguhx_1{position:relative;z-index:1}._root_v4248_1{position:absolute;z-index:2;top:0;left:0;right:0;min-height:100vh;height:var(--height)}._root_1ya0k_1{position:absolute;cursor:pointer;outline:1px solid transparent;outline-offset:-1px}._root_1ya0k_1:after{content:"";position:absolute;inset:0;opacity:.25}._root_1ya0k_1:hover{outline-color:#29b6f6;outline-style:solid}._root_1ya0k_1:hover:after{background-color:#29b6f6}._isHoveredInBlockList_1ya0k_24{outline-color:#29b6f6;outline-style:solid}._isHoveredInBlockList_1ya0k_24:after{background-color:#29b6f6}._showBlockOutlines_1ya0k_32:not(:hover){outline-color:#d9d9d9;outline-style:dashed}._blockIsSelected_1ya0k_37{outline-color:#29b6f6}._blockIsSelected_1ya0k_37 ._label_1ya0k_40{display:inline-block}._label_1ya0k_40{position:absolute;padding:2px;background-color:#57b0eb;line-height:16px;color:#fff;top:1px;right:1px;font-size:12px;display:none}._barSkeleton_zomv4_1{min-height:20px;margin-bottom:5px;margin-right:5px;background-color:var(--background-color);color:var(--color)}._rowsContainer_zomv4_9{width:100%;min-width:300px}._rowSkeleton_zomv4_14{margin-bottom:10px;min-height:20px;width:var(--width);background-color:var(--background-color);color:var(--color)}._imageContainer_zomv4_22{width:100%;aspect-ratio:var(--aspect-ratio);height:var(--height);background-color:var(--background-color);color:var(--color)}._root_1jlho_1{position:relative;height:100%}._video_1jlho_6{width:100%;object-fit:cover;aspect-ratio:var(--aspect-ratio)}._fill_1jlho_12{height:100%}._button_4r8gs_1{position:absolute;left:8px;bottom:8px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;background-color:#000000b3;z-index:10}._button_4r8gs_1:hover,._button_4r8gs_1:active{background-color:#9e9e9e}._button_4r8gs_1:focus{border-radius:6px}._animatedPlayPause_4r8gs_23{box-sizing:border-box;height:12px;border-color:transparent transparent transparent #fff;cursor:pointer;border-style:solid;border-width:6px 0 6px 9px;transition:border-width .1s ease,border-style .1s ease}._animatedPlayPausePaused_4r8gs_32{border-style:double;border-width:0 0 0 9px}._root_1k5a5_1{position:relative;width:100%}._fill_1k5a5_6{height:100%}._iconWrapper_1k5a5_10{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background-color:#000;opacity:.5;appearance:none;border:none;padding:0;cursor:pointer}._iconWrapper_1k5a5_10:focus ._playIcon_1k5a5_26{outline:4px solid white;outline-offset:10px;border-radius:2px}._playIcon_1k5a5_26{width:64px;height:64px;box-sizing:border-box;border-style:solid;border-width:32px 0 32px 64px;border-color:transparent transparent transparent white}._videoContainer_1flq1_1{overflow:hidden;position:relative;aspect-ratio:var(--aspect-ratio)}._fill_1flq1_7{width:100%;height:100%}._vimeoContainer_1flq1_12{position:absolute;border:0;top:0;left:0;width:100%;height:100%}._videoContainer_bhmhr_1{overflow:hidden;position:relative;aspect-ratio:var(--aspect-ratio)}._fill_bhmhr_7{width:100%;height:100%}._youtubeContainer_bhmhr_12{position:absolute;border:0;top:0;left:0;width:100%;height:100%}._imageContainer_ecpdr_1{position:relative;width:100%;aspect-ratio:var(--aspect-ratio)}