next-sanity
Version:
Sanity.io toolkit for Next.js
1 lines • 12.7 kB
Source Map (JSON)
{"version":3,"file":"VisualEditing.cjs","sources":["../../src/visual-editing/client-component/utils.ts","../../src/visual-editing/client-component/VisualEditing.tsx"],"sourcesContent":["/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/path-has-prefix.ts#L10-L17\n * Checks if a given path starts with a given prefix. It ensures it matches\n * exactly without containing extra chars. e.g. prefix /docs should replace\n * for /docs, /docs/, /docs/a but not /docsss\n * @param path The path to check.\n * @param prefix The prefix to check against.\n */\nfunction pathHasPrefix(path: string, prefix: string): boolean {\n if (typeof path !== 'string') {\n return false\n }\n\n const {pathname} = parsePath(path)\n return pathname === prefix || pathname.startsWith(`${prefix}/`)\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/parse-path.ts#L6-L22\n * Given a path this function will find the pathname, query and hash and return\n * them. This is useful to parse full paths on the client side.\n * @param path A path to parse e.g. /foo/bar?id=1#hash\n */\nfunction parsePath(path: string): {\n pathname: string\n query: string\n hash: string\n} {\n const hashIndex = path.indexOf('#')\n const queryIndex = path.indexOf('?')\n const hasQuery = queryIndex > -1 && (hashIndex < 0 || queryIndex < hashIndex)\n\n if (hasQuery || hashIndex > -1) {\n return {\n pathname: path.substring(0, hasQuery ? queryIndex : hashIndex),\n query: hasQuery ? path.substring(queryIndex, hashIndex > -1 ? hashIndex : undefined) : '',\n hash: hashIndex > -1 ? path.slice(hashIndex) : '',\n }\n }\n\n return {pathname: path, query: '', hash: ''}\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/add-path-prefix.ts#L3C1-L14C2\n * Adds the provided prefix to the given path. It first ensures that the path\n * is indeed starting with a slash.\n */\nexport function addPathPrefix(path: string, prefix?: string): string {\n if (!path.startsWith('/') || !prefix) {\n return path\n }\n // If the path is exactly '/' then return just the prefix\n if (path === '/' && prefix) {\n return prefix\n }\n\n const {pathname, query, hash} = parsePath(path)\n return `${prefix}${pathname}${query}${hash}`\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/remove-path-prefix.ts#L3-L39\n * Given a path and a prefix it will remove the prefix when it exists in the\n * given path. It ensures it matches exactly without containing extra chars\n * and if the prefix is not there it will be noop.\n *\n * @param path The path to remove the prefix from.\n * @param prefix The prefix to be removed.\n */\nexport function removePathPrefix(path: string, prefix: string): string {\n // If the path doesn't start with the prefix we can return it as is. This\n // protects us from situations where the prefix is a substring of the path\n // prefix such as:\n //\n // For prefix: /blog\n //\n // /blog -> true\n // /blog/ -> true\n // /blog/1 -> true\n // /blogging -> false\n // /blogging/ -> false\n // /blogging/1 -> false\n if (!pathHasPrefix(path, prefix)) {\n return path\n }\n\n // Remove the prefix from the path via slicing.\n const withoutPrefix = path.slice(prefix.length)\n\n // If the path without the prefix starts with a `/` we can return it as is.\n if (withoutPrefix.startsWith('/')) {\n return withoutPrefix\n }\n\n // If the path without the prefix doesn't start with a `/` we need to add it\n // back to the path to make sure it's a valid path.\n return `/${withoutPrefix}`\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/client/normalize-trailing-slash.ts#L16\n * Normalizes the trailing slash of a path according to the `trailingSlash` option\n * in `next.config.js`.\n */\nexport const normalizePathTrailingSlash = (path: string, trailingSlash: boolean): string => {\n const {pathname, query, hash} = parsePath(path)\n if (trailingSlash) {\n if (pathname.endsWith('/')) {\n return `${pathname}${query}${hash}`\n }\n return `${pathname}/${query}${hash}`\n }\n\n return `${removeTrailingSlash(pathname)}${query}${hash}`\n}\n\n/**\n * From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/shared/lib/router/utils/remove-trailing-slash.ts#L8\n * Removes the trailing slash for a given route or page path. Preserves the\n * root page. Examples:\n * - `/foo/bar/` -> `/foo/bar`\n * - `/foo/bar` -> `/foo/bar`\n * - `/` -> `/`\n */\nfunction removeTrailingSlash(route: string) {\n return route.replace(/\\/$/, '') || '/'\n}\n","import {\n type HistoryAdapter,\n type HistoryAdapterNavigate,\n type HistoryRefresh,\n VisualEditing as VisualEditingComponent,\n type VisualEditingOptions,\n} from '@sanity/visual-editing/react'\nimport {usePathname, useRouter, useSearchParams} from 'next/navigation.js'\nimport {revalidateRootLayout} from 'next-sanity/visual-editing/server-actions'\nimport {useCallback, useEffect, useMemo, useRef, useState} from 'react'\n\nimport {addPathPrefix, normalizePathTrailingSlash, removePathPrefix} from './utils'\n\n/**\n * @public\n */\nexport interface VisualEditingProps extends Omit<VisualEditingOptions, 'history'> {\n /**\n * @deprecated The histoy adapter is already implemented\n */\n history?: never\n /**\n * If next.config.ts is configured with a basePath we try to configure it automatically,\n * you can disable this by setting basePath to ''.\n * @example basePath=\"/my-custom-base-path\"\n * @alpha experimental and may change without notice\n * @defaultValue process.env.__NEXT_ROUTER_BASEPATH || ''\n */\n basePath?: string\n /**\n * If next.config.ts is configured with a `trailingSlash` we try to detect it automatically,\n * it can be controlled manually by passing a boolean.\n * @example trailingSlash={true}\n * @alpha experimental and may change without notice\n * @defaultValue Boolean(process.env.__NEXT_TRAILING_SLASH)\n */\n trailingSlash?: boolean\n}\n\nexport default function VisualEditing(props: VisualEditingProps): React.JSX.Element | null {\n const {basePath = '', components, refresh, trailingSlash = false, zIndex} = props\n\n const router = useRouter()\n const routerRef = useRef(router)\n const [navigate, setNavigate] = useState<HistoryAdapterNavigate | undefined>()\n\n useEffect(() => {\n routerRef.current = router\n }, [router])\n\n const history = useMemo<HistoryAdapter>(\n () => ({\n subscribe: (_navigate) => {\n setNavigate(() => _navigate)\n return () => setNavigate(undefined)\n },\n update: (update) => {\n switch (update.type) {\n case 'push':\n return routerRef.current.push(removePathPrefix(update.url, basePath))\n case 'pop':\n return routerRef.current.back()\n case 'replace':\n return routerRef.current.replace(removePathPrefix(update.url, basePath))\n default:\n throw new Error(`Unknown update type: ${update.type}`)\n }\n },\n }),\n [basePath],\n )\n\n const pathname = usePathname()\n const searchParams = useSearchParams()\n useEffect(() => {\n if (navigate) {\n navigate({\n type: 'push',\n url: normalizePathTrailingSlash(\n addPathPrefix(`${pathname}${searchParams?.size ? `?${searchParams}` : ''}`, basePath),\n trailingSlash,\n ),\n })\n }\n }, [basePath, navigate, pathname, searchParams, trailingSlash])\n\n const handleRefresh = useCallback(\n (payload: HistoryRefresh) => {\n if (refresh) return refresh(payload)\n\n const manualFastRefresh = () => {\n // eslint-disable-next-line no-console\n console.debug(\n 'Live preview is setup, calling router.refresh() to refresh the server components without refetching cached data',\n )\n routerRef.current.refresh()\n return Promise.resolve()\n }\n const manualFallbackRefresh = () => {\n // eslint-disable-next-line no-console\n console.debug(\n 'No loaders in live mode detected, or preview kit setup, revalidating root layout',\n )\n return revalidateRootLayout()\n }\n const mutationFastRefresh = (): false => {\n // eslint-disable-next-line no-console\n console.debug(\n 'Live preview is setup, mutation is skipped assuming its handled by the live preview',\n )\n return false\n }\n const mutationFallbackRefresh = () => {\n // eslint-disable-next-line no-console\n console.debug(\n 'No loaders in live mode detected, or preview kit setup, revalidating root layout',\n )\n return revalidateRootLayout()\n }\n\n switch (payload.source) {\n case 'manual':\n return payload.livePreviewEnabled ? manualFastRefresh() : manualFallbackRefresh()\n case 'mutation':\n return payload.livePreviewEnabled ? mutationFastRefresh() : mutationFallbackRefresh()\n default:\n throw new Error('Unknown refresh source', {cause: payload})\n }\n },\n [refresh],\n )\n\n return (\n <VisualEditingComponent\n components={components}\n history={history}\n portal\n refresh={handleRefresh}\n zIndex={zIndex}\n />\n )\n}\n"],"names":["useRouter","useRef","useState","useEffect","useMemo","usePathname","useSearchParams","useCallback","revalidateRootLayout","jsx","VisualEditingComponent"],"mappings":"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"}