next-sanity
Version:
Sanity.io toolkit for Next.js
145 lines (144 loc) • 5.8 kB
JavaScript
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { VisualEditing as VisualEditing$1 } from "@sanity/visual-editing/react";
import { jsx } from "react/jsx-runtime";
import { useCallback, useEffect, useMemo, useState } from "react";
/**
* From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/path-has-prefix.ts#L10-L17
* Checks if a given path starts with a given prefix. It ensures it matches
* exactly without containing extra chars. e.g. prefix /docs should replace
* for /docs, /docs/, /docs/a but not /docsss
* @param path The path to check.
* @param prefix The prefix to check against.
*/
function pathHasPrefix(path, prefix) {
if (typeof path !== "string") return false;
const { pathname } = parsePath(path);
return pathname === prefix || pathname.startsWith(`${prefix}/`);
}
/**
* From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/parse-path.ts#L6-L22
* Given a path this function will find the pathname, query and hash and return
* them. This is useful to parse full paths on the client side.
* @param path A path to parse e.g. /foo/bar?id=1#hash
*/
function parsePath(path) {
const hashIndex = path.indexOf("#");
const queryIndex = path.indexOf("?");
const hasQuery = queryIndex > -1 && (hashIndex < 0 || queryIndex < hashIndex);
if (hasQuery || hashIndex > -1) return {
pathname: path.substring(0, hasQuery ? queryIndex : hashIndex),
query: hasQuery ? path.substring(queryIndex, hashIndex > -1 ? hashIndex : void 0) : "",
hash: hashIndex > -1 ? path.slice(hashIndex) : ""
};
return {
pathname: path,
query: "",
hash: ""
};
}
/**
* From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/add-path-prefix.ts#L3C1-L14C2
* Adds the provided prefix to the given path. It first ensures that the path
* is indeed starting with a slash.
*/
function addPathPrefix(path, prefix) {
if (!path.startsWith("/") || !prefix) return path;
if (path === "/" && prefix) return prefix;
const { pathname, query, hash } = parsePath(path);
return `${prefix}${pathname}${query}${hash}`;
}
/**
* From: https://github.com/vercel/next.js/blob/5469e6427b54ab7e9876d4c85b47f9c3afdc5c1f/packages/next/src/shared/lib/router/utils/remove-path-prefix.ts#L3-L39
* Given a path and a prefix it will remove the prefix when it exists in the
* given path. It ensures it matches exactly without containing extra chars
* and if the prefix is not there it will be noop.
*
* @param path The path to remove the prefix from.
* @param prefix The prefix to be removed.
*/
function removePathPrefix(path, prefix) {
if (!pathHasPrefix(path, prefix)) return path;
const withoutPrefix = path.slice(prefix.length);
if (withoutPrefix.startsWith("/")) return withoutPrefix;
return `/${withoutPrefix}`;
}
/**
* From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/client/normalize-trailing-slash.ts#L16
* Normalizes the trailing slash of a path according to the `trailingSlash` option
* in `next.config.js`.
*/
const normalizePathTrailingSlash = (path, trailingSlash) => {
const { pathname, query, hash } = parsePath(path);
if (trailingSlash) {
if (pathname.endsWith("/")) return `${pathname}${query}${hash}`;
return `${pathname}/${query}${hash}`;
}
return `${removeTrailingSlash(pathname)}${query}${hash}`;
};
/**
* From: https://github.com/vercel/next.js/blob/dfe7fc03e2268e7cb765dce6a89e02c831c922d5/packages/next/src/shared/lib/router/utils/remove-trailing-slash.ts#L8
* Removes the trailing slash for a given route or page path. Preserves the
* root page. Examples:
* - `/foo/bar/` -> `/foo/bar`
* - `/foo/bar` -> `/foo/bar`
* - `/` -> `/`
*/
function removeTrailingSlash(route) {
return route.replace(/\/$/, "") || "/";
}
function VisualEditing(props) {
const { basePath = "", plugins, components, refresh, trailingSlash = false, zIndex, onPerspectiveChange } = props;
const router = useRouter();
const [navigate, setNavigate] = useState();
const history = useMemo(() => ({
subscribe: (_navigate) => {
setNavigate(() => _navigate);
return () => setNavigate(void 0);
},
update: (update) => {
switch (update.type) {
case "push": return router.push(removePathPrefix(update.url, basePath));
case "pop": return router.back();
case "replace": return router.replace(removePathPrefix(update.url, basePath));
default: throw new Error(`Unknown update type`, { cause: update });
}
}
}), [basePath, router]);
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (navigate) navigate({
type: "push",
url: normalizePathTrailingSlash(addPathPrefix(`${pathname}${searchParams?.size ? `?${searchParams.toString()}` : ""}`, basePath), trailingSlash)
});
}, [
basePath,
navigate,
pathname,
searchParams,
trailingSlash
]);
const handleRefresh = useCallback((payload) => {
switch (payload.source) {
case "manual":
router.refresh();
break;
case "mutation":
console.debug("<VisualEditing /> refresh called with source \"mutation\", if you want automatic refresh when this happens, or silence this message, provide your own handler to the refresh prop");
return false;
default: throw new Error("Unknown refresh source", { cause: payload });
}
return new Promise((resolve) => setTimeout(resolve, 1e3));
}, [router]);
return /* @__PURE__ */ jsx(VisualEditing$1, {
plugins,
components,
history,
portal: true,
refresh: refresh ?? handleRefresh,
onPerspectiveChange,
zIndex
});
}
export { VisualEditing as default };
//# sourceMappingURL=VisualEditing.js.map