├── .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 |
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 |
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 |
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 |
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 |
2 |
3 |
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 | Disable Preview
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 | Disable Preview
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 |
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/packages/comlink/playground/app/components/MessageStack.tsx:
--------------------------------------------------------------------------------
1 | import {FunctionComponent, PropsWithChildren} from 'react'
2 | import {RenderedMessage} from '../types'
3 | import {MessageEntry} from './MessageEntry'
4 |
5 | export const MessageStack: FunctionComponent<
6 | PropsWithChildren<{
7 | messages: RenderedMessage[]
8 | }>
9 | > = (props) => {
10 | const {messages} = props
11 | return (
12 |
13 | {messages.map((message, i) => (
14 |
15 | ))}
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/packages/comlink/playground/app/entry.client.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * By default, Remix will handle hydrating your app on the client for you.
3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4 | * For more information, see https://remix.run/file-conventions/entry.client
5 | */
6 |
7 | import {RemixBrowser} from '@remix-run/react'
8 | import {startTransition, StrictMode} from 'react'
9 | import {hydrateRoot} from 'react-dom/client'
10 |
11 | startTransition(() => {
12 | hydrateRoot(
13 | document,
14 |
15 |
16 | ,
17 | )
18 | })
19 |
--------------------------------------------------------------------------------
/packages/comlink/playground/app/root.tsx:
--------------------------------------------------------------------------------
1 | import {Links, Meta, Outlet, Scripts, ScrollRestoration} from '@remix-run/react'
2 | import './tailwind.css'
3 |
4 | export function Layout({children}: {children: React.ReactNode}) {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {children}
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | export default function App() {
23 | return
24 | }
25 |
--------------------------------------------------------------------------------
/packages/comlink/playground/app/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/packages/comlink/playground/app/types.ts:
--------------------------------------------------------------------------------
1 | import type {ProtocolMessage, WithoutResponse} from '@sanity/comlink'
2 |
3 | export interface ControllerMessage {
4 | type: 'controller'
5 | data: {message: string}
6 | }
7 |
8 | export type NodeMessage =
9 | | {
10 | type: 'node'
11 | data: {message: string}
12 | response: {message: string}
13 | }
14 | | {
15 | type: 'foo'
16 | data: {message: string}
17 | response: {foo: string}
18 | }
19 |
20 | export type PlaygroundMessage = ControllerMessage | NodeMessage
21 |
22 | export type RenderedMessage =
23 | | WithoutResponse
24 | | ProtocolMessage>
25 |
--------------------------------------------------------------------------------
/packages/comlink/playground/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/packages/comlink/playground/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sanity-io/visual-editing/1a3ba89e8f56f3d6554d25481a9c1775d382ac8a/packages/comlink/playground/public/favicon.ico
--------------------------------------------------------------------------------
/packages/comlink/playground/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type {Config} from 'tailwindcss'
2 |
3 | export default {
4 | content: ['./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}'],
5 | theme: {
6 | extend: {},
7 | },
8 | plugins: [],
9 | } satisfies Config
10 |
--------------------------------------------------------------------------------
/packages/comlink/playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "**/*.ts",
4 | "**/*.tsx",
5 | "**/.server/**/*.ts",
6 | "**/.server/**/*.tsx",
7 | "**/.client/**/*.ts",
8 | "**/.client/**/*.tsx"
9 | ],
10 | "compilerOptions": {
11 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
12 | "types": ["@remix-run/node", "vite/client"],
13 | "isolatedModules": true,
14 | "esModuleInterop": true,
15 | "jsx": "react-jsx",
16 | "module": "ESNext",
17 | "moduleResolution": "Bundler",
18 | "resolveJsonModule": true,
19 | "target": "ES2022",
20 | "strict": true,
21 | "allowJs": true,
22 | "skipLibCheck": true,
23 | "forceConsistentCasingInFileNames": true,
24 | "baseUrl": ".",
25 | "paths": {
26 | "~/*": ["./app/*"]
27 | },
28 |
29 | // Vite takes care of building everything, not tsc.
30 | "noEmit": true
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/comlink/playground/vite.config.ts:
--------------------------------------------------------------------------------
1 | import {vitePlugin as remix} from '@remix-run/dev'
2 | import {defineConfig} from 'vite'
3 | import tsconfigPaths from 'vite-tsconfig-paths'
4 |
5 | export default defineConfig({
6 | plugins: [
7 | remix({
8 | future: {
9 | v3_fetcherPersist: true,
10 | v3_relativeSplatPath: true,
11 | v3_throwAbortReason: true,
12 | },
13 | }),
14 | tsconfigPaths(),
15 | ],
16 | })
17 |
--------------------------------------------------------------------------------
/packages/comlink/src/util.ts:
--------------------------------------------------------------------------------
1 | // Returns Promise.withResolvers or polyfill if unavailable
2 | export function createPromiseWithResolvers(): {
3 | promise: Promise
4 | resolve: (value: T | PromiseLike) => void
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
6 | reject: (reason?: any) => void
7 | } {
8 | if (typeof Promise.withResolvers === 'function') {
9 | return Promise.withResolvers()
10 | }
11 |
12 | let resolve!: (value: T | PromiseLike) => void
13 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
14 | let reject!: (reason?: any) => void
15 |
16 | const promise = new Promise((res, rej) => {
17 | resolve = res
18 | reject = rej
19 | })
20 |
21 | return {promise, resolve, reject}
22 | }
23 |
--------------------------------------------------------------------------------
/packages/comlink/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "noUnusedLocals": false,
7 | "noUnusedParameters": false
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/comlink/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "compilerOptions": {
4 | "rootDir": "src"
5 | },
6 | "include": ["src/**/*.ts"],
7 | "exclude": ["dist", "node_modules", "playground"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/comlink/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "include": ["**/*.ts"],
4 | "exclude": ["dist", "node_modules", "playground"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/comlink/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "dev": {
6 | "persistent": false
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core-loader/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
--------------------------------------------------------------------------------
/packages/core-loader/README.md:
--------------------------------------------------------------------------------
1 | # @sanity/core-loader
2 |
3 | [](https://npm-stat.com/charts.html?package=@sanity/core-loader)
4 | [](https://www.npmjs.com/package/@sanity/core-loader)
5 | [![gzip size][gzip-badge]][bundlephobia]
6 | [![size][size-badge]][bundlephobia]
7 |
8 | ```sh
9 | npm install @sanity/core-loader @sanity/client
10 | ```
11 |
12 | [gzip-badge]: https://img.shields.io/bundlephobia/minzip/@sanity/core-loader?label=gzip%20size&style=flat-square
13 | [size-badge]: https://img.shields.io/bundlephobia/min/@sanity/core-loader?label=size&style=flat-square
14 | [bundlephobia]: https://bundlephobia.com/package/@sanity/core-loader
15 |
--------------------------------------------------------------------------------
/packages/core-loader/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 | extract: {
7 | ...baseConfig.extract,
8 | bundledPackages: ['nanostores'],
9 | rules: {
10 | ...baseConfig.extract.rules,
11 | 'ae-forgotten-export': 'warn',
12 | },
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/packages/core-loader/src/createDataAttribute.ts:
--------------------------------------------------------------------------------
1 | export {
2 | /**
3 | * @deprecated use `import type {CreateDataAttribute} from '@sanity/visual-editing'` instead
4 | */
5 | type CreateDataAttribute,
6 | /**
7 | * @deprecated use `import {createDataAttribute} from '@sanity/visual-editing'` instead
8 | */
9 | createDataAttribute,
10 | /**
11 | * @deprecated use `import type {CreateDataAttributeProps} from '@sanity/visual-editing'` instead
12 | */
13 | type CreateDataAttributeProps,
14 | } from '@sanity/visual-editing-csm'
15 |
--------------------------------------------------------------------------------
/packages/core-loader/src/env.ts:
--------------------------------------------------------------------------------
1 | /** @internal */
2 | export const runtime = typeof document === 'undefined' ? 'server' : 'browser'
3 |
--------------------------------------------------------------------------------
/packages/core-loader/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "noUnusedLocals": false,
7 | "noUnusedParameters": false
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core-loader/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/core-loader/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "include": ["**/*.ts", "**/*.tsx"],
4 | "exclude": ["dist", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core-loader/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "dev": {
6 | "persistent": false
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core-loader/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | environment: 'happy-dom',
6 | typecheck: {
7 | tsconfig: 'tsconfig.build.json',
8 | },
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/packages/insert-menu/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/packages/insert-menu/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/packages/insert-menu/.gitignore:
--------------------------------------------------------------------------------
1 | *.local
2 | *.log
3 | *.tgz
4 |
5 | .DS_Store
6 | .workshop
7 | dist
8 | etc
9 | node_modules
10 |
--------------------------------------------------------------------------------
/packages/insert-menu/README.md:
--------------------------------------------------------------------------------
1 | # `@sanity/insert-menu`
2 |
--------------------------------------------------------------------------------
/packages/insert-menu/package.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from '@sanity/pkg-utils'
2 |
3 | // https://github.com/sanity-io/pkg-utils#configuration
4 | export default defineConfig({
5 | // the path to the tsconfig file for distributed builds
6 | tsconfig: 'tsconfig.dist.json',
7 | rollup: {
8 | optimizeLodash: true,
9 | },
10 | babel: {reactCompiler: true},
11 | reactCompilerOptions: {target: '18'},
12 | })
13 |
--------------------------------------------------------------------------------
/packages/insert-menu/src/InsertMenuOptions.ts:
--------------------------------------------------------------------------------
1 | /** @alpha This API may change */
2 | export interface InsertMenuOptions {
3 | /**
4 | * @defaultValue `'auto'`
5 | * `filter: 'auto'` automatically turns on filtering if there are more than 5
6 | * schema types added to the menu.
7 | */
8 | filter?: 'auto' | boolean
9 | groups?: Array<{name: string; title?: string; of?: Array}>
10 | /** defaultValue `true` */
11 | showIcons?: boolean
12 | /** @defaultValue `[{name: 'list'}]` */
13 | views?: Array<
14 | | {name: 'list'}
15 | | {name: 'grid'; previewImageUrl?: (schemaTypeName: string) => string | undefined}
16 | >
17 | }
18 |
--------------------------------------------------------------------------------
/packages/insert-menu/src/__workshop__/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "@typescript-eslint/explicit-module-boundary-types": "off",
4 | "@typescript-eslint/no-unused-vars": "off",
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/packages/insert-menu/src/__workshop__/index.ts:
--------------------------------------------------------------------------------
1 | import {defineScope} from '@sanity/ui-workshop'
2 | import {lazy} from 'react'
3 |
4 | export default defineScope({
5 | name: 'insert-menu',
6 | title: 'Insert Menu',
7 | stories: [
8 | {
9 | name: 'InsertMenu',
10 | title: 'InsertMenu',
11 | component: lazy(() => import('./full')),
12 | },
13 | ],
14 | })
15 |
--------------------------------------------------------------------------------
/packages/insert-menu/src/getSchemaTypeIcon.ts:
--------------------------------------------------------------------------------
1 | import {type ReferenceSchemaType, type SchemaType} from '@sanity/types'
2 | import {type ComponentType} from 'react'
3 |
4 | /** @internal */
5 | export function getSchemaTypeIcon(schemaType: SchemaType): ComponentType | undefined {
6 | // Use reference icon if reference is to one schemaType only
7 | const referenceIcon =
8 | isReferenceSchemaType(schemaType) && (schemaType.to ?? []).length === 1
9 | ? schemaType.to[0].icon
10 | : undefined
11 |
12 | return schemaType.icon ?? schemaType.type?.icon ?? referenceIcon
13 | }
14 |
15 | function isReferenceSchemaType(type: unknown): type is ReferenceSchemaType {
16 | return isRecord(type) && (type['name'] === 'reference' || isReferenceSchemaType(type['type']))
17 | }
18 |
19 | function isRecord(value: unknown): value is Record {
20 | return !!value && (typeof value == 'object' || typeof value == 'function')
21 | }
22 |
--------------------------------------------------------------------------------
/packages/insert-menu/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InsertMenu'
2 | export * from './InsertMenuOptions'
3 |
--------------------------------------------------------------------------------
/packages/insert-menu/tsconfig.dist.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.settings",
3 | "compilerOptions": {
4 | "rootDir": "src"
5 | },
6 | "include": ["src/**/*.ts", "src/**/*.tsx"],
7 | "exclude": ["dist", "node_modules"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/insert-menu/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.settings",
3 | "include": ["**/*.ts", "**/*.tsx"],
4 | "exclude": ["dist", "./node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/insert-menu/tsconfig.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "./dist"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/insert-menu/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "build": {
6 | "dependsOn": ["^build"]
7 | },
8 | "dev": {
9 | "persistent": false
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/insert-menu/workshop.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from '@sanity/ui-workshop'
2 | import {buildTheme} from '@sanity/ui/theme'
3 |
4 | export default defineConfig({
5 | theme: buildTheme(),
6 | title: '@sanity/insert-menu',
7 | })
8 |
--------------------------------------------------------------------------------
/packages/next-loader/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
--------------------------------------------------------------------------------
/packages/next-loader/README.md:
--------------------------------------------------------------------------------
1 | # @sanity/next-loader
2 |
3 | [](https://npm-stat.com/charts.html?package=@sanity/next-loader)
4 | [](https://www.npmjs.com/package/@sanity/next-loader)
5 | [![gzip size][gzip-badge]][bundlephobia]
6 | [![size][size-badge]][bundlephobia]
7 |
8 | > [!WARNING]
9 | > This package is not meant to be used directly, it's a dependency of [`next-sanity`].
10 |
11 | [`next-sanity`]: https://github.com/sanity-io/next-sanity
12 | [gzip-badge]: https://img.shields.io/bundlephobia/minzip/@sanity/next-loader?label=gzip%20size&style=flat-square
13 | [size-badge]: https://img.shields.io/bundlephobia/min/@sanity/next-loader?label=size&style=flat-square
14 | [bundlephobia]: https://bundlephobia.com/package/@sanity/next-loader
15 |
--------------------------------------------------------------------------------
/packages/next-loader/src/client-components/live-stream/SanityLiveStreamLazy.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This file works around a new restriction in Next v15 where server components are not allowed
3 | * to use dynamic(() => import('...), {ssr: false})
4 | * only Client Components can set ssr: false.
5 | */
6 |
7 | import dynamic from 'next/dynamic'
8 | import type {SanityLiveStreamProps} from './SanityLiveStream'
9 |
10 | const SanityLiveStreamClientComponent = dynamic(() => import('./SanityLiveStream'), {ssr: false})
11 |
12 | export function SanityLiveStreamLazyClientComponent(props: SanityLiveStreamProps): React.ReactNode {
13 | return
14 | }
15 |
--------------------------------------------------------------------------------
/packages/next-loader/src/client-components/live-stream/index.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | export type {SanityLiveStreamProps} from './SanityLiveStream'
4 | export {SanityLiveStreamLazyClientComponent as default} from './SanityLiveStreamLazy'
5 |
--------------------------------------------------------------------------------
/packages/next-loader/src/client-components/live/RefreshOnFocus.tsx:
--------------------------------------------------------------------------------
1 | import {useRouter} from 'next/navigation.js'
2 | import {useEffect} from 'react'
3 |
4 | const focusThrottleInterval = 5_000
5 |
6 | export default function RefreshOnFocus(): null {
7 | const router = useRouter()
8 |
9 | useEffect(() => {
10 | const controller = new AbortController()
11 | let nextFocusRevalidatedAt = 0
12 | const callback = () => {
13 | const now = Date.now()
14 | if (now > nextFocusRevalidatedAt && document.visibilityState !== 'hidden') {
15 | router.refresh()
16 | nextFocusRevalidatedAt = now + focusThrottleInterval
17 | }
18 | }
19 | const {signal} = controller
20 | document.addEventListener('visibilitychange', callback, {passive: true, signal})
21 | window.addEventListener('focus', callback, {passive: true, signal})
22 | return () => controller.abort()
23 | }, [router])
24 |
25 | return null
26 | }
27 | RefreshOnFocus.displayName = 'RefreshOnFocus'
28 |
--------------------------------------------------------------------------------
/packages/next-loader/src/client-components/live/RefreshOnMount.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Handles refreshing the page when the page is mounted,
3 | * in case the content changes at a high enough frequency that by
4 | * the time the page started streaming, and the component sets
5 | * up the EventSource connection, content might have changed.
6 | */
7 |
8 | import {useRouter} from 'next/navigation.js'
9 | import {useEffect, useReducer} from 'react'
10 |
11 | export default function RefreshOnMount(): null {
12 | const router = useRouter()
13 | const [mounted, mount] = useReducer(() => true, false)
14 |
15 | useEffect(() => {
16 | if (!mounted) {
17 | mount()
18 | router.refresh()
19 | }
20 | }, [mounted, router])
21 |
22 | return null
23 | }
24 | RefreshOnMount.displayName = 'RefreshOnMount'
25 |
--------------------------------------------------------------------------------
/packages/next-loader/src/client-components/live/RefreshOnReconnect.tsx:
--------------------------------------------------------------------------------
1 | import {useRouter} from 'next/navigation.js'
2 | import {useEffect} from 'react'
3 |
4 | export default function RefreshOnReconnect(): null {
5 | const router = useRouter()
6 |
7 | useEffect(() => {
8 | const controller = new AbortController()
9 | const {signal} = controller
10 | window.addEventListener('online', () => router.refresh(), {passive: true, signal})
11 | return () => controller.abort()
12 | }, [router])
13 |
14 | return null
15 | }
16 | RefreshOnReconnect.displayName = 'RefreshOnReconnect'
17 |
--------------------------------------------------------------------------------
/packages/next-loader/src/client-components/live/index.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | export type {SanityLiveProps} from './SanityLive'
4 | export {SanityLive as default} from './SanityLive'
5 |
--------------------------------------------------------------------------------
/packages/next-loader/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | export * from './useDraftMode'
4 | export type {DraftPerspective, DraftEnvironment} from './context'
5 | export type {ClientPerspective} from '@sanity/client'
6 | export * from './useIsPresentationTool'
7 | export * from './useIsLivePreview'
8 | export * from './usePresentationQuery'
9 |
--------------------------------------------------------------------------------
/packages/next-loader/src/hooks/useIsPresentationTool.ts:
--------------------------------------------------------------------------------
1 | import {useDraftModeEnvironment} from './useDraftMode'
2 |
3 | /**
4 | * Detects if the application is being previewed inside Sanity Presentation Tool.
5 | * Presentation Tool can open the application in an iframe, or in a new window.
6 | * When in this context there are some UI you usually don't want to show,
7 | * for example a Draft Mode toggle, or a "Viewing draft content" indicators, these are unnecessary and add clutter to
8 | * the editorial experience.
9 | * The hook returns `null` initially, when it's not yet sure if the application is running inside Presentation Tool,
10 | * then `true` if it is, and `false` otherwise.
11 | * @public
12 | */
13 | export function useIsPresentationTool(): boolean | null {
14 | const environment = useDraftModeEnvironment()
15 | return environment === 'checking'
16 | ? null
17 | : environment === 'presentation-iframe' || environment === 'presentation-window'
18 | }
19 |
--------------------------------------------------------------------------------
/packages/next-loader/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './defineLive'
2 | export * from './isCorsOriginError'
3 |
--------------------------------------------------------------------------------
/packages/next-loader/src/isCorsOriginError.ts:
--------------------------------------------------------------------------------
1 | import {CorsOriginError} from '@sanity/client'
2 |
3 | /** @public */
4 | export function isCorsOriginError(error: unknown): error is CorsOriginError {
5 | return error instanceof CorsOriginError
6 | }
7 |
8 | export type {CorsOriginError}
9 |
--------------------------------------------------------------------------------
/packages/next-loader/src/resolveCookiePerspective.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import type {ClientPerspective} from '@sanity/client'
4 | import {perspectiveCookieName} from '@sanity/preview-url-secret/constants'
5 | import {cookies, draftMode} from 'next/headers.js'
6 | import {sanitizePerspective} from './utils'
7 |
8 | /**
9 | * @internal
10 | */
11 | export async function resolveCookiePerspective(): Promise> {
12 | return (await draftMode()).isEnabled
13 | ? (await cookies()).has(perspectiveCookieName)
14 | ? sanitizePerspective((await cookies()).get(perspectiveCookieName)?.value, 'drafts')
15 | : 'drafts'
16 | : 'published'
17 | }
18 |
--------------------------------------------------------------------------------
/packages/next-loader/src/utils.ts:
--------------------------------------------------------------------------------
1 | import {validateApiPerspective, type ClientPerspective} from '@sanity/client'
2 |
3 | /** @internal */
4 | export function sanitizePerspective(
5 | _perspective: unknown,
6 | fallback: 'drafts' | 'published',
7 | ): Exclude {
8 | const perspective =
9 | typeof _perspective === 'string' && _perspective.includes(',')
10 | ? _perspective.split(',')
11 | : _perspective
12 | try {
13 | validateApiPerspective(perspective)
14 | return perspective === 'raw' ? fallback : perspective
15 | } catch (err) {
16 | // eslint-disable-next-line no-console
17 | console.warn(`Invalid perspective:`, _perspective, perspective, err)
18 | return fallback
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/next-loader/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "paths": {
5 | "@sanity/next-loader/client-components/live": ["./src/client-components/live/index.ts"],
6 | "@sanity/next-loader/client-components/live-stream": [
7 | "./src/client-components/live-stream/index.ts"
8 | ],
9 | "@sanity/next-loader/hooks": ["./src/hooks/index.ts"],
10 | "@sanity/next-loader/server-actions": ["./src/server-actions/index.ts"],
11 | "@sanity/next-loader": ["./src/index"]
12 | },
13 | "rootDir": ".",
14 | "outDir": "dist",
15 | "noUnusedLocals": false,
16 | "noUnusedParameters": false
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/next-loader/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/next-loader/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "include": ["**/*.ts", "**/*.tsx"],
4 | "exclude": ["dist", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/next-loader/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "build": {
6 | "env": ["NODE_ENV"]
7 | },
8 | "dev": {
9 | "env": ["NODE_ENV"],
10 | "persistent": false
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/next-loader/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | typecheck: {
6 | tsconfig: 'tsconfig.build.json',
7 | },
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/packages/presentation-comlink/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/packages/presentation-comlink/.gitignore:
--------------------------------------------------------------------------------
1 | src/**/*.d.ts
2 |
--------------------------------------------------------------------------------
/packages/presentation-comlink/README.md:
--------------------------------------------------------------------------------
1 | # @sanity/presentation-comlink
2 |
3 | > [!WARNING]
4 | > This package is not meant to be used directly, it's a shared dependency of `sanity/presentation` and `@sanity/visual-editing`. Using it in production is at your own risk.
5 |
6 | The `sanity/presentation` tool requires a way to communicate with the application that lives within its preview iframe. The application is required to load up at least `@sanity/visual-editing`, but can also load up `@sanity/core-loader`, `@sanity/next-loader`, `@sanity/svelte-loader`, and `@sanity/react-loader`.
7 | It uses `@sanity/comlink` to communicate over the iframe, and any popup preview windows, over the `window.postMessage` protocol.
8 | The typings for those messages are defined in this package, as well as utils for maintaining compatibility with how older versions of `sanity/presentation` and `@sanity/visual-editing` used to format its message payloads, and helpers for handling payloads..
9 |
--------------------------------------------------------------------------------
/packages/presentation-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/presentation-comlink/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './comlinkCompatibility'
2 | export * from './isMaybePresentation'
3 | export type * from './types'
4 | export type {Path} from '@sanity/client/csm'
5 | export type {
6 | DocumentSchema,
7 | InsertMenuOptions,
8 | PreviewSnapshot,
9 | ResolvedSchemaTypeMap,
10 | SanityNode,
11 | SanityStegaNode,
12 | SchemaArrayItem,
13 | SchemaArrayNode,
14 | SchemaBooleanNode,
15 | SchemaInlineNode,
16 | SchemaNode,
17 | SchemaNullNode,
18 | SchemaNumberNode,
19 | SchemaObjectField,
20 | SchemaObjectNode,
21 | SchemaStringNode,
22 | SchemaType,
23 | SchemaUnionNode,
24 | SchemaUnionNodeOptions,
25 | SchemaUnionOption,
26 | SchemaUnknownNode,
27 | TypeSchema,
28 | UnresolvedPath,
29 | } from '@sanity/visual-editing-types'
30 |
--------------------------------------------------------------------------------
/packages/presentation-comlink/src/isMaybePresentation.ts:
--------------------------------------------------------------------------------
1 | export function isMaybePreviewIframe(): boolean {
2 | return window.self !== window.top
3 | }
4 | export function isMaybePreviewWindow(): boolean {
5 | return Boolean(window.opener)
6 | }
7 | export function isMaybePresentation(): boolean {
8 | return isMaybePreviewIframe() || isMaybePreviewWindow()
9 | }
10 |
--------------------------------------------------------------------------------
/packages/presentation-comlink/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/presentation-comlink/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "compilerOptions": {
4 | "rootDir": "src"
5 | },
6 | "include": ["src/**/*.ts"],
7 | "exclude": ["dist", "node_modules"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/presentation-comlink/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "include": ["**/*.ts"],
4 | "exclude": ["dist", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/presentation-comlink/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "dev": {
6 | "persistent": false
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/presentation-comlink/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | typecheck: {
6 | tsconfig: 'tsconfig.build.json',
7 | },
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/packages/presentation/README.md:
--------------------------------------------------------------------------------
1 | > [!WARNING]
2 | > This package is replaced by [`sanity/presentation`].
3 |
4 | ## Migrate to [`sanity/presentation`]
5 |
6 | Replace the dependency:
7 |
8 | ```sh
9 | npm uninstall @sanity/presentation
10 | npm install sanity@latest
11 | ```
12 |
13 | Replace import statements:
14 |
15 | ```diff
16 | -import { presentationTool } from '@sanity/presentation'
17 | +import { presentationTool } from 'sanity/presentation'
18 | ```
19 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | legacy
3 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/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 | external: ['@sanity/icons', 'sanity'],
7 | })
8 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/_exports/constants.ts:
--------------------------------------------------------------------------------
1 | export * from '../constants'
2 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/_exports/create-secret.ts:
--------------------------------------------------------------------------------
1 | export * from '../createPreviewSecret'
2 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/_exports/define-preview-url.ts:
--------------------------------------------------------------------------------
1 | export * from '../definePreviewUrl'
2 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/_exports/get-redirect-to.ts:
--------------------------------------------------------------------------------
1 | export * from '../getRedirectTo'
2 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/_exports/sanity-plugin-debug-secrets.ts:
--------------------------------------------------------------------------------
1 | export * from '../sanityPluginDebugSecrets'
2 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/_exports/toggle-preview-access-sharing.ts:
--------------------------------------------------------------------------------
1 | export * from '../togglePreviewAccessSharing'
2 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/_exports/toggle-vercel-protection-bypass.ts:
--------------------------------------------------------------------------------
1 | export * from '../toggleVercelProtectionBypass'
2 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/_exports/without-secret-search-params.ts:
--------------------------------------------------------------------------------
1 | export * from '../withoutSecretSearchParams'
2 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/generateSecret.ts:
--------------------------------------------------------------------------------
1 | /** @internal */
2 | export function generateUrlSecret(): string {
3 | // Try using WebCrypto if available
4 | if (typeof crypto !== 'undefined') {
5 | // Generate a random array of 16 bytes
6 | const array = new Uint8Array(16)
7 | crypto.getRandomValues(array)
8 |
9 | // Convert the array to a URL-safe string
10 | let key = ''
11 | for (let i = 0; i < array.length; i++) {
12 | // Convert each byte to a 2-digit hexadecimal number
13 | key += array[i].toString(16).padStart(2, '0')
14 | }
15 |
16 | // Replace '+' and '/' from base64url to '-' and '_'
17 | key = btoa(key).replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]+$/, '')
18 |
19 | return key
20 | }
21 | // If not fallback to Math.random
22 | return Math.random().toString(36).slice(2)
23 | }
24 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/getRedirectTo.ts:
--------------------------------------------------------------------------------
1 | import {urlSearchParamPreviewPathname} from './constants'
2 |
3 | /**
4 | * @internal
5 | */
6 | export function getRedirectTo(url: URL): URL {
7 | if (url.searchParams.has(urlSearchParamPreviewPathname)) {
8 | return new URL(url.searchParams.get(urlSearchParamPreviewPathname)!, url.origin)
9 | }
10 |
11 | return url
12 | }
13 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/src/index.ts:
--------------------------------------------------------------------------------
1 | export {urlSearchParamPreviewPathname, urlSearchParamPreviewSecret} from './constants'
2 | export * from './validatePreviewUrl'
3 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "noUnusedLocals": false,
7 | "noUnusedParameters": false,
8 | "noPropertyAccessFromIndexSignature": false
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/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/preview-url-secret/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "include": ["**/*.ts", "**/*.tsx"],
4 | "exclude": ["dist", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "build": {
6 | "env": ["NODE_ENV"]
7 | },
8 | "dev": {
9 | "persistent": false
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/preview-url-secret/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | typecheck: {
6 | tsconfig: 'tsconfig.build.json',
7 | },
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/packages/react-loader/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
--------------------------------------------------------------------------------
/packages/react-loader/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 | bundles: [
7 | {
8 | source: './src/rsc/index.react-server.ts',
9 | import: './dist/rsc/index.react-server.js',
10 | },
11 | ],
12 | })
13 |
--------------------------------------------------------------------------------
/packages/react-loader/src/index.browser.ts:
--------------------------------------------------------------------------------
1 | export * from './createQueryStore/client-only'
2 | export * from './useEncodeDataAttribute'
3 |
--------------------------------------------------------------------------------
/packages/react-loader/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createQueryStore/universal'
2 | export * from './useEncodeDataAttribute'
3 | export * from '@sanity/core-loader/create-data-attribute'
4 |
--------------------------------------------------------------------------------
/packages/react-loader/src/jsx/SanityElement.tsx:
--------------------------------------------------------------------------------
1 | import {encodeSanityNodeData} from '@sanity/visual-editing-csm'
2 | import type {HTMLProps, Ref} from 'react'
3 | import {forwardRef} from 'react'
4 | import type {SourceNode} from './wrap'
5 |
6 | export interface SanityElementProps {
7 | children?: SourceNode | null
8 | }
9 |
10 | export const SanityElement = forwardRef(function SanityElement(
11 | props: {as: string} & SanityElementProps & Omit, 'children'>,
12 | ref: Ref,
13 | ) {
14 | const {as: As, children: node, ...restProps} = props
15 |
16 | if (node?.source) {
17 | return (
18 | // @ts-expect-error @TODO find out why children is not allowed in IntrinsicAttributes
19 |
20 | {node.value}
21 |
22 | )
23 | }
24 |
25 | return {node?.value}
26 | })
27 |
--------------------------------------------------------------------------------
/packages/react-loader/src/jsx/wrap/constants.ts:
--------------------------------------------------------------------------------
1 | import type {SanityKey} from './types'
2 |
3 | export const SANITY_KEYS: SanityKey[] = [
4 | '_createdAt',
5 | '_dataset',
6 | '_id',
7 | '_key',
8 | '_originalId',
9 | '_projectId',
10 | '_ref',
11 | '_rev',
12 | '_strengthenOnPublish',
13 | '_type',
14 | '_updatedAt',
15 | '_weak',
16 | ]
17 |
--------------------------------------------------------------------------------
/packages/react-loader/src/jsx/wrap/helpers.ts:
--------------------------------------------------------------------------------
1 | export function isRecord(value: unknown): value is Record {
2 | return typeof value === 'object' && value !== null && !Array.isArray(value)
3 | }
4 | export function isArray(value: unknown): value is Array {
5 | return value !== null && Array.isArray(value)
6 | }
7 |
--------------------------------------------------------------------------------
/packages/react-loader/src/jsx/wrap/index.ts:
--------------------------------------------------------------------------------
1 | export * from './types'
2 | export * from './unwrapData'
3 | export * from './wrapData'
4 |
--------------------------------------------------------------------------------
/packages/react-loader/src/jsx/wrap/isSourceNode.ts:
--------------------------------------------------------------------------------
1 | import {isRecord} from './helpers'
2 | import type {SourceNode} from './types'
3 |
4 | export function isSourceNode(t: unknown): t is SourceNode {
5 | return isRecord(t) && t['$$type$$'] === 'sanity'
6 | }
7 |
--------------------------------------------------------------------------------
/packages/react-loader/src/rsc/index.react-server.ts:
--------------------------------------------------------------------------------
1 | export * from '../createQueryStore/server-only'
2 |
3 | export const useEncodeDataAttribute = (): void => {
4 | throw new Error('The `useEncodeDataAttribute` hook can only be called from a client component.')
5 | }
6 |
--------------------------------------------------------------------------------
/packages/react-loader/src/rsc/index.ts:
--------------------------------------------------------------------------------
1 | export * from '../createQueryStore/client-only'
2 | export * from '../useEncodeDataAttribute'
3 |
--------------------------------------------------------------------------------
/packages/react-loader/src/useEncodeDataAttribute.ts:
--------------------------------------------------------------------------------
1 | import type {ContentSourceMap} from '@sanity/client'
2 | import type {ResolveStudioUrl, StudioPathLike, StudioUrl} from '@sanity/client/csm'
3 | import {
4 | defineEncodeDataAttribute,
5 | type EncodeDataAttributeFunction,
6 | } from '@sanity/core-loader/encode-data-attribute'
7 | import {useMemo} from 'react'
8 |
9 | /** @public */
10 | export type EncodeDataAttributeCallback = (path: StudioPathLike) => string | undefined
11 |
12 | /** @public */
13 | export function useEncodeDataAttribute(
14 | result: QueryResponseResult,
15 | sourceMap: ContentSourceMap | undefined,
16 | studioUrl: StudioUrl | ResolveStudioUrl | undefined,
17 | ): EncodeDataAttributeFunction {
18 | return useMemo(
19 | () => defineEncodeDataAttribute(result, sourceMap, studioUrl),
20 | [result, sourceMap, studioUrl],
21 | )
22 | }
23 |
24 | export type {ContentSourceMap, ResolveStudioUrl, StudioPathLike, StudioUrl}
25 |
--------------------------------------------------------------------------------
/packages/react-loader/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "noUnusedLocals": false,
7 | "noUnusedParameters": false
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/react-loader/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/react-loader/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "compilerOptions": {
4 | "paths": {
5 | "@sanity/core-loader": ["./node_modules/@sanity/core-loader/src"]
6 | }
7 | },
8 | "include": [
9 | "**/*.ts",
10 | "**/*.tsx",
11 | "./node_modules/@sanity/core-loader/src",
12 | ],
13 | "exclude": ["dist", "node_modules"]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/react-loader/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "dev": {
6 | "persistent": false
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/react-loader/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | typecheck: {
6 | tsconfig: 'tsconfig.build.json',
7 | },
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/packages/svelte-loader/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
--------------------------------------------------------------------------------
/packages/svelte-loader/src/LiveMode.svelte:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/packages/svelte-loader/src/defineStudioUrlStore.ts:
--------------------------------------------------------------------------------
1 | import type {SanityClient} from '@sanity/client'
2 | import type {ResolveStudioUrl, StudioUrl} from '@sanity/client/csm'
3 | import type {CreateQueryStoreOptions} from '@sanity/core-loader'
4 | import {writable, type Writable} from 'svelte/store'
5 |
6 | type StudioUrlLike = StudioUrl | ResolveStudioUrl | undefined
7 |
8 | export function defineStudioUrlStore(
9 | client: CreateQueryStoreOptions['client'],
10 | ): Writable {
11 | return writable(
12 | typeof client === 'object' ? (client as SanityClient)?.config().stega.studioUrl : undefined,
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/packages/svelte-loader/src/global.d.ts:
--------------------------------------------------------------------------------
1 | import {LoaderLocals} from './types'
2 |
3 | declare global {
4 | namespace App {
5 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type
6 | interface Locals extends LoaderLocals {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/svelte-loader/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './createQueryStore'
2 | export * from './hooks'
3 | export {default as LiveMode} from './LiveMode.svelte'
4 | export * from './previewStore'
5 | export type {
6 | HandleOptions,
7 | HandlePreviewOptions,
8 | LoaderLocals,
9 | LoadQuery,
10 | LoadQueryOptions,
11 | NonUndefinedGuard,
12 | QueryResponseInitial,
13 | QueryStore,
14 | UseLiveMode,
15 | UseQueryOptionsDefinedInitial,
16 | UseQueryOptionsUndefinedInitial,
17 | VisualEditingLocals,
18 | WithEncodeDataAttribute,
19 | } from './types'
20 | export * from './useEncodeDataAttribute'
21 | export * from '@sanity/core-loader/create-data-attribute'
22 |
--------------------------------------------------------------------------------
/packages/svelte-loader/src/previewStore.ts:
--------------------------------------------------------------------------------
1 | export {
2 | /**
3 | * @deprecated use `import {isPreviewing} from '@sanity/visual-editing/svelte'` instead
4 | */
5 | isPreviewing,
6 | /**
7 | * @deprecated use `import {setPreviewing} from '@sanity/visual-editing/svelte'` instead
8 | */
9 | setPreviewing,
10 | } from '@sanity/visual-editing/svelte'
11 |
--------------------------------------------------------------------------------
/packages/svelte-loader/src/useEncodeDataAttribute.ts:
--------------------------------------------------------------------------------
1 | import type {ContentSourceMap} from '@sanity/client'
2 | import type {ResolveStudioUrl, StudioPathLike, StudioUrl} from '@sanity/client/csm'
3 | import {
4 | defineEncodeDataAttribute,
5 | type EncodeDataAttributeFunction,
6 | } from '@sanity/core-loader/encode-data-attribute'
7 |
8 | /** @public */
9 | export type EncodeDataAttributeCallback = (path: StudioPathLike) => string | undefined
10 |
11 | /** @public */
12 | export function useEncodeDataAttribute(
13 | result: QueryResponseResult,
14 | sourceMap: ContentSourceMap | undefined,
15 | studioUrl: StudioUrl | ResolveStudioUrl | undefined,
16 | ): EncodeDataAttributeFunction {
17 | return defineEncodeDataAttribute(result, sourceMap, studioUrl)
18 | }
19 |
20 | export type {ContentSourceMap, ResolveStudioUrl, StudioPathLike, StudioUrl}
21 |
--------------------------------------------------------------------------------
/packages/svelte-loader/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 | lib: 'svelte',
7 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
8 | // for more information about preprocessors
9 | preprocess: vitePreprocess(),
10 |
11 | kit: {
12 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
13 | // If your environment is not supported or you settled on a specific environment, switch out the adapter.
14 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
15 | adapter: adapter(),
16 | },
17 | }
18 |
19 | export default config
20 |
--------------------------------------------------------------------------------
/packages/svelte-loader/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "noUnusedLocals": false,
7 | "noUnusedParameters": false
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/svelte-loader/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/svelte-loader/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "include": ["**/*.ts", "**/*.tsx"],
4 | "exclude": ["dist", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/svelte-loader/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "dev": {
6 | "persistent": false
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/svelte-loader/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | typecheck: {
6 | tsconfig: 'tsconfig.build.json',
7 | },
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/packages/vercel-protection-bypass/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | legacy
3 |
--------------------------------------------------------------------------------
/packages/vercel-protection-bypass/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 | babel: {reactCompiler: true},
7 | reactCompilerOptions: {target: '18'},
8 | })
9 |
--------------------------------------------------------------------------------
/packages/vercel-protection-bypass/src/index.ts:
--------------------------------------------------------------------------------
1 | import {lazy} from 'react'
2 | import {definePlugin} from 'sanity'
3 |
4 | const id = 'vercel-protection-bypass'
5 |
6 | export interface VercelProtectionBypassConfig {
7 | name?: string
8 | title?: string
9 | icon?: React.ComponentType
10 | }
11 |
12 | export const vercelProtectionBypassTool = definePlugin(
13 | (options) => {
14 | const {name, title, icon, ...config} = options || {}
15 | return {
16 | name: `@sanity/preview-url-secret/${id}`,
17 | tools: [
18 | {
19 | name: name || 'vercel-protection-bypass',
20 | title: title || 'Vercel Protection Bypass',
21 | icon: icon,
22 | component: lazy(() => import('./VercelProtectionBypassTool')),
23 | options: config,
24 | __internalApplicationType: `sanity/${id}`,
25 | },
26 | ],
27 | }
28 | },
29 | )
30 |
--------------------------------------------------------------------------------
/packages/vercel-protection-bypass/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "noUnusedLocals": false,
7 | "noUnusedParameters": false,
8 | "noPropertyAccessFromIndexSignature": false
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vercel-protection-bypass/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/vercel-protection-bypass/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "include": ["**/*.ts", "**/*.tsx"],
4 | "exclude": ["dist", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/vercel-protection-bypass/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "build": {
6 | "env": ["NODE_ENV"]
7 | },
8 | "dev": {
9 | "persistent": false
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/vercel-protection-bypass/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | typecheck: {
6 | tsconfig: 'tsconfig.build.json',
7 | },
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/.gitignore:
--------------------------------------------------------------------------------
1 | src/**/*.d.ts
2 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/README.md:
--------------------------------------------------------------------------------
1 | # @sanity/visual-editing-csm
2 |
3 | > [!WARNING]
4 | > This package is not meant to be used directly, it's an internal dependency. Using it in production is at your own risk.
5 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/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/visual-editing-csm/src/decodeSanityNodeData.test.ts:
--------------------------------------------------------------------------------
1 | import {expect, test} from 'vitest'
2 | import {decodeSanityString} from './decodeSanityNodeData'
3 |
4 | test('an encoded string returns node data', async () => {
5 | const input = decodeSanityString(
6 | 'id=documentId;type=documentType;path=sections:abcdef.tagline;base=https%3A%2F%2Fsome.sanity.studio;workspace=docs;tool=desk',
7 | )
8 |
9 | const output = {
10 | id: 'documentId',
11 | type: 'documentType',
12 | path: 'sections[_key=="abcdef"].tagline',
13 | baseUrl: 'https://some.sanity.studio',
14 | tool: 'desk',
15 | workspace: 'docs',
16 | }
17 |
18 | expect(input).toMatchObject(output)
19 | })
20 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | createDataAttribute,
3 | type CreateDataAttributeProps,
4 | type CreateDataAttribute,
5 | type WithRequired,
6 | } from './createDataAttribute'
7 | export {urlStringToPath} from './urlStringToPath'
8 | export {encodeSanityNodeData} from './encodeSanityNodeData'
9 | export {decodeSanityNodeData} from './decodeSanityNodeData'
10 | export {pathToUrlString} from './pathToUrlString'
11 | export type {SanityNode, SanityStegaNode} from '@sanity/visual-editing-types'
12 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/src/isArray.ts:
--------------------------------------------------------------------------------
1 | export function isArray(value: unknown): value is Array {
2 | return value !== null && Array.isArray(value)
3 | }
4 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/src/isValidSanityNode.ts:
--------------------------------------------------------------------------------
1 | import type {SanityNode} from '@sanity/visual-editing-types'
2 | import {is} from 'valibot'
3 | import {sanityNodeSchema} from './sanityNodeSchema'
4 |
5 | /** @internal */
6 | export function isValidSanityNode(node: Partial): node is SanityNode {
7 | return is(sanityNodeSchema, node)
8 | }
9 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/src/pathToUrlString.ts:
--------------------------------------------------------------------------------
1 | import type {Path} from '@sanity/client/csm'
2 | import {isArray} from './isArray'
3 |
4 | /** @internal */
5 | export function pathToUrlString(path: Path): string {
6 | let str = ''
7 |
8 | for (const segment of path) {
9 | if (typeof segment === 'string') {
10 | if (str) str += '.'
11 | str += segment
12 | continue
13 | }
14 |
15 | if (typeof segment === 'number') {
16 | if (str) str += ':'
17 | str += `${segment}`
18 | continue
19 | }
20 |
21 | if (isArray(segment)) {
22 | if (str) str += ':'
23 | str += `${segment.join(',')}}`
24 | continue
25 | }
26 |
27 | if (segment._key) {
28 | if (str) str += ':'
29 | str += `${segment._key}`
30 | continue
31 | }
32 | }
33 |
34 | return str
35 | }
36 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/src/sanityNodeSchema.ts:
--------------------------------------------------------------------------------
1 | import {minLength, object, optional, pipe, string} from 'valibot'
2 |
3 | const lengthyStr = pipe(string(), minLength(1))
4 | const optionalLengthyStr = optional(lengthyStr)
5 |
6 | export const sanityNodeSchema = object({
7 | baseUrl: lengthyStr,
8 | dataset: optionalLengthyStr,
9 | id: lengthyStr,
10 | path: lengthyStr,
11 | projectId: optionalLengthyStr,
12 | tool: optionalLengthyStr,
13 | type: optionalLengthyStr,
14 | workspace: optionalLengthyStr,
15 | })
16 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "compilerOptions": {
4 | "rootDir": "src"
5 | },
6 | "include": ["src/**/*.ts"],
7 | "exclude": ["dist", "node_modules"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "include": ["**/*.ts"],
4 | "exclude": ["dist", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "dev": {
6 | "persistent": false
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/visual-editing-csm/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | typecheck: {
6 | tsconfig: 'tsconfig.build.json',
7 | },
8 | },
9 | })
10 |
--------------------------------------------------------------------------------
/packages/visual-editing-types/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/packages/visual-editing-types/.gitignore:
--------------------------------------------------------------------------------
1 | src/**/*.d.ts
2 |
--------------------------------------------------------------------------------
/packages/visual-editing-types/README.md:
--------------------------------------------------------------------------------
1 | # @sanity/visual-editing-types
2 |
3 | > [!WARNING]
4 | > Don't use this package directly, it's an internal package used by `@sanity/presentation-comlink` & `@sanity/visual-editing-csm`
5 |
--------------------------------------------------------------------------------
/packages/visual-editing-types/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 | })
7 |
--------------------------------------------------------------------------------
/packages/visual-editing-types/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/visual-editing-types/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "compilerOptions": {
4 | "rootDir": "src"
5 | },
6 | "include": ["src/**/*.ts"],
7 | "exclude": ["dist", "node_modules"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/visual-editing-types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base",
3 | "include": ["**/*.ts"],
4 | "exclude": ["dist", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/visual-editing-types/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "dev": {
6 | "persistent": false
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/visual-editing/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | dist-svelte
3 | storybook-static
4 |
--------------------------------------------------------------------------------
/packages/visual-editing/.gitignore:
--------------------------------------------------------------------------------
1 | src/**/*.d.ts
2 |
3 | *storybook.log
4 | storybook-static
5 |
--------------------------------------------------------------------------------
/packages/visual-editing/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/packages/visual-editing/.storybook/preview.ts:
--------------------------------------------------------------------------------
1 | import './tailwind.css'
2 | import {withThemeByClassName} from '@storybook/addon-themes'
3 | import type {Preview} from '@storybook/react'
4 |
5 | const darkModeMq = window.matchMedia('(prefers-color-scheme: dark)')
6 |
7 | export const decorators = [
8 | withThemeByClassName({
9 | themes: {
10 | light: 'light',
11 | dark: 'dark',
12 | },
13 | defaultTheme: darkModeMq.matches ? 'dark' : 'light',
14 | }),
15 | ]
16 |
17 | const preview: Preview = {
18 | parameters: {
19 | controls: {
20 | matchers: {
21 | color: /(background|color)$/i,
22 | date: /Date$/i,
23 | },
24 | },
25 | },
26 | }
27 |
28 | export default preview
29 |
--------------------------------------------------------------------------------
/packages/visual-editing/.storybook/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,
6 | body,
7 | #root {
8 | margin: 0;
9 | padding: 0;
10 | height: 100%;
11 | }
12 |
13 | body {
14 | -webkit-font-smoothing: antialiased;
15 | @apply bg-white text-black;
16 | }
17 |
18 | .dark body {
19 | @apply bg-black text-white;
20 | }
21 |
--------------------------------------------------------------------------------
/packages/visual-editing/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 | // `@sanity/visual-editing` isn't designed to be server side rendered
7 | runtime: 'browser',
8 | define: {
9 | 'process.env.NODE_ENV': 'production',
10 | },
11 | rollup: {
12 | ...baseConfig.rollup,
13 | treeshake: {
14 | preset: 'smallest',
15 | manualPureFunctions: ['createElement', 'forwardRef', 'memo', 'styled'],
16 | },
17 | },
18 | babel: {reactCompiler: true},
19 | reactCompilerOptions: {target: '18'},
20 | })
21 |
--------------------------------------------------------------------------------
/packages/visual-editing/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | },
5 | }
6 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/constants.ts:
--------------------------------------------------------------------------------
1 | export {VERCEL_STEGA_REGEX} from '@vercel/stega'
2 |
3 | /**
4 | * How long to wait after the last subscriber has unsubscribed before resetting the observable and disconnecting the listener
5 | * We want to keep the listener alive for a short while after the last subscriber has unsubscribed to avoid unnecessary reconnects
6 | */
7 | export const LISTENER_RESET_DELAY = 2000
8 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/create-data-attribute.ts:
--------------------------------------------------------------------------------
1 | export {
2 | type CreateDataAttribute,
3 | createDataAttribute,
4 | type CreateDataAttributeProps,
5 | type WithRequired,
6 | } from '@sanity/visual-editing-csm'
7 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/next-pages-router/VisualEditing.tsx:
--------------------------------------------------------------------------------
1 | import {lazy, Suspense} from 'react'
2 | import type {VisualEditingProps} from './VisualEditingComponent'
3 |
4 | const VisualEditingComponent = lazy(() => import('./VisualEditingComponent'))
5 |
6 | /**
7 | * @public
8 | */
9 | export function VisualEditing(props: VisualEditingProps): React.JSX.Element {
10 | return (
11 |
12 |
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/optimistic/context.ts:
--------------------------------------------------------------------------------
1 | import {createEmptyActor, type ActorRefFrom} from 'xstate'
2 | import {createDatasetMutator} from './state/datasetMutator'
3 |
4 | export type MutatorActor = ActorRefFrom>
5 | export type EmptyActor = typeof emptyActor
6 |
7 | export const emptyActor = createEmptyActor()
8 |
9 | export let actor: MutatorActor | EmptyActor = emptyActor
10 |
11 | export const listeners = new Set<() => void>()
12 |
13 | export function isEmptyActor(actor: MutatorActor | EmptyActor): actor is EmptyActor {
14 | return actor === emptyActor
15 | }
16 |
17 | export function setActor(nextActor: MutatorActor): void {
18 | actor = nextActor
19 | for (const onActorChange of listeners) {
20 | onActorChange()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/overlay-components/components/PointerEvents.tsx:
--------------------------------------------------------------------------------
1 | import type {FunctionComponent, HTMLAttributes, PropsWithChildren} from 'react'
2 |
3 | export const PointerEvents: FunctionComponent<
4 | PropsWithChildren>
5 | > = ({children, style, ...rest}) => {
6 | return (
7 |
8 | {children}
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/overlay-components/defineOverlayComponent.ts:
--------------------------------------------------------------------------------
1 | import type {ComponentProps} from 'react'
2 | import type {OverlayComponent, OverlayComponentProps} from '../types'
3 |
4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
5 | export function defineOverlayComponent, any>>(
6 | component: T,
7 | props?: Omit, keyof OverlayComponentProps>,
8 | ): {component: T; props: typeof props} {
9 | return {
10 | component: component,
11 | props: props,
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/overlay-components/defineOverlayComponents.ts:
--------------------------------------------------------------------------------
1 | import type {OverlayComponent, OverlayComponentResolver} from '../types'
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
4 | export function defineOverlayComponents(
5 | resolver: OverlayComponentResolver,
6 | ): typeof resolver {
7 | return resolver
8 | }
9 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/react-router/VisualEditing.tsx:
--------------------------------------------------------------------------------
1 | import {lazy, Suspense, useSyncExternalStore} from 'react'
2 | import type {VisualEditingProps} from './VisualEditingComponent'
3 |
4 | const VisualEditingComponent = lazy(() => import('./VisualEditingComponent'))
5 |
6 | const subcribe = () => () => {}
7 |
8 | /**
9 | * @public
10 | */
11 | export function VisualEditing(props: VisualEditingProps): React.JSX.Element | null {
12 | const mounted = useSyncExternalStore(
13 | subcribe,
14 | () => true,
15 | () => false,
16 | )
17 |
18 | // Don't render Suspense while hydration, this enables compatibility with React v17 apps
19 | // where Suspense where a browser-only API
20 | if (!mounted) {
21 | return null
22 | }
23 |
24 | return (
25 |
26 |
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/react/useOptimisticActor.ts:
--------------------------------------------------------------------------------
1 | import {useCallback, useMemo, useSyncExternalStore} from 'react'
2 | import {
3 | actor,
4 | emptyActor,
5 | isEmptyActor,
6 | listeners,
7 | type EmptyActor,
8 | type MutatorActor,
9 | } from '../optimistic/context'
10 |
11 | export function useOptimisticActor(): MutatorActor | EmptyActor {
12 | const subscribe = useCallback((listener: () => void) => {
13 | listeners.add(listener)
14 | return () => listeners.delete(listener)
15 | }, [])
16 |
17 | const actorRef = useSyncExternalStore(
18 | subscribe,
19 | () => actor,
20 | () => emptyActor,
21 | )
22 |
23 | return actorRef
24 | }
25 |
26 | export function useOptimisticActorReady(): boolean {
27 | const actor = useOptimisticActor()
28 | return useMemo(() => !isEmptyActor(actor), [actor])
29 | }
30 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/remix/VisualEditing.tsx:
--------------------------------------------------------------------------------
1 | import {lazy, Suspense, useSyncExternalStore} from 'react'
2 | import type {VisualEditingProps} from './VisualEditingComponent'
3 |
4 | const VisualEditingComponent = lazy(() => import('./VisualEditingComponent'))
5 |
6 | const subcribe = () => () => {}
7 |
8 | /**
9 | * @public
10 | */
11 | export function VisualEditing(props: VisualEditingProps): React.JSX.Element | null {
12 | const mounted = useSyncExternalStore(
13 | subcribe,
14 | () => true,
15 | () => false,
16 | )
17 |
18 | // Don't render Suspense while hydration, this enables compatibility with React v17 apps
19 | // where Suspense where a browser-only API
20 | if (!mounted) {
21 | return null
22 | }
23 |
24 | return (
25 |
26 |
27 |
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/stories/examples/media/Figure.tsx:
--------------------------------------------------------------------------------
1 | export function Figure(props: {
2 | caption?: React.ReactNode
3 | className?: string
4 | img?: React.ReactNode
5 | }): React.JSX.Element {
6 | const {caption, className, img} = props
7 |
8 | return (
9 |
10 | {img}
11 |
12 |
13 | {caption}
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/stories/examples/media/Footer.tsx:
--------------------------------------------------------------------------------
1 | import {Link} from './Link'
2 |
3 | export function Footer(): React.JSX.Element {
4 | return (
5 |
6 |
7 |
8 | ● Media
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/stories/examples/media/Link.tsx:
--------------------------------------------------------------------------------
1 | import LinkTo from '@storybook/addon-links/react'
2 |
3 | export function Link(props: {
4 | children: React.ReactNode
5 | className?: string
6 | kind?: string
7 | story?: string
8 | name?: string
9 | }): React.JSX.Element {
10 | const {className, kind, story, name, children} = props
11 |
12 | return (
13 |
20 | {children}
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/stories/examples/media/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import {Link} from './Link'
2 |
3 | export function Navbar(): React.JSX.Element {
4 | return (
5 |
6 |
7 |
8 | ● Media
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/History.tsx:
--------------------------------------------------------------------------------
1 | import {useEffect, type FunctionComponent} from 'react'
2 | import type {HistoryAdapter, VisualEditingNode} from '../types'
3 |
4 | /**
5 | * @internal
6 | */
7 | export const History: FunctionComponent<{
8 | comlink: VisualEditingNode
9 | history?: HistoryAdapter
10 | }> = (props) => {
11 | const {comlink, history} = props
12 |
13 | useEffect(() => {
14 | return comlink?.on('presentation/navigate', (data) => {
15 | history?.update(data)
16 | })
17 | }, [comlink, history])
18 |
19 | useEffect(() => {
20 | if (history) {
21 | return history.subscribe((update) => {
22 | update.title = update.title || document.title
23 | comlink?.post('visual-editing/navigate', update)
24 | })
25 | }
26 | return
27 | }, [comlink, history])
28 |
29 | return null
30 | }
31 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/Meta.ts:
--------------------------------------------------------------------------------
1 | import {useEffect, type FunctionComponent} from 'react'
2 | import type {VisualEditingNode} from '../types'
3 |
4 | /**
5 | * @internal
6 | */
7 | export const Meta: FunctionComponent<{
8 | comlink: VisualEditingNode
9 | }> = (props) => {
10 | const {comlink} = props
11 |
12 | useEffect(() => {
13 | const sendMeta = () => {
14 | comlink.post('visual-editing/meta', {title: document.title})
15 | }
16 |
17 | const observer = new MutationObserver(([mutation]) => {
18 | if (mutation.target.nodeName === 'TITLE') {
19 | sendMeta()
20 | }
21 | })
22 |
23 | observer.observe(document.head, {
24 | subtree: true,
25 | characterData: true,
26 | childList: true,
27 | })
28 |
29 | sendMeta()
30 |
31 | return () => observer.disconnect()
32 | }, [comlink])
33 |
34 | return null
35 | }
36 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/OverlayDragGroupRect.tsx:
--------------------------------------------------------------------------------
1 | import type {FunctionComponent} from 'react'
2 | import type {OverlayRect} from '../types'
3 |
4 | export const OverlayDragGroupRect: FunctionComponent<{
5 | dragGroupRect: OverlayRect
6 | }> = ({dragGroupRect}) => {
7 | return (
8 |
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/OverlayMinimapPrompt.tsx:
--------------------------------------------------------------------------------
1 | import {ExpandIcon} from '@sanity/icons'
2 | import {Card, Flex, Hotkeys, Text} from '@sanity/ui/_visual-editing'
3 | import type {FunctionComponent} from 'react'
4 | import {styled} from 'styled-components'
5 |
6 | const Root = styled(Card)`
7 | position: fixed;
8 | bottom: 2rem;
9 | left: 2rem;
10 | `
11 |
12 | export const OverlayMinimapPrompt: FunctionComponent = () => {
13 | return (
14 |
15 |
16 |
17 | Zoom Out
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/preview/PreviewSnapshotsContext.tsx:
--------------------------------------------------------------------------------
1 | import type {PreviewSnapshot} from '@sanity/presentation-comlink'
2 | import {createContext} from 'react'
3 |
4 | export type PreviewSnapshotsContextValue = PreviewSnapshot[]
5 |
6 | export const PreviewSnapshotsContext = createContext(null)
7 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/preview/usePreviewSnapshots.ts:
--------------------------------------------------------------------------------
1 | import {useContext} from 'react'
2 | import {PreviewSnapshotsContext, type PreviewSnapshotsContextValue} from './PreviewSnapshotsContext'
3 |
4 | export function usePreviewSnapshots(): PreviewSnapshotsContextValue {
5 | const context = useContext(PreviewSnapshotsContext)
6 |
7 | if (!context) {
8 | throw new Error('Preview Snapshots context is missing')
9 | }
10 |
11 | return context
12 | }
13 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/schema/SchemaContext.tsx:
--------------------------------------------------------------------------------
1 | import type {
2 | DocumentSchema,
3 | SanityNode,
4 | SanityStegaNode,
5 | TypeSchema,
6 | } from '@sanity/presentation-comlink'
7 | import {createContext} from 'react'
8 | import type {OverlayElementField, OverlayElementParent} from '../../types'
9 |
10 | export interface SchemaContextValue {
11 | getField: (node: SanityNode | SanityStegaNode) => {
12 | field: OverlayElementField
13 | parent: OverlayElementParent
14 | }
15 | getType: (
16 | node: SanityNode | SanityStegaNode | string,
17 | type?: T,
18 | ) => T extends 'document' ? DocumentSchema | undefined : TypeSchema | undefined
19 | }
20 |
21 | export const SchemaContext = createContext(null)
22 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/schema/useSchema.ts:
--------------------------------------------------------------------------------
1 | import {useContext} from 'react'
2 | import {SchemaContext, type SchemaContextValue} from './SchemaContext'
3 |
4 | export function useSchema(): SchemaContextValue {
5 | const context = useContext(SchemaContext)
6 |
7 | if (!context) {
8 | throw new Error('Schema context is missing')
9 | }
10 |
11 | return context
12 | }
13 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/shared-state/SharedStateContext.ts:
--------------------------------------------------------------------------------
1 | import {createContext} from 'react'
2 | import type {VisualEditingNode} from '../../types'
3 | import type {SharedStateStore} from './sharedStateStore'
4 |
5 | export interface SharedStateContextValue {
6 | comlink?: VisualEditingNode
7 | store: SharedStateStore
8 | }
9 |
10 | export const SharedStateContext = createContext(null)
11 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/shared-state/sharedStateStore.ts:
--------------------------------------------------------------------------------
1 | import type {SerializableObject} from '@sanity/presentation-comlink'
2 |
3 | export interface SharedStateStore {
4 | getState: () => T
5 | setState: (fn: (state: T) => T) => void
6 | subscribe: (listener: () => void) => () => void
7 | }
8 |
9 | const createStore = (initialState: T): SharedStateStore => {
10 | let state = initialState
11 | const listeners = new Set<() => void>()
12 |
13 | const getState = () => state
14 | const setState = (fn: (state: T) => T) => {
15 | state = fn(state)
16 | listeners.forEach((l) => l())
17 | }
18 |
19 | const subscribe = (listener: () => void) => {
20 | listeners.add(listener)
21 | return () => listeners.delete(listener)
22 | }
23 |
24 | return {getState, setState, subscribe}
25 | }
26 |
27 | export const store = createStore({})
28 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/shared-state/useSharedState.ts:
--------------------------------------------------------------------------------
1 | import {useCallback, useContext, useSyncExternalStore} from 'react'
2 | import {SharedStateContext} from './SharedStateContext'
3 |
4 | export function useSharedState<
5 | T extends boolean | null | number | object | string | undefined | unknown = unknown,
6 | >(key: string): T {
7 | const context = useContext(SharedStateContext)
8 | if (!context) {
9 | throw new Error('useSharedState must be used within a SharedStateProvider')
10 | }
11 |
12 | const {store} = context
13 |
14 | const value = useSyncExternalStore(
15 | store.subscribe,
16 | useCallback(() => store.getState()[key] as T, [key, store]),
17 | )
18 |
19 | return value
20 | }
21 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/telemetry/TelemetryProvider.tsx:
--------------------------------------------------------------------------------
1 | import {useCallback, type FunctionComponent, type PropsWithChildren} from 'react'
2 | import type {VisualEditingNode} from '../../types'
3 | import {events, TelemetryContext, type TelemetryContextValue} from './TelemetryContext'
4 |
5 | export const TelemetryProvider: FunctionComponent<
6 | PropsWithChildren<{comlink?: VisualEditingNode}>
7 | > = ({children, comlink}) => {
8 | const log = useCallback(
9 | (name, data) => {
10 | if (!comlink) return
11 |
12 | const event = events[name]
13 |
14 | if (!event) {
15 | throw new Error(`Telemetry event: ${name} does not exist`)
16 | } else {
17 | comlink.post('visual-editing/telemetry-log', {event, data})
18 | }
19 | },
20 | [comlink],
21 | )
22 |
23 | return {children}
24 | }
25 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/ui/telemetry/useTelemetry.tsx:
--------------------------------------------------------------------------------
1 | import {useContext} from 'react'
2 | import {TelemetryContext, type TelemetryContextValue} from './TelemetryContext'
3 |
4 | export function useTelemetry(): TelemetryContextValue {
5 | const context = useContext(TelemetryContext)
6 |
7 | if (!context) {
8 | throw new Error('Telemetry context is missing')
9 | }
10 |
11 | return context
12 | }
13 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/util/elements.ts:
--------------------------------------------------------------------------------
1 | import type {ElementNode} from '../types'
2 |
3 | export const isElementNode = (target: EventTarget | null): target is ElementNode => {
4 | return target instanceof HTMLElement || target instanceof SVGElement
5 | }
6 |
7 | export function findNonInlineElement(element: ElementNode): ElementNode | null {
8 | const {display} = window.getComputedStyle(element)
9 |
10 | if (display !== 'inline') return element
11 |
12 | const parent = element.parentElement
13 |
14 | if (!parent) return null
15 |
16 | return findNonInlineElement(parent)
17 | }
18 |
19 | export const findOverlayElement = (
20 | el: EventTarget | ElementNode | null | undefined,
21 | ): ElementNode | null => {
22 | if (!el || !isElementNode(el)) {
23 | return null
24 | }
25 |
26 | if (el.dataset?.['sanityOverlayElement']) {
27 | return el
28 | }
29 |
30 | return findOverlayElement(el.parentElement)
31 | }
32 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/util/getLinkHref.ts:
--------------------------------------------------------------------------------
1 | export function getLinkHref(href: string, referer: string): string {
2 | try {
3 | const parsed = new URL(href, typeof location === 'undefined' ? undefined : location.origin)
4 | if (parsed.hash) {
5 | const hash = new URL(getLinkHref(parsed.hash.slice(1), referer))
6 | return `${parsed.origin}${parsed.pathname}${parsed.search}#${hash.pathname}${hash.search}`
7 | }
8 | parsed.searchParams.set('preview', referer)
9 | return parsed.toString()
10 | } catch {
11 | return href
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/visual-editing/src/util/randomKey.ts:
--------------------------------------------------------------------------------
1 | import getRandomValues from 'get-random-values-esm'
2 |
3 | // WHATWG crypto RNG - https://w3c.github.io/webcrypto/Overview.html
4 | function whatwgRNG(length = 16) {
5 | const rnds8 = new Uint8Array(length)
6 | getRandomValues(rnds8)
7 | return rnds8
8 | }
9 |
10 | const getByteHexTable = (() => {
11 | let table: string[]
12 | return () => {
13 | if (table) {
14 | return table
15 | }
16 | table = []
17 | for (let i = 0; i < 256; ++i) {
18 | table[i] = (i + 0x100).toString(16).slice(1)
19 | }
20 | return table
21 | }
22 | })()
23 |
24 | export function randomKey(length?: number): string {
25 | const table = getByteHexTable()
26 | return whatwgRNG(length)
27 | .reduce((str, n) => str + table[n], '')
28 | .slice(0, length)
29 | }
30 |
--------------------------------------------------------------------------------
/packages/visual-editing/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 | lib: 'svelte',
7 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
8 | // for more information about preprocessors
9 | preprocess: vitePreprocess(),
10 |
11 | kit: {
12 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
13 | // If your environment is not supported or you settled on a specific environment, switch out the adapter.
14 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
15 | adapter: adapter(),
16 | },
17 | }
18 |
19 | export default config
20 |
--------------------------------------------------------------------------------
/packages/visual-editing/svelte/global.d.ts:
--------------------------------------------------------------------------------
1 | import {VisualEditingLocals} from './types'
2 |
3 | declare global {
4 | namespace App {
5 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type
6 | interface Locals extends VisualEditingLocals {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/visual-editing/svelte/index.ts:
--------------------------------------------------------------------------------
1 | export {handlePreview} from './hooks'
2 | export {isPreviewing, setPreviewing} from './previewStore'
3 | export type {VisualEditingProps, HandlePreviewOptions, VisualEditingLocals} from './types'
4 | export {default as VisualEditing} from './VisualEditing.svelte'
5 | export {useOptimistic} from './optimistic/useOptimistic'
6 |
--------------------------------------------------------------------------------
/packages/visual-editing/svelte/optimistic/optimisticActor.ts:
--------------------------------------------------------------------------------
1 | import {actor, listeners} from '@sanity/visual-editing/optimistic'
2 | import {readable} from 'svelte/store'
3 |
4 | export const optimisticActor = readable(actor, (set) => {
5 | const listener = () => {
6 | set(actor)
7 | }
8 |
9 | listeners.add(listener)
10 |
11 | return () => {
12 | actor.stop()
13 | listeners.delete(listener)
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/packages/visual-editing/svelte/previewStore.ts:
--------------------------------------------------------------------------------
1 | import {readonly, writable} from 'svelte/store'
2 |
3 | const previewStore = writable(false)
4 |
5 | /**
6 | * @beta
7 | */
8 | export const isPreviewing = readonly(previewStore)
9 |
10 | /**
11 | * @beta
12 | */
13 | export const setPreviewing = previewStore.set
14 |
--------------------------------------------------------------------------------
/packages/visual-editing/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/stories/**/*.{ts,tsx}'],
8 | darkMode: 'selector',
9 | theme,
10 | plugins: [require('@tailwindcss/typography')],
11 | }
12 |
--------------------------------------------------------------------------------
/packages/visual-editing/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@sanity/pkg-utils/tsconfig/strictest.json",
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "noUnusedLocals": false,
7 | "noUnusedParameters": false
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/visual-editing/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", "src/stories"]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/visual-editing/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["./.svelte-kit/tsconfig.json", "./tsconfig.base"],
3 | "include": ["**/*.ts", "**/*.tsx"],
4 | "exclude": ["dist", "dist-svelte", "node_modules"],
5 | "compilerOptions": {
6 | "jsx": "react-jsx"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/visual-editing/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "extends": ["//"],
4 | "tasks": {
5 | "build": {
6 | "env": ["NODE_ENV"]
7 | },
8 | "build-storybook": {
9 | "dependsOn": ["build"],
10 | "outputs": ["storybook-static/**"]
11 | },
12 | "storybook": {
13 | "persistent": false
14 | },
15 | "dev": {
16 | "persistent": false
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/visual-editing/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | environment: 'happy-dom',
6 | typecheck: {
7 | tsconfig: 'tsconfig.build.json',
8 | },
9 | },
10 | })
11 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'apps/*'
3 | - 'packages/*'
4 | - 'packages/@repo/*'
5 | - 'packages/comlink/playground'
6 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3 | "plugins": ["node-workspace"],
4 | "last-release-sha": "9bae0e6b2bc088c57ad305732f4606314a693012",
5 | "packages": {
6 | "packages/comlink": {},
7 | "packages/core-loader": {},
8 | "packages/insert-menu": {},
9 | "packages/next-loader": {},
10 | "packages/presentation-comlink": {},
11 | "packages/preview-url-secret": {},
12 | "packages/react-loader": {},
13 | "packages/svelte-loader": {},
14 | "packages/vercel-protection-bypass": {},
15 | "packages/visual-editing": {},
16 | "packages/visual-editing-csm": {},
17 | "packages/visual-editing-types": {}
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "tui",
4 | "globalDependencies": [".npmrc", ".prettierrc.cjs", "**/.env.*local"],
5 | "tasks": {
6 | "build": {
7 | "outputs": [".svelte-kit/**", "dist/**", "dist-svelte/**"],
8 | "dependsOn": ["^build"]
9 | },
10 | "test": {
11 | "dependsOn": ["^build"],
12 | "cache": false
13 | },
14 | "lint": {
15 | "outputLogs": "errors-only"
16 | },
17 | "preview": {
18 | "dependsOn": ["^build"]
19 | },
20 | "dev": {
21 | "cache": false,
22 | "persistent": true
23 | },
24 | "react-compiler-healthcheck": {
25 | "cache": false
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/vitest.workspace.js:
--------------------------------------------------------------------------------
1 | import {defineWorkspace} from 'vitest/config'
2 |
3 | export default defineWorkspace([
4 | './packages/presentation-comlink/vitest.config.ts',
5 | './packages/preview-url-secret/vitest.config.ts',
6 | './packages/react-loader/vitest.config.ts',
7 | './packages/visual-editing/vitest.config.ts',
8 | './packages/core-loader/vitest.config.ts',
9 | './packages/svelte-loader/vitest.config.ts',
10 | './packages/visual-editing-csm/vitest.config.ts',
11 | ])
12 |
--------------------------------------------------------------------------------