first commit
This commit is contained in:
108
packages/core/src/visual-editing/editable.ts
Normal file
108
packages/core/src/visual-editing/editable.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Visual editing annotation system
|
||||
*
|
||||
* Creates Proxy objects that emit data-emdash-ref attributes when spread onto elements.
|
||||
*/
|
||||
|
||||
export interface CMSAnnotation {
|
||||
collection: string;
|
||||
id: string;
|
||||
field?: string;
|
||||
/** Entry status — only present on entry-level annotations (not field-level) */
|
||||
status?: string;
|
||||
/** Whether the entry has unpublished draft changes */
|
||||
hasDraft?: boolean;
|
||||
}
|
||||
|
||||
/** The shape returned when spreading an edit annotation onto an element */
|
||||
export interface FieldAnnotation {
|
||||
"data-emdash-ref": string;
|
||||
}
|
||||
|
||||
export interface EditableOptions {
|
||||
/** Entry status: "draft", "published", "scheduled" */
|
||||
status?: string;
|
||||
/** true when draftRevisionId exists and differs from liveRevisionId */
|
||||
hasDraft?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an editable proxy for an entry.
|
||||
*
|
||||
* Usage:
|
||||
* - `{...entry.edit}` - entry-level annotation (includes status/hasDraft)
|
||||
* - `{...entry.edit.title}` - field-level annotation
|
||||
* - `{...entry.edit['nested.field']}` - nested field (bracket notation)
|
||||
*/
|
||||
export function createEditable(
|
||||
collection: string,
|
||||
id: string,
|
||||
options?: EditableOptions,
|
||||
): EditProxy {
|
||||
const base: CMSAnnotation = {
|
||||
collection,
|
||||
id,
|
||||
...(options?.status && { status: options.status }),
|
||||
...(options?.hasDraft && { hasDraft: true }),
|
||||
};
|
||||
|
||||
return new Proxy({} as EditProxy, {
|
||||
get(_, prop) {
|
||||
if (prop === "toJSON") return () => ({ "data-emdash-ref": JSON.stringify(base) });
|
||||
if (typeof prop === "symbol") return undefined;
|
||||
|
||||
// data-emdash-ref access returns the entry-level string
|
||||
if (prop === "data-emdash-ref") return JSON.stringify(base);
|
||||
|
||||
// Field-level: return a FieldAnnotation for the specific field
|
||||
return {
|
||||
"data-emdash-ref": JSON.stringify({ ...base, field: String(prop) }),
|
||||
} satisfies FieldAnnotation;
|
||||
},
|
||||
ownKeys() {
|
||||
return ["data-emdash-ref"];
|
||||
},
|
||||
getOwnPropertyDescriptor(_, prop) {
|
||||
if (prop === "data-emdash-ref") {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: JSON.stringify(base),
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a noop proxy for production mode.
|
||||
* Spreading this produces no attributes.
|
||||
*/
|
||||
export function createNoop(): EditProxy {
|
||||
return new Proxy({} as EditProxy, {
|
||||
get(_, prop) {
|
||||
if (typeof prop === "symbol") return undefined;
|
||||
// All property access returns undefined in noop mode
|
||||
return undefined;
|
||||
},
|
||||
ownKeys() {
|
||||
return [];
|
||||
},
|
||||
getOwnPropertyDescriptor() {
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Visual editing proxy type.
|
||||
*
|
||||
* Spread directly onto elements for entry-level annotations: `{...entry.edit}`
|
||||
* Access a field for field-level annotations: `{...entry.edit.title}`
|
||||
*
|
||||
* In production, spreading produces no attributes (noop).
|
||||
*/
|
||||
export type EditProxy = {
|
||||
readonly [field: string]: Partial<FieldAnnotation>;
|
||||
};
|
||||
1229
packages/core/src/visual-editing/toolbar.ts
Normal file
1229
packages/core/src/visual-editing/toolbar.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user