Skip to content

docs: presentation mode #1085

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ dist/
storybook-static/
next-env.d.ts

packages/icons-react/icons.tsx

# editors
.idea/
Expand All @@ -17,5 +18,4 @@ next-env.d.ts

# Secrets
.FIGMA_TOKEN
.vercel
packages/icons-react/icons.tsx
.env.local
2 changes: 2 additions & 0 deletions apps/docs/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Creata a viewer token at sanity.io/manage
SANITY_VIEWER_TOKEN=
4 changes: 3 additions & 1 deletion apps/docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.output
.vinxi
public/resources/icons/
docgen.ts
docgen.ts

.env
12 changes: 12 additions & 0 deletions apps/docs/app/lib/preview-middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createMiddleware } from '@tanstack/start';
import { getCookie } from 'vinxi/http';

export const previewMiddleware = createMiddleware().server(({ next }) => {
const isPreview = getCookie('__sanity_preview') === 'true';
console.log('middleware', { isPreview });
return next({
context: {
previewMode: isPreview,
},
});
});
22 changes: 22 additions & 0 deletions apps/docs/app/lib/sanity.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { QueryParams } from '@sanity/client';
import { createServerFn } from '@tanstack/start';
import { previewMiddleware } from './preview-middleware';
import { sanityFetch as _sanityFetch, client } from './sanity';

export const sanityFetch = createServerFn({ method: 'GET' })
.middleware([previewMiddleware])
.validator((data: { query: string; params: QueryParams }) => data)
.handler(async ({ data, context }) => {
const { query, params } = data;

if (context.previewMode) {
const previewClient = client.withConfig({
perspective: 'previewDrafts',
token: process.env.SANITY_VIEWER_TOKEN, // Needed for accessing previewDrafts perspective
useCdn: false, // the previewDrafts perspective requires this to be `false
});
return _sanityFetch({ query, params, client: previewClient });
}

return _sanityFetch({ query, params });
});
5 changes: 4 additions & 1 deletion apps/docs/app/lib/sanity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@ export const client = createClient({
dataset: 'grunnmuren',
apiVersion: '2024-09-18',
useCdn: true,
perspective: 'published',
});

export async function sanityFetch<const QueryString extends string>({
query,
params = {},
client: _client = client,
}: {
query: QueryString;
params?: QueryParams;
client?: typeof client;
}) {
// Not sure what's happening here, but I need to set filterReponse to false to get the data as an array?
const { result } = await client.fetch(query, params, {
const { result } = await _client.fetch(query, params, {
filterResponse: false,
});

Expand Down
57 changes: 57 additions & 0 deletions apps/docs/app/lib/visual-editing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
type HistoryAdapterNavigate,
enableVisualEditing,
} from '@sanity/visual-editing';
import { useNavigate, useRouter } from '@tanstack/react-router';
import { useEffect, useState } from 'react';

export function VisualEditing() {
const router = useRouter();
const [navigate, setNavigate] = useState<
HistoryAdapterNavigate | undefined
>();

useEffect(() => {
const disable = enableVisualEditing({
history: {
subscribe: (_navigate) => {
console.log('subscribe');
setNavigate(() => {
_navigate({ type: 'replace', url: router.state.location.href });
return _navigate;
});
return () => setNavigate(undefined);
},
update: (update) => {
console.log('update', update);
switch (update.type) {
case 'push':
router.history.push(update.url);
break;
case 'replace':
router.history.replace(update.url);
break;
case 'pop':
router.history.back();
break;
}
},
},
});

return disable;
}, [router]);

useEffect(() => {
if (navigate) {
const unsubscribe = router.subscribe('onResolved', (evt) => {
console.log(evt);
navigate({ type: 'push', url: evt.toLocation.href });
});

return unsubscribe;
}
}, [router, navigate]);

return null;
}
30 changes: 29 additions & 1 deletion apps/docs/app/routes/_docs.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { previewMiddleware } from '@/lib/preview-middleware';
import { sanityFetch } from '@/lib/sanity';
import { VisualEditing } from '@/lib/visual-editing';
import appCss from '@/styles/app.css?url';
import { DisablePreviewMode } from '@/ui/disable-preview-mode';
import { Footer } from '@/ui/footer';
import { MainNav } from '@/ui/main-nav';
import { GrunnmurenProvider } from '@obosbbl/grunnmuren-react';
Expand All @@ -11,13 +14,20 @@ import {
createFileRoute,
useRouter,
} from '@tanstack/react-router';
import { createServerFn } from '@tanstack/start';
import { defineQuery } from 'groq';

const COMPONENTS_NAVIGATION_QUERY = defineQuery(
// make sure the slug is always a string so we don't have add fallback value in code just to make TypeScript happy
`*[_type == "component"]{ _id, name, 'slug': coalesce(slug.current, '')} | order(name asc)`,
);

const checkIsPreview = createServerFn({ method: 'GET' })
.middleware([previewMiddleware])
.handler(({ context }) => {
return context.previewMode;
});

// This is the shared layout for all the Grunnmuren docs pages that are "public", ie not the Sanity studio
export const Route = createFileRoute('/_docs')({
component: RootLayout,
Expand All @@ -29,11 +39,23 @@ export const Route = createFileRoute('/_docs')({
},
],
}),
loader: () => sanityFetch({ query: COMPONENTS_NAVIGATION_QUERY }),
beforeLoad: async () => {
const isPreview = await checkIsPreview();
return { isPreview };
},
loader: async ({ context }) => {
return {
componentsNavItems: (
await sanityFetch({ query: COMPONENTS_NAVIGATION_QUERY })
).data,
isPreview: context.isPreview,
};
},
});

function RootLayout() {
const router = useRouter();
const { isPreview } = Route.useLoaderData();

return (
<>
Expand All @@ -45,6 +67,12 @@ function RootLayout() {
navigate={(to, options) => router.navigate({ to, ...options })}
useHref={(to) => router.buildLocation({ to }).href}
>
{isPreview && (
<>
<VisualEditing />
<DisablePreviewMode />
</>
)}
<div className="grid min-h-screen lg:flex">
<div className="flex grow flex-col px-6">
<main className="grow">
Expand Down
11 changes: 7 additions & 4 deletions apps/docs/app/routes/_docs/komponenter/$slug.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as badgeExamples from '@/examples/badge';
import * as buttonExamples from '@/examples/button';
import { sanityFetch } from '@/lib/sanity';
import { sanityFetch } from '@/lib/sanity.server';
import { Content } from '@/ui/content';
import { PropsTable } from '@/ui/props-table';
import { createFileRoute, notFound } from '@tanstack/react-router';
Expand All @@ -13,10 +13,13 @@ const COMPONENT_QUERY = defineQuery(

export const Route = createFileRoute('/_docs/komponenter/$slug')({
component: Page,
loader: async ({ params }) => {
loader: async ({ params, context }) => {
console.log('context in component route', context);
const res = await sanityFetch({
query: COMPONENT_QUERY,
params: { slug: params.slug },
data: {
query: COMPONENT_QUERY,
params: { slug: params.slug },
},
});

if (res.data == null) {
Expand Down
14 changes: 14 additions & 0 deletions apps/docs/app/routes/api/preview-mode/disable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createAPIFileRoute } from '@tanstack/start/api';
import { deleteCookie, sendRedirect } from 'vinxi/http';

export const APIRoute = createAPIFileRoute('/api/preview-mode/disable')({
GET: () => {
deleteCookie('__sanity_preview', {
path: '/',
secure: import.meta.env.PROD,
httpOnly: true,
sameSite: 'strict',
});
sendRedirect('/');
},
});
37 changes: 37 additions & 0 deletions apps/docs/app/routes/api/preview-mode/enable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { randomBytes } from 'node:crypto';
import { client } from '@/lib/sanity';
import { validatePreviewUrl } from '@sanity/preview-url-secret';
import { createAPIFileRoute } from '@tanstack/start/api';
import { SanityClient } from 'sanity';
import { sendRedirect, setCookie } from 'vinxi/http';

export const APIRoute = createAPIFileRoute('/api/preview-mode/enable')({
GET: async ({ request }) => {
if (!process.env.SANITY_VIEWER_TOKEN) {
throw new Response('Preview mode missing token', { status: 401 });
}

const clientWithToken = client.withConfig({
token: process.env.SANITY_VIEWER_TOKEN,
});

const { isValid, redirectTo = '/' } = await validatePreviewUrl(
clientWithToken,
request.url,
);

if (!isValid) {
throw new Response('Invalid secret', { status: 401 });
}

// we can use sameSite: 'strict' because we're running an embedded studio
// setCookie('__sanity_preview', randomBytes(16).toString('hex'), {
setCookie('__sanity_preview', 'true', {
path: '/',
secure: import.meta.env.PROD,
httpOnly: true,
sameSite: 'strict',
});
sendRedirect(redirectTo);
},
});
10 changes: 10 additions & 0 deletions apps/docs/app/ui/disable-preview-mode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function DisablePreviewMode() {
return (
<a
href="/api/preview-mode/disable"
className="block bg-blue py-2 text-center text-white"
>
Disable preview mode
</a>
);
}
4 changes: 2 additions & 2 deletions apps/docs/app/ui/main-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ const mainNavItems = [

export const MainNav = () => {
const routeApi = getRouteApi('/_docs');
const { data } = routeApi.useLoaderData();
const { componentsNavItems } = routeApi.useLoaderData();

const componentsNavLinks = data.map((component) => ({
const componentsNavLinks = componentsNavItems.map((component) => ({
to: `/komponenter/${component.slug}`,
title: component.name as string,
}));
Expand Down
3 changes: 3 additions & 0 deletions apps/docs/dktp/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ properties:
containers:
- image: dktprodacr.azurecr.io/grunnmuren/docs:${IMAGE_TAG}
name: docs
env:
- name: SANITY_VIEWER_TOKEN
secretRef: ${todo}
resources:
cpu: 0.25
memory: 0.5Gi
Expand Down
3 changes: 3 additions & 0 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
"@react-aria/utils": "3.25.1",
"@sanity/client": "6.24.3",
"@sanity/code-input": "5.1.2",
"@sanity/presentation": "1.21.1",
"@sanity/preview-url-secret": "2.1.0",
"@sanity/vision": "3.69.0",
"@sanity/visual-editing": "2.12.0",
"@tanstack/react-router": "1.95.3",
"@tanstack/start": "1.95.3",
"cva": "1.0.0-beta.2",
Expand Down
23 changes: 22 additions & 1 deletion apps/docs/sanity.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { obosAuthStore } from '@code-obos/sanity-auth';
import { codeInput } from '@sanity/code-input';
import { visionTool } from '@sanity/vision';
import { defineConfig } from 'sanity';
import { defineDocuments, presentationTool } from 'sanity/presentation';
import { structureTool } from 'sanity/structure';
import { schemaTypes } from './studio/schema-types';

Expand All @@ -13,7 +14,27 @@ export default defineConfig({
basePath: '/studio',
title: 'Grunnmuren - Sanity Studio',
auth: obosAuthStore({ dataset }),
plugins: [structureTool(), visionTool(), codeInput()],
plugins: [
structureTool(),
visionTool(),
codeInput(),
presentationTool({
previewUrl: {
previewMode: {
enable: '/api/preview-mode/enable',
disable: '/api/preview-mode/disable',
},
},
resolve: {
mainDocuments: defineDocuments([
{
route: '/komponenter/:slug',
filter: `_type == "component" && slug.current == $slug`,
},
]),
},
}),
],
schema: {
types: schemaTypes,
},
Expand Down
Loading
Loading