@cloudcannon/editable-regions 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +5 -0
  3. package/components/editable-array-component.ts +26 -0
  4. package/components/editable-array-item-component.ts +26 -0
  5. package/components/editable-component-component.ts +26 -0
  6. package/components/editable-image-component.ts +26 -0
  7. package/components/editable-snippet-component.ts +30 -0
  8. package/components/editable-source-component.ts +26 -0
  9. package/components/editable-text-component.ts +26 -0
  10. package/components/index.ts +35 -0
  11. package/components/ui/editable-array-item-controls.ts +123 -0
  12. package/components/ui/editable-component-controls.ts +57 -0
  13. package/components/ui/editable-region-error-card.ts +77 -0
  14. package/helpers/checks.ts +126 -0
  15. package/helpers/cloudcannon.ts +67 -0
  16. package/helpers/hydrate-editable-regions.ts +71 -0
  17. package/integrations/astro/astro-integration.mjs +110 -0
  18. package/integrations/astro/index.mjs +193 -0
  19. package/integrations/astro/modules/actions.js +74 -0
  20. package/integrations/astro/modules/assets.js +38 -0
  21. package/integrations/astro/modules/client-router.astro +5 -0
  22. package/integrations/astro/modules/content.js +116 -0
  23. package/integrations/astro/modules/i18n.js +76 -0
  24. package/integrations/astro/modules/image.astro +33 -0
  25. package/integrations/astro/modules/middleware.js +27 -0
  26. package/integrations/astro/modules/picture.astro +7 -0
  27. package/integrations/astro/modules/transitions.js +63 -0
  28. package/integrations/astro/react-renderer.mjs +40 -0
  29. package/integrations/react.mjs +32 -0
  30. package/nodes/editable-array-item.ts +427 -0
  31. package/nodes/editable-array.ts +241 -0
  32. package/nodes/editable-component.ts +273 -0
  33. package/nodes/editable-image.ts +253 -0
  34. package/nodes/editable-snippet.ts +148 -0
  35. package/nodes/editable-source.ts +245 -0
  36. package/nodes/editable-text.ts +163 -0
  37. package/nodes/editable.ts +471 -0
  38. package/nodes/index.ts +8 -0
  39. package/package.json +64 -0
  40. package/styles/editable-array-item.css +8 -0
  41. package/styles/editable-component.css +8 -0
  42. package/styles/editable-image.css +4 -0
  43. package/styles/editable-snippet.css +12 -0
  44. package/styles/editable-source.css +3 -0
  45. package/styles/editable-text.css +3 -0
  46. package/styles/index.css +101 -0
  47. package/styles/index.ts +6 -0
  48. package/styles/ui/editable-component-controls.css +104 -0
  49. package/styles/ui/editable-region-error-card.css +25 -0
  50. package/types/astro.d.ts +19 -0
  51. package/types/cloudcannon.d.ts +11 -0
  52. package/types/modules.d.ts +12 -0
  53. package/types/react.d.ts +5 -0
  54. package/types/vite.d.ts +9 -0
@@ -0,0 +1,71 @@
1
+ import {
2
+ type Editable,
3
+ EditableArray,
4
+ EditableArrayItem,
5
+ EditableComponent,
6
+ EditableImage,
7
+ EditableSource,
8
+ EditableText,
9
+ } from "../nodes";
10
+ import { hasEditable } from "./checks";
11
+
12
+ const editableMap: Record<string, typeof Editable | undefined> = {
13
+ array: EditableArray,
14
+ "array-item": EditableArrayItem,
15
+ component: EditableComponent,
16
+ image: EditableImage,
17
+ source: EditableSource,
18
+ text: EditableText,
19
+ };
20
+
21
+ export const dehydrateDataEditableRegions = (root: Element) => {
22
+ if (root instanceof HTMLElement && hasEditable(root)) {
23
+ root.editable.disconnect();
24
+ }
25
+
26
+ root.querySelectorAll("[data-editable]").forEach((element) => {
27
+ if (element instanceof HTMLElement && hasEditable(element)) {
28
+ element.editable.disconnect();
29
+ }
30
+ });
31
+ };
32
+
33
+ export const hydrateDataEditableRegions = (root: Element) => {
34
+ if (
35
+ root instanceof HTMLElement &&
36
+ root.dataset.editable &&
37
+ !("editable" in root)
38
+ ) {
39
+ const Editable = editableMap[root.dataset.editable];
40
+ if (Editable) {
41
+ const editable = new Editable(root);
42
+ editable.connect();
43
+ }
44
+ }
45
+
46
+ root.querySelectorAll("[data-editable]").forEach((element) => {
47
+ if (!(element instanceof HTMLElement) || "editable" in element) {
48
+ return;
49
+ }
50
+
51
+ if (!element.dataset.editable || element.dataset.cloudcannonIgnore) {
52
+ return;
53
+ }
54
+
55
+ const Editable = editableMap[element.dataset.editable];
56
+ if (!Editable) {
57
+ const error = document.createElement("editable-region-error-card");
58
+ error.setAttribute("heading", "Failed to render editable region");
59
+ error.setAttribute(
60
+ "message",
61
+ `Unrecognized editable type: "${element.dataset.editable}". The supported types are: ${Object.keys(editableMap).join(", ")}`,
62
+ );
63
+ element.replaceWith(error);
64
+ return;
65
+ }
66
+
67
+ const editable = new Editable(element);
68
+
69
+ editable.connect();
70
+ });
71
+ };
@@ -0,0 +1,110 @@
1
+ import { dirname, join } from "node:path";
2
+
3
+ const SUPPORTED_VIRTUAL_MODULES = [
4
+ "actions",
5
+ "assets",
6
+ "content",
7
+ "i18n",
8
+ "middleware",
9
+ "transitions",
10
+ ];
11
+
12
+ /**
13
+ * @return {import("astro").AstroIntegration}
14
+ */
15
+ export default () => {
16
+ /** @type {import("astro").AstroConfig} */
17
+ let astroConfig;
18
+
19
+ return {
20
+ name: "editable-regions",
21
+ hooks: {
22
+ "astro:config:setup": ({ config, updateConfig }) => {
23
+ updateConfig({
24
+ vite: {
25
+ define: {
26
+ ENV_CLIENT: false,
27
+ },
28
+ },
29
+ });
30
+ astroConfig = config;
31
+ },
32
+ "astro:build:setup": async ({ target, vite }) => {
33
+ if (target === "client") {
34
+ vite.define ??= {};
35
+ vite.define.ENV_CLIENT = true;
36
+
37
+ vite.plugins?.unshift({
38
+ name: "vite-plugin-editable-regions",
39
+ enforce: "pre",
40
+
41
+ resolveId(id) {
42
+ if (id.startsWith("astro:")) {
43
+ const type = id
44
+ .replace("astro:", "")
45
+ .replace("/client", "")
46
+ .replace("/server", "");
47
+
48
+ if (type === "env") {
49
+ return "\0editable-region:env";
50
+ }
51
+
52
+ if (!SUPPORTED_VIRTUAL_MODULES.includes(type)) {
53
+ return;
54
+ }
55
+
56
+ let dir = "";
57
+ if (typeof __dirname !== "undefined") {
58
+ dir = __dirname;
59
+ } else {
60
+ dir = dirname(import.meta.url);
61
+ }
62
+
63
+ const path = join(dir, "modules", `${type}.js`).replace(
64
+ "file:",
65
+ "",
66
+ );
67
+
68
+ return path;
69
+ }
70
+ },
71
+
72
+ load(id) {
73
+ if (id === "\0editable-region:env") {
74
+ let contents = "";
75
+ Object.entries(astroConfig?.env?.schema ?? {}).forEach(
76
+ ([key, schema]) => {
77
+ if (
78
+ schema.context !== "client" ||
79
+ schema.access !== "public"
80
+ ) {
81
+ return;
82
+ }
83
+
84
+ try {
85
+ switch (schema.type) {
86
+ case "boolean":
87
+ contents += `export const ${key} = ${!!process.env[key]};\n`;
88
+ break;
89
+ case "number":
90
+ contents += `export const ${key} = ${Number(process.env[key])};\n`;
91
+ break;
92
+ default:
93
+ contents += `export const ${key} = ${JSON.stringify(process.env[key] ?? "")};\n`;
94
+ }
95
+ } catch (e) {
96
+ //Error intentionally ignored
97
+ }
98
+ },
99
+ );
100
+ contents +=
101
+ 'export const getSecret = () => console.log("[CloudCannon] getSecret is not supported in an editable component. Please use an editing fallback instead.");';
102
+ return contents;
103
+ }
104
+ },
105
+ });
106
+ }
107
+ },
108
+ },
109
+ };
110
+ };
@@ -0,0 +1,193 @@
1
+ import {
2
+ renderSlotToString,
3
+ renderToString,
4
+ } from "astro/runtime/server/index.js";
5
+ import { addEditableComponentRenderer } from "../../helpers/cloudcannon";
6
+
7
+ /**
8
+ * Queue of React components waiting to be rendered
9
+ * @type {((node: Element) => void)[]}
10
+ */
11
+ const renderRoots = [];
12
+
13
+ const renderers = [
14
+ {
15
+ name: "dynamic-tags",
16
+ ssr: {
17
+ /**
18
+ * Checks if the component is a string (HTML tag name).
19
+ * @param {any} Component - The component to check
20
+ * @returns {boolean} True if component is a string tag name
21
+ */
22
+ check: (Component) => {
23
+ return typeof Component === "string";
24
+ },
25
+ /**
26
+ * Renders a dynamic HTML tag with props and slots.
27
+ * @param {string} Component - The HTML tag name
28
+ * @param {Record<string, any>} props - Props to render as attributes
29
+ * @param {Record<string, string>} slots - Slot content
30
+ * @returns {Promise<string>} The rendered HTML string
31
+ */
32
+ renderToStaticMarkup: async (Component, props, slots) => {
33
+ const propsString = Object.entries(props)
34
+ .map(([key, value]) => `${key}="${value}"`)
35
+ .join(" ");
36
+ return `<${Component} ${propsString}>${
37
+ slots.default ?? ""
38
+ }</${Component}>`;
39
+ },
40
+ },
41
+ },
42
+ ];
43
+
44
+ /**
45
+ * @param {*} renderer
46
+ */
47
+ export const addFrameworkRenderer = (renderer) => {
48
+ renderers.push(renderer);
49
+ };
50
+
51
+ /**
52
+ * @param {(node: Element) => void} renderFunction
53
+ */
54
+ export const queueForClientSideRender = (renderFunction) => {
55
+ renderRoots.push(renderFunction);
56
+ return renderRoots.length - 1;
57
+ };
58
+
59
+ /**
60
+ * Registers an Astro component with the CloudCannon component system.
61
+ * Creates a wrapper that handles Astro SSR rendering with React hydration support.
62
+ *
63
+ * @param {string} key - Unique identifier for the component
64
+ * @param {unknown} component - The Astro component function to register
65
+ * @returns {void}
66
+ */
67
+ export const registerAstroComponent = (key, component) => {
68
+ /**
69
+ * Wrapper function that renders the Astro component with SSR and client-side hydration.
70
+ *
71
+ * @param {any} props - Props to pass to the Astro component
72
+ * @returns {Promise<HTMLElement>} The rendered component as an HTMLElement
73
+ */
74
+ const wrappedComponent = async (props) => {
75
+ /**
76
+ * Encryption key for Astro server islands
77
+ * @type {CryptoKey | undefined}
78
+ */
79
+ let encryptionKey;
80
+ try {
81
+ encryptionKey = await window.crypto.subtle.generateKey(
82
+ {
83
+ name: "AES-GCM",
84
+ length: 256,
85
+ },
86
+ true,
87
+ ["encrypt", "decrypt"],
88
+ );
89
+ } catch (err) {
90
+ console.warn(
91
+ "[CloudCannon] Could not generate a key for Astro component. This may cause issues with Astro components that use server-islands",
92
+ );
93
+ }
94
+
95
+ const SSRResult = {
96
+ styles: new Set(),
97
+ scripts: new Set(),
98
+ links: new Set(),
99
+ propagation: new Map(),
100
+ propagators: new Map(),
101
+ inlinedScripts: new Map(),
102
+ serverIslandNameMap: { get: () => "EditableRegions" },
103
+ key: encryptionKey,
104
+ base: "/",
105
+ extraHead: [],
106
+ compressHTML: false,
107
+ partial: false,
108
+ shouldInjectCspMetaTags: false,
109
+ componentMetadata: new Map(),
110
+ renderers,
111
+ _metadata: {
112
+ renderers,
113
+ hasHydrationScript: false,
114
+ hasRenderedHead: true,
115
+ hasRenderedServerIslandRuntime: true,
116
+ hasDirectives: new Set(),
117
+ propagators: new Set(),
118
+ rendererSpecificHydrationScripts: new Set(),
119
+ renderedScripts: new Set(),
120
+ extraHead: [],
121
+ extraStyleHashes: [],
122
+ extraScriptHashes: [],
123
+ },
124
+ clientDirectives: new Map([
125
+ ["load", "editable-region-placeholder"],
126
+ ["idle", "editable-region-placeholder"],
127
+ ["visible", "editable-region-placeholder"],
128
+ ["media", "editable-region-placeholder"],
129
+ ]),
130
+ slots: null,
131
+ props,
132
+ resolve: () => "editable-region-placeholder",
133
+ /**
134
+ * @param {*} astroGlobal
135
+ * @param {*} props
136
+ * @param {*} slots
137
+ */
138
+ createAstro(astroGlobal, props, slots) {
139
+ const astroSlots = {
140
+ /**
141
+ * @param {string} name
142
+ * @returns boolean
143
+ */
144
+ has: (name) => {
145
+ if (!slots) return false;
146
+ return Boolean(slots[name]);
147
+ },
148
+ /**
149
+ * @param {string} name
150
+ * @returns string
151
+ */
152
+ render: (name) => {
153
+ return renderSlotToString(SSRResult, slots[name]);
154
+ },
155
+ };
156
+ return {
157
+ __proto__: astroGlobal,
158
+ props,
159
+ slots: astroSlots,
160
+ request: new Request(window.location.href),
161
+ };
162
+ },
163
+ };
164
+ // Render the Astro component to HTML string
165
+ const result = await renderToString(SSRResult, component, props, null);
166
+ const doc = document.implementation.createHTMLDocument();
167
+ doc.body.innerHTML = result;
168
+
169
+ doc.querySelectorAll("[data-editable-region-csr-id]").forEach((node) => {
170
+ const csrId = Number(node.getAttribute("data-editable-region-csr-id"));
171
+ renderRoots[csrId]?.(node);
172
+ });
173
+
174
+ // Clear the React roots queue
175
+ renderRoots.length = 0;
176
+
177
+ doc.querySelectorAll("link, [data-island-id]").forEach((node) => {
178
+ node.remove();
179
+ });
180
+
181
+ doc.querySelectorAll("astro-island").forEach((node) => {
182
+ for (const child of node.children) {
183
+ node.before(child);
184
+ }
185
+ node.remove();
186
+ });
187
+
188
+ return doc.body;
189
+ };
190
+
191
+ // Register the wrapped component in the global registry
192
+ addEditableComponentRenderer(key, wrappedComponent);
193
+ };
@@ -0,0 +1,74 @@
1
+ export const actions = new Proxy(
2
+ {},
3
+ {
4
+ get() {
5
+ console.warn(
6
+ "[CloudCannon] actions is not supported in an editable component. Please use an editing fallback instead.",
7
+ );
8
+ return () => {};
9
+ },
10
+ },
11
+ );
12
+
13
+ export const defineAction = () => {
14
+ console.warn(
15
+ "[CloudCannon] defineAction is not supported in an editable component. Please use an editing fallback instead.",
16
+ );
17
+ return {
18
+ handler: () => {},
19
+ input: null,
20
+ };
21
+ };
22
+
23
+ export const isInputError = () => {
24
+ console.warn(
25
+ "[CloudCannon] isInputError is not supported in an editable component. Please use an editing fallback instead.",
26
+ );
27
+ return false;
28
+ };
29
+
30
+ export const isActionError = () => {
31
+ console.warn(
32
+ "[CloudCannon] isActionError is not supported in an editable component. Please use an editing fallback instead.",
33
+ );
34
+ return false;
35
+ };
36
+
37
+ export class ActionError extends Error {
38
+ /**
39
+ * @param {any} code
40
+ * @param {any} message
41
+ */
42
+ constructor(code, message) {
43
+ super(message);
44
+ console.warn(
45
+ "[CloudCannon] ActionError is not supported in an editable component. Please use an editing fallback instead.",
46
+ );
47
+ this.code = code;
48
+ }
49
+ }
50
+
51
+ export const getActionContext = () => {
52
+ console.warn(
53
+ "[CloudCannon] getActionContext is not supported in an editable component. Please use an editing fallback instead.",
54
+ );
55
+ return {
56
+ action: undefined,
57
+ setActionResult: () => {},
58
+ serializeActionResult: () => ({}),
59
+ };
60
+ };
61
+
62
+ export const deserializeActionResult = () => {
63
+ console.warn(
64
+ "[CloudCannon] deserializeActionResult is not supported in an editable component. Please use an editing fallback instead.",
65
+ );
66
+ return {};
67
+ };
68
+
69
+ export const getActionPath = () => {
70
+ console.warn(
71
+ "[CloudCannon] getActionPath is not supported in an editable component. Please use an editing fallback instead.",
72
+ );
73
+ return "";
74
+ };
@@ -0,0 +1,38 @@
1
+ import ImageInternal from "./image.astro";
2
+ import PictureInternal from "./picture.astro";
3
+
4
+ export const Image = ImageInternal;
5
+ export const Picture = PictureInternal;
6
+
7
+ /**
8
+ * @param {{src: any }} options
9
+ * @returns
10
+ */
11
+ export const getImage = async (options) => {
12
+ const resolvedSrc =
13
+ typeof options.src === "object" && "then" in options.src
14
+ ? ((await options.src).default ?? (await options.src))
15
+ : options.src;
16
+ return {
17
+ rawOptions: {
18
+ src: {
19
+ src: resolvedSrc,
20
+ },
21
+ },
22
+ options: {
23
+ src: {
24
+ src: resolvedSrc,
25
+ },
26
+ },
27
+ src: resolvedSrc,
28
+ srcSet: { values: [] },
29
+ attributes: {},
30
+ };
31
+ };
32
+
33
+ export const inferRemoteSize = async () => {
34
+ console.warn(
35
+ "[CloudCannon] inferRemoteSize is not supported in an editable component. Please use an editing fallback instead.",
36
+ );
37
+ return {};
38
+ };
@@ -0,0 +1,5 @@
1
+ ---
2
+ console.warn(
3
+ "[CloudCannon] view transitions are not supported in an editable component. Please use an editing fallback instead.",
4
+ );
5
+ ---
@@ -0,0 +1,116 @@
1
+ import { CloudCannon } from "../../../helpers/cloudcannon";
2
+
3
+ /**
4
+ * @param {string} collectionKey
5
+ * @param {(value: any) => boolean} [filter]
6
+ * @returns {Promise<Array<any>>}
7
+ */
8
+ export const getCollection = async (collectionKey, filter) => {
9
+ const collection = CloudCannon.collection(collectionKey);
10
+ let files = await collection.items();
11
+ if (files.length === 0) {
12
+ const allFiles = await CloudCannon.files();
13
+ files = allFiles.filter((file) =>
14
+ file.path.startsWith(`/src/content/${collectionKey}/`),
15
+ );
16
+ }
17
+
18
+ const promises = files.map(async (file) => {
19
+ const data = await file.data.get();
20
+ let id = file.path.replace(`/src/content/${collectionKey}/`, "");
21
+ let slug = id.replace(/\.[^.]*$/, "");
22
+ if (!id.match(/\.md(x|oc)?$/)) {
23
+ id = slug;
24
+ }
25
+ if (data && "slug" in data) {
26
+ slug = data.slug;
27
+ }
28
+
29
+ return {
30
+ collection: collectionKey,
31
+ id: id,
32
+ data: data,
33
+ slug: slug,
34
+ body: await file.get(),
35
+ };
36
+ });
37
+
38
+ const result = await Promise.all(promises);
39
+
40
+ return filter ? result.filter(filter) : result;
41
+ };
42
+
43
+ /**
44
+ *
45
+ * @param {string | {collection: string, slug?: string, id?: string}} objOrString
46
+ * @param {string} [maybeString]
47
+ * @returns
48
+ */
49
+ export const getEntry = async (objOrString, maybeString) => {
50
+ if (typeof objOrString === "object") {
51
+ const {
52
+ collection: collectionKey,
53
+ slug: entrySlug,
54
+ id: entryId,
55
+ } = objOrString;
56
+ const collection = await getCollection(collectionKey);
57
+ if (entryId) {
58
+ return collection.find(({ id }) => id === entryId);
59
+ }
60
+ if (entrySlug) {
61
+ return collection.find(({ slug }) => slug === entrySlug);
62
+ }
63
+ return console.warn(
64
+ "[CloudCannon] Failed to load entries, invalid arguments: ",
65
+ [objOrString, maybeString],
66
+ );
67
+ }
68
+
69
+ if (typeof objOrString === "string" && typeof maybeString === "string") {
70
+ const [collectionKey, entryKey] = [objOrString, maybeString];
71
+ const collection = await getCollection(collectionKey);
72
+
73
+ return collection.find(({ id, slug }) => entryKey === (slug ?? id));
74
+ }
75
+
76
+ return console.warn(
77
+ "[CloudCannon] Failed to load entries, invalid arguments: ",
78
+ [objOrString, maybeString],
79
+ );
80
+ };
81
+
82
+ /**
83
+ * @param {{collection: string, slug?: string, id?: string}[]} entries
84
+ * @returns
85
+ */
86
+ export const getEntries = (entries) => {
87
+ return Promise.all(entries.map((entry) => getEntry(entry)));
88
+ };
89
+
90
+ /**
91
+ * @param {string} collection
92
+ * @param {string} slug
93
+ * @returns
94
+ */
95
+ export const getEntryBySlug = (collection, slug) => {
96
+ return getEntry({ collection, slug });
97
+ };
98
+
99
+ /**
100
+ * @param {any} entry
101
+ * @returns
102
+ */
103
+ export const render = async (entry) => ({
104
+ Content: () => entry?.body ?? "Content is not available when live editing",
105
+ headings: [],
106
+ remarkPluginFrontmatter: {},
107
+ });
108
+
109
+ export const defineCollection = () =>
110
+ console.warn(
111
+ "[CloudCannon] defineCollection is not supported in an editable component. Make sure you're not importing your config in a component file by mistake.",
112
+ );
113
+ export const reference = () =>
114
+ console.warn(
115
+ "[CloudCannon] reference is not supported in an editable component. Make sure you're not importing your config in a component file by mistake.",
116
+ );
@@ -0,0 +1,76 @@
1
+ export const getRelativeLocaleUrl = () => {
2
+ console.warn(
3
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
4
+ );
5
+ return "";
6
+ };
7
+
8
+ export const getAbsoluteLocaleUrl = () => {
9
+ console.warn(
10
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
11
+ );
12
+ return "";
13
+ };
14
+
15
+ export const getRelativeLocaleUrlList = () => {
16
+ console.warn(
17
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
18
+ );
19
+ return [];
20
+ };
21
+
22
+ export const getAbsoluteLocaleUrlList = () => {
23
+ console.warn(
24
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
25
+ );
26
+ return [];
27
+ };
28
+
29
+ export const getPathByLocale = () => {
30
+ console.warn(
31
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
32
+ );
33
+ return "";
34
+ };
35
+
36
+ export const getLocaleByPath = () => {
37
+ console.warn(
38
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
39
+ );
40
+ return "";
41
+ };
42
+
43
+ export const redirectToDefaultLocale = () => {
44
+ console.warn(
45
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
46
+ );
47
+ return Promise.resolve(new Response());
48
+ };
49
+
50
+ export const redirectToFallback = () => {
51
+ console.warn(
52
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
53
+ );
54
+ return Promise.resolve(new Response());
55
+ };
56
+
57
+ export const notFound = () => {
58
+ console.warn(
59
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
60
+ );
61
+ return Promise.resolve(new Response());
62
+ };
63
+
64
+ export const middleware = () => {
65
+ console.warn(
66
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
67
+ );
68
+ return () => {};
69
+ };
70
+
71
+ export const requestHasLocale = () => {
72
+ console.warn(
73
+ "[CloudCannon] i18n routing is not supported in an editable component. Please use an editing fallback instead.",
74
+ );
75
+ return false;
76
+ };