├── .editorconfig ├── .github ├── CODEOWNERS ├── renovate.json └── workflows │ ├── ci.yml │ ├── lint-fix.yml │ ├── lock.yml │ ├── pr-validate.yml │ ├── prettier.yml │ ├── react-compiler.yml │ ├── release-canary.yml │ ├── release-corel.yml │ └── release-please.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .prettierignore ├── .release-please-manifest.json ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── apps ├── astro │ ├── .env.example │ ├── .gitignore │ ├── astro.config.mjs │ ├── package.json │ ├── public │ │ └── favicon.svg │ ├── src │ │ ├── env.d.ts │ │ ├── layouts │ │ │ └── layout.astro │ │ ├── load-query.ts │ │ ├── pages │ │ │ ├── index.astro │ │ │ └── shoes │ │ │ │ ├── [slug].astro │ │ │ │ └── index.astro │ │ ├── queries.ts │ │ ├── sanity.ts │ │ └── utils.ts │ ├── tailwind.config.mjs │ ├── tsconfig.json │ └── turbo.json ├── live-next │ ├── .env.local.example │ ├── .eslintignore │ ├── .eslintrc │ ├── .gitignore │ ├── app │ │ ├── actions.ts │ │ ├── alert-banner.tsx │ │ ├── animated-h1.tsx │ │ ├── api │ │ │ └── draft-mode │ │ │ │ └── enable │ │ │ │ └── route.ts │ │ ├── avatar.tsx │ │ ├── cors.tsx │ │ ├── cover-image.tsx │ │ ├── date.tsx │ │ ├── draft-mode-status.tsx │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── more-stories.tsx │ │ ├── page.tsx │ │ ├── portable-text.tsx │ │ └── posts │ │ │ └── [slug] │ │ │ ├── OptimisticPostContent.tsx │ │ │ └── page.tsx │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.js │ ├── sanity-typegen.json │ ├── sanity.types.ts │ ├── sanity │ │ └── lib │ │ │ ├── api.ts │ │ │ ├── client.ts │ │ │ ├── live.ts │ │ │ ├── queries.ts │ │ │ └── utils.ts │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── turbo.json ├── next-with-i18n │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── app │ │ ├── (website) │ │ │ └── [locale] │ │ │ │ ├── [...path] │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ ├── api │ │ │ ├── disable-draft │ │ │ │ └── route.ts │ │ │ └── draft │ │ │ │ └── route.ts │ │ ├── favicon.ico │ │ ├── icon.png │ │ └── studio │ │ │ ├── [[...index]] │ │ │ ├── Studio.tsx │ │ │ └── page.tsx │ │ │ └── layout.tsx │ ├── components │ │ ├── Page.tsx │ │ └── StudioLogo.tsx │ ├── config.ts │ ├── data │ │ └── sanity │ │ │ ├── client.ts │ │ │ ├── generateStaticSlugs.ts │ │ │ ├── index.ts │ │ │ ├── loadQuery.ts │ │ │ └── queries.ts │ ├── middleware.ts │ ├── next-env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── static │ │ │ ├── logo.png │ │ │ └── studio.png │ ├── sanity.cli.ts │ ├── sanity.config.ts │ ├── sanity │ │ └── schemas │ │ │ ├── index.ts │ │ │ └── page.ts │ ├── styles │ │ └── index.css │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── turbo.json │ └── types │ │ └── index.ts ├── next │ ├── .eslintrc.json │ ├── .gitignore │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ │ ├── next.svg │ │ └── vercel.svg │ ├── sanity-typegen.json │ ├── src │ │ ├── app │ │ │ ├── Timesince.tsx │ │ │ ├── api │ │ │ │ └── draft-mode │ │ │ │ │ ├── disable │ │ │ │ │ └── route.ts │ │ │ │ │ └── enable │ │ │ │ │ └── route.ts │ │ │ ├── favicon.ico │ │ │ ├── only-visual-editing │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.client.tsx │ │ │ │ └── page.tsx │ │ │ └── shoes │ │ │ │ ├── VisualEditing.tsx │ │ │ │ ├── [slug] │ │ │ │ ├── page.client.tsx │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.client.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── sanity.client.ts │ │ │ │ ├── sanity.live.ts │ │ │ │ ├── sanity.ssr.ts │ │ │ │ └── utils.ts │ │ ├── components │ │ │ ├── VisualEditing.tsx │ │ │ ├── sanity.client.ts │ │ │ ├── sanity.ssr.ts │ │ │ └── utils.ts │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ └── pages-router │ │ │ │ ├── performance-test │ │ │ │ └── index.tsx │ │ │ │ └── shoes │ │ │ │ ├── [slug].tsx │ │ │ │ └── index.tsx │ │ ├── queries.ts │ │ ├── tailwind.css │ │ ├── types.ts │ │ └── utils.ts │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── turbo.json ├── nuxt │ ├── .gitignore │ ├── app.vue │ ├── assets │ │ └── css │ │ │ └── tailwind.css │ ├── nuxt.config.ts │ ├── package.json │ ├── pages │ │ ├── index.vue │ │ ├── preview.vue │ │ └── shoes │ │ │ ├── [slug].vue │ │ │ └── index.vue │ ├── public │ │ └── favicon.ico │ ├── queries.ts │ ├── tsconfig.json │ ├── turbo.json │ └── utils.ts ├── page-builder-demo │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── next.config.ts │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ │ ├── scene.bin │ │ └── scene.gltf │ ├── sanity-typegen.json │ ├── src │ │ ├── app │ │ │ ├── AppLayout.tsx │ │ │ ├── actions.ts │ │ │ ├── alert-banner.tsx │ │ │ ├── api │ │ │ │ └── draft-mode │ │ │ │ │ └── enable │ │ │ │ │ └── route.ts │ │ │ ├── dnd │ │ │ │ ├── OptimisticSortOrder.tsx │ │ │ │ └── page.tsx │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ ├── pages │ │ │ │ └── [slug] │ │ │ │ │ └── page.tsx │ │ │ ├── product │ │ │ │ └── [slug] │ │ │ │ │ └── page.tsx │ │ │ ├── products │ │ │ │ └── page.tsx │ │ │ ├── project │ │ │ │ └── [slug] │ │ │ │ │ └── page.tsx │ │ │ └── projects │ │ │ │ └── page.tsx │ │ ├── components │ │ │ ├── image │ │ │ │ ├── Image.tsx │ │ │ │ └── index.ts │ │ │ ├── overlay-plugins │ │ │ │ ├── example-exclusive.tsx │ │ │ │ ├── example-hud.tsx │ │ │ │ ├── exciting-title.tsx │ │ │ │ ├── image-res.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── led-lifespan.tsx │ │ │ │ └── rotate-3d.tsx │ │ │ ├── overlays │ │ │ │ ├── OverlayHighlight.tsx │ │ │ │ └── resolver.tsx │ │ │ ├── page │ │ │ │ ├── DnDCustomBehaviour.tsx │ │ │ │ ├── Page.tsx │ │ │ │ ├── PageSection.tsx │ │ │ │ ├── ProductModel.tsx │ │ │ │ ├── SimpleContent.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── sectionFragment.ts │ │ │ │ ├── sections │ │ │ │ │ ├── FeatureHighlight.tsx │ │ │ │ │ ├── FeaturedProducts.tsx │ │ │ │ │ ├── Hero.tsx │ │ │ │ │ ├── Intro.tsx │ │ │ │ │ └── Section.tsx │ │ │ │ └── types.ts │ │ │ └── slideshow │ │ │ │ ├── Slideshow.css │ │ │ │ ├── Slideshow.tsx │ │ │ │ └── index.ts │ │ ├── sanity.types.ts │ │ └── sanity │ │ │ ├── client.ts │ │ │ ├── dataAttribute.ts │ │ │ ├── image.ts │ │ │ └── live.ts │ ├── tailwind.config.cjs │ ├── tsconfig.json │ └── turbo.json ├── remix │ ├── .eslintrc.js │ ├── .gitignore │ ├── app │ │ ├── CustomControlsComponent.tsx │ │ ├── LiveVisualEditing.tsx │ │ ├── queries.ts │ │ ├── root.tsx │ │ ├── routes │ │ │ ├── _index.tsx │ │ │ ├── api.perspective.tsx │ │ │ ├── api.preview-mode.disable.ts │ │ │ ├── api.preview-mode.enable.ts │ │ │ ├── shoes.$slug.tsx │ │ │ └── shoes._index.tsx │ │ ├── sanity.loader.server.ts │ │ ├── sanity.ts │ │ ├── sessions.ts │ │ ├── tailwind.css │ │ └── utils.ts │ ├── env.d.ts │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ └── favicon.ico │ ├── tailwind.config.ts │ ├── tsconfig.json │ ├── turbo.json │ └── vite.config.ts ├── studio │ ├── .env.local │ ├── .gitignore │ ├── package.json │ ├── presentation │ │ ├── CustomHeader.tsx │ │ ├── CustomNavigator.tsx │ │ └── DebugStega.tsx │ ├── sanity.cli.ts │ ├── sanity.config.ts │ ├── tsconfig.json │ ├── turbo.json │ └── vercel.json └── svelte │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .npmrc │ ├── package.json │ ├── postcss.config.js │ ├── src │ ├── app.css │ ├── app.d.ts │ ├── app.html │ ├── components │ │ ├── Shoe.svelte │ │ └── Shoes.svelte │ ├── hooks.server.ts │ ├── lib │ │ ├── images │ │ │ ├── github.svg │ │ │ ├── svelte-logo.svg │ │ │ ├── svelte-welcome.png │ │ │ └── svelte-welcome.webp │ │ ├── queries.ts │ │ ├── sanity.ts │ │ ├── server │ │ │ └── sanity.ts │ │ └── utils.ts │ └── routes │ │ ├── +layout.server.ts │ │ ├── +layout.svelte │ │ ├── +layout.ts │ │ ├── +page.svelte │ │ ├── +page.ts │ │ ├── shoes-with-loaders │ │ ├── +layout.svelte │ │ ├── +page.server.ts │ │ ├── +page.svelte │ │ └── [slug] │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ │ ├── shoes │ │ ├── +layout.svelte │ │ ├── +page.server.ts │ │ ├── +page.svelte │ │ └── [slug] │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ │ └── styles.css │ ├── static │ ├── favicon.png │ └── robots.txt │ ├── svelte.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── turbo.json │ └── vite.config.ts ├── knip.json ├── package.json ├── packages ├── @repo │ ├── env │ │ ├── index.ts │ │ ├── package.json │ │ └── tsconfig.json │ ├── eslint-config │ │ ├── index.js │ │ └── package.json │ ├── package.config │ │ ├── package.json │ │ └── src │ │ │ └── package.config.ts │ ├── prettier-config │ │ ├── index.js │ │ └── package.json │ ├── sanity-extracted-schema │ │ ├── .gitignore │ │ ├── package.json │ │ ├── sanity.cli.ts │ │ ├── sanity.config.ts │ │ ├── tsconfig.json │ │ └── turbo.json │ ├── sanity-schema │ │ ├── .eslintignore │ │ ├── package.config.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── cross-dataset-references │ │ │ │ └── index.tsx │ │ │ ├── index.ts │ │ │ ├── live-demo │ │ │ │ └── index.tsx │ │ │ ├── page-builder-demo │ │ │ │ ├── PageSectionInput.tsx │ │ │ │ └── index.tsx │ │ │ ├── performance-test │ │ │ │ └── index.tsx │ │ │ └── shoes │ │ │ │ └── index.tsx │ │ ├── tsconfig.base.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── turbo.json │ └── studio-url │ │ ├── .eslintignore │ │ ├── package.config.ts │ │ ├── package.json │ │ ├── src │ │ └── index.ts │ │ ├── tsconfig.base.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── turbo.json ├── comlink │ ├── .eslintignore │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── comlink-lines.png │ ├── package.config.ts │ ├── package.json │ ├── playground │ │ ├── .eslintrc.cjs │ │ ├── .gitignore │ │ ├── README.md │ │ ├── app │ │ │ ├── components │ │ │ │ ├── Button.tsx │ │ │ │ ├── Card.tsx │ │ │ │ ├── Frame.tsx │ │ │ │ ├── MessageControls.tsx │ │ │ │ ├── MessageEntry.tsx │ │ │ │ └── MessageStack.tsx │ │ │ ├── entry.client.tsx │ │ │ ├── entry.server.tsx │ │ │ ├── root.tsx │ │ │ ├── routes │ │ │ │ ├── _index.tsx │ │ │ │ └── frame.tsx │ │ │ ├── tailwind.css │ │ │ └── types.ts │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── public │ │ │ └── favicon.ico │ │ ├── tailwind.config.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── src │ │ ├── common.ts │ │ ├── connection.ts │ │ ├── constants.ts │ │ ├── controller.ts │ │ ├── index.ts │ │ ├── node.ts │ │ ├── request.ts │ │ ├── types.ts │ │ └── util.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── turbo.json ├── core-loader │ ├── .eslintignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ ├── createDataAttribute.ts │ │ ├── encodeDataAttribute.ts │ │ ├── env.ts │ │ ├── index.ts │ │ ├── live-mode │ │ │ ├── enableLiveMode.ts │ │ │ └── index.ts │ │ └── types.ts │ ├── test │ │ └── encodeDataAttribute.test.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── turbo.json │ └── vitest.config.ts ├── insert-menu │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ ├── InsertMenu.tsx │ │ ├── InsertMenuOptions.ts │ │ ├── __workshop__ │ │ │ ├── .eslintrc │ │ │ ├── full.tsx │ │ │ └── index.ts │ │ ├── getSchemaTypeIcon.ts │ │ └── index.ts │ ├── tsconfig.dist.json │ ├── tsconfig.json │ ├── tsconfig.settings.json │ ├── turbo.json │ └── workshop.config.ts ├── next-loader │ ├── .eslintignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ ├── client-components │ │ │ ├── live-stream │ │ │ │ ├── SanityLiveStream.tsx │ │ │ │ ├── SanityLiveStreamLazy.tsx │ │ │ │ └── index.ts │ │ │ └── live │ │ │ │ ├── PresentationComlink.tsx │ │ │ │ ├── RefreshOnFocus.tsx │ │ │ │ ├── RefreshOnMount.tsx │ │ │ │ ├── RefreshOnReconnect.tsx │ │ │ │ ├── SanityLive.tsx │ │ │ │ └── index.ts │ │ ├── defineLive.tsx │ │ ├── hooks │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ ├── useDraftMode.ts │ │ │ ├── useIsLivePreview.ts │ │ │ ├── useIsPresentationTool.ts │ │ │ └── usePresentationQuery.ts │ │ ├── index.server-only.ts │ │ ├── index.ts │ │ ├── isCorsOriginError.ts │ │ ├── resolveCookiePerspective.ts │ │ ├── server-actions │ │ │ └── index.ts │ │ └── utils.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── turbo.json │ └── vitest.config.ts ├── nuxt-loader │ └── README.md ├── overlays │ └── README.md ├── presentation-comlink │ ├── .eslintignore │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ ├── comlinkCompatibility.ts │ │ ├── index.ts │ │ ├── isMaybePresentation.ts │ │ └── types.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── turbo.json │ └── vitest.config.ts ├── presentation │ └── README.md ├── preview-url-secret │ ├── .eslintignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ ├── _exports │ │ │ ├── constants.ts │ │ │ ├── create-secret.ts │ │ │ ├── define-preview-url.ts │ │ │ ├── get-redirect-to.ts │ │ │ ├── sanity-plugin-debug-secrets.ts │ │ │ ├── toggle-preview-access-sharing.ts │ │ │ ├── toggle-vercel-protection-bypass.ts │ │ │ └── without-secret-search-params.ts │ │ ├── constants.ts │ │ ├── createClientWithConfig.ts │ │ ├── createPreviewSecret.ts │ │ ├── definePreviewUrl.ts │ │ ├── generateSecret.ts │ │ ├── getRedirectTo.ts │ │ ├── index.ts │ │ ├── parsePreviewUrl.test.ts │ │ ├── parsePreviewUrl.ts │ │ ├── sanityPluginDebugSecrets │ │ │ ├── debugUrlSecrets.tsx │ │ │ └── index.ts │ │ ├── togglePreviewAccessSharing.ts │ │ ├── toggleVercelProtectionBypass.ts │ │ ├── types.ts │ │ ├── validatePreviewUrl.ts │ │ ├── validateSecret.ts │ │ └── withoutSecretSearchParams.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── turbo.json │ └── vitest.config.ts ├── react-loader │ ├── .eslintignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ ├── createQueryStore │ │ │ ├── client-only.ts │ │ │ ├── server-only.ts │ │ │ └── universal.ts │ │ ├── defineStudioUrlStore.ts │ │ ├── defineUseLiveMode.ts │ │ ├── defineUseQuery.ts │ │ ├── index.browser.ts │ │ ├── index.ts │ │ ├── jsx │ │ │ ├── SanityElement.tsx │ │ │ ├── html.ts │ │ │ ├── index.ts │ │ │ ├── jsx.tsx │ │ │ ├── svg.ts │ │ │ └── wrap │ │ │ │ ├── __fixtures__ │ │ │ │ └── responses.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── index.ts │ │ │ │ ├── isSourceNode.ts │ │ │ │ ├── resolveSanityNode.ts │ │ │ │ ├── types.ts │ │ │ │ ├── unwrapData.test.ts │ │ │ │ ├── unwrapData.ts │ │ │ │ ├── wrapData.test.ts │ │ │ │ └── wrapData.ts │ │ ├── rsc │ │ │ ├── index.react-server.ts │ │ │ └── index.ts │ │ ├── types.ts │ │ └── useEncodeDataAttribute.ts │ ├── test │ │ └── createQueryStore │ │ │ └── universal.test-d.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── turbo.json │ └── vitest.config.ts ├── svelte-loader │ ├── .eslintignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── LiveMode.svelte │ │ ├── createQueryStore.ts │ │ ├── defineStudioUrlStore.ts │ │ ├── defineUseLiveMode.ts │ │ ├── defineUseQuery.ts │ │ ├── global.d.ts │ │ ├── hooks.ts │ │ ├── index.ts │ │ ├── previewStore.ts │ │ ├── types.ts │ │ └── useEncodeDataAttribute.ts │ ├── svelte.config.js │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── turbo.json │ └── vitest.config.ts ├── vercel-protection-bypass │ ├── .eslintignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ ├── VercelProtectionBypassTool.tsx │ │ └── index.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── turbo.json │ └── vitest.config.ts ├── visual-editing-csm │ ├── .eslintignore │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ ├── createDataAttribute.test.ts │ │ ├── createDataAttribute.ts │ │ ├── decodeSanityNodeData.test.ts │ │ ├── decodeSanityNodeData.ts │ │ ├── encodeSanityNodeData.test.ts │ │ ├── encodeSanityNodeData.ts │ │ ├── index.ts │ │ ├── isArray.ts │ │ ├── isValidSanityNode.ts │ │ ├── pathToUrlString.ts │ │ ├── sanityNodeSchema.ts │ │ ├── urlStringToPath.test.ts │ │ └── urlStringToPath.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── turbo.json │ └── vitest.config.ts ├── visual-editing-types │ ├── .eslintignore │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── turbo.json └── visual-editing │ ├── .eslintignore │ ├── .gitignore │ ├── .storybook │ ├── main.ts │ ├── preview-head.html │ ├── preview.ts │ └── tailwind.css │ ├── CHANGELOG.md │ ├── README.md │ ├── package.config.ts │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ ├── constants.ts │ ├── controller.ts │ ├── create-data-attribute.ts │ ├── index.ts │ ├── next-pages-router │ │ ├── VisualEditing.tsx │ │ ├── VisualEditingComponent.tsx │ │ └── index.ts │ ├── optimistic │ │ ├── context.ts │ │ ├── index.ts │ │ ├── state │ │ │ ├── createSharedListener.ts │ │ │ ├── datasetMutator.ts │ │ │ └── documentMutator.ts │ │ └── types.ts │ ├── overlay-components │ │ ├── components │ │ │ ├── InsertMenu.tsx │ │ │ ├── PointerEvents.tsx │ │ │ └── UnionInsertMenuOverlay.tsx │ │ ├── defineOverlayComponent.ts │ │ ├── defineOverlayComponents.ts │ │ ├── defineOverlayPlugin.ts │ │ └── index.ts │ ├── react-router │ │ ├── VisualEditing.tsx │ │ ├── VisualEditingComponent.tsx │ │ └── index.ts │ ├── react │ │ ├── index.ts │ │ ├── useDocuments.ts │ │ ├── useOptimistic.ts │ │ └── useOptimisticActor.ts │ ├── remix │ │ ├── VisualEditing.tsx │ │ ├── VisualEditingComponent.tsx │ │ └── index.ts │ ├── stories │ │ ├── Overlays.stories.tsx │ │ └── examples │ │ │ ├── marketing │ │ │ └── MarketingPage.tsx │ │ │ └── media │ │ │ ├── Figure.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Link.tsx │ │ │ ├── MediaArticlePage.tsx │ │ │ ├── MediaHomePage.tsx │ │ │ └── Navbar.tsx │ ├── types.ts │ ├── ui │ │ ├── ElementOverlay.tsx │ │ ├── History.tsx │ │ ├── Meta.ts │ │ ├── OverlayDragGroupRect.tsx │ │ ├── OverlayDragInsertMarker.tsx │ │ ├── OverlayDragPreview.tsx │ │ ├── OverlayMinimapPrompt.tsx │ │ ├── Overlays.tsx │ │ ├── PopoverPortal.tsx │ │ ├── Refresh.tsx │ │ ├── VisualEditing.tsx │ │ ├── context-menu │ │ │ ├── ContextMenu.tsx │ │ │ └── contextMenuItems.tsx │ │ ├── elementsReducer.ts │ │ ├── enableVisualEditing.tsx │ │ ├── overlayStateReducer.ts │ │ ├── preview │ │ │ ├── PreviewSnapshotsContext.tsx │ │ │ ├── PreviewSnapshotsProvider.tsx │ │ │ └── usePreviewSnapshots.ts │ │ ├── renderVisualEditing.tsx │ │ ├── schema │ │ │ ├── SchemaContext.tsx │ │ │ ├── SchemaProvider.tsx │ │ │ └── useSchema.ts │ │ ├── shared-state │ │ │ ├── SharedStateContext.ts │ │ │ ├── SharedStateProvider.tsx │ │ │ ├── sharedStateStore.ts │ │ │ └── useSharedState.ts │ │ ├── telemetry │ │ │ ├── TelemetryContext.tsx │ │ │ ├── TelemetryProvider.tsx │ │ │ └── useTelemetry.tsx │ │ ├── useComlink.tsx │ │ ├── useController.tsx │ │ ├── useDatasetMutator.ts │ │ ├── usePerspectiveSync.tsx │ │ └── useReportDocuments.ts │ └── util │ │ ├── __tests__ │ │ └── getLinkHref.test.ts │ │ ├── drag-and-drop.md │ │ ├── dragAndDrop.ts │ │ ├── elements.ts │ │ ├── findSanityNodes.ts │ │ ├── geometry.ts │ │ ├── getLinkHref.ts │ │ ├── getNodeIcon.tsx │ │ ├── mutations.ts │ │ ├── randomKey.ts │ │ ├── shareReplayLatest.ts │ │ ├── stega.ts │ │ └── useDragEvents.ts │ ├── svelte.config.js │ ├── svelte │ ├── VisualEditing.svelte │ ├── global.d.ts │ ├── hooks.ts │ ├── index.ts │ ├── optimistic │ │ ├── optimisticActor.ts │ │ └── useOptimistic.ts │ ├── previewStore.ts │ └── types.ts │ ├── tailwind.config.cjs │ ├── tsconfig.base.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── turbo.json │ └── vitest.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── release-please-config.json ├── scripts ├── release-canary.mjs └── release-corel.mjs ├── turbo.json └── vitest.workspace.js /.editorconfig: -------------------------------------------------------------------------------- 1 | ; editorconfig.org 2 | root = true 3 | charset= utf8 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sanity-io/ecosystem 2 | -------------------------------------------------------------------------------- /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | name: Lock Threads 2 | 3 | on: 4 | issues: 5 | types: [closed] 6 | pull_request: 7 | types: [closed] 8 | schedule: 9 | - cron: '0 0 * * *' 10 | workflow_dispatch: 11 | 12 | permissions: 13 | issues: write 14 | pull-requests: write 15 | 16 | concurrency: 17 | group: ${{ github.workflow }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | action: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5 25 | with: 26 | issue-inactive-days: 0 27 | pr-inactive-days: 7 28 | -------------------------------------------------------------------------------- /.github/workflows/pr-validate.yml: -------------------------------------------------------------------------------- 1 | name: "Validate PR" 2 | 3 | on: 4 | pull_request: 5 | 6 | permissions: 7 | pull-requests: read 8 | 9 | jobs: 10 | main: 11 | name: Validate PR title 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm lint-staged 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | enable-pre-post-scripts = true 2 | shamefully-hoist = true 3 | link-workspace-packages = deep 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .github/workflows/*.yml 3 | .release-please-manifest.json 4 | .sanity 5 | .turbo 6 | .vercel 7 | apps/*/.next 8 | apps/*/build 9 | apps/*/dist 10 | apps/*/.output 11 | apps/*/public/build 12 | apps/*/sanity.types.ts 13 | CHANGELOG.md 14 | packages/*/dist 15 | packages/*/dist-svelte 16 | packages/*/storybook-static 17 | packages/@repo/*/dist 18 | packages/@repo/*/dist-svelte 19 | packages/@repo/*/storybook-static 20 | pnpm-lock.yaml 21 | tsconfig.json 22 | 23 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | {"packages/core-loader":"1.8.10","packages/react-loader":"1.11.11","packages/svelte-loader":"1.13.41","packages/presentation":"2.0.0","packages/preview-url-secret":"2.1.11","packages/visual-editing":"2.15.0","packages/insert-menu":"1.1.12","packages/comlink":"3.0.5","packages/next-loader":"1.6.2","packages/vercel-protection-bypass":"1.0.23","packages/presentation-comlink":"1.0.21","packages/visual-editing-csm":"2.0.18","packages/visual-editing-types":"1.1.0"} 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "sanity-io.vscode-sanity", 4 | "joshbolduc.story-explorer", 5 | "bradlc.vscode-tailwindcss", 6 | "mattpocock.ts-error-translator", 7 | "Vercel.turbo-vsc", 8 | "vitest.explorer", 9 | "styled-components.vscode-styled-components", 10 | "statelyai.stately-vscode" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /apps/astro/.env.example: -------------------------------------------------------------------------------- 1 | SANITY_API_READ_TOKEN="" 2 | SANITY_VISUAL_EDITING_ENABLED="true" 3 | -------------------------------------------------------------------------------- /apps/astro/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | # jetbrains setting folder 24 | .idea/ 25 | .vercel 26 | .env*.local 27 | -------------------------------------------------------------------------------- /apps/astro/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import react from '@astrojs/react' 2 | import tailwind from '@astrojs/tailwind' 3 | import vercel from '@astrojs/vercel' 4 | import {apiVersion, workspaces} from '@repo/env' 5 | import {studioUrl as baseUrl} from '@repo/studio-url' 6 | import sanity from '@sanity/astro' 7 | import {defineConfig} from 'astro/config' 8 | 9 | const {projectId, dataset} = workspaces['astro'] 10 | 11 | // https://astro.build/config 12 | export default defineConfig({ 13 | output: 'server', 14 | integrations: [ 15 | sanity({ 16 | projectId, 17 | dataset, 18 | useCdn: true, 19 | apiVersion, 20 | // studioUrl must be a string for Astro https://github.com/sanity-io/sanity-astro/blob/61f984a207a7cb61b3ed9cf16b3842d17e923689/packages/sanity-astro/src/vite-plugin-sanity-client.ts#L22 21 | stega: { 22 | studioUrl: `${baseUrl}/astro/`, 23 | }, 24 | }), 25 | react(), 26 | tailwind(), 27 | ], 28 | adapter: vercel(), 29 | }) 30 | -------------------------------------------------------------------------------- /apps/astro/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /apps/astro/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | interface ImportMetaEnv { 6 | readonly SANITY_VISUAL_EDITING_ENABLED: string 7 | readonly SANITY_API_READ_TOKEN: string 8 | } 9 | 10 | interface ImportMeta { 11 | readonly env: ImportMetaEnv 12 | } 13 | -------------------------------------------------------------------------------- /apps/astro/src/layouts/layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import {VisualEditing} from '@sanity/astro/visual-editing' 3 | 4 | const visualEditingEnabled = import.meta.env.SANITY_VISUAL_EDITING_ENABLED == 'true' 5 | 6 | export type props = { 7 | title: string 8 | } 9 | const {title} = Astro.props 10 | --- 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {title} 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /apps/astro/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../layouts/layout.astro' 3 | --- 4 | 5 | 6 | Shoes 7 | 8 | -------------------------------------------------------------------------------- /apps/astro/src/sanity.ts: -------------------------------------------------------------------------------- 1 | import {workspaces} from '@repo/env' 2 | import imageUrlBuilder from '@sanity/image-url' 3 | 4 | const {projectId, dataset} = workspaces['astro'] 5 | 6 | const builder = imageUrlBuilder({projectId, dataset}) 7 | export function urlFor(source: any) { 8 | return builder.image(source).auto('format').fit('max') 9 | } 10 | 11 | const crossDatasetBuilder = imageUrlBuilder({ 12 | projectId: workspaces['cross-dataset-references'].projectId, 13 | dataset: workspaces['cross-dataset-references'].dataset, 14 | }) 15 | export function urlForCrossDatasetReference(source: any) { 16 | return crossDatasetBuilder.image(source).auto('format').fit('max') 17 | } 18 | -------------------------------------------------------------------------------- /apps/astro/src/utils.ts: -------------------------------------------------------------------------------- 1 | import {vercelStegaSplit} from '@vercel/stega' 2 | 3 | export function formatCurrency(_value: number | string): string { 4 | let value = typeof _value === 'string' ? undefined : _value 5 | let encoded = '' 6 | if (typeof _value === 'string') { 7 | const split = vercelStegaSplit(_value) 8 | value = parseInt(split.cleaned, 10) 9 | encoded = split.encoded 10 | } 11 | const formatter = new Intl.NumberFormat('en', { 12 | style: 'currency', 13 | currency: 'USD', 14 | minimumFractionDigits: 0, 15 | maximumFractionDigits: 0, 16 | }) 17 | return `${formatter.format(value!)}${encoded}` 18 | } 19 | -------------------------------------------------------------------------------- /apps/astro/tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /apps/astro/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strictest" 3 | } 4 | -------------------------------------------------------------------------------- /apps/astro/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"], 7 | "env": ["SANITY_API_READ_TOKEN", "SANITY_VISUAL_EDITING_ENABLED"], 8 | "outputs": [".vercel/**", "dist/**"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/live-next/.env.local.example: -------------------------------------------------------------------------------- 1 | SANITY_API_READ_TOKEN= 2 | SANITY_API_WRITE_TOKEN= 3 | SANITY_API_BROWSER_TOKEN= 4 | -------------------------------------------------------------------------------- /apps/live-next/.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignoring generated files 2 | ./sanity.types.ts 3 | -------------------------------------------------------------------------------- /apps/live-next/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "root": true, 4 | "plugins": ["react-compiler"], 5 | "rules": { 6 | "react-compiler/react-compiler": "error" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/live-next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # sanity 17 | /.sanity/ 18 | /dist/ 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | # Env files created by scripts for working locally 40 | .env 41 | .env.local 42 | -------------------------------------------------------------------------------- /apps/live-next/app/api/draft-mode/enable/route.ts: -------------------------------------------------------------------------------- 1 | import {client} from '@/sanity/lib/client' 2 | import {defineEnableDraftMode} from 'next-sanity/draft-mode' 3 | 4 | export const {GET} = defineEnableDraftMode({ 5 | client: client.withConfig({ 6 | token: process.env.SANITY_API_READ_TOKEN, 7 | }), 8 | }) 9 | -------------------------------------------------------------------------------- /apps/live-next/app/avatar.tsx: -------------------------------------------------------------------------------- 1 | import type {Author} from '@/sanity.types' 2 | import {urlForImage} from '@/sanity/lib/utils' 3 | import {Image} from 'next-sanity/image' 4 | 5 | interface Props { 6 | name: string 7 | picture: Exclude | null 8 | } 9 | 10 | export default function Avatar({name, picture}: Props) { 11 | return ( 12 |
13 | {picture?.asset?._ref ? ( 14 |
15 | {picture?.alt 22 |
23 | ) : ( 24 |
By
25 | )} 26 |
{name}
27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /apps/live-next/app/cors.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {isCorsOriginError} from '@sanity/next-loader' 4 | import {toast} from 'sonner' 5 | 6 | export function handleError(error: unknown) { 7 | if (isCorsOriginError(error)) { 8 | const {addOriginUrl} = error 9 | toast.error(`Sanity Live couldn't connect`, { 10 | description: `Your origin is blocked by CORS policy`, 11 | duration: Infinity, 12 | action: addOriginUrl 13 | ? { 14 | label: 'Manage', 15 | onClick: () => window.open(addOriginUrl.toString(), '_blank'), 16 | } 17 | : undefined, 18 | }) 19 | } else if (error instanceof Error) { 20 | console.error(error) 21 | toast.error(error.name, {description: error.message, duration: Infinity}) 22 | } else { 23 | console.error(error) 24 | toast.error('Unknown error', { 25 | description: 'Check the console for more details', 26 | duration: Infinity, 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/live-next/app/cover-image.tsx: -------------------------------------------------------------------------------- 1 | import {urlForImage} from '@/sanity/lib/utils' 2 | import {Image} from 'next-sanity/image' 3 | 4 | interface CoverImageProps { 5 | image: any 6 | priority?: boolean 7 | } 8 | 9 | export default function CoverImage(props: CoverImageProps) { 10 | const {image: source, priority} = props 11 | const image = source?.asset?._ref ? ( 12 | {source?.alt 21 | ) : ( 22 |
23 | ) 24 | 25 | return ( 26 |
27 | {image} 28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /apps/live-next/app/date.tsx: -------------------------------------------------------------------------------- 1 | import {format} from 'date-fns' 2 | 3 | export default function DateComponent({dateString}: {dateString: string}) { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/live-next/app/draft-mode-status.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { 4 | useDraftModeEnvironment, 5 | useDraftModePerspective, 6 | useIsLivePreview, 7 | } from '@sanity/next-loader/hooks' 8 | 9 | export function DraftModeStatus() { 10 | const isLivePreview = useIsLivePreview() 11 | const perspective = useDraftModePerspective() 12 | const environment = useDraftModeEnvironment() 13 | 14 | if (isLivePreview !== true) return null 15 | 16 | return ( 17 |
18 |

perspective: {Array.isArray(perspective) ? perspective.join(',') : perspective}

19 |

environment: {environment}

20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /apps/live-next/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/live-next/app/favicon.ico -------------------------------------------------------------------------------- /apps/live-next/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /apps/live-next/next.config.ts: -------------------------------------------------------------------------------- 1 | import withBundleAnalyzer from '@next/bundle-analyzer' 2 | import type {NextConfig} from 'next' 3 | 4 | const nextConfig: NextConfig = { 5 | logging: { 6 | fetches: { 7 | fullUrl: true, 8 | }, 9 | }, 10 | 11 | images: { 12 | remotePatterns: [{hostname: 'cdn.sanity.io'}, {hostname: 'source.unsplash.com'}], 13 | }, 14 | 15 | experimental: { 16 | reactCompiler: true, 17 | }, 18 | } satisfies NextConfig 19 | 20 | export default withBundleAnalyzer({ 21 | enabled: process.env.ANALYZE === 'true', 22 | })(nextConfig) 23 | -------------------------------------------------------------------------------- /apps/live-next/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/live-next/sanity-typegen.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "./node_modules/@repo/sanity-extracted-schema/live-demo.json" 3 | } 4 | -------------------------------------------------------------------------------- /apps/live-next/sanity/lib/api.ts: -------------------------------------------------------------------------------- 1 | import {workspaces} from '@repo/env' 2 | 3 | export const {projectId, dataset} = workspaces['live-demo'] 4 | 5 | export {apiVersion} from '@repo/env' 6 | -------------------------------------------------------------------------------- /apps/live-next/sanity/lib/live.ts: -------------------------------------------------------------------------------- 1 | import 'server-only' 2 | import {defineLive} from '@sanity/next-loader' 3 | import {client} from './client' 4 | 5 | const serverToken = process.env.SANITY_API_READ_TOKEN 6 | const browserToken = process.env.SANITY_API_BROWSER_TOKEN 7 | 8 | if (!serverToken) { 9 | throw new Error('Missing SANITY_API_READ_TOKEN') 10 | } 11 | if (!browserToken) { 12 | throw new Error('Missing SANITY_API_BROWSER_TOKEN') 13 | } 14 | 15 | export const { 16 | sanityFetch, 17 | SanityLive, 18 | SanityLiveStream, 19 | // verifyPreviewSecret 20 | } = defineLive({ 21 | client, 22 | serverToken, 23 | browserToken, 24 | }) 25 | -------------------------------------------------------------------------------- /apps/live-next/sanity/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import {dataset, projectId} from '@/sanity/lib/api' 2 | import createImageUrlBuilder from '@sanity/image-url' 3 | 4 | const imageBuilder = createImageUrlBuilder({ 5 | projectId: projectId || '', 6 | dataset: dataset || '', 7 | }) 8 | 9 | export const urlForImage = (source: any) => { 10 | // Ensure that source image contains a valid reference 11 | if (!source?.asset?._ref) { 12 | return undefined 13 | } 14 | 15 | return imageBuilder?.image(source).auto('format').fit('max') 16 | } 17 | 18 | export function resolveOpenGraphImage(image: any, width = 1200, height = 627) { 19 | if (!image) return 20 | const url = urlForImage(image)?.width(1200).height(627).fit('crop').url() 21 | if (!url) return 22 | return {url, alt: image?.alt as string, width, height} 23 | } 24 | -------------------------------------------------------------------------------- /apps/live-next/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import typography from '@tailwindcss/typography' 2 | import type {Config} from 'tailwindcss' 3 | 4 | export default { 5 | content: ['./app/**/*.{ts,tsx}', './sanity/**/*.{ts,tsx}'], 6 | theme: { 7 | extend: { 8 | backgroundColor: { 9 | 'theme': 'var(--theme-background,#fff)', 10 | 'theme-inverse': 'var(--theme-text,#fff)', 11 | }, 12 | fontFamily: { 13 | sans: ['var(--font-inter)'], 14 | }, 15 | textColor: { 16 | 'theme': 'var(--theme-text,#000)', 17 | 'theme-inverse': 'var(--theme-background,#fff)', 18 | }, 19 | }, 20 | }, 21 | future: { 22 | hoverOnlyWhenSupported: true, 23 | }, 24 | plugins: [typography], 25 | } satisfies Config 26 | -------------------------------------------------------------------------------- /apps/live-next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "module": "preserve", 11 | "isolatedModules": true, 12 | "jsx": "preserve", 13 | "incremental": true, 14 | "plugins": [ 15 | { 16 | "name": "next" 17 | } 18 | ], 19 | "paths": { 20 | "@/*": ["./*"] 21 | } 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /apps/live-next/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"], 7 | "env": [ 8 | "NEXT_PUBLIC_SANITY_*", 9 | "NEXT_PUBLIC_VERCEL_ENV", 10 | "SANITY_API_READ_TOKEN", 11 | "SANITY_API_WRITE_TOKEN", 12 | "SANITY_API_BROWSER_TOKEN", 13 | "ANALYZE" 14 | ], 15 | "outputs": [".next/**", "!.next/cache/**"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/next-with-i18n/.env.example: -------------------------------------------------------------------------------- 1 | # Created by Vercel CLI 2 | NEXT_PUBLIC_SANITY_DATASET="tinloof" 3 | NEXT_PUBLIC_SANITY_PROJECT_ID="hiomol4a" 4 | SANITY_API_TOKEN="" 5 | -------------------------------------------------------------------------------- /apps/next-with-i18n/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/next-with-i18n/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /studio/node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | /studio/dist 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | .vscode 24 | .idea/ 25 | *.iml 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | .env*.local 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | 42 | # Env files created by scripts for working locally 43 | .env 44 | studio/.env.development 45 | -------------------------------------------------------------------------------- /apps/next-with-i18n/app/(website)/[locale]/[...path]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function DynamicRoute({ 5 | params, 6 | }: { 7 | params: Promise<{ path: string[]; locale: string }> 8 | }) { 9 | const { path, locale } = await params 10 | const pathname = `/${path.join('/')}` 11 | const data = await loadPage(pathname, locale) 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /apps/next-with-i18n/app/(website)/[locale]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Page } from '@/components/Page' 2 | import { loadPage } from '@/data/sanity' 3 | 4 | export default async function IndexRoute({ 5 | params, 6 | }: { 7 | params: Promise<{ locale: string }> 8 | }) { 9 | const { locale } = await params 10 | const data = await loadPage('/', locale) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /apps/next-with-i18n/app/api/disable-draft/route.ts: -------------------------------------------------------------------------------- 1 | import { draftMode } from 'next/headers' 2 | import { NextRequest, NextResponse } from 'next/server' 3 | 4 | export async function GET(request: NextRequest) { 5 | ;(await draftMode()).disable() 6 | const url = new URL(request.nextUrl) 7 | return NextResponse.redirect(new URL('/', url.origin)) 8 | } 9 | -------------------------------------------------------------------------------- /apps/next-with-i18n/app/api/draft/route.ts: -------------------------------------------------------------------------------- 1 | import { validatePreviewUrl } from '@sanity/preview-url-secret' 2 | import { draftMode } from 'next/headers' 3 | import { redirect } from 'next/navigation' 4 | 5 | import config from '@/config' 6 | import { client } from '@/data/sanity/client' 7 | 8 | const clientWithToken = client.withConfig({ token: config.sanity.token }) 9 | 10 | export async function GET(request: Request) { 11 | const { isValid, redirectTo = '/' } = await validatePreviewUrl( 12 | clientWithToken, 13 | request.url, 14 | ) 15 | if (!isValid) { 16 | return new Response('Invalid secret', { status: 401 }) 17 | } 18 | 19 | ;(await draftMode()).enable() 20 | 21 | redirect(redirectTo) 22 | } 23 | -------------------------------------------------------------------------------- /apps/next-with-i18n/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/next-with-i18n/app/favicon.ico -------------------------------------------------------------------------------- /apps/next-with-i18n/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/next-with-i18n/app/icon.png -------------------------------------------------------------------------------- /apps/next-with-i18n/app/studio/[[...index]]/Studio.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { NextStudio } from 'next-sanity/studio' 4 | 5 | import config from '@/sanity.config' 6 | 7 | export default function Studio() { 8 | return 9 | } 10 | -------------------------------------------------------------------------------- /apps/next-with-i18n/app/studio/[[...index]]/page.tsx: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { Metadata } from 'next' 3 | import Studio from './Studio' 4 | 5 | export const dynamic = 'force-static' 6 | 7 | export const metadata: Metadata = { 8 | title: `${config.siteName} - CMS`, 9 | } 10 | 11 | export default function StudioPage() { 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /apps/next-with-i18n/app/studio/layout.tsx: -------------------------------------------------------------------------------- 1 | import 'tailwindcss/tailwind.css' 2 | 3 | export default async function RootLayout({ 4 | children, 5 | }: { 6 | children: React.ReactNode 7 | }) { 8 | return ( 9 | 10 | {children} 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /apps/next-with-i18n/components/StudioLogo.tsx: -------------------------------------------------------------------------------- 1 | export default function StudioLogo() { 2 | return ( 3 | Tinloof Logo 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /apps/next-with-i18n/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | sanity: { 3 | projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || '', 4 | dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || '', 5 | // Not exposed to the front-end, used solely by the server 6 | token: process.env.SANITY_API_TOKEN || '', 7 | apiVersion: process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-06-21', 8 | revalidateSecret: process.env.SANITY_REVALIDATE_SECRET || '', 9 | studioUrl: '/studio', 10 | }, 11 | siteName: 'With i18n', 12 | siteDomain: process.env.NEXT_PUBLIC_SITE_DOMAIN || '', 13 | baseUrl: process.env.NEXT_PUBLIC_BASE_URL || '', 14 | i18n: { 15 | locales: [ 16 | { id: 'en', title: 'English' }, 17 | { id: 'fr', title: 'French' }, 18 | ], 19 | defaultLocaleId: 'en', 20 | }, 21 | } 22 | 23 | export default config 24 | -------------------------------------------------------------------------------- /apps/next-with-i18n/data/sanity/client.ts: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { ClientPerspective, createClient } from '@sanity/client' 3 | 4 | const clientConfig = { 5 | projectId: config.sanity.projectId, 6 | dataset: config.sanity.dataset, 7 | apiVersion: config.sanity.apiVersion, 8 | useCdn: process.env.NODE_ENV === 'production', 9 | perspective: 'published' as ClientPerspective, 10 | } 11 | 12 | export const client = createClient({ 13 | ...clientConfig, 14 | stega: { 15 | studioUrl: config.sanity.studioUrl, 16 | // logger: console, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /apps/next-with-i18n/data/sanity/generateStaticSlugs.ts: -------------------------------------------------------------------------------- 1 | import 'server-only' 2 | 3 | import { groq } from 'next-sanity' 4 | 5 | import config from '@/config' 6 | import { client } from '@/data/sanity/client' 7 | 8 | // Used in `generateStaticParams` 9 | export function generateStaticPaths(types: string[]) { 10 | return client 11 | .withConfig({ 12 | token: config.sanity.token, 13 | perspective: 'published', 14 | useCdn: false, 15 | stega: false, 16 | }) 17 | .fetch( 18 | groq`*[_type in $types && defined(pathname.current)][].pathname.current`, 19 | { types }, 20 | { 21 | next: { 22 | tags: types, 23 | }, 24 | }, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /apps/next-with-i18n/data/sanity/index.ts: -------------------------------------------------------------------------------- 1 | import { PagePayload } from '@/types' 2 | import { loadQuery } from './loadQuery' 3 | import { PAGE_QUERY } from './queries' 4 | 5 | export async function loadPage(pathname: string, locale: string) { 6 | return loadQuery({ 7 | query: PAGE_QUERY, 8 | params: { pathname, locale }, 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /apps/next-with-i18n/data/sanity/queries.ts: -------------------------------------------------------------------------------- 1 | import { groq } from 'next-sanity' 2 | 3 | export const PAGE_QUERY = groq`*[pathname.current == $pathname && locale == $locale][0]` 4 | -------------------------------------------------------------------------------- /apps/next-with-i18n/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/next-with-i18n/postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /apps/next-with-i18n/public/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/next-with-i18n/public/static/logo.png -------------------------------------------------------------------------------- /apps/next-with-i18n/public/static/studio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/next-with-i18n/public/static/studio.png -------------------------------------------------------------------------------- /apps/next-with-i18n/sanity.cli.ts: -------------------------------------------------------------------------------- 1 | import { loadEnvConfig } from '@next/env' 2 | import { defineCliConfig } from 'sanity/cli' 3 | 4 | const dev = process.env.NODE_ENV !== 'production' 5 | loadEnvConfig(__dirname, dev, { info: () => null, error: console.error }) 6 | 7 | // @TODO report top-level await bug 8 | // Using a dynamic import here as `loadEnvConfig` needs to run before this file is loaded 9 | // const { projectId, dataset } = await import('@/lib/sanity.api') 10 | const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID 11 | const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET 12 | 13 | export default defineCliConfig({ api: { projectId, dataset } }) 14 | -------------------------------------------------------------------------------- /apps/next-with-i18n/sanity/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import page from './page' 2 | 3 | const schemas = [page] 4 | 5 | export default schemas 6 | -------------------------------------------------------------------------------- /apps/next-with-i18n/sanity/schemas/page.ts: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | import { definePathname } from '@tinloof/sanity-studio' 3 | import { defineType } from 'sanity' 4 | 5 | export default defineType({ 6 | type: 'document', 7 | name: 'page', 8 | fields: [ 9 | { 10 | type: 'string', 11 | name: 'title', 12 | }, 13 | definePathname({ 14 | name: 'pathname', 15 | options: { 16 | i18n: { 17 | enabled: true, 18 | defaultLocaleId: config.i18n.defaultLocaleId, 19 | }, 20 | }, 21 | }), 22 | { 23 | type: 'string', 24 | name: 'locale', 25 | hidden: true, 26 | }, 27 | { 28 | name: 'body', 29 | title: 'Body', 30 | type: 'array', 31 | of: [{ type: 'block' }], 32 | }, 33 | ], 34 | }) 35 | -------------------------------------------------------------------------------- /apps/next-with-i18n/styles/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | -webkit-font-smoothing: antialiased; 3 | -moz-osx-font-smoothing: grayscale; 4 | overflow-x: hidden; 5 | } 6 | 7 | p:not(:last-child) { 8 | margin-bottom: 0.875rem; 9 | } 10 | 11 | ol, 12 | ul { 13 | margin-left: 1rem; 14 | } 15 | 16 | ol { 17 | list-style-type: disc; 18 | } 19 | -------------------------------------------------------------------------------- /apps/next-with-i18n/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | content: [ 6 | './app/**/*.{js,ts,jsx,tsx}', 7 | './components/**/*.{js,ts,jsx,tsx}', 8 | './intro-template/**/*.{js,ts,jsx,tsx}', 9 | ], 10 | theme: { 11 | ...defaultTheme, 12 | // Overriding fontFamily to use @next/font loaded families 13 | fontFamily: { 14 | mono: 'var(--font-mono)', 15 | sans: 'var(--font-sans)', 16 | serif: 'var(--font-serif)', 17 | }, 18 | }, 19 | plugins: [require('@tailwindcss/typography')], 20 | } 21 | -------------------------------------------------------------------------------- /apps/next-with-i18n/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "strictNullChecks": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "bundler", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "plugins": [ 19 | { 20 | "name": "next" 21 | } 22 | ], 23 | "paths": { 24 | "@/*": ["./*"] 25 | } 26 | }, 27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /apps/next-with-i18n/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"], 7 | "env": ["NEXT_PUBLIC_SANITY_*", "NEXT_PUBLIC_VERCEL_ENV"], 8 | "outputs": [".next/**", "!.next/cache/**"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/next-with-i18n/types/index.ts: -------------------------------------------------------------------------------- 1 | export type PagePayload = { 2 | _id: string 3 | _type: string 4 | pathname: { current: string } 5 | title?: string 6 | body?: any[] 7 | } 8 | -------------------------------------------------------------------------------- /apps/next/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /apps/next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /apps/next/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/next/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/next/sanity-typegen.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "./node_modules/@repo/sanity-extracted-schema/shoes.json", 3 | "generates": "./src/types.ts" 4 | } 5 | -------------------------------------------------------------------------------- /apps/next/src/app/api/draft-mode/disable/route.ts: -------------------------------------------------------------------------------- 1 | import {draftMode} from 'next/headers' 2 | import {NextRequest, NextResponse} from 'next/server' 3 | 4 | export async function GET(request: NextRequest) { 5 | ;(await draftMode()).disable() 6 | const url = new URL(request.nextUrl) 7 | return NextResponse.redirect(new URL('/pages-router/shoes', url.origin)) 8 | } 9 | -------------------------------------------------------------------------------- /apps/next/src/app/api/draft-mode/enable/route.ts: -------------------------------------------------------------------------------- 1 | import {client} from '@/components/sanity.client' 2 | import {defineEnableDraftMode} from 'next-sanity/draft-mode' 3 | 4 | export const {GET} = defineEnableDraftMode({ 5 | client: client.withConfig({ 6 | token: process.env.SANITY_API_READ_TOKEN, 7 | }), 8 | }) 9 | -------------------------------------------------------------------------------- /apps/next/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/next/src/app/favicon.ico -------------------------------------------------------------------------------- /apps/next/src/app/only-visual-editing/page.tsx: -------------------------------------------------------------------------------- 1 | import {shoesList} from '@/queries' 2 | import {draftMode} from 'next/headers' 3 | import {client} from '../shoes/sanity.client' 4 | import ShoesPageClient from './page.client' 5 | 6 | const stegaClient = client.withConfig({ 7 | stega: {enabled: true}, 8 | token: process.env.SANITY_API_READ_TOKEN, 9 | perspective: 'published', 10 | useCdn: false, 11 | }) 12 | 13 | export default async function ShoesPage() { 14 | const {result, resultSourceMap} = await stegaClient.fetch( 15 | shoesList, 16 | {}, 17 | { 18 | filterResponse: false, 19 | perspective: (await draftMode()).isEnabled ? 'drafts' : 'published', 20 | useCdn: (await draftMode()).isEnabled ? false : true, 21 | next: {revalidate: false, tags: ['shoe']}, 22 | }, 23 | ) 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /apps/next/src/app/shoes/VisualEditing.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {useLiveMode} from '@sanity/react-loader' 4 | import {VisualEditing} from 'next-sanity' 5 | import {client} from './sanity.client' 6 | 7 | // Always enable stega in Live Mode 8 | const stegaClient = client.withConfig({stega: true}) 9 | 10 | export default function LiveVisualEditing(props: React.ComponentProps) { 11 | useLiveMode({client: stegaClient}) 12 | 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /apps/next/src/app/shoes/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import {shoe} from '@/queries' 2 | import {sanityFetch} from '../sanity.live' 3 | import ShoePageClient from './page.client' 4 | 5 | type Props = { 6 | params: Promise<{slug: string}> 7 | } 8 | 9 | export default async function ShoePage({params}: Props) { 10 | const {data: initial} = await sanityFetch({query: shoe, params}) 11 | 12 | return 13 | } 14 | -------------------------------------------------------------------------------- /apps/next/src/app/shoes/page.tsx: -------------------------------------------------------------------------------- 1 | import {shoesList} from '@/queries' 2 | import ShoesPageClient from './page.client' 3 | import {sanityFetch} from './sanity.live' 4 | 5 | export default async function ShoesPage() { 6 | const {data} = await sanityFetch({query: shoesList}) 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /apps/next/src/app/shoes/sanity.client.ts: -------------------------------------------------------------------------------- 1 | import {apiVersion, workspaces} from '@repo/env' 2 | import {studioUrl as baseUrl} from '@repo/studio-url' 3 | import {createClient} from '@sanity/client' 4 | 5 | const {projectId, dataset} = workspaces['next-app-router'] 6 | 7 | export const client = createClient({ 8 | projectId, 9 | dataset, 10 | useCdn: false, 11 | apiVersion, 12 | stega: { 13 | studioUrl: (sourceDocument) => { 14 | if ( 15 | sourceDocument._projectId === workspaces['cross-dataset-references'].projectId && 16 | sourceDocument._dataset === workspaces['cross-dataset-references'].dataset 17 | ) { 18 | const {workspace, tool} = workspaces['cross-dataset-references'] 19 | return {baseUrl, workspace, tool} 20 | } 21 | const {workspace, tool} = workspaces['next-app-router'] 22 | return {baseUrl, workspace, tool} 23 | }, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /apps/next/src/app/shoes/sanity.live.ts: -------------------------------------------------------------------------------- 1 | import {defineLive} from '@sanity/next-loader' 2 | import {client} from './sanity.client' 3 | 4 | const token = process.env.SANITY_API_READ_TOKEN 5 | 6 | if (!token) { 7 | throw new Error('Missing SANITY_API_READ_TOKEN') 8 | } 9 | 10 | export const {sanityFetch, SanityLive} = defineLive({ 11 | client, 12 | serverToken: token, 13 | browserToken: token, 14 | }) 15 | -------------------------------------------------------------------------------- /apps/next/src/app/shoes/sanity.ssr.ts: -------------------------------------------------------------------------------- 1 | import {loadQuery as _loadQuery, setServerClient} from '@sanity/react-loader' 2 | import {draftMode} from 'next/headers' 3 | import {client} from './sanity.client' 4 | 5 | const token = process.env.SANITY_API_READ_TOKEN 6 | 7 | if (!token) { 8 | throw new Error('Missing SANITY_API_READ_TOKEN') 9 | } 10 | 11 | setServerClient( 12 | client.withConfig({ 13 | token, 14 | }), 15 | ) 16 | 17 | // Automatically handle draft mode 18 | export const loadQuery = (async (query, params = {}, options = {}) => { 19 | const isDraftMode = (await draftMode()).isEnabled 20 | const perspective = options.perspective || (isDraftMode ? 'drafts' : 'published') 21 | return _loadQuery(query, params, { 22 | ...options, 23 | perspective, 24 | stega: process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview' || perspective !== 'published', 25 | next: {revalidate: isDraftMode ? 0 : 60}, 26 | }) 27 | }) satisfies typeof _loadQuery 28 | -------------------------------------------------------------------------------- /apps/next/src/app/shoes/utils.ts: -------------------------------------------------------------------------------- 1 | import {workspaces} from '@repo/env' 2 | import imageUrlBuilder from '@sanity/image-url' 3 | 4 | const {projectId, dataset} = workspaces['next-app-router'] 5 | 6 | const builder = imageUrlBuilder({projectId, dataset}) 7 | export function urlFor(source: any) { 8 | return builder.image(source).auto('format').fit('max') 9 | } 10 | 11 | const crossDatasetBuilder = imageUrlBuilder({ 12 | projectId: workspaces['cross-dataset-references'].projectId, 13 | dataset: workspaces['cross-dataset-references'].dataset, 14 | }) 15 | export function urlForCrossDatasetReference(source: any) { 16 | return crossDatasetBuilder.image(source).auto('format').fit('max') 17 | } 18 | -------------------------------------------------------------------------------- /apps/next/src/components/VisualEditing.tsx: -------------------------------------------------------------------------------- 1 | import {useLiveMode} from '@sanity/react-loader' 2 | import {VisualEditing} from '@sanity/visual-editing/next-pages-router' 3 | import {client} from './sanity.client' 4 | 5 | // Always enable stega in Live Mode 6 | const stegaClient = client.withConfig({stega: true}) 7 | 8 | export default function LiveVisualEditing() { 9 | useLiveMode({client: stegaClient}) 10 | 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /apps/next/src/components/sanity.client.ts: -------------------------------------------------------------------------------- 1 | import {apiVersion, workspaces} from '@repo/env' 2 | import {studioUrl as baseUrl} from '@repo/studio-url' 3 | import {createClient} from '@sanity/client' 4 | 5 | const {projectId, dataset} = workspaces['next-pages-router'] 6 | 7 | export const client = createClient({ 8 | projectId, 9 | dataset, 10 | useCdn: false, 11 | apiVersion, 12 | stega: { 13 | studioUrl: (sourceDocument) => { 14 | if ( 15 | sourceDocument._projectId === workspaces['cross-dataset-references'].projectId && 16 | sourceDocument._dataset === workspaces['cross-dataset-references'].dataset 17 | ) { 18 | const {workspace, tool} = workspaces['cross-dataset-references'] 19 | return {baseUrl, workspace, tool} 20 | } 21 | const {workspace, tool} = workspaces['next-pages-router'] 22 | return {baseUrl, workspace, tool} 23 | }, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /apps/next/src/components/sanity.ssr.ts: -------------------------------------------------------------------------------- 1 | import * as serverOnly from '@sanity/react-loader' 2 | import {client} from './sanity.client' 3 | 4 | const token = process.env.SANITY_API_READ_TOKEN 5 | 6 | if (!token) { 7 | throw new Error('Missing SANITY_API_READ_TOKEN') 8 | } 9 | 10 | const {loadQuery, setServerClient} = serverOnly 11 | setServerClient( 12 | client.withConfig({ 13 | token, 14 | // Enable stega if it's a Vercel preview deployment, as the Vercel Toolbar has controls that shows overlays 15 | stega: process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview', 16 | }), 17 | ) 18 | 19 | // Exports to be used by getInitialProps, getServerSideProps, getStaticProps 20 | export {loadQuery} 21 | -------------------------------------------------------------------------------- /apps/next/src/components/utils.ts: -------------------------------------------------------------------------------- 1 | import {workspaces} from '@repo/env' 2 | import imageUrlBuilder from '@sanity/image-url' 3 | 4 | const {projectId, dataset} = workspaces['next-pages-router'] 5 | 6 | const builder = imageUrlBuilder({projectId, dataset}) 7 | export function urlFor(source: any) { 8 | return builder.image(source).auto('format').fit('max') 9 | } 10 | 11 | const crossDatasetBuilder = imageUrlBuilder({ 12 | projectId: workspaces['cross-dataset-references'].projectId, 13 | dataset: workspaces['cross-dataset-references'].dataset, 14 | }) 15 | export function urlForCrossDatasetReference(source: any) { 16 | return crossDatasetBuilder.image(source).auto('format').fit('max') 17 | } 18 | -------------------------------------------------------------------------------- /apps/next/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type {AppProps} from 'next/app' 2 | import dynamic from 'next/dynamic' 3 | import '../tailwind.css' 4 | 5 | const VisualEditing = dynamic(() => import('../components/VisualEditing')) 6 | 7 | export interface SharedProps { 8 | draftMode: boolean 9 | } 10 | 11 | export default function App({Component, pageProps}: AppProps) { 12 | const {draftMode} = pageProps 13 | return ( 14 | <> 15 | 16 | {draftMode && } 17 | 22 | pages-router:{' '} 23 | {draftMode ? 'draftMode' : process.env.NEXT_PUBLIC_VERCEL_ENV || 'development'} 24 | 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /apps/next/src/queries.ts: -------------------------------------------------------------------------------- 1 | import {defineQuery} from 'next-sanity' 2 | 3 | export const shoesList = defineQuery(`*[_type == "shoe" && defined(slug.current)]{ 4 | title, 5 | slug, 6 | "price": string(price), 7 | "media": media[0]{ alt, asset, crop, hotspot }, 8 | "brand": brandReference->{name, slug, logo{ alt, asset, crop, hotspot }}, 9 | } | order(_updatedAt desc) `) 10 | 11 | export const shoe = defineQuery(`*[_type == "shoe" && slug.current == $slug]{ 12 | title, 13 | slug, 14 | "price": string(price), 15 | "media": media[]{ alt, asset, crop, hotspot }, 16 | "brand": brandReference->{name, slug, logo{ alt, asset, crop, hotspot }}, 17 | description, 18 | }[0]`) 19 | -------------------------------------------------------------------------------- /apps/next/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /apps/next/src/utils.ts: -------------------------------------------------------------------------------- 1 | import {vercelStegaSplit} from '@vercel/stega' 2 | 3 | export function formatCurrency(_value: number | string): string { 4 | let value = typeof _value === 'string' ? undefined : _value 5 | let encoded = '' 6 | if (typeof _value === 'string') { 7 | const split = vercelStegaSplit(_value) 8 | value = parseInt(split.cleaned, 10) 9 | encoded = split.encoded 10 | } 11 | const formatter = new Intl.NumberFormat('en', { 12 | style: 'currency', 13 | currency: 'USD', 14 | minimumFractionDigits: 0, 15 | maximumFractionDigits: 0, 16 | }) 17 | return `${formatter.format(value!)}${encoded}` 18 | } 19 | -------------------------------------------------------------------------------- /apps/next/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type {Config} from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } 19 | export default config 20 | -------------------------------------------------------------------------------- /apps/next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "rootDir": "../..", 22 | "paths": { 23 | "@/*": ["./src/*"], 24 | } 25 | }, 26 | "include": [ 27 | "next-env.d.ts", 28 | "**/*.ts", 29 | "**/*.tsx", 30 | ".next/types/**/*.ts" 31 | ], 32 | "exclude": ["node_modules"] 33 | } 34 | -------------------------------------------------------------------------------- /apps/next/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"], 7 | "env": ["NEXT_PUBLIC_SANITY_*", "NEXT_PUBLIC_VERCEL_ENV", "SANITY_API_READ_TOKEN"], 8 | "outputs": [".next/**", "!.next/cache/**"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | .vercel 26 | .env*.local 27 | v8* 28 | -------------------------------------------------------------------------------- /apps/nuxt/app.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /apps/nuxt/assets/css/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body.nextStyle { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) 22 | rgb(var(--background-start-rgb)); 23 | } 24 | -------------------------------------------------------------------------------- /apps/nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apps-nuxt", 3 | "private": true, 4 | "scripts": { 5 | "build": "nuxt build", 6 | "predev": "nuxt prepare", 7 | "dev": "nuxt dev -p 3003 --dotenv .env.local", 8 | "generate": "nuxt generate", 9 | "preview": "nuxt preview" 10 | }, 11 | "prettier": "@repo/prettier-config", 12 | "dependencies": { 13 | "@nuxtjs/sanity": "^1.13.3", 14 | "@nuxtjs/tailwindcss": "^6.14.0", 15 | "@repo/env": "workspace:*", 16 | "@repo/studio-url": "workspace:*", 17 | "@sanity/client": "^7.3.0", 18 | "@sanity/comlink": "workspace:^", 19 | "@sanity/presentation-comlink": "workspace:^", 20 | "@sanity/preview-url-secret": "workspace:*", 21 | "@sanity/visual-editing": "workspace:*", 22 | "@sanity/visual-editing-csm": "workspace:*", 23 | "sanity": "3.90.0" 24 | }, 25 | "devDependencies": { 26 | "@nuxt/devtools": "^2.4.1", 27 | "@repo/prettier-config": "workspace:*", 28 | "nuxt": "^3.17.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /apps/nuxt/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/nuxt/public/favicon.ico -------------------------------------------------------------------------------- /apps/nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json", 4 | "compilerOptions": { 5 | "moduleResolution": "bundler" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/nuxt/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "dependsOn": ["@sanity/core-loader#build", "^build"], 7 | "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"], 8 | "env": ["NUXT_ENV_VERCEL_ENV"] 9 | }, 10 | "generate": { 11 | "outputs": ["dist/**"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/page-builder-demo/.env.example: -------------------------------------------------------------------------------- 1 | # Needs a viewer token to fetch drafts 2 | # SANITY_API_READ_TOKEN="sk..." 3 | # Needs a browser token to support live previewing drafts outside of Presentation Tool 4 | # SANITY_API_BROWSER_TOKEN="sk..." 5 | -------------------------------------------------------------------------------- /apps/page-builder-demo/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "root": true, 4 | "plugins": ["react-compiler"], 5 | "rules": { 6 | "react-compiler/react-compiler": "error" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/page-builder-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /apps/page-builder-demo/next.config.ts: -------------------------------------------------------------------------------- 1 | import withBundleAnalyzer from '@next/bundle-analyzer' 2 | import type {NextConfig} from 'next' 3 | 4 | const nextConfig: NextConfig = { 5 | experimental: { 6 | reactCompiler: true, 7 | }, 8 | compiler: { 9 | styledComponents: { 10 | displayName: true, 11 | }, 12 | }, 13 | logging: { 14 | fetches: { 15 | fullUrl: true, 16 | }, 17 | }, 18 | } 19 | 20 | export default withBundleAnalyzer({ 21 | enabled: process.env.ANALYZE === 'true', 22 | })( 23 | // @ts-ignore - typings need to be fixed upstream 24 | nextConfig, 25 | ) 26 | -------------------------------------------------------------------------------- /apps/page-builder-demo/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /apps/page-builder-demo/public/scene.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/page-builder-demo/public/scene.bin -------------------------------------------------------------------------------- /apps/page-builder-demo/sanity-typegen.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "./node_modules/@repo/sanity-extracted-schema/page-builder-demo.json", 3 | "generates": "./src/sanity.types.ts" 4 | } 5 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/app/actions.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import {draftMode} from 'next/headers' 4 | 5 | export async function disableDraftMode() { 6 | 'use server' 7 | await Promise.allSettled([ 8 | (await draftMode()).disable(), 9 | // Simulate a delay to show the loading state 10 | new Promise((resolve) => setTimeout(resolve, 1000)), 11 | ]) 12 | } 13 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/app/api/draft-mode/enable/route.ts: -------------------------------------------------------------------------------- 1 | import {client} from '@/sanity/client' 2 | import {defineEnableDraftMode} from 'next-sanity/draft-mode' 3 | 4 | export const {GET} = defineEnableDraftMode({ 5 | client: client.withConfig({ 6 | token: process.env.SANITY_API_READ_TOKEN, 7 | }), 8 | }) 9 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body, 7 | #root { 8 | height: 100%; 9 | } 10 | 11 | body { 12 | -webkit-font-smoothing: antialiased; 13 | } 14 | 15 | .icon { 16 | display: block; 17 | font-size: calc(24 / 16 * 1rem); 18 | } 19 | 20 | .leading-none .icon { 21 | margin: -4px; 22 | } 23 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import {Page, sectionFragment} from '@/components/page' 2 | import {sanityFetch} from '@/sanity/live' 3 | import {defineQuery} from 'next-sanity' 4 | 5 | const frontPageQuery = defineQuery(` 6 | *[_id == "siteSettings"][0]{ 7 | frontPage->{ 8 | _type, 9 | _id, 10 | title, 11 | ${sectionFragment}, 12 | style 13 | } 14 | }.frontPage 15 | `) 16 | 17 | export default async function IndexPage() { 18 | const {data} = await sanityFetch({query: frontPageQuery}) 19 | 20 | return 21 | } 22 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/app/pages/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import {Page, sectionFragment} from '@/components/page' 2 | import {sanityFetch} from '@/sanity/live' 3 | import {defineQuery} from 'next-sanity' 4 | 5 | const pageQuery = defineQuery(` 6 | *[_type == "page" && slug.current == $slug][0]{ 7 | _type, 8 | _id, 9 | title, 10 | ${sectionFragment}, 11 | style 12 | } 13 | `) 14 | 15 | const pageSlugs = defineQuery(`*[_type == "page" && defined(slug.current)]{"slug": slug.current}`) 16 | export async function generateStaticParams() { 17 | const {data} = await sanityFetch({ 18 | query: pageSlugs, 19 | perspective: 'published', 20 | stega: false, 21 | }) 22 | return data 23 | } 24 | 25 | export default async function PagesPage({params}: {params: Promise<{slug: string}>}) { 26 | const {data} = await sanityFetch({query: pageQuery, params}) 27 | 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/image/Image.tsx: -------------------------------------------------------------------------------- 1 | import {imageUrlBuilder} from '@/sanity/image' 2 | import {Image as NextImage} from 'next-sanity/image' 3 | import {HTMLProps} from 'react' 4 | 5 | export function Image( 6 | props: { 7 | value: { 8 | _type: 'image' 9 | asset?: {_type: 'reference'; _ref?: string} 10 | } 11 | width?: number 12 | height?: number 13 | } & Omit, 'src' | 'value' | 'width' | 'height'>, 14 | ) { 15 | const {value, width = 800, height = 800, ...rest} = props 16 | 17 | if (!value?.asset?._ref) { 18 | return null 19 | } 20 | 21 | return ( 22 | // @ts-expect-error - NextImage is not typed correctly 23 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/image/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Image' 2 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/overlay-plugins/example-exclusive.tsx: -------------------------------------------------------------------------------- 1 | import {OverlayPluginDefinition} from '@sanity/visual-editing/react' 2 | 3 | export const ExampleExclusivePlugin: OverlayPluginDefinition = { 4 | type: 'exclusive', 5 | name: 'example-exclusive', 6 | title: 'Example Exclusive', 7 | component: function ExampleExclusiveComponent({closeExclusiveView}) { 8 | return ( 9 |
20 | Example Exclusive 21 | 22 |
23 | ) 24 | }, 25 | guard: () => { 26 | return true 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/overlay-plugins/example-hud.tsx: -------------------------------------------------------------------------------- 1 | import {OverlayPluginDefinition} from '@sanity/visual-editing/react' 2 | 3 | export const ExampleHUDPlugin: OverlayPluginDefinition = { 4 | type: 'hud', 5 | name: 'example-hud', 6 | title: 'Example HUD', 7 | component: function ExampleHUDComponent() { 8 | return ( 9 |
18 | Example HUD 19 |
20 | ) 21 | }, 22 | guard: () => { 23 | return true 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/overlay-plugins/index.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {OverlayPluginDefinition} from '@sanity/visual-editing/react' 4 | import {ExcitingTitlePlugin} from './exciting-title' 5 | import {ImageResolutionHUD} from './image-res' 6 | import {LEDLifespanHUD} from './led-lifespan' 7 | import {Rotate3D} from './rotate-3d' 8 | 9 | export const plugins: OverlayPluginDefinition[] = [ 10 | LEDLifespanHUD(), 11 | ImageResolutionHUD(), 12 | Rotate3D({guard: ({node}) => node.path.endsWith('rotations')}), 13 | ExcitingTitlePlugin({ 14 | guard: ({node}) => node.path.endsWith('title'), 15 | options: {append: '?', buttonText: '🤩'}, 16 | }), 17 | ExcitingTitlePlugin({ 18 | guard: ({node}) => node.path.endsWith('headline'), 19 | }), 20 | ] 21 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/overlays/OverlayHighlight.tsx: -------------------------------------------------------------------------------- 1 | import {useSharedState} from '@sanity/visual-editing' 2 | import {FunctionComponent} from 'react' 3 | 4 | export const OverlayHighlight: FunctionComponent = () => { 5 | const overlayEnabled = useSharedState('overlay-enabled') 6 | 7 | if (!overlayEnabled) { 8 | return null 9 | } 10 | 11 | return ( 12 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/page/PageSection.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import {HTMLProps} from 'react' 3 | 4 | const variants: Record<'default' | 'inverted', string> = { 5 | default: 'bg-white text-black dark:bg-black dark:text-white', 6 | inverted: 'bg-[#364c35] text-white dark:bg-[#b5cbb4] dark:text-black', 7 | } 8 | 9 | export function PageSection(props: {variant?: 'default' | 'inverted'} & HTMLProps) { 10 | const {children, className, variant = 'default', ...restProps} = props 11 | 12 | return ( 13 |
14 | {children} 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/page/SimpleContent.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | PortableText, 3 | PortableTextBlockComponent, 4 | PortableTextComponents, 5 | PortableTextTypeComponent, 6 | } from '@portabletext/react' 7 | 8 | export function SimpleContent(props: {value: any[]}) { 9 | const {value} = props 10 | 11 | return 12 | } 13 | 14 | const Block: PortableTextBlockComponent = (props) => { 15 | const {children} = props 16 | 17 | return

{children}

18 | } 19 | 20 | const Span: PortableTextTypeComponent = (props) => { 21 | return props.value.text.value 22 | // return {props.value.text} 23 | } 24 | 25 | const components: Partial = { 26 | block: Block, 27 | types: { 28 | span: Span, 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/page/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Page' 2 | export * from './types' 3 | export * from './SimpleContent' 4 | export * from './sectionFragment' 5 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/page/types.ts: -------------------------------------------------------------------------------- 1 | import type {FrontPageQueryResult} from '@/sanity.types' 2 | 3 | export type PageSection = NonNullable['sections']>[number] 4 | 5 | // @TODO can we be rid of these? 6 | export interface SectionStyleData { 7 | variant?: 'default' | 'inverted' 8 | } 9 | 10 | export type HeroSectionData = Extract 11 | 12 | export type IntroSectionData = Extract 13 | 14 | export type FeaturedProductsSectionData = Extract 15 | 16 | export type FeatureHighlightSectionData = Extract 17 | 18 | export type PageSectionData = Extract 19 | 20 | export type {Page as PageData} from '@/sanity.types' 21 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/slideshow/Slideshow.css: -------------------------------------------------------------------------------- 1 | .slideshow { 2 | } 3 | 4 | .slideshow__image:not([data-current]) { 5 | display: none; 6 | } 7 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/components/slideshow/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Slideshow' 2 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/sanity/client.ts: -------------------------------------------------------------------------------- 1 | import {apiVersion, workspaces} from '@repo/env' 2 | import {studioUrl as baseUrl} from '@repo/studio-url' 3 | import {createClient} from '@sanity/client' 4 | 5 | const {projectId, dataset, workspace, tool} = workspaces['page-builder-demo'] 6 | 7 | export const client = createClient({ 8 | projectId, 9 | dataset, 10 | useCdn: true, 11 | apiVersion, 12 | perspective: 'published', 13 | stega: { 14 | studioUrl: () => ({baseUrl, workspace, tool}), 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/sanity/dataAttribute.ts: -------------------------------------------------------------------------------- 1 | import {workspaces} from '@repo/env' 2 | import {studioUrl as baseUrl} from '@repo/studio-url' 3 | import {createDataAttribute} from 'next-sanity' 4 | 5 | const {projectId, dataset, workspace, tool} = workspaces['page-builder-demo'] 6 | 7 | export function dataAttribute( 8 | node: Omit< 9 | Parameters[0], 10 | 'baseUrl' | 'workspace' | 'tool' | 'projectId' | 'dataset' 11 | >, 12 | ) { 13 | return createDataAttribute({ 14 | baseUrl, 15 | workspace, 16 | tool, 17 | projectId, 18 | dataset, 19 | ...node, 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/sanity/image.ts: -------------------------------------------------------------------------------- 1 | import {workspaces} from '@repo/env' 2 | import createImageUrlBuilder from '@sanity/image-url' 3 | 4 | const {projectId, dataset} = workspaces['page-builder-demo'] 5 | 6 | export const imageUrlBuilder = createImageUrlBuilder({projectId, dataset}) 7 | 8 | export function urlFor(source: any) { 9 | return imageUrlBuilder.image(source).auto('format').fit('max') 10 | } 11 | 12 | export const crossDatasetImageUrlBuilder = createImageUrlBuilder({ 13 | projectId: workspaces['cross-dataset-references'].projectId, 14 | dataset: workspaces['cross-dataset-references'].dataset, 15 | }) 16 | 17 | export function urlForCrossDatasetImage(source: any) { 18 | return crossDatasetImageUrlBuilder.image(source).auto('format').fit('max') 19 | } 20 | -------------------------------------------------------------------------------- /apps/page-builder-demo/src/sanity/live.ts: -------------------------------------------------------------------------------- 1 | import 'server-only' 2 | import {defineLive} from 'next-sanity' 3 | import {client} from './client' 4 | 5 | const serverToken = process.env.SANITY_API_READ_TOKEN 6 | const browserToken = process.env.SANITY_API_BROWSER_TOKEN 7 | 8 | if (!serverToken) { 9 | throw new Error('Missing SANITY_API_READ_TOKEN') 10 | } 11 | if (!browserToken) { 12 | throw new Error('Missing SANITY_API_BROWSER_TOKEN') 13 | } 14 | 15 | export const {sanityFetch, SanityLive} = defineLive({ 16 | client, 17 | serverToken, 18 | browserToken, 19 | }) 20 | -------------------------------------------------------------------------------- /apps/page-builder-demo/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const {theme} = require('@sanity/demo/tailwind') 4 | 5 | /** @type {import('tailwindcss').Config} */ 6 | module.exports = { 7 | content: ['./src/**/*.{ts,tsx}'], 8 | theme: { 9 | ...theme, 10 | fontFamily: { 11 | mono: 'var(--font-mono)', 12 | sans: 'var(--font-sans)', 13 | serif: 'var(--font-serif)', 14 | }, 15 | }, 16 | plugins: [require('@tailwindcss/typography')], 17 | } 18 | -------------------------------------------------------------------------------- /apps/page-builder-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "rootDir": "../..", 22 | "paths": { 23 | "@/*": ["./src/*"], 24 | } 25 | }, 26 | "include": [ 27 | "next-env.d.ts", 28 | "**/*.ts", 29 | "**/*.tsx", 30 | ".next/types/**/*.ts" 31 | ], 32 | "exclude": ["node_modules"] 33 | } 34 | -------------------------------------------------------------------------------- /apps/page-builder-demo/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"], 7 | "env": [ 8 | "NEXT_PUBLIC_SANITY_*", 9 | "NEXT_PUBLIC_VERCEL_ENV", 10 | "SANITY_API_READ_TOKEN", 11 | "SANITY_API_BROWSER_TOKEN" 12 | ], 13 | "outputs": [".next/**", "!.next/cache/**"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/remix/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint').Linter.Config} */ 2 | module.exports = { 3 | extends: ['@remix-run/eslint-config', '@remix-run/eslint-config/node'], 4 | } 5 | -------------------------------------------------------------------------------- /apps/remix/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /.output 5 | /.vercel 6 | /api/index.js 7 | /api/index.js.map 8 | /api/metafile.* 9 | /api/version.txt 10 | /build 11 | /public/build 12 | .env 13 | .vercel 14 | .env*.local 15 | -------------------------------------------------------------------------------- /apps/remix/app/CustomControlsComponent.tsx: -------------------------------------------------------------------------------- 1 | import {at, set} from '@sanity/mutate' 2 | import {useDocuments, type OverlayComponent} from '@sanity/visual-editing' 3 | 4 | export const CustomControlsComponent: OverlayComponent = (props) => { 5 | const {PointerEvents, node} = props 6 | 7 | const {getDocument} = useDocuments() 8 | 9 | const onChange = () => { 10 | const doc = getDocument<{ 11 | title: string 12 | }>(node.id) 13 | 14 | doc.patch(async ({getSnapshot}) => { 15 | const snapshot = await getSnapshot() 16 | const newValue = `${snapshot?.title}!` 17 | return [at(node.path, set(newValue))] 18 | }) 19 | } 20 | 21 | return ( 22 | 23 | 29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /apps/remix/app/routes/_index.tsx: -------------------------------------------------------------------------------- 1 | import {Link} from '@remix-run/react' 2 | 3 | export default function Index() { 4 | return ( 5 |
6 |
7 | Shoes 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /apps/remix/app/routes/api.preview-mode.disable.ts: -------------------------------------------------------------------------------- 1 | import type {LoaderFunctionArgs} from '@vercel/remix' 2 | import {destroySession, getSession} from '~/sessions' 3 | 4 | export async function loader({request}: LoaderFunctionArgs) { 5 | const session = await getSession(request.headers.get('Cookie')) 6 | 7 | return new Response(null, { 8 | status: 307, 9 | headers: { 10 | 'Location': '/', 11 | 'Set-Cookie': await destroySession(session), 12 | }, 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /apps/remix/app/sanity.loader.server.ts: -------------------------------------------------------------------------------- 1 | import {loadQuery as server__loadQuery, setServerClient} from '@sanity/react-loader' 2 | import {client} from './sanity' 3 | 4 | setServerClient(client) 5 | 6 | export {server__loadQuery as loadQuery} 7 | -------------------------------------------------------------------------------- /apps/remix/app/sessions.ts: -------------------------------------------------------------------------------- 1 | import {ClientPerspective, validateApiPerspective} from '@sanity/client' 2 | import {createCookieSessionStorage, type Session} from '@vercel/remix' 3 | 4 | const {getSession, commitSession, destroySession} = createCookieSessionStorage({ 5 | cookie: { 6 | name: '__session', 7 | httpOnly: true, 8 | sameSite: 'none', 9 | secure: true, 10 | secrets: [process.env.NODE_ENV, process.env.VERCEL_GIT_COMMIT_SHA as string], 11 | }, 12 | }) 13 | 14 | export {getSession, commitSession, destroySession} 15 | 16 | export function getPerspective(session: Session): ClientPerspective { 17 | if (!session.has('perspective')) { 18 | return 'published' 19 | } 20 | const perspective = session.get('perspective').split(',') 21 | validateApiPerspective(perspective) 22 | return perspective 23 | } 24 | -------------------------------------------------------------------------------- /apps/remix/app/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /apps/remix/app/utils.ts: -------------------------------------------------------------------------------- 1 | import {vercelStegaSplit} from '@vercel/stega' 2 | 3 | export function formatCurrency(_value: number | string): string { 4 | let value = typeof _value === 'string' ? undefined : _value 5 | let encoded = '' 6 | if (typeof _value === 'string') { 7 | const split = vercelStegaSplit(_value) 8 | value = parseInt(split.cleaned, 10) 9 | encoded = split.encoded 10 | } 11 | const formatter = new Intl.NumberFormat('en', { 12 | style: 'currency', 13 | currency: 'USD', 14 | minimumFractionDigits: 0, 15 | maximumFractionDigits: 0, 16 | }) 17 | return `${formatter.format(value!)}${encoded}` 18 | } 19 | -------------------------------------------------------------------------------- /apps/remix/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /apps/remix/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /apps/remix/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/remix/public/favicon.ico -------------------------------------------------------------------------------- /apps/remix/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type {Config} from 'tailwindcss' 2 | 3 | export default { 4 | content: ['./app/**/*.{js,jsx,ts,tsx}'], 5 | theme: { 6 | extend: {}, 7 | }, 8 | plugins: [], 9 | } satisfies Config 10 | -------------------------------------------------------------------------------- /apps/remix/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "env.d.ts", 4 | "sanity.config.ts", 5 | "tailwind.config.ts", 6 | "**/*.ts", 7 | "**/*.tsx" 8 | ], 9 | "compilerOptions": { 10 | "skipLibCheck": true, 11 | "lib": ["DOM", "DOM.Iterable", "ES2020"], 12 | "isolatedModules": true, 13 | "esModuleInterop": true, 14 | "jsx": "react-jsx", 15 | "moduleResolution": "bundler", 16 | "resolveJsonModule": true, 17 | "target": "ES2020", 18 | "module": "ES2020", 19 | "strict": true, 20 | "allowJs": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "baseUrl": ".", 23 | "paths": { 24 | "~/*": ["./app/*"] 25 | }, 26 | 27 | // Remix takes care of building everything in `remix build`. 28 | "noEmit": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /apps/remix/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"], 7 | "env": ["NODE_ENV", "VERCEL_ENV", "VERCEL_GIT_COMMIT_SHA", "SANITY_API_READ_TOKEN"], 8 | "outputs": [".output/**", "api/**", "build/**", "public/build/**"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/remix/vite.config.ts: -------------------------------------------------------------------------------- 1 | import {vitePlugin as remix} from '@remix-run/dev' 2 | import {installGlobals} from '@remix-run/node' 3 | import {vercelPreset} from '@vercel/remix/vite' 4 | import {defineConfig} from 'vite' 5 | import tsconfigPaths from 'vite-tsconfig-paths' 6 | 7 | installGlobals() 8 | 9 | export default defineConfig({ 10 | server: { 11 | port: 3000, 12 | }, 13 | plugins: [ 14 | remix({ 15 | presets: [vercelPreset()], 16 | }), 17 | tsconfigPaths(), 18 | ], 19 | }) 20 | -------------------------------------------------------------------------------- /apps/studio/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | .sanity 3 | public 4 | -------------------------------------------------------------------------------- /apps/studio/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "types": ["vite/client"], 6 | "noPropertyAccessFromIndexSignature": false 7 | }, 8 | "include": ["**/*.ts", "**/*.tsx"], 9 | "exclude": ["node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/studio/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://openapi.vercel.sh/vercel.json", 3 | "headers": [ 4 | { 5 | "source": "/(.*)", 6 | "headers": [ 7 | { 8 | "key": "Cache-Control", 9 | "value": "public, max-age=5" 10 | } 11 | ] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /apps/svelte/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /apps/svelte/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:svelte/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | plugins: ['@typescript-eslint'], 10 | parserOptions: { 11 | sourceType: 'module', 12 | ecmaVersion: 2020, 13 | extraFileExtensions: ['.svelte'], 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true, 19 | }, 20 | overrides: [ 21 | { 22 | files: ['*.svelte'], 23 | parser: 'svelte-eslint-parser', 24 | parserOptions: { 25 | parser: '@typescript-eslint/parser', 26 | }, 27 | }, 28 | ], 29 | } 30 | -------------------------------------------------------------------------------- /apps/svelte/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | .vercel 10 | .output 11 | vite.config.js.timestamp-* 12 | vite.config.ts.timestamp-* 13 | .env*.local 14 | -------------------------------------------------------------------------------- /apps/svelte/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | -------------------------------------------------------------------------------- /apps/svelte/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/svelte/src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /apps/svelte/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | import type {LoaderLocals} from '@sanity/svelte-loader' 3 | 4 | // for information about these interfaces 5 | declare global { 6 | namespace App { 7 | // interface Error {} 8 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type 9 | interface Locals extends LoaderLocals {} 10 | // interface PageData {} 11 | // interface Platform {} 12 | } 13 | } 14 | 15 | export {} 16 | -------------------------------------------------------------------------------- /apps/svelte/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/svelte/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import {createRequestHandler, setServerClient} from '@sanity/svelte-loader' 2 | import {redirect} from '@sveltejs/kit' 3 | import {serverClient} from '$lib/server/sanity' 4 | 5 | setServerClient(serverClient) 6 | 7 | export const handle = createRequestHandler({preview: {redirect}}) 8 | -------------------------------------------------------------------------------- /apps/svelte/src/lib/images/svelte-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/svelte/src/lib/images/svelte-welcome.png -------------------------------------------------------------------------------- /apps/svelte/src/lib/images/svelte-welcome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/svelte/src/lib/images/svelte-welcome.webp -------------------------------------------------------------------------------- /apps/svelte/src/lib/server/sanity.ts: -------------------------------------------------------------------------------- 1 | import {SANITY_API_READ_TOKEN} from '$env/static/private' 2 | import {client} from '$lib/sanity' 3 | 4 | export const serverClient = client.withConfig({ 5 | token: SANITY_API_READ_TOKEN, 6 | }) 7 | -------------------------------------------------------------------------------- /apps/svelte/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import {vercelStegaSplit} from '@vercel/stega' 2 | 3 | export function formatCurrency(_value: number | string): string { 4 | let value = typeof _value === 'string' ? undefined : _value 5 | let encoded = '' 6 | if (typeof _value === 'string') { 7 | const split = vercelStegaSplit(_value) 8 | value = parseInt(split.cleaned, 10) 9 | encoded = split.encoded 10 | } 11 | const formatter = new Intl.NumberFormat('en', { 12 | style: 'currency', 13 | currency: 'USD', 14 | minimumFractionDigits: 0, 15 | maximumFractionDigits: 0, 16 | }) 17 | return `${formatter.format(value!)}${encoded}` 18 | } 19 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type {LayoutServerLoad} from './$types' 2 | 3 | export const load: LayoutServerLoad = ({locals: {preview}}) => { 4 | return {preview} 5 | } 6 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | import {setPreviewing} from '@sanity/visual-editing/svelte' 2 | import type {LayoutLoad} from './$types' 3 | 4 | export const load: LayoutLoad = ({data}) => { 5 | const {preview} = data 6 | setPreviewing(preview) 7 | return data 8 | } 9 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Home 6 | 7 | 8 | 9 |
10 |

Choose a Svelte application

11 | 21 |
22 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/+page.ts: -------------------------------------------------------------------------------- 1 | // since there's no dynamic data here, we can prerender 2 | // it so that it gets served as a static asset in production 3 | export const prerender = true 4 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes-with-loaders/+layout.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | {#if $isPreviewing} 9 | 13 | Preview Enabled (Shoes with Loaders) 14 | 15 | 16 | {/if} 17 | 18 | 19 | 20 | {#if $isPreviewing} 21 | 22 | 23 | {/if} 24 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes-with-loaders/+page.server.ts: -------------------------------------------------------------------------------- 1 | import {shoesList as query, type ShoesListResult} from '$lib/queries' 2 | import type {PageServerLoad} from './$types' 3 | 4 | export const load: PageServerLoad = async ({locals: {loadQuery}}) => ({ 5 | query, 6 | params: {}, 7 | options: {initial: await loadQuery(query)}, 8 | }) 9 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes-with-loaders/+page.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes-with-loaders/[slug]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import {shoe as query, type ShoeResult} from '$lib/queries' 2 | import type {PageServerLoad} from './$types' 3 | 4 | export const load: PageServerLoad = async ({locals: {loadQuery}, params: {slug}}) => { 5 | const params = {slug} 6 | return { 7 | query, 8 | params, 9 | options: {initial: await loadQuery(query, params)}, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes-with-loaders/[slug]/+page.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | {#if $isPreviewing} 7 | 11 | Preview Enabled (Shoes) 12 | 13 | 14 | 15 | {/if} 16 | 17 | 18 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes/+page.server.ts: -------------------------------------------------------------------------------- 1 | import {shoesList, type ShoesListResult} from '$lib/queries' 2 | import type {PageServerLoad} from './$types' 3 | 4 | export const load: PageServerLoad = async ({locals: {client, preview}}) => { 5 | const products = await client.fetch( 6 | shoesList, 7 | {}, 8 | {stega: preview ? true : false}, 9 | ) 10 | return {products} 11 | } 12 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes/+page.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes/[slug]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import {shoe, type ShoeResult} from '$lib/queries' 2 | import type {PageServerLoad} from './$types' 3 | 4 | export const load: PageServerLoad = async ({locals: {client, preview}, params: {slug}}) => { 5 | const params = {slug} 6 | const product = await client.fetch(shoe, params, {stega: preview ? true : false}) 7 | 8 | return {product, params} 9 | } 10 | -------------------------------------------------------------------------------- /apps/svelte/src/routes/shoes/[slug]/+page.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/svelte/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/apps/svelte/static/favicon.png -------------------------------------------------------------------------------- /apps/svelte/static/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /apps/svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto' 2 | import {vitePreprocess} from '@sveltejs/vite-plugin-svelte' 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter(), 15 | }, 16 | } 17 | 18 | export default config 19 | -------------------------------------------------------------------------------- /apps/svelte/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./src/**/*.{html,js,svelte,ts}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /apps/svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // 16 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 17 | // from the referenced tsconfig.json - TypeScript does not merge them in 18 | } 19 | -------------------------------------------------------------------------------- /apps/svelte/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"], 7 | "env": ["VERCEL_ENV", "SANITY_API_READ_TOKEN"], 8 | "outputs": [".svelte-kit/**", ".vercel/**"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/svelte/vite.config.ts: -------------------------------------------------------------------------------- 1 | import {sveltekit} from '@sveltejs/kit/vite' 2 | import {defineConfig} from 'vite' 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | }) 7 | -------------------------------------------------------------------------------- /knip.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignoreWorkspaces": ["apps/**"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/@repo/env/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/env", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Holds the Sanity API config used by all the example apps", 6 | "exports": { 7 | ".": "./index.ts" 8 | }, 9 | "scripts": { 10 | "test": "tsc --noEmit" 11 | }, 12 | "devDependencies": { 13 | "typescript": "5.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/@repo/env/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/@repo/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/eslint-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "@typescript-eslint/eslint-plugin": "^8.32.0", 7 | "@typescript-eslint/parser": "^8.32.0", 8 | "eslint-config-prettier": "^10.1.3", 9 | "eslint-config-turbo": "2.5.3", 10 | "eslint-plugin-react": "^7.37.5", 11 | "eslint-plugin-react-compiler": "19.1.0-rc.2", 12 | "eslint-plugin-react-hooks": "0.0.0-experimental-f9ae0a4c-20250527" 13 | }, 14 | "devDependencies": { 15 | "@types/eslint": "^8.56.12", 16 | "eslint": "^8.57.1", 17 | "typescript": "5.8.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/@repo/package.config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@repo/package.config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Shared @sanity/pkg-utils configuration", 6 | "main": "./src/package.config.ts", 7 | "types": "./src/package.config.ts", 8 | "scripts": { 9 | "check:types": "tsc --noEmit --skipLibCheck src/package.config.ts" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/@repo/package.config/src/package.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@sanity/pkg-utils' 2 | 3 | export default defineConfig({ 4 | rollup: { 5 | optimizeLodash: true, 6 | }, 7 | extract: { 8 | rules: { 9 | 'ae-forgotten-export': 'error', 10 | 'ae-incompatible-release-tags': 'warn', 11 | 'ae-internal-missing-underscore': 'off', 12 | 'ae-missing-release-tag': 'off', 13 | }, 14 | }, 15 | tsconfig: 'tsconfig.build.json', 16 | }) 17 | -------------------------------------------------------------------------------- /packages/@repo/prettier-config/index.js: -------------------------------------------------------------------------------- 1 | const preset = require('@sanity/prettier-config') 2 | 3 | /** @type {import("prettier").Config} */ 4 | const config = { 5 | ...preset, 6 | plugins: [ 7 | ...preset.plugins, 8 | '@ianvs/prettier-plugin-sort-imports', 9 | 'prettier-plugin-astro', 10 | 'prettier-plugin-svelte', 11 | 'prettier-plugin-tailwindcss', 12 | ], 13 | } 14 | 15 | module.exports = config 16 | -------------------------------------------------------------------------------- /packages/@repo/sanity-extracted-schema/.gitignore: -------------------------------------------------------------------------------- 1 | .sanity 2 | cross-dataset-references.json 3 | live-demo.json 4 | page-builder-demo.json 5 | shoes.json 6 | -------------------------------------------------------------------------------- /packages/@repo/sanity-extracted-schema/sanity.cli.ts: -------------------------------------------------------------------------------- 1 | import {datasets, projectId} from '@repo/env' 2 | import {defineCliConfig} from 'sanity/cli' 3 | 4 | export default defineCliConfig({api: {projectId, dataset: datasets.development}}) 5 | -------------------------------------------------------------------------------- /packages/@repo/sanity-extracted-schema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/@repo/sanity-extracted-schema/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "outputs": [ 7 | "cross-dataset-references.json", 8 | "live-demo.json", 9 | "page-builder-demo.json", 10 | "shoes.json" 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/@repo/sanity-schema/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/@repo/sanity-schema/package.config.ts: -------------------------------------------------------------------------------- 1 | import baseConfig from '@repo/package.config' 2 | import {defineConfig} from '@sanity/pkg-utils' 3 | 4 | export default defineConfig(baseConfig) 5 | -------------------------------------------------------------------------------- /packages/@repo/sanity-schema/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cross-dataset-references' 2 | export * from './live-demo' 3 | export * from './page-builder-demo' 4 | export * from './performance-test' 5 | export * from './shoes' 6 | -------------------------------------------------------------------------------- /packages/@repo/sanity-schema/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/@repo/sanity-schema/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "include": ["src/**/*.ts", "src/**/*.tsx"], 7 | "exclude": ["dist", "node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/@repo/sanity-schema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base", 3 | "include": ["**/*.ts", "**/*.tsx"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/@repo/sanity-schema/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "outputs": ["dist/**"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/@repo/studio-url/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/@repo/studio-url/package.config.ts: -------------------------------------------------------------------------------- 1 | import baseConfig from '@repo/package.config' 2 | import {defineConfig} from '@sanity/pkg-utils' 3 | 4 | export default defineConfig({ 5 | ...baseConfig, 6 | define: { 7 | 'process.env.NODE_ENV': process.env['VERCEL_ENV'] || process.env['NODE_ENV'] || 'development', 8 | 'process.env.VERCEL_BRANCH_URL': process.env['VERCEL_BRANCH_URL'], 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/@repo/studio-url/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/@repo/studio-url/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "include": ["src/**/*.ts", "src/**/*.tsx"], 7 | "exclude": ["dist", "node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/@repo/studio-url/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base", 3 | "include": ["**/*.ts", "**/*.tsx"], 4 | "exclude": ["dist", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/@repo/studio-url/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "env": ["NODE_ENV", "VERCEL_ENV", "VERCEL_BRANCH_URL"], 7 | "outputs": ["dist/**"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/comlink/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | playground -------------------------------------------------------------------------------- /packages/comlink/.gitignore: -------------------------------------------------------------------------------- 1 | src/**/*.d.ts 2 | -------------------------------------------------------------------------------- /packages/comlink/comlink-lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/packages/comlink/comlink-lines.png -------------------------------------------------------------------------------- /packages/comlink/package.config.ts: -------------------------------------------------------------------------------- 1 | import baseConfig from '@repo/package.config' 2 | import {defineConfig} from '@sanity/pkg-utils' 3 | 4 | export default defineConfig({ 5 | ...baseConfig, 6 | runtime: 'browser', 7 | define: { 8 | 'process.env.NODE_ENV': 'production', 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/comlink/playground/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | .env 6 | -------------------------------------------------------------------------------- /packages/comlink/playground/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Remix! 2 | 3 | - 📖 [Remix docs](https://remix.run/docs) 4 | 5 | ## Development 6 | 7 | Run the dev server: 8 | 9 | ```shellscript 10 | npm run dev 11 | ``` 12 | 13 | ## Deployment 14 | 15 | First, build your app for production: 16 | 17 | ```sh 18 | npm run build 19 | ``` 20 | 21 | Then run the app in production mode: 22 | 23 | ```sh 24 | npm start 25 | ``` 26 | 27 | Now you'll need to pick a host to deploy it to. 28 | 29 | ### DIY 30 | 31 | If you're familiar with deploying Node applications, the built-in Remix app server is production-ready. 32 | 33 | Make sure to deploy the output of `npm run build` 34 | 35 | - `build/server` 36 | - `build/client` 37 | 38 | ## Styling 39 | 40 | This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever css framework you prefer. See the [Vite docs on css](https://vitejs.dev/guide/features.html#css) for more information. 41 | -------------------------------------------------------------------------------- /packages/comlink/playground/app/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import {FunctionComponent, PropsWithChildren} from 'react' 2 | 3 | export const Button: FunctionComponent< 4 | PropsWithChildren<{ 5 | disabled?: boolean 6 | onClick?: () => void 7 | }> 8 | > = (props) => { 9 | const {onClick = () => {}, children, disabled} = props 10 | return ( 11 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /packages/comlink/playground/app/components/Frame.tsx: -------------------------------------------------------------------------------- 1 | import {type Controller} from '@sanity/comlink' 2 | import {FunctionComponent, useEffect, useRef} from 'react' 3 | 4 | export const Frame: FunctionComponent<{ 5 | controller: Controller 6 | }> = (props) => { 7 | const {controller} = props 8 | const frameRef = useRef(null) 9 | 10 | useEffect(() => { 11 | const contentWindow = frameRef.current?.contentWindow 12 | if (!contentWindow) { 13 | return 14 | } 15 | const unsub = controller.addTarget(contentWindow) 16 | return () => { 17 | unsub() 18 | } 19 | }, [controller]) 20 | 21 | return ( 22 |
23 |