Skip to content

Commit d2e48bf

Browse files
committed
fix(site,sanity): add history adapter to enableVisualEditing for Presentation iframe navigation
1 parent 7f31c19 commit d2e48bf

3 files changed

Lines changed: 255 additions & 23 deletions

File tree

‎apps/sanity/sanity.config.ts‎

Lines changed: 182 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,24 @@ const localPreviewOrigin = "http://localhost:4321";
8989

9090
// ── Shared helpers ───────────────────────────────────────────────────
9191
function resolveHref(type: string, slug?: string): string | undefined {
92+
if (!slug) {
93+
return undefined;
94+
}
9295
switch (type) {
9396
case "post":
94-
return slug ? `/post/${slug}` : undefined;
97+
return `/post/${slug}`;
9598
case "podcast":
96-
return slug ? `/podcast/${slug}` : undefined;
99+
return `/podcast/${slug}`;
100+
case "course":
101+
return `/course/${slug}`;
102+
case "author":
103+
return `/author/${slug}`;
104+
case "guest":
105+
return `/guest/${slug}`;
106+
case "sponsor":
107+
return `/sponsor/${slug}`;
97108
case "page":
98-
return slug ? `/${slug}` : undefined;
109+
return `/${slug}`;
99110
default:
100111
return undefined;
101112
}
@@ -106,6 +117,21 @@ const homeLocation = {
106117
href: "/",
107118
} satisfies DocumentLocation;
108119

120+
const blogLocation = {
121+
title: "Blog",
122+
href: "/blog",
123+
} satisfies DocumentLocation;
124+
125+
const podcastsLocation = {
126+
title: "Podcasts",
127+
href: "/podcasts",
128+
} satisfies DocumentLocation;
129+
130+
const coursesLocation = {
131+
title: "Courses",
132+
href: "/courses",
133+
} satisfies DocumentLocation;
134+
109135
// ── Shared schema types ──────────────────────────────────────────────
110136
const schemaTypes = [
111137
// Portable text block types (table)
@@ -148,6 +174,26 @@ function buildPlugins(previewUrl: string): PluginOptions[] {
148174
route: "/podcast/:slug",
149175
filter: `_type == "podcast" && slug.current == $slug`,
150176
},
177+
{
178+
route: "/course/:slug",
179+
filter: `_type == "course" && slug.current == $slug`,
180+
},
181+
{
182+
route: "/author/:slug",
183+
filter: `_type == "author" && slug.current == $slug`,
184+
},
185+
{
186+
route: "/guest/:slug",
187+
filter: `_type == "guest" && slug.current == $slug`,
188+
},
189+
{
190+
route: "/sponsor/:slug",
191+
filter: `_type == "sponsor" && slug.current == $slug`,
192+
},
193+
{
194+
route: "/:slug",
195+
filter: `_type == "page" && slug.current == $slug`,
196+
},
151197
]),
152198
locations: {
153199
settings: defineLocations({
@@ -160,32 +206,145 @@ function buildPlugins(previewUrl: string): PluginOptions[] {
160206
title: "title",
161207
slug: "slug.current",
162208
},
163-
resolve: (doc) => ({
164-
locations: [
165-
{
166-
title: doc?.title || "Untitled",
167-
// biome-ignore lint/style/noNonNullAssertion: resolveHref returns string for known types
168-
href: resolveHref("post", doc?.slug)!,
169-
},
170-
homeLocation,
171-
],
172-
}),
209+
resolve: (doc) => {
210+
const href = resolveHref("post", doc?.slug);
211+
return {
212+
locations: [
213+
...(href
214+
? [
215+
{
216+
title: doc?.title || "Untitled",
217+
href,
218+
},
219+
]
220+
: []),
221+
blogLocation,
222+
homeLocation,
223+
],
224+
};
225+
},
173226
}),
174227
podcast: defineLocations({
175228
select: {
176229
title: "title",
177230
slug: "slug.current",
178231
},
179-
resolve: (doc) => ({
180-
locations: [
181-
{
182-
title: doc?.title || "Untitled",
183-
// biome-ignore lint/style/noNonNullAssertion: resolveHref returns string for known types
184-
href: resolveHref("podcast", doc?.slug)!,
185-
},
186-
homeLocation,
187-
],
188-
}),
232+
resolve: (doc) => {
233+
const href = resolveHref("podcast", doc?.slug);
234+
return {
235+
locations: [
236+
...(href
237+
? [
238+
{
239+
title: doc?.title || "Untitled",
240+
href,
241+
},
242+
]
243+
: []),
244+
podcastsLocation,
245+
homeLocation,
246+
],
247+
};
248+
},
249+
}),
250+
course: defineLocations({
251+
select: {
252+
title: "title",
253+
slug: "slug.current",
254+
},
255+
resolve: (doc) => {
256+
const href = resolveHref("course", doc?.slug);
257+
return {
258+
locations: [
259+
...(href
260+
? [
261+
{
262+
title: doc?.title || "Untitled",
263+
href,
264+
},
265+
]
266+
: []),
267+
coursesLocation,
268+
homeLocation,
269+
],
270+
};
271+
},
272+
}),
273+
page: defineLocations({
274+
select: {
275+
title: "title",
276+
slug: "slug.current",
277+
},
278+
resolve: (doc) => {
279+
const href = resolveHref("page", doc?.slug);
280+
return {
281+
locations: href
282+
? [
283+
{
284+
title: doc?.title || "Untitled",
285+
href,
286+
},
287+
]
288+
: [],
289+
};
290+
},
291+
}),
292+
author: defineLocations({
293+
select: {
294+
title: "title",
295+
slug: "slug.current",
296+
},
297+
resolve: (doc) => {
298+
const href = resolveHref("author", doc?.slug);
299+
return {
300+
locations: href
301+
? [
302+
{
303+
title: doc?.title || "Untitled",
304+
href,
305+
},
306+
]
307+
: [],
308+
};
309+
},
310+
}),
311+
guest: defineLocations({
312+
select: {
313+
title: "title",
314+
slug: "slug.current",
315+
},
316+
resolve: (doc) => {
317+
const href = resolveHref("guest", doc?.slug);
318+
return {
319+
locations: href
320+
? [
321+
{
322+
title: doc?.title || "Untitled",
323+
href,
324+
},
325+
]
326+
: [],
327+
};
328+
},
329+
}),
330+
sponsor: defineLocations({
331+
select: {
332+
title: "title",
333+
slug: "slug.current",
334+
},
335+
resolve: (doc) => {
336+
const href = resolveHref("sponsor", doc?.slug);
337+
return {
338+
locations: href
339+
? [
340+
{
341+
title: doc?.title || "Untitled",
342+
href,
343+
},
344+
]
345+
: [],
346+
};
347+
},
189348
}),
190349
},
191350
},

‎apps/site/src/layouts/BaseLayout.astro‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,13 +320,63 @@ const graph = buildGraph([
320320
</div>
321321
{preview && <DraftModeToggle />}
322322
<script>
323+
import "@sanity/visual-editing-standalone/styles.css";
323324
import { enableVisualEditing } from "@sanity/visual-editing-standalone";
324325

325326
if (
326327
window.self !== window.top ||
327328
document.querySelector("[data-sanity-preview]")
328329
) {
329330
enableVisualEditing({
331+
history: {
332+
subscribe: (navigate) => {
333+
const onPopState = () => {
334+
navigate({ type: "pop", url: window.location.href });
335+
};
336+
window.addEventListener("popstate", onPopState);
337+
return () => window.removeEventListener("popstate", onPopState);
338+
},
339+
update: (update) => {
340+
if (update.type === "push" || update.type === "replace") {
341+
const nextUrl = new URL(update.url, window.location.origin);
342+
const currentPerspective = new URLSearchParams(
343+
window.location.search,
344+
).get("sanity-preview-perspective");
345+
if (
346+
currentPerspective &&
347+
!nextUrl.searchParams.has("sanity-preview-perspective")
348+
) {
349+
nextUrl.searchParams.set(
350+
"sanity-preview-perspective",
351+
currentPerspective,
352+
);
353+
}
354+
if (update.type === "replace") {
355+
window.location.replace(nextUrl.toString());
356+
} else {
357+
window.location.assign(nextUrl.toString());
358+
}
359+
} else if (update.type === "pop") {
360+
window.history.back();
361+
}
362+
},
363+
},
364+
onPerspectiveChange: (perspective) => {
365+
const serialized = Array.isArray(perspective)
366+
? perspective.join(",")
367+
: String(perspective);
368+
const nextUrl = new URL(window.location.href);
369+
if (
370+
nextUrl.searchParams.get("sanity-preview-perspective") !==
371+
serialized
372+
) {
373+
nextUrl.searchParams.set(
374+
"sanity-preview-perspective",
375+
serialized,
376+
);
377+
window.location.replace(nextUrl.toString());
378+
}
379+
},
330380
refresh: (payload) => {
331381
if (payload.source === "mutation" || payload.source === "manual") {
332382
window.location.reload();

‎apps/site/tests/tier1-features.test.mjs‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,29 @@ describe("Tier 1 - Feature Coverage", () => {
10061006
});
10071007
assert.equal(resDisablePostJson.headers.get("Location"), "/");
10081008
});
1009+
1010+
it("6.9 BaseLayout configures @sanity/visual-editing-standalone with history adapter and onPerspectiveChange", () => {
1011+
const baseLayoutSrc = fs.readFileSync(
1012+
path.resolve(SRC_DIR, "layouts/BaseLayout.astro"),
1013+
"utf-8",
1014+
);
1015+
assert.ok(
1016+
baseLayoutSrc.includes("@sanity/visual-editing-standalone/styles.css"),
1017+
"BaseLayout must import visual editing styles",
1018+
);
1019+
assert.ok(
1020+
baseLayoutSrc.includes("history:"),
1021+
"enableVisualEditing must configure history adapter for Presentation iframe navigation",
1022+
);
1023+
assert.ok(
1024+
baseLayoutSrc.includes("window.location.replace(nextUrl.toString())"),
1025+
"history.update must perform full MPA navigation on replace updates from Sanity Presentation",
1026+
);
1027+
assert.ok(
1028+
baseLayoutSrc.includes("onPerspectiveChange:"),
1029+
"enableVisualEditing must handle perspective changes from Sanity Presentation",
1030+
);
1031+
});
10091032
});
10101033

10111034
// ── 7. Agent Readiness & Autonomous Discovery Endpoints ───────────────

0 commit comments

Comments
 (0)