@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.
Files changed (84) hide show
  1. package/README.md +64 -0
  2. package/dist/attrs.d.ts +33 -0
  3. package/dist/attrs.d.ts.map +1 -0
  4. package/dist/attrs.js +52 -0
  5. package/dist/attrs.js.map +1 -0
  6. package/dist/cdn.d.ts +5 -0
  7. package/dist/cdn.d.ts.map +1 -0
  8. package/dist/cdn.js +5 -0
  9. package/dist/cdn.js.map +1 -0
  10. package/dist/client.d.ts +12 -7
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +13 -8
  13. package/dist/client.js.map +1 -1
  14. package/dist/crafty-visual-editing.global.js +4 -0
  15. package/dist/crafty-visual-editing.global.js.map +7 -0
  16. package/dist/http.d.ts +7 -1
  17. package/dist/http.d.ts.map +1 -1
  18. package/dist/http.js +32 -2
  19. package/dist/http.js.map +1 -1
  20. package/dist/index.d.ts +10 -6
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +4 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/preview.d.ts +56 -0
  25. package/dist/preview.d.ts.map +1 -0
  26. package/dist/preview.js +174 -0
  27. package/dist/preview.js.map +1 -0
  28. package/dist/resources/collections.d.ts +2 -2
  29. package/dist/resources/collections.d.ts.map +1 -1
  30. package/dist/resources/entries.d.ts +3 -2
  31. package/dist/resources/entries.d.ts.map +1 -1
  32. package/dist/resources/entries.js +20 -4
  33. package/dist/resources/entries.js.map +1 -1
  34. package/dist/resources/forms.d.ts +2 -2
  35. package/dist/resources/forms.d.ts.map +1 -1
  36. package/dist/resources/forms.js +1 -1
  37. package/dist/resources/forms.js.map +1 -1
  38. package/dist/resources/mediaCollections.d.ts +2 -2
  39. package/dist/resources/mediaCollections.d.ts.map +1 -1
  40. package/dist/resources/mediaFiles.d.ts +2 -2
  41. package/dist/resources/mediaFiles.d.ts.map +1 -1
  42. package/dist/resources/pages.d.ts +2 -2
  43. package/dist/resources/pages.d.ts.map +1 -1
  44. package/dist/resources/pages.js +4 -0
  45. package/dist/resources/pages.js.map +1 -1
  46. package/dist/types/public.d.ts +14 -2
  47. package/dist/types/public.d.ts.map +1 -1
  48. package/dist/version.d.ts +2 -0
  49. package/dist/version.d.ts.map +1 -0
  50. package/dist/version.js +3 -0
  51. package/dist/version.js.map +1 -0
  52. package/dist/visual-editing/bridge.d.ts +35 -0
  53. package/dist/visual-editing/bridge.d.ts.map +1 -0
  54. package/dist/visual-editing/bridge.js +385 -0
  55. package/dist/visual-editing/bridge.js.map +1 -0
  56. package/dist/visual-editing/index.d.ts +10 -0
  57. package/dist/visual-editing/index.d.ts.map +1 -0
  58. package/dist/visual-editing/index.js +10 -0
  59. package/dist/visual-editing/index.js.map +1 -0
  60. package/dist/visual-editing/live-preview.d.ts +15 -0
  61. package/dist/visual-editing/live-preview.d.ts.map +1 -0
  62. package/dist/visual-editing/live-preview.js +63 -0
  63. package/dist/visual-editing/live-preview.js.map +1 -0
  64. package/dist/visual-editing/markers.d.ts +11 -0
  65. package/dist/visual-editing/markers.d.ts.map +1 -0
  66. package/dist/visual-editing/markers.js +43 -0
  67. package/dist/visual-editing/markers.js.map +1 -0
  68. package/dist/visual-editing/origin.d.ts +10 -0
  69. package/dist/visual-editing/origin.d.ts.map +1 -0
  70. package/dist/visual-editing/origin.js +37 -0
  71. package/dist/visual-editing/origin.js.map +1 -0
  72. package/dist/visual-editing/overlay.d.ts +13 -0
  73. package/dist/visual-editing/overlay.d.ts.map +1 -0
  74. package/dist/visual-editing/overlay.js +107 -0
  75. package/dist/visual-editing/overlay.js.map +1 -0
  76. package/dist/visual-editing/protocol.d.ts +79 -0
  77. package/dist/visual-editing/protocol.d.ts.map +1 -0
  78. package/dist/visual-editing/protocol.js +150 -0
  79. package/dist/visual-editing/protocol.js.map +1 -0
  80. package/package.json +13 -4
  81. package/skills/crafty-cms/SKILL.md +16 -2
  82. package/skills/crafty-cms/references/mcp-configuration.md +23 -0
  83. package/skills/crafty-cms/references/sdk-development.md +13 -1
  84. 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.2.1",
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
- "typescript": "^5.8.2"
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 submitting Crafty forms."
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 25-tool catalog: read `references/mcp-configuration.md`.**
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: `collections`, `entries`, `pages`, `forms`, `mediaCollections`, `mediaFiles`.
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.