next-sanity 0.0.0-dev.0
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/LICENSE +21 -0
- package/README.md +910 -0
- package/dist/_chunks/NextStudioLoading-8cf56cdf.js +127 -0
- package/dist/_chunks/NextStudioLoading-8cf56cdf.js.map +1 -0
- package/dist/_chunks/NextStudioLoading-bf57e61a.cjs +131 -0
- package/dist/_chunks/NextStudioLoading-bf57e61a.cjs.map +1 -0
- package/dist/index.cjs +26 -0
- package/dist/index.cjs.js +6 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/preview.cjs +19 -0
- package/dist/preview.cjs.js +6 -0
- package/dist/preview.cjs.map +1 -0
- package/dist/preview.d.ts +20 -0
- package/dist/preview.js +2 -0
- package/dist/preview.js.map +1 -0
- package/dist/studio/head.cjs +51 -0
- package/dist/studio/head.cjs.js +5 -0
- package/dist/studio/head.cjs.map +1 -0
- package/dist/studio/head.d.ts +79 -0
- package/dist/studio/head.js +46 -0
- package/dist/studio/head.js.map +1 -0
- package/dist/studio/index.cjs +111 -0
- package/dist/studio/index.cjs.js +10 -0
- package/dist/studio/index.cjs.map +1 -0
- package/dist/studio/index.d.ts +88 -0
- package/dist/studio/index.js +96 -0
- package/dist/studio/index.js.map +1 -0
- package/dist/studio/loading.cjs +8 -0
- package/dist/studio/loading.cjs.js +5 -0
- package/dist/studio/loading.cjs.map +1 -0
- package/dist/studio/loading.d.ts +23 -0
- package/dist/studio/loading.js +2 -0
- package/dist/studio/loading.js.map +1 -0
- package/dist/webhook.cjs +54 -0
- package/dist/webhook.cjs.js +6 -0
- package/dist/webhook.cjs.map +1 -0
- package/dist/webhook.d.ts +31 -0
- package/dist/webhook.js +42 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +207 -0
- package/src/client.ts +2 -0
- package/src/index.ts +3 -0
- package/src/preview/definePreview.tsx +37 -0
- package/src/preview/index.ts +2 -0
- package/src/studio/NextStudio.tsx +61 -0
- package/src/studio/NextStudioClientOnly.tsx +15 -0
- package/src/studio/NextStudioLayout.tsx +46 -0
- package/src/studio/NextStudioLoading.tsx +120 -0
- package/src/studio/NextStudioNoScript.tsx +40 -0
- package/src/studio/head/NextStudioHead.tsx +112 -0
- package/src/studio/head/apple-touch-icon.png +0 -0
- package/src/studio/head/favicon.ico +0 -0
- package/src/studio/head/favicon.svg +7 -0
- package/src/studio/head/index.ts +1 -0
- package/src/studio/index.ts +6 -0
- package/src/studio/loading.ts +1 -0
- package/src/studio/usePrefersColorScheme.ts +30 -0
- package/src/studio/useTheme.ts +13 -0
- package/src/webhook/config.ts +18 -0
- package/src/webhook/index.ts +2 -0
- package/src/webhook/parseBody.ts +44 -0
- package/src/webhook/readBody.ts +10 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* eslint-disable react/no-danger */
|
|
2
|
+
|
|
3
|
+
const style = {
|
|
4
|
+
__html: `
|
|
5
|
+
.sanity-app-no-js__root {
|
|
6
|
+
position: absolute;
|
|
7
|
+
top: 0;
|
|
8
|
+
right: 0;
|
|
9
|
+
left: 0;
|
|
10
|
+
bottom: 0;
|
|
11
|
+
background: #fff;
|
|
12
|
+
z-index: 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.sanity-app-no-js__content {
|
|
16
|
+
position: absolute;
|
|
17
|
+
top: 50%;
|
|
18
|
+
left: 50%;
|
|
19
|
+
transform: translate(-50%, -50%);
|
|
20
|
+
text-align: center;
|
|
21
|
+
font-family: helvetica, arial, sans-serif;
|
|
22
|
+
}
|
|
23
|
+
`,
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
/** @alpha */
|
|
27
|
+
export const NextStudioNoScript = () => (
|
|
28
|
+
<noscript>
|
|
29
|
+
<div className="sanity-app-no-js__root">
|
|
30
|
+
<div className="sanity-app-no-js__content">
|
|
31
|
+
<style type="text/css" dangerouslySetInnerHTML={style} />
|
|
32
|
+
<h1>JavaScript disabled</h1>
|
|
33
|
+
<p>
|
|
34
|
+
Please <a href="https://www.enable-javascript.com/">enable JavaScript</a> in your
|
|
35
|
+
browser and reload the page to proceed.
|
|
36
|
+
</p>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</noscript>
|
|
40
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import _faviconPng from './apple-touch-icon.png'
|
|
2
|
+
import _faviconIco from './favicon.ico'
|
|
3
|
+
import _faviconSvg from './favicon.svg'
|
|
4
|
+
|
|
5
|
+
const faviconPng = typeof _faviconPng === 'string' ? _faviconPng : _faviconPng.default
|
|
6
|
+
const faviconIco = typeof _faviconIco === 'string' ? _faviconIco : _faviconIco.default
|
|
7
|
+
const faviconSvg = typeof _faviconSvg === 'string' ? _faviconSvg : _faviconSvg.default
|
|
8
|
+
|
|
9
|
+
/** @public */
|
|
10
|
+
export interface NextStudioHeadProps {
|
|
11
|
+
/**
|
|
12
|
+
* @defaultValue 'utf-8'
|
|
13
|
+
*/
|
|
14
|
+
charSet?: false | string
|
|
15
|
+
/**
|
|
16
|
+
* Sets the viewport to `viewport-fit=cover` to integrate with iOS devices with display cutouts (The Notch, Dynamic Island).
|
|
17
|
+
* Also sets `width=device-width, initial-scale=1` to make the studio page responsive.
|
|
18
|
+
* @defaultValue true
|
|
19
|
+
*/
|
|
20
|
+
viewport?: boolean
|
|
21
|
+
/**
|
|
22
|
+
* It's common practice to hide the address to your Sanity Studio from search engines by setting `robots` to `noindex`
|
|
23
|
+
* @defaultValue 'noindex'
|
|
24
|
+
*/
|
|
25
|
+
robots?: false | string
|
|
26
|
+
/**
|
|
27
|
+
* @defaultValue 'same-origin'
|
|
28
|
+
*/
|
|
29
|
+
referrer?: false | string
|
|
30
|
+
/**
|
|
31
|
+
* Adds the same favicons as the `npx sanity dev` pipeline.
|
|
32
|
+
* @defaultValue true
|
|
33
|
+
*/
|
|
34
|
+
favicons?: boolean
|
|
35
|
+
/**
|
|
36
|
+
* @defaultValue 'Sanity'
|
|
37
|
+
*/
|
|
38
|
+
title?: false | string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* In Next 13 appDir mode (`/app/studio/[[...index]]/head.tsx`):
|
|
43
|
+
* ```tsx
|
|
44
|
+
* // If you don't want to change any defaults you can just re-export the head component directly:
|
|
45
|
+
* export {NextStudioHead as default} from 'next-sanity/studio/head'
|
|
46
|
+
*
|
|
47
|
+
* // To customize it, use it as a children component:
|
|
48
|
+
* import {NextStudioHead} from 'next-sanity/studio/head'
|
|
49
|
+
*
|
|
50
|
+
* export default function CustomStudioHead() {
|
|
51
|
+
* return (
|
|
52
|
+
* <>
|
|
53
|
+
* <NextStudioHead favicons={false} />
|
|
54
|
+
* <link
|
|
55
|
+
* rel="icon"
|
|
56
|
+
* type="image/png"
|
|
57
|
+
* sizes="32x32"
|
|
58
|
+
* href="https://www.sanity.io/static/images/favicons/favicon-32x32.png"
|
|
59
|
+
* />
|
|
60
|
+
* </>
|
|
61
|
+
* )
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
* If you're using Next 12 or the `pages` folder (`/pages/studio/[[...index]].tsx`):
|
|
65
|
+
* ```tsx
|
|
66
|
+
* import Head from 'next/head'
|
|
67
|
+
* import {NextStudio} from 'next-sanity/studio'
|
|
68
|
+
* import {NextStudioHead} from 'next-sanity/studio/head'
|
|
69
|
+
*
|
|
70
|
+
* export default function StudioPage() {
|
|
71
|
+
* return (
|
|
72
|
+
* <>
|
|
73
|
+
* <Head>
|
|
74
|
+
* <NextStudioHead />
|
|
75
|
+
* </Head>
|
|
76
|
+
* <NextStudio config={config} />
|
|
77
|
+
* </>
|
|
78
|
+
* )
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
* @public
|
|
82
|
+
*/
|
|
83
|
+
export function NextStudioHead(props: NextStudioHeadProps) {
|
|
84
|
+
const {
|
|
85
|
+
charSet = 'utf-8',
|
|
86
|
+
viewport = true,
|
|
87
|
+
robots = 'noindex',
|
|
88
|
+
referrer = 'same-origin',
|
|
89
|
+
favicons = true,
|
|
90
|
+
title = 'Sanity',
|
|
91
|
+
} = props
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
{charSet && <meta key="charset" charSet={charSet} />}
|
|
96
|
+
{viewport && (
|
|
97
|
+
<meta
|
|
98
|
+
key="viewport"
|
|
99
|
+
name="viewport"
|
|
100
|
+
// Studio implements display cutouts CSS (The iPhone Notch ™ ) and needs `viewport-fit=covered` for it to work correctly
|
|
101
|
+
content="width=device-width,initial-scale=1,viewport-fit=cover"
|
|
102
|
+
/>
|
|
103
|
+
)}
|
|
104
|
+
{robots && <meta key="robots" name="robots" content={robots} />}
|
|
105
|
+
{referrer && <meta key="referrer" name="referrer" content={referrer} />}
|
|
106
|
+
{title && <title>{title}</title>}
|
|
107
|
+
{favicons && <link key="favicon.ico" rel="icon" href={faviconIco} sizes="any" />}
|
|
108
|
+
{favicons && <link key="apple-touch-icon.png" rel="apple-touch-icon" href={faviconPng} />}
|
|
109
|
+
{favicons && <link key="favicon.svg" rel="icon" href={faviconSvg} type="image/svg+xml" />}
|
|
110
|
+
</>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<rect width="512" height="512" fill="#F03E2F" rx="30" />
|
|
4
|
+
<path d="M161.527 136.723C161.527 179.76 187.738 205.443 240.388 219.095L296 232.283C345.687 243.852 376 272.775 376 319.514C376 341.727 369.162 360.931 357.538 375.971C357.538 329.232 333.607 303.78 276.171 288.74L221.47 276.246C177.709 266.065 143.977 242.464 143.977 191.56C143.977 170.505 150.359 151.994 161.527 136.723Z" fill="white" />
|
|
5
|
+
<path opacity="0.5" d="M323.35 308.176C347.054 323.679 357.538 345.197 357.538 376.202C337.709 401.654 303.293 416 262.724 416C194.575 416 146.484 381.756 136 322.753H201.641C210.074 350.056 232.41 362.551 262.268 362.551C298.735 362.32 322.895 342.652 323.35 308.176Z" fill="white" />
|
|
6
|
+
<path opacity="0.5" d="M195.715 200.816C172.923 186.007 161.527 165.183 161.527 136.954C180.672 111.503 213.493 96 253.835 96C323.35 96 363.692 133.252 373.721 185.776H310.359C303.293 165.183 285.971 148.986 254.291 148.986C220.33 148.986 197.311 169.116 195.715 200.816Z" fill="white" />
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './NextStudioHead'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {NextStudioLoading, type NextStudioLoadingProps} from './NextStudioLoading'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
2
|
+
import type {ThemeColorSchemeKey} from '@sanity/ui'
|
|
3
|
+
import {useSyncExternalStore} from 'react'
|
|
4
|
+
|
|
5
|
+
function createStore() {
|
|
6
|
+
if (typeof document === 'undefined') {
|
|
7
|
+
return {
|
|
8
|
+
subscribe: () => () => {},
|
|
9
|
+
getSnapshot: () => 'light' as const,
|
|
10
|
+
getServerSnapshot: () => 'light' as const,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const matchMedia = window.matchMedia('(prefers-color-scheme: dark)')
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
subscribe: (onStoreChange: () => void) => {
|
|
18
|
+
matchMedia.addEventListener('change', onStoreChange)
|
|
19
|
+
return () => matchMedia.removeEventListener('change', onStoreChange)
|
|
20
|
+
},
|
|
21
|
+
getSnapshot: () => (matchMedia.matches ? 'dark' : 'light'),
|
|
22
|
+
getServerSnapshot: () => 'light' as const,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const store = createStore()
|
|
26
|
+
|
|
27
|
+
/** @alpha */
|
|
28
|
+
export function usePrefersColorScheme(): ThemeColorSchemeKey {
|
|
29
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot)
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {studioTheme} from '@sanity/ui'
|
|
2
|
+
import {useMemo} from 'react'
|
|
3
|
+
import type {Config, SingleWorkspace, StudioTheme} from 'sanity'
|
|
4
|
+
|
|
5
|
+
/** @alpha */
|
|
6
|
+
export function useTheme(
|
|
7
|
+
config?: Config | Required<Pick<SingleWorkspace, 'theme'>>
|
|
8
|
+
): StudioTheme {
|
|
9
|
+
const workspace = useMemo<
|
|
10
|
+
SingleWorkspace | Required<Pick<SingleWorkspace, 'theme'>> | undefined
|
|
11
|
+
>(() => (Array.isArray(config) ? config[0] : config), [config])
|
|
12
|
+
return useMemo<StudioTheme>(() => workspace?.theme || studioTheme, [workspace])
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type {PageConfig} from 'next/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configurates the API function with the right runtime and body parsing to handle Sanity Webhook events.
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export const config: PageConfig = {
|
|
8
|
+
api: {
|
|
9
|
+
/**
|
|
10
|
+
* Next.js will by default parse the body, which can lead to invalid signatures.
|
|
11
|
+
*/
|
|
12
|
+
bodyParser: false,
|
|
13
|
+
},
|
|
14
|
+
/**
|
|
15
|
+
* `@sanity/webhook` isn't updated to support the edge runtime yet, and currently requires Node.js APIs such as Buffer.
|
|
16
|
+
*/
|
|
17
|
+
runtime: 'nodejs',
|
|
18
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type {SanityDocument} from '@sanity/types'
|
|
2
|
+
import sanityWebhook from '@sanity/webhook'
|
|
3
|
+
import type {NextApiRequest} from 'next'
|
|
4
|
+
|
|
5
|
+
// As `@sanity/webhook` isn't shipping ESM, extracting from the default export have the best ecosystem support
|
|
6
|
+
const {isValidSignature, SIGNATURE_HEADER_NAME} = sanityWebhook
|
|
7
|
+
|
|
8
|
+
import {_readBody as readBody} from './readBody'
|
|
9
|
+
|
|
10
|
+
/** @public */
|
|
11
|
+
export type ParseBody = {
|
|
12
|
+
/**
|
|
13
|
+
* If a secret is given then it returns a boolean. If no secret is provided then no validation is done on the signature, and it'll return `null`
|
|
14
|
+
*/
|
|
15
|
+
isValidSignature: boolean | null
|
|
16
|
+
body: SanityDocument
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Handles parsing the body JSON, and validating its signature. Also waits for Content Lake eventual consistency so you can run your queries
|
|
20
|
+
* without worrying about getting stale data.
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export async function parseBody(
|
|
24
|
+
req: NextApiRequest,
|
|
25
|
+
secret?: string,
|
|
26
|
+
waitForContentLakeEventualConsistency: boolean = true
|
|
27
|
+
): Promise<ParseBody> {
|
|
28
|
+
let signature = req.headers[SIGNATURE_HEADER_NAME]!
|
|
29
|
+
if (Array.isArray(signature)) {
|
|
30
|
+
signature = signature[0]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const body = await readBody(req)
|
|
34
|
+
const validSignature = secret ? isValidSignature(body, signature, secret.trim()) : null
|
|
35
|
+
|
|
36
|
+
if (validSignature !== false && waitForContentLakeEventualConsistency) {
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
body: body.trim() && JSON.parse(body),
|
|
42
|
+
isValidSignature: validSignature,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type {NextApiRequest} from 'next'
|
|
2
|
+
|
|
3
|
+
/** @internal */
|
|
4
|
+
export async function _readBody(readable: NextApiRequest): Promise<string> {
|
|
5
|
+
const chunks = []
|
|
6
|
+
for await (const chunk of readable) {
|
|
7
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk)
|
|
8
|
+
}
|
|
9
|
+
return Buffer.concat(chunks).toString('utf8')
|
|
10
|
+
}
|