@craftware/crafty-sdk 0.2.1 → 0.3.0
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.
- package/README.md +64 -0
- package/dist/attrs.d.ts +33 -0
- package/dist/attrs.d.ts.map +1 -0
- package/dist/attrs.js +52 -0
- package/dist/attrs.js.map +1 -0
- package/dist/cdn.d.ts +5 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +5 -0
- package/dist/cdn.js.map +1 -0
- package/dist/client.d.ts +12 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +13 -8
- package/dist/client.js.map +1 -1
- package/dist/crafty-visual-editing.global.js +4 -0
- package/dist/crafty-visual-editing.global.js.map +7 -0
- package/dist/http.d.ts +7 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +32 -2
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/preview.d.ts +56 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +174 -0
- package/dist/preview.js.map +1 -0
- package/dist/resources/collections.d.ts +2 -2
- package/dist/resources/collections.d.ts.map +1 -1
- package/dist/resources/entries.d.ts +3 -2
- package/dist/resources/entries.d.ts.map +1 -1
- package/dist/resources/entries.js +20 -4
- package/dist/resources/entries.js.map +1 -1
- package/dist/resources/forms.d.ts +2 -2
- package/dist/resources/forms.d.ts.map +1 -1
- package/dist/resources/forms.js +1 -1
- package/dist/resources/forms.js.map +1 -1
- package/dist/resources/mediaCollections.d.ts +2 -2
- package/dist/resources/mediaCollections.d.ts.map +1 -1
- package/dist/resources/mediaFiles.d.ts +2 -2
- package/dist/resources/mediaFiles.d.ts.map +1 -1
- package/dist/resources/pages.d.ts +2 -2
- package/dist/resources/pages.d.ts.map +1 -1
- package/dist/resources/pages.js +4 -0
- package/dist/resources/pages.js.map +1 -1
- package/dist/types/public.d.ts +14 -2
- package/dist/types/public.d.ts.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/visual-editing/bridge.d.ts +35 -0
- package/dist/visual-editing/bridge.d.ts.map +1 -0
- package/dist/visual-editing/bridge.js +385 -0
- package/dist/visual-editing/bridge.js.map +1 -0
- package/dist/visual-editing/index.d.ts +10 -0
- package/dist/visual-editing/index.d.ts.map +1 -0
- package/dist/visual-editing/index.js +10 -0
- package/dist/visual-editing/index.js.map +1 -0
- package/dist/visual-editing/live-preview.d.ts +15 -0
- package/dist/visual-editing/live-preview.d.ts.map +1 -0
- package/dist/visual-editing/live-preview.js +63 -0
- package/dist/visual-editing/live-preview.js.map +1 -0
- package/dist/visual-editing/markers.d.ts +11 -0
- package/dist/visual-editing/markers.d.ts.map +1 -0
- package/dist/visual-editing/markers.js +43 -0
- package/dist/visual-editing/markers.js.map +1 -0
- package/dist/visual-editing/origin.d.ts +10 -0
- package/dist/visual-editing/origin.d.ts.map +1 -0
- package/dist/visual-editing/origin.js +37 -0
- package/dist/visual-editing/origin.js.map +1 -0
- package/dist/visual-editing/overlay.d.ts +13 -0
- package/dist/visual-editing/overlay.d.ts.map +1 -0
- package/dist/visual-editing/overlay.js +107 -0
- package/dist/visual-editing/overlay.js.map +1 -0
- package/dist/visual-editing/protocol.d.ts +79 -0
- package/dist/visual-editing/protocol.d.ts.map +1 -0
- package/dist/visual-editing/protocol.js +150 -0
- package/dist/visual-editing/protocol.js.map +1 -0
- package/package.json +13 -4
- package/skills/crafty-cms/SKILL.md +16 -2
- package/skills/crafty-cms/references/mcp-configuration.md +23 -0
- package/skills/crafty-cms/references/sdk-development.md +13 -1
- package/skills/crafty-cms/references/visual-editor.md +218 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Crafty admin panels: https://crafty.do and https://<tenant>.crafty.do. */
|
|
2
|
+
export const DEFAULT_ADMIN_ORIGINS = [/^https:\/\/([a-z0-9-]+\.)*crafty\.do$/];
|
|
3
|
+
function originOf(value) {
|
|
4
|
+
try {
|
|
5
|
+
return new URL(value).origin;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function isAllowedAdminOrigin(origin, allowlist) {
|
|
12
|
+
if (origin === "" || origin === "null") {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return allowlist.some((allowed) => {
|
|
16
|
+
if (typeof allowed === "string") {
|
|
17
|
+
return originOf(allowed) === origin;
|
|
18
|
+
}
|
|
19
|
+
allowed.lastIndex = 0;
|
|
20
|
+
return allowed.test(origin);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Origin of the embedding admin. `ancestorOrigins` (Chromium, WebKit) cannot be
|
|
25
|
+
* spoofed by the framed page; Firefox falls back to the referrer the admin sent
|
|
26
|
+
* when it set the iframe `src`.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveParentOrigin(win) {
|
|
29
|
+
const ancestors = win.location.ancestorOrigins;
|
|
30
|
+
const first = ancestors && ancestors.length > 0 ? ancestors[0] : undefined;
|
|
31
|
+
if (typeof first === "string" && first !== "") {
|
|
32
|
+
return first;
|
|
33
|
+
}
|
|
34
|
+
const referrer = win.document.referrer;
|
|
35
|
+
return referrer ? originOf(referrer) : null;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=origin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/visual-editing/origin.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,CAAC,MAAM,qBAAqB,GAAmC,CAAC,uCAAuC,CAAC,CAAC;AAE/G,SAAS,QAAQ,CAAC,KAAa;IAC7B,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,SAAyC;IAC5F,IAAI,MAAM,KAAK,EAAE,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;QAChC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO,QAAQ,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC;QACtC,CAAC;QAED,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC;QAEtB,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,eAAgD,CAAC;IAChF,MAAM,KAAK,GAAG,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE3E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAEvC,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const FLASH_CLASS = "crafty-ve-flash";
|
|
2
|
+
export type Overlay = {
|
|
3
|
+
show(element: Element, label: string): void;
|
|
4
|
+
hide(): void;
|
|
5
|
+
/** Re-measures the highlighted element, e.g. after its content changed. */
|
|
6
|
+
reposition(): void;
|
|
7
|
+
flash(elements: Element[], durationMs: number): void;
|
|
8
|
+
ensureAttached(): void;
|
|
9
|
+
contains(node: Node | null): boolean;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
};
|
|
12
|
+
export declare function createOverlay(doc: Document): Overlay;
|
|
13
|
+
//# sourceMappingURL=overlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/visual-editing/overlay.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,WAAW,oBAAoB,CAAC;AAU7C,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,IAAI,IAAI,IAAI,CAAC;IACb,2EAA2E;IAC3E,UAAU,IAAI,IAAI,CAAC;IACnB,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACrD,cAAc,IAAI,IAAI,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAEF,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAiHpD"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const STYLE_ELEMENT_ID = "crafty-visual-editing-style";
|
|
2
|
+
const ACCENT = "#4f46e5";
|
|
3
|
+
export const FLASH_CLASS = "crafty-ve-flash";
|
|
4
|
+
const STYLES = [
|
|
5
|
+
"[data-crafty-field]{cursor:pointer}",
|
|
6
|
+
`.crafty-ve-box{position:fixed;top:0;left:0;pointer-events:none;z-index:2147483647;box-sizing:border-box;border:2px solid ${ACCENT};border-radius:4px;background:rgba(79,70,229,.06)}`,
|
|
7
|
+
".crafty-ve-box[hidden]{display:none}",
|
|
8
|
+
`.crafty-ve-label{position:absolute;left:-2px;bottom:100%;margin-bottom:4px;padding:2px 8px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-radius:4px;background:${ACCENT};color:#fff;font:500 12px/1.5 system-ui,-apple-system,sans-serif}`,
|
|
9
|
+
`.${FLASH_CLASS}{outline:2px dashed ${ACCENT} !important;outline-offset:2px !important}`
|
|
10
|
+
].join("\n");
|
|
11
|
+
export function createOverlay(doc) {
|
|
12
|
+
const win = doc.defaultView;
|
|
13
|
+
const style = doc.createElement("style");
|
|
14
|
+
style.id = STYLE_ELEMENT_ID;
|
|
15
|
+
style.textContent = STYLES;
|
|
16
|
+
const box = doc.createElement("div");
|
|
17
|
+
box.setAttribute("data-crafty-overlay", "");
|
|
18
|
+
box.className = "crafty-ve-box";
|
|
19
|
+
box.hidden = true;
|
|
20
|
+
const label = doc.createElement("span");
|
|
21
|
+
label.className = "crafty-ve-label";
|
|
22
|
+
box.appendChild(label);
|
|
23
|
+
let target = null;
|
|
24
|
+
let flashed = [];
|
|
25
|
+
let flashTimer;
|
|
26
|
+
const Observer = win?.ResizeObserver;
|
|
27
|
+
const place = () => {
|
|
28
|
+
if (target === null) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const rect = target.getBoundingClientRect();
|
|
32
|
+
box.style.top = `${rect.top}px`;
|
|
33
|
+
box.style.left = `${rect.left}px`;
|
|
34
|
+
box.style.width = `${rect.width}px`;
|
|
35
|
+
box.style.height = `${rect.height}px`;
|
|
36
|
+
};
|
|
37
|
+
const resizeObserver = typeof Observer === "function" ? new Observer(() => place()) : null;
|
|
38
|
+
const track = (element) => {
|
|
39
|
+
if (target === element) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (target !== null) {
|
|
43
|
+
resizeObserver?.unobserve(target);
|
|
44
|
+
}
|
|
45
|
+
target = element;
|
|
46
|
+
if (target !== null) {
|
|
47
|
+
resizeObserver?.observe(target);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const clearFlash = () => {
|
|
51
|
+
flashed.forEach((element) => element.classList.remove(FLASH_CLASS));
|
|
52
|
+
flashed = [];
|
|
53
|
+
if (flashTimer !== undefined) {
|
|
54
|
+
win?.clearTimeout(flashTimer);
|
|
55
|
+
flashTimer = undefined;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const ensureAttached = () => {
|
|
59
|
+
if (!style.isConnected) {
|
|
60
|
+
(doc.head ?? doc.documentElement).appendChild(style);
|
|
61
|
+
}
|
|
62
|
+
if (!box.isConnected) {
|
|
63
|
+
(doc.body ?? doc.documentElement).appendChild(box);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
ensureAttached();
|
|
67
|
+
win?.addEventListener("scroll", place, { capture: true, passive: true });
|
|
68
|
+
win?.addEventListener("resize", place, { passive: true });
|
|
69
|
+
return {
|
|
70
|
+
show(element, text) {
|
|
71
|
+
ensureAttached();
|
|
72
|
+
track(element);
|
|
73
|
+
if (label.textContent !== text) {
|
|
74
|
+
label.textContent = text;
|
|
75
|
+
}
|
|
76
|
+
box.hidden = false;
|
|
77
|
+
place();
|
|
78
|
+
},
|
|
79
|
+
hide() {
|
|
80
|
+
track(null);
|
|
81
|
+
box.hidden = true;
|
|
82
|
+
},
|
|
83
|
+
reposition() {
|
|
84
|
+
place();
|
|
85
|
+
},
|
|
86
|
+
flash(elements, durationMs) {
|
|
87
|
+
clearFlash();
|
|
88
|
+
flashed = [...elements];
|
|
89
|
+
flashed.forEach((element) => element.classList.add(FLASH_CLASS));
|
|
90
|
+
flashTimer = win?.setTimeout(clearFlash, durationMs);
|
|
91
|
+
},
|
|
92
|
+
ensureAttached,
|
|
93
|
+
contains(node) {
|
|
94
|
+
return node !== null && (box === node || box.contains(node) || style === node || style.contains(node));
|
|
95
|
+
},
|
|
96
|
+
destroy() {
|
|
97
|
+
clearFlash();
|
|
98
|
+
win?.removeEventListener("scroll", place, { capture: true });
|
|
99
|
+
win?.removeEventListener("resize", place);
|
|
100
|
+
resizeObserver?.disconnect();
|
|
101
|
+
box.remove();
|
|
102
|
+
style.remove();
|
|
103
|
+
target = null;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=overlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../src/visual-editing/overlay.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export declare const PROTOCOL_VERSION = 1;
|
|
2
|
+
export type CraftyTargetType = "block" | "entry";
|
|
3
|
+
export type CraftyFieldRef = {
|
|
4
|
+
type: CraftyTargetType;
|
|
5
|
+
id: number;
|
|
6
|
+
field: string;
|
|
7
|
+
};
|
|
8
|
+
export type CraftyMarkedField = CraftyFieldRef & {
|
|
9
|
+
label?: string;
|
|
10
|
+
};
|
|
11
|
+
export type CraftyShownPage = {
|
|
12
|
+
id: number;
|
|
13
|
+
slug: string;
|
|
14
|
+
};
|
|
15
|
+
export type CraftyShownEntry = {
|
|
16
|
+
id: number;
|
|
17
|
+
collection: string;
|
|
18
|
+
};
|
|
19
|
+
export type ReadyPayload = {
|
|
20
|
+
sdkVersion: string;
|
|
21
|
+
path: string;
|
|
22
|
+
fields: CraftyMarkedField[];
|
|
23
|
+
page: CraftyShownPage | null;
|
|
24
|
+
entries: CraftyShownEntry[];
|
|
25
|
+
};
|
|
26
|
+
type Version = typeof PROTOCOL_VERSION;
|
|
27
|
+
type EmptyPayload = Record<string, never>;
|
|
28
|
+
/** iframe -> admin. `crafty:ready` is a snapshot that may be re-sent; the last one wins. */
|
|
29
|
+
export type OutboundMessage = {
|
|
30
|
+
v: Version;
|
|
31
|
+
type: "crafty:ready";
|
|
32
|
+
payload: ReadyPayload;
|
|
33
|
+
} | {
|
|
34
|
+
v: Version;
|
|
35
|
+
type: "crafty:select";
|
|
36
|
+
payload: CraftyMarkedField;
|
|
37
|
+
} | {
|
|
38
|
+
v: Version;
|
|
39
|
+
type: "crafty:navigate";
|
|
40
|
+
payload: {
|
|
41
|
+
path: string;
|
|
42
|
+
};
|
|
43
|
+
} | {
|
|
44
|
+
v: Version;
|
|
45
|
+
type: "crafty:preview-invalid";
|
|
46
|
+
payload: EmptyPayload;
|
|
47
|
+
};
|
|
48
|
+
/** admin -> iframe. */
|
|
49
|
+
export type InboundMessage = {
|
|
50
|
+
v: Version;
|
|
51
|
+
type: "crafty:token";
|
|
52
|
+
payload: {
|
|
53
|
+
token: string;
|
|
54
|
+
};
|
|
55
|
+
} | {
|
|
56
|
+
v: Version;
|
|
57
|
+
type: "crafty:refresh";
|
|
58
|
+
payload: EmptyPayload;
|
|
59
|
+
} | {
|
|
60
|
+
v: Version;
|
|
61
|
+
type: "crafty:highlight";
|
|
62
|
+
payload: CraftyFieldRef;
|
|
63
|
+
} | {
|
|
64
|
+
v: Version;
|
|
65
|
+
type: "crafty:preview-field";
|
|
66
|
+
payload: CraftyFieldRef & {
|
|
67
|
+
value: string;
|
|
68
|
+
};
|
|
69
|
+
} | {
|
|
70
|
+
v: Version;
|
|
71
|
+
type: "crafty:preview-reset";
|
|
72
|
+
payload: CraftyFieldRef;
|
|
73
|
+
};
|
|
74
|
+
export declare function parseFieldRef(value: unknown): CraftyFieldRef | null;
|
|
75
|
+
export declare function parseMarkedField(value: unknown): CraftyMarkedField | null;
|
|
76
|
+
export declare function parseInboundMessage(data: unknown): InboundMessage | null;
|
|
77
|
+
export declare function parseOutboundMessage(data: unknown): OutboundMessage | null;
|
|
78
|
+
export {};
|
|
79
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/visual-editing/protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,OAAO,CAAC;AACjD,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AACnF,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACpE,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAC3D,MAAM,MAAM,gBAAgB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B,CAAC;AAEF,KAAK,OAAO,GAAG,OAAO,gBAAgB,CAAC;AACvC,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE1C,4FAA4F;AAC5F,MAAM,MAAM,eAAe,GACvB;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC3D;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,GACjE;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAClE;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,wBAAwB,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC;AAE1E,uBAAuB;AACvB,MAAM,MAAM,cAAc,GACtB;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAChE;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC7D;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACjE;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,cAAc,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACzF;IAAE,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,CAAC;AAqB1E,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAgBnE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,GAAG,IAAI,CAWzE;AAyCD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAwCxE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CAoD1E"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export const PROTOCOL_VERSION = 1;
|
|
2
|
+
/** Same limits as the Crafty admin and backend (content_drafts.field / label). */
|
|
3
|
+
const MAX_FIELD_LENGTH = 255;
|
|
4
|
+
const MAX_LABEL_LENGTH = 120;
|
|
5
|
+
const MAX_TOKEN_LENGTH = 4096;
|
|
6
|
+
const MAX_VALUE_LENGTH = 100_000;
|
|
7
|
+
const MAX_PATH_LENGTH = 2048;
|
|
8
|
+
const FIELD_PATTERN = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+|\[\d+\])*$/;
|
|
9
|
+
const TOKEN_PATTERN = /^[\x21-\x7E]+$/;
|
|
10
|
+
const CONTROL_CHARACTERS = /[\u0000-\u001F\u007F]/;
|
|
11
|
+
const PATH_BASE = "https://crafty.invalid";
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function isPositiveInteger(value) {
|
|
16
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
17
|
+
}
|
|
18
|
+
export function parseFieldRef(value) {
|
|
19
|
+
if (!isRecord(value)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const { type, id, field } = value;
|
|
23
|
+
if ((type !== "block" && type !== "entry") || !isPositiveInteger(id) || typeof field !== "string") {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const trimmed = field.trim();
|
|
27
|
+
return trimmed === "" || trimmed.length > MAX_FIELD_LENGTH || !FIELD_PATTERN.test(trimmed)
|
|
28
|
+
? null
|
|
29
|
+
: { type, id, field: trimmed };
|
|
30
|
+
}
|
|
31
|
+
export function parseMarkedField(value) {
|
|
32
|
+
const ref = parseFieldRef(value);
|
|
33
|
+
if (ref === null) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const rawLabel = isRecord(value) ? value.label : undefined;
|
|
37
|
+
const label = typeof rawLabel === "string" ? rawLabel.trim().slice(0, MAX_LABEL_LENGTH).trim() : "";
|
|
38
|
+
return label === "" ? ref : { ...ref, label };
|
|
39
|
+
}
|
|
40
|
+
function isSameOriginPath(value) {
|
|
41
|
+
if (!value.startsWith("/") || value.startsWith("//") || value.startsWith("/\\")) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const url = new URL(value, PATH_BASE);
|
|
46
|
+
// Dot segments can still resolve to a protocol-relative path ("/.//evil.example").
|
|
47
|
+
return url.origin === PATH_BASE && !url.pathname.startsWith("//") && !url.pathname.startsWith("/\\");
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function parsePath(value) {
|
|
54
|
+
if (typeof value !== "string" || value.length > MAX_PATH_LENGTH || CONTROL_CHARACTERS.test(value)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return isSameOriginPath(value) ? value : null;
|
|
58
|
+
}
|
|
59
|
+
function parseShownEntry(value) {
|
|
60
|
+
return isRecord(value) && isPositiveInteger(value.id) && typeof value.collection === "string"
|
|
61
|
+
? { id: value.id, collection: value.collection }
|
|
62
|
+
: null;
|
|
63
|
+
}
|
|
64
|
+
function readEnvelope(data) {
|
|
65
|
+
if (!isRecord(data) || data.v !== PROTOCOL_VERSION || typeof data.type !== "string") {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const payload = data.payload === undefined ? {} : data.payload;
|
|
69
|
+
return isRecord(payload) ? { type: data.type, payload } : null;
|
|
70
|
+
}
|
|
71
|
+
export function parseInboundMessage(data) {
|
|
72
|
+
const message = readEnvelope(data);
|
|
73
|
+
if (message === null) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const { payload } = message;
|
|
77
|
+
switch (message.type) {
|
|
78
|
+
case "crafty:token": {
|
|
79
|
+
const token = typeof payload.token === "string" ? payload.token.trim() : "";
|
|
80
|
+
return token !== "" && token.length <= MAX_TOKEN_LENGTH && TOKEN_PATTERN.test(token)
|
|
81
|
+
? { v: PROTOCOL_VERSION, type: "crafty:token", payload: { token } }
|
|
82
|
+
: null;
|
|
83
|
+
}
|
|
84
|
+
case "crafty:refresh":
|
|
85
|
+
return { v: PROTOCOL_VERSION, type: "crafty:refresh", payload: {} };
|
|
86
|
+
case "crafty:highlight": {
|
|
87
|
+
const ref = parseFieldRef(payload);
|
|
88
|
+
return ref ? { v: PROTOCOL_VERSION, type: "crafty:highlight", payload: ref } : null;
|
|
89
|
+
}
|
|
90
|
+
case "crafty:preview-field": {
|
|
91
|
+
const ref = parseFieldRef(payload);
|
|
92
|
+
const value = payload.value;
|
|
93
|
+
return ref && typeof value === "string" && value.length <= MAX_VALUE_LENGTH
|
|
94
|
+
? { v: PROTOCOL_VERSION, type: "crafty:preview-field", payload: { ...ref, value } }
|
|
95
|
+
: null;
|
|
96
|
+
}
|
|
97
|
+
case "crafty:preview-reset": {
|
|
98
|
+
const ref = parseFieldRef(payload);
|
|
99
|
+
return ref ? { v: PROTOCOL_VERSION, type: "crafty:preview-reset", payload: ref } : null;
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function parseOutboundMessage(data) {
|
|
106
|
+
const message = readEnvelope(data);
|
|
107
|
+
if (message === null) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const { payload } = message;
|
|
111
|
+
switch (message.type) {
|
|
112
|
+
case "crafty:ready": {
|
|
113
|
+
const path = parsePath(payload.path);
|
|
114
|
+
const rawPage = payload.page;
|
|
115
|
+
if (typeof payload.sdkVersion !== "string" || path === null || !Array.isArray(payload.fields) || !Array.isArray(payload.entries)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
v: PROTOCOL_VERSION,
|
|
120
|
+
type: "crafty:ready",
|
|
121
|
+
payload: {
|
|
122
|
+
sdkVersion: payload.sdkVersion,
|
|
123
|
+
path,
|
|
124
|
+
fields: payload.fields
|
|
125
|
+
.map((field) => parseMarkedField(field))
|
|
126
|
+
.filter((field) => field !== null),
|
|
127
|
+
page: isRecord(rawPage) && isPositiveInteger(rawPage.id) && typeof rawPage.slug === "string"
|
|
128
|
+
? { id: rawPage.id, slug: rawPage.slug }
|
|
129
|
+
: null,
|
|
130
|
+
entries: payload.entries
|
|
131
|
+
.map((entry) => parseShownEntry(entry))
|
|
132
|
+
.filter((entry) => entry !== null)
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
case "crafty:select": {
|
|
137
|
+
const field = parseMarkedField(payload);
|
|
138
|
+
return field ? { v: PROTOCOL_VERSION, type: "crafty:select", payload: field } : null;
|
|
139
|
+
}
|
|
140
|
+
case "crafty:navigate": {
|
|
141
|
+
const path = parsePath(payload.path);
|
|
142
|
+
return path ? { v: PROTOCOL_VERSION, type: "crafty:navigate", payload: { path } } : null;
|
|
143
|
+
}
|
|
144
|
+
case "crafty:preview-invalid":
|
|
145
|
+
return { v: PROTOCOL_VERSION, type: "crafty:preview-invalid", payload: {} };
|
|
146
|
+
default:
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/visual-editing/protocol.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craftware/crafty-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
".": {
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"import": "./dist/index.js"
|
|
15
|
-
}
|
|
15
|
+
},
|
|
16
|
+
"./visual-editing": {
|
|
17
|
+
"types": "./dist/visual-editing/index.d.ts",
|
|
18
|
+
"import": "./dist/visual-editing/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
16
21
|
},
|
|
17
22
|
"main": "./dist/index.js",
|
|
18
23
|
"module": "./dist/index.js",
|
|
@@ -25,14 +30,18 @@
|
|
|
25
30
|
"node": ">=20"
|
|
26
31
|
},
|
|
27
32
|
"scripts": {
|
|
28
|
-
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"build": "node scripts/write-version.mjs && tsc -p tsconfig.json && node scripts/build-cdn.mjs",
|
|
29
34
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
35
|
+
"test": "npm run build && vitest run",
|
|
30
36
|
"codegen": "node scripts/codegen.mjs",
|
|
31
37
|
"changeset": "npx -y @changesets/cli@2.29.7",
|
|
32
38
|
"changeset:add": "npm run changeset -- add",
|
|
33
39
|
"changeset:version": "npm run changeset -- version"
|
|
34
40
|
},
|
|
35
41
|
"devDependencies": {
|
|
36
|
-
"
|
|
42
|
+
"esbuild": "^0.28.2",
|
|
43
|
+
"happy-dom": "^20.14.5",
|
|
44
|
+
"typescript": "^5.8.2",
|
|
45
|
+
"vitest": "^4.1.11"
|
|
37
46
|
}
|
|
38
47
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: crafty-cms
|
|
3
|
-
description: "Build on Crafty CMS — configure sites, pages, blocks, collections, entries, forms, and media through the Crafty MCP server, then develop a frontend with the @craftware/crafty-sdk JavaScript SDK. Use when connecting an agent to a Crafty MCP server; calling Crafty MCP tools like list_sites, create_page, publish_form, or upload_media; installing or importing @craftware/crafty-sdk or createCraftyClient; fetching Crafty pages, entries, collections, or media; rendering page blocks; or
|
|
3
|
+
description: "Build on Crafty CMS — configure sites, pages, blocks, collections, entries, forms, and media through the Crafty MCP server, then develop a frontend with the @craftware/crafty-sdk JavaScript SDK. Use when connecting an agent to a Crafty MCP server; calling Crafty MCP tools like list_sites, create_page, publish_form, or upload_media; installing or importing @craftware/crafty-sdk or createCraftyClient; fetching Crafty pages, entries, collections, or media; rendering page blocks; submitting Crafty forms; or preparing a site for the Crafty visual editor (crafty.attrs, enableVisualEditing, preview tokens, configure_visual_editor)."
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
6
|
author: craftware
|
|
@@ -51,7 +51,7 @@ claude mcp add crafty \
|
|
|
51
51
|
--header "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
The server URL is **per-tenant**: `https://<your-tenant-domain>/mcp`. Tokens carry scopes — `crafty:read`, `crafty:write`, `crafty:publish`, and `crafty:submissions` (reading form submissions, which may contain personal data) — and they are **enforced per tool** (a tool without its scope is hidden and errors if called). Request the scopes the agent needs. **Full connection guide for every client (PAT and OAuth), scopes, and the complete
|
|
54
|
+
The server URL is **per-tenant**: `https://<your-tenant-domain>/mcp`. Tokens carry scopes — `crafty:read`, `crafty:write`, `crafty:publish`, and `crafty:submissions` (reading form submissions, which may contain personal data) — and they are **enforced per tool** (a tool without its scope is hidden and errors if called). Request the scopes the agent needs. **Full connection guide for every client (PAT and OAuth), scopes, and the complete tool catalog: read `references/mcp-configuration.md`.**
|
|
55
55
|
|
|
56
56
|
Canonical authoring workflow (exact tool names):
|
|
57
57
|
|
|
@@ -63,6 +63,8 @@ Canonical authoring workflow (exact tool names):
|
|
|
63
63
|
Collections/entries: `list_collections` → `create_entry` (`collection_slug`, `site_id`, `slug`, `data`) → `update_entry` (snapshots a revision).
|
|
64
64
|
Forms: `create_form` (`site_id`, `name`, `slug`, `fields`) → `publish_form` → `list_form_submissions`.
|
|
65
65
|
Media: `upload_media` (`url`, `site_id`, optional `collection_name`).
|
|
66
|
+
Cache: `set_api_cache_status` (`site_id`, `enabled`) to toggle per-site; `flush_api_cache` (`site_id`, optional `resource`) to invalidate cached responses after bulk edits.
|
|
67
|
+
Visual editor: `configure_visual_editor` (`site_id`, optional `environment`, `frontend_url`, `routes`, `render_mode`, `upload_media_collection`) — see `references/visual-editor.md`.
|
|
66
68
|
|
|
67
69
|
> `update_page` **replaces all blocks** when `blocks` is supplied. `update_entry` creates a revision. `create_page` always starts as `draft`.
|
|
68
70
|
|
|
@@ -116,6 +118,17 @@ crafty deploy # uploads dist/ to the configured environment
|
|
|
116
118
|
|
|
117
119
|
> `crafty.json` is safe to commit. `.env` (which contains your API key) must be gitignored — use `.env.example` with placeholder values.
|
|
118
120
|
|
|
121
|
+
## Visual editor (click-to-edit) — always ask
|
|
122
|
+
|
|
123
|
+
Whenever you integrate Crafty into a frontend, ask the user exactly once:
|
|
124
|
+
|
|
125
|
+
> **¿Quieres dejar el sitio preparado para el editor visual de Crafty?**
|
|
126
|
+
|
|
127
|
+
- **Yes** → follow `references/visual-editor.md` completely: mark content with `crafty.attrs()`, deliver the preview token for the site's render mode, call `enableVisualEditing()` (pass `client` for `ssr` sites, so the bridge can confirm the token itself), allow framing (`frame-ancestors`), check images are public, run `configure_visual_editor`, and verify.
|
|
128
|
+
- **No** → add nothing and tell the user it can be enabled later by asking «habilita el editor visual de Crafty».
|
|
129
|
+
|
|
130
|
+
A later request like «habilita el editor visual de Crafty» on an existing site runs the Yes flow without asking again.
|
|
131
|
+
|
|
119
132
|
## Key distinction: MCP auth vs SDK API key
|
|
120
133
|
|
|
121
134
|
These are two different credentials — do not mix them:
|
|
@@ -137,3 +150,4 @@ These are two different credentials — do not mix them:
|
|
|
137
150
|
|
|
138
151
|
- `references/mcp-configuration.md` — connecting every client, scopes, and the full MCP tool catalog with workflows.
|
|
139
152
|
- `references/sdk-development.md` — the complete SDK surface, block helpers, error handling, and examples.
|
|
153
|
+
- `references/visual-editor.md` — preparing a site for the visual editor: the mandatory question, marking, preview tokens, bridge, headers, `configure_visual_editor`, verification.
|
|
@@ -130,6 +130,8 @@ Form submissions may contain personal data, so they require their own scope.
|
|
|
130
130
|
|
|
131
131
|
| Tool | Required | Optional | Notes |
|
|
132
132
|
|---|---|---|---|
|
|
133
|
+
| `flush_api_cache` | `site_id` | `resource` | flushes cached public API responses for the site; `resource` = `pages`, `entries`, `collections`, `media`, or `all` (default `all`) |
|
|
134
|
+
| `set_api_cache_status` | `site_id`, `enabled` | — | enables (`true`) or disables (`false`) the public API response cache for the site; per-site override, preserves other cache config (TTLs, bypass rules) |
|
|
133
135
|
| `create_site` | `name` | `slug` | slug auto-generated from name; returns the site (with a generated `api_token`) |
|
|
134
136
|
| `create_page` | `site_id`, `title` | `slug`, `blocks` | starts as `draft`; `blocks: [{ block_type_id, data }]` created in order |
|
|
135
137
|
| `update_page` | `page_id` | `title`, `slug`, `blocks` | **if `blocks` is supplied, ALL existing blocks are replaced** |
|
|
@@ -138,6 +140,7 @@ Form submissions may contain personal data, so they require their own scope.
|
|
|
138
140
|
| `upload_media` | `url`, `site_id` | `collection_name` (default `library`), `file_name` | downloads the file from the URL |
|
|
139
141
|
| `create_form` | `site_id`, `name`, `slug` | `description`, `fields` | `fields: [{ key, type, label, required? }]`; starts as `draft` |
|
|
140
142
|
| `update_form` | `form_id` | `name`, `slug`, `description`, `fields`, `notification_emails` | only supplied fields change; `fields`/`notification_emails` replace |
|
|
143
|
+
| `configure_visual_editor` | `site_id` | `environment` (default `production`), `frontend_url`, `routes` (`page_pattern`, `home_slug`, `collections`, `fixed`), `render_mode` (`ssr`/`hybrid`/`static`/`unknown`), `upload_media_collection` | visual editor settings of one environment; only sent keys change, `null` resets to the fallback; with only `site_id` it just reads; returns the effective settings |
|
|
141
144
|
|
|
142
145
|
### Publish (`crafty:publish`)
|
|
143
146
|
|
|
@@ -174,6 +177,26 @@ Form submissions may contain personal data, so they require their own scope.
|
|
|
174
177
|
1. `upload_media` with `url` and `site_id` (optionally `collection_name`).
|
|
175
178
|
2. `list_media` to confirm and retrieve URLs.
|
|
176
179
|
|
|
180
|
+
**Visual editor**
|
|
181
|
+
|
|
182
|
+
1. Prepare the frontend (see `visual-editor.md`).
|
|
183
|
+
2. `configure_visual_editor` with `site_id`, `frontend_url`, `routes`, and `render_mode`.
|
|
184
|
+
3. Ask the user to open **Editar sitio** in the admin to confirm click-to-edit works.
|
|
185
|
+
|
|
186
|
+
**API response cache management**
|
|
187
|
+
|
|
188
|
+
The public API has a tag-based response cache (enabled per environment). Per-site overrides take priority over the global setting.
|
|
189
|
+
|
|
190
|
+
- Disable cache for a site before bulk edits, then re-enable when done:
|
|
191
|
+
1. `set_api_cache_status` with `site_id` and `enabled: false`.
|
|
192
|
+
2. Make all your content changes (`update_entry`, `update_page`, etc.).
|
|
193
|
+
3. `set_api_cache_status` with `enabled: true`.
|
|
194
|
+
4. `flush_api_cache` with `site_id` (and optionally `resource`) to clear stale entries.
|
|
195
|
+
- Flush only a specific resource type after targeted changes:
|
|
196
|
+
- `flush_api_cache` with `site_id` and `resource: "entries"` after updating entries.
|
|
197
|
+
- `flush_api_cache` with `site_id` and `resource: "pages"` after updating pages.
|
|
198
|
+
- `flush_api_cache` with no `resource` (or `resource: "all"`) clears all cached responses for the site at once.
|
|
199
|
+
|
|
177
200
|
## Troubleshooting
|
|
178
201
|
|
|
179
202
|
- **401 / authentication error** — the Bearer token is missing, invalid, or expired; reconnect or regenerate it. Every `/mcp` request must carry a valid token.
|
|
@@ -23,7 +23,7 @@ const crafty = createCraftyClient({
|
|
|
23
23
|
});
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
`createCraftyClient(config)` returns a plain object with six resources
|
|
26
|
+
`createCraftyClient(config)` returns a plain object with six resources (`collections`, `entries`, `pages`, `forms`, `mediaCollections`, `mediaFiles`) and the visual editor helpers `attrs`, `attrsHtml` and `previewFromRequest`.
|
|
27
27
|
|
|
28
28
|
### Config options
|
|
29
29
|
|
|
@@ -35,6 +35,8 @@ const crafty = createCraftyClient({
|
|
|
35
35
|
| `fetch` | no | Custom fetch. Defaults to `globalThis.fetch`. If none is available, construction throws `CraftyError` (`NETWORK_ERROR`). |
|
|
36
36
|
| `headers` | no | Default headers merged into every request. |
|
|
37
37
|
| `timeoutMs` | no | Default per-request timeout. A timeout is only armed when the value is a number `> 0`. |
|
|
38
|
+
| `preview` | no | Visual editor preview token sent as `X-Crafty-Preview`. Omit in the browser (read from `?crafty_preview=`, kept in `sessionStorage`); pass `previewFromRequest(req)` on the server; `false` never sends one. |
|
|
39
|
+
| `onPreviewInvalid` | no | Called when a request that carried a token gets `X-Crafty-Preview-Status: invalid` (the response is the public content). |
|
|
38
40
|
|
|
39
41
|
## Per-call options
|
|
40
42
|
|
|
@@ -191,6 +193,16 @@ try {
|
|
|
191
193
|
}
|
|
192
194
|
```
|
|
193
195
|
|
|
196
|
+
## Visual editor helpers
|
|
197
|
+
|
|
198
|
+
- `attrs(target, field, { label? })` / `crafty.attrs(...)` → `{ "data-crafty-block" | "data-crafty-entry", "data-crafty-field", "data-crafty-label"? }` to spread on the element that renders the value; `{}` when the target has no valid id. `field` is a field key or a JSON path (`sections[3].title`).
|
|
199
|
+
- `attrsHtml(target, field, { label? })` → the same attributes as an escaped HTML string.
|
|
200
|
+
- `previewFromRequest(req)` → preview token of an incoming request (URL string, `URL`, Web `Request`, Node `req.url`, Express `req.query`) or `null`.
|
|
201
|
+
- `readPreviewToken()` → token in the browser, or `null`.
|
|
202
|
+
- `enableVisualEditing(options)` from `@craftware/crafty-sdk/visual-editing` → connects the page to the editor; no-op outside the admin iframe. Options: `onRefresh`, `adminOrigins`, `page`, `entries`, `touchHighlightMs`.
|
|
203
|
+
|
|
204
|
+
Full integration checklist: `visual-editor.md`.
|
|
205
|
+
|
|
194
206
|
## Notes
|
|
195
207
|
|
|
196
208
|
- `Accept: application/json` is set automatically unless overridden.
|